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¶
- Availability Check (AvailabilityGrid)
- Query:
GET /rentals WHERE cartId == X - Optimization: Composite index on (cartId, startTime)
-
Load: ~5 carts Γ 54 slots = 270 operations/refresh
-
Statistics Aggregation (RentalStatisticsPage)
- Query:
GET /rentals(all rentals) - Optimization: In-memory aggregation by playerId
-
Load: Single query, then JavaScript map/reduce
-
Bookings List (BookingsListPage)
- Query:
GET /rentals ORDER BY startTime DESC - Optimization: Index on startTime
- 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¶
- Cart Availability Check Fails
- Show: "Laster tilgjengelighet..." spinner
-
Fallback: Display empty grid with retry button
-
Booking Submission Fails
- Show: Alert with error message
- Log: Console error for debugging
-
Action: User can retry
-
Firestore Connection Lost
- Behavior: onSnapshot listener reconnects automatically
- Timeout: After 60s, show offline indicator
Admin Error Cases¶
- Cancellation Fails
- Show: Alert "β Feil ved avslutting av booking"
- Log: Console error
-
Action: User can retry
-
Statistics Load Fails
- Show: Skeleton loaders
- Fallback: Empty state "Ingen spillerdata"
Monitoring & Logging¶
Firebase Console Links¶
- 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
Testing Strategy (Recommended)¶
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¶
- Multiple Functions Regions
- Current: europe-west3 only
-
Future: Add europe-west1, us-central1 for global latency
-
Database Sharding
- Current: Single rentals collection
-
Future: Shard by date range if > 100K records/month
-
Cloud Storage
- For booking confirmations (PDF generation)
- For export files (CSV, Excel)
Performance Improvements¶
- Caching Layer
- Redis cache for frequently accessed queries
-
CDN caching for static assets
-
Database Optimization
- Add more composite indexes
-
Archive old records (> 1 year) to separate collection
-
Frontend Optimization
- Code splitting by route
- Lazy loading of admin pages
- Service Worker for offline capability
Last Updated: December 4, 2025