refactor(dashboard): Seiten von der Datenquelle entkoppeln

Die Seiten holten ihre Zahlen selbst aus einem globalen Store. HiveGrid Cloud
verlangt reine Screens nach dem Muster <XPage vm={} actions={} /> — jede Seite
hätte beim Einbau auseinandergenommen werden müssen.

Neue Schichtung:

  view-models/<seite>.vm.ts   baue<Seite>VM(daten, filter) → VMState<…>
                              reine Funktion, kein React, kein Store
  pages/<Seite>.tsx           ({ vm, optionen }) → JSX      wird portiert
  routes/<Seite>Route.tsx     Store + Builder + Seite       wird ersetzt

view-models/index.ts spiegelt VMState wortgleich aus @hivegrid/view-models;
beim Einbau ist es ein Import-Wechsel. Der Loader ruft denselben Builder mit
echten Daten, die Seiten bleiben unberührt.

Dabei abgefallen: die fünf Seitenzustände (idle · loading · ready · empty ·
error) über SeitenZustand — bisher lief der Prototyp nur im Zustand "Daten da",
in der Cloud sind alle fünf DoD-Pflicht. Die Filterleiste bekommt ihre
Auswahllisten jetzt als Prop (in der Software die Facetten aus dem Loader)
statt sie selbst aus dem Datensatz zu ziehen.

Store- und Rohdatenzugriff gibt es damit ausschließlich in routes/. Alle sechs
Seiten rendern pixelgleich; typecheck, lint und build sind grün.

Nicht geändert: Next.js bleibt. Im gesamten Projekt gibt es vier
Next-spezifische Importe, alle in AppShell und app/layout — beide Dateien
entfallen beim Einbau ohnehin, weil die Cloud Shell und Root-Layout mitbringt.
Ein Framework-Wechsel wäre Aufwand ohne Ertrag.

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:39:41 +02:00
1 parent b8bf987d0e
commit 99be627dad
28 files changed
+1030 -340

No files matched your search

+9 -4
View File
@@ -204,8 +204,13 @@ Umgekehrt übernommen wurden: keine Versalien in Spaltenköpfen, Raster mit 16 p
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
**Bereits umgesetzt:** Die Seiten sind reine `({ vm })`-Komponenten mit dem
wortgleich gespiegelten `VMState`-Vertrag, die Berechnung liegt in reinen
Buildern unter `view-models/`, und die fünf Seitenzustände sind ausgestaltet.
Store-Zugriff gibt es ausschließlich in `routes/` — genau der Schicht, die beim
Einbau durch den Loader ersetzt wird.
**Offen für den Einbau** (Portierungsarbeit, keine Gestaltung): Texte über
i18n-Keys, `mobile:`-Verhalten, `data-ui-id` auf allen Flächen, Textalternative
je Diagramm, `actions`-Callbacks statt Demo-Toasts. Details in
[ZIELSYSTEM.md](ZIELSYSTEM.md) Abschnitt 3.
+58 -9
View File
@@ -61,12 +61,44 @@ 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
### 3.1 Daten kommen als View-Model-Prop — bereits umgesetzt
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.
**Das ist im Prototyp schon so gebaut.** Jede Analyse-Seite ist eine reine
Komponente:
```tsx
export function UebersichtSeite({ vm }: { vm: VMState<UebersichtDaten> }) {
if (vm.status !== "ready") return <SeitenZustand vm={vm} />;
const d = vm.data;
…
}
```
Die Verdrahtung sitzt in einem eigenen Container unter
`features/dashboard/routes/`, die Berechnung in einem reinen Builder unter
`features/dashboard/view-models/`:
```
view-models/uebersicht.vm.ts baueUebersichtVM(daten, filter) → VMState<UebersichtDaten>
pages/UebersichtSeite.tsx ({ vm }) → JSX ← das ist der Teil, der portiert wird
routes/UebersichtRoute.tsx Store + Builder + Seite ← das ist der Teil, der ersetzt wird
app/page.tsx rendert den Container
```
Beim Einbau tritt der Loader an die Stelle des Containers:
```tsx
// apps/web/app/routes/app.$tenant/dashboard.tsx
export default function Route() {
const { vm } = useLoaderData<LoaderData>();
return <UebersichtSeite vm={vm} actions={{ onAbsprung }} />;
}
```
`features/dashboard/view-models/index.ts` spiegelt `VMState` wortgleich aus
`@hivegrid/view-models`. Der Import wird getauscht, die Seiten bleiben
unberührt. Die fünf Zustände (`idle` · `loading` · `ready` · `empty` · `error`)
sind über `SeitenZustand` bereits ausgestaltet.
```ts
export type DashboardPageVM = VMState<DashboardPageData>;
@@ -83,10 +115,27 @@ 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.
Was der Builder noch **nicht** kennt: `DashboardPageData` führt in der Cloud
zusätzlich `activeRole`, `layout`, `generatedAt`, `streamUrl`, `canCustomize`,
`liveUpdatesEnabled`, `layoutSource` und `catalog`. Das Dashboard ist dort
rollen- und berechtigungsabhängig — diese Felder kommen beim Einbau dazu.
### 3.1a Warum der Prototyp auf Next.js bleibt
Die Frage liegt nahe, weil die Software auf React Router 7 läuft. Ein Umbau
brächte aber nichts: Im gesamten Prototyp gibt es **vier** Next-spezifische
Importe, alle in zwei Dateien —
| Datei | Import | Schicksal beim Einbau |
|---|---|---|
| `components/layout/AppShell.tsx` | `next/link`, `next/navigation` | entfällt, die Cloud bringt Shell und Sidebar mit |
| `app/layout.tsx` | `next`, `next/font/google` | entfällt, Root-Layout gehört der Cloud |
Portiert wird nicht die Anwendung, sondern der Ordner
`features/dashboard/{pages,components,view-models,utils}` — und der enthält
keine Framework-Bindung. React 19, Tailwind v4 und Recharts sind in beiden
Repos dieselben. Ein Framework-Wechsel im Prototyp wäre Aufwand ohne Ertrag und
würde die Gestaltungsarbeit nur verzögern.
### 3.2 Kein eigenes App-Chrome