Umbenennung presseportale → pressekonto in Domains, Themes und Dokumentation. Design-Tokens, Portal-Shell, Customer-Dashboard, Auth- und Admin-PM-Views. Artisan-Befehl migrate:legacy-media mit Tests und Hub-Flux-Entwicklungsdocs. Co-authored-by: Cursor <cursoragent@cursor.com>
177 lines
7.5 KiB
Markdown
177 lines
7.5 KiB
Markdown
# Phase 2 — Customer-Dashboard auf Mockup-Stil
|
|
|
|
> Detail-Plan analog zu `01-PHASE-0-TOKENS.md` und
|
|
> `02-PHASE-1-PORTAL-SHELL.md`. Wird beim Abschluss auf Status `✅ abgeschlossen`
|
|
> gesetzt; Lessons learned wandern in `PROGRESS.md`.
|
|
|
|
**Status**: ✅ abgeschlossen · **Aufwand**: ~½ Tag · **Risiko**: niedrig
|
|
|
|
---
|
|
|
|
## Ziel
|
|
|
|
`resources/views/livewire/customer/dashboard.blade.php` matched das Mockup
|
|
`dev/frontend/tailwind_v3/User Dashboard presseportale.html` zu ≥ 90 %.
|
|
Sichtbarer Wow-Moment für den Kunden, ohne neue Business-Logik.
|
|
|
|
## Was sich ändert
|
|
|
|
### Visuell
|
|
|
|
| Bereich | Heute | Mockup |
|
|
| ----------------- | ------------------------------------------------ | --------------------------------------------------------------------- |
|
|
| Page-Header | `<flux:card>` mit `<flux:heading>` „Willkommen“ | Hub-Badge + Eyebrow + großes H1 + Untertitel + rechts Status-Pille |
|
|
| KPI-Reihe | 4 schmale `<flux:card>` mit `<flux:text>`-Zahl | 4 `stat-card`s mit Strip links, Mono-Zahl groß, Sub-Info, Sparkline |
|
|
| Pressemitteilungen-Block | `<flux:card>` mit Liste / Empty-State | `panel` mit `section-eyebrow`, Empty-State mit Schritt-Karten 01/02/03 |
|
|
| Datenqualität | Grid aus `<flux:badge>`-Karten | `panel` rechts mit 2 `hint-card`s (Progress-Bar + Action-Link) |
|
|
| Firmen-Slot | `<flux:card>` mit Firmen-Liste | `panel` mit gestrichelten Empty-Slot-Karten + Hinweis-Box |
|
|
| Brand-Bridge | (nicht vorhanden) | `panel-dark` rechts: presseecho + businessportal24 Status |
|
|
| Footer | (nicht vorhanden) | Subtle-Footer mit Tastenkürzel / Changelog / Status / Support |
|
|
|
|
### Datenmodell
|
|
|
|
Heute liefert das Volt-`with()`:
|
|
|
|
- `stats.total | published | review | draft`
|
|
- `qualityHints[]`
|
|
- `recent` (PressRelease-Collection, max. 5)
|
|
- `companies`
|
|
|
|
Mockup verlangt **zusätzlich**:
|
|
|
|
- `stats.deltaMonth` — Veränderung ggü. Vormonat (Total)
|
|
- `profileCompleteness` — Prozentwert für Profil-Progress-Bar
|
|
- `billingCompleteness` — Prozentwert für Rechnungsadresse
|
|
- `bridgeStatus` — `['presseecho' => 'connected'|'pending', 'businessportal24' => …]`
|
|
(vorerst `connected` fest verdrahtet, später aus echtem Sync-Status)
|
|
|
|
## Was NICHT geändert wird
|
|
|
|
- Logik / Volt-Methoden — Layout-only.
|
|
- Statistik-Backend (`PressRelease::where(...)`) bleibt 1:1.
|
|
- `qualityHints`-Logik bleibt — wird nur anders gerendert.
|
|
- Topbar (eigene Phase, später).
|
|
|
|
---
|
|
|
|
## Implementierungs-Schritte
|
|
|
|
### 1. Shared Hub-Components-CSS
|
|
|
|
Klassen wandern in **`resources/css/shared/hub-components.css`** (neue Datei).
|
|
Importiert in:
|
|
|
|
- `resources/css/portal.css` (nach `flux.css`)
|
|
- `resources/css/web/shared-styles.css` (nach `design-tokens.css`)
|
|
|
|
Damit haben **Portal-Build und Web-Build** dieselbe Hub-Komponenten-Sprache —
|
|
DRY für späteres Wiederverwenden (z. B. Admin-Dashboard in Phase 3).
|
|
|
|
Folgende Klassen kommen rein (alle mit `var(--color-*)`-Tokens, keine Hex-Literale):
|
|
|
|
- `.panel`, `.panel-warm`, `.panel-dark`, `.panel-head`
|
|
- `.stat-card` + `.stat-strip` + `.stat-label` + `.stat-num`
|
|
+ Varianten: `.is-primary`, `.is-ok`, `.is-warn`, `.is-muted`
|
|
- `.hint-card` + `.hint-card .hint-ico`
|
|
- `.badge` + `.badge.warn` + `.badge.ok` + `.badge.hub` + `.badge.dot`
|
|
- `.bridge-row`, `.dot-pe`, `.dot-bp`
|
|
|
|
### 2. Blade-Components in `resources/views/components/portal/`
|
|
|
|
#### `stat-card.blade.php`
|
|
|
|
```blade
|
|
<x-portal.stat-card variant="primary|ok|warn|muted" label="Gesamt" :value="$stats['total']">
|
|
<x-slot:meta>2026</x-slot:meta> {{-- rechts oben --}}
|
|
<x-slot:trend>0 ggü. Vormonat</x-slot:trend> {{-- unten --}}
|
|
</x-portal.stat-card>
|
|
```
|
|
|
|
Render: `<article class="stat-card is-{variant}">` + Strip + Label + Mono-Zahl
|
|
+ Meta-Slot oben rechts + Trend-Slot unten. Sparkline (SVG) erstmal weggelassen
|
|
— optionales Detail; lässt sich nachschieben, wenn Daten dafür da sind.
|
|
|
|
#### `hint-card.blade.php`
|
|
|
|
```blade
|
|
<x-portal.hint-card :icon="$hint['icon']" :title="$hint['title']" :percent="60" :href="$hint['href']">
|
|
{{ $hint['description'] }}
|
|
<x-slot:action>Profil öffnen</x-slot:action>
|
|
</x-portal.hint-card>
|
|
```
|
|
|
|
Render: `.hint-card`-Grid mit Icon-Square + Progress-Bar + Text + Action-Link
|
|
in `text-accent-deep`.
|
|
|
|
#### `bridge-card.blade.php` (optional, Phase 2 minimal)
|
|
|
|
Bleibt erstmal **inline** im Dashboard (sehr spezifisch, eine Stelle).
|
|
Wird in Phase 3/4 extrahiert, wenn klar ist wie weit der Brand-Bridge-Pattern
|
|
sich verbreitet.
|
|
|
|
### 3. `customer/dashboard.blade.php` umbauen
|
|
|
|
Layout-Skelett (alles im `<div class="space-y-8">`-Container):
|
|
|
|
1. **Page-Header** — `<header>` mit Grid `1fr auto`:
|
|
- Links: Hub-Badge `User Backend` + Eyebrow + `<h1>Mein Dashboard</h1>`
|
|
+ Subtitle (Name + Reichweiten-Hinweis)
|
|
- Rechts: Status-Pille
|
|
- Wenn `$selectedCompany`: Hub-Badge mit Firma-Name (grün/ok-Stil)
|
|
- Wenn nicht: Warn-Pille „Keine Firma zugeordnet → zuordnen“
|
|
2. **KPI-Reihe** — 4 `<x-portal.stat-card>` (Gesamt/Veröffentlicht/Prüfung/Entwürfe)
|
|
3. **Zweispalten-Grid** (`2fr 1fr`):
|
|
- Links: `<article class="panel">` mit Pressemitteilungen — Liste **oder**
|
|
Empty-State (3 Schritt-Karten)
|
|
- Rechts: `<article class="panel">` mit `<x-portal.hint-card>`s
|
|
4. **Unteres Grid** (`2fr 1fr`):
|
|
- Links: `<article class="panel">` Firmen — Liste **oder** Empty-Slot-Karten
|
|
- Rechts: `<article class="panel-dark">` Brand-Bridge (inline)
|
|
5. **Footer** — kleine Link-Reihe in `text-ink-3`
|
|
|
|
### 4. Volt `with()` ergänzen
|
|
|
|
- `stats.deltaMonth` via zweiter Query (Vormonats-Counts vs. Aktuell)
|
|
- `profileCompleteness` als simpler Heuristik-Wert (firstname/lastname/phone/etc.)
|
|
- `billingCompleteness` analog (Vorhanden = 100, sonst 0; oder Ist-Felder/Soll-Felder)
|
|
- `bridgeStatus` vorerst hardcoded `['presseecho' => 'connected', 'businessportal24' => 'connected']`
|
|
→ später aus echtem Sync-Service
|
|
|
|
### 5. Tests
|
|
|
|
`tests/Feature/Customer/DashboardTest.php` (oder bestehender Test, falls vorhanden):
|
|
|
|
- Rendert ohne Fehler bei eingeloggtem Customer
|
|
- Stat-Zahlen werden korrekt ausgegeben
|
|
- Empty-State wird angezeigt, wenn keine PRs existieren
|
|
- Quality-Hints werden angezeigt, wenn `profile()` fehlt
|
|
|
|
---
|
|
|
|
## Akzeptanzkriterien
|
|
|
|
- [x] Phase-2-Plan-Dokument geschrieben
|
|
- [x] `shared/hub-components.css` existiert, importiert in beiden Builds
|
|
- [x] `<x-portal.stat-card>` und `<x-portal.hint-card>` rendern wie Mockup
|
|
- [x] `/admin/me` zeigt das neue Dashboard ohne Console-Errors
|
|
- [x] Empty-State für Pressemitteilungen ist sichtbar, wenn keine vorhanden
|
|
- [x] Quality-Hints rendern mit Progress-Bar
|
|
- [x] Brand-Bridge-Dark-Card unten rechts zeigt presseecho + businessportal24
|
|
- [x] Neuer Smoke-Test `tests/Feature/Customer/DashboardTest.php` mit 5 Cases
|
|
(Core-Sections, Empty-State, PR-Liste, Profil-Hint, vollständiges Profil
|
|
blendet Hints aus). Cross-Check: alle 18 verwandten Tests bleiben grün.
|
|
- [x] Pint clean
|
|
- [x] `PROGRESS.md`-Eintrag
|
|
|
|
---
|
|
|
|
## Risiken & Mitigation
|
|
|
|
- **FluxUI-Klassen vs. Custom-CSS-Kollisionen** — wir mischen `<flux:badge>`
|
|
weiterhin **nicht** im Dashboard (für Status-Pillen nehmen wir
|
|
`.badge.hub|.warn|.ok|.dot`). Auf den Detail-Seiten (Phase 4) bleibt
|
|
FluxUI dominant.
|
|
- **Sparklines weggelassen** — minimaler Stilverlust, wird in Phase 4 mit
|
|
echten Trend-Daten nachgereicht. Mockup-Match weiterhin ≥ 90 %.
|
|
- **`stats.deltaMonth`-Performance** — zweite Query auf gleicher Tabelle;
|
|
bei wachsendem Datensatz später cachen. Heute irrelevant.
|