Im Monatsraster wechselt ein gezogener Termin den Tag und behaelt seine Uhrzeit; in Woche und Tag rastet er auf 15 Minuten ein. Der Greifpunkt wandert mit, die Dauer bleibt erhalten, und eine gestrichelte Linie zeigt die neue Startzeit vorab an. Die Anzeige springt sofort ans Ziel (pendingMoves) und faellt zurueck, wenn der Server den PATCH ablehnt. Ziehen darf nur, wer den Termin auch aendern darf - das prueft der Server ohnehin erneut. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
112 lines
4.2 KiB
Markdown
112 lines
4.2 KiB
Markdown
# 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<br/>(Startansicht)"]
|
|
K --> C["Kalender"]
|
|
K --> L["Kontakte"]
|
|
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["Scanner (admin)"]
|
|
E --> E4["Whisper"]
|
|
```
|
|
|
|
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<br/>hell/dunkel/System"]
|
|
TO["ToastProvider"]
|
|
AU["AuthProvider<br/>JWT, Benutzer"]
|
|
WH["WhisperProvider<br/>Modell, Worker"]
|
|
end
|
|
|
|
APP["App.tsx<br/>currentView, settingsTab"]
|
|
NAV["NavigationProvider<br/>goTo(view, {tab})"]
|
|
DATA["loadData()<br/>leads, connections, events, users, opportunities"]
|
|
|
|
Kontexte --> APP
|
|
APP --> NAV
|
|
APP --> DATA
|
|
DATA --> V1["Opportunities"]
|
|
DATA --> V2["Kalender"]
|
|
DATA --> V3["Kontakte"]
|
|
NAV --> DF["DictationField<br/>springt nach Whisper"]
|
|
|
|
SCV["Scanner-Ansichten"] -->|"laden selbst"| API["/api/scanner"]
|
|
```
|
|
|
|
Die CRM-Daten werden **einmal zentral** geladen und als Props durchgereicht; die
|
|
Scanner-Ansichten laden dagegen selbst, weil sie eine andere Datenbank und eine
|
|
eigene Auffrischung im 30-Sekunden-Takt haben.
|
|
|
|
`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
|
|
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)
|
|
scanner/ Dashboard, Unpassend, Suchagenten, Boersen-Konten
|
|
settings/ Reiter: Nutzerverwaltung, Konto, Scanner, Whisper
|
|
whisper/ Einrichtung der Spracherkennung
|
|
ui/ Modal, Avatare, Diktatfeld
|
|
```
|
|
|
|
## Dark Mode ohne `dark:`-Varianten
|
|
|
|
Die Klasse `dark` auf `<html>` 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<br/>perinco_crm_theme"]
|
|
P --> H["class 'dark' auf html"]
|
|
H --> V["Variablen in index.css<br/>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).
|