# Frontend-Dokumentation ## Übersicht Das Frontend ist eine **Progressive Web App (PWA)** gebaut mit React 18, Vite und React Router. Die Architektur folgt einem **Component-based Pattern** mit Context-basiertem State Management (kein Redux). **Technologien:** - React 18 (ohne TypeScript) - Vite (Build Tool + Dev Server) - React Router v6 (Client-side Routing) - Recharts (Chart-Bibliothek) - Lucide React (Icon Library) - Day.js (Datum-Handling) **Bundle-Größe:** ~450 KB (gzip), PWA-Cache für Offline-Nutzung --- ## Seiten-Übersicht | Seite | Route | Beschreibung | Auth | Admin | |-------|-------|--------------|------|-------| | **LoginScreen** | `/` (ohne Auth) | E-Mail + Passwort Login, SHA256→bcrypt Auto-Migration | ❌ | ❌ | | **Register** | `/register` | Selbst-Registrierung + E-Mail-Verifizierung | ❌ | ❌ | | **Verify** | `/verify?token=...` | E-Mail-Verifizierung nach Registrierung | ❌ | ❌ | | **SetupScreen** | `/` (First Run) | Initiales Setup (erster Admin-Account) | ❌ | ❌ | | **Dashboard** | `/` | Übersicht: Quick Weight, Stats, Charts, Widgets | ✅ | ❌ | | **CaptureHub** | `/capture` | Quick-Entry-Auswahl (Gewicht/Umfänge/Caliper/Fotos/Aktivität/Schlaf) | ✅ | ❌ | | **WeightScreen** | `/weight` | Gewichts-Tracking mit Inline-Edit | ✅ | ❌ | | **CircumScreen** | `/circum` | Umfänge (8 Punkte) | ✅ | ❌ | | **CaliperScreen** | `/caliper` | Hautfaltenmessungen (4 Methoden) | ✅ | ❌ | | **MeasureWizard** | `/wizard` | Geführte Messung (Schritt-für-Schritt) | ✅ | ❌ | | **ActivityPage** | `/activity` | Training + Trainingstypen (v9d) | ✅ | ❌ | | **NutritionPage** | `/nutrition` | 3-Tab Layout: Entry / Import / Charts | ✅ | ❌ | | **SleepPage** | `/sleep` | Schlaf-Tracking + Phasen + Apple Health Import | ✅ | ❌ | | **RestDaysPage** | `/rest-days` | Ruhetage (Kraft/Cardio/Entspannung) | ✅ | ❌ | | **VitalsPage** | `/vitals` | 3-Tab: Baseline / Blutdruck / Import | ✅ | ❌ | | **History** | `/history` | Verlauf mit Charts (Gewicht, KF%, Umfänge, etc.) | ✅ | ❌ | | **Analysis** | `/analysis` | KI-Auswertung + Pipeline | ✅ | ❌ | | **SettingsPage** | `/settings` | Profil, PIN-Change, Export, Feature-Usage-Übersicht | ✅ | ❌ | | **SubscriptionPage** | `/subscription` | Membership-Status (v9c) | ✅ | ❌ | | **GuidePage** | `/guide` | Anleitungen (Caliper, Umfänge) | ✅ | ❌ | | **AdminPanel** | `/admin/*` (in Settings) | Admin-Übersicht | ✅ | ✅ | | **AdminTierLimitsPage** | `/admin/tier-limits` | Tier-Limits Matrix (v9c) | ✅ | ✅ | | **AdminFeaturesPage** | `/admin/features` | Feature-Verwaltung (v9c) | ✅ | ✅ | | **AdminTiersPage** | `/admin/tiers` | Tier-Verwaltung (v9c) | ✅ | ✅ | | **AdminCouponsPage** | `/admin/coupons` | Coupon-System (v9c) | ✅ | ✅ | | **AdminUserRestrictionsPage** | `/admin/user-restrictions` | User-spezifische Limits (v9c) | ✅ | ✅ | | **AdminTrainingTypesPage** | `/admin/training-types` | Trainingstypen-CRUD (v9d) | ✅ | ✅ | | **AdminActivityMappingsPage** | `/admin/activity-mappings` | Activity Mapping-Verwaltung (v9d) | ✅ | ✅ | | **AdminTrainingProfiles** | `/admin/training-profiles` | Training Type Profiling (v9d #15) | ✅ | ✅ | **Gesamt:** 31 Seiten (22 User-facing, 9 Admin) --- ## Komponenten ### Wiederverwendbare Komponenten | Komponente | Props | Beschreibung | |-----------|-------|--------------| | **Avatar** | `profile, size` | Runder Avatar mit Initialen + Farbe | | **Markdown** | `text` | Lightweight Markdown-Renderer (## Headings, **bold**, Listen) | | **TrialBanner** | – | Trial-Countdown-Banner (3 Urgency-Level) | | **EmailVerificationBanner** | – | E-Mail-Verifizierungs-Hinweis | | **FeatureUsageOverview** | – | Tabelle mit allen Feature-Limits + Usage (v9c Phase 3) | | **UsageBadge** | `feature` | Inline-Badge mit Limit-Status (z.B. "3/10") | | **TrainingTypeDistribution** | `days` | Pie-Chart für Trainingstypen-Verteilung | | **SleepWidget** | `days` | Dashboard-Widget mit Schlaf-Stats | | **RestDaysWidget** | `weeks` | Dashboard-Widget mit aktuellen Ruhetagen | **Location:** `frontend/src/components/` ### Inline-Komponenten (in Seiten definiert) **Dashboard.jsx:** - `QuickWeight` – Schnelle Gewichts-Eingabe mit Feature-Limit-Check - `StatCard` – Statistik-Karte mit Delta-Anzeige - `Pill` – Status-Pill mit Tooltip (WHR, WHtR, KF, Protein Ø7T) **SettingsPage.jsx:** - `ProfileForm` – Formular für Profil-Bearbeitung **NutritionPage.jsx:** - `EntryTab` – Manuelle Eingabe + CSV-Import - `ImportHistoryTab` – Import-Historie mit Gruppierung - `ChartsTab` – Korrelationen + Wochendaten **VitalsPage.jsx:** - `BaselineTab` – Morgenmessungen (RHR, HRV, VO2 Max, SpO2) - `BloodPressureTab` – Blutdruck mehrfach täglich + Context-Tagging - `ImportTab` – CSV-Import (Omron Deutsch, Apple Health) --- ## Context / State Management ### 1. AuthContext (`frontend/src/context/AuthContext.jsx`) **Verantwortlichkeit:** Session-Management + Login/Logout **State:** ```javascript { session: { token: string, profile_id: string, role: 'user' | 'admin', profile: { id, name, email, tier, ... } }, loading: boolean, needsSetup: boolean, // First-run detection } ``` **Methods:** - `login(credentials)` – Login mit E-Mail + Passwort (oder Legacy profile_id + PIN) - `setup(formData)` – Initial Setup (First Run) - `logout()` – Logout + Token-Löschung - `setAuthFromToken(token, profile)` – Direkt-Login (für E-Mail-Verifizierung) - `checkStatus()` – Auth-Status prüfen (beim App-Start) **Computed:** - `isAdmin` – `session.role === 'admin'` - `canUseAI` – `session.profile.ai_enabled !== 0` - `canExport` – `session.profile.export_enabled !== 0` **Storage:** - `localStorage.bodytrack_token` – Auth-Token - `localStorage.bodytrack_active_profile` – Aktive Profile-ID **Flow:** ``` App-Start → checkStatus() ↓ GET /api/auth/status → {needs_setup: true/false} ↓ (wenn needs_setup = false) GET /api/auth/me (mit Token aus localStorage) ↓ Session gesetzt → App.jsx zeigt Dashboard ``` ### 2. ProfileContext (`frontend/src/context/ProfileContext.jsx`) **Verantwortlichkeit:** Aktives Profil + Profil-Liste **State:** ```javascript { profiles: Array, // Alle Profile activeProfile: Profile, // Aktuelles Profil loading: boolean, } ``` **Methods:** - `setActiveProfile(profile)` – Profil wechseln (speichert in localStorage) - `refreshProfiles()` – Profile neu laden (nach Update) **Flow:** ``` session.profile_id ändert sich ↓ GET /api/profiles (mit X-Auth-Token) ↓ profiles gesetzt, activeProfile = match(session.profile_id) ``` **Hinweis:** Profile-Wechsel ist derzeit Single-User-optimiert (Multi-User-Support in Planung). --- ## API-Integration (`frontend/src/utils/api.js`) **Zweck:** Zentrale API-Schnittstelle – **ALLE** API-Calls gehen über `api.js` **Features:** - Automatisches Token-Injection (`X-Auth-Token` Header) - Automatisches Profile-ID-Injection (`X-Profile-Id` Header, derzeit deprecated) - Einheitliche Fehlerbehandlung (parst `{detail: "..."}` aus Backend) - Typed-like API (alle Methoden dokumentiert) **Beispiel:** ```javascript import { api } from '../utils/api' // GET-Request const weights = await api.listWeight(365) // limit=365 // POST-Request await api.upsertWeight('2026-03-23', 75.5, 'Morgens nüchtern') // DELETE-Request await api.deleteWeight(entryId) // File-Upload const result = await api.importCsv(file) ``` **Headers-Injection:** ```javascript function hdrs(extra={}) { const h = {...extra} if (_profileId) h['X-Profile-Id'] = _profileId // Deprecated, bleibt für Legacy const token = getToken() if (token) h['X-Auth-Token'] = token return h } ``` **Error-Handling:** ```javascript if (!res.ok) { const err = await res.text() try { const parsed = JSON.parse(err) throw new Error(parsed.detail || err) } catch { throw new Error(err) } } ``` **API-Methoden (285 Zeilen):** - **Profiles:** `getActiveProfile, listProfiles, createProfile, updateProfile, deleteProfile` - **Weight:** `listWeight, upsertWeight, updateWeight, deleteWeight, weightStats` - **Circumferences:** `listCirc, upsertCirc, updateCirc, deleteCirc` - **Caliper:** `listCaliper, upsertCaliper, updateCaliper, deleteCaliper` - **Activity:** `listActivity, createActivity, updateActivity, deleteActivity, activityStats, bulkCategorizeActivities, importActivityCsv` - **Nutrition:** `importCsv, listNutrition, nutritionCorrelations, nutritionWeekly, nutritionImportHistory, createNutrition, updateNutrition, deleteNutrition` - **Photos:** `uploadPhoto, listPhotos, photoUrl` - **AI:** `insightTrend, listPrompts, runInsight, insightPipeline, listInsights, latestInsights` - **Export:** `exportZip, exportJson, exportCsv` (Download-Handling inkludiert) - **Admin:** `adminListProfiles, adminCreateProfile, adminDeleteProfile, adminSetPermissions, changePin` - **Auth:** `register, verifyEmail, resendVerification` - **Subscription (v9c):** `getMySubscription, getMyUsage, getMyLimits, redeemCoupon, getFeatureUsage` - **Admin Features (v9c):** `listFeatures, createFeature, updateFeature, deleteFeature` - **Admin Tiers (v9c):** `listTiers, createTier, updateTier, deleteTier, getTierLimitsMatrix, updateTierLimit, updateTierLimitsBatch` - **Admin User Restrictions (v9c):** `listUserRestrictions, createUserRestriction, updateUserRestriction, deleteUserRestriction` - **Admin Coupons (v9c):** `listCoupons, createCoupon, updateCoupon, deleteCoupon, getCouponRedemptions` - **Admin Access Grants (v9c):** `listAccessGrants, createAccessGrant, updateAccessGrant, revokeAccessGrant` - **Training Types (v9d):** `listTrainingTypes, listTrainingTypesFlat, getTrainingCategories, adminListTrainingTypes, adminCreateTrainingType, adminUpdateTrainingType, adminDeleteTrainingType, getAbilitiesTaxonomy` - **Training Profiles (v9d #15):** `getProfileStats, getProfileTemplates, getProfileTemplate, applyProfileTemplate, getTrainingParameters, batchEvaluateActivities` - **Activity Mappings (v9d):** `adminListActivityMappings, adminCreateActivityMapping, adminUpdateActivityMapping, adminDeleteActivityMapping, adminGetMappingCoverage` - **Sleep (v9d):** `listSleep, getSleepByDate, createSleep, updateSleep, deleteSleep, getSleepStats, getSleepDebt, getSleepTrend, getSleepPhases, importAppleHealthSleep` - **Rest Days (v9d):** `listRestDays, createRestDay, getRestDay, updateRestDay, deleteRestDay, getRestDaysStats, validateActivity` - **Vitals Baseline (v9d):** `listBaseline, getBaselineByDate, createBaseline, updateBaseline, deleteBaseline, getBaselineStats, importBaselineAppleHealth` - **Blood Pressure (v9d):** `listBloodPressure, getBPByDate, createBloodPressure, updateBloodPressure, deleteBloodPressure, getBPStats, importBPOmron` --- ## Berechnungs-Utils ### 1. calc.js (`frontend/src/utils/calc.js`) **Zweck:** Körperfett-Berechnungen + Derived Metrics **Funktionen:** **`calcBodyFat(method, skinfolds, sex, age)`** - Berechnet Körperfett-% nach 4 Methoden: - `jackson3` – Jackson-Pollock 3-Punkt (Standard) - `jackson7` – Jackson-Pollock 7-Punkt - `durnin` – Durnin-Womersley 4-Punkt - `parrillo` – Parrillo 9-Punkt (linear) - Nutzt Siri-Formel: `BF% = (495 / D) - 450` - Parameter: - `method`: String ('jackson3', 'jackson7', 'durnin', 'parrillo') - `skinfolds`: Object mit Hautfalten in mm (z.B. `{chest: 12, abdomen: 24, thigh: 18}`) - `sex`: 'm' | 'f' - `age`: Number **`getBfCategory(pct, sex)`** - Kategorisiert Körperfett-% in Bereiche: - Männer: Essenziell (<6%), Athletisch (6-14%), Fit (14-18%), Durchschnitt (18-25%), Übergewicht (>25%) - Frauen: Essenziell (<14%), Athletisch (14-21%), Fit (21-25%), Durchschnitt (25-32%), Übergewicht (>32%) - Returns: `{max, label, color, desc}` **`calcDerived(measurement, height)`** - Berechnet abgeleitete Metriken: - **WHR** (Waist-Hip-Ratio): `waist / hip` (Ziel: <0.90 M / <0.85 F) - **WHtR** (Waist-to-Height-Ratio): `waist / height` (Ziel: <0.50) - **FFMI** (Fat-Free Mass Index): `lean_mass / (height_m²)` (Natural Limit: ~25 M / ~22 F) - Returns: `{whr, whtr, ffmi}` **`getRuleBasedAssessment(current, previous, profile)`** - Generiert automatische Interpretationen basierend auf: - Körperfett-Kategorie - Änderungen seit letzter Messung - FFMI (Muskel-Index) - WHR / WHtR (Fettverteilung) - Taillenumfang (WHO-Grenzwerte) - Returns: `{findings: Array, summary: string, summaryType: 'good'|'warn'|'bad'}` **Guide-Daten:** - `CIRCUMFERENCE_GUIDE` – Messanleitung für 8 Umfangspunkte (wo, wie, Posture, Tipps) - `CALIPER_GUIDE` – Messanleitung für Hautfalten-Punkte ### 2. interpret.js (`frontend/src/utils/interpret.js`) **Zweck:** Interpretation von Messwerten **`getInterpretation(measurement, profile, prevMeasurement)`** - Analysiert Messung und generiert strukturierte Interpretation: - Körperfett-Status (mit Kategorie + Farbe) - WHR-Status - WHtR-Status - FFMI-Status - BMI-Status - Vergleich zur letzten Messung (Deltas) - Returns: Array von Interpretations-Objects: ```javascript { category: 'Körperfett', icon: '🫧', status: 'good' | 'warn' | 'bad', title: 'Athletischer Körperfettanteil', detail: 'Ausgezeichnet. Typisch für aktive Sportler...', value: '12.5%', badge: 'Athletisch', color: '#1D9E75', } ``` **`getStatusColor(status)`** – Farbe für Status ('good'→Grün, 'warn'→Orange, 'bad'→Rot) **`getStatusBg(status)`** – Background-Farbe für Status ### 3. Markdown.jsx (`frontend/src/utils/Markdown.jsx`) **Zweck:** Leichtgewichtiger Markdown-Renderer für KI-Texte **Unterstützte Syntax:** - `# Heading 1`, `## Heading 2`, `### Heading 3` - `**bold**`, `*italic*` - `- Bullet List`, `1. Numbered List` - `---` (Horizontal Rule) - Line Breaks **Verwendung:** ```jsx ``` **Vorteil:** Kein remark/rehype-Dependency – nur 134 Zeilen pures React --- ## CSS-System (`frontend/src/app.css`) ### CSS-Variablen (Light + Dark Mode) **Farben:** ```css :root { --bg: #f4f3ef; /* Hintergrund */ --surface: #ffffff; /* Cards */ --surface2: #f9f8f5; /* Inputs, Secondary */ --border: rgba(0,0,0,0.09); /* Standard-Border */ --border2: rgba(0,0,0,0.16);/* Input-Border */ --text1: #1c1b18; /* Primär-Text */ --text2: #5a5955; /* Sekundär-Text */ --text3: #9a9892; /* Muted */ --accent: #1D9E75; /* Primär-Farbe */ --accent-light: #E1F5EE; /* Accent-Background */ --accent-dark: #0a5c43; /* Hover */ --danger: #D85A30; /* Fehler/Löschen */ --warn: #EF9F27; /* Warnung */ } @media (prefers-color-scheme: dark) { :root { --bg: #181816; --surface: #222220; --surface2: #1e1e1c; --border: rgba(255,255,255,0.08); --text1: #eeecea; --text2: #aaa9a4; --text3: #686762; --accent-light: #04342C; --accent-dark: #5DCAA5; } } ``` **Layout:** ```css --nav-h: 64px; /* Bottom Navigation Höhe */ --header-h: 52px; /* App-Header Höhe */ ``` ### Utility Classes **Cards:** ```css .card /* Standard-Card (white background, border, rounded) */ .card-title /* Card-Überschrift (uppercase, small, muted) */ ``` **Stats:** ```css .stats-grid /* 2-Column Grid für Stats */ .stat-card /* Einzelne Stat-Card */ .stat-val /* Wert (groß, bold) */ .stat-label /* Label (klein, muted) */ .stat-delta /* Delta (z.B. "+2.5 kg") */ .delta-pos /* Positive Änderung (grün) */ .delta-neg /* Negative Änderung (rot) */ ``` **Forms:** ```css .form-section /* Formular-Sektion mit Abstand */ .form-section-title /* Sektions-Titel (uppercase, border-bottom) */ .form-row /* Zeile mit Label + Input + Unit */ .form-label /* Label (links, flex:1) */ .form-input /* Input (90px breit, text-align:right) */ .form-unit /* Einheit (z.B. "kg", 24px breit) */ .form-select /* Select-Dropdown */ .form-sub /* Sub-Label (klein, muted) */ ``` **Buttons:** ```css .btn /* Base Button */ .btn-primary /* Primär-Button (accent) */ .btn-secondary /* Sekundär-Button (grau) */ .btn-danger /* Löschen-Button (rot) */ .btn-full /* Full-Width Button */ ``` **Tabs:** ```css .tabs /* Tab-Container (segmented control) */ .tab /* Einzelner Tab */ .tab.active /* Aktiver Tab (white background, shadow) */ ``` **Misc:** ```css .badge /* Inline-Badge (klein, rounded) */ .spinner /* Loading-Spinner (CSS-Animation) */ .empty-state /* Leerer Zustand (zentriert, muted) */ .muted /* Muted-Text (text3) */ ``` ### Responsive Design **Mobile-First Approach:** - Standard-Layout für 375px–600px (Mobile) - Max-Width: 600px (zentriert auf Desktop) - Bottom-Navigation für Mobile (64px hoch) - Touch-optimierte Button-Größen (min 44px) **Bottom Navigation:** ```css .bottom-nav { position: fixed; bottom: 0; left: 50%; transform: translateX(-50%); width: 100%; max-width: 600px; height: var(--nav-h); z-index: 20; } ``` **Safe Area (iPhone):** ```css padding-bottom: env(safe-area-inset-bottom, 0); /* Notch-Handling */ ``` **Desktop-Optimierung:** - App zentriert mit max-width: 600px - Kein responsives Layout für >600px (bewusst Mobile-optimiert) --- ## PWA-Konfiguration ### Service Worker **Location:** `frontend/public/service-worker.js` **Cache-Strategie:** - **Static Assets:** Cache-First (HTML, CSS, JS, Icons) - **API-Calls:** Network-First mit Fallback - **Photos:** Cache-First mit Expiry **Registrierung:** ```javascript // frontend/src/main.jsx if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js') } ``` ### Manifest **Location:** `frontend/public/manifest.json` **Wichtige Felder:** ```json { "name": "Mitai Jinkendo", "short_name": "Mitai", "theme_color": "#1D9E75", "background_color": "#f4f3ef", "display": "standalone", "icons": [ { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ``` **Installation:** - iOS: "Zum Home-Bildschirm" - Android: "Installieren"-Prompt - Desktop: Chrome/Edge Install-Button --- ## Chart-Bibliothek (Recharts) **Verwendete Charts:** | Chart-Typ | Verwendung | Seite | |-----------|-----------|-------| | **LineChart** | Gewicht, Körperfett, Umfänge, Vitalwerte | Dashboard, History | | **BarChart** | Wöchentliche Ernährung, Aktivität | NutritionPage, History | | **PieChart** | Trainingstypen-Verteilung | Dashboard, ActivityPage | | **ScatterChart** | Korrelationen (Gewicht vs. Kalorien) | NutritionPage | | **ComposedChart** | Multi-Axis (Gewicht + KF% kombiniert) | History | **Standard-Konfiguration:** ```jsx ``` **Farb-Schema:** - Gewicht: `var(--accent)` (#1D9E75) - Körperfett: `#D85A30` (Danger) - Umfänge: Individual Colors (siehe `CIRCUMFERENCE_GUIDE`) - 7-Tage-Durchschnitt: `var(--accent-dark)` (#0a5c43, gestrichelt) --- ## Feature Usage Badges (v9c Phase 3) **Zweck:** Sichtbarkeit der Feature-Limits direkt in der UI **Komponenten:** ### 1. UsageBadge (`components/UsageBadge.jsx`) **Inline-Badge mit Limit-Status:** ```jsx // Rendert: "5/10" (grün) oder "10/10 🔒" (rot) ``` **Logik:** ```javascript const { allowed, used, limit, remaining } = await api.getFeatureUsage() const color = allowed ? 'var(--accent)' : 'var(--danger)' const text = limit === null ? '∞' : `${used}/${limit}` ``` **Verwendung:** In Buttons (z.B. "Speichern 5/10") ### 2. FeatureUsageOverview (`components/FeatureUsageOverview.jsx`) **Tabelle mit allen Features:** ```jsx ``` **Darstellung:** | Feature | Genutzt | Limit | Verbleibend | Status | |---------|---------|-------|-------------|--------| | Gewichtseinträge | 45 | 100 | 55 | ✓ OK | | KI-Aufrufe | 10 | 10 | 0 | 🔒 Limit erreicht | | Daten-Export | 1 | 5 | 4 | ✓ OK | **Farbcodierung:** - Grün: `allowed === true` - Rot: `allowed === false` - Gelb: `remaining < 10% && allowed` **Location:** Settings-Seite (Tab "Quota") --- ## Routing-Architektur ### App-Struktur (`App.jsx`) ``` AuthProvider ↓ ProfileProvider ↓ BrowserRouter ↓ AppShell ├── Public Routes (ohne Auth) │ ├── /register → Register │ ├── /verify?token=... → Verify │ └── /reset-password?token=... → ResetPassword │ ├── Auth Gates │ ├── authLoading → Spinner │ ├── needsSetup → SetupScreen │ └── !session → LoginScreen │ └── Authenticated Routes ├── Header (Logo + Logout + Avatar) ├── Main (Scrollable Content) │ └── Routes (31 Seiten) └── Nav (Bottom Navigation, 5 Items) ``` ### Navigation-Items ```javascript const links = [ { to: '/', icon: , label: 'Übersicht' }, { to: '/capture', icon: , label: 'Erfassen' }, { to: '/history', icon: , label: 'Verlauf' }, { to: '/analysis', icon: , label: 'Analyse' }, { to: '/settings', icon: , label: 'Einst.' }, ] ``` **Besonderheit:** Active-State via React Router `NavLink` (`isActive` prop) ### Route-Guards **Auth-Schutz:** ```jsx if (!session) return ``` **Admin-Schutz:** ```jsx // In AdminPanel-Seiten: const { isAdmin } = useAuth() if (!isAdmin) return
Nur für Admins
``` **Setup-Check:** ```jsx if (needsSetup) return ``` --- ## Performance-Optimierungen ### 1. Code Splitting **React.lazy() für Admin-Seiten:** ```javascript const AdminPanel = React.lazy(() => import('./pages/AdminPanel')) ``` **Vorteil:** Admin-Code nicht im Initial Bundle (~80 KB gespart) ### 2. Memoization **useMemo für teure Berechnungen:** ```javascript const stats = useMemo(() => { return calculateStats(data) }, [data]) ``` **Verwendung:** Chart-Daten-Transformation, Aggregationen ### 3. Lazy Loading **Images:** ```jsx ``` **Charts:** - Nur sichtbare Charts rendern (Intersection Observer in Planung) ### 4. API-Call-Batching **Parallel-Loading:** ```javascript const [stats, insights, weights] = await Promise.all([ api.getStats(), api.latestInsights(), api.listWeight(30), ]) ``` **Verwendung:** Dashboard initial load --- ## Error-Handling ### 1. API-Fehler **Pattern in allen Seiten:** ```javascript const [error, setError] = useState(null) try { const data = await api.someEndpoint() setData(data) } catch(e) { setError(e.message) // api.js parsed bereits {detail: "..."} } finally { setLoading(false) } ``` **Anzeige:** ```jsx {error && (
{error}
)} ``` ### 2. Network-Fehler **Offline-Detection:** ```javascript useEffect(() => { const handleOnline = () => setOnline(true) const handleOffline = () => setOnline(false) window.addEventListener('online', handleOnline) window.addEventListener('offline', handleOffline) return () => { window.removeEventListener('online', handleOnline) window.removeEventListener('offline', handleOffline) } }, []) ``` **Anzeige:** Banner "Keine Internetverbindung – Änderungen werden gespeichert sobald Online" ### 3. Form-Validierung **Client-Side:** ```javascript if (!weight || weight < 20 || weight > 300) { setError('Gewicht zwischen 20 und 300 kg') return } ``` **Server-Side:** - Backend wirft `HTTPException(400, detail="...")` → Frontend zeigt `detail` --- ## Besonderheiten & Design-Entscheidungen ### 1. Warum kein TypeScript? **Entscheidung:** Bewusst auf TypeScript verzichtet **Gründe:** - Schnellere Prototyping-Geschwindigkeit - Weniger Build-Komplexität - JSDoc-Kommentare für Dokumentation ausreichend - Type Safety durch Backend (Pydantic validiert alle Inputs) ### 2. Warum Context statt Redux? **Entscheidung:** Context API ausreichend für diesen Use-Case **Gründe:** - Nur 2 globale States (Auth + Profile) - Kein komplexes State-Update-Pattern nötig - Weniger Boilerplate - Performance ausreichend (keine häufigen Re-Renders) **Hinweis:** Bei >5 Contexts würde Redux Sinn machen ### 3. Warum Custom Markdown statt remark/rehype? **Entscheidung:** Eigener Markdown-Renderer (134 Zeilen) **Gründe:** - Nur Subset von Markdown benötigt (Headings, Bold, Listen) - remark + rehype + plugins = ~200 KB Bundle-Size - Custom-Renderer = 0 Dependencies - Full Control über Styling ### 4. Warum Recharts statt Chart.js? **Entscheidung:** Recharts für alle Charts **Gründe:** - React-native (kein Canvas, sondern SVG) - Declarative API passt zu React - Responsive by default - Kleineres Bundle als Chart.js ### 5. Inline-Editing statt Modal-Forms **Entscheidung:** Inline-Edit für alle Listen (Gewicht, Ernährung, Vitalwerte) **Pattern:** ```javascript const [editingId, setEditingId] = useState(null) {entries.map(e => ( editingId === e.id ? : setEditingId(e.id)} /> ))} ``` **Vorteil:** - Keine Modal-Komponente nötig - Besserer Mobile-UX (kein Overlay) - Schnelleres Editing (kein Dialog öffnen) --- ## Bekannte Limitationen ### 1. Desktop-Optimierung **Problem:** App ist Mobile-First, Desktop-Layout nicht optimiert **Aktuell:** Max-Width 600px, zentriert auf Desktop **Geplant (v10+):** Responsive Grid-Layout für Desktop (Sidebar + Multi-Column) ### 2. Offline-Modus **Problem:** Service Worker cached nur Static Assets, nicht API-Responses **Aktuell:** Offline = Keine Daten-Eingabe möglich **Geplant (v10+):** IndexedDB für Offline-Queue ### 3. Multi-Profil-Support **Problem:** Profile-Wechsel funktioniert, aber Session ist Single-User **Aktuell:** Logout + Login für Profil-Wechsel **Geplant (v9f+):** Multi-Session-Support (Switch ohne Logout) ### 4. Accessibility **Problem:** ARIA-Labels fehlen, Keyboard-Navigation unvollständig **Aktuell:** Maus/Touch-optimiert **Geplant (v10+):** WCAG 2.1 AA Compliance --- ## Testing **Aktueller Stand:** Kein automatisiertes Testing implementiert **Geplant (v10+):** - Unit-Tests: Vitest für utils (calc.js, interpret.js) - Component-Tests: React Testing Library - E2E-Tests: Playwright für kritische Flows (Login, Gewicht-Eingabe, KI-Analyse) **Manual Testing:** - Alle Features manuell auf iOS Safari, Android Chrome, Desktop Firefox getestet - Regression-Tests bei jedem Deploy --- ## Zusammenfassung **Architektur-Highlights:** - ✅ 31 Seiten (22 User, 9 Admin) - ✅ Context-basiertes State Management (Auth + Profile) - ✅ Zentrale API-Schnittstelle (api.js) - ✅ Berechnungs-Utils für Body-Metrics (calc.js, interpret.js) - ✅ CSS-Variablen für Light/Dark Mode - ✅ PWA mit Service Worker - ✅ Recharts für alle Charts - ✅ Feature Usage Badges (v9c Phase 3) - ✅ Mobile-First Design (max-width 600px) - ✅ Inline-Editing statt Modals - ✅ Custom Markdown-Renderer (0 Dependencies) **Performance:** - Initial Bundle: ~450 KB (gzip) - Code Splitting: Admin-Seiten lazy-loaded - API-Call-Batching: Parallel-Loading für Dashboard **Nächste Schritte:** - Testing (Vitest + React Testing Library) - Desktop-Responsive-Layout - Offline-Modus (IndexedDB) - Accessibility (WCAG 2.1 AA)