Skip to content

Architecture & Design Documentation

System Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Firebase Hosting                          β”‚
β”‚                   (europe-west3 region)                      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚   User App (/)      β”‚      β”‚   Admin App (/admin/)   β”‚   β”‚
β”‚  β”‚                     β”‚      β”‚                         β”‚   β”‚
β”‚  β”‚ β€’ React 19.2.0      β”‚      β”‚ β€’ React 19.2.0          β”‚   β”‚
β”‚  β”‚ β€’ Vite 7.2.6        β”‚      β”‚ β€’ Vite 7.2.6            β”‚   β”‚
β”‚  β”‚ β€’ TypeScript        β”‚      β”‚ β€’ TypeScript            β”‚   β”‚
β”‚  β”‚                     β”‚      β”‚ β€’ React Router          β”‚   β”‚
β”‚  β”‚ Features:           β”‚      β”‚                         β”‚   β”‚
β”‚  β”‚ - Booking calendar  β”‚      β”‚ Features:               β”‚   β”‚
β”‚  β”‚ - 10-min slots      β”‚      β”‚ - Bookings list         β”‚   β”‚
β”‚  β”‚ - Real-time grid    β”‚      β”‚ - Cancellations         β”‚   β”‚
β”‚  β”‚ - Form validation   β”‚      β”‚ - Statistics            β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚         β”‚                                 β”‚                   β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                       β”‚ (Firebase SDK)                        β”‚
β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                        β”‚
β”‚         β”‚  Firebase Auth / Init      β”‚                        β”‚
β”‚         β”‚  Project: golfbilkontroll- β”‚                        β”‚
β”‚         β”‚  skigk                     β”‚                        β”‚
β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚   Firebase Backend Services        β”‚
        β”‚      (europe-west3 region)         β”‚
        β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
        β”‚                                    β”‚
        β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
        β”‚  β”‚  Firestore Database          β”‚  β”‚
        β”‚  β”‚                              β”‚  β”‚
        β”‚  β”‚  Collections:                β”‚  β”‚
        β”‚  β”‚  β€’ carts                     β”‚  β”‚
        β”‚  β”‚  β€’ rentals                   β”‚  β”‚
        β”‚  β”‚                              β”‚  β”‚
        β”‚  β”‚  Features:                   β”‚  β”‚
        β”‚  β”‚  - Real-time listeners       β”‚  β”‚
        β”‚  β”‚  - Automatic indexing        β”‚  β”‚
        β”‚  β”‚  - Security rules            β”‚  β”‚
        β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
        β”‚                                    β”‚
        β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
        β”‚  β”‚  Cloud Functions (Gen 2)     β”‚  β”‚
        β”‚  β”‚  Node.js 20                  β”‚  β”‚
        β”‚  β”‚                              β”‚  β”‚
        β”‚  β”‚  Functions:                  β”‚  β”‚
        β”‚  β”‚  β€’ checkAvailability()       β”‚  β”‚
        β”‚  β”‚  β€’ createRental()            β”‚  β”‚
        β”‚  β”‚                              β”‚  β”‚
        β”‚  β”‚  HTTP endpoints:             β”‚  β”‚
        β”‚  β”‚  β€’ /checkAvailability        β”‚  β”‚
        β”‚  β”‚  β€’ /createRental             β”‚  β”‚
        β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
        β”‚                                    β”‚
        β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
        β”‚  β”‚  Firestore Security Rules    β”‚  β”‚
        β”‚  β”‚                              β”‚  β”‚
        β”‚  β”‚  β€’ Rentals: Public read      β”‚  β”‚
        β”‚  β”‚  β€’ Carts: Public read        β”‚  β”‚
        β”‚  β”‚  β€’ Admin: Write access       β”‚  β”‚
        β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
        β”‚                                    β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Frontend Architecture

User App Component Hierarchy

UserApp.tsx (Root Component)
β”œβ”€β”€ State Management:
β”‚   β”œβ”€β”€ selectedDate: string
β”‚   β”œβ”€β”€ carts: GolfCart[]
β”‚   β”œβ”€β”€ selectedCart: GolfCart | null
β”‚   └── bookingStep: 'select' | 'form'
β”‚
β”œβ”€β”€ Lifecycle:
β”‚   └── useEffect: Load carts on mount
β”‚
β”œβ”€β”€ Handlers:
β”‚   β”œβ”€β”€ handleCartSelect(cart)
β”‚   └── handleBooking(bookingData)
β”‚
└── Render:
    β”œβ”€β”€ Step 1: Select Cart
    β”‚   β”œβ”€β”€ Calendar
    β”‚   β”‚   └── Props: selectedDate, onDateChange
    β”‚   └── AvailabilityGrid
    β”‚       β”œβ”€β”€ Props: selectedDate, carts, onCartSelect
    β”‚       β”œβ”€β”€ State: cellStatuses (Map)
    β”‚       β”œβ”€β”€ Firestore Query: Get all rentals
    β”‚       └── Logic: Check conflicts with chargingEndTime
    β”‚
    └── Step 2: Fill Form
        └── BookingForm
            β”œβ”€β”€ Props: cart, selectedDate, onSubmit, onCancel
            β”œβ”€β”€ State: formData, playerIdError
            β”œβ”€β”€ Functions:
            β”‚   β”œβ”€β”€ calculateEndTimes(holes, startTime)
            β”‚   β”œβ”€β”€ validatePlayerId(id)
            β”‚   └── handleChange(e)
            └── Validation:
                β”œβ”€β”€ Player ID format: 073-1234567
                β”œβ”€β”€ Required fields
                └── Auto-calculate duration

Admin App Page Structure

AdminLayout (Wrapper)
β”œβ”€β”€ Header: Logo, notifications, user menu
β”œβ”€β”€ Sidebar: Navigation with icons
β”œβ”€β”€ Main Content: Outlet
└── Mobile Bottom Nav

Routes:
β”œβ”€β”€ / β†’ DashboardHome
β”œβ”€β”€ /booking β†’ BookingPage
β”œβ”€β”€ /bookings β†’ BookingsListPage
β”‚   β”œβ”€β”€ Features:
β”‚   β”‚   β”œβ”€β”€ Filter by date range (all/today/upcoming/past)
β”‚   β”‚   β”œβ”€β”€ Cancel upcoming rentals
β”‚   β”‚   β”œβ”€β”€ Modal for cancellation reason
β”‚   β”‚   └── Display cancellation status
β”‚   └── Firestore: Query rentals with status filter
β”œβ”€β”€ /carts β†’ CartsPage
β”œβ”€β”€ /reports β†’ ReportsPage
β”‚   β”œβ”€β”€ /revenue β†’ RevenueReportPage
β”‚   β”œβ”€β”€ /analytics β†’ BookingAnalyticsPage
β”‚   β”œβ”€β”€ /performance β†’ CartPerformancePage
β”‚   └── /statistics β†’ RentalStatisticsPage ← NEW
└── ...

RentalStatisticsPage:
β”œβ”€β”€ Tabs:
β”‚   β”œβ”€β”€ Overview
β”‚   β”‚   β”œβ”€β”€ Metric cards (totals, avg)
β”‚   β”‚   └── Top 5 players table
β”‚   β”œβ”€β”€ Players
β”‚   β”‚   └── Complete player list with aggregates
β”‚   └── Carts
β”‚       └── Cart utilization with progress bars
└── Firestore: Query all rentals, aggregate by playerId

Data Flow

Booking Creation Flow

1. User selects date + cart
   └─> AvailabilityGrid checks conflicts via Firestore

2. User fills booking form
   β”œβ”€> Real-time validation (player ID format)
   β”œβ”€> Auto-calculate endTime (base duration)
   └─> Auto-calculate chargingEndTime (+charging period)

3. User submits form
   └─> UserApp.handleBooking()

4. Save to Firestore
   └─> addDoc(rentals, {
       cartId, renterName, playerId, holes,
       startTime, endTime, chargingEndTime,
       phone, email, notes, price,
       status: 'confirmed', createdAt
   })

5. Real-time sync
   β”œβ”€> AvailabilityGrid updates via onSnapshot()
   β”œβ”€> BookingsListPage updates via onSnapshot()
   └─> User sees confirmation

Time Flow:
10:00 startTime
β”œβ”€ 10:00-14:20: Play period (4h 20m for 18 holes)
└─ 14:20-15:10: Charging period (50m for 18 holes)
   = 15:10 chargingEndTime
   = Next slot available: 15:20 (10-min grid)

Availability Check Logic

// For each cart + time slot
const slotTime = new Date(`${selectedDate}T${slot.time}:00`);

rentals.forEach(rental => {
  if (rental.cartId !== cartId) return;
  if (rental.status === 'cancelled') return;

  const rentalStart = new Date(rental.startTime);
  const rentalEnd = new Date(rental.chargingEndTime);  // ← includes charging

  // Slot is booked if it falls within rental duration
  if (slotTime >= rentalStart && slotTime < rentalEnd) {
    setStatus(slot, 'booked');
  }
});

Cancellation Flow

1. Admin clicks "Avslut" button on upcoming rental
   └─> Open modal for cancellation reason

2. Admin enters reason and confirms
   └─> BookingsListPage.handleCancelRental()

3. Update Firestore rental document
   └─> updateDoc(rentalRef, {
       status: 'cancelled',
       cancelledAt: Timestamp.now(),
       cancellationReason: 'text'
   })

4. Real-time sync
   β”œβ”€> BookingsListPage re-renders (status filter)
   β”œβ”€> AvailabilityGrid re-queries rentals
   β”‚   └─> Cancelled rentals excluded from conflict check
   └─> StatisticsPage updates cancellation counters

Database Schema

Firestore Collections

Collection: carts

Document ID: auto-generated or numeric
{
  id: number,              // 1, 2, 3, 4, 5
  name: string,            // "BlΓ₯ 4", "BlΓ₯ 5", "GrΓΈnn", "Hvit", "Svart"
  status: string           // "available", "rented", "out_of_order"
}

Indexes:
- None required (small collection, full scan acceptable)

Collection: rentals

Document ID: auto-generated UUID
{
  cartId: number,                    // 1-5 (references cart)
  renterName: string,                // "John Doe"
  playerId: string,                  // "073-1234567" (format: XXX-XXXXXXX)
  holes: number,                     // 9 or 18
  startTime: Timestamp,              // 2025-12-04T10:00:00Z
  endTime: Timestamp,                // 2025-12-04T14:20:00Z (play duration)
  chargingEndTime: Timestamp,        // 2025-12-04T15:10:00Z (play + charging)
  phone: string,                     // "98765432"
  email: string,                     // "user@example.com"
  notes: string,                     // Optional comments
  price: number,                     // 450 or 250
  status: string,                    // "confirmed" or "cancelled"
  createdAt: Timestamp,              // When booked
  cancelledAt: Timestamp,            // When cancelled (optional)
  cancellationReason: string         // Why cancelled (optional)
}

Indexes:
- startTime (ascending)
- cartId + startTime (composite)
- status + startTime (composite)
- playerId + startTime (composite)

Security Architecture

Firestore Security Rules

// Public reads for availability checks
match /carts/{document=**} {
  allow read: if true;  // Everyone can see carts
}

match /rentals/{document=**} {
  allow read: if true;  // Everyone can see bookings
  allow create: if request.auth != null;  // Authenticated users can book
  allow update: if request.auth.token.admin == true;  // Only admin can modify
}

Authentication Flow (Future)

Current: Open access
Future: 
β”œβ”€β”€ User sign-up with email/password
β”œβ”€β”€ Admin authentication for panel
└── Role-based access control (RBAC)

Performance Considerations

Firestore Query Optimization

  1. Availability Check (AvailabilityGrid)
  2. Query: GET /rentals WHERE cartId == X
  3. Optimization: Composite index on (cartId, startTime)
  4. Load: ~5 carts Γ— 54 slots = 270 operations/refresh

  5. Statistics Aggregation (RentalStatisticsPage)

  6. Query: GET /rentals (all rentals)
  7. Optimization: In-memory aggregation by playerId
  8. Load: Single query, then JavaScript map/reduce

  9. Bookings List (BookingsListPage)

  10. Query: GET /rentals ORDER BY startTime DESC
  11. Optimization: Index on startTime
  12. Filtering: Client-side after fetch

Caching Strategy

  • Client-side: React component state + Firestore SDK caching
  • Real-time: onSnapshot listeners for live updates
  • No server cache: Firestore handles real-time sync

Bundle Size

  • User App: 535.81 KB JS (gzip: 167.34 KB)
  • Admin App: 1,025.89 KB JS (gzip: 308.19 KB)
  • Total: ~1.5 MB JS (gzipped: ~475 KB)

Deployment Architecture

GitHub Repository (Master Branch)
         ↓
Git Push Trigger
         ↓
Local Build
β”œβ”€β”€ npm run build:all
β”œβ”€β”€β”€ npm run build (User app)
β”œβ”€β”€β”€ cd admin && npm run build (Admin app)
└─── Copy admin/dist to public/admin
         ↓
Firebase Deploy
β”œβ”€β”€ firebase deploy --only hosting
β”œβ”€β”€β”€ Upload public/ to Firebase Hosting
β”œβ”€β”€β”€ firebase deploy --only functions
└─── Deploy Cloud Functions to europe-west3
         ↓
Firebase Hosting (europe-west3)
β”œβ”€β”€ Public CDN distribution
β”œβ”€β”€ Index.html β†’ User app at /
β”œβ”€β”€ public/admin/ β†’ Admin app at /admin/
└── Automatic HTTPS + caching

Live URLs:
- User: https://GolfChart-MultiClub.web.app
- Admin: https://GolfChart-MultiClub.web.app/admin/

Error Handling

Frontend Error Cases

  1. Cart Availability Check Fails
  2. Show: "Laster tilgjengelighet..." spinner
  3. Fallback: Display empty grid with retry button

  4. Booking Submission Fails

  5. Show: Alert with error message
  6. Log: Console error for debugging
  7. Action: User can retry

  8. Firestore Connection Lost

  9. Behavior: onSnapshot listener reconnects automatically
  10. Timeout: After 60s, show offline indicator

Admin Error Cases

  1. Cancellation Fails
  2. Show: Alert "βœ— Feil ved avslutting av booking"
  3. Log: Console error
  4. Action: User can retry

  5. Statistics Load Fails

  6. Show: Skeleton loaders
  7. Fallback: Empty state "Ingen spillerdata"

Monitoring & Logging

  • Project: https://console.firebase.google.com/project/golfbilkontroll-skigk
  • Firestore: .../firestore/databases
  • Functions logs: .../functions/logs
  • Hosting: .../hosting/realtime

Local Development Logging

# Real-time function logs
firebase functions:log

# Emulator logs
firebase emulators:start --debug

Unit Tests

  • Component rendering
  • Validation functions (player ID, dates)
  • Calculation functions (duration, charging)

Integration Tests

  • Firestore real-time sync
  • Booking creation flow
  • Cancellation flow

E2E Tests

  • User booking journey
  • Admin cancellation
  • Statistics accuracy

Future Scalability

Horizontal Scaling

  1. Multiple Functions Regions
  2. Current: europe-west3 only
  3. Future: Add europe-west1, us-central1 for global latency

  4. Database Sharding

  5. Current: Single rentals collection
  6. Future: Shard by date range if > 100K records/month

  7. Cloud Storage

  8. For booking confirmations (PDF generation)
  9. For export files (CSV, Excel)

Performance Improvements

  1. Caching Layer
  2. Redis cache for frequently accessed queries
  3. CDN caching for static assets

  4. Database Optimization

  5. Add more composite indexes
  6. Archive old records (> 1 year) to separate collection

  7. Frontend Optimization

  8. Code splitting by route
  9. Lazy loading of admin pages
  10. Service Worker for offline capability

Last Updated: December 4, 2025