Files
ICON-CRM/docs/03-frontend.md
T

7.4 KiB
Raw Blame History

03 Frontend

React 19 mit TypeScript, gebaut von Vite 6, gestaltet mit Tailwind 4. Eine einzige Seite; Apache liefert sie über FallbackResource für jeden Pfad aus.

Menü

Dashboard
Leads                 Pipeline als Kanban
  └ Archiviert        alles, was per Aktenschrank weggelegt wurde
Kontakte
Unternehmen
Ausschreibungen       Tabelle; nur der laufende Bestand
  ├ Live!             Kanban über die Phasen
  └ Archiv            archivierte Ausschreibungen, von dort zurückholbar
Laufende Projekte
Historie
Einstellungen

Die Unterpunkte klappen auf, sobald ein Punkt des Zweigs benutzt wird. Dauerhaft ausgeklappt wäre die Leiste doppelt so lang, und Archive schaut man selten an.

Die Netzwerkansicht ist kein Menüpunkt, sondern eine Ansicht innerhalb von Kontakten und Unternehmen – genau wie im perinco CRM.

Komponentenkarte

flowchart TD
    M["main.tsx<br/>AuthProvider, ThemeProvider, ToastProvider,<br/>OptionsProvider, WhisperProvider"] --> A["App.tsx<br/>Ansichtswechsel, gemeinsame Daten"]
    A --> SB["Sidebar"]
    A --> HD["Header<br/>Suche, Design, hell/dunkel, Konto"]
    A --> V{"Ansicht"}
    V --> DSH["dashboard/"]
    V --> LDS["leads/<br/>Kanban, Karte, ToDos, Erinnerungen"]
    V --> KON["contacts/<br/>Liste, Kacheln, Netzwerk, CV-Import"]
    V --> UNT["companies/<br/>Liste, Kacheln, Group, Netzwerk"]
    V --> AUS["tenders/<br/>Tabelle, Live!, Archiv"]
    V --> PRJ["projects/"]
    V --> HIS["history/"]
    V --> SET["settings/<br/>Nutzer, Stammdaten, Konto, Diktat"]
    KON --> NW["network/Network.tsx<br/>d3-force + SVG"]
    UNT --> NW
    NW --> CT["network/CompanyTree.tsx"]

Zwei Dinge liegen bewusst in App.tsx:

  • Die Listen der Unternehmen und Benutzer werden einmal geladen und weitergereicht – fast jedes Formular braucht sie als Auswahl.
  • open(type, id) wechselt die Ansicht und reicht die Kennung durch. Damit kann jede @-Erwähnung und jeder Suchtreffer die passende Karte öffnen, ohne dass die Ebenen dazwischen davon wissen (src/lib/navigation.tsx). Dort hängt auch goToSettings(tab) – der Weg, den der Diktat-Knopf nimmt, wenn das Modell noch fehlt.

Zustand

Was Wo
Angemeldeter Benutzer, JWT lib/auth.tsx, Token im localStorage
Design und hell/dunkel lib/theme.tsx – am Benutzer und im localStorage
Auswahllisten (Branchen, Arten) lib/options.tsx
Spracherkennung lib/whisper.tsx
Meldungen components/Toast.tsx

Es gibt keinen globalen Datenspeicher. Jede Ansicht lädt, was sie braucht, und meldet über onChanged zurück, wenn die gemeinsamen Listen neu gelesen werden müssen. Bei der Größe des Bestands ist das die einfachere Wahl.

Designs

Rechts oben zwei Umschalter: Farbwelt und hell/dunkel. Beides gilt für den angemeldeten Benutzer, nicht nur für den Browser.

Design Beschreibung
ICON Gold #C1862F auf neutralem Grau, Montserrat – bewusst ohne Grafik
Half-Life Black Mesa: HEV-Orange auf Sichtbeton, harte Schatten, Oswald in den Überschriften
Pixel Art 8-bit: keine Radien, versetzte Schatten, Arcade-Grün, Silkscreen in den Überschriften

Half-Life und Pixel Art bringen Grafiken hinter Kopfleiste und Menü mit (SVG unter public/themes/). Die CSS-Variable enthält dabei zwei Ebenen: einen Schleier aus der Flächenfarbe und darunter die Zeichnung – nur so funktioniert dieselbe Grafik in hell und dunkel, ohne dass die Schrift verliert.

Ein Design tauscht ausschließlich Variablenwerte; im Markup steht nirgends eine feste Farbe. Auch der Dunkelmodus arbeitet ohne dark:-Varianten: die Klasse dark auf <html> tauscht in src/index.css die Werte von canvas, surface, ink-*, brand-* und den getönten Statusfarben. Ein Inline-Skript in index.html setzt die Klasse vor dem ersten Frame, sonst blitzt die helle Variante auf.

Die Liste der Designs steht an zwei Stellen und muss übereinstimmen: THEME_PALETTES in src/types.ts und THEMES in server/src/userFields.js. Ein unbekannter Wert wird vom Backend mit 400 abgewiesen und beim Lesen auf null normalisiert – sonst reicht ein entfernter Palettenname, um die Oberfläche beim Anmelden abstürzen zu lassen.

Texte: drei Feldarten

Art Komponente Wo
WYSIWYG (HTML) ui/RichTextEditor.tsx Ausschreibungstext, Projektnotizen
Text mit @-Erwähnungen lib/mentions.tsx (MentionField) Notizen an Kontakt, Unternehmen, Lead
einfacher Text ui/form.tsx alles Übrige

Der WYSIWYG-Editor kommt ohne Fremdpaket aus: contentEditable plus document.execCommand für sechs Formate. Quill & Co. bringen ihr eigenes Farbschema mit, das im Dunkelmodus gegen die Variablen arbeitet, und kosten über 200 kB.

Aus dem Altbestand kommt HTML mit Inline-Farben, geschützten Leerzeichen und verschachtelten Listen. src/lib/richText.ts räumt es beim Laden zurückhaltend auf (sanitize()): Farbe, Hintergrund, Schriftart und -größe fliegen aus den Stilangaben, Fett und Kursiv bleiben. Zusätzlich hebt src/index.css alle Inline-Farben im Editor aus – sonst stünde im Dunkelmodus schwarze Schrift auf weißen Kästen.

@-Erwähnungen stehen als Token im Text: @[Anzeigename](contact:12), ebenso für company und user. Das Feld hat zwei Zustände: lesen (Erwähnungen sind Knöpfe und führen auf das Profil) und bearbeiten (der rohe Token – anders ließe sich eine Erwähnung nicht setzen oder löschen).

Netzwerkansicht

Kraftgerichtetes Layout (d3-force), gerendert als SVG.

  • Rechtecke = Unternehmen, Größe nach Anzahl Verbindungen, dunkler Rahmen bei Kunden
  • Kreise = Kontakte
  • Goldene Linie = arbeitet dort · graue gestrichelte = hat dort gearbeitet
  • Feine Linie = Kontakt kennt Kontakt
  • Grün umrandete Kreise = Kontakte aus dem perinco CRM (siehe 06 Schnittstellen)

Hintergrund ziehen verschiebt, Mausrad zoomt auf den Zeiger, Knoten ziehen fixiert ihn, Doppelklick löst die Fixierung. Eine Geste gilt erst ab 4 px Weg als Ziehen, damit Verschieben nicht die Auswahl verwirft.

Falle: Die Liste der externen Knoten muss stabil sein. Ein externals = [] als Vorgabewert erzeugt bei jedem Rendern ein neues Array; die Simulation startet dann endlos neu und alle Knoten liegen auf einem Punkt. Deshalb steht in Network.tsx ein NO_EXTERNALS auf Modulebene, und die aufrufende Ansicht memoisiert ihre Liste.

Stammbaum eines Unternehmens

Ein Klick auf ein Unternehmen mit Kontakten öffnet dessen Stammbaum: das Unternehmen oben, die Kontakte darunter.

Ast Bedeutung
golden Berater – Kontaktart „Experte"
hell normale Verbindung – Ansprechpartner und übrige
grau, gestrichelt Ehemalige – beendete Anstellung; darunter steht, wo die Person heute ist

Die Art des Astes ergibt sich aus der Beziehung, nicht aus der Person: wer das Unternehmen verlassen hat, hängt am grauen Ast, auch wenn er Berater ist.

Fotos

Fotos und Logos liegen als Data-URL in der Datenbank. Der Browser verkleinert jedes Bild vor dem Upload (src/lib/imageCompress.ts): mittiger Ausschnitt auf 400 px im Quadrat, WebP mit Qualität 0,82, Rückfall JPEG, dann PNG.

Der Knopf Von LinkedIn holt das öffentliche Profilfoto – über das Backend (server/src/linkedinPhoto.js), weil aus dem Browser die Same-Origin-Policy davorstünde. Gelesen wird ausschließlich die Open-Graph-Vorschau der öffentlichen Profilseite.