Utvikling¶
🏗️ Development Workflow¶
1. Start Dev Server¶
npm run dev
- Åpner http://localhost:3000 automatisk
- Hot module reload (HMR) for React komponenter
- TypeScript errors vises i browser + terminal
2. Development Branching¶
git checkout -b feature/my-feature
# ... gjør endringer ...
git add .
git commit -m "feat: description"
git push origin feature/my-feature
# Create Pull Request on GitHub
Branch naming:
- feature/ - Nye features
- fix/ - Bugfikser
- docs/ - Dokumentasjonsoppdateringer
- refactor/ - Kodeomstrukturering
3. Code Quality¶
# Type checking (no runtime)
npx tsc --noEmit
# Full build
npm run build
# Preview production build
npm run preview
📐 Project Structure¶
Root Level¶
GKIT-Meeting-Suite/
├── admin/ # Admin dashboard components
├── client/ # Delegate/member client
├── components/ # Shared React components
├── hooks/ # Custom React hooks
├── services/ # Firebase & external services
├── views/ # View pages
├── types.ts # TypeScript type definitions
├── App.tsx # Main app router
├── index.tsx # Entry point
├── vite.config.ts # Build config
└── tsconfig.json # TypeScript config
admin/¶
admin/
├── AdminApp.tsx # Main admin interface (role-based)
├── ConductorView.tsx # Dirigent (§ behandling)
└── SecretaryView.tsx # Sekretær (protokoll)
services/¶
services/
├── firebaseConfig.ts # Firebase SDK initialization
└── geminiService.ts # Gemini API integration (WIP)
hooks/¶
hooks/
└── useFirestore.ts # Real-time Firestore subscriptions
- useCollection() # Subscribe to collection
- useDocument() # Subscribe to single document
🔄 Real-time Data Binding¶
Custom Hooks Pattern¶
// Subscribe to agenda collection (all documents)
const { data: agenda, loading, error } = useCollection('agenda');
// Subscribe to single document
const { data: activeItem, exists } = useDocument('agenda', itemId);
// With Firestore constraints (e.g., ordering, filtering)
const { data: speakers, loading } = useCollection(
'speakers',
where('agendaItemId', '==', itemId),
orderBy('timestamp', 'asc')
);
Hook benefits: - ✅ Auto-unsubscribe on unmount - ✅ Loading + error states - ✅ Real-time updates - ✅ Null-safe
Component Usage Example¶
export const ConductorView = ({ agenda, activeItem, speakers, meetingState }) => {
// activeItem, speakers osv oppdateres automatisk fra Firestore
return (
<div>
<h1>§{activeItem?.number}: {activeItem?.title}</h1>
<div>Talere: {speakers.length}</div>
</div>
);
};
🎨 Component Development¶
Creating a New Component¶
-
Type Definition (types.ts)
export interface MyComponentProps { title: string; onAction: () => void; } -
Component (components/MyComponent.tsx)
import React from 'react'; import type { MyComponentProps } from '../types'; export const MyComponent: React.FC<MyComponentProps> = ({ title, onAction }) => { return ( <div className="bg-slate-800 p-4 rounded-xl"> <h2 className="text-xl font-bold text-white">{title}</h2> <button onClick={onAction} className="mt-4 px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded" > Handling </button> </div> ); }; -
Use in App
<MyComponent title="Min Komponent" onAction={() => console.log('Klikket!')} />
🎯 Styling with Tailwind¶
Utility Classes¶
// Button
<button className="px-4 py-2 bg-emerald-600 hover:bg-emerald-500 text-white rounded-lg">
Click
</button>
// Card
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700 shadow-lg">
Content
</div>
// Grid Layout
<div className="grid grid-cols-2 gap-6">
<div>Left</div>
<div>Right</div>
</div>
// Responsive
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
Cards...
</div>
Fargeskala: indigo, emerald, amber, red, slate
🔥 Firebase Integration¶
Read Data (useCollection)¶
const AdminApp = () => {
const { data: members, loading } = useCollection('members');
if (loading) return <div>Laster...</div>;
return (
<div>
{members.map(member => (
<div key={member.id}>{member.name}</div>
))}
</div>
);
};
Write Data (Cloud Functions)¶
import { httpsCallable } from 'firebase/functions';
import { functions } from '../services/firebaseConfig';
const importMembers = httpsCallable(functions, 'processMemberUpload');
const handleUpload = async (csvFile: File) => {
try {
const result = await importMembers({ fileName: csvFile.name });
console.log('Imported:', result.data);
} catch (error) {
console.error('Upload failed:', error);
}
};
🧪 Testing¶
Manual Testing Checklist¶
[ ] Admin login (Golf ID + PIN)
[ ] Load agenda from Firestore
[ ] Add speaker to queue
[ ] Start voting
[ ] Vote as delegate
[ ] View vote count
[ ] Download meeting report
Component Testing (Future)¶
// Test file structure (not yet implemented)
__tests__/
├── components/
│ └── ConductorView.test.tsx
├── hooks/
│ └── useFirestore.test.ts
└── services/
└── firebaseConfig.test.ts
Would use: Jest + React Testing Library
🐛 Debugging¶
Browser DevTools¶
npm run dev
# F12 or Ctrl+Shift+I in browser
- React DevTools extension (Chrome/Firefox)
- Network tab for API calls
- Console for errors
Firebase Emulator UI¶
firebase emulators:start
# Visit http://localhost:4000
Browse Firestore data in real-time
TypeScript Type Checking¶
npx tsc --noEmit
# Check types without compiling
📚 Coding Standards¶
File Naming¶
- Components:
PascalCase.tsx(e.g.,ConductorView.tsx) - Services:
camelCase.ts(e.g.,firebaseConfig.ts) - Types: Inline in
types.ts
Component Format¶
import React from 'react';
import type { PropType } from '../types';
interface ComponentProps {
prop1: string;
prop2: number;
}
export const MyComponent = ({ prop1, prop2 }: ComponentProps) => {
return <div>JSX</div>;
};
export default MyComponent;
Import Order¶
// 1. React
import React from 'react';
// 2. Third-party
import { collection } from 'firebase/firestore';
// 3. Internal
import type { AgendaItem } from '../types';
import { useCollection } from '../hooks/useFirestore';
🚀 Performance Tips¶
- Use useCollection hooks instead of direct Firestore calls
- Lazy load views with React.lazy()
- Memoize expensive computations with useMemo
- Avoid unnecessary re-renders with useCallback
- Bundle analysis:
npm run build -- --analyze
Next Steps¶
- Se API-referanse for hook-dokumentasjon
- Se Deploying for GitHub Actions
- Se Arkitektur for datamodell