Skip to content

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

  1. Type Definition (types.ts)

    export interface MyComponentProps {
      title: string;
      onAction: () => void;
    }
    

  2. 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>
      );
    };
    

  3. 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

  1. Use useCollection hooks instead of direct Firestore calls
  2. Lazy load views with React.lazy()
  3. Memoize expensive computations with useMemo
  4. Avoid unnecessary re-renders with useCallback
  5. Bundle analysis:
    npm run build -- --analyze
    

Next Steps