feat(dashboard): Analytics-Dashboard als Gestaltungsvorlage zur Übergabe

UI/UX-Prototyp der Analytics-Dashboards in HiveGrid, fachlich auf einen
SHK-/Handwerksbetrieb zugeschnitten. Ziel ist die Übergabe an die Entwicklung,
nicht der Produktivbetrieb.

Inhalt: sechs Seiten (Übersicht, Finanzen, Aufträge, Kunden, Team, Komponenten),
26 KPIs, neun Chart-Bausteine, Filtersystem mit seitenabhängiger Relevanz und ein
deterministischer Demo-Datensatz (420 Kunden, 1.900 Aufträge, Stichtag 30.07.2026).

Drei Quellen sind zusammengeführt: die fachliche Planung mit dem Branchenpartner
(Januar 2026), die Subframe-Komponentenbibliothek aus dem HiveGrid-Pitch und
IBM Carbon Data Visualization für die Diagrammregeln. Herkunft und bewusste
Abweichungen stehen in docs/HERKUNFT.md.

Dokumentation: README, HANDOVER (was echte Logik braucht), docs/DESIGN-SYSTEM,
KPI-KATALOG, DATENMODELL, NAVIGATION, HERKUNFT, ZIELSYSTEM (Einbauort in
HiveGrid Cloud) und LEITPLANKEN (welche Cloud-Screens als Vorbild taugen).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
This commit is contained in:
dejanandClaude Opus 5 committed 2026-08-29 17:25:20 +02:00
commit b8bf987d0e
250 files changed
+33153

No files matched your search

+99
View File
@@ -0,0 +1,99 @@
# Datenmodell
Definiert in `features/dashboard/data/types.ts`, erzeugt in `data/seed.ts`.
Die Struktur ist bewusst nah an den HiveGrid-Entities gehalten, damit beim
Einbau nur die Datenquelle getauscht werden muss.
---
## Zuordnung zu HiveGrid
| Prototyp | HiveGrid-Entity | Verwendete Felder |
|---|---|---|
| `Rechnung` | `invoice` | `amount`, `status`, `issuedAt`, `dueDate`, bezahlt-am |
| `Auftrag` | `order` | `totalValue`, `status`, `startDate`, `endDate`, Gewerk, Marge, Förderung |
| `Kunde` | `company` | `name`, `source`, `createdAt`, Typ, Region |
| `Mitarbeiter` | `TenantUser` + `User` | Name, Rolle, Gewerk, Status, Wochenstunden |
| `Zeiteintrag` | `timeEntry` | `hours`, `date`, Auftragsbezug, Abwesenheitsgrund |
**Felder, die es so noch nicht gibt** und beim Einbau ergänzt werden müssen:
| Feld | Wo | Wofür |
|---|---|---|
| `gewerk` | Auftrag, Mitarbeiter | Alle Gewerke-Auswertungen und der Gewerk-Filter |
| `margeProzent` | Auftrag | Deckungsbeitrag |
| `foerderung` / `foerderbetrag` | Auftrag | Förderquote BAFA/KfW |
| `verlorenAb` | Auftrag | Ehrlicher Vertriebstrichter (siehe unten) |
| `ueberAngebot` | Auftrag | Trennt Angebotsgeschäft von Direktaufträgen |
| `wochenstunden` | Mitarbeiter | Auslastungsberechnung |
| `zahlungsmoralTage` | Kunde | Auffällige Zahlungsmoral (auch aus Rechnungen ableitbar) |
---
## Statuswerte
**Auftrag:** `anfrage` → `aufmass` → `angebot` → `beauftragt` → `in_arbeit` →
`abgeschlossen`, dazu `verloren` als Endzustand.
`verlorenAb` hält fest, welche Stufe ein verlorener Vorgang zuletzt erreicht
hat. Ohne dieses Feld müsste der Trichter raten und würde die oberen Stufen
systematisch zu klein zeigen.
`ueberAngebot` trennt das Angebotsgeschäft (ab etwa 6.000 €) von Aufträgen, die
direkt beauftragt werden — Wartung, Notdienst, E-Check. Trichter und
Abschlussquote beziehen sich ausschließlich auf das Angebotsgeschäft.
**Rechnung:** `entwurf` · `gestellt` · `bezahlt` · `ueberfaellig`.
---
## Gewerke und Leistungsarten
| Gewerk | Leistungsarten |
|---|---|
| Heizung | Wärmepumpe, Gastherme, Heizkörper, Wartung |
| Sanitär | Bad-Sanierung, Rohrleitungen, Armaturen, Notdienst |
| Klima & Lüftung | Splitgerät, Lüftungsanlage, Kanalreinigung, Wartung |
| Photovoltaik | PV-Anlage, Batteriespeicher, Wallbox, Wartung |
| Elektro | Elektroinstallation, Zählerschrank, Smart Home, E-Check |
Förderfähig (BAFA/KfW) sind Wärmepumpe, PV-Anlage, Batteriespeicher und
Lüftungsanlage.
---
## Der Demo-Datensatz
Erzeugt mit festem Seed (`mulberry32`, Seed `20260730`) — die Zahlen bleiben
über Reloads und Screenshots identisch. Bezugsdatum ist der **30.07.2026**
(`HEUTE` in `utils/dateUtils.ts`).
Fiktiver Betrieb: **Meister Krause GmbH**, Raum Rhein-Main, 24 Beschäftigte
(3 Meister, 14 Monteure, 3 Auszubildende, 4 Büro).
| Größe | Wert |
|---|---|
| Kunden | 420 |
| Aufträge (24 Monate) | 1.900 |
| Rechnungen | ~1.270 |
| Zeiteinträge (12 Wochen) | ~1.500 |
| Jahresumsatz | ~2,4 Mio. € |
| Offene Forderungen | ~230 T€ |
| Abschlussquote (Angebotsgeschäft) | ~38 % |
| Ø Auslastung | ~73 % |
**Eingebaute Effekte**, damit die Charts nicht künstlich flach aussehen:
- **Saisonalität** — Heizung und Lüftung ziehen von September bis Februar an,
der Hochsommer ist schwächer (`SAISON_INDEX`)
- **Wachstum** — rund 12 % gegenüber dem Vorjahr
- **Größenabhängige Abschlussquote** — Wartung und Notdienst werden fast immer
ausgeführt, große Anlagen dagegen verglichen und häufiger verloren
- **Zahlungsverhalten** — die Mehrheit zahlt nahe am Zahlungsziel (21 Tage),
ein kleiner Rest wird zum Mahnfall; Forderungen älter als sieben Monate sind
eingebracht oder ausgebucht und stehen nicht mehr in der offenen Liste
- **Förderanträge** — nur bei Vorgängen der letzten rund 100 Tage offen; ein
Bescheid dauert Wochen, nicht Jahre
Diese Effekte sind fachlich begründet, nicht kosmetisch: Ein gleichverteilter
Zufallsdatensatz erzeugt Charts, an denen sich keine Gestaltung prüfen lässt.
+248
View File
@@ -0,0 +1,248 @@
# Design-System: Datenvisualisierung
Grundlage ist **IBM Carbon Data Visualization**, ergänzt um die Spacing- und
Zahlenregeln aus der eigenen Planung (`UI_UX_GUIDELINES.md`, Januar 2026).
Die konkreten Farb- und Linienwerte sind aus dem ausgelieferten Stylesheet von
`@carbon/charts` (`dist/styles.css`, Light Theme) übernommen — nicht aus der
Dokumentation abgeschrieben. Alles Folgende ist in
`features/dashboard/utils/chartTheme.ts` als Code hinterlegt.
---
## 1. Farben
### Kategoriale Palette (14 Farben, Reihenfolge verbindlich)
```
1 #6929c4 2 #1192e8 3 #005d5d 4 #9f1853 5 #fa4d56
6 #520408 7 #198038 8 #002d9c 9 #ee5396 10 #b28600
11 #009d9a 12 #012749 13 #8a3800 14 #a56eff
```
Die Reihenfolge ist auf Unterscheidbarkeit optimiert — auch bei
Farbfehlsichtigkeit und in Graustufen. Farben deshalb **immer von vorne
vergeben**, nie nach Geschmack auswählen und nie umsortieren.
**Maximal fünf Kategorien je Chart.** Alles darüber fasst
`gruppiereRest()` zu „Sonstige" zusammen. Ein Ring mit zwölf Segmenten ist
keine Information mehr, sondern Dekoration.
### Statusfarben (nur für Aussagen, nie für Kategorien)
| Bedeutung | Wert |
|---|---|
| positiv | `#198038` |
| Warnung | `#b28600` |
| negativ | `#da1e28` |
| neutral | `#8d8d8d` |
| Information | `#1192e8` |
Kategorie- und Statusfarben nie im selben Chart mischen — eine grüne Kategorie
neben einem grünen Positiv-Status ist nicht mehr eindeutig lesbar.
**Rangfolgen einfarbig.** Top-Kunden oder Aufträge je Mitarbeiter sind keine
Kategorien: Dort trägt die Farbe keine Information, und eine bunte Palette
erzeugt nur Unruhe. Palette nur, wenn die Farbe etwas bedeutet.
### Feste Farbzuordnung
Wiederkehrende Kategorien behalten ihre Farbe über alle Charts und Seiten
hinweg (`GEWERK_FARBEN`, `STATUS_KATEGORIE_FARBEN` in `chartTheme.ts`). Sonst
ist Heizung im Ring lila und im Balkendiagramm blau, und jede Legende muss neu
gelesen werden.
| Gewerk | Farbe | | Status | Farbe |
|---|---|---|---|---|
| Heizung | `#6929c4` | | Verloren / Überfällig | `#fa4d56` / `#da1e28` |
| Sanitär | `#1192e8` | | In Arbeit / Bezahlt | `#198038` |
| Klima & Lüftung | `#005d5d` | | Beauftragt | `#009d9a` |
| Photovoltaik | `#9f1853` | | Angebot | `#6929c4` |
| Elektro | `#198038` | | Entwurf / Sonstige | `#8d8d8d` |
Bei Statuswerten trägt die Farbe Bedeutung: „Verloren" und „Überfällig" sind
rot, „Bezahlt" grün. Eine zufällige Palettenfarbe für „Verloren" würde die
Aussage der Grafik verwässern.
### Geordnete Dimensionen
Altersklassen, Risikostufen und Ähnliches sind **keine Kategorien**, sondern eine
Reihenfolge mit wachsendem Risiko. Dafür gilt die vierstufige Risikorampe
(`RISIKO_RAMPE` in `chartTheme.ts`) — durchgehend Statusfarben, keine
Palettenfarbe dazwischen:
| Stufe | Wert | Bedeutung |
|---|---|---|
| 1 | `#198038` | im Zahlungsziel |
| 2 | `#b28600` | beobachten |
| 3 | `#ba4e00` | mahnen |
| 4 | `#da1e28` | Abschreibungsrisiko |
### Einzelserien
| Rolle | Wert |
|---|---|
| Hauptreihe (Umsatzverlauf o. Ä.) | `#1192e8` |
| Vergleichsreihe (Vorjahr) | `#8d8d8d`, gestrichelt |
Die Vergleichsreihe ist bewusst neutral: Sie soll einordnen, nicht konkurrieren.
---
## 2. Achsen und Gitter
| Element | Wert | Begründung |
|---|---|---|
| Gitterlinien | `#e0e0e0`, **nur horizontal** | Vertikale Linien zerschneiden den Verlauf |
| Achsenlinie | `#8d8d8d`, 1 px | Nur an der X-Achse; die Y-Achse braucht keine |
| Tick-Marks | keine | Die Beschriftung reicht als Orientierung |
| Beschriftung | 12 px, `#525252` | Carbon-Standard |
| Y-Achsen-Breite | 58 px | Platz für „245 T€" ohne Umbruch |
Bei mehr als 12 Datenpunkten wird nur jede n-te Beschriftung gezeigt, sonst
überlappen sie (`MAX_ACHSENLABELS` in `chartTheme.ts` — eine Konstante für
alle Diagrammtypen).
**Achsenwerte sind rund.** Recharts leitet Ticks aus `max/(n-1)` ab und rundet
den Rohschritt auf; bei 219 T€ ergibt das 0/55/110/165/220. Krumme Achsenwerte
kosten den Leser bei jedem Blick eine Rechenoperation. `nettesRaster()` erzwingt
deshalb Schrittweiten aus der 1-2-2,5-5-Reihe.
**Die Einheit gehört zur Achse, nicht zum Wert.** `achsenFormat(max)` wählt sie
einmal aus dem Achsenmaximum. Sonst steht „0 €" neben „10 T€" und die Skala
wechselt mitten im Lesen die Einheit; die Nulllinie trägt gar keine Einheit.
---
## 3. Tooltip
Einheitlich für alle Charts (`components/charts/ChartTooltip.tsx`):
- Fläche `#393939`, Text `#ffffff`
- Label 12 px mit 80 % Deckkraft, Wert 13 px fett in Tabellenziffern
- kein Rahmen, kein Doppelpunkt zwischen Name und Wert
- Farbmarker je Reihe, 10 × 10 px
Ein gemeinsamer Tooltip verhindert, dass jedes Chart eigene Regeln entwickelt.
---
## 4. Balken, Flächen, Ringe
| Element | Regel |
|---|---|
| Balkenbreite | maximal 48 px |
| Balkenabstand | mindestens halbe Balkenbreite (`barCategoryGap: 28 %`) |
| Balkenecken | 2 px, nur an der Werteseite |
| Linienstärke Fläche | 2 px, Verlaufsfüllung 24 % → 2 % |
| Interpolation | **linear** — eine Monotone-Kurve erfindet Zwischenwerte zwischen zwei Messpunkten |
| Punkte | nur beim Hovern, Radius 4, weißer Rand |
| Ring innen/außen | 62 % / 88 %, 2° Segmentabstand |
| Ringmitte | Gesamtwert — sonst muss man die Segmente addieren |
| Ringlegende | rechts, einzeilig, mit Wert und Prozentanteil |
Ringsegmente starten oben (12 Uhr) mit dem größten Wert und laufen im
Uhrzeigersinn absteigend. Auch „Sonstige" wird nach Größe einsortiert, nicht
ans Ende gehängt.
**Ein Ring braucht ein echtes Ganzes.** Beträgt ein Segment über 80 %, trägt die
Grafik nichts mehr bei — dann ist die Bezugsgröße falsch gewählt. Beispiel: Der
Rechnungsbestand aus zwei Jahren gegen den heutigen Forderungsstand ergibt
92 % „Bezahlt". Richtig ist der offene Bestand allein; dann zeigt der Ring, wie
viel davon bereits überfällig ist.
### Werte in einem engen Band: Punkt statt Balken
Auslastung liegt betrieblich zwischen etwa 70 und 100 %. Ein Balken **muss** bei
Null beginnen, sonst verzerrt seine Länge — und dann sind 74 % und 96 % optisch
fast gleich lang. Ein Punkt kodiert über die **Position**, nicht über die Länge,
und darf deshalb einen ausgeschnittenen Bereich zeigen.
Der Zielkorridor liegt als graues Band hinter den Punkten, die 100-%-Marke als
beschriftete Linie, darunter eine Achse. Damit erklärt sich die Grafik ohne
Ampel-Legende — und tote Legendeneinträge („Luft frei", wenn niemand darunter
liegt) entfallen.
---
## 5. Abstände (8-px-System)
| Bereich | Wert |
|---|---|
| Seitenrand | 24 px |
| Abstand zwischen Karten | 16 px |
| Innenabstand Karte | 16 px |
| Kopf zu Inhalt in der Karte | 12 px |
| Label zu Wert in der KPI-Karte | 4 px |
| Icon zu Text | 8 px |
**Grundregel: innen ≤ außen.** Ist der Innenabstand größer als der Abstand
zwischen den Karten, zerfällt das Raster optisch.
---
## 6. Typografie
| Rolle | Größe | Gewicht |
|---|---|---|
| Seitentitel | 22 px | 600 |
| KPI-Wert | 28 px | 600 |
| Kartentitel | 14 px | 600 |
| Fließtext / Tabelle | 13 px | 400 |
| Label, Achsen, Legende | 12 px | 400–500 |
| Kleinstschrift (Meta) | 11 px | 400 |
Eine Schriftfamilie (Inter). Zahlen immer in Tabellenziffern
(`font-variant-numeric: tabular-nums`, global gesetzt), damit Spalten
untereinander stehen.
---
## 7. Zahlenformate (deutsch)
| Fall | Ausgabe |
|---|---|
| Währung, KPI | `238.412 €` |
| Währung, kompakt (Achsen, Balken) | `8,4 T€` · `245 T€` · `1,25 Mio. €` |
| Prozent | `58,2 %` |
| Tage / Stunden | `34,2 Tage` · `7,5 h` |
| Datum | `30.07.2026`, kurz `30.07.`, Monat `Jul 26` |
Punkt als Tausendertrenner, Komma als Dezimaltrenner. Innerhalb einer Ansicht
immer dasselbe Format — nicht in einer Zeile `245.000 €` und in der nächsten
`245 T€`.
---
## 8. Zustände
Jedes Chart braucht einen **Leerzustand** — Filter können jede Ansicht leer
laufen lassen. `ChartLeer` zeigt dann eine gestrichelte Fläche mit Hinweistext
statt eines leeren Rechtecks.
Hover-Zustände: Karten erhalten einen stärkeren Schatten, Balken und
Ringsegmente werden hervorgehoben (die übrigen auf 35–45 % Deckkraft).
Tabellenzeilen bekommen **keinen** Hover, solange sie nicht anklickbar sind —
ein Hover ohne Ziel verspricht eine Interaktion, die es nicht gibt.
## 9. Fokus und Kontrast
**Fokus** ist eine einzige Regel in `app/globals.css` statt `focus:`-Klassen an
jedem Element:
```css
:where(a, button, [role="button"], input, select, textarea, [tabindex]):focus-visible {
outline: none;
box-shadow: var(--shadow-focus);
border-radius: var(--radius-sm);
}
```
`:focus-visible` zeigt den Ring nur bei Tastaturbedienung. Overlays (Filter-
Dropdown, Karten-Menü) schließen mit **Escape** und geben den Fokus an den
auslösenden Knopf zurück.
**Kontrast:** `--color-text-neutral-tertiary` liegt auf `neutral-700`
(5,15:1 auf Weiß), nicht auf `neutral-600` (3,68:1 — unter WCAG AA). Das
entspricht dem a11y-Override im Zielsystem. `text-neutral-subtle` ist ein
Placeholder-Token und für lesbaren Text nicht geeignet.
+173
View File
@@ -0,0 +1,173 @@
# Herkunft
Woher die Gestaltung dieses Prototyps stammt, was aus welcher Quelle übernommen
wurde und wo bewusst abgewichen wurde.
Der Zweck: Wer beim Einbau eine Entscheidung anzweifelt, soll nachlesen können,
ob sie begründet ist oder nur mitgeschleppt wurde. Vieles hier sieht auf den
ersten Blick wie eine Abweichung von der Januar-Planung aus und ist eine
absichtliche Korrektur.
---
## Die drei Quellen
| Quelle | Was daraus stammt | Stand |
|---|---|---|
| **Fachliche Planung** mit dem Branchenpartner | Seitenschnitt, KPI-Auswahl, Filtersystem, Spacing- und Zahlenregeln | Januar 2026 |
| **HiveGrid-Pitch-Prototyp** | Komponentenbibliothek `ui/`, Design-Tokens, Icon-Set, Gestaltungssprache der Oberfläche | Juli/August 2026 |
| **IBM Carbon Data Visualization** | Chart-Regeln, Farbpalette, Achsen, Tooltips, Kategorien-Obergrenze | laufend |
Die ersten beiden liegen als lokale Ordner außerhalb dieses Repos und sind
**nicht Teil der Übergabe** — alles fachlich Relevante daraus ist in `docs/`
überführt. Die Zuordnung unten ist die Brücke zurück, falls jemand die
Originale doch braucht.
---
## 1. Fachliche Planung (Januar 2026)
Ordner `dashboard-planung/`, entstanden mit dem Branchenpartner. Drei Teile
sind hier eingeflossen:
| Teil der Planung | Wirkung in diesem Repo |
|---|---|
| `01_CRM-Analytics-Clickdummy/` — React/Vite-Click-Dummy | Seitenschnitt, Komponentenzuschnitt, Filterarchitektur |
| `UI_UX_GUIDELINES.md` | 8-px-Raster, Regel „innen ≤ außen", Zahlenformate, KPI-Karten-Aufbau |
| `03_Planung-und-Konzept/Analytics/REQUIREMENTS-analytics.md` | Anforderungs-IDs (INFRA, DASH, FIN, ORD, CUST, COMP, FILT) |
Die Anforderungs-IDs stehen an mehreren Stellen als Kommentar im Code
(`FilterContext.tsx` → INFRA-01/02, `filtering.ts` → FILT-05,
`AnalyticsToolbar.tsx` → FILT-06). Sie sind der Faden zurück in die Planung.
### Übernommen
- **Fünf Analyse-Seiten** in derselben Reihenfolge: Übersicht, Finanzen,
Aufträge, Kunden, Team
- **Komponentenzuschnitt**: KPICard, ChartCard, AlertList, DataTable,
Filterleiste, Mehrfachauswahl mit Suche — dieselbe Aufteilung, neu geschrieben
- **Filterarchitektur**: globaler Filter-State über alle Seiten, in
`localStorage` persistiert, aktive Einschränkungen als entfernbare Chips
- **Seitenabhängige Filterrelevanz** (FILT-05) als Konzept
- **Recharts** als Chart-Bibliothek — dieselbe Wahl wie im Click-Dummy
- **8-px-Raster und deutsche Zahlenformate** aus `UI_UX_GUIDELINES.md`,
siehe [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) Abschnitt 5 und 7
### Bewusst anders
| Thema | Click-Dummy (Januar) | Hier | Grund |
|---|---|---|---|
| **Chart-Farben** | 12 kräftige Farben, Regel „keine Grautöne, wirken traurig" | Carbon-Palette, Grau ausdrücklich für Vergleichsreihen und Neutrales | Die Januar-Palette ist auf Freundlichkeit optimiert, nicht auf Unterscheidbarkeit. Carbon ist auf Farbfehlsichtigkeit und Graustufen geprüft. Grau ist kein Makel, sondern das Mittel, mit dem eine Vergleichsreihe zurücktritt |
| **Irrelevante Filter** | rechts ausgegraut angezeigt | werden gar nicht erst angeboten | Ein sichtbarer Filter, der nichts bewirkt, kostet Vertrauen in die Zahlen |
| **Schriftgrößen** | zur Laufzeit verstellbar (`TypographyContext` + Dev-Panel), KPI-Wert 37 px | feste Werte, KPI-Wert 28 px | Das Panel war ein Abstimmungswerkzeug. Die Abstimmung ist erfolgt; die Werte gehören jetzt fest ins Design-System |
| **Chart-Elemente** | Drop-Shadow statt Rahmen | flach, Trennung über Weißraum und Segmentabstand | Schatten auf Datenflächen erzeugen eine Tiefe, die keine Bedeutung trägt |
| **Fachlichkeit** | generisches CRM: Abteilungen, Bewertungen, Kundenkontakte | SHK-Handwerk: Gewerke, Förderung, Auslastung, Angebotsgeschäft | Auftrag dieses Prototyps |
| **Bezeichner** | englisch (`revenue`, `orders`) | deutsch (`umsatz`, `auftraege`) | Die Oberfläche ist deutsch. Ein Bruch in der Mitte erzwingt beim Einbau ständiges Übersetzen |
| **Datensatz** | 25 Mitarbeiter, 200 Kunden, 800 Aufträge, gleichverteilt | 24, 420, 1.900 — mit Saisonalität, Wachstum, Zahlungsverhalten | An einem gleichverteilten Datensatz lässt sich keine Gestaltung prüfen, siehe [DATENMODELL.md](DATENMODELL.md) |
### Anforderungen, die hier nicht umgesetzt sind
Die Planung führt 47 v1-Anforderungen als vollständig. In diesem Prototyp
fehlen vier davon bewusst:
| ID | Anforderung | Warum nicht |
|---|---|---|
| INFRA-03 | Filter-Presets speichern und laden | Bedienkomfort ohne Gestaltungsfrage. Gehört in die echte Software ans Benutzerprofil, nicht in die Vorlage |
| INFRA-04 (teilweise) | Zeitraum mit benutzerdefiniertem Bereich | Die sechs Presets decken die Abstimmung ab; ein Datumsbereichs-Picker ist eine Umsetzungs-, keine Gestaltungsfrage |
| FILT-02 / FILT-04 | Filter für Rechnungsstatus und Betragsbereich | Rechnungsstatus ist auf der Finanzseite bereits als Chart-Dimension sichtbar; ein Betragsfilter verzerrt Summen-KPIs, ohne dass es an der Karte steht |
| CUST-03 | KPI „Kontakte gesamt" | Der Prototyp führt keine Kontakt-Entität. In HiveGrid existiert sie — die Karte lässt sich beim Einbau ergänzen |
Umgekehrt ist **Team** enthalten, obwohl die Planung es unter v2 führt
(TEAM-01 bis TEAM-03): Auslastung ist im Handwerk die Kennzahl, nach der
disponiert wird.
---
## 2. HiveGrid-Pitch-Prototyp
Der Next.js-Prototyp aus dem Kundentermin (Juli 2026). Von dort stammt die
gesamte Oberfläche außerhalb der Diagramme.
| Übernommen | Wo |
|---|---|
| Komponentenbibliothek — 86 Komponenten, 4 Layouts | `ui/components/`, `ui/layouts/` |
| Design-Tokens (Farben, Radien, Schatten, Typo-Skala) | `ui/theme.css`, eingebunden in `app/globals.css` |
| Icon-Set (Feather über `@subframe/core`) | überall, aufgelöst in `components/layout/FeatherIcon.tsx` |
| Stack | Next.js 16 · React 19 · Tailwind v4 — gleiche Versionen |
`ui/` ist **byteidentisch** mit dem Stand im Pitch-Repo und wurde nicht
angefasst. Die Bibliothek wird dort über die Subframe-CLI aus dem Projekt
*Basedesign* synchronisiert:
```bash
npx @subframe/cli@latest sync --all
```
Wer die Bibliothek aktualisiert, sollte das im Pitch-Repo tun und den Ordner
hierher kopieren — sonst laufen die beiden Stände auseinander.
### Was aus der Bibliothek nicht verwendet wird
`ui/components/` enthält eigene Chart-Wrapper (`AreaChart`, `BarChart`,
`LineChart`, `PieChart`). Sie sind hier **nicht** im Einsatz. Sie geben zu
wenig Kontrolle über Achsen, Tooltips und Ringproportionen, um die
Carbon-Regeln umzusetzen. Die Diagramme unter
`features/dashboard/components/charts/` setzen deshalb direkt auf Recharts auf.
Das ist die einzige Stelle, an der dieser Prototyp von der Pitch-Bibliothek
abweicht — überall sonst gilt sie.
---
## 3. IBM Carbon Data Visualization
Grundlage aller Diagramm-Entscheidungen. Ausformuliert in
[DESIGN-SYSTEM.md](DESIGN-SYSTEM.md), als Code hinterlegt in
`features/dashboard/utils/chartTheme.ts`.
Die Farb- und Linienwerte sind aus dem ausgelieferten Stylesheet von
`@carbon/charts` (`dist/styles.css`, Light Theme) übernommen — nicht aus der
Dokumentation abgeschrieben. Das Paket selbst ist **keine Abhängigkeit** dieses
Repos; gezogen wurden nur die Werte.
| Von Carbon | Konkret |
|---|---|
| Kategoriale Palette | 14 Farben in verbindlicher Reihenfolge, immer von vorne vergeben |
| Obergrenze | maximal 5 Kategorien je Chart, Rest über `gruppiereRest()` zu „Sonstige" |
| Achsen | nur horizontale Gitterlinien, keine Tick-Marks, Achsenlinie nur an X |
| Tooltip | dunkle Fläche `#393939`, weißer Text, kein Rahmen |
| Neutrale Werte | Grid `#e0e0e0`, Achse `#8d8d8d`, Text `#161616` / `#525252` |
**Ergänzt, nicht aus Carbon:** die festen Farbzuordnungen je Gewerk und je
Status (`GEWERK_FARBEN`, `STATUS_KATEGORIE_FARBEN`). Carbon regelt die Palette,
nicht welche Kategorie welche Farbe bekommt. Eine Kategorie behält ihre Farbe
über alle Seiten hinweg, damit man Legenden nicht ständig neu liest.
---
## 4. Was aus keiner der drei Quellen stammt
Die SHK-Fachlogik ist für diesen Prototyp entstanden und beim Einbau der Teil,
der mit dem Betrieb abzustimmen ist:
| Thema | Wo | Offene Fachfrage |
|---|---|---|
| Gewerke und Leistungsarten | `data/types.ts` | Schnitt deckungsgleich mit dem Betrieb? |
| Förderquote BAFA/KfW | `calculations.ts` → `foerderQuote()` | Braucht ein Feld am Auftrag |
| Vertriebstrichter nur über Angebotsgeschäft | `vertriebsTrichter()`, Feld `ueberAngebot` | Ab welchem Wert wird schriftlich angeboten? |
| Absprungstufe verlorener Vorgänge | Feld `verlorenAb` | Wird das heute erfasst? |
| Auslastung und ihre Schwellen | `AuslastungsBalken.tsx` | Zählen Rüst- und Fahrzeiten als produktiv? |
Diese Punkte stehen mit Begründung in [HANDOVER.md](../HANDOVER.md) Abschnitt 5
und in [KPI-KATALOG.md](KPI-KATALOG.md).
---
## Zeitleiste
| Wann | Was |
|---|---|
| 18.–22.01.2026 | CRM-Analytics-Click-Dummy mit dem Branchenpartner, UI/UX-Regeln, Anforderungskatalog |
| 22.01.2026 | Dashboard- und Analytics-Bausteine ins HiveGrid-Projekt übernommen |
| Juli 2026 | Pitch-Prototyp mit der Subframe-Bibliothek *Basedesign* |
| 27.08.2026 | Dieses Repo: Planung, Pitch-Bibliothek und Carbon zusammengeführt, fachlich auf SHK zugeschnitten |
+147
View File
@@ -0,0 +1,147 @@
# KPI-Katalog
Jede Kennzahl mit Definition, Berechnung und Fundstelle im Code.
Alle Funktionen liegen in `features/dashboard/data/calculations.ts`.
**Zwei Zeitbezüge sind zu unterscheiden:**
- **zeitraumbezogen** — reagiert auf die Zeitraum-Umschaltung (Umsatz, neue Kunden)
- **stichtagsbezogen** — gilt zum heutigen Tag, unabhängig vom Zeitraum
(offene Forderungen, aktive Aufträge, Auslastung des Bestands)
Das ist Absicht: „Offene Forderungen der letzten 7 Tage" wäre keine sinnvolle
Zahl.
In der Oberfläche steht der Zeitbezug in der **Fußzeile jeder KPI-Karte**
(„im gewählten Zeitraum" / „zum Stichtag" / „über alle Zeiträume"). Karten mit
Vorperiodenvergleich zeigen dort stattdessen den Trend — die Anatomie bleibt
gleich, damit die Reihe eine gemeinsame Grundlinie hat. Diagrammkarten, die
den Zeitraumfilter ignorieren, sagen es im Untertitel.
---
## Übersicht
| KPI | Definition | Bezug | Funktion |
|---|---|---|---|
| Umsatz (Zahlungseingang) | Summe bezahlter Ausgangsrechnungen, gezählt am Zahlungsdatum | Zeitraum | `umsatz()` |
| Trend Umsatz | Vergleich mit dem gleich langen Zeitraum davor | Zeitraum | `umsatzVorperiode()` + `berechneTrend()` |
| Offene Forderungen | Summe aller nicht bezahlten Ausgangsrechnungen | Stichtag | `offenePosten().gesamt` |
| Aktive Aufträge | Vorgänge im Status „beauftragt" oder „in Arbeit" | Stichtag | `aktiveAuftraege()` |
| Ø Auslastung Monteure | Mittel der produktiven Stunden je verfügbarer Stunde | Zeitraum | `teamKennzahlen()` |
| Neue Kunden | Kundendatensätze mit Anlagedatum im Zeitraum | Zeitraum | `neueKunden()` |
| Abschlussquote | Gewonnene Aufträge im Angebotsgeschäft, bezogen auf alle Angebote | Stichtag | `abschlussquote()` |
| Deckungsbeitrag | Nach Auftragswert gewichtete Marge abgeschlossener Aufträge | Zeitraum | `marge()` |
**Umsatzentwicklung** (`umsatzVerlauf()`) bildet zusätzlich den
Vorjahreszeitraum ab: Jede Zahlung wird um zwölf Monate verschoben in denselben
Zeitraster einsortiert. Die Auflösung wechselt automatisch — bis 30 Tage
tageweise, darüber monatsweise.
---
## Finanzen
| KPI | Definition | Bezug | Funktion |
|---|---|---|---|
| Umsatz (bezahlt) | wie oben | Zeitraum | `umsatz()` |
| Offene Forderungen | Status „gestellt" oder „überfällig" | Stichtag | `offenePosten()` |
| Überfällig | Nur Status „überfällig" (Zahlungsziel deutlich überschritten) | Stichtag | `offenePosten().ueberfaellig*` |
| Ø Zahlungsdauer | Mittel aus Rechnungsdatum bis Zahlungseingang | Zeitraum | `zahlungsdauer()` |
| Deckungsbeitrag | siehe oben | Zeitraum | `marge()` |
| Forderungen nach Alter | Buckets ab Fälligkeit: 0–30 / 31–60 / 61–90 / > 90 Tage | Stichtag | `offenePosten().nachAlter` |
| Rechnungsvolumen nach Status | Betragssumme je Rechnungsstatus | Stichtag | `rechnungenNachStatus()` |
| Umsatz nach Gewerk | Bezahlte Rechnungen, zugeordnet über den Auftrag | Zeitraum | `umsatzNachGewerk()` |
| Top-Kunden | Kunden nach bezahltem Gesamtumsatz | gesamt | `topKunden()` |
Die Altersstruktur ist die wichtigste Karte der Seite: Ab etwa 60 Tagen sinkt
die Einbringungsquote erfahrungsgemäß deutlich. Die Balken sind deshalb von
grün nach rot eingefärbt.
---
## Aufträge
| KPI | Definition | Bezug | Funktion |
|---|---|---|---|
| Neue Aufträge | Vorgänge mit Anlagedatum im Zeitraum | Zeitraum | `auftraegeImZeitraum()` |
| Aktive Aufträge | „beauftragt" oder „in Arbeit" | Stichtag | `aktiveAuftraege()` |
| Auftragsbestand | Summe der Werte aktiver Aufträge | Stichtag | abgeleitet |
| Über Termin | Beauftragt oder in Arbeit und Fälligkeit überschritten | Stichtag | `ueberfaelligeAuftraege()` |
| Abschlussquote | siehe Übersicht | Stichtag | `abschlussquote()` |
| Ø Durchlaufzeit | Anlagedatum bis Abschluss, nur abgeschlossene Vorgänge | Stichtag | `durchlaufzeit()` |
| Vertriebstrichter | Vorgänge je erreichter Stufe | Stichtag | `vertriebsTrichter()` |
| Förderquote BAFA/KfW | Anteil **förderfähiger** Aufträge mit beantragter oder bewilligter Förderung | Stichtag | `foerderQuote()` |
**Zum Trichter:** Er zeigt ausschließlich das **Angebotsgeschäft** — Vorhaben
ab etwa 6.000 €, für die schriftlich angeboten wird (Feld `ueberAngebot`).
Wartung, Notdienst und E-Check gehen ohne Angebot direkt in die Ausführung;
zählte man sie mit, würde der Trichter unten breiter als oben.
Verlorene Vorgänge zählen bis zu der Stufe, auf der sie abgesprungen sind
(Feld `verlorenAb`). Ohne diese Unterscheidung wären die oberen Stufen
systematisch zu klein und die Übergangsquoten wertlos.
**Zur Abschlussquote:** gleiche Bezugsgröße wie der Trichter. Sie entspricht
deshalb exakt der Übergangsquote von „Angebot" auf „Beauftragt" — im
Demo-Datensatz rund 38 %.
**Zur Förderquote:** Bezugsgröße sind nur förderfähige Leistungen (Wärmepumpe,
PV-Anlage, Batteriespeicher, Lüftungsanlage). Über alle Aufträge gerechnet läge
sie im einstelligen Bereich und wäre wertlos — eine Badsanierung war nie
förderfähig.
---
## Kunden
| KPI | Definition | Bezug | Funktion |
|---|---|---|---|
| Kunden gesamt | Alle Kundendatensätze | Stichtag | — |
| Neue Kunden | Anlagedatum im Zeitraum | Zeitraum | `neueKunden()` |
| Wiederkehrende Kunden | Anteil mit mehr als einem Auftrag | Stichtag | `wiederkehrquote()` |
| Ø Kundenwert | Bezahlter Umsatz je Kunde mit mindestens einem Auftrag | gesamt | abgeleitet |
| Reaktivierbar | Kunden mit Historie, über 12 Monate ohne Auftrag | Stichtag | `inaktiveKunden()` |
| Kunden nach Quelle / Typ / Region | Verteilungen | Stichtag | `kundenNach()` |
| Auffällige Zahlungsmoral | Ø Zahlungsdauer über 30 Tage | Stichtag | `zahlungsauffaellig()` |
Empfehlung und Bestandskunden zusammen zeigen, wie stark der Betrieb ohne
Werbebudget wächst — im Handwerk die aussagekräftigste Quellenkennzahl.
---
## Team
| KPI | Definition | Bezug | Funktion |
|---|---|---|---|
| Im Einsatz | Gewerblich Beschäftigte mit Status „aktiv" | Stichtag | — |
| Ø Auslastung | Produktive Stunden / verfügbare Stunden (aus Wochenstunden hochgerechnet) | Zeitraum | `teamKennzahlen()` |
| Produktivquote | Auftragsbezogene Stunden / erfasste Stunden | Zeitraum | abgeleitet |
| Erfasste Stunden | Summe aller Zeiteinträge | Zeitraum | abgeleitet |
| Abwesenheit | Stunden für Urlaub, Krankheit, Fortbildung | Zeitraum | abgeleitet |
| Abgeschlossene Aufträge je Mitarbeiter | Anzahl im Zeitraum abgeschlossener Aufträge | Zeitraum | `teamKennzahlen()` |
| Auftragswert je Mitarbeiter | Summe der Werte dieser Aufträge | Zeitraum | `teamKennzahlen()` |
**Auslastungsschwellen** (in `AuslastungsBalken.tsx`, betrieblich festzulegen):
| Bereich | Bedeutung |
|---|---|
| < 70 % | Luft frei — Kapazität ungenutzt |
| 70–95 % | Zielkorridor |
| > 95 % | Ausgebucht — Terminrisiko |
Der Auftragswert je Mitarbeiter ist ausdrücklich **kein Leistungsvergleich**:
Ein Wärmepumpen-Auftrag ist um ein Vielfaches größer als eine Wartung. Der
Hinweis steht als Tooltip an der Karte.
---
## Bewusst nicht enthalten
| Kennzahl | Grund |
|---|---|
| Live-Aktualisierung | Für die Gestaltungsabstimmung ohne Nutzen |
| CSV-/Excel-Export | Reine Umsetzungsfrage, kein Gestaltungsthema |
| Drilldown auf Einzeldatensätze | Der Absprung in die bestehenden Listen genügt |
| E-Mail-Marketing-Kennzahlen | Eigenes Modul (siehe Planungsordner 04) |
| Workflow-Kennzahlen | Eigenes Modul |
+211
View File
@@ -0,0 +1,211 @@
# Leitplanken aus HiveGrid Cloud
Welche Bereiche der Zielanwendung bereits Davids finale Subframe-Screens tragen
und welche noch auf Interim-UI laufen — und was daraus für dieses Dashboard
verbindlich ist.
Warum die Unterscheidung zählt: Ein nachgezogener Screen ist eine
**Design-Entscheidung**, an der man sich ausrichten kann. Eine Interim-UI ist
eine **Notlösung des Devs** — sie kopieren hieße, einen Platzhalter zum Standard
zu erklären.
Stand der Auswertung: 2026-08-29 gegen den lokalen Repo-Stand.
---
## 1. Woran man es im Code erkennt
Der Repo pflegt dafür bewusste Marker. In absteigender Verlässlichkeit:
| Muster | Bedeutung |
|---|---|
| `Interim-UI (AD-28)` im Kopfkommentar | Optik ist ausdrücklich vorläufig |
| `design-nachzug` | offener Punkt — steht auch in sonst fertigen Screens für einzelne Reste |
| Kopfkommentar mit Subframe-Screen-ID (`WF-P8 S03`, `S02/S02L/S03/S05`), Page-UUID oder „gegen Davids Export vom …" | nachgezogen, Optik ist abgestimmt |
| `@subframe/sync-disable` in `apps/web/app/ui/**` | lokal geänderte Komponente, die der Sync nicht überschreiben darf |
Ein typischer Interim-Kopf (`projects/PipelineKanbanPage.tsx`):
> `// R-P8b (P8 configurable Kanban) Interim-UI (AD-28) — pipeline board screen`
> `// (D-KANBAN), composed from ~/ui …`
Ein typischer nachgezogener Kopf (`projects/ProjektListPage.tsx`):
> `// WF-P8 S03 "Projekt-Liste" — rebuilt against Davids Subframe export of`
> `// 2026-07-28 (page 5c5bdb0a-995e-4c22-aa8b-e48333c60297) …`
**Autoritativ ist aber die Liste, nicht der Kommentar:**
`.planning/SUBFRAME-FLOW-INVENTAR.md` trackt jeden Subframe-Flow mit Seitenzahl,
Owner und Stand. Der dortige Hinweis ist wichtig: Der Subframe-Sync pflegt nur
Components, Layouts und Theme — **Pages werden nie automatisch integriert**,
jeder Screen wird einzeln hinter der Loader/Action-Grenze verdrahtet.
**Grenze der Methode:** Ein fehlender Marker sagt nichts. Von 214
`*Page.tsx`-Dateien tragen 92 einen Interim-Marker, 36 einen Subframe-Bezug und
**86 gar keinen** — überwiegend Plattform-Admin (31) und Integrationen (13),
also interne Flächen mit niedrigem Politur-Druck.
---
## 2. Die Abgrenzung
### Nachgezogen — als Leitplanke brauchbar
| Bereich | Stand laut Inventar | Im Code |
|---|---|---|
| **Team-Chat** | 🟢 24 von 24 Screens integriert (Handoff 2026-08-08) | `screens/chat/` — kein einziger Interim-Marker |
| **Form-Builder** | ✅ integriert (PR #71) | `screens/forms/` — Kopf nennt „WF-P16, screens S02/S02L/S03/S05" |
| **Automation / Workflow-Editor** | 🟡 voller Delta-Nachzug (Davids „V1 Vollbild minimal") | `screens/automation/` — 6 Screens mit Subframe-Bezug, keiner interim |
| **Projekt-Pipelines** | 🟡 Kernstrecke S01–S07 integriert | `screens/projects/` — 7 nachgezogen, 4 noch interim (darunter das Kanban selbst) |
| **Aufgaben** | 🟡 Stufen 0–6 fertig | `screens/tasks/` — 2 nachgezogen, 1 interim |
| **Lead-Liste** | 🟡 Kernstrecke (`/leads` = S01, `/leads/inbox` = S02) | `screens/leads/` — 2 nachgezogen, 6 noch interim |
### Vollständig Interim — nicht als Vorbild nehmen
Kein einziger nachgezogener Screen: **HR** (21 Seiten), **Mobile-PWA** (19),
**CRM** (6), **Booking** (5), **DSGVO/Privacy** (5), **E-Mail-Postfach** (4),
**Files** (3), **Kalender** (2), **Audit** (2), Signatur, Plugins, Auth —
**und das Dashboard** (1).
### Noch gar nicht entworfen
Die zehn Auth-Flows (Login/2FA, Passwort-Reset, Set-Password, Admin-RBAC,
Signup, Konto löschen, 2FA verwalten, Passwort ändern, Workspace-Auswahl,
Profil & Sicherheit) stehen im Inventar auf ❌ offen — zusammen 44 Seiten.
Ergänzend: Die 30 Designer-Briefs unter `.planning/designer-briefs/` haben ihren
Block „Davids Delta" durchgehend **leer**. Die tatsächlichen Handoffs werden
über das Flow-Inventar und die Ticket-Docs geführt, nicht über die Briefs —
102 Ticket-Docs führen einen offenen `design-nachzug`.
---
## 3. Was daraus für dieses Dashboard folgt
### 3.1 KPI-Kachel: `StatsCard` ist gesetzt
Die nachgezogenen Screens benutzen durchgehend `~/ui/components/StatsCard`
(Billing-Übersicht, Zahlungsseite, Admin-Health, Admin-Jobs, Admin-Billing).
Die API ist knapp:
```tsx
<StatsCard
trend="neutral" | "positive" | "negative"
icon={<FeatherUsers />}
label={t("…")}
value={formatters.number(totalSeats)}
percentage={t("…")} // "" um den Slot abzuschalten
/>
```
Unsere `KPICard` kann mehr: Sparkline, Akzentfarbe, `richtungGut` (steigender
Wert ist je nach Kennzahl gut oder schlecht), Trendlabel, ⋯-Menü zum Ausblenden.
Das ist fachlich begründet — **aber es ist eine Abweichung vom gesetzten
Baustein.** Beim Einbau gibt es genau zwei saubere Wege:
1. `StatsCard` erweitern und die Datei mit `@subframe/sync-disable` schützen
(so wurde es dort schon einmal gemacht, siehe Kopf der Komponente), oder
2. die eigene Karte bewusst als lokale Komposition begründen.
Was nicht geht: eine zweite KPI-Kachel kommentarlos danebenstellen.
### 3.2 Raster: 16 px, mobil einspaltig
Das durchgängige Muster der nachgezogenen Screens:
```
mobile:grid-cols-1 grid w-full grid-cols-4 gap-4
```
`gap-4` = 16 px = unser Kartenabstand ✓. Das `mobile:`-Präfix ist der
hauseigene Breakpoint und in 44 Stellen belegt — unser Prototyp hat kein
Mobil-Verhalten und muss es beim Einbau ergänzen.
### 3.3 Filterleiste: `FilterBar` statt Eigenbau
`ProjektListPage`, `AutomationListPage`, `AutomationExecutionsPage`,
`FormsAdminPage` und die Betriebsmittel-Übersicht nutzen alle
`~/ui/components/FilterBar`. Unsere `AnalyticsToolbar` ist handgebautes
Tailwind — funktional gleichwertig, aber am gesetzten Baustein vorbei. Das ist
die auffälligste Stelle, an der der Prototyp beim Einbau umgestellt gehört.
Bemerkenswert: Die Betriebsmittel-Übersicht wurde 2026-08-19 genau in diese
Richtung überarbeitet — „auf kompakte Kennzahlen plus echte `FilterBar`
umgestellt". Kennzahlenreihe über Filterleiste über Liste ist dort also bereits
das etablierte Muster. Unser Seitenaufbau folgt derselben Logik.
### 3.4 Weitere Konventionen der fertigen Screens
- Seitenkopf über `PageShell` / `PageHeader` / `PageBody`, nie eigenes Chrome
- i18n mit Namespace: `t("billing:subscriptionOverview.summary.plan")`
- Screen-Komponente ist rein: `({ vm, actions })`, kein `useLoaderData`,
kein `useFetcher` — Navigation und Schreibpfade sind Callbacks
- Tabellen über `~/ui/components/Table`, Dichte über die Komponente wählen
---
## 4. Wo es keine Leitplanke gibt: die Diagramme
Recharts kommt im gesamten Repo an **drei** Stellen vor: die beiden
Dashboard-Diagramme und `leads/LeadAnalyticsPage.tsx`. Es gibt keine geteilte
Palette, kein Chart-Theme, keine Kategorienregel — die vorhandenen Diagramme
setzen ihre Farben direkt im JSX.
Auch Davids Standards decken das nicht ab:
`.planning/INTERIM-UI-DESIGN-STANDARDS.md` regelt Spacing, Control-Höhen,
Dialoggrößen und Zustände sehr genau, **Datenvisualisierung gar nicht**.
Für dieses Dashboard heißt das beides:
- Für Kacheln, Filter, Tabellen und Seitenaufbau **gibt es** einen Standard —
daran halten wir uns, statt ihn neu zu erfinden.
- Für Diagramme gibt es keinen. Was wir in
[DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) und `chartTheme.ts` festlegen, füllt eine
echte Lücke und wird im Zweifel zur Referenz für alle späteren Charts. Das ist
der wertvollste, aber auch der begründungspflichtigste Teil der Übergabe —
deshalb steht zu jeder Regel dort, warum sie so ist.
---
## 5. Selbst nachprüfen
```sh
cd <hivegrid-cloud>
# Interim-Screens
grep -rl "Interim-UI\|AD-28" apps/web/app/components/screens --include='*Page.tsx'
# nachgezogene Screens
grep -rl "Davids\|WF-P[0-9]" apps/web/app/components/screens --include='*Page.tsx'
# autoritative Flow-Liste
cat .planning/SUBFRAME-FLOW-INVENTAR.md
```
Die Zahlen in diesem Dokument sind eine Momentaufnahme. Das Flow-Inventar wird
fortgeschrieben (Stand dort: 2026-08-25, 12. Update) — vor einer Entscheidung
lieber neu zählen.
---
## 6. Wo dieser Prototyp bewusst abweicht
Ein Abgleich mit den nachgezogenen Cloud-Screens fördert drei Stellen zutage, an
denen dieser Prototyp anders arbeitet. Sie stehen hier, damit sie beim Einbau
als **Entscheidung** erkennbar sind und nicht als Versehen wegkorrigiert werden.
| Thema | Cloud | Hier | Begründung |
|---|---|---|---|
| **Zahlen in Tabellen** | linksbündig; `align-right` nur für die Aktionsspalte | rechtsbündig mit `tabular-nums` | IBM Carbon und die eigene Planung (`UI_UX_GUIDELINES.md` §2) verlangen es: Nur rechtsbündig stehen Tausenderstellen untereinander, und genau darum geht es in einem Analytics-Dashboard. Die Cloud-Tabellen zeigen überwiegend Text, nicht Beträge |
| **Filterleiste** | `~/ui` `FilterBar` mit Facetten-Slots | eigene `AnalyticsToolbar` | Der Prototyp braucht ein Zeitraum-Segment-Control, das die FilterBar nicht kennt. Beim Einbau ist das der erste Umbau — die Logik passt fast 1:1 auf `FacetFilterBar` |
| **Formatierung** | `locale/format.ts`, Prozent erwartet 0–1 | eigene `formatters.ts`, Prozent erwartet 0–100 | Der Prototyp hat keine i18n-Schicht. Beim Einbau ersetzen, aber die **Achsenformatierung** (`achsenFormat`, `nettesRaster`) hat dort kein Gegenstück und sollte mitkommen |
Umgekehrt übernommen wurden: keine Versalien in Spaltenköpfen, Raster mit 16 px
Abstand, Kennzahlenreihe über Filterleiste über Liste, Leerzustände an jeder
Visualisierung.
**Offen für den Einbau** (aus dem Cloud-Abgleich, hier bewusst nicht gebaut):
Screens als reine `({ vm, actions })`-Komponenten, Texte über i18n-Keys,
`mobile:`-Verhalten, `data-ui-id` auf allen Flächen, Textalternative je Diagramm.
Das ist Portierungsarbeit, keine Gestaltung — Details in
[ZIELSYSTEM.md](ZIELSYSTEM.md) Abschnitt 3.
+96
View File
@@ -0,0 +1,96 @@
# Navigation und Seitenaufbau
## Struktur
```
Seitenleiste (fest, einklappbar auf Icon-Breite)
├── Analyse
│ ├── Übersicht / Kernkennzahlen, Handlungsbedarf
│ ├── Finanzen /finanzen Umsatz, Forderungen, Zahlungsverhalten
│ ├── Aufträge /auftraege Pipeline, Termine, Förderung
│ ├── Kunden /kunden Akquise, Quellen, Reaktivierung
│ └── Team /team Auslastung, Stunden, Abschlüsse
└── Entwicklung
└── Komponenten /komponenten Bausteine und Regeln (nur für die Umsetzung)
```
**Warum Seitenleiste statt Tab-Leiste:** Die bestehende HiveGrid-Oberfläche
navigiert links. Der Prototyp lässt sich so ohne Umbau der
Informationsarchitektur einsetzen. Einklappen schafft Platz für breite Tabellen
und das fünfspaltige KPI-Raster.
---
## Aufbau jeder Analyse-Seite
```
┌──────────────────────────────────────────────────────────────┐
│ Kopfzeile: Breadcrumb · Suche · Benachrichtigungen · Profil │ fest
├──────────────────────────────────────────────────────────────┤
│ Seitenkopf: Titel · Einordnung Aktualisieren Export ⇗ │
├──────────────────────────────────────────────────────────────┤
│ Filterleiste: Zeitraum │ Dimensionen │ zurücksetzen │ klebt oben
│ Aktive Filter als entfernbare Chips │
│ Ausgeblendete Widgets (nur wenn welche versteckt sind) │
├──────────────────────────────────────────────────────────────┤
│ KPI-Reihe (5 Karten, gleiche Höhe) │
│ [nur Übersicht] Handlungsbedarf über volle Breite │
│ Hauptdiagramm (2/3) │ Nebendiagramm (1/3) │
│ Detaildiagramme (2–3, oben bündig) │
│ Arbeitsliste (Tabelle) │
└──────────────────────────────────────────────────────────────┘
```
Die Reihenfolge folgt der Lesefrage: **Wie steht es? → Was ist zu tun? → Wie hat
es sich entwickelt? → Woran liegt es?**
Auf der **Übersicht** steht der Handlungsbedarf direkt unter der KPI-Reihe und
über die volle Breite. Sie ist die Seite der Geschäftsführung; dort ist „was ist
zu tun" die zweite Frage, nicht die letzte. Jede Zeile trägt Betrag und Handlung
(„Mahnlauf starten", „Nachfassen") — eine Liste ohne Verb ist ein Bericht.
Auf den vier Detailseiten bleibt die Arbeitsliste unten: Dort ist sie die
Konsequenz aus dem, was darüber steht.
**Gleiche Kartenhöhe nur bei gleicher Struktur.** Die KPI-Reihe wird gestreckt,
damit die fünf Karten als ein Objekt lesbar sind. Gemischte Reihen (Diagramm
mit fester Höhe neben Tabelle) stehen auf `items-start` — Strecken erzeugt dort
nur Weißraum.
---
## Interaktionen
| Element | Verhalten |
|---|---|
| **Zeitraum** | Heute · 7 Tage · 30 Tage · Quartal · Jahr · Gesamt. Rechnet alle zeitraumbezogenen Kennzahlen und Charts neu, inklusive Vorperiodenvergleich |
| **Dimensionsfilter** | Mehrfachauswahl mit Suche; wirkt sofort. Nur Filter, die auf der Seite etwas bewirken, werden angeboten |
| **Filter-Chips** | Jede Einschränkung erscheint als Chip und lässt sich einzeln entfernen |
| **Widget ausblenden** | ⋯-Menü an jeder Karte. Ausgeblendete Karten erscheinen in der Filterleiste und lassen sich einzeln oder gesammelt zurückholen |
| **Hover** | Balken, Ringsegmente und Trichterstufen heben sich hervor, die übrigen treten zurück; Verläufe zeigen einen Tooltip mit allen Reihen des Datenpunkts |
| **Handlungsbedarf** | Jede Zeile ist ein Absprung in die zugehörige Arbeitsliste (im Prototyp Attrappe mit Hinweis) |
| **Tastatur** | Fokus ist auf allen interaktiven Elementen sichtbar; Filter-Dropdown und Karten-Menü schließen mit Escape und geben den Fokus zurück |
| **Ringlegende** | Klick hebt ein Segment dauerhaft hervor, erneuter Klick löst es wieder |
| **Tabellen** | Spaltenkopf sortiert, Suchfeld filtert, Blättern über Pfeile |
| **Seitenleiste** | Einklappbar; die Auswahl bleibt beim Seitenwechsel erhalten |
Filterauswahl und ausgeblendete Widgets überleben einen Reload
(`localStorage`, Schlüssel `hivegrid.analytics.*`).
---
## Seitenabhängige Filterrelevanz
Definiert in `features/dashboard/data/filtering.ts`:
| Seite | Zeitraum | Gewerk | Auftragsstatus | Kundenquelle | Region | Mitarbeiter |
|---|:--:|:--:|:--:|:--:|:--:|:--:|
| Übersicht | ✓ | ✓ | | | ✓ | |
| Finanzen | ✓ | ✓ | | ✓ | ✓ | |
| Aufträge | ✓ | ✓ | ✓ | | ✓ | ✓ |
| Kunden | ✓ | ✓ | | ✓ | ✓ | |
| Team | ✓ | ✓ | | | | ✓ |
Ein Filter, der sichtbar ist aber nichts bewirkt, kostet Vertrauen in die
Zahlen. Deshalb werden irrelevante Filter gar nicht erst angeboten — die
Auswahl bleibt aber im Hintergrund erhalten und wirkt beim Seitenwechsel wieder.
+215
View File
@@ -0,0 +1,215 @@
# Zielsystem: HiveGrid Cloud
Wohin dieser Prototyp eingebaut wird, was dort heute schon steht und was beim
Einbau konkret anzupassen ist.
Repo: `HiveGrid-Cloud` (Gitea `git.tool-ctrl.de/toolctrl-labs/HiveGrid-Cloud`).
pnpm-Monorepo, React Router 7, Prisma/Postgres, mandantenfähig mit RLS.
`apps/web` (Tenant- und Admin-App), dazu `api`, `worker`, `desktop`,
`hocuspocus`, `portal-domain-controller` und rund 40 Pakete unter `packages/`.
Größenordnung: 678 Route-Dateien, davon 575 im Core-Manifest.
---
## 1. Der Platz dieses Prototyps
Das Ziel ist der Tenant-Dashboard-Screen unter `…/<tenant>/dashboard`.
Dort liegt bereits eine Implementierung:
| Datei | Was drin steht |
|---|---|
| `apps/web/app/components/screens/dashboard/DashboardPage.tsx` | Widget-Grid, 495 Zeilen |
| `…/DashboardEditPanel.tsx` | Edit-Modus (Widget-Filter/Zeitraum) |
| `…/DashboardDrilldownDialog.tsx` | Drilldown je Kachel |
| `…/DashboardMonthlySeriesChart.tsx`, `…/DashboardStageDistributionChart.tsx` | die zwei vorhandenen Diagramme |
| `apps/web/app/routes/app.$tenant/dashboard.tsx` | Loader, SSE-Stream, Export |
Fachlich ist das die Phase **P11** (Modul M20): drei feste Rollen-Layouts
(**GF · Büro · Monteur**) aus einer Widget-Registry, live über SSE, plus das
Komfort-Delta aus **P30** (Edit-Modus, Drilldown, CSV-/Report-Export,
Realtime-Opt-out).
**Wichtig:** Dieser Stand ist ausdrücklich **Interim-UI nach AD-28** — der Dev
baut aus `~/ui` gegen ein stabiles View-Model, damit das Ticket nicht auf den
finalen Screen wartet. Der Kommentarkopf von `DashboardPage.tsx` sagt es
wörtlich: *„Final Subframe optics = tracked design-nachzug (D-DASH / VD-P30)."*
Genau diese Lücke füllt dieser Prototyp. Der Einbau ist der geplante Nachzug,
kein Umbau gegen den Strom.
---
## 2. Gemeinsame Basis — und wo sie auseinanderläuft
| | Prototyp | HiveGrid Cloud |
|---|---|---|
| Subframe-Projekt | `f023064a5ca0` | `f023064a5ca0` — **dasselbe** |
| Bibliothek | `ui/`, 86 Komponenten | `apps/web/app/ui/`, Alias `~/ui/*`, 92 Komponenten |
| Überschneidung | 82 Komponenten gleich benannt | dieselben 82 |
| Diagramme | Recharts ^2.15.4 | Recharts ^2.15.3 — **dieselbe Bibliothek** |
| Framework | Next.js 16 | React Router 7 |
Die vier Namensunterschiede: `DialogFullscreen` → `FullscreenDialog`,
`DrawerRight` → `Drawer`, `AddressDialog` und `BannerOffline` existieren dort
nicht. Cloud hat zusätzlich `BulkActionBar`, `ConfirmDialog`, `DropdownMenu`,
`ModalBlank`, `ResultDialog`, `BannerPermanent`, `ChatSprechblase`.
Dass Komponentenbibliothek **und** Chart-Bibliothek übereinstimmen, ist der
Grund, warum der Einbau realistisch ein Port ist und keine Neuimplementierung.
---
## 3. Was beim Einbau konkret anders wird
### 3.1 Daten kommen als View-Model-Prop
Der Prototyp importiert `DATENSATZ` direkt. In der Cloud gilt das Muster
`<XPage vm={} actions={} />`: Die Route besitzt Loader, SSE und Actions, die
Screen-Komponente ist rein und bekommt alles als Prop. Kein `useLoaderData`
in der Komponente.
```ts
export type DashboardPageVM = VMState<DashboardPageData>;
type VMState<T> =
| { status: "idle" }
| { status: "loading"; data?: T }
| { status: "ready"; data: T }
| { status: "empty" }
| { status: "error"; data?: T; error: { messageKey: string; code?: string } };
```
Fünf Zustände statt einem. Der Prototyp kennt nur „Daten da"; `loading`,
`empty` und `error` sind je Seite zu ergänzen. Die Chart-Leerzustände
(`ChartLeer`) decken davon nur einen Teil ab.
`DashboardPageData` führt außerdem `activeRole`, `layout`, `generatedAt`,
`streamUrl`, `canCustomize`, `liveUpdatesEnabled`, `layoutSource` und
`catalog` — das Dashboard ist dort rollen- und berechtigungsabhängig, was der
Prototyp nicht abbildet.
### 3.2 Kein eigenes App-Chrome
`AppShell.tsx` aus diesem Prototyp fällt beim Einbau **weg**. Die Cloud-Routen
laufen bereits in der echten App-Shell mit Sidebar, Tenant-Layout und
Breadcrumb (`SubframeAppShell.tsx`, `AppSidebar.tsx`). Screen-Komponenten
dürfen dort ausdrücklich **kein** `~/ui/layouts/DefaultPageLayout` rendern —
das bringt eine eigene Beispiel-Sidebar mit und erzeugt eine doppelte
Navigation. Stattdessen gilt `PageShell` / `PageHeader` / `PageBody` aus
`components/screens/shared/PageShell.tsx`.
Unsere Seitenköpfe und die Filterleiste bleiben, die Rahmen-Navigation nicht.
### 3.3 Texte über i18n
Alle statischen Texte laufen über `react-i18next` (`t("app.dashboard.…")`).
Dieser Prototyp hat deutsche Strings fest im JSX. Das ist beim Einbau
mechanische Arbeit, aber flächendeckend — es betrifft jede Karte, jede
Achsenbeschriftung und jeden Leerzustand.
Die deutschen **Bezeichner im Code** (`umsatz`, `auftraege`) bleiben davon
unberührt und passen zur Cloud, deren UI ebenfalls deutsch ist (EN-Locale erst
in P30).
### 3.4 Barrierefreiheit der Diagramme
In der Cloud sind Charts konsequent `aria-hidden="true"`, und die umgebende
Karte liefert eine Textzusammenfassung plus Datentabelle für Screenreader.
Dieser Prototyp hat das **nicht**. Beim Einbau ist es Pflicht — das Repo fährt
ein a11y-Lint-Baseline-Gate und axe-Tests je Primitive.
### 3.5 Stabile Oberflächen-IDs
Jede relevante Fläche trägt ein `data-ui-id` (`ui.dashboard.widget.…`), erfasst
in `docs/ui-inventory/surfaces.json`. Tests und Agenten adressieren darüber.
Unsere Widget-IDs aus dem `WidgetContext` sind der natürliche Anknüpfungspunkt,
müssen aber auf das dortige Namensschema umgestellt werden.
### 3.6 Spacing passt bereits
`.planning/INTERIM-UI-DESIGN-STANDARDS.md` schreibt fest: Page-Padding **24**,
Card **16**, Grid-Gap **16**, Section-Gap **24**, Field-Gap **16**,
Control-Höhe Medium **36**, Input-Radius **4**. Das deckt sich mit dem
8-px-System in [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) Abschnitt 5 — hier ist
nichts anzupassen.
---
## 4. Was dieser Prototyp dem Zielsystem voraushat
Nicht alles fließt nur in eine Richtung. Drei Dinge fehlen dort heute:
**Ein gemeinsames Chart-Theme.** Die beiden vorhandenen Diagramme setzen
Farben direkt im JSX (`fill="#334155"`, `fill="#cbd5e1"`), jedes mit eigenem
Gitter (`strokeDasharray="3 3"`), eigener Achsenbreite und eigenem
Balkenradius. Es gibt keine geteilte Palette, keine Kategorien-Obergrenze und
keine feste Farbzuordnung. `features/dashboard/utils/chartTheme.ts` ist genau
das, was dort fehlt — und der Teil, der sich am ehesten unabhängig vom Rest
übernehmen lässt.
**Ausformulierte Diagramm-Regeln.** [DESIGN-SYSTEM.md](DESIGN-SYSTEM.md) hat in
der Cloud keine Entsprechung. Die Standards dort regeln Layout, Controls und
Zustände sehr genau, Datenvisualisierung dagegen gar nicht.
**Fachliche Kennzahl-Definitionen.** [KPI-KATALOG.md](KPI-KATALOG.md) mit
Zeitbezug und Berechnung je KPI ist im Zielsystem so nicht vorhanden; die
Widget-Registry beschreibt Kacheln, nicht deren betriebswirtschaftliche
Definition.
---
## 5. Wer entwirft was — der Rest ist noch nicht fertig designt
HiveGrid Cloud ist nach dem **Designer-First-Prinzip** organisiert
(`.planning/DESIGNER-FIRST-PLAYBOOK.md`). Der Ablauf ist verbindlich:
1. Dev/Planung stellt je Phase einen **Designer-Brief** bereit —
*was* das Modul tut und *welche UI-Elemente* vorkommen müssen, **kein Layout**.
2. Der Designer entwirft die Screens in Subframe und darf Spec und UI/UX
**überschreiben** — Anordnung und Layout sind eine Design-Entscheidung.
3. Das **Delta** (geänderte Spec-Punkte, neue UX-Entscheidungen, Subframe-Link)
wird in den Brief eingetragen.
4. Der Dev setzt um und zieht Spec und Doku nach.
Es gibt 31 solcher Briefs (`.planning/designer-briefs/P01…P34.md`) — Auth, CRM,
Lead-Inbox, Projekt-Pipelines, Automation-Flow-Editor, Form-Builder, Plantafel,
Kalender, Telefonie, HR, Billing, Customer-Portal, Help-Center. Für einen
großen Teil davon steht der Delta-Block **noch leer**: Der finale Screen ist
noch nicht entworfen, die Route läuft bis dahin auf Interim-UI.
**Für die Einordnung dieses Prototyps heißt das dreierlei:**
- Das Dashboard ist **ein Baustein unter vielen**, nicht die Leitgestaltung der
Anwendung. Es fügt sich in eine App-Shell und ein Design-System ein, die
bereits stehen.
- Was hier an Anordnung und Raster festgelegt ist, ist ein **Vorschlag**. Die
Layout-Hoheit liegt beim Designer — der Brief zu P11 sagt ausdrücklich
„Anordnung = Davids freie Hand". Verbindlich zu übergeben sind die
**Kennzahl-Definitionen, die Chart-Regeln und das Verhalten**, nicht die
Pixelanordnung.
- Andere Bereiche der Anwendung sehen heute noch nicht final aus, weil sie
entweder auf Interim-UI laufen oder noch beim Designer liegen. Ein Vergleich
„unser Dashboard gegen den aktuellen Screen X" misst deshalb nicht
zwangsläufig gegen den Zielzustand.
---
## 6. Wenn du dir das Zielsystem selbst ansehen willst
```sh
corepack enable && pnpm install
pnpm dev:services # Postgres + Redis
pnpm dev:services:reset # Wegwerf-DB inkl. Migrationen und Demo-Daten
pnpm dev:local
```
Tenant-App auf `http://app.localhost:3000`. Die lokalen Demo-Zugänge (Tenant
„HiveGrid Demo SHK") stehen im README von HiveGrid Cloud unter „App starten" —
sie werden hier bewusst nicht wiederholt. Die Demo-Daten sind zunächst
ausgeblendet und erscheinen über **„Demo-Daten anzeigen"** oder `?demo=show`.
Der Weg zum Dashboard ist nach dem Login die erste Seite der Tenant-App.
Für einen schnellen Überblick ohne Setup:
`docs/ui-inventory/surface-map.md` listet alle stabilen Oberflächen mit ihren
Quelldateien, `docs/route-inventory.md` die Größenordnung des Routenbestands.