# 05 Frontend Eine einzige React-Anwendung ohne Router: `App.tsx` haelt die aktuelle Ansicht in einem Zustand und rendert sie. Das genuegt, weil die App hinter einer Anmeldung sitzt und keine tiefen Links braucht. ## Menue und Ansichten ```mermaid flowchart TD SB["Seitenleiste"] --> K["Kategorien"] K --> O["Opportunities
(Startansicht)"] K --> C["Kalender"] K --> L["Kontakte"] K --> M["Mails"] M --> M1["Postfach"] M --> M2["Kontakt-Vorschlaege"] K --> SC["Scanner"] SC --> SC1["Dashboard"] SC --> SC2["Unpassend"] SC --> SC3["Suchagenten"] SC --> SC4["Boersen-Konten"] SB --> E["Einstellungen"] E --> E1["Nutzerverwaltung (admin)"] E --> E2["Mein Konto"] E --> E3["Postfach (admin)"] E --> E4["Scanner (admin)"] E --> E5["Whisper"] ``` *Postfach* und *Kontakt-Vorschlaege* sind Reiter **innerhalb** der Ansicht *Mails*, keine Menuepunkte - sie gehoeren zusammen und wechseln oft hin und her. Der Menuepunkt *Scanner* klappt seine Unterpunkte auf; *Scanner* selbst ist das Dashboard. Bei eingeklappter Leiste (Symbolbreite) erscheinen nur die Symbole, die Unterpunkte kommen mit den Beschriftungen zurueck. ## Zustand und Datenfluss ```mermaid flowchart TB subgraph Kontexte["Kontexte in main.tsx"] TH["ThemeProvider
hell/dunkel/System"] TO["ToastProvider"] AU["AuthProvider
JWT, Benutzer"] WH["WhisperProvider
Modell, Worker"] end APP["App.tsx
currentView, settingsTab, focusLeadId"] NAV["NavigationProvider
goTo(view, {tab, leadId})"] DATA["loadData()
leads, connections, events, users, opportunities"] Kontexte --> APP APP --> NAV APP --> DATA DATA --> V1["Opportunities"] DATA --> V2["Kalender"] DATA --> V3["Kontakte"] DATA --> V4["Mails
leads + opportunities fuer den Zuordnungsdialog"] NAV --> DF["DictationField
springt nach Whisper"] NAV --> ML["Mails
springt zum Kontakt einer Zuordnung"] SCV["Scanner-Ansichten"] -->|"laden selbst"| API["/api/scanner"] MV["Mailansicht"] -->|"laedt selbst"| MAPI["/api/mail"] ``` Die CRM-Daten werden **einmal zentral** geladen und als Props durchgereicht; die Scanner- und Mailansichten laden ihre eigenen Daten selbst - der Scanner, weil er eine andere Datenbank und eine Auffrischung im 30-Sekunden-Takt hat; die Mails, weil sie blaettern, suchen und filtern und dabei nicht die ganze Liste im Speicher halten sollen. Kontakte und Opportunities bekommt die Mailansicht dagegen als Props: der Zuordnungsdialog braucht sie beide zum Vorschlagen. `goTo('Kontakte', { leadId })` ist der Sprung aus einer Mailzuordnung zum Kontakt. `App.tsx` haelt die Id in `focusLeadId`, die Kontaktansicht oeffnet den Kontakt und meldet mit `onFocusHandled()` zurueck - der Effekt haengt an `leads` und wartet damit auf einen gerade erst angelegten Kontakt. `reload={() => loadData(true)}` laedt still nach - ohne Ladeanzeige, damit ein Speichern im Dialog nicht die ganze Liste flackern laesst. ## Komponentenkarte ``` src/ App.tsx Layout, Datenladen, Ansichtswechsel types.ts Gemeinsame Typen, Farbpalette, Rechenhilfen lib/ api.ts Fetch-Wrapper (JWT, 401-Behandlung) + CRM-Endpunkte scannerApi.ts Typisierte Endpunkte unter /api/scanner mailApi.ts Endpunkte unter /api/mail, UTC-Datumshelfer auth.tsx Anmeldung, Benutzer, Rolle theme.tsx Hell/Dunkel/System navigation.tsx Ansichtswechsel fuer tiefe Komponenten whisper.tsx Modell, Worker-Lebenszyklus dictation.tsx Aufnahme-Hook audio.ts Aufnahme zu 16-kHz-Mono-PCM imageCompress.ts Bilder vor dem Upload verkleinern components/ Sidebar.tsx Menue inkl. aufklappbarer Scanner-Gruppe Header.tsx Titel, Untertitel, Dark-Mode-Schalter opportunities/ Kachel, Liste, Pipeline (Drag & Drop), Formular calendar/ Monat/Woche/Tag, Termine (Drag & Drop), @-Mentions leads/ Kachel, Liste, Netzwerkdiagramm (d3-force) mail/ Postfachliste, Detail, Zuordnung, Kontakt-Vorschlaege scanner/ Dashboard, Unpassend, Suchagenten, Boersen-Konten settings/ Reiter: Nutzerverwaltung, Konto, Postfach, Scanner, Whisper whisper/ Einrichtung der Spracherkennung ui/ Modal, Avatare, Diktatfeld ``` ## Dark Mode ohne `dark:`-Varianten Die Klasse `dark` auf `` tauscht in `src/index.css` nur die Werte der Variablen `canvas`, `surface`, `ink-*`, `brand-*` und der getoenten Statusfarben. Die Komponenten benutzen durchgaengig diese Skalen und folgen damit automatisch - kein `dark:` im Markup. ```mermaid flowchart LR T["Umschalter im Header"] --> P["localStorage
perinco_crm_theme"] P --> H["class 'dark' auf html"] H --> V["Variablen in index.css
canvas, surface, ink-*, brand-*"] V --> U["Utility-Klassen der Komponenten"] SYS["Systemeinstellung"] -.->|"ohne eigene Wahl"| H ``` Ein Inline-Skript in `index.html` setzt die Klasse vor dem ersten Frame, sonst blitzt beim Laden die helle Variante auf. Drei Werte bleiben in beiden Modi gleich: `ink-950` (liegt nur unter Dialog-Overlays), `text-white` (steht immer auf farbigen Flaechen) und `brand-500` (das CI-Gruen der Buttons). Eine bewusste Ausnahme: der **Mailinhalt** bleibt auch im Dunkelmodus auf weissem Grund. Fremdes Mail-HTML bringt seine eigenen Farben mit; ein dunkler Hintergrund darunter macht schwarze Schrift auf schwarzer Flaeche. Der Rahmen liest sich damit wie ein Blatt Papier im dunklen Fenster - gewollt.