Files
perinco-CRM/docs/05-frontend.md
T
Sascha WolfandClaude Opus 5 3647201111 feat(kalender): Termine per Drag & Drop verschieben
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>
2026-08-07 09:42:39 +02:00

4.2 KiB

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

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

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.

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).