Spiegelt das Vertriebspostfach ueber Microsoft Graph ins CRM. Aus jeder Mail entstehen Kontakt-Vorschlaege, und jede Mail laesst sich an Opportunities und Kontakte haengen. Uebernommen ist die Idee aus dem Altsystem KeepTrack, nicht die Umsetzung: - Delta Query statt "$filter=isRead eq false" mit anschliessendem PATCH. Damit genuegt die Anwendungsberechtigung Mail.Read, es braucht keinen Schreibzugriff auf das Postfach, und eine vorher in Outlook geoeffnete Mail geht nicht verloren. Der Fortschritt liegt als delta_link in der Datenbank und ueberlebt Neustarts; ein Lauf ist gedeckelt und fortsetzbar. - Tenant- und Client-ID kommen aus der .env, nicht aus dem Browser. Im Altsystem konnte jeder Angemeldete das Server-Secret gegen einen fremden Tenant verwenden. - Kontaktdaten werden aus den Graph-Feldern und der Signatur gelesen, ohne Sprachmodell. Mailinhalte verlassen den Server nicht. - Ein Vorschlag ohne E-Mail-Adresse wird nicht mit einer erfundenen Adresse angelegt, sondern bleibt zur Durchsicht liegen. Ist die Adresse schon als Kontakt vorhanden, werden nur leere Felder ergaenzt. - Mails haengen als eigene Verknuepfung an Opportunity und Kontakt, nicht als Text im Notizfeld - loesbar und mehrfach moeglich. - Fremdes Mail-HTML wird serverseitig entschaerft und in einem iframe mit sandbox="" gezeigt. Externe Bilder sind per CSP gesperrt, damit das Oeffnen dem Absender nichts meldet. - Der Verbindungstest prueft Anmeldung und tatsaechlichen Postfachzugriff und benennt den wahrscheinlichen Grund. Der alte Test holte nur ein Token. Fehlen die MS_*-Werte, startet das CRM normal; die Einstellungen nennen die fehlende Variable. Neue Tabellen: crm_mail_messages, crm_mail_links, crm_mail_contact_candidates, crm_mail_sync_state, crm_settings. Uebernommene Kontakte tragen source = 'Mail'; die Kontaktliste hat dafuer einen Herkunftsfilter. Nicht enthalten: Senden, Antworten, Weiterleiten, Anhaenge und die automatische Zuordnung zu Opportunities. npm test im Verzeichnis server prueft Kontaktextraktion und Graph-Client (47 Pruefungen, ohne Framework und ohne Datenbank). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
137 lines
5.7 KiB
Markdown
137 lines
5.7 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 --> 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<br/>hell/dunkel/System"]
|
|
TO["ToastProvider"]
|
|
AU["AuthProvider<br/>JWT, Benutzer"]
|
|
WH["WhisperProvider<br/>Modell, Worker"]
|
|
end
|
|
|
|
APP["App.tsx<br/>currentView, settingsTab, focusLeadId"]
|
|
NAV["NavigationProvider<br/>goTo(view, {tab, leadId})"]
|
|
DATA["loadData()<br/>leads, connections, events, users, opportunities"]
|
|
|
|
Kontexte --> APP
|
|
APP --> NAV
|
|
APP --> DATA
|
|
DATA --> V1["Opportunities"]
|
|
DATA --> V2["Kalender"]
|
|
DATA --> V3["Kontakte"]
|
|
DATA --> V4["Mails<br/>leads + opportunities fuer den Zuordnungsdialog"]
|
|
NAV --> DF["DictationField<br/>springt nach Whisper"]
|
|
NAV --> ML["Mails<br/>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 `<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).
|
|
|
|
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.
|