Rebrand Hub+Flux
This commit is contained in:
parent
0a3e52d603
commit
9b47296cea
130 changed files with 9357 additions and 3345 deletions
|
|
@ -150,7 +150,7 @@ Bearbeiten Sie die Komponenten in `resources/views/livewire/auth/`:
|
|||
|
||||
```php
|
||||
// Beispiel: Login-Komponente anpassen
|
||||
new #[Layout('components.layouts.auth')] class extends Component {
|
||||
new #[Layout('components.layouts.auth.pressekonto')] class extends Component {
|
||||
#[Validate('required|string|email')]
|
||||
public string $email = '';
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,15 @@
|
|||
|
||||
## Phase 2 — Customer-Dashboard auf Mockup-Stil
|
||||
|
||||
**Status**: ⚪ später · **Aufwand**: ~½ Tag · **Risiko**: niedrig
|
||||
**Status**: ✅ **abgeschlossen** (de facto in Phase 1 +
|
||||
verfeinert in 4J) · **Aufwand**: ~½ Tag · **Risiko**: niedrig
|
||||
|
||||
> Während der Phase-1-Migration wurden Page-Header, KPI-Reihe,
|
||||
> 2-Spalten-Grids, `<x-portal.stat-card>`,
|
||||
> `<x-portal.hint-card>` und Brand-Bridge-Dark-Card bereits
|
||||
> umgesetzt. Phase 4J hat die Recent-PM-Liste mit Portal-Pills
|
||||
> + PM-ID-Sub auf den finalen 4H/4I-Pattern-Stand gebracht.
|
||||
> Match zum Mockup ≥ 95 %.
|
||||
|
||||
### Ziel
|
||||
`livewire/customer/dashboard.blade.php` matched das Mockup
|
||||
|
|
@ -40,26 +48,36 @@
|
|||
|
||||
## Phase 3 — Admin-Dashboard konsistent
|
||||
|
||||
**Status**: ⚪ später · **Aufwand**: ~½ Tag · **Risiko**: niedrig
|
||||
**Status**: ✅ **abgeschlossen** (in Phase 1 + 4J)
|
||||
· **Aufwand**: ~½ Tag · **Risiko**: niedrig
|
||||
|
||||
### Ziel
|
||||
`resources/views/admin/dashboard.blade.php` nutzt dasselbe Vokabular wie
|
||||
Customer-Dashboard.
|
||||
|
||||
### Heutiger Stand
|
||||
Reines Tailwind mit `zinc-*`-Klassen, **keine** FluxUI-Komponenten,
|
||||
visuell aus der Zeit gefallen.
|
||||
|
||||
### Schritte
|
||||
- KPI-Karten als `<x-portal.stat-card>`
|
||||
- Wenn Charts vorhanden: über `flux:chart` umsetzen (FluxUI Pro)
|
||||
- Recent-Activity-Liste in `flux:card` mit Hub-Akzenten
|
||||
> Admin-Dashboard nutzt seit Phase 1 dasselbe Vokabular wie
|
||||
> das Customer-Dashboard: Page-Header, 5-KPI-Reihe mit
|
||||
> `<x-portal.stat-card>`, 2-Spalten-Grid (Recent + Pending
|
||||
> Reviews), Newsletter `panel-warm`, Quick-Actions-Grid,
|
||||
> Footer. Phase 4J hat Portal-Pills, PM-ID-Sub und
|
||||
> Inline-Action „Prüfen →" mit `is-row-warn`-Tinting für
|
||||
> die Review-Queue ergänzt.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Listen-/Detail-Pages durchgehen
|
||||
|
||||
**Status**: 🚧 iterativ · **Aufwand**: ~3–5 Tage gesamt · **Risiko**: niedrig
|
||||
**Status**: ✅ **komplett abgeschlossen** (4A–4J)
|
||||
· **Aufwand**: ~3–5 Tage gesamt · **Risiko**: niedrig
|
||||
|
||||
> Alle 10 Sub-Päckchen (4A–4J) sind grün, Build + Pint
|
||||
> sauber, alle relevanten Tests bestehen
|
||||
> (230/231 gesamt — der eine Fail ist der pre-existing
|
||||
> `ApiDocumentationTest` ohne Bezug zur UI-Migration).
|
||||
> Das gesamte Backend (Admin + Customer) nutzt damit
|
||||
> dieselbe Design-Sprache: Page-Header mit
|
||||
> Eyebrow/Badge/Subtitle, `<x-portal.stat-card>`-KPI-Reihen,
|
||||
> `article.panel` als Container, `flux:table` mit Hub-Padding
|
||||
> + Sortierung, `flux:card` nur wo Test-Verträge sie noch
|
||||
> brauchen, sowie die Mockup-Patterns aus 4H/4I/4J
|
||||
> (Saved-Views-Tabs, Active-Chips, Portal-Pills,
|
||||
> Inline-Actions, Row-Tinting, 3-stufige Empty-States).
|
||||
|
||||
### Ziel
|
||||
Alle Volt-Pages im Admin- und Customer-Bereich nutzen denselben Hub-Stil.
|
||||
|
|
@ -73,9 +91,20 @@ Alle Volt-Pages im Admin- und Customer-Bereich nutzen denselben Hub-Stil.
|
|||
siehe `08-PHASE-4B-PRESS-RELEASES-DETAIL.md`
|
||||
- **4C** = Press-Releases Forms (create/edit, Admin + Customer) — ✅ **abgeschlossen**
|
||||
siehe `09-PHASE-4C-PRESS-RELEASES-FORMS.md`
|
||||
- **4D** = Companies (`admin.companies.*`) — ⚪ pending
|
||||
- **4E** = Profile/Settings (`settings.*`, `me.profile`, `me.security`) — ⚪ pending
|
||||
- **4F** = Restliche Admin-Bereiche — ⚪ pending
|
||||
- **4D** = Companies (`admin.companies.*`) — ✅ **abgeschlossen**
|
||||
siehe `10-PHASE-4D-COMPANIES.md`
|
||||
- **4E** = Profile/Settings (`settings.*`, `customer.profile`, `customer.security`) — ✅ **abgeschlossen**
|
||||
siehe `11-PHASE-4E-PROFILE-SETTINGS.md`
|
||||
- **4F** = Restliche Admin-Bereiche (contacts, categories, presets, footer-codes, users, roles, newsletter, reports, invoices, coupons, payments, portal-switcher) — ✅ **abgeschlossen**
|
||||
siehe `13-PHASE-4F-ADMIN-REST.md`
|
||||
- **4G** = Restliche Customer-Bereiche (invoices, tokens, bookings, company-switcher, me/press-kits/*) — ✅ **abgeschlossen**
|
||||
siehe `12-PHASE-4G-CUSTOMER-PORTAL.md`
|
||||
- **4H** = Customer „Meine Pressemitteilungen" auf Mockup-Stil (Counter-Strip, Saved-Views-Tabs, Filter-Chips, Active-Chips, Portal-Pills, Inline-Actions, Row-Tint, 3-fach-Empty-State, Status-Aktionen-Legende) — ✅ **abgeschlossen**
|
||||
siehe `14-PHASE-4H-PRESS-RELEASES-MOCKUP.md`
|
||||
- **4I** = Admin „Pressemitteilungen" — Mockup-Patterns übertragen (Saved-Views-Tabs, Active-Chips, Portal-Pills, Row-Tint, Inline-Actions als Modal-Trigger, reduzierte Spalten, 2-stufiger Empty-State) — ✅ **abgeschlossen**
|
||||
siehe `15-PHASE-4I-ADMIN-PRESS-RELEASES.md`
|
||||
- **4J** = Dashboard-PM-Listen mit 4H/4I-Patterns (Portal-Pills, PM-ID-Sub, Customer + Admin Dashboard, Inline-Action „Prüfen →" für Admin-Review-Queue mit `is-row-warn` Tinting) — ✅ **abgeschlossen**
|
||||
siehe `16-PHASE-4J-DASHBOARD-LISTS.md`
|
||||
|
||||
### Was geändert wird
|
||||
- Page-Header-Struktur (Eyebrow + H1 + Subtitle + Aktions-Bar)
|
||||
|
|
@ -91,72 +120,125 @@ Alle Volt-Pages im Admin- und Customer-Bereich nutzen denselben Hub-Stil.
|
|||
|
||||
## Phase 5 — Dark Mode konsistent
|
||||
|
||||
**Status**: ⚪ später · **Aufwand**: ~½ Tag · **Risiko**: niedrig
|
||||
**Status**: ✅ **abgeschlossen** · **Aufwand**: ~½ Tag (Großteil
|
||||
der Arbeit war Vorbereitung in Phase 0–4) · **Risiko**: niedrig
|
||||
|
||||
### Ziel
|
||||
Dark Mode funktioniert sauber im Portal, **ohne doppelte UI-Pflege**.
|
||||
> Tokens, Switcher und alle Custom-CSS-Komponenten waren
|
||||
> bereits token-basiert vorbereitet — `shared/design-tokens.css`
|
||||
> hat einen kompletten `.dark { … }`-Block (Z. 182–248) mit allen
|
||||
> Surfaces, Hub/Bernstein-Akzenten, Ink-Skala, Status-Farben,
|
||||
> Schatten und `color-scheme: dark`. `hub-components.css` ist
|
||||
> zu 100 % token-basiert und schaltet automatisch um. FluxUI's
|
||||
> `@fluxAppearance` ist in beiden Head-Partials integriert,
|
||||
> Switcher liegt im User-Menü (Desktop + Mobile) sowie in
|
||||
> `settings/appearance.blade.php`. Bei der Inventur fiel nur
|
||||
> ein latenter Bug auf (`--color-ink-deep`-Token existiert
|
||||
> nicht, in `customer/tokens.blade.php` auf
|
||||
> `--color-panel-dark-2` umgestellt). QR-Code in
|
||||
> `customer/security.blade.php` behält bewusst `bg-white` in
|
||||
> beiden Modi (Scan-Tauglichkeit), klarstellender Kommentar
|
||||
> ergänzt.
|
||||
>
|
||||
> Siehe `17-PHASE-5-DARK-MODE.md` für Details.
|
||||
|
||||
### Quelle
|
||||
`dev/frontend/tailwind_v3/User Dashboard presseportale Dark.html` hat
|
||||
alle Dark-Tokens bereits vorgegeben.
|
||||
`dev/frontend/tailwind_v3/User Dashboard presseportale Dark.html`
|
||||
hat alle Dark-Tokens bereits vorgegeben — alle Werte sind in
|
||||
`design-tokens.css` übernommen.
|
||||
|
||||
### Schritte
|
||||
1. In `shared/design-tokens.css` Dark-Werte ergänzen:
|
||||
```css
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@theme {
|
||||
--color-bg: #0E1218;
|
||||
--color-bg-elev: #14181F;
|
||||
--color-hub: #5A78C2;
|
||||
--color-accent: #D9A560;
|
||||
/* … */
|
||||
}
|
||||
}
|
||||
.dark {
|
||||
/* gleiche Werte für expliziten Switch */
|
||||
}
|
||||
```
|
||||
2. `class="dark"` wird **nicht** mehr hardcoded gesetzt
|
||||
3. Flux Appearance-Switcher (`settings/appearance.blade.php`) steuert
|
||||
`.dark` auf `<html>`
|
||||
4. Hub-Frontend bleibt erstmal Light-Only (so wie heute)
|
||||
|
||||
### Erwartung
|
||||
- Settings → Appearance → „Dark" schaltet Portal um
|
||||
- Settings → Appearance → „System" folgt OS-Präferenz
|
||||
- Sidebar, Topbar, Dashboards, Listen, Forms — alles funktioniert
|
||||
- Hub-Landing und Hub-Auth bleiben Light
|
||||
### Was geliefert wird
|
||||
- Settings → Erscheinung → „Dunkel" schaltet Portal um
|
||||
- Settings → Erscheinung → „System" folgt OS-Präferenz
|
||||
- Switcher zusätzlich direkt im User-Menü (Sidebar + Mobile)
|
||||
- Sidebar, Topbar, Dashboards, Listen, Forms — alles schaltet
|
||||
automatisch über Token-Cascade um
|
||||
- `panel-dark` bleibt KONSTANT dunkel (Brand-Bridge-Card)
|
||||
- `--color-accent-warm` bleibt KONSTANT Bernstein (Hint-Cards)
|
||||
- Hub-Landing und Hub-Auth bleiben Light (kein
|
||||
`@fluxAppearance` im Web-Build)
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Auth-Konsolidierung (optional)
|
||||
## Phase 6 — Auth-Cleanup
|
||||
|
||||
**Status**: ⚪ optional · **Aufwand**: 0–1 Tag · **Risiko**: mittel
|
||||
**Status**: ✅ **abgeschlossen** · **Aufwand**: ~30 min · **Risiko**: niedrig
|
||||
|
||||
### Frage
|
||||
Bleibt der Hub-Login (`auth/pressekonto`-Layout, Web-Build) so wie er ist?
|
||||
Oder konsolidieren wir auf den Portal-Build?
|
||||
> Hub-Auth bleibt eigenständig (auf Web-Build mit dem `pressekonto`-Layout),
|
||||
> weil Hub-Atmosphäre + Light-Bundle als Trade-off bewusst gewollt sind.
|
||||
> Die ungenutzten Starter-Kit-Layouts wurden entfernt: `auth/simple`,
|
||||
> `auth/split`, `auth/card`, der Wrapper `auth.blade.php`, das alternative
|
||||
> `app/header.blade.php` sowie die Debug-Views `livewire/auth/login-simple`
|
||||
> und `test-simple`. Damit gibt es keine hardcoded `class="dark"`-Reste
|
||||
> mehr, die Phase 5 hätten torpedieren können. Doku-Beispiel in
|
||||
> `_docs/FORTIFY-SANCTUM-SETUP.md` auf `components.layouts.auth.pressekonto`
|
||||
> umgestellt. CSS-Bundle ist dabei sogar ~2.4 KB kleiner geworden.
|
||||
>
|
||||
> Siehe `18-PHASE-6-AUTH-CLEANUP.md` für die Inventur.
|
||||
|
||||
### Pro Konsolidierung
|
||||
- Ein Build weniger
|
||||
- Konsistente Komponenten-Sprache
|
||||
### Optional für später (NICHT Teil von Phase 6)
|
||||
Eine echte **Auth-Konsolidierung** (Hub-Auth-CSS in den Portal-Build
|
||||
ziehen, Web-Build nur für die Landing-Pages behalten) wäre denkbar,
|
||||
aber:
|
||||
- Hub-Atmosphäre (Konzentrische Kreise, Hub-Grid) ist visuell stark
|
||||
- Hub-Auth ist leichtgewichtig (kein FluxUI im Bundle)
|
||||
- Aktueller Stand funktioniert sauber
|
||||
|
||||
### Pro Beibehaltung (heutiger Stand)
|
||||
- Hub-Atmosphäre der Auth-Seiten (Konzentrische Kreise, Hub-Grid) ist
|
||||
visuell sehr stark — würde verloren gehen
|
||||
- Hub-Auth ist **leichtgewichtig** (kein FluxUI im Bundle)
|
||||
- Vorlage `Login pressekonto A3 Tailwind.html` ist bewusst minimalistisch
|
||||
→ keine Action notwendig, bleibt offen.
|
||||
|
||||
### Entscheidung
|
||||
Vermutlich **beibehalten**. Aber: Die alten Auth-Layouts
|
||||
`auth/simple.blade.php`, `auth/split.blade.php`, `auth/card.blade.php` aus
|
||||
dem Starter-Kit können vermutlich gelöscht werden, sobald geprüft ist,
|
||||
dass keine Komponente sie noch verwendet.
|
||||
---
|
||||
|
||||
### Schritte (wenn konsolidiert)
|
||||
- Hub-Auth-CSS in `portal.css` ziehen (mit `@source` für die Tokens)
|
||||
- `auth/pressekonto`-Layout auf Portal-Build umstellen
|
||||
- Hub-Auth-Klassen (`.auth-card`, `.auth-grid`, `.auth-btn-primary`)
|
||||
bleiben — laufen nur jetzt aus dem Portal-Bundle
|
||||
- `theme-pressekonto.css` aus dem Web-Build entfernen (oder behalten
|
||||
für die Landing)
|
||||
## Gesamt-Status (Stand 2026-05-20)
|
||||
|
||||
| Phase | Inhalt | Status |
|
||||
|---|---|---|
|
||||
| 0 | Hub-Auth (Login/Register) im Hub-Stil | ✅ |
|
||||
| 1 | Portal-Migration (Tokens, FluxUI-Overrides, Sidebar/Topbar, App-Shell, Dark-Switch im User-Menü) | ✅ |
|
||||
| 2 | Customer-Dashboard auf Mockup-Stil | ✅ (in P1, verfeinert in 4J) |
|
||||
| 3 | Admin-Dashboard konsistent | ✅ (in P1, verfeinert in 4J) |
|
||||
| 4 | Listen/Detail durchgehen (4A–4J) | ✅ **komplett** |
|
||||
| 5 | Dark Mode konsistent | ✅ **abgeschlossen** |
|
||||
| 6 | Auth-Cleanup | ✅ **abgeschlossen** |
|
||||
|
||||
### Phase 4 — Sub-Päckchen im Detail
|
||||
|
||||
| ID | Bereich | Plan-Doc |
|
||||
|---|---|---|
|
||||
| 4A | Press-Releases Listen (Admin + Customer) | `07-PHASE-4A-…` |
|
||||
| 4B | Press-Releases Detail/Show | `08-PHASE-4B-…` |
|
||||
| 4C | Press-Releases Forms (create/edit) | `09-PHASE-4C-…` |
|
||||
| 4D | Companies (Admin) | `10-PHASE-4D-…` |
|
||||
| 4E | Profile/Settings (Admin + Customer) | `11-PHASE-4E-…` |
|
||||
| 4F | Restliche Admin-Bereiche (contacts, categories, presets, footer-codes, users, roles, newsletter, reports, invoices, coupons, payments, portal-switcher) | `13-PHASE-4F-…` |
|
||||
| 4G | Restliche Customer-Bereiche (invoices, tokens, bookings, company-switcher, press-kits) | `12-PHASE-4G-…` |
|
||||
| 4H | Customer „Meine Pressemitteilungen" Mockup-Stil (Counter-Strip, Saved-Views, Filter-/Active-Chips, Portal-Pills, Inline-Actions, Row-Tint, 3-stufiger Empty-State, Aktionen-Legende) | `14-PHASE-4H-…` |
|
||||
| 4I | Admin „Pressemitteilungen" — Mockup-Patterns übertragen | `15-PHASE-4I-…` |
|
||||
| 4J | Dashboard-PM-Listen mit 4H/4I-Patterns | `16-PHASE-4J-…` |
|
||||
|
||||
### Nächste sinnvolle Schritte (Priorität)
|
||||
|
||||
> Die hub-flux-Roadmap ist mit Phase 6 **vollständig** abgeschlossen.
|
||||
> Alle weiteren Themen sind eigene Initiativen.
|
||||
|
||||
1. **Manueller Dark-Mode Smoke-Test**: Im Browser User-Menü →
|
||||
Erscheinung → „Dunkel" und durch die Hauptseiten klicken
|
||||
(Dashboard, Listen, Detail, Security mit QR, Tokens). Erwartung:
|
||||
Lesbar + konsistent. Kleine Polish-Runde, falls visuelle
|
||||
Auffälligkeiten.
|
||||
|
||||
2. **PM-Form-Wizard-Refactor**: Mockup
|
||||
`User Neue Mitteilung presseportale.html` auf den bestehenden
|
||||
Press-Release-Create/Edit-Flow übertragen. Größere Aktion mit
|
||||
eigener Phase.
|
||||
|
||||
3. **Web-Frontend-Block** (eigenständig, NICHT Teil von Phase 1–6):
|
||||
Die noch ungenutzten Mockups
|
||||
`Startseite Tailwind.html`,
|
||||
`Startseite presseecho Tailwind.html`,
|
||||
`Branchenseite Tailwind.html`,
|
||||
`Detailseite Tailwind.html`,
|
||||
`Veröffentlichen Tailwind.html`
|
||||
sind Public-Facing-Pages der beiden Portale (businessportal24 +
|
||||
presseecho), nicht Portal/Backend. Würde eine **eigene Roadmap**
|
||||
bekommen, weil andere Build-Pipeline (`vite.web.config.js`), andere
|
||||
Komponenten und ein ganz anderer Stil-Stack
|
||||
(Source-Serif-Headlines, Brand-orange für businessportal24).
|
||||
|
|
|
|||
79
dev/frontend/hub-flux/10-PHASE-4D-COMPANIES.md
Normal file
79
dev/frontend/hub-flux/10-PHASE-4D-COMPANIES.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Phase 4D — Companies (admin)
|
||||
|
||||
> Viertes Päckchen aus Phase 4. Folgt auf 4A/4B/4C
|
||||
> (Press-Releases-Strecke komplett).
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~½ Tag · **Risiko**: niedrig–mittel
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
- `resources/views/livewire/admin/companies/index.blade.php` (573 Z.)
|
||||
- `resources/views/livewire/admin/companies/show.blade.php` (400 Z.)
|
||||
- `resources/views/livewire/admin/companies/edit.blade.php` (412 Z.)
|
||||
- `resources/views/livewire/admin/companies/create.blade.php` (281 Z.)
|
||||
|
||||
NICHT in diesem Päckchen:
|
||||
- `admin/contacts/*` — eigener Bereich, Päckchen 4F.
|
||||
- `me/press-kits/*` — Customer-Sicht auf Firmen, eigener Päckchen-Anteil.
|
||||
- Settings / Profile (Päckchen 4E).
|
||||
|
||||
## Ziel
|
||||
|
||||
Alle vier Companies-Pages im Hub-Vokabular — gleiches Muster wie
|
||||
die Press-Releases-Strecke:
|
||||
|
||||
- **Page-Header** mit Hub-Badge „Admin Backend" + Eyebrow + H1 +
|
||||
Subtitle. Bei Show/Edit zusätzlich Portal-Pille (Presseecho /
|
||||
Businessportal24 / Both) und ID/Slug-Hinweis.
|
||||
- **KPI-Reihe** auf Index als `<x-portal.stat-card>` falls Stats
|
||||
vorhanden (Gesamt, je Portal, ggf. mit/ohne PMs).
|
||||
- **Filter-Bar** als `.panel` mit `.panel-head` „Filter & Suche".
|
||||
- **Tabelle/Listen** in `.panel` mit Hub-Badges
|
||||
(`.badge.hub|ok|warn`).
|
||||
- **Form-Sektionen** (Edit, Create) als `.panel` mit `.panel-head`
|
||||
und `section-eyebrow`.
|
||||
- **Tabs** (Show: „contacts", andere?) bleiben als FluxUI-Tabs —
|
||||
optisch über das Token-Bridging genug Hub-Look.
|
||||
- **Confirm-Modals** unverändert lassen (z.B. delete-confirm).
|
||||
|
||||
## Was explizit NICHT angefasst wird
|
||||
|
||||
- **Volt-Logik** in allen 4 Files — Layout-only.
|
||||
- **`deleteCompany`**-Methode + Confirm-Modal — Test
|
||||
`UserManagementTest` assertiert Redirect-Verhalten.
|
||||
- **Wortlaute, die Tests prüfen**:
|
||||
- `Firmen` (Sidebar-Link), `Portal`, `Alle Portale`,
|
||||
`Firmen PM Zaehler GmbH` (Faktur-Name).
|
||||
- Portal-Labels `Presseecho`, `Businessportal24`.
|
||||
- URL-Pfade `/admin/companies/{id}`.
|
||||
- **FluxUI Form-Felder, Combobox, Tab-Groups** bleiben.
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] Plan
|
||||
- [x] Index: Page-Header + Stats + Filter-Panel + Tabellen-Panel +
|
||||
Hub-Badges.
|
||||
- [x] Show: Page-Header (mit Portal-Pille) + Logo-/Meta-Block +
|
||||
Tabs + Hub-Badges.
|
||||
- [x] Edit: Page-Header (mit Portal-Pille) + Form-Panels +
|
||||
Aktions-Panel. Delete-Modal unverändert.
|
||||
- [x] Create: Page-Header + Form-Panels + Aktions-Panel.
|
||||
- [x] `UserManagementTest`, `PortalAssetManifestTest` grün
|
||||
(25 Tests, 227 Assertions). Volle Suite: 230 grün
|
||||
(Vorhandener `ApiDocumentationTest`-Fail nicht von uns).
|
||||
- [x] Build + Pint + PROGRESS.
|
||||
|
||||
## Notes
|
||||
|
||||
- Tabs als schlankes Bottom-Border-Pattern statt FluxUI-Tabs:
|
||||
spart Layout-Komplexität und ist optisch näher am Hub.
|
||||
- Portal-Badges bewusst alle als `badge hub` vereinheitlicht —
|
||||
Portal-spezifische Farben (purple/blue) brachen aus dem
|
||||
Tokensystem aus. Portal-Label bleibt sichtbar im Text.
|
||||
- Logo-Boxen folgen jetzt dem gleichen Token-Pattern wie auf den
|
||||
Press-Release-Detail-Seiten (`var(--color-bg-elev)` +
|
||||
`var(--color-bg-rule)` border).
|
||||
- Delete-Confirm-Modal komplett unverändert (Test prüft Redirect
|
||||
nach `deleteCompany`, nicht UI).
|
||||
103
dev/frontend/hub-flux/11-PHASE-4E-PROFILE-SETTINGS.md
Normal file
103
dev/frontend/hub-flux/11-PHASE-4E-PROFILE-SETTINGS.md
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
# Phase 4E — Profile & Settings
|
||||
|
||||
> Fünftes Päckchen aus Phase 4. Folgt auf 4D Companies.
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~½ Tag · **Risiko**: niedrig
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
**Admin / „/settings/*" (alle Rollen)**
|
||||
|
||||
- `resources/views/livewire/settings/profile.blade.php` (117 Z.)
|
||||
- `resources/views/livewire/settings/password.blade.php` (82 Z.)
|
||||
- `resources/views/livewire/settings/appearance.blade.php` (21 Z.)
|
||||
- `resources/views/livewire/settings/delete-user-form.blade.php`
|
||||
(59 Z., wird in profile.blade.php als Komponente eingebunden)
|
||||
|
||||
**Customer („Mein Bereich")**
|
||||
|
||||
- `resources/views/livewire/customer/profile.blade.php` (451 Z.)
|
||||
- `resources/views/livewire/customer/security.blade.php` (295 Z.)
|
||||
|
||||
NICHT in diesem Päckchen:
|
||||
|
||||
- `customer/invoices.blade.php`, `customer/tokens.blade.php`,
|
||||
`customer/bookings.blade.php`, `customer/company-switcher` —
|
||||
eigenes Päckchen 4F.
|
||||
- 2FA-Pages aus Fortify-Standard-Setup — bleiben FluxUI bis sie
|
||||
separat angepackt werden.
|
||||
|
||||
## Ziel
|
||||
|
||||
Beide Settings-Strecken im Hub-Vokabular wie Press-Releases /
|
||||
Companies:
|
||||
|
||||
- **Page-Header** mit Rolle-Pille („Admin Backend" oder
|
||||
„User Backend") + Eyebrow + H1 + Subtitle.
|
||||
- **Form-Sektionen** als `.panel` mit `.panel-head` und
|
||||
`section-eyebrow`.
|
||||
- **FluxUI-Form-Felder** bleiben (`flux:field`, `flux:input`,
|
||||
`flux:label`, `flux:error`, `flux:checkbox`, `flux:radio`,
|
||||
`flux:textarea`, `flux:button`).
|
||||
- **Required-Marker** auf Hub-Token (`text-[color:var(--color-err)]`).
|
||||
- **Save-Action** in eigenem Panel-Footer mit Save-Indicator-Span.
|
||||
- **Flash-/Success-Messages** auf Hub-Token-Pillen.
|
||||
- **Danger-Zone** (Delete-Account, Sessions löschen) als
|
||||
`.panel` mit `is-danger`-Akzent (linker Roter Strip).
|
||||
|
||||
## Was explizit NICHT angefasst wird
|
||||
|
||||
- Volt-Logik in allen Dateien.
|
||||
- `<x-settings.layout>` Wrapper-Komponente (falls vorhanden) —
|
||||
Layout-only-Änderungen, keine Hülle.
|
||||
- 2FA-/Confirm-Password-Modals.
|
||||
- Test-relevante Strings auf customer/profile + customer/security:
|
||||
- **profile**: „Rechnungsadresse"
|
||||
- **security**: „Konto-Sicherheit", „Letzter Login",
|
||||
„Aktive Sessions", „Passwort ändern", „E-Mail-Adresse ändern",
|
||||
„Zwei-Faktor-Authentifizierung"
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] Plan
|
||||
- [x] `partials/settings-heading` zentral auf Hub-Page-Header.
|
||||
- [x] `components/settings/layout` zentral auf Hub-Sidebar +
|
||||
Content-Panel.
|
||||
- [x] `settings/appearance` Hub-styled (profitiert vom neuen
|
||||
Wrapper, kein Body-Touch nötig).
|
||||
- [x] `settings/profile` Hub-styled + Verification-Hinweis als
|
||||
Hub-Warn-Box + Save-Button mit Token-„Saved."-Pill.
|
||||
- [x] `settings/delete-user-form` als Hub-Danger-Box mit
|
||||
linkem Roten Strip. Modal-Markup unverändert.
|
||||
- [x] `settings/password` Hub-styled (Save-Bar mit Border-Top).
|
||||
- [x] `customer/profile` Hub-styled: Header, 3 Panels (Konto,
|
||||
Profil, Rechnungsadresse), Aktionen-Panel, Zugeordnete-Firmen-
|
||||
Panel mit Hub-Badges.
|
||||
- [x] `customer/security` Hub-styled: Header, 4-spaltige
|
||||
KPI-Reihe, Passwort+E-Mail 2-Col-Grid, 2FA-Panel mit
|
||||
Hub-Recovery-Codes, Sessions-Panel mit Hub-Empty-State.
|
||||
- [x] Tests grün:
|
||||
- `Settings|CustomerProfileSecurity|CustomerCompanyContext`
|
||||
→ 33 passed (146 assertions).
|
||||
- Volle Suite → 230 passed, 3 skipped, 1 pre-existing
|
||||
`ApiDocumentationTest`-Fail (nicht von 4E).
|
||||
- [x] Build + Pint + PROGRESS.
|
||||
|
||||
## Notes
|
||||
|
||||
- Der zentrale Umbau der beiden Wrapper (`settings-heading`,
|
||||
`settings.layout`) hat sich bezahlt gemacht: die drei
|
||||
`settings/*`-Pages sind weitgehend identisch klein geblieben,
|
||||
Page-Header und Side-Nav kommen automatisch im Hub-Look.
|
||||
- `<x-action-message>` für „Saved."-Toast bleibt drin, aber
|
||||
der Slot ist auf einen Hub-getönten `<span>` umgestellt.
|
||||
- Empty-State für „Aktive Sessions" nutzt das gleiche
|
||||
Hub-Icon-Box-Pattern wie auf den Press-Releases-Listen.
|
||||
- 2FA-QR-Container behält explizit weißen Hintergrund —
|
||||
QR-Codes brauchen hohen Kontrast unabhängig vom Mode.
|
||||
- Alle Test-Pflicht-Strings unverändert: „Rechnungsadresse",
|
||||
„Konto-Sicherheit", „Letzter Login", „Aktive Sessions",
|
||||
„Passwort ändern", „E-Mail-Adresse ändern",
|
||||
„Zwei-Faktor-Authentifizierung".
|
||||
78
dev/frontend/hub-flux/12-PHASE-4G-CUSTOMER-PORTAL.md
Normal file
78
dev/frontend/hub-flux/12-PHASE-4G-CUSTOMER-PORTAL.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# Phase 4G — Customer Portal (Mein Bereich)
|
||||
|
||||
> Sechstes Päckchen aus Phase 4. Folgt auf 4E
|
||||
> (Profile/Settings).
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~½ Tag · **Risiko**: niedrig–mittel
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
**Liste / Detail / Helper**:
|
||||
|
||||
- `resources/views/livewire/customer/invoices.blade.php` (194 Z.)
|
||||
- `resources/views/livewire/customer/tokens.blade.php` (212 Z.)
|
||||
- `resources/views/livewire/customer/bookings.blade.php` (52 Z.)
|
||||
- `resources/views/livewire/customer/company-switcher.blade.php`
|
||||
(91 Z., sitzt in der Sidebar)
|
||||
- `resources/views/livewire/customer/press-kits/index.blade.php`
|
||||
(119 Z.)
|
||||
- `resources/views/livewire/customer/press-kits/show.blade.php`
|
||||
(734 Z., großes Detail-Cockpit)
|
||||
|
||||
NICHT in diesem Päckchen:
|
||||
|
||||
- `customer/press-releases/*` — schon in 4A/4B/4C.
|
||||
- `customer/dashboard.blade.php` — schon in Phase 2.
|
||||
- `customer/profile.blade.php`, `customer/security.blade.php` —
|
||||
schon in 4E.
|
||||
- Admin-Bereich (Päckchen 4F).
|
||||
|
||||
## Ziel
|
||||
|
||||
Alle Customer-Pages im Hub-Vokabular:
|
||||
|
||||
- **Page-Header** „User Backend"-Pille + Eyebrow
|
||||
„Mein Bereich · …" + H1 + Subtitle.
|
||||
- **KPI/Summary-Reihe** wo Stats existieren
|
||||
(`<x-portal.stat-card>`).
|
||||
- **Tabellen, Listen, Forms** in `.panel` mit `.panel-head`.
|
||||
- **Flash-Boxen** (success/info/warn/error) auf Hub-Token.
|
||||
- **Hub-Badges** statt `flux:badge` (außer dort wo speziell
|
||||
begründet).
|
||||
- **Empty-States** als Hub-Icon-Box.
|
||||
- **Company-Switcher**: bleibt funktional, optisch Hub-Akzent.
|
||||
|
||||
## Was explizit NICHT angefasst wird
|
||||
|
||||
- Volt-Logik in allen Dateien.
|
||||
- `<flux:select variant="combobox">`, `<flux:input>` usw.
|
||||
- Konfirm-/Edit-Modals in `press-kits/show.blade.php` (falls
|
||||
vorhanden).
|
||||
- Bestimmte Strings, die Tests assertieren:
|
||||
- **tokens**: „API-Tokens" (Heading),
|
||||
„API-Tokens werden erst freigeschaltet" (Hinweis).
|
||||
- **invoices**: „Hinweis zu Rechnungen",
|
||||
„Rechnungsadresse im Profil pflegen", „Öffnen".
|
||||
- **company-switcher**: „Firma öffnen",
|
||||
`route('me.press-kits.show', …)`.
|
||||
- **press-kits.show**: „Abrechnung", „Statistik" (Tab-Labels
|
||||
bzw. Section-Headings).
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] Plan
|
||||
- [x] `customer/invoices`
|
||||
- [x] `customer/tokens`
|
||||
- [x] `customer/bookings` (Coming-Soon-Stub)
|
||||
- [x] `customer/company-switcher` (Sidebar-Komponente)
|
||||
- [x] `customer/press-kits/index`
|
||||
- [x] `customer/press-kits/show` (Stammdaten/Kontakte/PMs/
|
||||
Abrechnung/Statistik im Hub-Stil; Forms inline behalten)
|
||||
- [x] `CustomerPortalTest`, `CustomerCompanyContextTest`,
|
||||
`PanelConsolidationTest` grün (29 Tests, 131 Assertions).
|
||||
- [x] Volle Suite: 230 passed, 3 skipped, 1 pre-existing fail
|
||||
(`ApiDocumentationTest`, fehlende `docs/api/v1.yml`).
|
||||
- [x] Build (portal: 418 kB CSS, 44 kB JS) + Pint clean +
|
||||
PROGRESS.
|
||||
84
dev/frontend/hub-flux/13-PHASE-4F-ADMIN-REST.md
Normal file
84
dev/frontend/hub-flux/13-PHASE-4F-ADMIN-REST.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Phase 4F — Restliche Admin-Bereiche
|
||||
|
||||
> Siebtes Päckchen aus Phase 4. Folgt auf 4G
|
||||
> (Customer Portal).
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~1,5–2 Tage · **Risiko**: niedrig
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
Insgesamt ~7.500 Z. Blade. Aufgeteilt in 4 Sub-Päckchen:
|
||||
|
||||
### 4F-1 — Stammdaten & Switcher (~1.250 Z.)
|
||||
|
||||
- `admin/presets/{index,create,edit}.blade.php` (361 Z.)
|
||||
- `admin/presets/partials/form-fields.blade.php`
|
||||
- `admin/categories/{index,create,edit}.blade.php` (813 Z.)
|
||||
- `admin/portal-switcher.blade.php` (68 Z., Sidebar-Komponente)
|
||||
|
||||
### 4F-2 — Pressekontakte (~1.360 Z.)
|
||||
|
||||
- `admin/contacts/index.blade.php` (729 Z.)
|
||||
- `admin/contacts/create.blade.php` (275 Z.)
|
||||
- `admin/contacts/edit.blade.php` (352 Z.)
|
||||
|
||||
### 4F-3 — Operations & Finance (~1.870 Z.)
|
||||
|
||||
- `admin/footer-codes/{index,create,edit}.blade.php` (673 Z.)
|
||||
- `admin/reports/slow-requests.blade.php` + table (288 Z.)
|
||||
- `admin/invoices/index.blade.php` (314 Z.)
|
||||
- `admin/coupons/index.blade.php` (43 Z., Stub)
|
||||
- `admin/payments/index.blade.php` (53 Z., Stub)
|
||||
|
||||
### 4F-4 — User-Verwaltung (~3.020 Z.)
|
||||
|
||||
- `admin/users.blade.php` (939 Z.) +
|
||||
`admin/users/{create,edit,show,table}.blade.php`
|
||||
- `admin/roles/{index,create,edit}.blade.php` (393 Z.)
|
||||
- `admin/newsletter/sync.blade.php` (171 Z.)
|
||||
|
||||
## Ziel
|
||||
|
||||
Alle Admin-Pages im Hub-Vokabular:
|
||||
|
||||
- **Page-Header** „Admin Backend"-Pille + Eyebrow
|
||||
„Administration · …" + H1 + Subtitle.
|
||||
- **KPI/Summary-Reihe** wo Stats existieren
|
||||
(`<x-portal.stat-card>`).
|
||||
- **Tabellen, Listen, Forms** in `.panel` mit `.panel-head`.
|
||||
- **Flash-Boxen** (success/info/warn/error) auf Hub-Token.
|
||||
- **Hub-Badges** statt `flux:badge` (außer in begründeten
|
||||
Fällen).
|
||||
- **Empty-States** als Hub-Icon-Box.
|
||||
- **Danger-Zones** mit linkem roten Strip.
|
||||
|
||||
## Was explizit NICHT angefasst wird
|
||||
|
||||
- Volt-Logik in allen Dateien.
|
||||
- `<flux:input>`, `<flux:select>`, `<flux:checkbox>`,
|
||||
`<flux:textarea>`, `<flux:field>`, `<flux:error>`,
|
||||
`<flux:table.*>`, `<flux:button>`, `<flux:modal>` etc.
|
||||
- Modals und ihre Bestätigungs-Flows.
|
||||
- Test-relevante Strings — vor jedem Sub-Päckchen werden die
|
||||
betreffenden Test-Assertions geprüft.
|
||||
|
||||
## Tests pro Sub-Päckchen
|
||||
|
||||
| Sub | Tests |
|
||||
|---|---|
|
||||
| 4F-1 | `CategoryIndexPerformanceTest`, `AdminCategoryManagementTest`, `AdminPresetManagementTest` |
|
||||
| 4F-2 | (keine spezifischen Contact-Tests gefunden, evtl. via `AdminAssetManifestTest` indirekt) |
|
||||
| 4F-3 | `AdminFooterCodeManagementTest`, `AdminSlowRequestReportTest`, `AdminSlowRequestLoggingTest`, `AdminLegacyInvoiceArchiveTest` |
|
||||
| 4F-4 | `UserManagementTest`, `UserImpersonationTest`, `RoleManagementTest` |
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] Plan
|
||||
- [x] 4F-1 Stammdaten & Switcher
|
||||
- [x] 4F-2 Pressekontakte
|
||||
- [x] 4F-3 Operations & Finance
|
||||
- [x] 4F-4 User-Verwaltung
|
||||
- [x] Tests pro Sub-Päckchen grün
|
||||
- [x] Build + Pint + PROGRESS.
|
||||
171
dev/frontend/hub-flux/14-PHASE-4H-PRESS-RELEASES-MOCKUP.md
Normal file
171
dev/frontend/hub-flux/14-PHASE-4H-PRESS-RELEASES-MOCKUP.md
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
# Phase 4H — Customer „Meine Pressemitteilungen" auf Mockup-Stil
|
||||
|
||||
> Nachgelagertes Päckchen aus Phase 4. Hebelt das wichtigste
|
||||
> Customer-Arbeitstool auf das volle Mockup-Vokabular.
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~1 Tag · **Risiko**: niedrig
|
||||
|
||||
---
|
||||
|
||||
## Anlass
|
||||
|
||||
`customer/press-releases/index.blade.php` ist heute auf
|
||||
~50 % Mockup-Match: Page-Header, Filter-Panel, Tabelle und
|
||||
Empty-State sind Hub-styled, aber alle hochwirksamen Patterns
|
||||
aus dem Mockup fehlen. Diese Seite ist die meistbesuchte des
|
||||
Customer-Bereichs (primäres Arbeitstool).
|
||||
|
||||
## Vorlage
|
||||
|
||||
`dev/frontend/tailwind_v3/User Pressemitteilungen presseportale.html`
|
||||
|
||||
## Match-Ziel
|
||||
|
||||
≥ 90 % zum Mockup, ohne Backend-API-Erweiterungen.
|
||||
|
||||
---
|
||||
|
||||
## Scope — was umgesetzt wird
|
||||
|
||||
### 1. Counter-Strip im Header
|
||||
|
||||
Statt langer Subzeile: Inline-Counter pro Status direkt
|
||||
unter H1.
|
||||
|
||||
`24 Mitteilungen · 18 veröffentlicht · 1 in Prüfung · 4 Entwürfe · 1 abgelehnt`
|
||||
|
||||
Zahlen kommen aus aggregiertem `withCount()` über die Status-Enum.
|
||||
|
||||
### 2. Saved-Views-Tabs
|
||||
|
||||
Tab-Leiste statt Status-Dropdown:
|
||||
|
||||
`Alle (24) · Veröffentlicht (18) · Entwürfe (4) · In Prüfung (1) · Abgelehnt (1) · Archiv (0)`
|
||||
|
||||
Aktive Tab unterstrichen mit Hub-Blau. Klick → `statusFilter`-Volt-Property.
|
||||
|
||||
### 3. Filter-Chips mit Caret
|
||||
|
||||
Status/Portal/Firma/Zeitraum als Chips mit Dropdown-Caret.
|
||||
Aktive Chip in Hub-Blau, inaktive auf weißem Background.
|
||||
|
||||
**Hinweis**: Status-Chip ist im UI redundant mit Saved-Views-Tabs;
|
||||
wir lassen ihn weg. Bleiben: Portal, Firma, Zeitraum (`erstellt` ab Datum X).
|
||||
|
||||
### 4. Active-Chips (entfernbare Filter)
|
||||
|
||||
Unter den Filter-Chips: Sichtbarmachung aktiver Filter mit
|
||||
`×`-Button zum Entfernen.
|
||||
|
||||
### 5. Bulk-Selection (nur UI-Vorbereitung)
|
||||
|
||||
Checkbox in Header und pro Zeile. Header-Checkbox kann
|
||||
indeterminate-State haben. **Keine Bulk-Aktionen** im Scope —
|
||||
das wird erst später ein eigenes Päckchen (Bulk-Approve etc.).
|
||||
|
||||
### 6. Portal-Pills mit farbigem Dot
|
||||
|
||||
Pro Zeile in Spalte „Portal": je nach `portal`-Enum:
|
||||
- `presseecho` → grüner Dot
|
||||
- `businessportal24` → orangener Dot
|
||||
- `both` → beide Pills nebeneinander
|
||||
|
||||
### 7. Inline-Actions neben Status
|
||||
|
||||
Statusspalte bekommt **zusätzlich** zur Badge eine kleine
|
||||
Inline-Action je nach Status:
|
||||
|
||||
| Status | Inline-Action |
|
||||
|---|---|
|
||||
| Draft | `Zur Prüfung →` |
|
||||
| Review | `Zurückziehen →` (nur wenn `withdrawFromReview` existiert, sonst weglassen) |
|
||||
| Published | — (keine Inline-Action, nur Hauptaktion „Vorschau") |
|
||||
| Rejected | `Grund ansehen →` (Modal/Tooltip mit `rejection_reason`) |
|
||||
| Archived | — |
|
||||
|
||||
### 8. Row-Tinting
|
||||
|
||||
Zeilen mit Status `review` bekommen `is-row-warn` (sehr
|
||||
dezent gelb beim Hover), `rejected` bekommen `is-row-err`
|
||||
(rot beim Hover).
|
||||
|
||||
### 9. Reichere Zeilen-Sub
|
||||
|
||||
Sub-Zeile unter Titel: `PM-Nummer · Datum · Hinweis`
|
||||
Wir nutzen vorhandene Felder: `id` (als „PM-{id}"), `published_at`,
|
||||
`rejection_reason`-Existenz.
|
||||
|
||||
### 10. Datum mit Sub
|
||||
|
||||
Datumsspalte zweizeilig: Datum (Mono-Font, tabular-nums) +
|
||||
Sub (Status-Kontext z.B. „veröffentlicht · 09:12").
|
||||
|
||||
### 11. 3-fach Empty-State
|
||||
|
||||
Drei Varianten je nach Kontext:
|
||||
|
||||
- **`empty-filter`** — wenn Filter aktiv: „Keine Mitteilungen mit diesen Filtern" + Reset-Button
|
||||
- **`empty-search`** — wenn `search` gesetzt: „Keine Treffer für `…`" + Suche-Reset
|
||||
- **`empty-none`** — wenn gar keine PMs überhaupt: „Noch keine Pressemitteilungen" + 3-Schritt-Onboarding
|
||||
|
||||
### 12. Status-Aktionen-Legende
|
||||
|
||||
`panel-warm` unter der Tabelle: 5-Spalten-Grid mit allen
|
||||
Aktionen pro Status. Reduziert Support-Anfragen.
|
||||
|
||||
### 13. Spalten-/Export-Buttons (rechts im Header)
|
||||
|
||||
„Spalten" (jetzt non-functional als `bald`-Badge) und „Export"
|
||||
(`bald`-Badge) als sekundäre Buttons. Optional — können auch
|
||||
weggelassen werden, da wir keine Spalten-Konfiguration haben.
|
||||
|
||||
→ **Entscheidung**: weglassen. Kein UX-Mehrwert ohne Backend.
|
||||
|
||||
---
|
||||
|
||||
## Was NICHT angefasst wird
|
||||
|
||||
- Volt-Logik (`submitForReview`, `sort`, `with`) — nur erweitert
|
||||
um Aggregate-Counts.
|
||||
- Backend-Endpoints / API.
|
||||
- Detail-/Show-Seite und Create/Edit-Forms.
|
||||
- Routen.
|
||||
|
||||
## Neue / erweiterte CSS-Klassen in `hub-components.css`
|
||||
|
||||
- `.counter-strip`, `.counter-strip .seg`, `.counter-strip .sep`
|
||||
- `.view-tab` (mit `.is-active`, `.cnt`)
|
||||
- `.filter-chip` (mit `.is-active`, `.caret`)
|
||||
- `.active-chip` (mit `.x`)
|
||||
- `.portal-pill` (mit `.pe`, `.bp`, `.pdot`)
|
||||
- `.inline-action` (mit `.warn`, `.err`)
|
||||
- `.is-row-warn`, `.is-row-err` (Row-Tinting)
|
||||
- `.empty-stage`, `.empty-ico`, `.empty-title`, `.empty-sub`
|
||||
- `.badge.muted` (für Draft / Archiv)
|
||||
- Optional: `.checkbox.indeterminate` (für Bulk-Header)
|
||||
|
||||
## Volt-Erweiterungen
|
||||
|
||||
- `statusCounts` (assoc-Array Status-Value → int) — aggregiert
|
||||
über `withoutGlobalScopes()->where('user_id', …)->groupBy('status')`
|
||||
- `totalCount` (int) — Gesamt-Counter unabhängig vom Filter
|
||||
- Saved-View-Klick mappt auf `statusFilter` (kein eigener Property nötig)
|
||||
|
||||
## Tests
|
||||
|
||||
Keine bestehenden Tests für `customer/press-releases/index`
|
||||
gefunden. Akzeptanz:
|
||||
|
||||
- `php artisan test --compact` läuft sauber (modulo
|
||||
pre-existing `ApiDocumentationTest`)
|
||||
- Build (`npm run build:portal`) clean
|
||||
- Pint clean
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] Plan
|
||||
- [x] CSS-Klassen ergänzt
|
||||
- [x] Volt-Counts ergänzt
|
||||
- [x] Markup auf Mockup-Stil
|
||||
- [x] Build + Pint + Tests grün
|
||||
- [x] PROGRESS.md + 03-WEITERE-PHASEN.md aktualisiert
|
||||
136
dev/frontend/hub-flux/15-PHASE-4I-ADMIN-PRESS-RELEASES.md
Normal file
136
dev/frontend/hub-flux/15-PHASE-4I-ADMIN-PRESS-RELEASES.md
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
# Phase 4I — Admin „Pressemitteilungen" Mockup-Patterns
|
||||
|
||||
> Folgepäckchen zu 4H. Überträgt die in der Customer-PM-Liste
|
||||
> entwickelten Mockup-Patterns auf die Admin-Seite. Die
|
||||
> CSS-Komponenten existieren bereits.
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~½ Tag · **Risiko**: niedrig
|
||||
|
||||
---
|
||||
|
||||
## Anlass
|
||||
|
||||
Nach 4H matched `customer/press-releases/index.blade.php` das
|
||||
Mockup zu ~90 %. Die gleichen Patterns gehören in die
|
||||
Admin-Liste — das ist der zentrale Editorial-Workflow.
|
||||
|
||||
## Aktueller Stand
|
||||
|
||||
`admin/press-releases/index.blade.php` ist bei ~60 % Match:
|
||||
|
||||
- ✅ Page-Header (Hub-Stil)
|
||||
- ✅ 4-KPI-Reihe (`<x-portal.stat-card>`)
|
||||
- ✅ Filter-Panel (search/status/portal/language/category +
|
||||
3 entity-comboboxes)
|
||||
- ✅ Tabellen-Panel + Empty-State (Hub-styled)
|
||||
- ✅ Modals (publish/reject/archive) — test-kritisch
|
||||
- ❌ Saved-Views-Tabs
|
||||
- ❌ Active-Chips für aktive Filter
|
||||
- ❌ Portal-Pills (statt Text)
|
||||
- ❌ Row-Tinting (review/rejected)
|
||||
- ❌ Inline-Actions („Veröffentlichen →", „Ablehnen →" neben Status)
|
||||
- ❌ Reichere Sub-Zeilen (PM-{id} + Datum + Status-Kontext)
|
||||
|
||||
## Scope
|
||||
|
||||
### 1. Saved-Views-Tabs
|
||||
|
||||
Tab-Leiste zwischen KPI-Reihe und Filter:
|
||||
|
||||
`Alle · Veröffentlicht · In Prüfung · Entwürfe · Abgelehnt · Archiv`
|
||||
|
||||
Counter-Pille pro Tab basierend auf erweiterten `stats`.
|
||||
|
||||
### 2. Active-Chips
|
||||
|
||||
Sichtbarkeit aller aktiven Filter (Status, Portal, Sprache,
|
||||
Kategorie, User, Firma, Kontakt, Suche), jeweils mit `×`.
|
||||
|
||||
### 3. Portal-Pills
|
||||
|
||||
Statt „Presseecho"/„Businessportal24"/„Beide" als Text:
|
||||
Portal-Pills mit farbigem Dot. Bei `Portal::Both` zwei
|
||||
Pills nebeneinander.
|
||||
|
||||
### 4. Row-Tinting
|
||||
|
||||
`is-row-warn` für review, `is-row-err` für rejected.
|
||||
|
||||
### 5. Inline-Actions
|
||||
|
||||
**Zusätzlich** zu den bestehenden Action-Buttons + Modals:
|
||||
|
||||
| Status | Inline-Action |
|
||||
|---|---|
|
||||
| Review | `Freigeben →` + `Ablehnen →` (öffnet die Modals) |
|
||||
| Published | `Archivieren →` (öffnet Modal) |
|
||||
|
||||
Die Inline-Actions sind **nur** Trigger für die bestehenden
|
||||
Modals (keine direkte Wire-Methode), damit die existierenden
|
||||
Test-Modal-Strings („Pressemitteilung veröffentlichen?")
|
||||
weiter angezeigt werden. Action-Buttons in der `Aktionen`-
|
||||
Spalte bleiben als Backup für Power-User.
|
||||
|
||||
### 6. Reichere Sub-Zeilen
|
||||
|
||||
In Titel-Zelle: `PM-{id}` als Sub. In Datum-Zelle: Hauptdatum
|
||||
+ Status-Kontext (`erstellt · 11:02`, `veröffentlicht · 14:30`, …).
|
||||
|
||||
### 7. Counter-Strip ausgelassen
|
||||
|
||||
KPI-Reihe ist bereits 4-spaltig (Gesamt, Veröffentlicht, In
|
||||
Prüfung, Entwürfe) und voll funktional. Counter-Strip wäre
|
||||
redundant. Bleibt bei KPI-Cards.
|
||||
|
||||
## Was bleibt unangetastet
|
||||
|
||||
- **Volt-Methoden** (`publish`, `reject`, `archive`, `sort`,
|
||||
`with`, `clearUserFilter`, …)
|
||||
- **Bestehende Modals** mit Strings „Pressemitteilung
|
||||
veröffentlichen?", „Pressemitteilung ablehnen?",
|
||||
„Pressemitteilung archivieren?" (Test-kritisch)
|
||||
- **Filter-Panel-Struktur** (Entity-Comboboxes erhalten,
|
||||
kein Reduktionsexperiment)
|
||||
- **`stats`-Cache-Methode** (`AdminPerformanceCache`)
|
||||
|
||||
## Erweiterung
|
||||
|
||||
- `pressReleaseStats()` erweitert um `rejected` und `archived`
|
||||
Counter, damit alle 6 Saved-Views-Tabs ihre Counter haben
|
||||
|
||||
## Tests
|
||||
|
||||
- `AdminPressReleaseActionsTest` (3 Tests, prüft auf Modal-Strings)
|
||||
- `PressReleaseWorkflowTest` (mehrere Tests, prüft auf
|
||||
`publish`/`reject`-Methoden + Audit-Logs)
|
||||
- Volle Suite muss weiter sauber laufen (modulo
|
||||
pre-existing `ApiDocumentationTest`)
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] Plan
|
||||
- [x] Volt: `stats` um `rejected` + `archived` erweitert
|
||||
- [x] Volt: `setView()` + `resetFilters()` ergänzt
|
||||
- [x] Saved-Views-Tabs eingebaut (6 Stück inkl. Counter)
|
||||
- [x] Active-Chips eingebaut (8 Filter-Typen)
|
||||
- [x] Portal-Pills statt Text (incl. Both → 2 Pills)
|
||||
- [x] Row-Tinting (review → warn, rejected → err)
|
||||
- [x] Inline-Actions als Modal-Trigger
|
||||
(Freigeben/Ablehnen/Archivieren) zusätzlich neben Badge
|
||||
- [x] Sub-Zeilen: PM-ID + Firma + Sprache, Datum + Status-Kontext
|
||||
- [x] Empty-State 2-stufig (mit/ohne Filter)
|
||||
- [x] Build + Pint + Tests grün (16/16 PR + 230/231 voll)
|
||||
- [x] PROGRESS.md + 03-WEITERE-PHASEN.md aktualisiert
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
- `@php(...)` Inline-Form führte beim Compile zu einem
|
||||
Syntax-Fehler im kompilierten View (führte zu
|
||||
`<?php($var = ...)` ohne Whitespace), obwohl alle Klammern
|
||||
balanced waren. Vermutung: Konflikt der Klammer-Zählung
|
||||
mit Methoden-Calls. Lösung: alle drei `@php(...)`
|
||||
Statements zu `@php ... @endphp` umgewandelt.
|
||||
- Tabellen-Spalten reduziert (8 → 7), weil die rechte
|
||||
„Aktionen"-Spalte mit den Status-Icons inhaltlich
|
||||
redundant zur neuen Inline-Action in der Status-Spalte
|
||||
war. Die linke „Aktionen"-Spalte (view/edit) blieb.
|
||||
63
dev/frontend/hub-flux/16-PHASE-4J-DASHBOARD-LISTS.md
Normal file
63
dev/frontend/hub-flux/16-PHASE-4J-DASHBOARD-LISTS.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Phase 4J — Dashboard-PM-Listen mit 4H/4I-Patterns
|
||||
|
||||
> Mini-Folgepäckchen: Die in 4H/4I entwickelten
|
||||
> Mockup-Patterns (Portal-Pills, Status-Sub-Zeilen,
|
||||
> Inline-Actions) werden auf die kompakten PM-Listen
|
||||
> in den Dashboards übertragen.
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~30 min · **Risiko**: niedrig
|
||||
|
||||
---
|
||||
|
||||
## Anlass
|
||||
|
||||
Nach 4H/4I sehen die Voll-Listen exakt wie das Mockup aus —
|
||||
die Dashboard-Listen ("Meine letzten Pressemitteilungen",
|
||||
"Letzte Pressemitteilungen", "Zur Prüfung") sind aber noch
|
||||
auf dem alten Stand (Badge rechts, Sub-Zeile ohne Portal).
|
||||
Das wirkt inkonsistent.
|
||||
|
||||
## Scope
|
||||
|
||||
### Customer-Dashboard (Volt `customer/dashboard.blade.php`)
|
||||
|
||||
- **Volt-Component**: `recent` lädt aktuell
|
||||
`['id', 'title', 'status', 'company_id', 'created_at']`.
|
||||
→ `portal` ergänzen.
|
||||
- **Markup `recent`-Liste**:
|
||||
- Portal-Pills (presseecho/businessportal24) neben dem Badge
|
||||
- Sub-Zeile mit PM-ID + Firma + Datum (statt nur Firma + Datum)
|
||||
|
||||
### Admin-Dashboard (Controller-View `admin/dashboard.blade.php`)
|
||||
|
||||
- **`recentPRs`-Liste**:
|
||||
- Portal-Pills neben dem Badge
|
||||
- Sub-Zeile mit PM-ID + Firma + User + Datum
|
||||
- **`pendingReviews`-Liste**:
|
||||
- Portal-Pills neben Titel
|
||||
- Inline-Action „Prüfen →" als Link zur Show-Page
|
||||
(im Dashboard keine direkte `publish`/`reject`-Methode
|
||||
möglich, da Controller+Blade statt Volt)
|
||||
|
||||
## Was bleibt unangetastet
|
||||
|
||||
- Backend-Logik (Controller, Volt-`with()`)
|
||||
- Customer-Tests (`DashboardTest`) — die geprüften
|
||||
Strings („Phase 2 Demo Release", „Alle anzeigen",
|
||||
„Meine letzten Pressemitteilungen", …) bleiben
|
||||
- Quick-Actions, Newsletter, Brand-Bridge — bereits gut
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] Plan
|
||||
- [x] Customer-Volt: `portal` + `published_at` in
|
||||
Recent-Select-Liste
|
||||
- [x] Customer-Dashboard: Portal-Pills + PM-ID-Sub
|
||||
+ published_at als Primärdatum bei Published
|
||||
- [x] Admin-Dashboard recentPRs: Portal-Pills + PM-ID-Sub,
|
||||
`badgeClass` mit `muted`-Variante (statt `hub` für
|
||||
archived/draft)
|
||||
- [x] Admin-Dashboard pendingReviews: Portal-Pills +
|
||||
Inline-Action „Prüfen →" + `is-row-warn` Tinting
|
||||
- [x] Build + Pint + Tests grün (5/5 Dashboard, 230/231 voll)
|
||||
- [x] PROGRESS.md + 03-WEITERE-PHASEN.md aktualisiert
|
||||
152
dev/frontend/hub-flux/17-PHASE-5-DARK-MODE.md
Normal file
152
dev/frontend/hub-flux/17-PHASE-5-DARK-MODE.md
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
# Phase 5 — Dark Mode
|
||||
|
||||
> Dark Mode für das Portal (Customer + Admin), gesteuert
|
||||
> über den FluxUI Appearance-Switcher. Hub-Frontend bleibt
|
||||
> Light-Only.
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~½ Tag
|
||||
(meiste Arbeit war Vorbereitung in Phase 0–4) · **Risiko**: niedrig
|
||||
|
||||
---
|
||||
|
||||
## Befund
|
||||
|
||||
Beim Start in Phase 5 war faktisch ~95 % der Arbeit schon
|
||||
in den vorherigen Phasen erledigt worden. Konkret:
|
||||
|
||||
### Was bereits da war
|
||||
|
||||
1. **`shared/design-tokens.css`** hat einen kompletten
|
||||
`.dark { … }`-Block (Z. 182–248) mit:
|
||||
- Surfaces (bg, bg-elev, bg-rule, bg-card, bg-card-warm)
|
||||
- Hub-Blau (heller im Dark Mode: `#5a78c2`)
|
||||
- Bernstein (heller: `#d9a560`), `--color-accent-warm`
|
||||
bewusst konstant
|
||||
- Ink-Skala (invertiert)
|
||||
- Status-Farben (ok/warn/err in helleren Varianten)
|
||||
- Bridge-Dots (heller)
|
||||
- Schatten (neutral schwarz statt warm-blau)
|
||||
- `color-scheme: dark` für native Controls
|
||||
|
||||
2. **`portal.css`** hat:
|
||||
- `@custom-variant dark (&:where(.dark, .dark *))` für
|
||||
Tailwind-`dark:`-Variants
|
||||
- Dark-Mode Shadow-Overrides für FluxUI-Primary-Buttons
|
||||
- Klare Erläuterung, warum kein Notfall-`.dark { … }`-Hack
|
||||
mehr nötig ist
|
||||
|
||||
3. **`shared/hub-components.css`** ist **zu 100 %
|
||||
token-basiert** (panels, badges, view-tabs, filter-chips,
|
||||
active-chips, portal-pills, inline-actions, empty-stage,
|
||||
counter-strip). Sobald die Tokens umschalten, schalten
|
||||
alle Komponenten automatisch um.
|
||||
|
||||
4. **`panel-dark`** nutzt `--color-panel-dark-2` /
|
||||
`--color-panel-dark` — die sind in beiden Modi
|
||||
identisch. Die Brand-Bridge-Card bleibt damit immer
|
||||
dunkel.
|
||||
|
||||
5. **`@fluxAppearance`** ist in beiden Head-Partials
|
||||
eingebunden (`partials/head.blade.php` +
|
||||
`partials/admin-head.blade.php`).
|
||||
|
||||
6. **Switcher** ist an drei Stellen verfügbar:
|
||||
- Sidebar Desktop User-Menü
|
||||
(`components/layouts/app/sidebar.blade.php`)
|
||||
- Sidebar Mobile User-Menü (gleiche Datei)
|
||||
- `settings/appearance.blade.php` (volle Card mit
|
||||
Light/Dark/System)
|
||||
Alle drei nutzen `x-model="$flux.appearance"` —
|
||||
FluxUI's Magic-Object, das LocalStorage-persistent ist
|
||||
und automatisch `class="dark"` auf `<html>` setzt.
|
||||
|
||||
### Was zu fixen war
|
||||
|
||||
Bei der Inventur fielen nur zwei Stellen auf, die im
|
||||
Dark Mode brechen würden:
|
||||
|
||||
1. **`customer/tokens.blade.php` Z. 137**: Token-Anzeige
|
||||
nach Generierung nutzte ein nicht existentes
|
||||
`--color-ink-deep`-Token. Damit war der Hintergrund
|
||||
bisher schlicht transparent (Light: kaum sichtbar,
|
||||
Dark: ebenso). Außerdem würde `--color-ink` im Dark
|
||||
Mode hell werden — weißer Text auf hellem Bg wäre
|
||||
unlesbar. Fix: auf `--color-panel-dark-2` (konstant
|
||||
dunkel) umstellen.
|
||||
|
||||
2. **`customer/security.blade.php` Z. 270**: 2FA-QR-Code
|
||||
in `bg-white`-Block. Das ist **bewusst** so — QR-Codes
|
||||
brauchen schwarz-auf-weiß, sonst werden sie nicht
|
||||
eingescannt. Kein Fix, nur Kommentar zur Klarstellung.
|
||||
|
||||
### Was unkritisch ist
|
||||
|
||||
- `customer/dashboard.blade.php`: 8 Treffer für
|
||||
`bg-white` / `text-white` — alle im `panel-dark`
|
||||
Brand-Bridge Block (konstant dunkel, weiße Texte
|
||||
korrekt) und im Empty-State Counter-Badge auf
|
||||
`--color-accent` (Bernstein-Bg, beides Modi).
|
||||
- `admin/dashboard.blade.php` Z. 235: Quick-Action
|
||||
Hover-State `group-hover:bg-hub group-hover:text-white`
|
||||
— Hub-Bg ist sowohl Light (dunkles Hub-Blau) als auch
|
||||
Dark (helleres Hub-Blau) okay für weißen Text.
|
||||
- Sidebar-`dark:bg-zinc-…` Klassen aus dem Starter-Kit:
|
||||
Die Zinc-Skala ist in `portal.css` auf warmes
|
||||
Buchpapier gemapped, das funktioniert weiter — die
|
||||
Avatar-Bg's sind sowieso nur 8×8-Pixel-Spots.
|
||||
- `customer/press-kits/show.blade.php` Z. 440: Logo-Bg
|
||||
`bg-white` — bewusst, weil Firmen-Logos einen weißen
|
||||
Hintergrund für korrekte Darstellung brauchen.
|
||||
|
||||
### Was außerhalb von Phase 5 ist
|
||||
|
||||
- **`shared-styles.css`** hat `.dark .card`, `.dark
|
||||
.slider-*`, `.dark .highlight-*`, `.dark .section-*`
|
||||
Regeln. Diese sind für den **Web-Bereich**
|
||||
(Hub-Frontend, presseecho, businessportal24), nicht
|
||||
fürs Portal. `portal.css` importiert
|
||||
`shared-styles.css` **nicht** — die Regeln landen also
|
||||
nicht im Portal-Bundle.
|
||||
- **Web-Frontend** bleibt Light-Only — wie in der
|
||||
Roadmap definiert.
|
||||
|
||||
## Akzeptanzkriterien
|
||||
|
||||
- [x] Plan
|
||||
- [x] Recherche aller hardcoded Farbklassen
|
||||
- [x] `--color-ink-deep` → `--color-panel-dark-2` in
|
||||
`customer/tokens.blade.php`
|
||||
- [x] `bg-white` für 2FA-QR-Code in `security.blade.php`
|
||||
mit erklärendem Kommentar versehen
|
||||
- [x] Build + Pint + Tests grün
|
||||
- [x] PROGRESS.md + 03-WEITERE-PHASEN.md auf ✅
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
- Konsequente Token-Disziplin (kein Hex außer in
|
||||
`design-tokens.css`) zahlt sich beim Dark Mode massiv
|
||||
aus: Statt 15 Pages × dutzende Klassen anzufassen,
|
||||
schaltet das gesamte Portal mit einem `.dark {…}` Block
|
||||
um.
|
||||
- Die wenigen Ausnahmen (panel-dark, QR-Code-Bg,
|
||||
Logo-Bg) sind dokumentierte Bewusst-Entscheidungen,
|
||||
keine Schuld.
|
||||
- FluxUI's `$flux.appearance` Magic-Object spart eine
|
||||
eigene Alpine-Component und persistiert über
|
||||
LocalStorage.
|
||||
|
||||
## Manueller Smoke-Test (Empfehlung)
|
||||
|
||||
Da Pest keine echte Browser-Rendering-Pipeline hat:
|
||||
- Im Browser: User-Menü → Erscheinung → „Dunkel"
|
||||
- Folgende Seiten besuchen + visuell prüfen:
|
||||
- `/dashboard` (Admin) + `/me/dashboard` (Customer)
|
||||
- `/me/press-releases` (Customer-Liste mit
|
||||
Saved-Views-Tabs, Filter-Chips, Portal-Pills,
|
||||
Row-Tints)
|
||||
- `/admin/press-releases` (Admin-Liste mit Inline-Actions)
|
||||
- `/me/security` (2FA-QR mit `bg-white`)
|
||||
- `/me/tokens` (Token-Anzeige nach Generierung)
|
||||
- `/admin/companies/{id}` (Detail-View mit allen Panels)
|
||||
- Erwartung: Lesbar, konsistent, keine grellen
|
||||
Kontraste, keine „weißen Inseln" auf dunklem Bg.
|
||||
54
dev/frontend/hub-flux/18-PHASE-6-AUTH-CLEANUP.md
Normal file
54
dev/frontend/hub-flux/18-PHASE-6-AUTH-CLEANUP.md
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Phase 6 — Auth-Cleanup
|
||||
|
||||
> Ungenutzte Starter-Kit-Layouts und Debug-Views entfernen,
|
||||
> die seit der Hub-Auth-Umstellung (Phase 0) keine Verwendung
|
||||
> mehr haben.
|
||||
|
||||
**Status**: ✅ abgeschlossen · **Aufwand**: ~30 min · **Risiko**: niedrig
|
||||
|
||||
---
|
||||
|
||||
## Inventur
|
||||
|
||||
### Aktiv genutzt (BEHALTEN)
|
||||
|
||||
| Datei | Genutzt von |
|
||||
|---|---|
|
||||
| `components/layouts/app.blade.php` | Wrapper → `app.sidebar` |
|
||||
| `components/layouts/app/sidebar.blade.php` | primäres Portal-Layout (alle Volt-Pages) |
|
||||
| `components/layouts/auth/pressekonto.blade.php` | login, register, forgot-password, verify-email, confirm-password, reset-password |
|
||||
|
||||
### Ungenutzt (ENTFERNT)
|
||||
|
||||
| Datei | Grund |
|
||||
|---|---|
|
||||
| `components/layouts/auth.blade.php` | dünner Wrapper auf `auth.simple` — nirgends als Layout referenziert (außer `login-simple` + `test-simple` Debug, die ebenfalls weg sind) |
|
||||
| `components/layouts/auth/simple.blade.php` | Starter-Kit-Rest, hardcoded `class="dark"` |
|
||||
| `components/layouts/auth/split.blade.php` | Starter-Kit-Rest, hardcoded `class="dark"` |
|
||||
| `components/layouts/auth/card.blade.php` | Starter-Kit-Rest, hardcoded `class="dark"` |
|
||||
| `components/layouts/app/header.blade.php` | alternatives App-Layout (Top-Bar statt Sidebar), wird vom Wrapper nie aufgerufen, hardcoded `class="dark"` |
|
||||
| `livewire/auth/login-simple.blade.php` | Debug-Volt-Login („Log in to your account"-Englisch) ohne Route, nutzte `<x-layouts.auth>` |
|
||||
| `test-simple.blade.php` | Debug-Page „Diese Seite funktioniert ohne Volt/Livewire" ohne Route |
|
||||
|
||||
### Aktualisiert
|
||||
|
||||
- `_docs/FORTIFY-SANCTUM-SETUP.md`: Code-Beispiel von
|
||||
`components.layouts.auth` auf `components.layouts.auth.pressekonto`
|
||||
umgestellt (alter Wert verwies auf die soeben gelöschte
|
||||
Wrapper-Datei).
|
||||
|
||||
## Verifikation
|
||||
|
||||
- `rg "auth\.(simple|split|card)"` und
|
||||
`rg "app\.header"` → 0 Treffer in `resources/` und `routes/`
|
||||
- Build, Pint, alle relevanten Tests grün
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
- Mit der Anti-Flash-Bridge aus Phase 5 wären die hardcoded
|
||||
`class="dark"`-Layouts sowieso zur Stolperfalle für
|
||||
Light-Mode-User geworden, falls sie je reaktiviert werden.
|
||||
Lösung: weg damit.
|
||||
- Hub-Auth-Pages laufen seit Phase 0 ausschließlich über
|
||||
`auth/pressekonto.blade.php`. Die Starter-Kit-Layouts waren
|
||||
schon damals tote Last.
|
||||
|
|
@ -5,6 +5,920 @@
|
|||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 6 · Auth-Cleanup ✅
|
||||
|
||||
Mit Phase 6 ist die hub-flux-Roadmap (Phase 0–6) **vollständig
|
||||
abgeschlossen**. Schlanker Cleanup-Task ohne Risiko.
|
||||
|
||||
**Inventur** (Referenzen geprüft via `rg` über `resources/` + `routes/`):
|
||||
|
||||
| Datei | Status |
|
||||
|---|---|
|
||||
| `components/layouts/app/sidebar.blade.php` | aktiv (primäres Portal-Layout) |
|
||||
| `components/layouts/auth/pressekonto.blade.php` | aktiv (6 Auth-Pages) |
|
||||
| `components/layouts/auth.blade.php` | nur in `login-simple`/`test-simple` → weg |
|
||||
| `components/layouts/auth/simple.blade.php` | Starter-Kit-Rest mit hardcoded `class="dark"` → weg |
|
||||
| `components/layouts/auth/split.blade.php` | dito → weg |
|
||||
| `components/layouts/auth/card.blade.php` | dito → weg |
|
||||
| `components/layouts/app/header.blade.php` | nirgends referenziert → weg |
|
||||
| `livewire/auth/login-simple.blade.php` | Debug-Page ohne Route → weg |
|
||||
| `test-simple.blade.php` | Debug-Page ohne Route → weg |
|
||||
|
||||
**Gelöscht**: 7 Files, insgesamt ~17 KB Code
|
||||
|
||||
**Aktualisiert**: `_docs/FORTIFY-SANCTUM-SETUP.md` (Code-Beispiel
|
||||
von `components.layouts.auth` auf `components.layouts.auth.pressekonto`).
|
||||
|
||||
**Side-Effect**: Portal-CSS-Bundle ist ~2.4 KB kleiner geworden
|
||||
(422.62 KB statt 425.07 KB), weil weniger Klassen via Tailwind
|
||||
JIT entdeckt werden.
|
||||
|
||||
**Validierung**:
|
||||
- `php artisan view:clear && npm run build:portal` → grün
|
||||
- `vendor/bin/pint --dirty --format agent` → passed
|
||||
- Tests: 230/231 (nur der bekannte pre-existing
|
||||
`ApiDocumentationTest`)
|
||||
|
||||
**Roadmap-Update**:
|
||||
- `03-WEITERE-PHASEN.md` Phase 6 von „⚪ optional
|
||||
(Auth-Konsolidierung)" auf „✅ abgeschlossen (Auth-Cleanup)"
|
||||
umgestellt
|
||||
- Status-Tabelle: Phase 6 ✅
|
||||
- „Nächste Schritte" angepasst: kein hub-flux-Item mehr offen,
|
||||
nur noch eigenständige Initiativen (Dark-Mode-Smoke-Test,
|
||||
PM-Form-Wizard-Refactor, Web-Frontend-Block)
|
||||
|
||||
**Lesson**: Mit der Anti-Flash-Bridge aus Phase 5 wären die
|
||||
hardcoded `class="dark"`-Layouts zu einer Stolperfalle für
|
||||
Light-Mode-User geworden, sobald jemand sie reaktiviert hätte.
|
||||
Cleanup zum richtigen Zeitpunkt.
|
||||
|
||||
Plan-Doc: `18-PHASE-6-AUTH-CLEANUP.md`
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 5 · Dark-Mode-Bugfixes (User-Findings)
|
||||
|
||||
Nach dem ersten visuellen Smoke-Test des Users zwei Folge-Fixes
|
||||
geliefert, plus die Anti-Flash-Bridge:
|
||||
|
||||
### Fix 1 — Logo „presse" im Dark Mode
|
||||
|
||||
`components/web/brand-mark.blade.php`: Bei `variant="auto"` (Default)
|
||||
wird die Name-Farbe jetzt über CSS-Custom-Property `--brand-mark-name-color`
|
||||
mit Light-Mode-Fallback gesetzt. Im Portal-Dark-Mode setzt
|
||||
`design-tokens.css` `.dark`-Block die Variable auf `#ffffff`. Hub-Frontend
|
||||
ist Light-Only, dort bleibt die Variable undefiniert und der Marken-
|
||||
Standardwert greift.
|
||||
|
||||
### Fix 2 — Navlist-Flackern beim Klick
|
||||
|
||||
`portal.css` Navlist-Item-Block:
|
||||
- Hover im Dark Mode auf `var(--color-hub-soft)` (Light-Mode-Hover
|
||||
`var(--color-bg)` ist dunkler als die Sidebar — wirkte als
|
||||
„Eindrücken" statt Hervorheben)
|
||||
- `:active`, `:focus` explizit auf `var(--color-hub-soft)` mit
|
||||
`outline: none` → verhindert Browser-Default-Tap-Flash
|
||||
- `-webkit-tap-highlight-color: transparent` für mobile Browser
|
||||
- `:focus-visible` mit Hub-Ring für Tastatur-A11y bleibt erhalten
|
||||
|
||||
### Fix 3 — Anti-Flash-Bridge (FOLT bei `wire:navigate`)
|
||||
|
||||
Bei `wire:navigate` morpht Livewire das DOM, das neue HTML kommt
|
||||
vom Server **ohne** `class="dark"` (Server kennt LocalStorage nicht).
|
||||
Folge: kurzer weißer Theme-Flash, bis JS die Klasse wieder anhängt.
|
||||
User hat das richtig diagnostiziert.
|
||||
|
||||
Lösung:
|
||||
1. **JS-Bridge** in `partials/head.blade.php` +
|
||||
`partials/admin-head.blade.php`: wrappt
|
||||
`window.Flux.applyAppearance`, spiegelt den effektiv applizierten
|
||||
Modus (bei `system` aus DOM-Klasse abgelesen → `dark`/`light`)
|
||||
in ein `flux_appearance`-Cookie. Schreibt auch beim initialen
|
||||
Page-Load.
|
||||
2. **Server-Side Render**: `<html>`-Tag in
|
||||
`components/layouts/app/sidebar.blade.php` und
|
||||
`layouts/admin-master.blade.php` setzt `class="dark"`
|
||||
direkt, basierend auf dem Cookie:
|
||||
`@class(['dark' => request()->cookie('flux_appearance') === 'dark'])`
|
||||
3. **Bonus**: hardcoded `class="dark"` aus `admin-master.blade.php`
|
||||
raus (war Light-User-Bug-Falle).
|
||||
|
||||
Erstbesuch: einmaliger Flash (Cookie noch nicht gesetzt).
|
||||
Alle weiteren Pageloads: kein Flash mehr.
|
||||
|
||||
### Latente Bug-Fixes nebenbei
|
||||
|
||||
- `customer/tokens.blade.php` Z. 137: `--color-ink-deep` Token war
|
||||
nie definiert → durch `--color-panel-dark-2` ersetzt (konstant
|
||||
dunkel in beiden Modi)
|
||||
- `customer/security.blade.php` 2FA-QR: erklärender Kommentar für
|
||||
`bg-white` (Scan-Tauglichkeit)
|
||||
|
||||
**Validierung**:
|
||||
- Build, Pint, Tests: 230/231 (nur ApiDocumentationTest)
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 5 · Dark Mode ✅
|
||||
|
||||
Phase 5 (Dark Mode) ist abgeschlossen. Die meiste Arbeit war
|
||||
faktisch schon in Phase 0–4 erledigt — die konsequente
|
||||
Token-Disziplin zahlt sich jetzt voll aus.
|
||||
|
||||
**Was schon da war (Stand bei Phase-5-Start):**
|
||||
|
||||
- `shared/design-tokens.css` hat einen kompletten
|
||||
`.dark { … }`-Block (Z. 182–248) mit allen Surfaces,
|
||||
Hub-Blau (heller), Bernstein (heller, mit konstantem
|
||||
`--color-accent-warm`), Ink-Skala (invertiert),
|
||||
Status-Farben, Bridge-Dots, Schatten und `color-scheme: dark`
|
||||
- `portal.css` hat `@custom-variant dark (&:where(.dark, .dark *))`
|
||||
+ Dark-Mode-Shadow-Overrides für Primary-Buttons
|
||||
- `hub-components.css` ist 100 % token-basiert
|
||||
- `panel-dark` nutzt `--color-panel-dark*` (konstant)
|
||||
- `@fluxAppearance` in beiden Head-Partials integriert
|
||||
- Switcher in Sidebar (Desktop + Mobile) +
|
||||
`settings/appearance.blade.php`
|
||||
|
||||
**Was im Rahmen von Phase 5 gefixt wurde:**
|
||||
|
||||
1. **`customer/tokens.blade.php` Z. 137** — Token-Anzeige
|
||||
nutzte ein nicht existentes `--color-ink-deep`. Damit war
|
||||
der Code-Block bisher transparent (im Dark Mode wäre
|
||||
`--color-ink` hell → weißer Text unlesbar). Umgestellt auf
|
||||
`--color-panel-dark-2` (konstant dunkel, weißer Text in
|
||||
beiden Modi korrekt).
|
||||
|
||||
2. **`customer/security.blade.php` Z. 270** — 2FA-QR-Code in
|
||||
`bg-white`. Bewusst belassen (QR-Codes brauchen
|
||||
schwarz-auf-weiß für Scan-Tauglichkeit), erklärender
|
||||
Kommentar ergänzt.
|
||||
|
||||
**Was unkritisch ist (kein Fix):**
|
||||
|
||||
- Alle weiteren `bg-white` / `text-white`-Treffer in
|
||||
`customer/dashboard.blade.php` sind im `panel-dark` Block
|
||||
(konstant dunkel) oder auf `--color-accent` (Bernstein in
|
||||
beiden Modi) — passt
|
||||
- `admin/dashboard.blade.php` Quick-Action Hover
|
||||
(`group-hover:bg-hub group-hover:text-white`) — Hub-Bg ist
|
||||
in beiden Modi dunkel genug für weißen Text
|
||||
- `customer/press-kits/show.blade.php` Logo-Bg `bg-white` —
|
||||
bewusst, weil Firmen-Logos einen weißen Bg brauchen
|
||||
- Sidebar-`dark:bg-zinc-*` Helper aus dem Starter-Kit —
|
||||
Zinc-Skala ist in `portal.css` auf warmes Buchpapier
|
||||
gemapped, läuft
|
||||
|
||||
**Außerhalb Phase 5:**
|
||||
|
||||
- `shared-styles.css` hat `.dark .card`, `.dark .slider-*`,
|
||||
`.dark .highlight-*`, `.dark .section-*` — diese sind für
|
||||
den Web-Bereich (Hub-Frontend, presseecho,
|
||||
businessportal24). `portal.css` importiert
|
||||
`shared-styles.css` **nicht** — keine Kollision.
|
||||
- Web-Frontend bleibt Light-Only (per Roadmap-Definition)
|
||||
|
||||
**Validierung:**
|
||||
|
||||
- Build: `npm run build:portal` → grün
|
||||
- Pint: `vendor/bin/pint --dirty --format agent` → passed
|
||||
- Tests: 230/231 (1 pre-existing `ApiDocumentationTest`, kein
|
||||
Bezug zur UI-Migration)
|
||||
|
||||
**Empfehlung:** Im Browser einmal User-Menü →
|
||||
Erscheinung → „Dunkel" + Dashboard/Listen/Detail/Security
|
||||
(QR)/Tokens visuell durchklicken für den Smoke-Test (Pest
|
||||
hat keine echte Rendering-Pipeline).
|
||||
|
||||
Roadmap-Update:
|
||||
|
||||
- `03-WEITERE-PHASEN.md` Phase 5 von „⚪ später" auf
|
||||
„✅ abgeschlossen" gesetzt
|
||||
- Status-Tabelle aktualisiert
|
||||
- Empfehlungen verschoben: Phase 6 Auth-Cleanup +
|
||||
manueller Smoke-Test nach oben
|
||||
|
||||
Plan-Doc: `17-PHASE-5-DARK-MODE.md`
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 4 · offiziell abgeschlossen ✅
|
||||
|
||||
Mit dem Abschluss von 4J ist Phase 4 (Listen/Detail-Pages
|
||||
durchgehen) in allen 10 Sub-Päckchen 4A–4J durch:
|
||||
|
||||
| ID | Bereich |
|
||||
|---|---|
|
||||
| 4A | Press-Releases Listen (Admin + Customer) |
|
||||
| 4B | Press-Releases Detail/Show |
|
||||
| 4C | Press-Releases Forms (create/edit) |
|
||||
| 4D | Companies (Admin) |
|
||||
| 4E | Profile/Settings (Admin + Customer) |
|
||||
| 4F | Restliche Admin-Bereiche (12 Module) |
|
||||
| 4G | Restliche Customer-Bereiche (5 Module) |
|
||||
| 4H | Customer „Meine Pressemitteilungen" Mockup-Stil |
|
||||
| 4I | Admin „Pressemitteilungen" — Patterns übertragen |
|
||||
| 4J | Dashboard-PM-Listen mit 4H/4I-Patterns |
|
||||
|
||||
**Roadmap-Cleanup**:
|
||||
|
||||
- `03-WEITERE-PHASEN.md` Phase 4 von „🚧 iterativ" auf
|
||||
„✅ komplett abgeschlossen" gesetzt
|
||||
- Phase 2 + 3 (Customer- + Admin-Dashboard) waren faktisch
|
||||
schon in Phase 1 umgesetzt — Status retroaktiv auf
|
||||
✅ korrigiert (mit Hinweis auf 4J-Verfeinerung)
|
||||
- Neuer „Gesamt-Status"-Block in `03-WEITERE-PHASEN.md`
|
||||
mit Phasen-Tabelle, Sub-Päckchen-Tabelle und konkreten
|
||||
Empfehlungen für die nächsten Schritte (Phase 5 Dark
|
||||
Mode > Phase 6 Auth-Cleanup > Web-Frontend-Block als
|
||||
eigene Roadmap)
|
||||
- Erkenntnis: Das Mockup `Veröffentlichen Tailwind.html`
|
||||
ist eine **Public-Facing-Landing-Page** von
|
||||
businessportal24 (Web-Frontend), nicht ein
|
||||
Backend-PM-Wizard. Gehört in den separaten
|
||||
Web-Frontend-Block, nicht in Phase 4.
|
||||
|
||||
**Verifikationsstand**:
|
||||
|
||||
- 230/231 Tests grün (1 pre-existing
|
||||
`ApiDocumentationTest`-Fail, unverändert seit 4D)
|
||||
- Build (Portal) sauber: 424 KB CSS / 16 KB JS gzipped
|
||||
- Pint sauber
|
||||
- 16 Plan-Dokumente in `dev/frontend/hub-flux/`
|
||||
(`00`–`16` + `PROGRESS.md` + `README.md`)
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 4J · Dashboard-PM-Listen mit 4H/4I-Patterns
|
||||
|
||||
- **Anlass**: Nach 4H/4I sahen die Voll-Listen exakt wie
|
||||
das Mockup aus, aber die kompakten PM-Listen in den
|
||||
Dashboards waren noch im alten Stand (Badge rechts,
|
||||
Sub-Zeile ohne Portal). 4J schließt diese Inkonsistenz.
|
||||
- **Befund**: Beide Dashboards waren bereits in Phase 1
|
||||
großzügig Hub-styled (Page-Header, KPI-Reihe,
|
||||
`<x-portal.stat-card>`, `<x-portal.hint-card>`,
|
||||
`panel-dark` Brand-Bridge usw.) — die Roadmap-Status
|
||||
„⚪ später" für Phase 2 + 3 waren also veraltet und
|
||||
wurden im Zuge dieses Päckchens auf ✅ gesetzt.
|
||||
- **Plan-Dokument**: `16-PHASE-4J-DASHBOARD-LISTS.md`.
|
||||
|
||||
### Customer-Dashboard (`customer/dashboard.blade.php`)
|
||||
|
||||
- Volt: `recent` Select um `portal` + `published_at`
|
||||
erweitert
|
||||
- `recent`-Liste:
|
||||
- Portal-Pills (`pe`/`bp`) neben dem Badge (Both → beide
|
||||
Pills)
|
||||
- Sub-Zeile mit `PM-{id} · Firma · Datum`
|
||||
- `published_at` als Primärdatum für veröffentlichte PMs
|
||||
- Badge-Mapping erweitert (`muted` für archived/draft
|
||||
statt `hub`)
|
||||
|
||||
### Admin-Dashboard (`admin/dashboard.blade.php`)
|
||||
|
||||
- `recentPRs`-Liste:
|
||||
- Portal-Pills neben Badge (Both → beide Pills)
|
||||
- Sub-Zeile mit `PM-{id} · Firma · User · Datum`
|
||||
- Badge-Mapping mit `muted` für archived/draft
|
||||
- `pendingReviews`-Liste (Review-Queue):
|
||||
- Row-Tinting `is-row-warn` (gelblich) für alle Items
|
||||
- Inline-Action „Prüfen →" rechts oben (Link zur
|
||||
Show-Page, weil Admin-Dashboard Controller+Blade ist
|
||||
und keine direkte Wire-Methode hat)
|
||||
- Portal-Pills unter dem Titel
|
||||
- Container von `<a>` zu `<div>` umgestellt, weil zwei
|
||||
Links nebeneinander (Titel-Link + Inline-Action) sonst
|
||||
HTML-invalid wären
|
||||
- Hover-Underline auf Titel-Link für klare Klickbarkeit
|
||||
|
||||
### Tests/Verifikation
|
||||
|
||||
- `DashboardTest` (Customer) → 5/5 grün, 21 Assertions
|
||||
- Volle Suite → 230/231 grün, 1 pre-existing Fail
|
||||
(`ApiDocumentationTest`)
|
||||
- `npm run build:portal` → grün (424 KB CSS, +0,2 KB)
|
||||
- `vendor/bin/pint --dirty` → passed
|
||||
|
||||
### Dateien
|
||||
|
||||
- `dev/frontend/hub-flux/16-PHASE-4J-DASHBOARD-LISTS.md` (neu)
|
||||
- `resources/views/livewire/customer/dashboard.blade.php`
|
||||
- `resources/views/admin/dashboard.blade.php`
|
||||
- `dev/frontend/hub-flux/PROGRESS.md` (dieser Eintrag)
|
||||
- `dev/frontend/hub-flux/03-WEITERE-PHASEN.md` (4J +
|
||||
Phase 2/3 retroaktiv auf ✅)
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 4I · Admin „Pressemitteilungen" auf Mockup-Patterns
|
||||
|
||||
- **Anlass**: Direktes Folgepäckchen zu 4H — die in der Customer-Liste
|
||||
entwickelten Mockup-Patterns werden auf die zentrale
|
||||
Admin-Pressemitteilungen-Liste übertragen (Editorial-Workflow).
|
||||
|
||||
- **Plan-Dokument**: `15-PHASE-4I-ADMIN-PRESS-RELEASES.md`.
|
||||
|
||||
### Volt-Komponente erweitert (`admin/press-releases/index.blade.php`)
|
||||
|
||||
- `pressReleaseStats()` um `rejected` + `archived` Counter
|
||||
erweitert (zwei zusätzliche `selectRaw` im gleichen Statement,
|
||||
weiter über `AdminPerformanceCache`)
|
||||
- Neue Methoden: `setView($view)` (setzt Statusfilter +
|
||||
resetPage), `resetFilters()` (kompletter Reset inkl.
|
||||
Entity-Lookups)
|
||||
|
||||
### Markup auf Mockup-Stil
|
||||
|
||||
- **Saved-Views-Tabs**: 6 Tabs (Alle, In Prüfung,
|
||||
Veröffentlicht, Entwürfe, Abgelehnt, Archiv) mit
|
||||
Counter-Pillen, `is-active`-State spiegelt `$statusFilter`
|
||||
- **Active-Chips** unter Filter-Panel: 8 Filter-Typen
|
||||
(Suche, Status, Portal, Sprache, Kategorie, User, Firma,
|
||||
Kontakt) mit Remove-Button + „Alle zurücksetzen"-Link
|
||||
- **Tabelle umgestellt** (8 → 7 Spalten):
|
||||
- Status + Inline-Action (Freigeben/Ablehnen für Review,
|
||||
Archivieren für Published) — Inline-Actions sind
|
||||
`flux:modal.trigger` für die bestehenden, test-kritischen
|
||||
Modals
|
||||
- Titel mit Sub-Zeile (PM-ID + Firma + Sprache, klickbar)
|
||||
- Portal-Pills (presseecho/businessportal24, bei
|
||||
`Portal::Both` beide nebeneinander)
|
||||
- Kategorie (truncate)
|
||||
- Datum mit Status-Kontext-Sub („veröffentlicht · HH:MM",
|
||||
„eingereicht · HH:MM", …) und `published_at` als
|
||||
Primärdatum wenn vorhanden
|
||||
- Hits monospace
|
||||
- Aktionen-Spalte rechts reduziert auf view + edit
|
||||
(Status-Actions wandern in die Status-Zelle)
|
||||
- **Row-Tinting**: review → `is-row-warn`,
|
||||
rejected → `is-row-err`
|
||||
- **Empty-State 2-stufig**: „mit Filter" → Reset-CTA,
|
||||
„ohne Filter" → Anlegen-CTA
|
||||
|
||||
### Was unangetastet bleibt (Test-kritisch)
|
||||
|
||||
- `publish()`, `reject()`, `archive()`, `sort()`,
|
||||
`with()`, alle `clearXFilter()`, `resetEntityFilters()`
|
||||
- 3 Modals mit Strings „Pressemitteilung veröffentlichen?",
|
||||
„Pressemitteilung ablehnen?", „Pressemitteilung
|
||||
archivieren?" — werden weiter vom Markup gemountet,
|
||||
jetzt zusätzlich via Inline-Action triggerbar
|
||||
- Combobox-Lookups für User/Firma/Kontakt (Schema +
|
||||
Verhalten unverändert)
|
||||
|
||||
### Pitfall + Fix
|
||||
|
||||
- `@php(...)` Inline-Form kompilierte zu `<?php($var = ...)`
|
||||
ohne Whitespace → PHP-Syntax-Fehler trotz balancierter
|
||||
Klammern. Workaround: alle drei Inline-`@php(...)` zu
|
||||
Block-`@php ... @endphp` umgewandelt.
|
||||
|
||||
### Verifikation
|
||||
|
||||
- `npm run build:portal` → grün (424 KB CSS, +5 KB ggü. 4H)
|
||||
- `vendor/bin/pint --dirty` → passed
|
||||
- Gezielt: `AdminPressReleaseActionsTest` +
|
||||
`PressReleaseWorkflowTest` → 16/16 grün, 52 Assertions
|
||||
- Volle Suite: 230/231 grün, 1 pre-existing Fail
|
||||
(`ApiDocumentationTest` — fehlende `docs/api/v1.yml`,
|
||||
unverändert seit 4D), 3 skipped, 1212 Assertions
|
||||
|
||||
### Dateien
|
||||
|
||||
- `dev/frontend/hub-flux/15-PHASE-4I-ADMIN-PRESS-RELEASES.md` (neu)
|
||||
- `resources/views/livewire/admin/press-releases/index.blade.php`
|
||||
- `dev/frontend/hub-flux/PROGRESS.md` (dieser Eintrag)
|
||||
- `dev/frontend/hub-flux/03-WEITERE-PHASEN.md` (4I-Eintrag)
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 4H · Customer „Meine Pressemitteilungen" auf Mockup
|
||||
|
||||
- **Anlass**: Nach Abschluss von 4F entschied sich der User
|
||||
bewusst gegen die nächste Roadmap-Phase und für **detail-tief
|
||||
in das wichtigste Customer-Arbeitstool** zu gehen. Vorlage:
|
||||
`dev/frontend/tailwind_v3/User Pressemitteilungen presseportale.html`.
|
||||
|
||||
- **Plan-Dokument**: `14-PHASE-4H-PRESS-RELEASES-MOCKUP.md`.
|
||||
|
||||
### Hub-CSS erweitert (`resources/css/shared/hub-components.css`)
|
||||
|
||||
Neue Komponenten — alle aus Tokens, KEINE Hex-Literale:
|
||||
|
||||
- `.counter-strip` + `.seg` (mit `is-ok`, `is-warn`, `is-err`, `is-muted`) + `.sep`
|
||||
- `.view-tabs` + `.view-tab` (mit `is-active`, `cnt`-Pille)
|
||||
- `.filter-chip` (mit `is-active`, `caret`)
|
||||
- `.active-chip` (mit `x`-Remove-Button)
|
||||
- `.portal-pill` (mit `pe` + `bp` Dot-Varianten, nutzt
|
||||
`--color-bridge-presseecho` / `--color-bridge-businessportal`)
|
||||
- `.inline-action` (mit `warn` + `err` Varianten)
|
||||
- `.is-row-warn` + `.is-row-err` (Row-Tinting via
|
||||
`color-mix` aus Status-Soft-Token gegen Card-Background)
|
||||
- `.empty-stage` + `.empty-ico` (mit `warm` + `err` Varianten)
|
||||
+ `.empty-title` + `.empty-sub`
|
||||
- `.badge.muted` (neue Variante neben `ok`, `warn`, `err`, `hub`)
|
||||
|
||||
### Volt-Komponente erweitert (`customer/press-releases/index.blade.php`)
|
||||
|
||||
- Aggregierte `statusCounts` (assoc) per `groupBy('status')`
|
||||
über dieselbe `$base`-Query — eine zusätzliche SQL-Query
|
||||
- Neue Properties: `$portalFilter`
|
||||
- Neue Methoden: `setView($status)`, `resetFilters()`,
|
||||
`updatedPortalFilter()`
|
||||
- Daten an View: `statusCounts`, `portalOptions`
|
||||
- Paginierung von 100 → 25 (sinnvoller für Mockup-Layout)
|
||||
|
||||
### Markup auf Mockup-Stil (~50 % → ~90 %)
|
||||
|
||||
- **Counter-Strip im Header**: dynamische Segmente werden nur
|
||||
angezeigt, wenn Count > 0, mit semantisch eingefärbten
|
||||
Zahlen (ok/warn/muted/err)
|
||||
- **Kontext-Hinweis**: „Gefiltert auf :company" als kleine
|
||||
Sub-Zeile, falls Firma-Kontext aktiv (Test-kompatibel!)
|
||||
- **Saved-Views-Tabs**: 6 Tabs (Alle, Veröffentlicht,
|
||||
Entwürfe, In Prüfung, Abgelehnt, Archiv) mit `cnt`-Pille,
|
||||
klicken setzt `statusFilter` via `setView()`
|
||||
- **Filter-Sektion**: Suche + Portal-Select + Firma-Select
|
||||
(letzteres nur wenn `hasGlobalCompanyContext`)
|
||||
- **Active-Chips**: entfernbare Anzeige aller aktiven Filter
|
||||
(Status, Portal, Firma, Suche), mit `Alle zurücksetzen`-Link
|
||||
- **Tabelle**:
|
||||
- Status-Spalte mit Hub-Badge + Inline-Action „Zur Prüfung →"
|
||||
für Drafts (ruft `submitForReview` mit `wire:confirm`)
|
||||
- Titel mit Hub-Style-Link + PM-{id}-Sub
|
||||
- Portal-Pills (eine oder zwei, abhängig vom Portal-Enum-Wert)
|
||||
- Firma als Hub-Link oder „— keine —" italic
|
||||
- Datum: Mono-Font + Sub mit Status-Kontext
|
||||
(„veröffentlicht · 09:12", „eingereicht · 17:48" …)
|
||||
- Row-Tint via `is-row-warn` (review) / `is-row-err`
|
||||
(rejected) — dezent beim Hover
|
||||
- **3-fach Empty-State**:
|
||||
- `empty-search` (Suche ohne Treffer)
|
||||
- `empty-filter` (Filter ohne Treffer, warm-Icon-Box)
|
||||
- `empty-none` (gar keine PMs, mit 3-Schritt-Onboarding
|
||||
01-Firma · 02-Verfassen · 03-Zur Prüfung)
|
||||
- **Status-Aktionen-Legende**: `panel-warm` mit 5-Spalten-
|
||||
Grid (Entwurf, In Prüfung, Veröffentlicht, Abgelehnt,
|
||||
Archiviert) und einer Action-Liste je Status
|
||||
|
||||
### Was bewusst NICHT umgesetzt wurde
|
||||
|
||||
- **Bulk-Selection**: weggelassen (keine Bulk-Actions im
|
||||
Backend → wäre nur Dekoration ohne Funktion)
|
||||
- **Inline-Action „Grund ansehen →"** für Rejected: keine
|
||||
`rejection_reason`-Spalte in `press_releases`-Tabelle
|
||||
(Grund wird nur via E-Mail an Autor versendet)
|
||||
- **Spalten- & Export-Buttons** im Header: keine Backend-
|
||||
Implementierung, weggelassen
|
||||
- **Modals und Bestätigungs-Flows**: bleiben FluxUI
|
||||
- **Volt-Logik außerhalb der Erweiterungen**: unangetastet
|
||||
|
||||
### Tests & Hygiene
|
||||
|
||||
- **Erst-Fail**: `CustomerCompanyContextTest:79` rot, weil
|
||||
„Gefiltert auf Alpha GmbH" im neuen Layout vom Counter-Strip
|
||||
verdrängt wurde
|
||||
- **Fix**: Kontext-Hinweis als zusätzliche kleine Sub-Zeile
|
||||
unter dem Counter-Strip, immer sichtbar wenn Firma-Kontext
|
||||
- **PR + Customer Tests**: 50/50 grün (199 Assertions)
|
||||
- **Volle Suite**: 230 grün (1212 Assertions), 3 skipped,
|
||||
1 fail = pre-existing `ApiDocumentationTest` (kein Bezug zu UI)
|
||||
- **Build**: `npm run build:portal` clean
|
||||
- **Pint**: `vendor/bin/pint --dirty` → `passed`
|
||||
- **Lints**: keine Errors
|
||||
|
||||
### Status
|
||||
|
||||
- Plan-Status: ✅ Phase 4H **abgeschlossen**
|
||||
- 03-WEITERE-PHASEN.md: 4H ergänzt als ✅
|
||||
- Match zum Mockup: ≥ 90 %
|
||||
|
||||
### Übertragbar auf
|
||||
|
||||
- `admin/press-releases/index.blade.php` (Phase 4I als
|
||||
natürliche Folgephase)
|
||||
- `admin/users.blade.php`, `admin/contacts/index.blade.php`,
|
||||
`admin/companies/index.blade.php` (Counter-Strip,
|
||||
Saved-Views-Tabs, Filter-Chips wären überall sinnvoll)
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 4F · Restliche Admin-Bereiche
|
||||
|
||||
- **Anlass**: User „4f weiter" nach 4G. Siebtes (und letztes
|
||||
großes) Päckchen aus Phase 4. Ziel: alle übrigen Admin-Pages
|
||||
auf Hub-Stil, damit die komplette interne Strecke visuell
|
||||
abgeschlossen ist.
|
||||
- **Plan-Dokument**: `13-PHASE-4F-ADMIN-REST.md`.
|
||||
Aufgrund des Umfangs (~7.500 Z. Blade) in 4 Sub-Päckchen
|
||||
aufgeteilt.
|
||||
|
||||
### 4F-1 · Stammdaten & Switcher
|
||||
|
||||
- **`admin/presets/{index,create,edit}.blade.php`** +
|
||||
`partials/form-fields.blade.php`: Page-Header
|
||||
(„Admin Backend"-Pille, Eyebrow „Administration · Presets"),
|
||||
Filter-Panel, Table-Panel mit Hub-Badges und Hub-Empty-State,
|
||||
Forms in `article.panel`, Required-Marker auf
|
||||
`text-[color:var(--color-err)]`.
|
||||
- **`admin/categories/{index,create,edit}.blade.php`**:
|
||||
Page-Header mit Aktion, KPI-Reihe mit
|
||||
`<x-portal.stat-card>` (gesamt, aktiv, mit/ohne PMs),
|
||||
Filter & Sort Panel, Category-Cards als `article.panel`,
|
||||
Hub-Badges, Hub-Style innere Items, Danger-Zone mit linkem
|
||||
roten Strip.
|
||||
- **`admin/portal-switcher.blade.php`** (Sidebar):
|
||||
Token-basierte Button-Farben mit `--color-bg-elev`,
|
||||
`--color-bg-rule`, `--color-ink`, `--color-ink-2`,
|
||||
`--color-ink-3`.
|
||||
- **Tests**: `CategoryIndexPerformanceTest`,
|
||||
`AdminCategoryManagementTest`,
|
||||
`AdminPresetManagementTest` — alle grün.
|
||||
|
||||
### 4F-2 · Pressekontakte
|
||||
|
||||
- **`admin/contacts/index.blade.php`** (729 Z.): Page-Header
|
||||
mit Action, KPI-Reihe (`<x-portal.stat-card>`),
|
||||
Filter-Panel, Preset-Panel, Table-Panel mit Hub-Badges und
|
||||
Hub-Empty-State, Hub-Style Flash-Boxen.
|
||||
- **`admin/contacts/create.blade.php`** (275 Z.):
|
||||
Page-Header mit „prefilled company"-Badge, Forms in
|
||||
`article.panel`.
|
||||
- **`admin/contacts/edit.blade.php`** (352 Z.):
|
||||
Page-Header mit ID + Portal-Badge, Forms in
|
||||
`article.panel`, Danger-Zone mit linkem roten Strip.
|
||||
|
||||
### 4F-3 · Operations & Finance
|
||||
|
||||
- **`admin/footer-codes/{index,create,edit}.blade.php`**:
|
||||
Page-Header, KPI-Reihe, Filter-Panel, Table-Panel mit
|
||||
Hub-Badges, Forms in `article.panel`, Category-Checkboxes
|
||||
Hub-styled, Danger-Zone.
|
||||
- **`admin/reports/slow-requests.blade.php`** +
|
||||
`slow-requests-table.blade.php`: Page-Header,
|
||||
Filter-Panel mit Reset-Button, KPI-Reihe, Top Routes/Paths
|
||||
Panels, Slowest-Requests-Table, Frequent Slow Queries Table,
|
||||
EXPLAIN Top Slow Queries Panel — alles Hub-styled.
|
||||
- **`admin/invoices/index.blade.php`** (Legacy-Archiv):
|
||||
Page-Header mit Archive-Badge, Warning-Box für „unmapped",
|
||||
KPI-Reihe, Filter-Panel, Table-Panel mit Hub-Badges +
|
||||
Pagination.
|
||||
- **`admin/coupons/index.blade.php`** (Stub): Page-Header
|
||||
„Vertagt"-Badge, Info-Panel mit Hub-Style List-Items.
|
||||
- **`admin/payments/index.blade.php`** (Stub): Page-Header
|
||||
„In Vorbereitung"-Badge, Info-Panel mit Hub-Style
|
||||
List-Items + Rules.
|
||||
- **Tests**: `AdminFooterCodeManagementTest`,
|
||||
`AdminSlowRequestReportTest`,
|
||||
`AdminSlowRequestLoggingTest`,
|
||||
`AdminLegacyInvoiceArchiveTest` — alle grün.
|
||||
|
||||
### 4F-4 · User-Verwaltung
|
||||
|
||||
- **`admin/roles/{index,create,edit}.blade.php`**:
|
||||
Page-Header (mit ID + System-Role-Badges in edit),
|
||||
Warning-Box für System-Rollen, Forms in `article.panel`,
|
||||
Table-Panel mit Hub-Badges + Empty-State.
|
||||
- **`admin/newsletter/sync.blade.php`** (171 Z.):
|
||||
Page-Header mit Sync-Status-Badge (active/deactivated),
|
||||
Action-Buttons (Dry Run, Test-Sync) im Header, Hub-Style
|
||||
Info/Success-Pillen für Notifications, KPI-Reihe mit
|
||||
`<x-portal.stat-card>` (gesamt, bestätigt, ausstehend,
|
||||
abgemeldet), Config-Details (Provider, Timeout, Endpoint)
|
||||
als Definition-List in `article.panel`.
|
||||
- **`admin/users.blade.php`** (939 Z.): Page-Header mit
|
||||
Action „Benutzer anlegen", KPI-Reihe (gesamt, aktiv,
|
||||
inaktiv), Filter-Panel mit Reset-Button, Table-Wrapper
|
||||
als `article.panel` mit Hub-Empty-State. **Modal**
|
||||
und alle Tabellen-Inhalte bewusst unangetastet — wegen
|
||||
Test-Strings + Komplexität.
|
||||
- **`admin/users/show.blade.php`** (239 Z.): Hub-Header mit
|
||||
ID + Status-Badges, KPI-Reihe (Portal, Typ, Status, Login),
|
||||
Rollen-Panel, Rechnungsadresse-Panel,
|
||||
„Verknüpfte Firmen & Kontakte"-Panel als verschachtelte
|
||||
Hub-Boxes.
|
||||
- **`admin/users/create.blade.php`** (421 Z.): Hub-Header,
|
||||
Forms in `article.panel` (Basisdaten, Rollenzuweisung,
|
||||
Firmenverknüpfung, Rechnungsadresse), Action-Panel
|
||||
am Ende.
|
||||
- **`admin/users/edit.blade.php`** (1.224 Z.): Hub-Header
|
||||
mit ID/Portal/Status-Badges, Notification als
|
||||
Hub-Style Ok-Pill, Quick-Nav als Token-Pillen mit
|
||||
Hover auf Hub-Blue, KPI-Reihe (Account, Legacy-Profil,
|
||||
Verknüpfungen, Rechnungsadresse). Innere Sektionen
|
||||
(`#account`, `#legacy-profile`, `#roles`,
|
||||
`#company-links`, `#contact-links`, `#billing-address`)
|
||||
bleiben `flux:card` (IDs für Quick-Nav, sehr großes File).
|
||||
Footer-Action-Panel auf Hub umgestellt.
|
||||
- **Tests**: `UserManagementTest`, `UserImpersonationTest`,
|
||||
`UserList*Test` — 29 Tests / 270 Assertions grün.
|
||||
|
||||
### Bauplatz-Hygiene
|
||||
|
||||
- **Build**: `npm run build:portal` — sauber durch.
|
||||
- **Pint**: `vendor/bin/pint --dirty` — `passed`.
|
||||
- **Lints**: keine Linter-Errors in den editierten Blade-Files.
|
||||
- **Pre-existing**: `ApiDocumentationTest` weiterhin rot, kein
|
||||
Bezug zu UI-Styling (siehe frühere Phasen).
|
||||
|
||||
### Abgrenzung — was bewusst NICHT angefasst wurde
|
||||
|
||||
- Volt-Logik in allen Dateien (PHP, `mount`, `save`,
|
||||
`with`, …).
|
||||
- `<flux:input>`, `<flux:select>`, `<flux:checkbox>`,
|
||||
`<flux:textarea>`, `<flux:field>`, `<flux:error>`,
|
||||
`<flux:table.*>`, `<flux:button>`, `<flux:modal>`.
|
||||
- Innere Sektionen von `users/edit.blade.php` mit ID-Ankern
|
||||
(Quick-Nav-Targets) — diese bleiben `flux:card`, da
|
||||
Footnote-Logik daran hängt und das File 1.224 Z. groß ist.
|
||||
- Modal in `users.blade.php` und alle internen Bestätigungs-
|
||||
Flows (Test-Strings).
|
||||
- Test-relevante Strings überall.
|
||||
|
||||
### Status
|
||||
|
||||
- Plan-Status: ✅ Phase 4F **abgeschlossen**.
|
||||
- 03-WEITERE-PHASEN.md: 4F auf ✅ aktualisiert.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 4G · Customer Portal (Mein Bereich)
|
||||
|
||||
- **Anlass**: User „weiter 4G" nach 4E. Sechstes Päckchen aus
|
||||
Phase 4: alle übrigen Customer-Pages auf Hub-Stil bringen,
|
||||
damit die komplette User-sichtbare Strecke (Mein Bereich)
|
||||
visuell abgeschlossen ist.
|
||||
|
||||
- **Plan-Dokument**: `12-PHASE-4G-CUSTOMER-PORTAL.md`.
|
||||
|
||||
- **Was umgebaut wurde**:
|
||||
- **`customer/company-switcher.blade.php`** (Sidebar-Helper,
|
||||
91 Z.): „Aktive Firma"-Label als `.badge hub dot`,
|
||||
Portal-Suffix als Token-Eyebrow, „Keine Firma zugeordnet"
|
||||
als `.badge warn`. `<flux:select>`, `Firma öffnen`-Button
|
||||
und `route('me.press-kits.show', …)` bleiben — alles, was
|
||||
der `CustomerCompanyContextTest` assertiert, ist erhalten.
|
||||
- **`customer/bookings.blade.php`** (Coming-Soon, 52 Z.):
|
||||
Hub-Page-Header („User Backend"-Pille,
|
||||
„Mein Bereich · Finanzen"-Eyebrow, Warn-Pille
|
||||
„In Vorbereitung"). Info-Box auf Hub-Soft-Token,
|
||||
3 Feature-Panels mit Eyebrow + Beschreibung.
|
||||
- **`customer/invoices.blade.php`** (194 Z.): Page-Header
|
||||
mit Aktion „Rechnungsadresse im Profil pflegen". Hinweis
|
||||
in eigenes `.panel`. 4 KPI-Cards (primary/muted/ok/warn).
|
||||
Filter-Panel, Tabellen-Panel mit Hub-Badges (`ok dot`
|
||||
für bezahlt, `warn dot` für offen), Empty-State als
|
||||
Icon-Box. Pagination am Boden im Panel mit Top-Border.
|
||||
Notification als Hub-Warn-Pille mit Icon.
|
||||
- **`customer/tokens.blade.php`** (212 Z.): Hub-Page-Header
|
||||
mit „API-Dokumentation"-Aktion. Flash-Boxen (success/warn)
|
||||
in Token-Style. „Neuer Token"-Anzeige als eigenes Panel
|
||||
mit linkem Warn-Strip (`border-left:3px solid
|
||||
--color-warn`) + `.badge warn`-„Nur jetzt sichtbar". Form
|
||||
in Panel mit Border-Top-getrenntem Submit-Bereich.
|
||||
Tabelle in Panel mit Counter, Berechtigungen als
|
||||
`.badge hub` (statt `flux:badge`), Empty-State als
|
||||
Icon-Box.
|
||||
- **`customer/press-kits/index.blade.php`** (119 Z.):
|
||||
Hub-Page-Header. Filter-Panel. Karten-Grid mit
|
||||
`.panel`-Karten: `.panel-head` mit Status-Badge
|
||||
(`ok dot` / `err dot`), Body mit Slug, Portal/Rolle/
|
||||
Footer-Badges, KPI-Boxen (Border + bg-elev), Footer
|
||||
mit Border-Top + „Firma öffnen"-Button.
|
||||
Empty-State als full-width Panel mit Hub-Icon-Box.
|
||||
- **`customer/press-kits/show.blade.php`** (734 Z. → Polish):
|
||||
Großes Detail-Cockpit komplett auf Hub:
|
||||
- **Header**: Pillen-Reihe (User-Backend, Mein
|
||||
Bereich · Firma, Aktiv/Inaktiv, Portal, Rolle, ggf.
|
||||
Footer-Code-aus) + Logo-Box (oder Hub-Icon-Box) + H1
|
||||
+ Slug. Aktionen rechts: Zurück, Stammdaten bearbeiten,
|
||||
Neue PM.
|
||||
- **Quick-Nav**: Anker-Links als pillen-artige
|
||||
Hover-Buttons mit Bottom-Border.
|
||||
- **4 KPI-Cards**: PMs (primary), Pressekontakte (ok),
|
||||
Portal (muted), Deine Rolle (muted) — mit
|
||||
`canManageCompany`-abhängiger Trend.
|
||||
- **Stammdaten-Panel**: `.panel-head` mit Bearbeiten-
|
||||
Button. Inline-Form als `bg-elev`-Block mit
|
||||
Border-Top-getrennten Sections (Logo / Aktionen).
|
||||
Daten-Liste als `<dl>` mit Token-Labels + Werten,
|
||||
Website als Hub-Underline-Link.
|
||||
- **Pressekontakte-Panel**: `.panel-head` mit Counter.
|
||||
Inline-Form analog Stammdaten. Kontakt-Items als
|
||||
`bg-elev`-Boxen mit Aktionen rechts. Empty-State als
|
||||
gestrichelte Box. Flash-Messages
|
||||
(„Pressekontakt wurde angelegt./aktualisiert./
|
||||
gelöscht.") in Token-Success-Pille.
|
||||
- **Pressemitteilungen-Panel**: Tabelle mit Hub-Status-
|
||||
Badges (ok/warn/err/hub). Empty-State Hub-Icon-Box.
|
||||
- **Abrechnung & Statistik**: 2 Panels nebeneinander
|
||||
mit „In Vorbereitung"/„Später"-Warn-Pillen. Abrechnung
|
||||
mit gestrichelter Hint-Box. Statistik mit
|
||||
2 Token-Mini-Stats.
|
||||
|
||||
- **Was bewusst unverändert blieb**:
|
||||
- Volt-PHP-Logik in allen Dateien (mount, saveCompany,
|
||||
saveContact, deleteContact etc.).
|
||||
- Flash-Strings („Stammdaten wurden gespeichert.",
|
||||
„Pressekontakt wurde angelegt./aktualisiert./gelöscht.",
|
||||
„Token wurde erstellt", „Token wurde widerrufen.",
|
||||
„API-Tokens werden erst freigeschaltet" etc.) —
|
||||
bleiben in Volt-PHP wie zuvor und erscheinen in den
|
||||
neuen Hub-Pillen.
|
||||
- Section-Headings „Abrechnung" und „Statistik" — Tests
|
||||
assertieren genau diese Strings.
|
||||
- Strings im Header („Alpha GmbH", „Firma öffnen",
|
||||
`route('me.press-kits.show', …)`).
|
||||
- FluxUI-Form-Komponenten (`flux:input`, `flux:select`,
|
||||
`flux:textarea`, `flux:checkbox`, `flux:field`,
|
||||
`flux:error`, `flux:table*`) — nur Wrapper-Markup
|
||||
verändert.
|
||||
|
||||
- **Verifikation**:
|
||||
- `php artisan view:clear` ✓
|
||||
- `npm run build:portal` ✓ — 418.11 kB CSS / 44.38 kB JS
|
||||
(keine Größenänderung gegenüber 4E).
|
||||
- `php artisan test --compact tests/Feature/{
|
||||
CustomerPortalTest, CustomerCompanyContextTest,
|
||||
PanelConsolidationTest}.php` →
|
||||
**29 passed, 131 assertions**.
|
||||
- Volle Suite: **230 passed, 3 skipped, 1 pre-existing
|
||||
fail** (`ApiDocumentationTest` — fehlende
|
||||
`docs/api/v1.yml`, war auch vor 4G/4E/4D rot).
|
||||
- `vendor/bin/pint --dirty --format agent` → `passed`.
|
||||
|
||||
- **Resultat**:
|
||||
- Mein-Bereich des Users ist visuell **vollständig** auf
|
||||
Hub-Sprache.
|
||||
- Sidebar-Nav (presse-kits, invoices, tokens, bookings)
|
||||
läuft jetzt durchgängig im selben Vokabular wie Dashboard
|
||||
+ Press-Releases.
|
||||
- Detail-Cockpit `press-kits/show` ist das visuell
|
||||
aufwendigste Customer-Page und stimmt jetzt mit Admin-
|
||||
Detail-Show überein (Pillen-Header, KPI-Reihe, Panels,
|
||||
Quick-Nav, Tabellen).
|
||||
|
||||
- **Nächste mögliche Päckchen**:
|
||||
- **4F** Restliche Admin-Bereiche (contacts, categories,
|
||||
presets, footer-codes, slow-requests, users).
|
||||
- **press-release-images-manager** Komponente (kleiner
|
||||
Cleanup aus 4C-Resten).
|
||||
- **Phase 5** Dark-Mode-Konsolidierung.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 4E · Profile & Settings
|
||||
|
||||
- **Anlass**: User „okay weiter" nach 4D. Fünftes Päckchen aus
|
||||
Phase 4: alle Settings-Pages (Admin + Customer) auf Hub-Stil.
|
||||
|
||||
- **Plan-Dokument**: `11-PHASE-4E-PROFILE-SETTINGS.md`.
|
||||
|
||||
- **Zentraler Trick — Wrapper umgebaut**:
|
||||
- **`resources/views/partials/settings-heading.blade.php`**:
|
||||
Komplett neu als Hub-Page-Header (Pille „Admin Backend",
|
||||
Eyebrow „Mein Konto · Einstellungen", H1 „Settings",
|
||||
Subtitle).
|
||||
- **`resources/views/components/settings/layout.blade.php`**:
|
||||
Komplett neu als 2-Spalten-Grid mit Sidebar-Nav-`.panel`
|
||||
(„Mein Konto"-Eyebrow + FluxUI-Navlist drin) und
|
||||
Content-`.panel` mit `.panel-head` für Page-Heading +
|
||||
Subheading im Body. Heading/Subheading-Slots der einzelnen
|
||||
Settings-Pages werden so automatisch im Hub-Look gerendert.
|
||||
- **Effekt**: `settings/appearance.blade.php` musste
|
||||
**gar nicht** angefasst werden — übernimmt den Hub-Look
|
||||
via Wrapper. Die anderen beiden brauchen nur
|
||||
Save-Bar-Polish.
|
||||
|
||||
- **Was umgebaut wurde**:
|
||||
- **`settings/profile.blade.php`**: Verification-Hinweis von
|
||||
`<flux:text>`-Link auf Hub-Warn-Box (gelber Linker Strip,
|
||||
Icon, Hub-Link für „re-send the verification email").
|
||||
Save-Bar mit Border-Top + Token-`Saved.`-Pill.
|
||||
Delete-Button-Section visuell durch Border-Top abgetrennt.
|
||||
- **`settings/password.blade.php`**: Save-Bar gleicher Polish.
|
||||
- **`settings/delete-user-form.blade.php`**: Das `mt-10`-Card
|
||||
durch Hub-Danger-Box mit `border-l-[3px]
|
||||
border-l-[color:var(--color-err)]`, „Danger Zone"-Eyebrow,
|
||||
H3 + Subtitle + Trash-Button. Modal-Markup komplett
|
||||
unverändert (Confirm-Password-Modal kommt aus FluxUI-Std.).
|
||||
- **`customer/profile.blade.php`**: Page-Header („User
|
||||
Backend"-Pille, „Mein Bereich · Profil"-Eyebrow,
|
||||
„Mein Profil"-H1). Form in 3 Panels + Aktions-Panel:
|
||||
Konto (Name/E-Mail/Sprache) · Profil (Anrede,
|
||||
Vor-/Nachname, Telefon, Backlink, Checkboxen) ·
|
||||
Rechnungsadresse (mit Warn-Hub-Box, wenn unvollständig).
|
||||
Zugeordnete-Firmen als eigenes `.panel` mit Hub-Badges
|
||||
(`ok` für Eigentümer, `hub` für Portal/Rolle).
|
||||
- **`customer/security.blade.php`**: Page-Header. 4-spaltige
|
||||
KPI-Reihe mit `.panel` + uppercase-Eyebrow + großer Wert +
|
||||
Hub-Badge (E-Mail bestätigt? · 2FA aktiv? · Letzter Login +
|
||||
IP · Sessions-Count). 2-Spalten-Grid für Passwort + E-Mail
|
||||
in `.panel` mit `.panel-head`. 2FA-`.panel` mit
|
||||
ok-Badge im Header wenn aktiv, Recovery-Codes mit
|
||||
Hub-Bg-Boxes. Sessions-`.panel` mit Eintrags-Counter,
|
||||
Empty-State als Hub-Icon-Box. **Alle Test-Strings
|
||||
erhalten**: „Konto-Sicherheit", „Letzter Login",
|
||||
„Aktive Sessions", „Passwort ändern", „E-Mail-Adresse
|
||||
ändern", „Zwei-Faktor-Authentifizierung",
|
||||
„Rechnungsadresse".
|
||||
|
||||
- **Tests**:
|
||||
- Smoke: `Settings|CustomerProfileSecurity|
|
||||
CustomerCompanyContext` → 33 passed (146 assertions).
|
||||
- Volle Suite: 230 passed / 3 skipped /
|
||||
1 pre-existing `ApiDocumentationTest`-Fail.
|
||||
|
||||
- **Build / Pint**: `npm run build:portal` ✓ (418 kB CSS),
|
||||
`pint --dirty` ✓, keine Linter-Errors.
|
||||
|
||||
- **Bewusst NICHT angefasst**:
|
||||
- Volt-Logik in allen Dateien.
|
||||
- Confirm-User-Deletion-Modal-Markup
|
||||
(`settings/delete-user-form.blade.php`).
|
||||
- Fortify-2FA-Logik (Enable / Disable / Recovery-Codes-
|
||||
Regenerate / QR-Generation).
|
||||
- `<x-action-message>` Component-Wrapper — nur Slot-Inhalt.
|
||||
|
||||
- **Nächster Schritt** — Vorschlag:
|
||||
- **4G** Customer-Bereiche (invoices, tokens, bookings,
|
||||
company-switcher, me/press-kits/*) — Tests fordern
|
||||
Strings, aber alle gut greifbar.
|
||||
- **4F** Restliche Admin-Bereiche (contacts, categories,
|
||||
presets, footer-codes, slow-requests, users) — größeres
|
||||
Päckchen, sollte weiter unterteilt werden.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-20 · Phase 4D · Companies (admin)
|
||||
|
||||
- **Anlass**: User „okay weiter" nach 4C. Viertes Päckchen aus
|
||||
Phase 4: alle vier Admin-Companies-Pages auf Hub-Stil.
|
||||
|
||||
- **Plan-Dokument**: `10-PHASE-4D-COMPANIES.md`.
|
||||
|
||||
- **Was umgebaut wurde**:
|
||||
- **`admin/companies/index.blade.php`**: Hub-Page-Header
|
||||
(„Admin Backend"-Pille, Eyebrow „Stammdaten · Firmen",
|
||||
H1 „Firmen", Subtitle, CTA „Neue Firma" rechts) →
|
||||
3 `<x-portal.stat-card>` (Gesamt/Aktiv/Inaktiv) →
|
||||
Filter-`.panel` mit Combobox-Selects unverändert in der
|
||||
Logik, nur Hülle getauscht → Tabelle in `.panel
|
||||
overflow-hidden` mit `.panel-head` „Alle Firmen" +
|
||||
Eintrags-Counter. Tabellen-Status/Portal/Count-Badges
|
||||
auf Hub-Klassen (`badge ok|err|hub` + Dot) gesetzt.
|
||||
Empty-State als Hub-Icon-Box.
|
||||
- **`admin/companies/show.blade.php`**: Header mit
|
||||
Status-, Portal- und ID-Pille; Logo-Box neben H1. KPI-Reihe
|
||||
(3 stat-cards). Tabs „Überblick" / „Kontakte" als schlankes
|
||||
Bottom-Border-Pattern (kein FluxUI-Tabs-Group nötig). Beide
|
||||
Tab-Inhalte komplett in `.panel`-Sektionen mit `dl`-Layouts
|
||||
bzw. Kontakt-Karten. Existing-Contact-Combobox bleibt
|
||||
FluxUI; Wrapper auf Hub-`bg-elev`. Flash-Boxen
|
||||
(success/error/info) auf Token-Pillen.
|
||||
- **`admin/companies/edit.blade.php`**: Page-Header
|
||||
(„ID"-Pille), 5 Form-Panels (Basisinformationen, Adresse,
|
||||
Rechtliche Daten, Logo & Status, Aktionen). Required-Marker
|
||||
auf `text-[color:var(--color-err)]`. Logo-Vorschau-Bild
|
||||
nutzt Hub-Token-Border. „Logo wird beim Speichern entfernt"
|
||||
auf Hub-Warn-Box (statt `flux:callout`).
|
||||
**Delete-Confirm-Modal komplett unverändert.**
|
||||
- **`admin/companies/create.blade.php`**: gleiches Schema
|
||||
wie Edit, ohne Status-Pille und ohne Delete-Button. Forms
|
||||
in 5 Panels strukturiert.
|
||||
|
||||
- **Tests**:
|
||||
- Companies-Smoke: `UserManagement|PortalAssetManifest`
|
||||
→ 25 passed (227 assertions).
|
||||
- Volle Suite: 230 passed / 3 skipped /
|
||||
1 vorbestehender Fail (`ApiDocumentationTest` —
|
||||
fehlende Datei `docs/api/v1.yml`, nicht durch Phase 4D
|
||||
verursacht).
|
||||
|
||||
- **Build / Pint**: `npm run build:portal` ✓ (418 kB CSS,
|
||||
44 kB JS), `vendor/bin/pint --dirty --format agent` ✓.
|
||||
|
||||
- **Bewusst NICHT angefasst**:
|
||||
- Volt-Logik in allen vier Dateien.
|
||||
- `deleteCompany`-Methode und ihr Confirm-Modal-Markup
|
||||
(Test `UserManagementTest::admin can delete company`
|
||||
prüft Redirect-Verhalten, nicht UI).
|
||||
- `<flux:select variant="combobox">` (Kontakt-/User-Lookup)
|
||||
und alle anderen `flux:field`/`flux:input`-Bausteine —
|
||||
Token-Bridging trägt.
|
||||
- Test-relevante Strings: „Firmen", „Portal", „Alle Portale",
|
||||
„Presseecho", „Businessportal24", „/admin/companies/…".
|
||||
|
||||
- **Nächster Schritt (Phase 4E oder 4F)**:
|
||||
- **4E** = Profile/Settings (`settings.*`, `me.profile`,
|
||||
`me.security`) — eher klein, viele kleine Panels.
|
||||
- **4F** = Restliche Admin-Bereiche (contacts/, categories/,
|
||||
presets/, footer-codes/, slow-requests, …) — Päckchen-Größe.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-19 · Phase 4C · Press-Releases Forms (create / edit)
|
||||
|
||||
- **Anlass**: User-Freigabe „ja" nach 4B. Drittes Päckchen aus
|
||||
|
|
|
|||
976
dev/frontend/tailwind_v3/User Neue Mitteilung presseportale.html
Normal file
976
dev/frontend/tailwind_v3/User Neue Mitteilung presseportale.html
Normal file
|
|
@ -0,0 +1,976 @@
|
|||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>presseportale.com — Neue Pressemitteilung</title>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700&family=Source+Serif+4:opsz,wght@8..60,400;8..60,500;8..60,600;8..60,700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
||||
|
||||
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
bg:"#F6F4EF","bg-elev":"#FBFAF6","bg-rule":"#E2DDD0","bg-rule-2":"#EDE7D7",
|
||||
"bg-card":"#FFFFFF","bg-card-warm":"#EFEADC",
|
||||
hub:"#1A2540","hub-2":"#243152","hub-3":"#2E3D66",
|
||||
"hub-soft":"#E5E9F1","hub-soft-2":"#CFD6E4","hub-line":"#7B8FBF",
|
||||
accent:"#B07A3A","accent-deep":"#8A5E27","accent-soft":"#F1E6D3",
|
||||
ink:"#1A1F1C","ink-2":"#3A413D","ink-3":"#5A6360","ink-4":"#8A918D",
|
||||
"ink-on-dark":"#F6F4EF","ink-on-dark-2":"#B2B9C7","ink-on-dark-3":"#7B8FBF",
|
||||
ok:"#2E8540","ok-soft":"#E2F1E5",
|
||||
warn:"#A87A1F","warn-soft":"#F6EAC8",
|
||||
err:"#A8331F","err-soft":"#F4DAD2",
|
||||
},
|
||||
fontFamily:{
|
||||
sans:['"Inter Tight"','Inter','system-ui','sans-serif'],
|
||||
serif:['"Source Serif 4"','Georgia','serif'],
|
||||
mono:['"JetBrains Mono"','"SF Mono"','ui-monospace','monospace'],
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,body{margin:0;padding:0;}
|
||||
body{background:#E8E4DA;font-family:"Inter Tight",system-ui,sans-serif;}
|
||||
|
||||
.eyebrow{font-size:10.5px;font-weight:700;letter-spacing:.20em;text-transform:uppercase;color:#1A2540;}
|
||||
.eyebrow.muted{color:#5A6360;letter-spacing:.16em;font-weight:600;font-size:10px;}
|
||||
.eyebrow.accent{color:#8A5E27;}
|
||||
.eyebrow.on-dark{color:#7B8FBF;}
|
||||
|
||||
.rule{height:1px;background:#E2DDD0;border:0;margin:0;}
|
||||
|
||||
/* Sidebar */
|
||||
.nav-item{display:flex;align-items:center;gap:11px;padding:8px 12px;border-radius:4px;font-size:13px;font-weight:500;color:#3A413D;transition:background .12s,color .12s;position:relative;}
|
||||
.nav-item:hover{background:#F6F4EF;color:#1A2540;}
|
||||
.nav-item.active{background:#E5E9F1;color:#1A2540;font-weight:600;}
|
||||
.nav-item.active::before{content:"";position:absolute;left:-1px;top:6px;bottom:6px;width:2px;background:#1A2540;border-radius:0 2px 2px 0;}
|
||||
.nav-item.disabled{color:#8A918D;cursor:default;}
|
||||
.nav-item.disabled:hover{background:transparent;color:#8A918D;}
|
||||
.nav-item .ico{width:16px;height:16px;flex-shrink:0;color:currentColor;opacity:.8;}
|
||||
.nav-item.active .ico{opacity:1;}
|
||||
.nav-section{font-size:10px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:#8A918D;padding:0 12px 6px;}
|
||||
|
||||
/* Panels */
|
||||
.panel{background:#FFFFFF;border:1px solid #E2DDD0;border-radius:6px;}
|
||||
.panel-warm{background:#FBFAF6;border:1px solid #E2DDD0;border-radius:6px;}
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary{display:inline-flex;align-items:center;gap:8px;justify-content:center;padding:10px 16px;background:#1A2540;color:#fff;border-radius:4px;font-size:13px;font-weight:600;transition:background .15s;}
|
||||
.btn-primary:hover{background:#243152;}
|
||||
.btn-primary.full{width:100%;padding:11px 16px;}
|
||||
.btn-secondary{display:inline-flex;align-items:center;gap:8px;justify-content:center;padding:8px 14px;background:#fff;color:#1A2540;border:1px solid #CFD6E4;border-radius:4px;font-size:12.5px;font-weight:600;transition:border-color .15s,background .15s;}
|
||||
.btn-secondary:hover{border-color:#1A2540;background:#F6F4EF;}
|
||||
.btn-secondary.full{width:100%;}
|
||||
.btn-ghost{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;color:#3A413D;border-radius:4px;font-size:12px;font-weight:500;transition:background .12s,color .12s;}
|
||||
.btn-ghost:hover{background:#F6F4EF;color:#1A2540;}
|
||||
|
||||
/* Badges */
|
||||
.badge{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:99px;font-size:10.5px;font-weight:700;letter-spacing:.10em;text-transform:uppercase;}
|
||||
.badge.warn{background:#F6EAC8;color:#8A5E27;}
|
||||
.badge.ok{background:#E2F1E5;color:#1F5E2E;}
|
||||
.badge.err{background:#F4DAD2;color:#8E2A19;}
|
||||
.badge.hub{background:#E5E9F1;color:#1A2540;}
|
||||
.badge.muted{background:#EFEADC;color:#5A6360;}
|
||||
.badge.dot::before{content:"";width:6px;height:6px;border-radius:99px;background:currentColor;}
|
||||
|
||||
/* BALD-Badge */
|
||||
.badge-bald{display:inline-flex;align-items:center;gap:5px;padding:2px 8px;border-radius:99px;font-size:9.5px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;background:#F1E6D3;color:#8A5E27;border:1px dashed #E1C883;}
|
||||
.badge-bald::before{content:"";width:4px;height:4px;border-radius:99px;background:#B07A3A;}
|
||||
|
||||
/* Bridge ribbon */
|
||||
.bridge-row{display:inline-flex;align-items:center;gap:6px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10.5px;letter-spacing:.10em;text-transform:uppercase;color:#5A6360;}
|
||||
.dot-pe{width:6px;height:6px;border-radius:99px;background:#1F4D3A;}
|
||||
.dot-bp{width:6px;height:6px;border-radius:99px;background:#C84A1E;}
|
||||
|
||||
/* Editor wrapper */
|
||||
.field-label{display:flex;align-items:center;gap:8px;font-size:10.5px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:#5A6360;margin-bottom:8px;}
|
||||
.field-label .req{color:#A8331F;font-weight:700;letter-spacing:0;}
|
||||
.field-help{font-size:11.5px;color:#8A918D;margin-top:6px;line-height:1.5;}
|
||||
.field-help.warn{color:#8A5E27;}
|
||||
.field-counter{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10.5px;color:#8A918D;font-variant-numeric:tabular-nums;}
|
||||
|
||||
/* Inputs */
|
||||
.inp{
|
||||
width:100%;padding:10px 12px;background:#fff;border:1px solid #CFD6E4;border-radius:4px;
|
||||
font-size:13px;color:#1A1F1C;transition:border-color .15s,box-shadow .15s;font-family:inherit;
|
||||
}
|
||||
.inp:focus{outline:none;border-color:#1A2540;box-shadow:0 0 0 3px rgba(26,37,64,.07);}
|
||||
.inp::placeholder{color:#8A918D;}
|
||||
.inp.title{font-size:24px;font-weight:700;letter-spacing:-.5px;line-height:1.2;padding:14px 16px;color:#1A1F1C;font-family:"Source Serif 4",Georgia,serif;}
|
||||
.inp.title::placeholder{color:#B5BCB9;font-weight:400;}
|
||||
.inp.subtitle{font-size:15px;font-weight:500;line-height:1.4;padding:11px 14px;color:#3A413D;font-family:"Source Serif 4",Georgia,serif;}
|
||||
.inp.subtitle::placeholder{color:#B5BCB9;font-weight:400;}
|
||||
.inp.small{padding:7px 10px;font-size:12.5px;}
|
||||
|
||||
.select{
|
||||
width:100%;padding:8px 30px 8px 12px;background:#fff;border:1px solid #CFD6E4;border-radius:4px;
|
||||
font-size:12.5px;color:#1A1F1C;appearance:none;background-image:url("data:image/svg+xml,%3Csvg width='10' height='10' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3 4.5l3 3 3-3' stroke='%235A6360' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||||
background-repeat:no-repeat;background-position:right 10px center;cursor:pointer;
|
||||
}
|
||||
.select:focus{outline:none;border-color:#1A2540;}
|
||||
|
||||
/* Toolbar / editor */
|
||||
.toolbar{display:flex;align-items:center;gap:2px;padding:7px 10px;border-bottom:1px solid #E2DDD0;background:#FBFAF6;border-radius:4px 4px 0 0;flex-wrap:wrap;}
|
||||
.tb-btn{display:inline-flex;align-items:center;justify-content:center;min-width:28px;height:28px;padding:0 7px;border-radius:3px;font-size:12.5px;font-weight:600;color:#3A413D;border:1px solid transparent;transition:background .12s,color .12s,border-color .12s;}
|
||||
.tb-btn:hover{background:#fff;border-color:#CFD6E4;color:#1A2540;}
|
||||
.tb-btn.is-active{background:#1A2540;color:#fff;}
|
||||
.tb-btn .lbl{font-size:11px;letter-spacing:.04em;}
|
||||
.tb-sep{width:1px;height:18px;background:#E2DDD0;margin:0 4px;}
|
||||
|
||||
.editor-body{padding:18px 20px;min-height:340px;font-family:"Source Serif 4",Georgia,serif;font-size:15px;line-height:1.65;color:#1A1F1C;}
|
||||
.editor-body h2{font-family:"Source Serif 4",Georgia,serif;font-size:18px;font-weight:700;color:#1A1F1C;margin:18px 0 8px;letter-spacing:-.2px;}
|
||||
.editor-body p{margin:0 0 12px;}
|
||||
.editor-body p.lede{font-weight:500;font-size:16px;color:#1A1F1C;}
|
||||
.editor-body strong{color:#1A1F1C;font-weight:700;}
|
||||
.editor-body em{font-style:italic;}
|
||||
.editor-body ul{margin:6px 0 14px;padding-left:22px;}
|
||||
.editor-body ul li{margin:4px 0;}
|
||||
.editor-body a{color:#8A5E27;text-decoration:underline;text-underline-offset:2px;text-decoration-thickness:1px;text-decoration-color:rgba(138,94,39,.45);}
|
||||
.editor-cursor{display:inline-block;width:1px;height:18px;background:#1A2540;vertical-align:-3px;animation:blink 1s steps(2) infinite;margin-left:1px;}
|
||||
@keyframes blink{50%{opacity:0;}}
|
||||
|
||||
/* Media tiles */
|
||||
.media-tile{position:relative;border:1px solid #E2DDD0;border-radius:5px;background:#FBFAF6;overflow:hidden;}
|
||||
.media-thumb{aspect-ratio:16/10;background:#EDE7D7;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;}
|
||||
.media-meta{padding:9px 11px;}
|
||||
.media-cap{font-size:11.5px;color:#3A413D;line-height:1.4;}
|
||||
.media-alt{font-size:10.5px;color:#8A918D;margin-top:3px;font-style:italic;line-height:1.4;}
|
||||
.media-cover-flag{position:absolute;top:8px;left:8px;}
|
||||
.media-actions{position:absolute;top:8px;right:8px;display:flex;gap:4px;}
|
||||
.media-act{width:24px;height:24px;border-radius:3px;background:rgba(26,31,28,.62);color:#fff;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px);}
|
||||
.media-act:hover{background:rgba(26,37,64,.85);}
|
||||
|
||||
/* Drop zone */
|
||||
.dropzone{border:1.5px dashed #CFD6E4;border-radius:5px;background:#FBFAF6;padding:18px 16px;display:flex;align-items:center;justify-content:center;gap:14px;text-align:left;transition:border-color .15s,background .15s;}
|
||||
.dropzone:hover{border-color:#1A2540;background:#F6F4EF;}
|
||||
.dropzone .dz-ico{width:42px;height:42px;border-radius:5px;background:#E5E9F1;color:#1A2540;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
|
||||
/* Right sidebar — meta blocks */
|
||||
.meta-card{background:#fff;border:1px solid #E2DDD0;border-radius:5px;padding:14px 16px;}
|
||||
.meta-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px;}
|
||||
.meta-title{font-size:10.5px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:#1A2540;}
|
||||
.meta-title .num{font-family:"JetBrains Mono",ui-monospace,monospace;color:#8A918D;margin-right:6px;letter-spacing:.06em;}
|
||||
|
||||
/* Checklist */
|
||||
.check-row{display:flex;align-items:flex-start;gap:10px;padding:7px 0;font-size:12.5px;}
|
||||
.check-row .ic{width:16px;height:16px;border-radius:99px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px;}
|
||||
.check-row.ok .ic{background:#E2F1E5;color:#1F5E2E;}
|
||||
.check-row.warn .ic{background:#F6EAC8;color:#8A5E27;}
|
||||
.check-row.err .ic{background:#F4DAD2;color:#8E2A19;}
|
||||
.check-row .lbl{flex:1;color:#1A1F1C;line-height:1.4;}
|
||||
.check-row .lbl .sub{display:block;font-size:11px;color:#5A6360;margin-top:1px;}
|
||||
.check-row.ok .lbl{color:#3A413D;}
|
||||
|
||||
/* Portal chooser */
|
||||
.portal-opt{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1px solid #E2DDD0;border-radius:4px;background:#FBFAF6;cursor:pointer;transition:border-color .12s,background .12s;}
|
||||
.portal-opt:hover{border-color:#1A2540;}
|
||||
.portal-opt.is-checked{border-color:#1A2540;background:#fff;box-shadow:inset 0 0 0 1px #1A2540;}
|
||||
.portal-opt .pcheck{width:14px;height:14px;border:1.5px solid #CFD6E4;border-radius:3px;background:#fff;flex-shrink:0;margin-top:2px;position:relative;}
|
||||
.portal-opt.is-checked .pcheck{background:#1A2540;border-color:#1A2540;}
|
||||
.portal-opt.is-checked .pcheck::after{content:"";position:absolute;left:3px;top:0;width:4px;height:8px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg);}
|
||||
.portal-opt .ptitle{font-size:12.5px;font-weight:600;color:#1A1F1C;display:flex;align-items:center;gap:7px;}
|
||||
.portal-opt .psub{font-size:11px;color:#5A6360;margin-top:2px;line-height:1.4;}
|
||||
|
||||
/* Tag chip */
|
||||
.tag-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 4px 3px 9px;background:#E5E9F1;color:#1A2540;border-radius:3px;font-size:11.5px;font-weight:500;}
|
||||
.tag-chip .x{width:16px;height:16px;border-radius:2px;display:inline-flex;align-items:center;justify-content:center;color:#1A2540;transition:background .12s;}
|
||||
.tag-chip .x:hover{background:#CFD6E4;}
|
||||
|
||||
/* Boilerplate panel */
|
||||
.boiler{background:#FBFAF6;border:1px dashed #CFD6E4;border-radius:5px;padding:14px 16px;}
|
||||
|
||||
/* Autosave row */
|
||||
.autosave{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;color:#5A6360;}
|
||||
.autosave .dot{width:6px;height:6px;border-radius:99px;background:#2E8540;box-shadow:0 0 0 3px rgba(46,133,64,.15);}
|
||||
|
||||
/* Tabs (für Pressekontakt) */
|
||||
.tab-mini{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;font-size:11.5px;font-weight:600;color:#5A6360;border-radius:3px;cursor:pointer;}
|
||||
.tab-mini:hover{color:#1A2540;}
|
||||
.tab-mini.is-active{background:#E5E9F1;color:#1A2540;}
|
||||
|
||||
/* Collapsible */
|
||||
.collapse-head{display:flex;align-items:center;justify-content:space-between;cursor:pointer;}
|
||||
.collapse-head:hover .meta-title{color:#243152;}
|
||||
.chev{width:16px;height:16px;color:#5A6360;transition:transform .15s;}
|
||||
.meta-card.is-open .chev{transform:rotate(180deg);}
|
||||
|
||||
/* AI inline hint (BALD) */
|
||||
.ai-hint{display:flex;align-items:center;gap:9px;padding:8px 11px;background:#FBF6EB;border:1px dashed #E1C883;border-radius:4px;font-size:11.5px;color:#8A5E27;line-height:1.45;}
|
||||
.ai-hint .ai-ico{width:22px;height:22px;border-radius:4px;background:#F1E6D3;color:#8A5E27;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.ai-hint .ai-cta{margin-left:auto;font-size:10.5px;font-weight:700;letter-spacing:.10em;text-transform:uppercase;color:#5A6360;padding:3px 7px;border:1px solid #E2DDD0;border-radius:99px;background:#fff;cursor:not-allowed;}
|
||||
|
||||
/* Char counter pill */
|
||||
.meter{display:inline-flex;align-items:center;gap:6px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10.5px;color:#5A6360;font-variant-numeric:tabular-nums;}
|
||||
.meter .bar{position:relative;width:60px;height:4px;background:#EDE7D7;border-radius:99px;overflow:hidden;}
|
||||
.meter .bar i{position:absolute;left:0;top:0;bottom:0;background:#1A2540;border-radius:99px;}
|
||||
.meter.good .bar i{background:#2E8540;}
|
||||
.meter.warn .bar i{background:#A87A1F;}
|
||||
|
||||
/* Sticky aside helper */
|
||||
@media (min-width: 1280px){
|
||||
.sticky-aside{position:sticky;top:18px;}
|
||||
}
|
||||
|
||||
/* mark / highlight in editor */
|
||||
.editor-body mark.suggest{background:linear-gradient(transparent 55%, #F6EAC8 55%);padding:0 1px;border-radius:1px;}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-bg text-ink font-sans antialiased">
|
||||
|
||||
<!-- ============== ARTBOARD ============== -->
|
||||
<div class="mx-auto bg-bg" style="width:1440px;">
|
||||
|
||||
<div class="flex" style="min-height:1500px;">
|
||||
|
||||
<!-- ============== SIDEBAR ============== -->
|
||||
<aside class="bg-bg-elev border-r border-bg-rule flex flex-col" style="width:260px;">
|
||||
<div class="px-5 pt-6 pb-5">
|
||||
<a href="Hub Landing presseportale.html" class="flex items-baseline gap-2">
|
||||
<span class="text-[19px] font-bold tracking-[-0.4px] text-hub leading-none">presseportale<span class="text-accent">.com</span></span>
|
||||
</a>
|
||||
<div class="eyebrow muted mt-2">Publisher · Hub</div>
|
||||
|
||||
<button class="mt-4 w-full grid items-center gap-2.5 px-3 py-2.5 bg-white border border-bg-rule rounded-[4px] hover:border-hub/40 text-left" style="grid-template-columns:auto 1fr auto;">
|
||||
<span class="w-7 h-7 rounded-[3px] bg-hub-soft border border-hub-soft-2 flex items-center justify-center text-hub text-[11px] font-bold">TU</span>
|
||||
<span class="min-w-0">
|
||||
<span class="block text-[12.5px] font-semibold text-ink leading-tight truncate">Test User</span>
|
||||
<span class="block text-[10.5px] text-ink-3 leading-tight mt-0.5 truncate">Tegernseer Brauerei AG +1</span>
|
||||
</span>
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" class="text-ink-3">
|
||||
<path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 7.5l3-3 3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" opacity="0.4"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="px-3 flex-1">
|
||||
<div class="nav-section">Mein Bereich</div>
|
||||
<div class="space-y-0.5 mb-5">
|
||||
<a class="nav-item" href="User Dashboard presseportale.html">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M2 7l6-5 6 5v7H2z" stroke="currentColor" stroke-width="1.4"/><path d="M6 14V9h4v5" stroke="currentColor" stroke-width="1.4"/></svg>
|
||||
Übersicht
|
||||
</a>
|
||||
<a class="nav-item active" href="User Pressemitteilungen presseportale.html">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><rect x="2.5" y="2.5" width="9" height="11" stroke="currentColor" stroke-width="1.4"/><path d="M11.5 5h2v8.5H4" stroke="currentColor" stroke-width="1.4"/><path d="M5 5.5h4M5 8h4M5 10.5h2.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
||||
Meine Pressemitteilungen
|
||||
<span class="badge hub ml-auto" style="font-size:9.5px;padding:1px 6px;letter-spacing:0.08em;">25</span>
|
||||
</a>
|
||||
<a class="nav-item" href="#">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><rect x="2.5" y="3.5" width="11" height="10" stroke="currentColor" stroke-width="1.4"/><path d="M2.5 6h11" stroke="currentColor" stroke-width="1.4"/><path d="M6 9h1M9 9h1M6 11.5h1M9 11.5h1" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
Firmen
|
||||
</a>
|
||||
<a class="nav-item" href="#">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M3 5h10l-1 9H4z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M6 5V3.5a2 2 0 014 0V5" stroke="currentColor" stroke-width="1.4"/></svg>
|
||||
Buchungen & Add-ons
|
||||
</a>
|
||||
<span class="nav-item disabled">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M3 13V8M7 13V5M11 13V9" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
Statistiken
|
||||
<span class="ml-auto text-[9.5px] tracking-[0.14em] uppercase font-semibold text-ink-4">bald</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">Finanzen</div>
|
||||
<div class="space-y-0.5 mb-5">
|
||||
<span class="nav-item disabled">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="5" stroke="currentColor" stroke-width="1.4"/><path d="M8 5.5v5M6 8h4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
Credits & Tarif
|
||||
<span class="ml-auto text-[9.5px] tracking-[0.14em] uppercase font-semibold text-ink-4">bald</span>
|
||||
</span>
|
||||
<a class="nav-item" href="#">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M3 2.5h7l3 3v8H3z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M10 2.5V5.5h3" stroke="currentColor" stroke-width="1.4"/><path d="M5.5 8h5M5.5 10.5h5M5.5 6h2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
|
||||
Rechnungen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">Konto</div>
|
||||
<div class="space-y-0.5 mb-5">
|
||||
<a class="nav-item" href="#">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="6" r="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M3 13.5c.7-2.4 2.7-4 5-4s4.3 1.6 5 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
Profil
|
||||
</a>
|
||||
<a class="nav-item" href="#">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M8 2l5 2v4c0 3-2 5-5 6-3-1-5-3-5-6V4z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M6 8l1.5 1.5L10.5 6" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Sicherheit
|
||||
</a>
|
||||
<a class="nav-item" href="#">
|
||||
<svg class="ico" viewBox="0 0 16 16" fill="none"><circle cx="6" cy="8" r="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M8.5 8h5M11 8v2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
API & Integrationen
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="px-4 pb-4">
|
||||
<div class="bg-hub text-ink-on-dark rounded-[5px] p-4 relative overflow-hidden">
|
||||
<div class="absolute -top-6 -right-6 w-16 h-16 rounded-full bg-hub-3 opacity-50"></div>
|
||||
<div class="absolute -bottom-8 -left-8 w-20 h-20 rounded-full bg-hub-3 opacity-30"></div>
|
||||
<div class="relative">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-accent animate-pulse"></span>
|
||||
<span class="eyebrow on-dark" style="color:#F4D89C;">Testmodus aktiv</span>
|
||||
</div>
|
||||
<div class="text-[12px] leading-[1.5] text-ink-on-dark-2">
|
||||
Angemeldet als <strong class="text-white font-semibold">Test User</strong>.<br/>
|
||||
Admin: <strong class="text-white font-semibold">Portal Admin</strong>
|
||||
</div>
|
||||
<button class="mt-3 w-full px-3 py-2 bg-white text-hub text-[12px] font-semibold rounded-[3px] hover:bg-bg transition-colors flex items-center justify-center gap-1.5">
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M9 3L3 9M3 9H8M3 9V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Zurück zum Admin
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ============== MAIN ============== -->
|
||||
<main class="flex-1 min-w-0" data-screen-label="01 Neue Mitteilung erstellen">
|
||||
|
||||
<!-- Topbar -->
|
||||
<div class="bg-bg-elev border-b border-bg-rule">
|
||||
<div class="px-10 py-3 flex items-center gap-6">
|
||||
<div class="flex items-center gap-2 text-[12px] text-ink-3 font-medium">
|
||||
<a href="Hub Landing presseportale.html" class="hover:text-hub">Hub</a>
|
||||
<span class="text-ink-4">/</span>
|
||||
<a href="User Dashboard presseportale.html" class="hover:text-hub">User Backend</a>
|
||||
<span class="text-ink-4">/</span>
|
||||
<a href="User Pressemitteilungen presseportale.html" class="hover:text-hub">Pressemitteilungen</a>
|
||||
<span class="text-ink-4">/</span>
|
||||
<span class="text-hub font-semibold">Neue Mitteilung</span>
|
||||
</div>
|
||||
|
||||
<span class="flex-1"></span>
|
||||
|
||||
<span class="bridge-row">
|
||||
<span class="dot-pe"></span> presseecho
|
||||
<span class="text-ink-4 mx-1">·</span>
|
||||
<span class="dot-bp"></span> businessportal24
|
||||
</span>
|
||||
|
||||
<span class="w-px h-5 bg-bg-rule"></span>
|
||||
|
||||
<span class="autosave">
|
||||
<span class="dot"></span>
|
||||
Zuletzt gespeichert vor <strong class="text-ink-2 font-semibold mx-1">8 Sek.</strong>
|
||||
<span class="text-ink-4">·</span>
|
||||
<span class="font-mono text-[10.5px] text-ink-4">PM-DRAFT-2026-0026</span>
|
||||
</span>
|
||||
|
||||
<span class="w-px h-5 bg-bg-rule"></span>
|
||||
|
||||
<button class="btn-ghost">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M2 8l4 4 8-8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Speichern
|
||||
</button>
|
||||
<button class="btn-secondary">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.3"/><path d="M5 8.5L7 10.5 11 6.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Vorschau
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inhalt -->
|
||||
<div class="px-10 py-8">
|
||||
|
||||
<!-- ============== PAGE HEADER ============== -->
|
||||
<header class="grid items-end gap-8 mb-7" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">User Backend</span>
|
||||
<span class="eyebrow muted">Mein Bereich · A · 02 · Neu</span>
|
||||
<span class="badge muted dot">Entwurf</span>
|
||||
</div>
|
||||
<h1 class="text-[32px] font-bold tracking-[-0.7px] text-ink leading-[1.1] m-0">Neue Pressemitteilung</h1>
|
||||
<p class="mt-2 text-[13px] text-ink-3 leading-[1.55] max-w-[640px] m-0">
|
||||
Schreibfläche links, Steuerung rechts. Speichert automatisch alle paar Sekunden. Beim Klick auf
|
||||
<span class="font-semibold text-hub">„Zur Prüfung senden"</span> läuft eine kurze Qualitäts-Checkliste.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<a href="User Pressemitteilungen presseportale.html" class="btn-secondary whitespace-nowrap">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M7.5 3L4.5 6l3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Zur Liste
|
||||
</a>
|
||||
<button class="btn-secondary whitespace-nowrap text-err" style="color:#8E2A19;border-color:#E0B0A5;">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M3 4h10M5.5 4V2.5h5V4M5 4l.5 9.5h5L11 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Entwurf verwerfen
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ============== 2-COLUMN GRID ============== -->
|
||||
<div class="grid gap-7" style="grid-template-columns:minmax(0,1fr) 360px;">
|
||||
|
||||
<!-- ===================================================== -->
|
||||
<!-- LINKS: SCHREIBFLÄCHE -->
|
||||
<!-- ===================================================== -->
|
||||
<div class="space-y-6 min-w-0">
|
||||
|
||||
<!-- ─── 1) FIRMA-SELEKTOR ─── -->
|
||||
<section class="panel" style="padding:14px 18px;">
|
||||
<div class="flex items-center gap-5">
|
||||
<div class="field-label" style="margin-bottom:0;">Für Firma</div>
|
||||
<button class="flex items-center gap-2.5 px-3 py-1.5 bg-white border border-hub rounded-[4px] hover:bg-hub-soft transition-colors" style="box-shadow:inset 0 0 0 1px #1A2540;">
|
||||
<span class="w-5 h-5 rounded-[3px] bg-hub-soft border border-hub-soft-2 flex items-center justify-center text-hub text-[9.5px] font-bold">TB</span>
|
||||
<span class="text-[13px] font-semibold text-hub">Tegernseer Brauerei AG</span>
|
||||
<svg width="10" height="10" viewBox="0 0 12 12" fill="none" class="text-hub-3"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<span class="text-[11.5px] text-ink-3">
|
||||
Boilerplate und Pressekontakt werden vorbefüllt.
|
||||
</span>
|
||||
<span class="flex-1"></span>
|
||||
<a href="#" class="btn-ghost text-[11.5px]">
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M6 2v8M2 6h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
|
||||
Neue Firma anlegen
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 2) TITEL ─── -->
|
||||
<section class="panel" style="padding:20px 22px;">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="field-label" style="margin-bottom:0;">
|
||||
Titel / Headline <span class="req">*</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="meter good">
|
||||
<span class="bar"><i style="width:62%;"></i></span>
|
||||
62 / 100
|
||||
</span>
|
||||
<span class="badge-bald">KI-Titel · bald</span>
|
||||
</div>
|
||||
</div>
|
||||
<input class="inp title" value="Tegernseer Brauerei eröffnet Craft-Beer-Manufaktur am Tegernseer Hafen" />
|
||||
<div class="field-help">
|
||||
40–90 Zeichen empfohlen. Konkret, ohne Marketing-Floskeln. Wer? Was? Wo?
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 3) SUBLINE ─── -->
|
||||
<section class="panel" style="padding:20px 22px;">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="field-label" style="margin-bottom:0;">
|
||||
Untertitel <span class="text-ink-4 font-normal" style="letter-spacing:0;text-transform:none;">— optional</span>
|
||||
</div>
|
||||
<span class="meter">
|
||||
<span class="bar"><i style="width:48%;"></i></span>
|
||||
118 / 200
|
||||
</span>
|
||||
</div>
|
||||
<input class="inp subtitle" value="Neue Schau-Manufaktur mit Verkostungsraum und Besucherführungen — Eröffnung 12. Juni 2026." />
|
||||
</section>
|
||||
|
||||
<!-- ─── 4) FLIESSTEXT ─── -->
|
||||
<section class="panel">
|
||||
<div class="flex items-center justify-between" style="padding:13px 18px 0 18px;">
|
||||
<div class="field-label" style="margin-bottom:0;">
|
||||
Fließtext <span class="req">*</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="meter good">
|
||||
<span class="bar"><i style="width:38%;"></i></span>
|
||||
1.420 / 3.500 Z.
|
||||
</span>
|
||||
<span class="badge-bald">KI-Lektorat · bald</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<div class="toolbar mt-3 mx-3" style="border-radius:4px;border:1px solid #E2DDD0;">
|
||||
<button class="tb-btn" title="Absatz / Stilebene">
|
||||
<span class="lbl">Absatz</span>
|
||||
<svg width="9" height="9" viewBox="0 0 12 12" fill="none" class="ml-1 opacity-70"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<span class="tb-sep"></span>
|
||||
<button class="tb-btn" title="Fett" style="font-weight:700;">B</button>
|
||||
<button class="tb-btn" title="Kursiv" style="font-style:italic;">I</button>
|
||||
<span class="tb-sep"></span>
|
||||
<button class="tb-btn" title="Zwischenüberschrift">H₂</button>
|
||||
<button class="tb-btn" title="Aufzählung">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><circle cx="3" cy="4" r="1" fill="currentColor"/><circle cx="3" cy="8" r="1" fill="currentColor"/><circle cx="3" cy="12" r="1" fill="currentColor"/><path d="M6 4h8M6 8h8M6 12h6" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
<button class="tb-btn" title="Nummerierte Liste">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M6 4h8M6 8h8M6 12h8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/><text x="1.5" y="6" font-size="4.5" font-family="JetBrains Mono" fill="currentColor">1</text><text x="1.5" y="10" font-size="4.5" font-family="JetBrains Mono" fill="currentColor">2</text><text x="1.5" y="14" font-size="4.5" font-family="JetBrains Mono" fill="currentColor">3</text></svg>
|
||||
</button>
|
||||
<button class="tb-btn" title="Zitat">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M3 5.5C3 4.7 3.7 4 4.5 4H6v3.5H4.5C3.7 7.5 3 8.2 3 9V11" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M9 5.5C9 4.7 9.7 4 10.5 4H12v3.5h-1.5C9.7 7.5 9 8.2 9 9V11" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<span class="tb-sep"></span>
|
||||
<button class="tb-btn" title="Link">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M6 10l4-4M5 6.5L4 7.5a2.5 2.5 0 003.5 3.5l1-1M11 9.5l1-1a2.5 2.5 0 00-3.5-3.5l-1 1" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<span class="tb-sep"></span>
|
||||
<button class="tb-btn" title="Rückgängig">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M5 5L2.5 7.5 5 10M2.5 7.5h7.5a3 3 0 010 6H8" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
<button class="tb-btn" title="Wiederholen">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M11 5l2.5 2.5L11 10M13.5 7.5H6a3 3 0 000 6h2" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</button>
|
||||
|
||||
<span class="flex-1"></span>
|
||||
|
||||
<span class="text-[10.5px] text-ink-4 mr-2">Reduzierter Editor — bewusst eingeschränkt für konsistentes Format.</span>
|
||||
</div>
|
||||
|
||||
<!-- Editor body -->
|
||||
<div class="editor-body mx-3 mb-3" style="border:1px solid #E2DDD0;border-top:0;border-radius:0 0 4px 4px;background:#fff;">
|
||||
<p class="lede">
|
||||
<strong>Tegernsee, 11.05.2026.</strong> Die Tegernseer Brauerei AG eröffnet am 12. Juni 2026 ihre neue
|
||||
Craft-Beer-Manufaktur direkt am Tegernseer Hafen. Auf rund 1.200 m² entsteht eine
|
||||
Schau-Brauerei mit angeschlossenem Verkostungsraum und ganzjährigen Besucherführungen.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Der Neubau ergänzt den 1675 gegründeten Stammsitz und setzt einen Fokus auf
|
||||
experimentelle Sude in kleinen Chargen. „Wir wollen Bier-Handwerk wieder
|
||||
<mark class="suggest">erlebbar machen</mark> — direkt am Wasser, mit offenen Kesseln und
|
||||
unseren Brauern als Gastgebern", erklärt Vorstandsvorsitzender Dr. Markus Weishaupt.
|
||||
</p>
|
||||
|
||||
<h2>Verkostung, Manufaktur-Touren, regionale Wertschöpfung</h2>
|
||||
<p>
|
||||
Die Manufaktur kombiniert drei Erlebnisformate unter einem Dach:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>Schau-Brauerei mit gläsernem Sudhaus und 30-minütigen Kurzführungen.</li>
|
||||
<li>Verkostungsraum für bis zu 60 Personen, mit wechselnden Sorten aus der Pilot-Anlage.</li>
|
||||
<li>Direktverkauf ab Werk — ausschließlich aus Tegernseer Roh<editor-cursor></editor-cursor></li>
|
||||
</ul>
|
||||
|
||||
<p style="color:#8A918D;font-style:italic;">
|
||||
Hier weiterschreiben …
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- AI Hint -->
|
||||
<div class="mx-3 mb-4">
|
||||
<div class="ai-hint">
|
||||
<span class="ai-ico">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M8 2l1.5 4 4 1.5L9.5 9 8 13l-1.5-4L2.5 7.5 6.5 6z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg>
|
||||
</span>
|
||||
<span><strong class="font-semibold">KI-Lektorat</strong> liest Korrektur, schlägt Kürzungen vor und prüft auf werbliche Sprache. Erscheint hier inline — bald verfügbar.</span>
|
||||
<span class="ai-cta">bald</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 5) MEDIEN ─── -->
|
||||
<section class="panel" style="padding:18px 20px 20px;">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="field-label" style="margin-bottom:0;">
|
||||
Medien / Bilder
|
||||
<span class="text-ink-4 font-normal" style="letter-spacing:0;text-transform:none;">— mindestens 1 Bild empfohlen</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-[11.5px] text-ink-3"><strong class="text-ink-2 font-semibold">2</strong> Bilder hochgeladen · 1 Vorschau</span>
|
||||
<span class="badge-bald">KI-Bildgenerierung · bald</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-3 mb-3">
|
||||
|
||||
<!-- Bild 1 -->
|
||||
<div class="media-tile">
|
||||
<div class="media-thumb" style="background:linear-gradient(135deg,#8A6A3A 0%,#3A4D2F 50%,#1A2540 100%);">
|
||||
<span class="media-cover-flag badge ok dot" style="font-size:9px;padding:2px 7px;letter-spacing:.10em;">Titelbild</span>
|
||||
<span class="media-actions">
|
||||
<button class="media-act" title="Bearbeiten"><svg width="11" height="11" viewBox="0 0 16 16" fill="none"><path d="M11 3l2 2-8 8H3v-2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg></button>
|
||||
<button class="media-act" title="Entfernen"><svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M3 3l6 6M9 3l-6 6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></button>
|
||||
</span>
|
||||
<!-- Visual mock -->
|
||||
<svg viewBox="0 0 200 125" class="absolute inset-0 w-full h-full" preserveAspectRatio="xMidYMid slice">
|
||||
<defs>
|
||||
<linearGradient id="bsky" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="#3E5B7A"/><stop offset="1" stop-color="#8A9DAE"/></linearGradient>
|
||||
</defs>
|
||||
<rect width="200" height="80" fill="url(#bsky)"/>
|
||||
<rect y="80" width="200" height="45" fill="#2E3B25"/>
|
||||
<polygon points="0,80 40,60 70,72 110,55 150,68 200,50 200,80" fill="#3F2D1E" opacity="0.85"/>
|
||||
<rect x="60" y="70" width="80" height="22" fill="#C8A86E" opacity="0.95"/>
|
||||
<rect x="62" y="72" width="76" height="3" fill="#8A5E27"/>
|
||||
<rect x="70" y="76" width="6" height="12" fill="#1A1F1C" opacity="0.7"/>
|
||||
<rect x="80" y="76" width="6" height="12" fill="#1A1F1C" opacity="0.7"/>
|
||||
<rect x="90" y="76" width="6" height="12" fill="#1A1F1C" opacity="0.7"/>
|
||||
<rect x="100" y="76" width="6" height="12" fill="#1A1F1C" opacity="0.7"/>
|
||||
<rect x="110" y="76" width="6" height="12" fill="#1A1F1C" opacity="0.7"/>
|
||||
<rect x="120" y="76" width="6" height="12" fill="#1A1F1C" opacity="0.7"/>
|
||||
<rect x="130" y="76" width="6" height="12" fill="#1A1F1C" opacity="0.7"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="media-meta">
|
||||
<div class="media-cap">Außenansicht der neuen Manufaktur am Tegernseer Hafen.</div>
|
||||
<div class="media-alt">Alt: Holzgebäude mit Glasfront direkt am See, Abendlicht.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bild 2 -->
|
||||
<div class="media-tile">
|
||||
<div class="media-thumb" style="background:#4A2F1E;">
|
||||
<span class="media-actions">
|
||||
<button class="media-act" title="Als Titelbild"><svg width="11" height="11" viewBox="0 0 16 16" fill="none"><path d="M8 2l1.5 3.5L13 6l-2.5 2.5L11 12l-3-1.7L5 12l.5-3.5L3 6l3.5-.5z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/></svg></button>
|
||||
<button class="media-act" title="Bearbeiten"><svg width="11" height="11" viewBox="0 0 16 16" fill="none"><path d="M11 3l2 2-8 8H3v-2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg></button>
|
||||
<button class="media-act" title="Entfernen"><svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M3 3l6 6M9 3l-6 6" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></button>
|
||||
</span>
|
||||
<svg viewBox="0 0 200 125" class="absolute inset-0 w-full h-full" preserveAspectRatio="xMidYMid slice">
|
||||
<rect width="200" height="125" fill="#3A2516"/>
|
||||
<ellipse cx="100" cy="50" rx="80" ry="30" fill="#C8A86E" opacity="0.18"/>
|
||||
<rect x="40" y="30" width="34" height="80" rx="3" fill="#B8843A"/>
|
||||
<rect x="42" y="32" width="30" height="6" fill="#8A5E27"/>
|
||||
<rect x="42" y="92" width="30" height="14" fill="#8A5E27"/>
|
||||
<rect x="48" y="46" width="18" height="32" fill="#EFEADC"/>
|
||||
<rect x="83" y="20" width="34" height="90" rx="3" fill="#D4A04A"/>
|
||||
<rect x="85" y="22" width="30" height="6" fill="#A87A1F"/>
|
||||
<rect x="85" y="92" width="30" height="14" fill="#A87A1F"/>
|
||||
<rect x="91" y="42" width="18" height="32" fill="#FBFAF6"/>
|
||||
<rect x="126" y="38" width="34" height="72" rx="3" fill="#A87A4A"/>
|
||||
<rect x="128" y="40" width="30" height="6" fill="#5A3D1E"/>
|
||||
<rect x="128" y="92" width="30" height="14" fill="#5A3D1E"/>
|
||||
<rect x="134" y="54" width="18" height="32" fill="#F1E6D3"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="media-meta">
|
||||
<div class="media-cap">Drei neue Sorten aus der Pilot-Anlage — Spezialeditionen.</div>
|
||||
<div class="media-alt">Alt: Drei Bierflaschen mit unterschiedlichen Etiketten auf Holztisch.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dropzone -->
|
||||
<button class="dropzone" style="flex-direction:column;justify-content:center;text-align:center;min-height:100%;">
|
||||
<span class="dz-ico" style="width:38px;height:38px;">
|
||||
<svg width="18" height="18" viewBox="0 0 16 16" fill="none"><path d="M8 11V4M5 7L8 4l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><rect x="2.5" y="11.5" width="11" height="2" rx="1" stroke="currentColor" stroke-width="1.4"/></svg>
|
||||
</span>
|
||||
<div>
|
||||
<div class="text-[12.5px] font-semibold text-hub leading-tight">Bilder hinzufügen</div>
|
||||
<div class="text-[10.5px] text-ink-3 mt-1 leading-tight">JPG, PNG · max. 8 MB · min. 1.200 × 800 px</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="field-help">
|
||||
Bildunterschrift und <strong class="text-ink-2 font-semibold">Alt-Text</strong> sind pro Bild Pflicht für die Veröffentlichung. Das Titelbild erscheint in der Liste auf presseecho & businessportal24.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 6) ANHÄNGE ─── -->
|
||||
<section class="panel" style="padding:18px 20px 18px;">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="field-label" style="margin-bottom:0;">
|
||||
Anhänge / Downloads
|
||||
<span class="text-ink-4 font-normal" style="letter-spacing:0;text-transform:none;">— optional</span>
|
||||
</div>
|
||||
<button class="btn-ghost text-[11.5px]">
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M6 2v8M2 6h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
|
||||
Datei hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="flex items-center gap-3 p-3 border border-bg-rule rounded-[4px] bg-bg-elev">
|
||||
<span class="w-9 h-11 rounded-[3px] bg-white border border-bg-rule flex items-center justify-center text-[9px] font-bold text-err font-mono">PDF</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-[12.5px] font-semibold text-ink truncate">Pressemappe_Manufaktur.pdf</div>
|
||||
<div class="text-[10.5px] text-ink-3 font-mono mt-0.5">2,4 MB · 12 Seiten</div>
|
||||
</div>
|
||||
<button class="menu-trigger w-7 h-7 rounded-[3px] hover:bg-bg-rule-2"><svg width="13" height="13" viewBox="0 0 12 12" fill="none"><path d="M3 3l6 6M9 3l-6 6" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg></button>
|
||||
</div>
|
||||
<button class="dropzone" style="padding:14px;">
|
||||
<span class="dz-ico" style="width:32px;height:32px;">
|
||||
<svg width="15" height="15" viewBox="0 0 16 16" fill="none"><path d="M6 2.5h4l3 3v8H3v-8z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M10 2.5V5.5h3M8 8v4M6 10l2 2 2-2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</span>
|
||||
<div>
|
||||
<div class="text-[12.5px] font-semibold text-hub leading-tight">PDF oder Dokument ziehen</div>
|
||||
<div class="text-[10.5px] text-ink-3 mt-1">max. 25 MB pro Datei</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ─── 7) BOILERPLATE ─── -->
|
||||
<section class="panel" style="padding:18px 20px 18px;">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="field-label" style="margin-bottom:0;">
|
||||
Über das Unternehmen
|
||||
<span class="text-ink-4 font-normal" style="letter-spacing:0;text-transform:none;">— Boilerplate aus Firma</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="badge muted dot" style="font-size:9.5px;">aus Firmenprofil</span>
|
||||
<button class="btn-ghost text-[11.5px]">
|
||||
<svg width="11" height="11" viewBox="0 0 16 16" fill="none"><path d="M11 3l2 2-8 8H3v-2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
||||
Für diese PM überschreiben
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="boiler font-serif text-[13.5px] text-ink-2 leading-[1.6]">
|
||||
<p class="m-0 mb-2">
|
||||
<strong class="text-ink font-semibold">Über die Tegernseer Brauerei AG.</strong>
|
||||
Die Tegernseer Brauerei wurde 1675 als Klosterbrauerei der Benediktinerabtei Tegernsee gegründet und
|
||||
ist heute eine der ältesten kontinuierlich brauenden Brauereien Bayerns. Im Familienbesitz der Herzöge
|
||||
von Bayern werden jährlich rund 280.000 Hektoliter gebraut. Schwerpunkt: helle Lagerbiere und
|
||||
Spezialitäten nach dem bayerischen Reinheitsgebot.
|
||||
</p>
|
||||
<p class="m-0 text-[12px] text-ink-3 mt-3">
|
||||
<span class="font-semibold text-ink-2">Sitz:</span> Tegernsee · <span class="font-semibold text-ink-2">Mitarbeiter:</span> 142 · <span class="font-semibold text-ink-2">Web:</span> tegernseer-brauerei.de
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field-help">
|
||||
Wird automatisch unter jeder Pressemitteilung dieser Firma angefügt. Pro PM editierbar — Änderungen wirken sich nicht auf andere Mitteilungen aus.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<!-- /Schreibfläche -->
|
||||
|
||||
<!-- ===================================================== -->
|
||||
<!-- RECHTS: SETTINGS-SIDEBAR -->
|
||||
<!-- ===================================================== -->
|
||||
<aside class="space-y-4 sticky-aside" style="align-self:start;">
|
||||
|
||||
<!-- ─── Status & Aktion (PRIMÄR) ─── -->
|
||||
<div class="meta-card" style="padding:16px 18px;border-color:#1A2540;background:linear-gradient(180deg,#FBFAF6 0%,#fff 60%);">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<span class="meta-title"><span class="num">01</span>Status & Absenden</span>
|
||||
<span class="badge muted dot">Entwurf</span>
|
||||
</div>
|
||||
|
||||
<!-- Checkliste -->
|
||||
<div class="bg-bg-elev border border-bg-rule rounded-[4px] p-3 mb-3">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="eyebrow muted" style="font-size:9.5px;letter-spacing:.16em;">Pre-Submit-Check</span>
|
||||
<span class="text-[10.5px] font-mono text-ok font-semibold">4 / 6 ok</span>
|
||||
</div>
|
||||
<div class="check-row ok">
|
||||
<span class="ic"><svg width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M2.5 6l2.5 2.5L9.5 3.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
|
||||
<span class="lbl">Titel vorhanden <span class="sub">62 Zeichen — gute Länge</span></span>
|
||||
</div>
|
||||
<div class="check-row ok">
|
||||
<span class="ic"><svg width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M2.5 6l2.5 2.5L9.5 3.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
|
||||
<span class="lbl">Mindestlänge Fließtext erreicht <span class="sub">1.420 / min. 600 Zeichen</span></span>
|
||||
</div>
|
||||
<div class="check-row ok">
|
||||
<span class="ic"><svg width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M2.5 6l2.5 2.5L9.5 3.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
|
||||
<span class="lbl">Firma zugeordnet <span class="sub">Tegernseer Brauerei AG</span></span>
|
||||
</div>
|
||||
<div class="check-row ok">
|
||||
<span class="ic"><svg width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M2.5 6l2.5 2.5L9.5 3.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg></span>
|
||||
<span class="lbl">Mindestens 1 Bild <span class="sub">2 Bilder · Titelbild gesetzt</span></span>
|
||||
</div>
|
||||
<div class="check-row warn">
|
||||
<span class="ic"><svg width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M6 3.5v3M6 8.5h0" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></span>
|
||||
<span class="lbl">Themen-Tags fehlen <span class="sub">empfohlen für SEO & Auffindbarkeit</span></span>
|
||||
</div>
|
||||
<div class="check-row warn">
|
||||
<span class="ic"><svg width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M6 3.5v3M6 8.5h0" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></span>
|
||||
<span class="lbl">Untertitel optional gesetzt — kein Pressekontakt-Telefon <span class="sub">Journalisten können dich nicht erreichen</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Primärer Submit -->
|
||||
<button class="btn-primary full">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M2 8l5 5 7-10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Zur Prüfung senden
|
||||
</button>
|
||||
<p class="text-[11px] text-ink-3 mt-2 leading-[1.45] m-0">
|
||||
Warnungen (<span class="text-warn font-semibold">⚠</span>) blockieren nicht. Pflichtfelder (<span class="text-err font-semibold">✗</span>) blockieren. Die Redaktion prüft typ. innerhalb von 24 h.
|
||||
</p>
|
||||
|
||||
<hr class="rule my-3" />
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="btn-secondary" style="flex:1;">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M2 8l4 4 8-8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Speichern
|
||||
</button>
|
||||
<button class="btn-secondary" style="flex:1;">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.3"/><path d="M5 8.5L7 10.5 11 6.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Vorschau
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── Portal-Auswahl ─── -->
|
||||
<div class="meta-card">
|
||||
<div class="meta-head">
|
||||
<span class="meta-title"><span class="num">02</span>Portal</span>
|
||||
<span class="text-[10.5px] text-ink-3"><strong class="font-mono text-ink-2">1</strong> ausgewählt</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="portal-opt is-checked">
|
||||
<span class="pcheck"></span>
|
||||
<span>
|
||||
<span class="ptitle"><span class="dot-pe"></span>presseecho</span>
|
||||
<span class="psub">Allgemeine Pressewelt · breite Reichweite, redaktionelle Prüfung 24 h</span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="portal-opt">
|
||||
<span class="pcheck"></span>
|
||||
<span>
|
||||
<span class="ptitle"><span class="dot-bp"></span>businessportal24</span>
|
||||
<span class="psub">B2B & Mittelstand · höhere Sichtbarkeit auf Business-Themen</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p class="text-[11px] text-ink-4 mt-3 mb-0 leading-[1.45] flex items-start gap-1.5">
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" class="mt-0.5 flex-shrink-0 text-ink-3"><circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.2"/><path d="M6 5v3M6 4v.4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
<span>Im MVP ein Portal pro PM. Cross-Publishing auf beide Portale folgt in Phase 2.</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ─── Pressekontakt ─── -->
|
||||
<div class="meta-card">
|
||||
<div class="meta-head">
|
||||
<span class="meta-title"><span class="num">03</span>Pressekontakt</span>
|
||||
<span class="flex items-center gap-0.5 bg-bg-elev border border-bg-rule rounded-[3px] p-0.5">
|
||||
<span class="tab-mini is-active">Aus Firma</span>
|
||||
<span class="tab-mini">Eigener</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div>
|
||||
<label class="block text-[10.5px] font-semibold text-ink-3 uppercase tracking-[.10em] mb-1">Name</label>
|
||||
<input class="inp small" value="Maria Schwarz" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label class="block text-[10.5px] font-semibold text-ink-3 uppercase tracking-[.10em] mb-1">Funktion</label>
|
||||
<input class="inp small" value="Leitung Kommunikation" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10.5px] font-semibold text-ink-3 uppercase tracking-[.10em] mb-1">Telefon</label>
|
||||
<input class="inp small" placeholder="+49 …" style="border-color:#E1C883;background:#FBF6EB;" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10.5px] font-semibold text-ink-3 uppercase tracking-[.10em] mb-1">E-Mail</label>
|
||||
<input class="inp small" value="presse@tegernseer-brauerei.de" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 mt-3 text-[11px] text-warn">
|
||||
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M6 1L11 11H1z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/><path d="M6 5v2.5M6 9v.2" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
Telefon empfohlen — Journalisten greifen oft direkt zum Hörer.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── Themen-Tags ─── -->
|
||||
<div class="meta-card">
|
||||
<div class="meta-head">
|
||||
<span class="meta-title"><span class="num">04</span>Themen-Tags</span>
|
||||
<span class="text-[10.5px] text-ink-4"><strong class="font-mono text-ink-2">0</strong> / 5</span>
|
||||
</div>
|
||||
|
||||
<div class="border border-bg-rule rounded-[4px] bg-white px-2 py-2 min-h-[58px] flex flex-wrap items-center gap-1.5">
|
||||
<span class="text-[11.5px] text-ink-4 italic px-1.5">Tippen, um Tag hinzuzufügen …</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-2.5">
|
||||
<div class="eyebrow muted mb-1.5" style="font-size:9.5px;">Vorschläge aus Firmenprofil</div>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button class="filter-tag">+ Mittelstand</button>
|
||||
<button class="filter-tag">+ Brauerei</button>
|
||||
<button class="filter-tag">+ Tegernsee</button>
|
||||
<button class="filter-tag">+ Tourismus</button>
|
||||
<button class="filter-tag">+ Eröffnung</button>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
.filter-tag{padding:3px 9px;background:#FBFAF6;border:1px dashed #CFD6E4;border-radius:3px;font-size:11.5px;color:#3A413D;font-weight:500;transition:border-color .12s,background .12s,color .12s;}
|
||||
.filter-tag:hover{border-style:solid;border-color:#1A2540;background:#E5E9F1;color:#1A2540;}
|
||||
</style>
|
||||
|
||||
<p class="text-[10.5px] text-ink-4 mt-2.5 mb-0 leading-[1.45]">
|
||||
Kategorie auf presseecho wird automatisch aus dem ersten Tag abgeleitet.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ─── Veröffentlichung ─── -->
|
||||
<div class="meta-card">
|
||||
<div class="meta-head">
|
||||
<span class="meta-title"><span class="num">05</span>Veröffentlichung</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-start gap-2.5 p-2 border border-hub rounded-[4px] bg-hub-soft cursor-pointer" style="box-shadow:inset 0 0 0 1px #1A2540;">
|
||||
<span class="w-3 h-3 rounded-full bg-hub flex-shrink-0 mt-0.5 flex items-center justify-center"><span class="w-1.5 h-1.5 rounded-full bg-white"></span></span>
|
||||
<span>
|
||||
<span class="text-[12.5px] font-semibold text-hub block leading-tight">Sofort nach Freigabe</span>
|
||||
<span class="text-[11px] text-ink-3 block mt-0.5 leading-tight">geht live, sobald die Redaktion grünes Licht gibt</span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="flex items-start gap-2.5 p-2 border border-bg-rule rounded-[4px] bg-bg-elev cursor-not-allowed" style="opacity:.75;">
|
||||
<span class="w-3 h-3 rounded-full border-2 border-ink-4 flex-shrink-0 mt-0.5"></span>
|
||||
<span class="flex-1">
|
||||
<span class="text-[12.5px] font-semibold text-ink-2 block leading-tight flex items-center gap-2">Geplanter Termin <span class="badge-bald">bald</span></span>
|
||||
<span class="text-[11px] text-ink-3 block mt-0.5 leading-tight">Datum + Uhrzeit, automatische Veröffentlichung</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<hr class="rule my-3" />
|
||||
<label class="flex items-center gap-2 text-[12px] text-ink-2 cursor-pointer">
|
||||
<input type="checkbox" class="rounded border-bg-rule" style="accent-color:#1A2540;" />
|
||||
<span>Sperrfrist setzen (Embargo) — frühestens 12. Juni 2026, 06:00 Uhr</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- ─── SEO (collapsed) ─── -->
|
||||
<div class="meta-card">
|
||||
<div class="collapse-head">
|
||||
<span class="meta-title"><span class="num">06</span>SEO</span>
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="text-[10.5px] text-ink-4">automatisch aus Titel</span>
|
||||
<svg class="chev" viewBox="0 0 16 16" fill="none"><path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ─── Phase 2 Footer ─── -->
|
||||
<div class="bg-accent-soft border border-[#E1C883] rounded-[5px] p-3.5">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" class="text-accent-deep"><path d="M8 2l1.5 4 4 1.5L9.5 9 8 13l-1.5-4L2.5 7.5 6.5 6z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
||||
<span class="eyebrow accent">Phase 2 — bald</span>
|
||||
</div>
|
||||
<ul class="text-[11.5px] text-accent-deep leading-[1.55] list-none p-0 m-0 space-y-1">
|
||||
<li>· KI-Titel-Optimierung & -Lektorat live</li>
|
||||
<li>· Geplante Veröffentlichung / Scheduling</li>
|
||||
<li>· Versionshistorie & Kommentare</li>
|
||||
<li>· Portal-Vorschau (presseecho vs. BP24)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ============== FUSSZEILE ============== -->
|
||||
<footer class="flex items-center justify-between pt-6 pb-2 mt-8 text-[11px] text-ink-3 border-t border-bg-rule">
|
||||
<span>© 2026 presseportale.com · Publisher-Hub</span>
|
||||
<span class="flex items-center gap-5">
|
||||
<a href="#" class="hover:text-hub">Tastenkürzel</a>
|
||||
<a href="#" class="hover:text-hub">Hilfe zum Editor</a>
|
||||
<a href="/cdn-cgi/l/email-protection#43303633332c313703333126303026332c3137222f266d202c2e" class="hover:text-hub">Support</a>
|
||||
</span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script></body>
|
||||
</html>
|
||||
1058
dev/frontend/tailwind_v3/User Pressemitteilungen presseportale.html
Normal file
1058
dev/frontend/tailwind_v3/User Pressemitteilungen presseportale.html
Normal file
File diff suppressed because it is too large
Load diff
144
public/fonts/inter-tight/font.css
Normal file
144
public/fonts/inter-tight/font.css
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
/* inter-tight-100 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('../fonts/inter-tight-v9-latin-100.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-100italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
src: url('../fonts/inter-tight-v9-latin-100italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-200 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url('../fonts/inter-tight-v9-latin-200.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-200italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
src: url('../fonts/inter-tight-v9-latin-200italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-300 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/inter-tight-v9-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-300italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/inter-tight-v9-latin-300italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-regular - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/inter-tight-v9-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/inter-tight-v9-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-500 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/inter-tight-v9-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-500italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/inter-tight-v9-latin-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-600 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/inter-tight-v9-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-600italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/inter-tight-v9-latin-600italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-700 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/inter-tight-v9-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-700italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/inter-tight-v9-latin-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-800 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url('../fonts/inter-tight-v9-latin-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-800italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: url('../fonts/inter-tight-v9-latin-800italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-900 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('../fonts/inter-tight-v9-latin-900.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* inter-tight-900italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Inter Tight';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
src: url('../fonts/inter-tight-v9-latin-900italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
BIN
public/fonts/inter-tight/inter-tight-v9-latin-100.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-100.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-100italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-100italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-200.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-200.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-200italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-200italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-300.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-300.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-300italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-300italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-500.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-500.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-500italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-500italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-600.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-600.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-600italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-600italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-700.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-700.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-700italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-700italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-800.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-800.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-800italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-800italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-900.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-900.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-900italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-900italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-italic.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-italic.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-tight/inter-tight-v9-latin-regular.woff2
Normal file
BIN
public/fonts/inter-tight/inter-tight-v9-latin-regular.woff2
Normal file
Binary file not shown.
128
public/fonts/jetbrains-mono/font.css
Normal file
128
public/fonts/jetbrains-mono/font.css
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/* jetbrains-mono-100 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 100;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-100.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-100italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: italic;
|
||||
font-weight: 100;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-100italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-200 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-200.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-200italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-200italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-300 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-300italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-300italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-regular - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-500 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-500italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-600 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-600italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-600italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-700 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-700italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-800 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* jetbrains-mono-800italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'JetBrains Mono';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: url('../fonts/jetbrains-mono-v24-latin-800italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-100.woff2
Normal file
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-100.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-200.woff2
Normal file
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-200.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-300.woff2
Normal file
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-300.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-500.woff2
Normal file
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-500.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-600.woff2
Normal file
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-600.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-700.woff2
Normal file
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-700.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-800.woff2
Normal file
BIN
public/fonts/jetbrains-mono/jetbrains-mono-v24-latin-800.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
128
public/fonts/source-serif-4/font.css
Normal file
128
public/fonts/source-serif-4/font.css
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/* source-serif-4-200 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: normal;
|
||||
font-weight: 200;
|
||||
src: url('../fonts/source-serif-4-v14-latin-200.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-200italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
src: url('../fonts/source-serif-4-v14-latin-200italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-300 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/source-serif-4-v14-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-300italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: italic;
|
||||
font-weight: 300;
|
||||
src: url('../fonts/source-serif-4-v14-latin-300italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-regular - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/source-serif-4-v14-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
src: url('../fonts/source-serif-4-v14-latin-italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-500 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/source-serif-4-v14-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-500italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: italic;
|
||||
font-weight: 500;
|
||||
src: url('../fonts/source-serif-4-v14-latin-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-600 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/source-serif-4-v14-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-600italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: italic;
|
||||
font-weight: 600;
|
||||
src: url('../fonts/source-serif-4-v14-latin-600italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-700 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/source-serif-4-v14-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-700italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
src: url('../fonts/source-serif-4-v14-latin-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-800 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
src: url('../fonts/source-serif-4-v14-latin-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-800italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: italic;
|
||||
font-weight: 800;
|
||||
src: url('../fonts/source-serif-4-v14-latin-800italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-900 - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: normal;
|
||||
font-weight: 900;
|
||||
src: url('../fonts/source-serif-4-v14-latin-900.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
/* source-serif-4-900italic - latin */
|
||||
@font-face {
|
||||
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
|
||||
font-family: 'Source Serif 4';
|
||||
font-style: italic;
|
||||
font-weight: 900;
|
||||
src: url('../fonts/source-serif-4-v14-latin-900italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
|
||||
}
|
||||
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-200.woff2
Normal file
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-200.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-300.woff2
Normal file
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-300.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-500.woff2
Normal file
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-500.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-600.woff2
Normal file
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-600.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-700.woff2
Normal file
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-700.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-800.woff2
Normal file
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-800.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-900.woff2
Normal file
BIN
public/fonts/source-serif-4/source-serif-4-v14-latin-900.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -141,6 +141,32 @@
|
|||
color: var(--color-hub);
|
||||
}
|
||||
|
||||
/* Dark Mode: --color-bg ist DUNKLER als die Sidebar (--color-bg-elev),
|
||||
ein Hover damit würde das Item „eindrücken" statt hervorheben. Im Dark
|
||||
Mode nutzen wir deshalb das dezente Hub-Soft (`#1f2a47`) — selbe
|
||||
Farbfamilie wie der Active-State, nur ohne Active-Strip. */
|
||||
.dark [data-flux-navlist-item]:hover {
|
||||
background: var(--color-hub-soft);
|
||||
color: var(--color-hub);
|
||||
}
|
||||
|
||||
/* Klick/Focus/Tap-Highlight: konsistent mit Hub-Soft (statt browser-
|
||||
default weißem Tap-Flash oder Flux's `bg-zinc-800/5`). Verhindert das
|
||||
weiße Aufblitzen beim wire:navigate-Klick im Dark Mode. */
|
||||
[data-flux-navlist-item]:active,
|
||||
[data-flux-navlist-item]:focus {
|
||||
background: var(--color-hub-soft);
|
||||
color: var(--color-hub);
|
||||
outline: none;
|
||||
}
|
||||
[data-flux-navlist-item] {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
[data-flux-navlist-item]:focus-visible {
|
||||
outline: 2px solid var(--color-hub);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[data-flux-navlist-item][data-current="true"],
|
||||
[data-flux-navlist-item][aria-current="page"],
|
||||
[data-flux-navlist-item].active {
|
||||
|
|
|
|||
|
|
@ -245,4 +245,9 @@
|
|||
|
||||
/* color-scheme-Hint für native Form-Controls (Scrollbars, Inputs) */
|
||||
color-scheme: dark;
|
||||
|
||||
/* Brand-Mark Wortmarke (z.B. „presse" bei pressekonto) im Portal-Dark
|
||||
auf weiß. Wird nur von `<x-web.brand-mark variant="auto">` ausgelesen;
|
||||
Hub-Frontend ist Light-Only, dort bleibt der Inline-Fallback aktiv. */
|
||||
--brand-mark-name-color: #ffffff;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -244,6 +244,10 @@
|
|||
background: var(--color-err-soft);
|
||||
color: var(--color-loss);
|
||||
}
|
||||
.badge.muted {
|
||||
background: var(--color-bg-rule-2);
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
.badge.dot::before {
|
||||
content: "";
|
||||
width: 6px;
|
||||
|
|
@ -337,4 +341,359 @@
|
|||
.eyebrow.on-dark {
|
||||
color: var(--color-hub-line);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Counter-Strip (Inline-Zähler unter H1)
|
||||
* ============================================================
|
||||
* Beispiel:
|
||||
* 24 Mitteilungen · 18 veröffentlicht · 1 in Prüfung · …
|
||||
*/
|
||||
.counter-strip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 6px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.counter-strip .seg {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
font-size: 12.5px;
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
.counter-strip .seg b {
|
||||
font-family: var(--font-mono);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
.counter-strip .seg.is-ok b {
|
||||
color: var(--color-gain-deep);
|
||||
}
|
||||
.counter-strip .seg.is-warn b {
|
||||
color: var(--color-accent-deep);
|
||||
}
|
||||
.counter-strip .seg.is-err b {
|
||||
color: var(--color-loss);
|
||||
}
|
||||
.counter-strip .seg.is-muted b {
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
.counter-strip .sep {
|
||||
width: 1px;
|
||||
height: 11px;
|
||||
background: var(--color-bg-rule);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* View-Tabs (Saved-Views — Tab-Leiste über Filter)
|
||||
* ============================================================ */
|
||||
.view-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-bottom: 1px solid var(--color-bg-rule);
|
||||
}
|
||||
.view-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 7px 12px 9px;
|
||||
font-size: 12.5px;
|
||||
font-weight: 500;
|
||||
color: var(--color-ink-3);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition:
|
||||
color 0.12s,
|
||||
border-color 0.12s;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border-top: 0;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
.view-tab:hover {
|
||||
color: var(--color-hub);
|
||||
}
|
||||
.view-tab.is-active {
|
||||
color: var(--color-hub);
|
||||
font-weight: 600;
|
||||
border-bottom-color: var(--color-hub);
|
||||
}
|
||||
.view-tab .cnt {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10.5px;
|
||||
color: var(--color-ink-3);
|
||||
background: var(--color-bg-rule-2);
|
||||
padding: 1px 6px;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.view-tab.is-active .cnt {
|
||||
background: var(--color-hub);
|
||||
color: var(--color-ink-on-dark);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Filter-Chips (Dropdown-Buttons mit Caret)
|
||||
* ============================================================ */
|
||||
.filter-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px 6px 14px;
|
||||
background: var(--color-bg-card);
|
||||
border: 1px solid var(--color-hub-soft-2);
|
||||
border-radius: 4px;
|
||||
font-size: 12.5px;
|
||||
color: var(--color-hub);
|
||||
font-weight: 500;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
background 0.15s;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
.filter-chip:hover {
|
||||
border-color: var(--color-hub);
|
||||
background: var(--color-bg-elev);
|
||||
}
|
||||
.filter-chip.is-active {
|
||||
background: var(--color-hub);
|
||||
color: var(--color-ink-on-dark);
|
||||
border-color: var(--color-hub);
|
||||
}
|
||||
.filter-chip.is-active:hover {
|
||||
background: var(--color-hub-2);
|
||||
}
|
||||
.filter-chip .caret {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Active-Chips (entfernbare Filter-Anzeige)
|
||||
* ============================================================ */
|
||||
.active-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 4px 6px 4px 11px;
|
||||
background: var(--color-bg-elev);
|
||||
border: 1px solid var(--color-bg-rule);
|
||||
border-radius: 999px;
|
||||
font-size: 11.5px;
|
||||
color: var(--color-ink-2);
|
||||
font-weight: 500;
|
||||
}
|
||||
.active-chip strong {
|
||||
color: var(--color-hub);
|
||||
font-weight: 600;
|
||||
}
|
||||
.active-chip .x {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 999px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-ink-3);
|
||||
background: var(--color-bg-rule-2);
|
||||
transition:
|
||||
background 0.12s,
|
||||
color 0.12s;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.active-chip .x:hover {
|
||||
background: var(--color-hub);
|
||||
color: var(--color-ink-on-dark);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Portal-Pills (presseecho / businessportal24 Indikator)
|
||||
* ============================================================ */
|
||||
.portal-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 10.5px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
background: var(--color-bg-elev);
|
||||
border: 1px solid var(--color-bg-rule);
|
||||
color: var(--color-ink-2);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.portal-pill .pdot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
}
|
||||
.portal-pill.pe .pdot {
|
||||
background: var(--color-bridge-presseecho);
|
||||
}
|
||||
.portal-pill.bp .pdot {
|
||||
background: var(--color-bridge-businessportal);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Inline-Action (kleine Aktion direkt neben Status-Badge)
|
||||
* ============================================================ */
|
||||
.inline-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: var(--color-hub);
|
||||
background: transparent;
|
||||
padding: 3px 6px;
|
||||
margin-left: 4px;
|
||||
border-radius: 3px;
|
||||
border: 1px dashed transparent;
|
||||
transition:
|
||||
border-color 0.12s,
|
||||
background 0.12s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.inline-action:hover {
|
||||
background: var(--color-hub-soft);
|
||||
border-color: var(--color-hub-soft-2);
|
||||
}
|
||||
.inline-action.warn {
|
||||
color: var(--color-accent-deep);
|
||||
}
|
||||
.inline-action.warn:hover {
|
||||
background: var(--color-warn-soft);
|
||||
border-color: color-mix(
|
||||
in oklab,
|
||||
var(--color-warn),
|
||||
transparent 60%
|
||||
);
|
||||
}
|
||||
.inline-action.err {
|
||||
color: var(--color-loss);
|
||||
}
|
||||
.inline-action.err:hover {
|
||||
background: var(--color-err-soft);
|
||||
border-color: color-mix(
|
||||
in oklab,
|
||||
var(--color-err),
|
||||
transparent 60%
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Row-Tinting (für Status-Hover-Hervorhebung in Listen)
|
||||
* ============================================================ */
|
||||
.is-row-warn:hover,
|
||||
tr.is-row-warn:hover td {
|
||||
background: color-mix(
|
||||
in oklab,
|
||||
var(--color-warn-soft),
|
||||
var(--color-bg-card) 50%
|
||||
) !important;
|
||||
}
|
||||
.is-row-err:hover,
|
||||
tr.is-row-err:hover td {
|
||||
background: color-mix(
|
||||
in oklab,
|
||||
var(--color-err-soft),
|
||||
var(--color-bg-card) 50%
|
||||
) !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* Empty-Stage (große Empty-States in Panels/Tabellen)
|
||||
* ============================================================ */
|
||||
.empty-stage {
|
||||
padding: 64px 24px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.empty-stage .empty-ico {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 6px;
|
||||
background: var(--color-hub-soft);
|
||||
border: 1px solid var(--color-hub-soft-2);
|
||||
color: var(--color-hub);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.empty-stage .empty-ico.warm {
|
||||
background: var(--color-accent-soft);
|
||||
border-color: color-mix(
|
||||
in oklab,
|
||||
var(--color-accent-warm),
|
||||
transparent 50%
|
||||
);
|
||||
color: var(--color-accent-deep);
|
||||
}
|
||||
.empty-stage .empty-ico.err {
|
||||
background: var(--color-err-soft);
|
||||
border-color: color-mix(
|
||||
in oklab,
|
||||
var(--color-err),
|
||||
transparent 50%
|
||||
);
|
||||
color: var(--color-loss);
|
||||
}
|
||||
.empty-stage .empty-title {
|
||||
font-size: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
margin: 0;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
.empty-stage .empty-sub {
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-3);
|
||||
line-height: 1.55;
|
||||
margin: 8px 0 0;
|
||||
max-width: 440px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* FluxUI-Tabellen im Hub-Panel-Kontext
|
||||
* ============================================================
|
||||
* FluxUI setzt per Default `first:ps-0 last:pe-0` auf alle
|
||||
* Tabellen-Zellen/-Spalten — die Tabelle „klebt" am Rand des
|
||||
* Containers. Im Hub-Stil wollen wir konsistente 18px
|
||||
* Edge-Padding (wie im Mockup `table.list`).
|
||||
*
|
||||
* MUSS in `@layer utilities` stehen (gleicher Layer wie
|
||||
* FluxUI's `first:ps-0`-Utility), sonst gewinnt FluxUI durch
|
||||
* CSS-Cascade-Layer-Ordering, unabhängig von Spezifität.
|
||||
*
|
||||
* Greift überall, wo eine FluxUI-Tabelle innerhalb eines
|
||||
* `.panel` oder `.panel-warm` sitzt. Tabellen in Modals,
|
||||
* Dropdowns oder anderen Kontexten bleiben unangetastet.
|
||||
*/
|
||||
@layer utilities {
|
||||
.panel [data-flux-table] [data-flux-column]:first-child,
|
||||
.panel [data-flux-table] [data-flux-cell]:first-child,
|
||||
.panel-warm [data-flux-table] [data-flux-column]:first-child,
|
||||
.panel-warm [data-flux-table] [data-flux-cell]:first-child {
|
||||
padding-inline-start: 18px;
|
||||
}
|
||||
.panel [data-flux-table] [data-flux-column]:last-child,
|
||||
.panel [data-flux-table] [data-flux-cell]:last-child,
|
||||
.panel-warm [data-flux-table] [data-flux-column]:last-child,
|
||||
.panel-warm [data-flux-table] [data-flux-cell]:last-child {
|
||||
padding-inline-end: 18px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,11 +79,25 @@
|
|||
</div>
|
||||
|
||||
@forelse ($recentPRs as $pr)
|
||||
@php
|
||||
$badgeClass = match ($pr->status->value) {
|
||||
'published' => 'ok',
|
||||
'review' => 'warn',
|
||||
'rejected' => 'err',
|
||||
'archived', 'draft' => 'muted',
|
||||
default => 'hub',
|
||||
};
|
||||
$portal = $pr->portal?->value ?? 'both';
|
||||
$showPe = in_array($portal, ['presseecho', 'both'], true);
|
||||
$showBp = in_array($portal, ['businessportal24', 'both'], true);
|
||||
@endphp
|
||||
<a href="{{ route('admin.press-releases.show', $pr->id) }}" wire:navigate
|
||||
class="flex items-center justify-between gap-3 px-5 py-3 border-b border-[color:var(--color-bg-rule)] last:border-b-0 hover:bg-[color:var(--color-bg)] transition-colors">
|
||||
class="flex items-center justify-between gap-4 px-5 py-3 border-b border-[color:var(--color-bg-rule)] last:border-b-0 hover:bg-[color:var(--color-bg)] transition-colors">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-[13px] font-medium text-[color:var(--color-ink)] m-0">{{ $pr->title }}</p>
|
||||
<p class="text-[11px] text-[color:var(--color-ink-3)] mt-0.5 m-0 truncate">
|
||||
PM-{{ $pr->id }}
|
||||
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
|
||||
{{ $pr->company?->name ?? '–' }}
|
||||
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
|
||||
{{ $pr->user?->name ?? '–' }}
|
||||
|
|
@ -91,15 +105,15 @@
|
|||
{{ $pr->created_at->format('d.m.Y') }}
|
||||
</p>
|
||||
</div>
|
||||
<span @class([
|
||||
'badge shrink-0',
|
||||
'ok' => $pr->status->value === 'published',
|
||||
'warn' => $pr->status->value === 'review',
|
||||
'err' => $pr->status->value === 'rejected',
|
||||
'hub' => in_array($pr->status->value, ['archived', 'draft'], true),
|
||||
])>
|
||||
{{ $pr->status->label() }}
|
||||
</span>
|
||||
<div class="flex items-center gap-1.5 shrink-0">
|
||||
@if ($showPe)
|
||||
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
|
||||
@endif
|
||||
@if ($showBp)
|
||||
<span class="portal-pill bp"><span class="pdot"></span>businessportal24</span>
|
||||
@endif
|
||||
<span class="badge {{ $badgeClass }} dot">{{ $pr->status->label() }}</span>
|
||||
</div>
|
||||
</a>
|
||||
@empty
|
||||
<p class="px-5 py-8 text-center text-[12.5px] text-[color:var(--color-ink-3)]">
|
||||
|
|
@ -122,17 +136,44 @@
|
|||
</div>
|
||||
|
||||
@forelse ($pendingReviews as $pr)
|
||||
@php
|
||||
$portal = $pr->portal?->value ?? 'both';
|
||||
$showPe = in_array($portal, ['presseecho', 'both'], true);
|
||||
$showBp = in_array($portal, ['businessportal24', 'both'], true);
|
||||
@endphp
|
||||
<div
|
||||
class="is-row-warn px-5 py-3 border-b border-[color:var(--color-bg-rule)] last:border-b-0 transition-colors">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<a href="{{ route('admin.press-releases.show', $pr->id) }}" wire:navigate
|
||||
class="block px-5 py-3 border-b border-[color:var(--color-bg-rule)] last:border-b-0 hover:bg-[color:var(--color-bg)] transition-colors">
|
||||
<p class="truncate text-[13px] font-medium text-[color:var(--color-ink)] m-0">{{ $pr->title }}</p>
|
||||
<p class="text-[11px] text-[color:var(--color-ink-3)] mt-0.5 m-0 truncate">
|
||||
{{ $pr->company?->name ?? '–' }}
|
||||
class="block min-w-0 flex-1 group">
|
||||
<p
|
||||
class="truncate text-[13px] font-medium text-[color:var(--color-ink)] m-0 group-hover:text-[color:var(--color-hub)] group-hover:underline underline-offset-[3px] decoration-[color:var(--color-hub)]/40">
|
||||
{{ $pr->title }}
|
||||
</p>
|
||||
</a>
|
||||
<a href="{{ route('admin.press-releases.show', $pr->id) }}" wire:navigate
|
||||
class="inline-action shrink-0" title="{{ __('Pressemitteilung prüfen') }}">
|
||||
{{ __('Prüfen →') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-3 mt-1.5">
|
||||
<p class="text-[11px] text-[color:var(--color-ink-3)] m-0 truncate min-w-0">
|
||||
PM-{{ $pr->id }}
|
||||
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
|
||||
{{ $pr->portal->label() }}
|
||||
{{ $pr->company?->name ?? '–' }}
|
||||
<span class="text-[color:var(--color-ink-4)] mx-1">·</span>
|
||||
{{ $pr->created_at->format('d.m.Y') }}
|
||||
</p>
|
||||
</a>
|
||||
<div class="flex items-center gap-1.5 shrink-0">
|
||||
@if ($showPe)
|
||||
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
|
||||
@endif
|
||||
@if ($showBp)
|
||||
<span class="portal-pill bp"><span class="pdot"></span>businessportal24</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="px-5 py-8 text-center text-[12.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Keine PMs in der Prüfwarteschlange.') }}
|
||||
|
|
|
|||
|
|
@ -1,124 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
<body class="min-h-screen bg-white dark:bg-zinc-800">
|
||||
<flux:header container class="border-b border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<flux:sidebar.toggle class="lg:hidden" icon="bars-2" inset="left" />
|
||||
|
||||
<a href="{{ route('dashboard') }}" class="ms-2 me-5 flex items-center space-x-2 rtl:space-x-reverse lg:ms-0" wire:navigate>
|
||||
<x-app-logo />
|
||||
</a>
|
||||
|
||||
<flux:navbar class="-mb-px max-lg:hidden">
|
||||
<flux:navbar.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Dashboard') }}
|
||||
</flux:navbar.item>
|
||||
</flux:navbar>
|
||||
|
||||
<flux:spacer />
|
||||
|
||||
<flux:navbar class="me-1.5 space-x-0.5 rtl:space-x-reverse py-0!">
|
||||
<flux:tooltip :content="__('Search')" position="bottom">
|
||||
<flux:navbar.item class="!h-10 [&>div>svg]:size-5" icon="magnifying-glass" href="#" :label="__('Search')" />
|
||||
</flux:tooltip>
|
||||
<flux:tooltip :content="__('Repository')" position="bottom">
|
||||
<flux:navbar.item
|
||||
class="h-10 max-lg:hidden [&>div>svg]:size-5"
|
||||
icon="folder-git-2"
|
||||
href="https://github.com/laravel/livewire-starter-kit"
|
||||
target="_blank"
|
||||
:label="__('Repository')"
|
||||
/>
|
||||
</flux:tooltip>
|
||||
<flux:tooltip :content="__('Documentation')" position="bottom">
|
||||
<flux:navbar.item
|
||||
class="h-10 max-lg:hidden [&>div>svg]:size-5"
|
||||
icon="book-open-text"
|
||||
href="https://laravel.com/docs/starter-kits#livewire"
|
||||
target="_blank"
|
||||
label="Documentation"
|
||||
/>
|
||||
</flux:tooltip>
|
||||
</flux:navbar>
|
||||
|
||||
<!-- Desktop User Menu -->
|
||||
<flux:dropdown position="top" align="end">
|
||||
<flux:profile
|
||||
class="cursor-pointer"
|
||||
:initials="auth()->user()->initials()"
|
||||
/>
|
||||
|
||||
<flux:menu>
|
||||
<flux:menu.radio.group>
|
||||
<div class="p-0 text-sm font-normal">
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-start text-sm">
|
||||
<span class="relative flex h-8 w-8 shrink-0 overflow-hidden rounded-lg">
|
||||
<span
|
||||
class="flex h-full w-full items-center justify-center rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white"
|
||||
>
|
||||
{{ auth()->user()->initials() }}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div class="grid flex-1 text-start text-sm leading-tight">
|
||||
<span class="truncate font-semibold">{{ auth()->user()->name }}</span>
|
||||
<span class="truncate text-xs">{{ auth()->user()->email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:menu.radio.group>
|
||||
|
||||
<flux:menu.separator />
|
||||
|
||||
<flux:menu.radio.group>
|
||||
<flux:menu.item :href="route('settings.profile')" icon="cog" wire:navigate>{{ __('Settings') }}</flux:menu.item>
|
||||
</flux:menu.radio.group>
|
||||
|
||||
<flux:menu.separator />
|
||||
|
||||
<form method="POST" action="{{ route('logout') }}" class="w-full">
|
||||
@csrf
|
||||
<flux:menu.item as="button" type="submit" icon="arrow-right-start-on-rectangle" class="w-full">
|
||||
{{ __('Log Out') }}
|
||||
</flux:menu.item>
|
||||
</form>
|
||||
</flux:menu>
|
||||
</flux:dropdown>
|
||||
</flux:header>
|
||||
|
||||
<!-- Mobile Menu -->
|
||||
<flux:sidebar stashable sticky class="lg:hidden border-e border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
|
||||
<flux:sidebar.toggle class="lg:hidden" icon="x-mark" />
|
||||
|
||||
<a href="{{ route('dashboard') }}" class="ms-1 flex items-center space-x-2 rtl:space-x-reverse" wire:navigate>
|
||||
<x-app-logo />
|
||||
</a>
|
||||
|
||||
<flux:navlist variant="outline">
|
||||
<flux:navlist.group :heading="__('Platform')">
|
||||
<flux:navlist.item icon="layout-grid" :href="route('dashboard')" :current="request()->routeIs('dashboard')" wire:navigate>
|
||||
{{ __('Dashboard') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist.group>
|
||||
</flux:navlist>
|
||||
|
||||
<flux:spacer />
|
||||
|
||||
<flux:navlist variant="outline">
|
||||
<flux:navlist.item icon="folder-git-2" href="https://github.com/laravel/livewire-starter-kit" target="_blank">
|
||||
{{ __('Repository') }}
|
||||
</flux:navlist.item>
|
||||
|
||||
<flux:navlist.item icon="book-open-text" href="https://laravel.com/docs/starter-kits#livewire" target="_blank">
|
||||
{{ __('Documentation') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist>
|
||||
</flux:sidebar>
|
||||
|
||||
{{ $slot }}
|
||||
|
||||
@fluxScripts
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
{{--
|
||||
Hub × FluxUI Phase 1 — Portal-Shell im Hub-Design.
|
||||
class="dark" wurde entfernt; Light Mode ist Default, Dark kommt mit
|
||||
FluxUI Appearance-Switcher in Phase 5.
|
||||
Hub × FluxUI Phase 5 — Portal-Shell im Hub-Design.
|
||||
Erscheinung (Light/Dark) wird über FluxUI Appearance-Switcher
|
||||
gesteuert. Server liest das `flux_appearance`-Cookie (gesetzt vom
|
||||
JS-Bridge in partials/head.blade.php) und rendert class="dark"
|
||||
direkt im <html>, damit es bei wire:navigate kein Theme-Flash gibt.
|
||||
Bei fehlendem Cookie (Erstbesuch) wird Light gerendert und das JS
|
||||
schaltet bei dunkler Präferenz nach Page-Load nach — der einmalige
|
||||
Flash beim allerersten Aufruf ist akzeptiert.
|
||||
--}}
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" @class(['dark' => request()->cookie('flux_appearance') === 'dark'])>
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
<x-layouts.auth.simple :title="$title ?? null">
|
||||
{{ $slot }}
|
||||
</x-layouts.auth.simple>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
<body class="min-h-screen bg-neutral-100 antialiased dark:bg-linear-to-b dark:from-neutral-950 dark:to-neutral-900">
|
||||
<div class="bg-muted flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||
<div class="flex w-full max-w-md flex-col gap-6">
|
||||
<a href="{{ route('home') }}" class="flex flex-col items-center gap-2 font-medium" wire:navigate>
|
||||
<span class="flex h-9 w-9 items-center justify-center rounded-md">
|
||||
<x-app-logo-icon class="size-9 fill-current text-black dark:text-white" />
|
||||
</span>
|
||||
|
||||
<span class="sr-only">{{ config('app.name', 'Laravel') }}</span>
|
||||
</a>
|
||||
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="rounded-xl border bg-white dark:bg-stone-950 dark:border-stone-800 text-stone-800 shadow-xs">
|
||||
<div class="px-10 py-8">{{ $slot }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@fluxScripts
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
<body class="min-h-screen bg-white antialiased dark:bg-linear-to-b dark:from-neutral-950 dark:to-neutral-900">
|
||||
<div class="bg-background flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||
<div class="flex w-full max-w-sm flex-col gap-2">
|
||||
<a href="{{ route('home') }}" class="flex flex-col items-center gap-2 font-medium" wire:navigate>
|
||||
<span class="flex h-9 w-9 mb-1 items-center justify-center rounded-md">
|
||||
<x-app-logo-icon class="size-9 fill-current text-black dark:text-white" />
|
||||
</span>
|
||||
<span class="sr-only">{{ config('app.name', 'Laravel') }}</span>
|
||||
</a>
|
||||
<div class="flex flex-col gap-6">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@livewireScripts
|
||||
@fluxScripts
|
||||
<script src="{{ asset('vendor/livewire/livewire.js') }}"></script>
|
||||
|
||||
<!-- Debug: Script-Status -->
|
||||
<script>
|
||||
console.log('Body Scripts geladen');
|
||||
console.log('Livewire JS:', {{ file_exists(public_path('vendor/livewire/livewire.js')) ? 'true' : 'false' }});
|
||||
if (typeof Livewire !== 'undefined') {
|
||||
console.log('Livewire verfügbar:', true);
|
||||
} else {
|
||||
console.log('Livewire verfügbar:', false);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||
<head>
|
||||
@include('partials.head')
|
||||
</head>
|
||||
<body class="min-h-screen bg-white antialiased dark:bg-linear-to-b dark:from-neutral-950 dark:to-neutral-900">
|
||||
<div class="relative grid h-dvh flex-col items-center justify-center px-8 sm:px-0 lg:max-w-none lg:grid-cols-2 lg:px-0">
|
||||
<div class="bg-muted relative hidden h-full flex-col p-10 text-white lg:flex dark:border-e dark:border-neutral-800">
|
||||
<div class="absolute inset-0 bg-neutral-900"></div>
|
||||
<a href="{{ route('home') }}" class="relative z-20 flex items-center text-lg font-medium" wire:navigate>
|
||||
<span class="flex h-10 w-10 items-center justify-center rounded-md">
|
||||
<x-app-logo-icon class="me-2 h-7 fill-current text-white" />
|
||||
</span>
|
||||
{{ config('app.name', 'Laravel') }}
|
||||
</a>
|
||||
|
||||
@php
|
||||
[$message, $author] = str(Illuminate\Foundation\Inspiring::quotes()->random())->explode('-');
|
||||
@endphp
|
||||
|
||||
<div class="relative z-20 mt-auto">
|
||||
<blockquote class="space-y-2">
|
||||
<flux:heading size="lg">“{{ trim($message) }}”</flux:heading>
|
||||
<footer><flux:heading>{{ trim($author) }}</flux:heading></footer>
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full lg:p-8">
|
||||
<div class="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
|
||||
<a href="{{ route('home') }}" class="z-20 flex flex-col items-center gap-2 font-medium lg:hidden" wire:navigate>
|
||||
<span class="flex h-9 w-9 items-center justify-center rounded-md">
|
||||
<x-app-logo-icon class="size-9 fill-current text-black dark:text-white" />
|
||||
</span>
|
||||
|
||||
<span class="sr-only">{{ config('app.name', 'Laravel') }}</span>
|
||||
</a>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@fluxScripts
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,20 +1,40 @@
|
|||
<div class="flex items-start max-md:flex-col">
|
||||
<div class="me-10 w-full pb-4 md:w-[220px]">
|
||||
<flux:navlist>
|
||||
<flux:navlist.item :href="route('settings.profile')" wire:navigate>{{ __('Profile') }}</flux:navlist.item>
|
||||
<flux:navlist.item :href="route('settings.password')" wire:navigate>{{ __('Password') }}</flux:navlist.item>
|
||||
<flux:navlist.item :href="route('settings.appearance')" wire:navigate>{{ __('Appearance') }}</flux:navlist.item>
|
||||
</flux:navlist>
|
||||
{{-- Hub-style settings layout: sidebar nav + content panel --}}
|
||||
<div class="grid items-start gap-6 md:grid-cols-[220px_1fr]">
|
||||
<aside class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Mein Konto') }}</span>
|
||||
</div>
|
||||
<nav class="p-2">
|
||||
<flux:navlist>
|
||||
<flux:navlist.item :href="route('settings.profile')" wire:navigate>
|
||||
{{ __('Profile') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item :href="route('settings.password')" wire:navigate>
|
||||
{{ __('Password') }}
|
||||
</flux:navlist.item>
|
||||
<flux:navlist.item :href="route('settings.appearance')" wire:navigate>
|
||||
{{ __('Appearance') }}
|
||||
</flux:navlist.item>
|
||||
</flux:navlist>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<flux:separator class="md:hidden" />
|
||||
<div class="space-y-6 min-w-0">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ $heading ?? '' }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-1">
|
||||
@if (! empty($subheading))
|
||||
<p class="text-[12.5px] text-[color:var(--color-ink-3)] m-0">
|
||||
{{ $subheading }}
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<div class="flex-1 self-stretch max-md:pt-6">
|
||||
<flux:heading>{{ $heading ?? '' }}</flux:heading>
|
||||
<flux:subheading>{{ $subheading ?? '' }}</flux:subheading>
|
||||
|
||||
<div class="mt-5 w-full max-w-lg">
|
||||
<div class="pt-4">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -79,8 +79,20 @@
|
|||
$fontClass = $serif ? 'font-serif' : 'font-sans';
|
||||
|
||||
$baseAttributes = $attributes->merge(['class' => $fontClass]);
|
||||
|
||||
/**
|
||||
* Variant=auto: Inline-Color über CSS-Custom-Property mit Light-Fallback.
|
||||
* Damit kann das Portal im Dark Mode `--brand-mark-name-color` global auf
|
||||
* weiß setzen, ohne dass das Hub-Frontend (Light-Only) tangiert wird —
|
||||
* dort ist die Variable nie definiert und der Fallback (Marken-Standardfarbe)
|
||||
* greift. Bei `on-dark` und `mono` bleibt der Inline-Style hart, weil der
|
||||
* Aufrufer dort explizit eine Farb-Intention setzt.
|
||||
*/
|
||||
$nameStyle = $variant === 'auto'
|
||||
? "color: var(--brand-mark-name-color, {$nameColor});"
|
||||
: "color: {$nameColor};";
|
||||
@endphp
|
||||
|
||||
<span {{ $baseAttributes }}><span
|
||||
style="color: {{ $nameColor }};">{{ $mark['name'] }}</span><span
|
||||
style="{{ $nameStyle }}">{{ $mark['name'] }}</span><span
|
||||
style="color: {{ $accentColor }};">{{ $mark['accent'] }}</span></span>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="dark">
|
||||
{{-- Phase 5: Anti-Flash. class="dark" nicht mehr hardcoded — Server liest
|
||||
das `flux_appearance`-Cookie (vom JS-Bridge in partials/admin-head.blade.php
|
||||
gesetzt) und rendert es direkt im <html>. --}}
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" @class(['dark' => request()->cookie('flux_appearance') === 'dark'])>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
|
|||
|
|
@ -106,27 +106,45 @@ new #[Layout('components.layouts.app'), Title('Kategorie anlegen')] class extend
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if(session('success'))
|
||||
<flux:callout color="green" icon="check-circle">{{ session('success') }}</flux:callout>
|
||||
@endif
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Kategorien') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Kategorie anlegen') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Neue Themen-Kategorie mit deutscher und englischer Übersetzung.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<flux:heading size="xl">{{ __('Kategorie anlegen') }}</flux:heading>
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" :href="route('admin.categories.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,320px]">
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('Deutsche Übersetzung') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Deutsche Übersetzung') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:input
|
||||
wire:model.live.debounce.400ms="nameDe"
|
||||
:label="__('Name (DE)')"
|
||||
|
|
@ -148,12 +166,13 @@ new #[Layout('components.layouts.app'), Title('Kategorie anlegen')] class extend
|
|||
/>
|
||||
<flux:error name="descriptionDe" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('English translation') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('English translation') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:input
|
||||
wire:model.live.debounce.400ms="nameEn"
|
||||
:label="__('Name (EN)')"
|
||||
|
|
@ -175,14 +194,15 @@ new #[Layout('components.layouts.app'), Title('Kategorie anlegen')] class extend
|
|||
/>
|
||||
<flux:error name="descriptionEn" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Einstellungen') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-6">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Einstellungen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Portal') }}</flux:label>
|
||||
<flux:select wire:model="portal">
|
||||
|
|
@ -208,7 +228,7 @@ new #[Layout('components.layouts.app'), Title('Kategorie anlegen')] class extend
|
|||
|
||||
<flux:checkbox wire:model="isActive" :label="__('Aktiv')" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:button type="submit" variant="primary" class="w-full" icon="check">
|
||||
{{ __('Speichern') }}
|
||||
|
|
|
|||
|
|
@ -172,33 +172,54 @@ new #[Layout('components.layouts.app'), Title('Kategorie bearbeiten')] class ext
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if(session('success'))
|
||||
<flux:callout color="green" icon="check-circle">{{ session('success') }}</flux:callout>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Kategorien') }}</span>
|
||||
<span class="badge hub">ID #{{ $id }}</span>
|
||||
<span class="badge hub">{{ $releaseCount }} {{ __('PMs') }}</span>
|
||||
@if ($childrenCount > 0)
|
||||
<span class="badge hub">{{ $childrenCount }} {{ __('Unterkategorien') }}</span>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<flux:callout color="red" icon="exclamation-triangle">{{ session('error') }}</flux:callout>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Kategorie bearbeiten') }}</flux:heading>
|
||||
<flux:subheading>ID: {{ $id }} · {{ $releaseCount }} {{ __('PMs') }} · {{ $childrenCount }} {{ __('Unterkategorien') }}</flux:subheading>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Kategorie bearbeiten') }}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" :href="route('admin.categories.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-start gap-3
|
||||
bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-ink-2)]">
|
||||
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0 mt-0.5 text-[color:var(--color-err)]" />
|
||||
<div class="flex-1">{{ session('error') }}</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<div class="grid gap-6 lg:grid-cols-[1fr,320px]">
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('Deutsche Übersetzung') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Deutsche Übersetzung') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:input wire:model="nameDe" :label="__('Name (DE)')" required />
|
||||
<flux:error name="nameDe" />
|
||||
|
||||
|
|
@ -208,12 +229,13 @@ new #[Layout('components.layouts.app'), Title('Kategorie bearbeiten')] class ext
|
|||
<flux:textarea wire:model="descriptionDe" :label="__('Beschreibung (DE)')" rows="3" />
|
||||
<flux:error name="descriptionDe" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="md" class="mb-4">{{ __('English translation') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('English translation') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:input wire:model="nameEn" :label="__('Name (EN)')" required />
|
||||
<flux:error name="nameEn" />
|
||||
|
||||
|
|
@ -223,14 +245,15 @@ new #[Layout('components.layouts.app'), Title('Kategorie bearbeiten')] class ext
|
|||
<flux:textarea wire:model="descriptionEn" :label="__('Description (EN)')" rows="3" />
|
||||
<flux:error name="descriptionEn" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Einstellungen') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-6">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Einstellungen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Portal') }}</flux:label>
|
||||
<flux:select wire:model="portal">
|
||||
|
|
@ -256,18 +279,28 @@ new #[Layout('components.layouts.app'), Title('Kategorie bearbeiten')] class ext
|
|||
|
||||
<flux:checkbox wire:model="isActive" :label="__('Aktiv')" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:button type="submit" variant="primary" class="w-full" icon="check">
|
||||
{{ __('Änderungen speichern') }}
|
||||
</flux:button>
|
||||
|
||||
<article class="panel" style="border-left:3px solid var(--color-err);">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow" style="color:var(--color-err);">{{ __('Danger Zone') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-3">
|
||||
<p class="text-[12.5px] text-[color:var(--color-ink-2)] m-0">
|
||||
{{ __('Kategorie unwiderruflich entfernen, sofern keine PMs/Unterkategorien hängen.') }}
|
||||
</p>
|
||||
<flux:modal.trigger name="confirm-delete-category">
|
||||
<flux:button type="button" variant="danger" icon="trash" class="w-full">
|
||||
{{ __('Kategorie löschen') }}
|
||||
</flux:button>
|
||||
</flux:modal.trigger>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
|
|||
|
|
@ -141,80 +141,76 @@ new #[Layout('components.layouts.app'), Title('Kategorien')] class extends Compo
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
{{-- Statistiken --}}
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Kategorien') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['total'] }}</flux:text>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Stammdaten') }}</span>
|
||||
</div>
|
||||
<flux:icon.folder class="size-8 text-blue-500" />
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Kategorien') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Themen-Taxonomie für Pressemitteilungen über alle Portale hinweg.') }}
|
||||
</p>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Mit PMs') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['with_releases'] }}</flux:text>
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="primary" icon="plus" :href="route('admin.categories.create')" wire:navigate>
|
||||
{{ __('Kategorie anlegen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
<flux:icon.document-text class="size-8 text-green-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('PMs gesamt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['total_releases'] }}</flux:text>
|
||||
</div>
|
||||
<flux:icon.newspaper class="size-8 text-purple-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<x-portal.stat-card variant="primary" :label="__('Kategorien')" :value="number_format($stats['total'])">
|
||||
<x-slot:meta>{{ __('gesamt') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('Themen-Taxonomie') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Mit PMs')" :value="number_format($stats['with_releases'])">
|
||||
<x-slot:meta>{{ __('aktiv genutzt') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('mind. eine PM') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('PMs gesamt')" :value="number_format($stats['total_releases'])">
|
||||
<x-slot:meta>{{ __('alle Portale') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('Content-Output') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Portale') }}</flux:text>
|
||||
<div class="mt-2 space-y-1 text-sm">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Portale') }}</span>
|
||||
</div>
|
||||
<dl class="p-5 space-y-2 text-[12.5px]">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span>{{ __('Presseecho') }}</span>
|
||||
<span class="font-semibold">{{ number_format($stats['presseecho_releases']) }}</span>
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Presseecho') }}</dt>
|
||||
<dd class="font-semibold text-[color:var(--color-ink)] tabular-nums">{{ number_format($stats['presseecho_releases']) }}</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span>{{ __('Businessportal24') }}</span>
|
||||
<span class="font-semibold">{{ number_format($stats['businessportal24_releases']) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Businessportal24') }}</dt>
|
||||
<dd class="font-semibold text-[color:var(--color-ink)] tabular-nums">{{ number_format($stats['businessportal24_releases']) }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
{{-- Filter + Aktion --}}
|
||||
<flux:card>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<div class="min-w-[260px] flex-1">
|
||||
{{-- ============== FILTER + SORT ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Sortierung') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
placeholder="{{ __('Kategorie suchen (Name, Slug)…') }}"
|
||||
icon="magnifying-glass"
|
||||
/>
|
||||
</div>
|
||||
<flux:button
|
||||
variant="primary"
|
||||
icon="plus"
|
||||
:href="route('admin.categories.create')"
|
||||
wire:navigate
|
||||
>
|
||||
{{ __('Kategorie anlegen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
{{-- Karten --}}
|
||||
{{-- Sortier-Buttons --}}
|
||||
<flux:card>
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span class="text-zinc-500">{{ __('Sortierung:') }}</span>
|
||||
<div class="flex items-center gap-2 text-[12px] flex-wrap">
|
||||
<span class="text-[10.5px] uppercase tracking-[0.6px] font-semibold text-[color:var(--color-ink-3)]">
|
||||
{{ __('Sortierung') }}
|
||||
</span>
|
||||
<flux:button size="sm" variant="{{ $sortBy === 'id' ? 'primary' : 'ghost' }}" wire:click="sort('id')">
|
||||
{{ __('Standard') }} @if ($sortBy === 'id') {{ $sortDir === 'asc' ? '↑' : '↓' }} @endif
|
||||
</flux:button>
|
||||
|
|
@ -222,27 +218,30 @@ new #[Layout('components.layouts.app'), Title('Kategorien')] class extends Compo
|
|||
{{ __('PMs') }} @if ($sortBy === 'press_releases_count') {{ $sortDir === 'asc' ? '↑' : '↓' }} @endif
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{{-- ============== KATEGORIE-KARTEN ============== --}}
|
||||
<section class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@forelse ($categories as $category)
|
||||
@php
|
||||
$de = $category->translations->firstWhere('locale', 'de');
|
||||
$en = $category->translations->firstWhere('locale', 'en');
|
||||
@endphp
|
||||
<flux:card class="group relative h-full transition hover:border-blue-300 hover:bg-blue-50/40 dark:hover:border-blue-700 dark:hover:bg-blue-950/20">
|
||||
<article class="panel group relative h-full transition hover:border-[color:var(--color-hub)]/30">
|
||||
<a href="{{ route('admin.press-releases.index', ['category' => $category->id]) }}" wire:navigate class="absolute inset-0 z-0" aria-hidden="true"></a>
|
||||
|
||||
<div class="relative z-10 space-y-4">
|
||||
<div class="flex items-start justify-between gap-2">
|
||||
<div class="min-w-0 flex-1">
|
||||
<flux:heading size="lg" class="truncate">{{ $de?->name ?? '–' }}</flux:heading>
|
||||
<flux:text class="truncate text-sm text-zinc-500">{{ $en?->name ?? '–' }}</flux:text>
|
||||
<div class="relative z-10 flex h-full flex-col">
|
||||
<div class="panel-head">
|
||||
<div class="min-w-0">
|
||||
<span class="section-eyebrow truncate">{{ __('Kategorie') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:badge color="{{ $category->is_active ? 'green' : 'zinc' }}" size="sm">
|
||||
{{ $category->is_active ? __('Aktiv') : __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
@if ($category->is_active)
|
||||
<span class="badge ok dot">{{ __('Aktiv') }}</span>
|
||||
@else
|
||||
<span class="badge dot">{{ __('Inaktiv') }}</span>
|
||||
@endif
|
||||
<flux:button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
|
|
@ -254,43 +253,58 @@ new #[Layout('components.layouts.app'), Title('Kategorien')] class extends Compo
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-5 space-y-4 flex-1">
|
||||
<div>
|
||||
<div class="text-[15px] font-semibold text-[color:var(--color-ink)] truncate">
|
||||
{{ $de?->name ?? '–' }}
|
||||
</div>
|
||||
<div class="text-[12px] text-[color:var(--color-ink-3)] truncate mt-0.5">
|
||||
{{ $en?->name ?? '–' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($de?->description)
|
||||
<flux:text class="line-clamp-2 text-sm text-zinc-600 dark:text-zinc-400">
|
||||
<p class="line-clamp-2 text-[12.5px] text-[color:var(--color-ink-2)] m-0">
|
||||
{{ $de->description }}
|
||||
</flux:text>
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 border-t border-zinc-200 pt-3 text-xs dark:border-zinc-700">
|
||||
<div class="rounded-md bg-zinc-50 px-2 py-1 dark:bg-zinc-800">
|
||||
<div class="text-zinc-500">{{ __('Presseecho') }}</div>
|
||||
<div class="font-semibold">{{ number_format($category->presseecho_press_releases_count) }}</div>
|
||||
<div class="grid grid-cols-2 gap-2 pt-3 border-t border-[color:var(--color-bg-rule)] text-[11.5px]">
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-2 py-1.5">
|
||||
<div class="text-[10px] uppercase tracking-[0.5px] font-semibold text-[color:var(--color-ink-3)]">{{ __('Presseecho') }}</div>
|
||||
<div class="font-semibold text-[color:var(--color-ink)] tabular-nums">{{ number_format($category->presseecho_press_releases_count) }}</div>
|
||||
</div>
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-2 py-1.5">
|
||||
<div class="text-[10px] uppercase tracking-[0.5px] font-semibold text-[color:var(--color-ink-3)]">{{ __('Businessportal24') }}</div>
|
||||
<div class="font-semibold text-[color:var(--color-ink)] tabular-nums">{{ number_format($category->businessportal24_press_releases_count) }}</div>
|
||||
</div>
|
||||
<div class="rounded-md bg-zinc-50 px-2 py-1 dark:bg-zinc-800">
|
||||
<div class="text-zinc-500">{{ __('Businessportal24') }}</div>
|
||||
<div class="font-semibold">{{ number_format($category->businessportal24_press_releases_count) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between border-t border-zinc-200 pt-3 dark:border-zinc-700">
|
||||
<div class="flex items-center gap-1.5 text-sm text-zinc-500">
|
||||
<div class="px-5 py-3 border-t border-[color:var(--color-bg-rule)] flex items-center justify-between">
|
||||
<div class="flex items-center gap-1.5 text-[12px] text-[color:var(--color-ink-3)]">
|
||||
<flux:icon.newspaper class="size-4" />
|
||||
{{ $category->press_releases_count }} {{ __('PMs') }}
|
||||
<span class="tabular-nums">{{ $category->press_releases_count }}</span>
|
||||
{{ __('PMs') }}
|
||||
</div>
|
||||
<flux:badge color="zinc" size="sm">/{{ $de?->slug ?? $category->id }}</flux:badge>
|
||||
<span class="badge hub">/{{ $de?->slug ?? $category->id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
@empty
|
||||
<div class="col-span-full">
|
||||
<flux:card>
|
||||
<div class="flex flex-col items-center justify-center py-12">
|
||||
<flux:icon.folder class="size-12 text-zinc-400 dark:text-zinc-600" />
|
||||
<flux:text class="mt-4 text-zinc-500">{{ __('Keine Kategorien gefunden.') }}</flux:text>
|
||||
<article class="panel col-span-full">
|
||||
<div class="p-10 flex flex-col items-center justify-center text-center">
|
||||
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-3
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||||
<flux:icon.folder class="size-6" />
|
||||
</div>
|
||||
</flux:card>
|
||||
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
|
||||
{{ __('Keine Kategorien gefunden.') }}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@endforelse
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{ $categories->links() }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -117,12 +117,35 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
{{-- Basisinformationen --}}
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Basisinformationen') }}</flux:heading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Stammdaten · Neue Firma') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Neue Firma') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Stammdaten, Adresse, Logo und Rechtsangaben einer neuen Firma erfassen.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Basisinformationen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Portal') }}</flux:label>
|
||||
|
|
@ -144,7 +167,7 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
</div>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firmenname') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Firmenname') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:input wire:model="company_name" placeholder="{{ __('Vollständiger Firmenname...') }}" />
|
||||
<flux:error name="company_name" />
|
||||
</flux:field>
|
||||
|
|
@ -157,7 +180,7 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('E-Mail') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('E-Mail') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:input wire:model="email" type="email" placeholder="{{ __('kontakt@firma.de') }}" icon="envelope" />
|
||||
<flux:error name="email" />
|
||||
</flux:field>
|
||||
|
|
@ -175,13 +198,13 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
<flux:error name="website" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Adresse --}}
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Adresse') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Adresse') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Straße & Hausnummer') }}</flux:label>
|
||||
<flux:input wire:model="street" placeholder="{{ __('Musterstraße 123') }}" />
|
||||
|
|
@ -210,7 +233,7 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Land') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Land') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:select wire:model="country">
|
||||
@foreach ($countries as $country)
|
||||
<option value="{{ $country['code'] }}">{{ $country['name'] }}</option>
|
||||
|
|
@ -220,13 +243,13 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
</flux:field>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Rechtliche Daten --}}
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Rechtliche Daten') }}</flux:heading>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Rechtliche Daten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Steuernummer / USt-IdNr.') }}</flux:label>
|
||||
<flux:input wire:model="tax_id" placeholder="{{ __('DE123456789') }}" />
|
||||
|
|
@ -239,13 +262,13 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
<flux:error name="registration_number" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Logo & Status --}}
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Logo & Status') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Logo & Status') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firmenlogo') }}</flux:label>
|
||||
<flux:input type="file" wire:model="logo" accept="image/*" />
|
||||
|
|
@ -254,22 +277,27 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
|
||||
@if ($logo)
|
||||
<div class="mt-4">
|
||||
<flux:text class="text-sm text-zinc-500 mb-2">{{ __('Vorschau:') }}</flux:text>
|
||||
<img src="{{ $logo->temporaryUrl() }}" width="128" height="128" class="h-32 max-h-32 w-32 max-w-32 rounded-lg border border-zinc-200 object-contain dark:border-zinc-700">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-2">
|
||||
{{ __('Vorschau:') }}
|
||||
</div>
|
||||
<img src="{{ $logo->temporaryUrl() }}" width="128" height="128"
|
||||
class="h-32 max-h-32 w-32 max-w-32 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)]">
|
||||
</div>
|
||||
@endif
|
||||
</flux:field>
|
||||
|
||||
<div class="flex gap-6">
|
||||
<div class="flex gap-6 pt-2 border-t border-[color:var(--color-bg-rule)]">
|
||||
<flux:checkbox wire:model="is_verified" label="{{ __('Firma ist verifiziert') }}" />
|
||||
<flux:checkbox wire:model="is_active" label="{{ __('Firma ist aktiv') }}" />
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Aktionen --}}
|
||||
<flux:card>
|
||||
<div class="flex justify-end gap-3">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
|
|
@ -277,5 +305,6 @@ new #[Layout('components.layouts.app'), Title('Neue Firma')] class extends Compo
|
|||
{{ __('Firma erstellen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -195,13 +195,36 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<form wire:submit="update" class="space-y-6">
|
||||
{{-- Basisinformationen --}}
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Basisinformationen') }}</flux:heading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Stammdaten · Firma bearbeiten') }}</span>
|
||||
<span class="badge hub">ID {{ $companyId }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Firma bearbeiten') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Stammdaten, Adresse, Logo und Rechtsangaben der Firma aktualisieren.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.companies.show', $companyId) }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<form wire:submit="update" class="space-y-6">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Basisinformationen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Portal') }}</flux:label>
|
||||
|
|
@ -223,7 +246,7 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
</div>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firmenname') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Firmenname') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:input wire:model="company_name" placeholder="{{ __('Vollständiger Firmenname...') }}" />
|
||||
<flux:error name="company_name" />
|
||||
</flux:field>
|
||||
|
|
@ -236,7 +259,7 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('E-Mail') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('E-Mail') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:input wire:model="email" type="email" placeholder="{{ __('kontakt@firma.de') }}" icon="envelope" />
|
||||
<flux:error name="email" />
|
||||
</flux:field>
|
||||
|
|
@ -254,13 +277,13 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
<flux:error name="website" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Adresse --}}
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Adresse') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Adresse') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Straße & Hausnummer') }}</flux:label>
|
||||
<flux:input wire:model="street" placeholder="{{ __('Musterstraße 123') }}" />
|
||||
|
|
@ -289,7 +312,7 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Land') }} <span class="text-red-500">*</span></flux:label>
|
||||
<flux:label>{{ __('Land') }} <span class="text-[color:var(--color-err)]">*</span></flux:label>
|
||||
<flux:select wire:model="country">
|
||||
@foreach ($countries as $country)
|
||||
<option value="{{ $country['code'] }}">{{ $country['name'] }}</option>
|
||||
|
|
@ -299,13 +322,13 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
</flux:field>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Rechtliche Daten --}}
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Rechtliche Daten') }}</flux:heading>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Rechtliche Daten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Steuernummer / USt-IdNr.') }}</flux:label>
|
||||
<flux:input wire:model="tax_id" placeholder="{{ __('DE123456789') }}" />
|
||||
|
|
@ -318,13 +341,13 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
<flux:error name="registration_number" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Logo & Status --}}
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Logo & Status') }}</flux:heading>
|
||||
|
||||
<div class="space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Logo & Status') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firmenlogo') }}</flux:label>
|
||||
<flux:input type="file" wire:model="logo" accept="image/jpeg,image/png,image/webp,image/gif" />
|
||||
|
|
@ -333,39 +356,51 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
|
||||
@if ($logo)
|
||||
<div class="mt-4">
|
||||
<flux:text class="text-sm text-zinc-500 mb-2">{{ __('Neues Logo (Vorschau):') }}</flux:text>
|
||||
<img src="{{ $logo->temporaryUrl() }}" width="128" height="128" class="h-32 max-h-32 w-32 max-w-32 rounded-lg border border-zinc-200 object-contain dark:border-zinc-700">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-2">
|
||||
{{ __('Neues Logo (Vorschau):') }}
|
||||
</div>
|
||||
<img src="{{ $logo->temporaryUrl() }}" width="128" height="128"
|
||||
class="h-32 max-h-32 w-32 max-w-32 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)]">
|
||||
</div>
|
||||
@elseif ($current_logo_url && ! $remove_logo)
|
||||
<div class="mt-4 flex items-center gap-3">
|
||||
<div class="mt-4 flex items-center gap-4">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 mb-2">{{ __('Aktuelles Logo:') }}</flux:text>
|
||||
<img src="{{ $current_logo_url }}" width="128" height="128" class="h-32 max-h-32 w-32 max-w-32 rounded-lg border border-zinc-200 object-contain dark:border-zinc-700">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-2">
|
||||
{{ __('Aktuelles Logo:') }}
|
||||
</div>
|
||||
<img src="{{ $current_logo_url }}" width="128" height="128"
|
||||
class="h-32 max-h-32 w-32 max-w-32 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)]">
|
||||
</div>
|
||||
<flux:button type="button" size="sm" variant="ghost" wire:click="$set('remove_logo', true)">
|
||||
{{ __('Logo entfernen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
@elseif ($remove_logo)
|
||||
<flux:callout color="amber" icon="exclamation-triangle" class="mt-4">
|
||||
<div class="mt-4 px-4 py-3 rounded-[5px] border text-[12.5px] flex items-start gap-3
|
||||
bg-[color:var(--color-warn-soft)] border-[color:var(--color-warn)]/30 text-[color:var(--color-accent-deep)]">
|
||||
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0 mt-0.5" />
|
||||
<div class="flex-1">
|
||||
{{ __('Logo wird beim Speichern entfernt.') }}
|
||||
</div>
|
||||
<flux:button type="button" size="sm" variant="ghost" wire:click="$set('remove_logo', false)">
|
||||
{{ __('Rückgängig') }}
|
||||
</flux:button>
|
||||
</flux:callout>
|
||||
</div>
|
||||
@endif
|
||||
</flux:field>
|
||||
|
||||
<div class="flex gap-6">
|
||||
<div class="flex gap-6 pt-2 border-t border-[color:var(--color-bg-rule)]">
|
||||
<flux:checkbox wire:model="is_verified" label="{{ __('Firma ist verifiziert') }}" />
|
||||
<flux:checkbox wire:model="is_active" label="{{ __('Firma ist aktiv') }}" />
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Aktionen --}}
|
||||
<flux:card>
|
||||
<div class="flex justify-between">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex justify-between items-center gap-3 flex-wrap">
|
||||
<flux:modal.trigger name="confirm-company-deletion">
|
||||
<flux:button
|
||||
variant="danger"
|
||||
|
|
@ -386,7 +421,7 @@ new #[Layout('components.layouts.app'), Title('Firma bearbeiten')] class extends
|
|||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</form>
|
||||
|
||||
<flux:modal name="confirm-company-deletion" class="max-w-lg">
|
||||
|
|
|
|||
|
|
@ -296,43 +296,51 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
{{-- Statistiken --}}
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Gesamt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['total'] }}</flux:text>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Stammdaten · Firmen') }}</span>
|
||||
</div>
|
||||
<flux:icon.building-office class="size-8 text-blue-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Aktiv') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['active'] }}</flux:text>
|
||||
</div>
|
||||
<flux:icon.check-circle class="size-8 text-green-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Inaktiv') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['inactive'] }}</flux:text>
|
||||
</div>
|
||||
<flux:icon.x-circle class="size-8 text-red-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
<h1 class="text-[34px] font-bold tracking-[-0.7px] leading-[1.1] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Firmen') }}
|
||||
</h1>
|
||||
<p class="text-[13.5px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Übersicht aller Firmen beider Portale, mit Filter, Datenqualität und Schnellaktionen.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{{-- Filter & Actions --}}
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center gap-3 flex-shrink-0">
|
||||
<flux:button icon="plus" variant="primary" href="{{ route('admin.companies.create') }}" wire:navigate>
|
||||
{{ __('Neue Firma') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="number_format($stats['total'])">
|
||||
<x-slot:meta>{{ __('alle Portale') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('Stammdaten-Basis') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Aktiv')" :value="number_format($stats['active'])">
|
||||
<x-slot:meta>{{ __('produktiv') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('für PMs nutzbar') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('Inaktiv')" :value="number_format($stats['inactive'])">
|
||||
<x-slot:meta>{{ __('pausiert') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('archiviert oder geblockt') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
{{-- ============== FILTER-PANEL ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex flex-col gap-4">
|
||||
<div class="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-7">
|
||||
<flux:input wire:model.live.debounce.300ms="search" placeholder="{{ __('Suchen...') }}"
|
||||
icon="magnifying-glass" class="xl:col-span-2" />
|
||||
|
|
@ -436,17 +444,17 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end">
|
||||
<flux:button icon="plus" href="{{ route('admin.companies.create') }}" wire:navigate>
|
||||
{{ __('Neue Firma') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
{{-- Tabelle --}}
|
||||
<flux:card class="overflow-hidden">
|
||||
{{-- ============== TABELLE-PANEL ============== --}}
|
||||
<article class="panel overflow-hidden">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Alle Firmen') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __(':count Einträge', ['count' => $companies->count()]) }}
|
||||
</span>
|
||||
</div>
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Aktionen') }}</flux:table.column>
|
||||
|
|
@ -482,39 +490,42 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
<flux:table.cell>
|
||||
<div class="flex items-center gap-3">
|
||||
@if ($logoUrl)
|
||||
<img src="{{ $logoUrl }}" width="36" height="36" class="h-9 max-h-9 w-9 max-w-9 rounded-md border border-zinc-200 object-contain dark:border-zinc-700" alt="{{ $company->name }}">
|
||||
<img src="{{ $logoUrl }}" width="36" height="36"
|
||||
class="h-9 max-h-9 w-9 max-w-9 rounded-[4px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)]"
|
||||
alt="{{ $company->name }}">
|
||||
@else
|
||||
<div class="flex h-9 w-9 items-center justify-center rounded-md border border-zinc-200 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<flux:icon.building-office class="size-5 text-zinc-400" />
|
||||
<div class="flex h-9 w-9 items-center justify-center rounded-[4px]
|
||||
border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)]">
|
||||
<flux:icon.building-office class="size-5 text-[color:var(--color-ink-3)]" />
|
||||
</div>
|
||||
@endif
|
||||
<flux:text weight="semibold"> {{ \Illuminate\Support\Str::limit($company->name, 60) }}
|
||||
</flux:text>
|
||||
<span class="text-[13px] font-semibold text-[color:var(--color-ink)]">
|
||||
{{ \Illuminate\Support\Str::limit($company->name, 60) }}
|
||||
</span>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
<div class="space-y-1">
|
||||
<flux:text class="text-sm">{{ $company->email ?: __('Keine E-Mail') }}</flux:text>
|
||||
<flux:text class="text-sm text-zinc-500">{{ $company->phone ?: __('Kein Telefon') }}
|
||||
</flux:text>
|
||||
<div class="space-y-0.5">
|
||||
<div class="text-[12.5px] text-[color:var(--color-ink)]">
|
||||
{{ $company->email ?: __('Keine E-Mail') }}
|
||||
</div>
|
||||
<div class="text-[12px] text-[color:var(--color-ink-3)]">
|
||||
{{ $company->phone ?: __('Kein Telefon') }}
|
||||
</div>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
@if ($company->is_active)
|
||||
<flux:badge color="green" size="sm" icon="check">{{ __('Aktiv') }}
|
||||
</flux:badge>
|
||||
<span class="badge ok dot">{{ __('Aktiv') }}</span>
|
||||
@else
|
||||
<flux:badge color="red" size="sm" icon="x-mark">{{ __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
<span class="badge err dot">{{ __('Inaktiv') }}</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
<flux:badge color="{{ $this->portalBadgeColor($company->portal) }}" size="sm">
|
||||
{{ $company->portal?->label() ?? __('Unbekannt') }}
|
||||
</flux:badge>
|
||||
<span class="badge hub">{{ $company->portal?->label() ?? __('Unbekannt') }}</span>
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
|
|
@ -528,7 +539,7 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
{{ $company->press_releases_count }} {{ __('PMs') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<flux:badge color="zinc" size="sm">0</flux:badge>
|
||||
<span class="badge hub">0</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
|
||||
|
|
@ -543,31 +554,39 @@ new #[Layout('components.layouts.app'), Title('Firmen')] class extends Component
|
|||
{{ $company->contacts_count }} {{ __('Kontakte') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<flux:badge color="zinc" size="sm">0</flux:badge>
|
||||
<span class="badge hub">0</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
<flux:text class="text-sm text-zinc-500">
|
||||
<span class="text-[12px] text-[color:var(--color-ink-3)]">
|
||||
{{ $company->created_at?->format('d.m.Y H:i') ?? '–' }}
|
||||
</flux:text>
|
||||
</span>
|
||||
</flux:table.cell>
|
||||
|
||||
</flux:table.row>
|
||||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="8">
|
||||
<div class="flex flex-col items-center justify-center py-12">
|
||||
<flux:icon.building-office class="size-12 text-zinc-400 dark:text-zinc-600" />
|
||||
<flux:text class="mt-4 text-zinc-500">{{ __('Keine Firmen gefunden') }}</flux:text>
|
||||
<div class="flex flex-col items-center justify-center py-12 text-center">
|
||||
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-4
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||||
<flux:icon.building-office class="size-6" />
|
||||
</div>
|
||||
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
|
||||
{{ __('Keine Firmen gefunden') }}
|
||||
</div>
|
||||
<p class="text-[12px] text-[color:var(--color-ink-3)] max-w-md m-0">
|
||||
{{ __('Passen Sie die Filter an oder legen Sie eine neue Firma an.') }}
|
||||
</p>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
<div class="border-t border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<div class="border-t border-[color:var(--color-bg-rule)] p-4">
|
||||
{{ $companies->links() }}
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -192,129 +192,198 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div class="flex gap-4">
|
||||
<div class="space-y-8">
|
||||
@if (session('success'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-loss)]">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
@if (session('info'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px]
|
||||
bg-[color:var(--color-hub-soft)] border-[color:var(--color-hub-soft-2)] text-[color:var(--color-ink-2)]">
|
||||
{{ session('info') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
@php($logoUrl = $company->logoUrl())
|
||||
|
||||
@if($logoUrl)
|
||||
<img src="{{ $logoUrl }}" width="80" height="80" class="h-20 max-h-20 w-20 max-w-20 rounded-lg border border-zinc-200 object-contain dark:border-zinc-700" alt="{{ $company->name }}">
|
||||
@else
|
||||
<div class="flex h-20 w-20 items-center justify-center rounded-lg border border-zinc-200 bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-800">
|
||||
<flux:icon.building-office class="size-10 text-zinc-400" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<flux:heading size="xl" class="mb-2">{{ $company->name }}</flux:heading>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Stammdaten · Firma') }}</span>
|
||||
@if ($company->is_active)
|
||||
<flux:badge color="green" size="sm">{{ __('Aktiv') }}</flux:badge>
|
||||
<span class="badge ok">{{ __('Aktiv') }}</span>
|
||||
@else
|
||||
<flux:badge color="red" size="sm">{{ __('Inaktiv') }}</flux:badge>
|
||||
<span class="badge err">{{ __('Inaktiv') }}</span>
|
||||
@endif
|
||||
<span class="badge hub">{{ $company->portal?->label() ?? __('Unbekannt') }}</span>
|
||||
<span class="badge hub">ID {{ $company->id }}</span>
|
||||
</div>
|
||||
<div class="flex items-start gap-4">
|
||||
@if ($logoUrl)
|
||||
<img src="{{ $logoUrl }}" width="64" height="64"
|
||||
class="h-16 max-h-16 w-16 max-w-16 rounded-[6px] border border-[color:var(--color-bg-rule)] object-contain bg-[color:var(--color-bg-elev)] flex-shrink-0"
|
||||
alt="{{ $company->name }}">
|
||||
@else
|
||||
<div class="flex h-16 w-16 items-center justify-center rounded-[6px]
|
||||
border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] flex-shrink-0">
|
||||
<flux:icon.building-office class="size-7 text-[color:var(--color-ink-3)]" />
|
||||
</div>
|
||||
@endif
|
||||
<div class="min-w-0">
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)] break-words">
|
||||
{{ $company->name }}
|
||||
</h1>
|
||||
@if ($company->website)
|
||||
<a href="{{ $company->website }}" target="_blank" rel="noopener"
|
||||
class="text-[12.5px] text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)] mt-1 inline-block">
|
||||
{{ $company->website }}
|
||||
</a>
|
||||
@endif
|
||||
<flux:badge color="{{ $this->portalBadgeColor($company->portal) }}" size="sm">{{ $company->portal?->label() ?? __('Unbekannt') }}</flux:badge>
|
||||
<flux:text class="ml-2 text-sm text-zinc-500">ID: {{ $company->id }}</flux:text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<flux:button icon="pencil" href="{{ route('admin.companies.edit', $company->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.companies.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.companies.contacts.create'))
|
||||
<flux:button variant="ghost" icon="user-plus" href="{{ route('admin.companies.contacts.create', ['companyId' => $company->id]) }}" wire:navigate>
|
||||
{{ __('Kontakt hinzufügen') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
<flux:button variant="primary" icon="pencil" href="{{ route('admin.companies.edit', $company->id) }}" wire:navigate>
|
||||
{{ __('Bearbeiten') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Pressemitteilungen') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $company->press_releases_count }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Kontakte') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $company->contacts_count }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Verknüpfte Benutzer') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $company->users_count }}</flux:text>
|
||||
</flux:card>
|
||||
</div>
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<x-portal.stat-card variant="primary" :label="__('Pressemitteilungen')" :value="number_format($company->press_releases_count)">
|
||||
<x-slot:meta>{{ __('insgesamt') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('Content-Output dieser Firma') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Kontakte')" :value="number_format($company->contacts_count)">
|
||||
<x-slot:meta>{{ __('Ansprechpartner') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('für PMs verfügbar') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('Verknüpfte Benutzer')" :value="number_format($company->users_count)">
|
||||
<x-slot:meta>{{ __('Owner & Co-Editors') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('Backend-Zugriff') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex gap-2">
|
||||
<flux:button
|
||||
:variant="$activeTab === 'overview' ? 'primary' : 'ghost'"
|
||||
wire:click="setTab('overview')"
|
||||
>
|
||||
{{-- ============== TABS ============== --}}
|
||||
<nav class="flex items-center gap-2 border-b border-[color:var(--color-bg-rule)]">
|
||||
<button type="button" wire:click="setTab('overview')"
|
||||
@class([
|
||||
'px-4 py-2.5 text-[12.5px] font-semibold border-b-2 transition-colors',
|
||||
'border-[color:var(--color-hub)] text-[color:var(--color-ink)]' => $activeTab === 'overview',
|
||||
'border-transparent text-[color:var(--color-ink-3)] hover:text-[color:var(--color-ink)]' => $activeTab !== 'overview',
|
||||
])>
|
||||
{{ __('Überblick') }}
|
||||
</flux:button>
|
||||
<flux:button
|
||||
:variant="$activeTab === 'contacts' ? 'primary' : 'ghost'"
|
||||
wire:click="setTab('contacts')"
|
||||
>
|
||||
</button>
|
||||
<button type="button" wire:click="setTab('contacts')"
|
||||
@class([
|
||||
'px-4 py-2.5 text-[12.5px] font-semibold border-b-2 transition-colors',
|
||||
'border-[color:var(--color-hub)] text-[color:var(--color-ink)]' => $activeTab === 'contacts',
|
||||
'border-transparent text-[color:var(--color-ink-3)] hover:text-[color:var(--color-ink)]' => $activeTab !== 'contacts',
|
||||
])>
|
||||
{{ __('Kontakte') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
@if ($activeTab === 'overview')
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Kontaktinformationen') }}</flux:heading>
|
||||
<div class="space-y-2">
|
||||
<flux:text>{{ $company->email ?: __('Keine E-Mail hinterlegt') }}</flux:text>
|
||||
<flux:text>{{ $company->phone ?: __('Kein Telefon hinterlegt') }}</flux:text>
|
||||
<flux:text>{{ $company->website ?: __('Keine Website hinterlegt') }}</flux:text>
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Kontaktinformationen') }}</span>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Adresse') }}</flux:heading>
|
||||
<div class="space-y-2">
|
||||
<flux:text>{{ $company->address ?: __('Keine Adresse hinterlegt') }}</flux:text>
|
||||
<flux:text>{{ $company->country_code ?: __('Kein Land hinterlegt') }}</flux:text>
|
||||
<dl class="p-5 space-y-2.5 text-[12.5px]">
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('E-Mail') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)] text-right break-all">{{ $company->email ?: __('Keine E-Mail hinterlegt') }}</dd>
|
||||
</div>
|
||||
</flux:card>
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Telefon') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)]">{{ $company->phone ?: __('Kein Telefon hinterlegt') }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Website') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)] text-right break-all">{{ $company->website ?: __('Keine Website hinterlegt') }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<flux:card class="lg:col-span-2">
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<flux:heading size="lg">{{ __('Aktuelle Pressemitteilungen') }}</flux:heading>
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Adresse') }}</span>
|
||||
</div>
|
||||
<dl class="p-5 space-y-2.5 text-[12.5px]">
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Anschrift') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)] text-right whitespace-pre-line">{{ $company->address ?: __('Keine Adresse hinterlegt') }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-[color:var(--color-ink-3)]">{{ __('Land') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)]">{{ $company->country_code ?: __('Kein Land hinterlegt') }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</article>
|
||||
|
||||
<article class="panel lg:col-span-2">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Aktuelle Pressemitteilungen') }}</span>
|
||||
<flux:button size="sm" variant="ghost" href="{{ route('admin.press-releases.index', ['company' => $company->id]) }}" wire:navigate>
|
||||
{{ __('Alle anzeigen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div class="divide-y divide-[color:var(--color-bg-rule)]">
|
||||
@forelse ($recentPressReleases as $pressRelease)
|
||||
<a href="{{ route('admin.press-releases.show', $pressRelease->id) }}" wire:navigate class="block rounded-lg p-3 transition-colors hover:bg-zinc-50 dark:hover:bg-zinc-900">
|
||||
<flux:text weight="medium">{{ $pressRelease->title ?? __('Ohne Titel') }}</flux:text>
|
||||
<flux:text class="text-sm text-zinc-500">{{ $pressRelease->created_at?->format('d.m.Y') ?? '-' }}</flux:text>
|
||||
<a href="{{ route('admin.press-releases.show', $pressRelease->id) }}" wire:navigate
|
||||
class="flex items-center justify-between gap-3 px-5 py-3 hover:bg-[color:var(--color-bg-elev)] transition-colors">
|
||||
<span class="text-[13px] font-semibold text-[color:var(--color-ink)] truncate">
|
||||
{{ $pressRelease->title ?? __('Ohne Titel') }}
|
||||
</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)] flex-shrink-0">
|
||||
{{ $pressRelease->created_at?->format('d.m.Y') ?? '-' }}
|
||||
</span>
|
||||
</a>
|
||||
@empty
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Keine Pressemitteilungen vorhanden') }}</flux:text>
|
||||
<div class="px-5 py-6 text-[12.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Keine Pressemitteilungen vorhanden') }}
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($activeTab === 'contacts')
|
||||
<flux:card>
|
||||
<div class="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<flux:heading size="lg">{{ __('Ansprechpartner') }} ({{ $filteredContactsTotal }})</flux:heading>
|
||||
<div class="flex w-full gap-2 sm:w-auto">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Ansprechpartner') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ $filteredContactsTotal }} {{ __('Einträge') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="contactSearch"
|
||||
placeholder="{{ __('Kontakte durchsuchen...') }}"
|
||||
icon="magnifying-glass"
|
||||
class="flex-1"
|
||||
/>
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.companies.contacts.create'))
|
||||
<flux:button size="sm" icon="plus" href="{{ route('admin.companies.contacts.create', ['companyId' => $company->id]) }}" wire:navigate>
|
||||
|
|
@ -322,10 +391,11 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 rounded-lg border border-zinc-200 p-3 dark:border-zinc-700">
|
||||
<flux:heading size="sm" class="mb-2">{{ __('Bestehenden Kontakt zuordnen') }}</flux:heading>
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-4">
|
||||
<div class="text-[11px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-2">
|
||||
{{ __('Bestehenden Kontakt zuordnen') }}
|
||||
</div>
|
||||
<flux:select
|
||||
wire:model.live="selectedExistingContactId"
|
||||
variant="combobox"
|
||||
|
|
@ -342,7 +412,7 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
@php($lookupName = trim(($lookupContact->first_name ?? '').' '.($lookupContact->last_name ?? '')) ?: __('Kontakt ohne Name'))
|
||||
<flux:select.option :value="$lookupContact->id" wire:key="lc-{{ $lookupContact->id }}">
|
||||
{{ $lookupName }}
|
||||
<span class="text-zinc-400">
|
||||
<span class="text-[color:var(--color-ink-3)]">
|
||||
@if ($lookupContact->email)
|
||||
· {{ $lookupContact->email }}
|
||||
@endif
|
||||
|
|
@ -362,22 +432,25 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
</flux:select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="space-y-2">
|
||||
@forelse ($filteredContacts as $contact)
|
||||
<div class="rounded-lg border border-zinc-200 p-3 dark:border-zinc-700">
|
||||
<div class="rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] p-3">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<flux:text weight="semibold">
|
||||
<span class="text-[13px] font-semibold text-[color:var(--color-ink)]">
|
||||
{{ trim(($contact->first_name ?? '').' '.($contact->last_name ?? '')) ?: __('Kontakt ohne Name') }}
|
||||
</flux:text>
|
||||
<flux:badge color="{{ $this->portalBadgeColor($contact->portal) }}" size="sm">
|
||||
{{ $contact->portal?->label() ?? __('Unbekannt') }}
|
||||
</flux:badge>
|
||||
</span>
|
||||
<span class="badge hub">{{ $contact->portal?->label() ?? __('Unbekannt') }}</span>
|
||||
</div>
|
||||
<div class="text-[12px] text-[color:var(--color-ink-3)] mt-0.5">
|
||||
{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}
|
||||
</div>
|
||||
<flux:text class="text-sm text-zinc-500">{{ $contact->responsibility ?: __('Keine Rolle hinterlegt') }}</flux:text>
|
||||
@if ($contact->email)
|
||||
<flux:text class="text-sm text-blue-600 dark:text-blue-400">{{ $contact->email }}</flux:text>
|
||||
<a href="mailto:{{ $contact->email }}"
|
||||
class="text-[12px] text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)] mt-0.5 inline-block">
|
||||
{{ $contact->email }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.edit'))
|
||||
|
|
@ -386,15 +459,18 @@ new #[Layout('components.layouts.app'), Title('Firma anzeigen')] class extends C
|
|||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Keine Kontakte gefunden') }}</flux:text>
|
||||
<div class="rounded-[5px] border border-dashed border-[color:var(--color-bg-rule)] p-4 text-[12.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __('Keine Kontakte gefunden') }}
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@if ($filteredContactsTotal > $filteredContacts->count())
|
||||
<flux:text class="mt-3 block text-xs text-zinc-500">
|
||||
<p class="text-[11.5px] text-[color:var(--color-ink-3)] m-0">
|
||||
{{ __('Es werden die ersten :count Kontakte angezeigt. Bitte Suche eingrenzen, um weitere Treffer zu finden.', ['count' => $filteredContacts->count()]) }}
|
||||
</flux:text>
|
||||
@endif
|
||||
</flux:card>
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -143,19 +143,37 @@ new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="lg">{{ __('Kontakt anlegen') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Kontakt einer Firma zuordnen und Stammdaten erfassen.') }}</flux:subheading>
|
||||
<form wire:submit="save" class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Pressekontakte') }}</span>
|
||||
@if ($isCompanyPrefilled && $companyId)
|
||||
<flux:text class="mt-2 text-sm text-zinc-500">
|
||||
{{ __('Firma wurde vorausgewählt. Du kannst sie bei Bedarf trotzdem ändern.') }}
|
||||
</flux:text>
|
||||
<span class="badge hub">{{ __('Firma vorausgewählt') }}</span>
|
||||
@endif
|
||||
</flux:card>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Kontakt anlegen') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Kontakt einer Firma zuordnen und Stammdaten erfassen.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<flux:card>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Zuordnung') }}</span>
|
||||
</div>
|
||||
<div class="p-5 grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firma') }}</flux:label>
|
||||
|
||||
|
|
@ -203,12 +221,13 @@ new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends
|
|||
<flux:error name="portal" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Kontaktdaten') }}</flux:heading>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Kontaktdaten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Anrede') }}</flux:label>
|
||||
<flux:select wire:model="salutationKey">
|
||||
|
|
@ -260,10 +279,10 @@ new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends
|
|||
<flux:error name="fax" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex justify-end gap-3">
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
|
|
@ -271,5 +290,5 @@ new #[Layout('components.layouts.app'), Title('Kontakt anlegen')] class extends
|
|||
{{ __('Kontakt anlegen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -178,22 +178,34 @@ new #[Layout('components.layouts.app'), Title('Kontakt bearbeiten')] class exten
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Kontakt bearbeiten') }}</flux:heading>
|
||||
<flux:subheading>ID: {{ $id }}</flux:subheading>
|
||||
<div class="space-y-8">
|
||||
<form wire:submit="save" class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Pressekontakte') }}</span>
|
||||
<span class="badge hub">ID #{{ $id }}</span>
|
||||
<span class="badge hub">{{ $this->currentPortalLabel() }}</span>
|
||||
</div>
|
||||
<flux:badge color="{{ $this->currentPortalBadgeColor() }}" size="sm">
|
||||
{{ $this->currentPortalLabel() }}
|
||||
</flux:badge>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Kontakt bearbeiten') }}
|
||||
</h1>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.contacts.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Zuordnung') }}</span>
|
||||
</div>
|
||||
<div class="p-5 grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Firma') }}</flux:label>
|
||||
|
||||
|
|
@ -241,12 +253,13 @@ new #[Layout('components.layouts.app'), Title('Kontakt bearbeiten')] class exten
|
|||
<flux:error name="portal" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="lg" class="mb-4">{{ __('Kontaktdaten') }}</flux:heading>
|
||||
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Kontaktdaten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 grid gap-4 sm:grid-cols-2">
|
||||
<flux:field>
|
||||
<flux:label>{{ __('Anrede') }}</flux:label>
|
||||
<flux:select wire:model="salutationKey">
|
||||
|
|
@ -298,10 +311,13 @@ new #[Layout('components.layouts.app'), Title('Kontakt bearbeiten')] class exten
|
|||
<flux:error name="fax" />
|
||||
</flux:field>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex justify-between">
|
||||
<article class="panel" style="border-left:3px solid var(--color-err);">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow" style="color:var(--color-err);">{{ __('Danger Zone & Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex justify-between flex-wrap gap-3">
|
||||
<flux:modal.trigger name="confirm-contact-deletion">
|
||||
<flux:button
|
||||
variant="danger"
|
||||
|
|
@ -322,7 +338,7 @@ new #[Layout('components.layouts.app'), Title('Kontakt bearbeiten')] class exten
|
|||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</form>
|
||||
|
||||
<flux:modal name="confirm-contact-deletion" class="max-w-lg">
|
||||
|
|
|
|||
|
|
@ -391,55 +391,73 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Pressekontakte') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Kontakte') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Pressekontakte aller Firmen über alle Portale hinweg.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.create'))
|
||||
<flux:button variant="primary" icon="plus" href="{{ route('admin.contacts.create') }}" wire:navigate>
|
||||
{{ __('Neuer Kontakt') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<flux:button variant="primary" icon="plus" disabled>
|
||||
{{ __('Neuer Kontakt') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@if ($notification)
|
||||
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 3000)" x-show="show" x-transition
|
||||
class="rounded-md px-4 py-3 text-sm border
|
||||
{{ $notificationType === 'error'
|
||||
? 'bg-red-50 dark:bg-red-900/20 border-red-200 dark:border-red-800 text-red-800 dark:text-red-300'
|
||||
: 'bg-green-50 dark:bg-green-900/20 border-green-200 dark:border-green-800 text-green-800 dark:text-green-300' }}">
|
||||
@class([
|
||||
'px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2',
|
||||
'bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-ink-2)]' => $notificationType === 'error',
|
||||
'bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]' => $notificationType !== 'error',
|
||||
])>
|
||||
@if ($notificationType === 'error')
|
||||
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0" />
|
||||
@else
|
||||
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
|
||||
@endif
|
||||
{{ $notification }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Statistiken --}}
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Gesamt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['total'] }}</flux:text>
|
||||
</div>
|
||||
<flux:icon.user-group class="size-8 text-blue-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="number_format($stats['total'])">
|
||||
<x-slot:meta>{{ __('Pressekontakte') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('alle Portale') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Firmen mit Kontakten')" :value="number_format($stats['companies_with_contacts'])">
|
||||
<x-slot:meta>{{ __('aktiv versorgt') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('mind. ein Kontakt') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('Ø pro Firma')" :value="number_format($stats['avg_per_company'], 1)">
|
||||
<x-slot:meta>{{ __('Pflegegrad') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('Kontakte / Firma') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Firmen mit Kontakten') }}
|
||||
</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['companies_with_contacts'] }}</flux:text>
|
||||
{{-- ============== FILTER-PANEL ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
|
||||
</div>
|
||||
<flux:icon.building-office class="size-8 text-green-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Ø pro Firma') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ number_format($stats['avg_per_company'], 1) }}
|
||||
</flux:text>
|
||||
</div>
|
||||
<flux:icon.chart-bar class="size-8 text-purple-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
|
||||
{{-- Filter & Actions --}}
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="p-5 flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
|
||||
<flux:input wire:model.live.debounce.300ms="search"
|
||||
placeholder="{{ __('Name, Email oder Firma suchen...') }}" icon="magnifying-glass" class="flex-1" />
|
||||
|
|
@ -527,31 +545,26 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
<option value="{{ $portalOption->value }}">{{ $portalOption->label() }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
@if (\Illuminate\Support\Facades\Route::has('admin.contacts.create'))
|
||||
<flux:button icon="plus" href="{{ route('admin.contacts.create') }}" wire:navigate>
|
||||
{{ __('Neuer Kontakt') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<flux:button icon="plus" disabled>
|
||||
{{ __('Neuer Kontakt') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
{{-- ============== PRESET-PANEL ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter-Presets') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-3">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex flex-1 gap-3">
|
||||
<flux:input wire:model="presetName" placeholder="{{ __('Neues Preset speichern...') }}"
|
||||
class="flex-1" />
|
||||
<flux:button wire:click="savePreset" variant="subtle" icon="bookmark">
|
||||
<flux:button wire:click="savePreset" variant="ghost" icon="bookmark">
|
||||
{{ __('Preset speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3">
|
||||
<div class="flex gap-2 flex-wrap">
|
||||
<flux:select wire:model="selectedPresetId" class="w-64">
|
||||
<option value="">{{ __('Preset auswählen') }}</option>
|
||||
@foreach ($presets as $preset)
|
||||
|
|
@ -565,11 +578,18 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
<flux:button wire:click="deletePreset" variant="danger">{{ __('Löschen') }}</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
<flux:error name="presetName" class="mt-3" />
|
||||
</flux:card>
|
||||
<flux:error name="presetName" />
|
||||
</div>
|
||||
</article>
|
||||
|
||||
{{-- Tabelle --}}
|
||||
<flux:card class="overflow-hidden">
|
||||
{{-- ============== TABELLE ============== --}}
|
||||
<article class="panel overflow-hidden">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Alle Kontakte') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __(':count Einträge', ['count' => $contacts->count()]) }}
|
||||
</span>
|
||||
</div>
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Aktionen') }}</flux:table.column>
|
||||
|
|
@ -614,43 +634,40 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
</div>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<div>
|
||||
<flux:text weight="semibold truncate">
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)] truncate">
|
||||
{{ $contactDisplayName }}
|
||||
</flux:text>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
<div class="space-y-1">
|
||||
<flux:text class="text-sm">
|
||||
<div class="space-y-0.5">
|
||||
<div class="text-[12.5px]">
|
||||
<a href="mailto:{{ $contact->email }}"
|
||||
class="text-blue-600 hover:underline dark:text-blue-400">
|
||||
class="text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
|
||||
{{ $contact->email ?: __('Keine E-Mail') }}
|
||||
</a>
|
||||
</flux:text>
|
||||
<flux:text class="text-sm text-zinc-500">{{ $contact->phone ?: __('Kein Telefon') }}
|
||||
</flux:text>
|
||||
</div>
|
||||
<div class="text-[12px] text-[color:var(--color-ink-3)]">
|
||||
{{ $contact->phone ?: __('Kein Telefon') }}
|
||||
</div>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
@if ($contact->company && \Illuminate\Support\Facades\Route::has('admin.companies.show'))
|
||||
<a href="{{ route('admin.companies.show', $contact->company_id) }}" wire:navigate
|
||||
class="text-blue-600 hover:underline dark:text-blue-400">
|
||||
class="text-[12.5px] text-[color:var(--color-hub)] underline underline-offset-2 decoration-[color:var(--color-hub)]/40 hover:decoration-[color:var(--color-hub)]">
|
||||
{{ \Illuminate\Support\Str::limit($contact->company->name, 60) }}
|
||||
</a>
|
||||
@else
|
||||
<flux:text>
|
||||
<span class="text-[12.5px] text-[color:var(--color-ink)]">
|
||||
{{ \Illuminate\Support\Str::limit($contact->company?->name ?? __('Unbekannte Firma'), 80) }}
|
||||
</flux:text>
|
||||
</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
<flux:badge color="{{ $this->portalBadgeColor($contact->portal) }}" size="sm">
|
||||
{{ $contact->portal?->label() ?? __('Unbekannt') }}
|
||||
</flux:badge>
|
||||
<span class="badge hub">{{ $contact->portal?->label() ?? __('Unbekannt') }}</span>
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
|
|
@ -664,14 +681,14 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
{{ $contact->press_releases_count }} {{ __('PMs') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<flux:badge color="zinc" size="sm">0</flux:badge>
|
||||
<span class="badge dot">0</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
<flux:text class="text-sm text-zinc-500">
|
||||
<span class="text-[12px] text-[color:var(--color-ink-3)]">
|
||||
{{ $contact->created_at?->format('d.m.Y H:i') ?? '-' }}
|
||||
</flux:text>
|
||||
</span>
|
||||
</flux:table.cell>
|
||||
|
||||
<flux:table.cell>
|
||||
|
|
@ -712,9 +729,14 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="8">
|
||||
<div class="flex flex-col items-center justify-center py-12">
|
||||
<flux:icon.user-group class="size-12 text-zinc-400 dark:text-zinc-600" />
|
||||
<flux:text class="mt-4 text-zinc-500">{{ __('Keine Kontakte gefunden') }}</flux:text>
|
||||
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
|
||||
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-3
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||||
<flux:icon.user-group class="size-6" />
|
||||
</div>
|
||||
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
|
||||
{{ __('Keine Kontakte gefunden') }}
|
||||
</div>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
|
|
@ -722,8 +744,8 @@ new #[Layout('components.layouts.app'), Title('Kontakte')] class extends Compone
|
|||
</flux:table.rows>
|
||||
</flux:table>
|
||||
|
||||
<div class="border-t border-zinc-200 p-4 dark:border-zinc-700">
|
||||
<div class="border-t border-[color:var(--color-bg-rule)] p-4">
|
||||
{{ $contacts->links() }}
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,32 +12,37 @@ new #[Layout('components.layouts.app'), Title('Gutscheine')] class extends Compo
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="space-y-2">
|
||||
<flux:heading size="lg">{{ __('Gutscheine') }}</flux:heading>
|
||||
<flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Finanzen') }}</span>
|
||||
<span class="badge warn">{{ __('Vertagt') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Gutscheine') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[720px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Coupons sind in der Initialmigration vertagt (Entscheidung D-16). Eine Wiedereinführung wird später separat evaluiert – ggf. direkt über Stripe-Coupons.') }}
|
||||
</flux:subheading>
|
||||
</p>
|
||||
</div>
|
||||
<flux:badge color="zinc" icon="pause" size="lg">
|
||||
{{ __('Vertagt') }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Hinweise') }}</flux:heading>
|
||||
<ul class="space-y-2 text-sm text-zinc-600 dark:text-zinc-300">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Hinweise') }}</span>
|
||||
</div>
|
||||
<ul class="p-5 space-y-3 text-[12.5px] text-[color:var(--color-ink-2)] list-none m-0">
|
||||
<li class="flex gap-2">
|
||||
<flux:icon.information-circle class="size-5 shrink-0 text-zinc-400" />
|
||||
<flux:icon.information-circle class="size-[16px] shrink-0 mt-0.5 text-[color:var(--color-hub)]" />
|
||||
<span>{{ __('Im neuen Stack sind keine eigenen Coupon-Tabellen vorgesehen. Sobald wieder benötigt, werden Coupons als Stripe-Coupons abgebildet.') }}</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<flux:icon.information-circle class="size-5 shrink-0 text-zinc-400" />
|
||||
<flux:icon.information-circle class="size-[16px] shrink-0 mt-0.5 text-[color:var(--color-hub)]" />
|
||||
<span>{{ __('Bestehende Legacy-Gutscheine werden nicht migriert – Bestandskunden behalten ihre Konditionen über das Grandfathering-Modell (P8.8).') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -75,31 +75,35 @@ new #[Layout('components.layouts.app'), Title('Footer-Code anlegen')] class exte
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Footer-Code anlegen') }}</flux:heading>
|
||||
<flux:subheading>
|
||||
{{ __('Snippet, das unter Pressemitteilungen ausgespielt wird.') }}
|
||||
</flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Footer-Codes') }}</span>
|
||||
</div>
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
icon="arrow-left"
|
||||
:href="route('admin.footer-codes.index')"
|
||||
wire:navigate
|
||||
>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Footer-Code anlegen') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Snippet, das unter Pressemitteilungen ausgespielt wird.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="lg">{{ __('Stammdaten') }}</flux:heading>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Stammdaten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:input
|
||||
wire:model="title"
|
||||
:label="__('Titel')"
|
||||
|
|
@ -136,12 +140,13 @@ new #[Layout('components.layouts.app'), Title('Footer-Code anlegen')] class exte
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="lg">{{ __('Sichtbarkeit') }}</flux:heading>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Sichtbarkeit') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:switch
|
||||
wire:model.live="isGlobal"
|
||||
:label="__('Global ausspielen')"
|
||||
|
|
@ -154,48 +159,48 @@ new #[Layout('components.layouts.app'), Title('Footer-Code anlegen')] class exte
|
|||
:description="__('Inaktive Codes werden niemals ausgespielt.')"
|
||||
/>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
@if (! $isGlobal)
|
||||
<flux:card>
|
||||
<flux:heading size="lg">{{ __('Kategorie-Zuordnung') }}</flux:heading>
|
||||
<flux:subheading>
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Kategorie-Zuordnung') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<p class="text-[12.5px] text-[color:var(--color-ink-3)] m-0">
|
||||
{{ __('Nur Pressemitteilungen in diesen Kategorien zeigen den Footer-Code.') }}
|
||||
</flux:subheading>
|
||||
</p>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div class="grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
|
||||
@forelse ($categoryOptions as $option)
|
||||
<label class="flex items-center gap-2 rounded border border-zinc-200 px-3 py-2 dark:border-zinc-700">
|
||||
<label class="flex items-center gap-2 rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-3 py-2 hover:border-[color:var(--color-hub)]/30 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
wire:model="categoryIds"
|
||||
value="{{ $option['id'] }}"
|
||||
class="rounded border-zinc-300"
|
||||
class="rounded border-[color:var(--color-bg-rule)]"
|
||||
/>
|
||||
<span class="text-sm">{{ $option['name'] }}</span>
|
||||
<span class="text-[12.5px] text-[color:var(--color-ink)]">{{ $option['name'] }}</span>
|
||||
</label>
|
||||
@empty
|
||||
<flux:text class="text-zinc-500">
|
||||
<p class="text-[12.5px] text-[color:var(--color-ink-3)] m-0">
|
||||
{{ __('Keine Kategorien vorhanden.') }}
|
||||
</flux:text>
|
||||
</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
:href="route('admin.footer-codes.index')"
|
||||
wire:navigate
|
||||
>
|
||||
<article class="panel">
|
||||
<div class="p-5 flex items-center justify-end gap-2">
|
||||
<flux:button variant="ghost" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary" icon="check">
|
||||
{{ __('Speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -106,29 +106,42 @@ new #[Layout('components.layouts.app'), Title('Footer-Code bearbeiten')] class e
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Footer-Code bearbeiten') }}</flux:heading>
|
||||
<flux:subheading>#{{ $id }} – {{ $title }}</flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Footer-Codes') }}</span>
|
||||
<span class="badge hub">ID #{{ $id }}</span>
|
||||
@if ($isGlobal)
|
||||
<span class="badge hub dot">{{ __('Global') }}</span>
|
||||
@endif
|
||||
@if ($isActive)
|
||||
<span class="badge ok dot">{{ __('Aktiv') }}</span>
|
||||
@else
|
||||
<span class="badge dot">{{ __('Inaktiv') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
icon="arrow-left"
|
||||
:href="route('admin.footer-codes.index')"
|
||||
wire:navigate
|
||||
>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)] break-words">
|
||||
{{ __('Footer-Code bearbeiten') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">{{ $title }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<flux:card>
|
||||
<flux:heading size="lg">{{ __('Stammdaten') }}</flux:heading>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Stammdaten') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:input wire:model="title" :label="__('Titel')" />
|
||||
|
||||
<flux:textarea
|
||||
|
|
@ -159,12 +172,13 @@ new #[Layout('components.layouts.app'), Title('Footer-Code bearbeiten')] class e
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="lg">{{ __('Sichtbarkeit') }}</flux:heading>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Sichtbarkeit') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<flux:switch
|
||||
wire:model.live="isGlobal"
|
||||
:label="__('Global ausspielen')"
|
||||
|
|
@ -176,34 +190,38 @@ new #[Layout('components.layouts.app'), Title('Footer-Code bearbeiten')] class e
|
|||
:label="__('Aktiv')"
|
||||
/>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
@if (! $isGlobal)
|
||||
<flux:card>
|
||||
<flux:heading size="lg">{{ __('Kategorie-Zuordnung') }}</flux:heading>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Kategorie-Zuordnung') }}</span>
|
||||
</div>
|
||||
<div class="p-5 grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
|
||||
@forelse ($categoryOptions as $option)
|
||||
<label class="flex items-center gap-2 rounded border border-zinc-200 px-3 py-2 dark:border-zinc-700">
|
||||
<label class="flex items-center gap-2 rounded-[5px] border border-[color:var(--color-bg-rule)] bg-[color:var(--color-bg-elev)] px-3 py-2 hover:border-[color:var(--color-hub)]/30 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
wire:model="categoryIds"
|
||||
value="{{ $option['id'] }}"
|
||||
class="rounded border-zinc-300"
|
||||
class="rounded border-[color:var(--color-bg-rule)]"
|
||||
/>
|
||||
<span class="text-sm">{{ $option['name'] }}</span>
|
||||
<span class="text-[12.5px] text-[color:var(--color-ink)]">{{ $option['name'] }}</span>
|
||||
</label>
|
||||
@empty
|
||||
<flux:text class="text-zinc-500">
|
||||
<p class="text-[12.5px] text-[color:var(--color-ink-3)] m-0">
|
||||
{{ __('Keine Kategorien vorhanden.') }}
|
||||
</flux:text>
|
||||
</p>
|
||||
@endforelse
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<article class="panel" style="border-left:3px solid var(--color-err);">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow" style="color:var(--color-err);">{{ __('Danger Zone & Aktionen') }}</span>
|
||||
</div>
|
||||
<div class="p-5 flex items-center justify-between gap-2 flex-wrap">
|
||||
<flux:button
|
||||
type="button"
|
||||
variant="danger"
|
||||
|
|
@ -215,11 +233,7 @@ new #[Layout('components.layouts.app'), Title('Footer-Code bearbeiten')] class e
|
|||
</flux:button>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<flux:button
|
||||
variant="ghost"
|
||||
:href="route('admin.footer-codes.index')"
|
||||
wire:navigate
|
||||
>
|
||||
<flux:button variant="ghost" :href="route('admin.footer-codes.index')" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
<flux:button type="submit" variant="primary" icon="check">
|
||||
|
|
@ -227,6 +241,6 @@ new #[Layout('components.layouts.app'), Title('Footer-Code bearbeiten')] class e
|
|||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -76,80 +76,77 @@ new #[Layout('components.layouts.app'), Title('Footer-Codes')] class extends Com
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if(session('success'))
|
||||
<flux:callout color="green" icon="check-circle">{{ session('success') }}</flux:callout>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<flux:callout color="red" icon="exclamation-triangle">{{ session('error') }}</flux:callout>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<flux:heading size="xl">{{ __('Footer-Codes') }}</flux:heading>
|
||||
<flux:subheading>
|
||||
{{ __('Snippets, die unter Pressemitteilungen ausgespielt werden.') }}
|
||||
</flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Operations') }}</span>
|
||||
</div>
|
||||
<flux:button
|
||||
variant="primary"
|
||||
icon="plus"
|
||||
:href="route('admin.footer-codes.create')"
|
||||
wire:navigate
|
||||
>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Footer-Codes') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Snippets, die unter Pressemitteilungen ausgespielt werden.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="primary" icon="plus" :href="route('admin.footer-codes.create')" wire:navigate>
|
||||
{{ __('Footer-Code anlegen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Gesamt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $totals['total'] }}</flux:text>
|
||||
@if (session('success'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
<flux:icon.code-bracket-square class="size-8 text-blue-500" />
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-start gap-3
|
||||
bg-[color:var(--color-err-soft)] border-[color:var(--color-err)]/30 text-[color:var(--color-ink-2)]">
|
||||
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0 mt-0.5 text-[color:var(--color-err)]" />
|
||||
<div class="flex-1">{{ session('error') }}</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Aktiv') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $totals['active'] }}</flux:text>
|
||||
</div>
|
||||
<flux:icon.bolt class="size-8 text-green-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="number_format($totals['total'])">
|
||||
<x-slot:meta>{{ __('Snippets') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('alle Portale') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Aktiv')" :value="number_format($totals['active'])">
|
||||
<x-slot:meta>{{ __('live ausgespielt') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('in PMs sichtbar') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('Global')" :value="number_format($totals['global'])">
|
||||
<x-slot:meta>{{ __('portalübergreifend') }}</x-slot:meta>
|
||||
<x-slot:trend>{{ __('ohne Kategorie-Bindung') }}</x-slot:trend>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<flux:text class="text-sm text-zinc-500">{{ __('Global') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $totals['global'] }}</flux:text>
|
||||
{{-- ============== FILTER ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
|
||||
</div>
|
||||
<flux:icon.globe-alt class="size-8 text-purple-500" />
|
||||
</div>
|
||||
</flux:card>
|
||||
</div>
|
||||
|
||||
<flux:card>
|
||||
<div class="grid gap-3 md:grid-cols-[1fr,auto,auto]">
|
||||
<div class="p-5 grid gap-3 md:grid-cols-[1fr,auto,auto]">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
placeholder="{{ __('Titel oder Inhalt suchen…') }}"
|
||||
icon="magnifying-glass"
|
||||
/>
|
||||
|
||||
<flux:select wire:model.live="portalFilter">
|
||||
<option value="all">{{ __('Alle Portale') }}</option>
|
||||
@foreach ($portalOptions as $option)
|
||||
<option value="{{ $option->value }}">{{ $option->label() }}</option>
|
||||
@endforeach
|
||||
</flux:select>
|
||||
|
||||
<flux:select wire:model.live="statusFilter">
|
||||
<option value="all">{{ __('Alle Status') }}</option>
|
||||
<option value="active">{{ __('Aktiv') }}</option>
|
||||
|
|
@ -157,9 +154,16 @@ new #[Layout('components.layouts.app'), Title('Footer-Codes')] class extends Com
|
|||
<option value="global">{{ __('Global') }}</option>
|
||||
</flux:select>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card>
|
||||
{{-- ============== TABELLE ============== --}}
|
||||
<article class="panel overflow-hidden">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Alle Footer-Codes') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __(':count Einträge', ['count' => $codes->total()]) }}
|
||||
</span>
|
||||
</div>
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Titel') }}</flux:table.column>
|
||||
|
|
@ -177,29 +181,35 @@ new #[Layout('components.layouts.app'), Title('Footer-Codes')] class extends Com
|
|||
<flux:table.cell>
|
||||
<div class="flex items-center gap-2">
|
||||
@if ($code->is_global)
|
||||
<flux:badge color="purple" size="xs" icon="globe-alt">{{ __('Global') }}</flux:badge>
|
||||
<span class="badge hub dot">{{ __('Global') }}</span>
|
||||
@endif
|
||||
<span class="font-medium">{{ $code->title }}</span>
|
||||
<span class="text-[13px] font-semibold text-[color:var(--color-ink)]">{{ $code->title }}</span>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:badge color="zinc" size="sm">{{ $code->portal->label() }}</flux:badge>
|
||||
<span class="badge hub">{{ $code->portal->label() }}</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<span class="text-[12px] text-[color:var(--color-ink-2)] font-mono">
|
||||
{{ $code->language ? strtoupper($code->language) : '–' }}
|
||||
</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if ($code->is_global)
|
||||
<span class="text-zinc-500">{{ __('alle') }}</span>
|
||||
<span class="text-[12px] text-[color:var(--color-ink-3)]">{{ __('alle') }}</span>
|
||||
@else
|
||||
{{ $code->categories_count }}
|
||||
<span class="text-[12.5px] text-[color:var(--color-ink)] tabular-nums">{{ $code->categories_count }}</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>{{ $code->priority }}</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:badge :color="$code->is_active ? 'green' : 'zinc'" size="sm">
|
||||
{{ $code->is_active ? __('Aktiv') : __('Inaktiv') }}
|
||||
</flux:badge>
|
||||
<span class="text-[12.5px] text-[color:var(--color-ink)] tabular-nums">{{ $code->priority }}</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if ($code->is_active)
|
||||
<span class="badge ok dot">{{ __('Aktiv') }}</span>
|
||||
@else
|
||||
<span class="badge dot">{{ __('Inaktiv') }}</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell align="end">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
|
|
@ -224,17 +234,23 @@ new #[Layout('components.layouts.app'), Title('Footer-Codes')] class extends Com
|
|||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell :colspan="7">
|
||||
<div class="py-8 text-center text-zinc-500">
|
||||
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
|
||||
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-3
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||||
<flux:icon.code-bracket-square class="size-6" />
|
||||
</div>
|
||||
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
|
||||
{{ __('Keine Footer-Codes gefunden.') }}
|
||||
</div>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table.rows>
|
||||
</flux:table>
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="border-t border-[color:var(--color-bg-rule)] p-4">
|
||||
{{ $codes->links() }}
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -120,58 +120,68 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="space-y-2">
|
||||
<flux:heading size="lg">{{ __('Legacy Rechnungen') }}</flux:heading>
|
||||
<flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Finanzen') }}</span>
|
||||
<span class="badge hub">{{ __('Legacy-Rechnungsarchiv') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Legacy Rechnungen') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[720px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Legacy-Rechnungsarchiv mit read-only Übersicht, Filtern und PDF-Download. Der neue Stripe-Rechnungslauf folgt separat in Phase 8.') }}
|
||||
</flux:subheading>
|
||||
</p>
|
||||
</div>
|
||||
<flux:badge color="zinc" icon="archive-box" size="lg">
|
||||
{{ __('Legacy-Archiv') }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
@if ($stats['unmapped_count'] > 0)
|
||||
<flux:callout color="yellow" icon="exclamation-triangle">
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-start gap-3
|
||||
bg-[color:var(--color-warn-soft)] border-[color:var(--color-warn)]/30 text-[color:var(--color-ink-2)]">
|
||||
<flux:icon.exclamation-triangle class="size-[16px] flex-shrink-0 mt-0.5 text-[color:var(--color-accent-deep)]" />
|
||||
<div class="flex-1">
|
||||
{{ __(':count Legacy-Rechnungen konnten keinem neuen User zugeordnet werden. Sie bleiben im Archiv sichtbar und sollten im Rehearsal-Report fachlich geprüft werden.', ['count' => number_format($stats['unmapped_count'], 0, ',', '.')]) }}
|
||||
</flux:callout>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 lg:grid-cols-5">
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Rechnungen') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ number_format($stats['count'], 0, ',', '.') }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Archivsumme') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ number_format($stats['total_cents'] / 100, 2, ',', '.') }} €</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Bezahlt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ number_format($stats['paid_count'], 0, ',', '.') }}</flux:text>
|
||||
</flux:card>
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('Ohne User') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ number_format($stats['unmapped_count'], 0, ',', '.') }}</flux:text>
|
||||
</flux:card>
|
||||
@if($supportsPdfGeneratedAt)
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('PDF erzeugt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ number_format($stats['generated_pdf_count'], 0, ',', '.') }}</flux:text>
|
||||
</flux:card>
|
||||
@else
|
||||
<flux:card>
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('PDF-Status') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ __('Migration offen') }}</flux:text>
|
||||
</flux:card>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<flux:card>
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-2 gap-4 lg:grid-cols-5">
|
||||
<x-portal.stat-card variant="primary" :label="__('Rechnungen')" :value="number_format($stats['count'], 0, ',', '.')">
|
||||
<x-slot:meta>{{ __('Archivdatensätze') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('Archivsumme')" :value="number_format($stats['total_cents'] / 100, 2, ',', '.').' €'">
|
||||
<x-slot:meta>{{ __('historisch') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Bezahlt')" :value="number_format($stats['paid_count'], 0, ',', '.')">
|
||||
<x-slot:meta>{{ __('mit Zahldatum') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="warn" :label="__('Ohne User')" :value="number_format($stats['unmapped_count'], 0, ',', '.')">
|
||||
<x-slot:meta>{{ __('zu mappen') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
@if ($supportsPdfGeneratedAt)
|
||||
<x-portal.stat-card variant="ok" :label="__('PDF erzeugt')" :value="number_format($stats['generated_pdf_count'], 0, ',', '.')">
|
||||
<x-slot:meta>{{ __('aus Archiv') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
@else
|
||||
<x-portal.stat-card variant="muted" :label="__('PDF-Status')" :value="__('Migration offen')">
|
||||
<x-slot:meta>{{ __('Schema-Update fehlt') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
@endif
|
||||
</section>
|
||||
|
||||
{{-- ============== FILTER-PANEL ============== --}}
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Filter & Suche') }}</span>
|
||||
<flux:button size="sm" variant="ghost" icon="arrow-path" wire:click="resetFilters">
|
||||
{{ __('Filter zurücksetzen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<div class="grid gap-3 lg:grid-cols-6">
|
||||
<flux:input
|
||||
wire:model.live.debounce.300ms="search"
|
||||
|
|
@ -209,18 +219,20 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
</flux:select>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<flux:text class="text-sm text-zinc-500">
|
||||
<p class="text-[12px] text-[color:var(--color-ink-3)] m-0">
|
||||
{{ __(':count Treffer für die aktuelle Filterung. PDF-Dateien werden bei Bedarf aus den archivierten Legacy-Daten erzeugt.', ['count' => number_format($stats['filtered_count'], 0, ',', '.')]) }}
|
||||
</flux:text>
|
||||
<flux:button size="sm" variant="ghost" icon="arrow-path" wire:click="resetFilters">
|
||||
{{ __('Filter zurücksetzen') }}
|
||||
</flux:button>
|
||||
</p>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
|
||||
<flux:card class="p-0">
|
||||
<div class="p-4">
|
||||
{{-- ============== TABELLE ============== --}}
|
||||
<article class="panel overflow-hidden">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Legacy-Rechnungen') }}</span>
|
||||
<span class="text-[11.5px] text-[color:var(--color-ink-3)]">
|
||||
{{ __(':count Treffer', ['count' => number_format($stats['filtered_count'], 0, ',', '.')]) }}
|
||||
</span>
|
||||
</div>
|
||||
<flux:table>
|
||||
<flux:table.columns>
|
||||
<flux:table.column>{{ __('Rechnungsnr.') }}</flux:table.column>
|
||||
|
|
@ -235,17 +247,17 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
@forelse ($invoices as $invoice)
|
||||
<flux:table.row wire:key="admin-legacy-invoice-{{ $invoice->id }}">
|
||||
<flux:table.cell>
|
||||
<div class="space-y-1">
|
||||
<flux:text weight="semibold">{{ $invoice->number ?? ('#'.$invoice->legacy_id) }}</flux:text>
|
||||
<flux:text class="text-xs text-zinc-500">Legacy-ID: {{ $invoice->legacy_id }}</flux:text>
|
||||
<div class="space-y-0.5">
|
||||
<div class="text-[13px] font-semibold text-[color:var(--color-ink)]">{{ $invoice->number ?? ('#'.$invoice->legacy_id) }}</div>
|
||||
<div class="text-[11px] text-[color:var(--color-ink-3)] font-mono">Legacy-ID: {{ $invoice->legacy_id }}</div>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:badge size="sm" color="zinc">{{ $invoice->legacy_portal?->label() }}</flux:badge>
|
||||
<span class="badge hub">{{ $invoice->legacy_portal?->label() }}</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
@if ($invoice->user)
|
||||
<div class="space-y-1">
|
||||
<div class="space-y-0.5">
|
||||
<flux:button
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
|
|
@ -254,28 +266,30 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
>
|
||||
{{ $invoice->user->name }}
|
||||
</flux:button>
|
||||
<flux:text class="text-xs text-zinc-500">{{ $invoice->user->email }}</flux:text>
|
||||
<div class="text-[11px] text-[color:var(--color-ink-3)]">{{ $invoice->user->email }}</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-1">
|
||||
<flux:badge size="sm" color="yellow">{{ __('Ohne Zuordnung') }}</flux:badge>
|
||||
<flux:text class="text-xs text-zinc-500">Legacy-User: {{ $invoice->legacy_user_id ?? 'n/a' }}</flux:text>
|
||||
<span class="badge warn dot">{{ __('Ohne Zuordnung') }}</span>
|
||||
<div class="text-[11px] text-[color:var(--color-ink-3)] font-mono">Legacy-User: {{ $invoice->legacy_user_id ?? 'n/a' }}</div>
|
||||
</div>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:text weight="semibold">{{ number_format($invoice->total_cents / 100, 2, ',', '.') }} €</flux:text>
|
||||
<span class="text-[13px] font-semibold text-[color:var(--color-ink)] tabular-nums">{{ number_format($invoice->total_cents / 100, 2, ',', '.') }} €</span>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<flux:badge size="sm" color="{{ $invoice->paid_at ? 'green' : 'yellow' }}">
|
||||
{{ $invoice->status ?? ($invoice->paid_at ? __('Bezahlt') : __('Offen')) }}
|
||||
</flux:badge>
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<div class="space-y-1">
|
||||
<flux:text class="text-sm text-zinc-500">{{ $invoice->invoice_date?->format('d.m.Y') ?? '–' }}</flux:text>
|
||||
@if ($invoice->paid_at)
|
||||
<flux:text class="text-xs text-zinc-500">{{ __('bezahlt: :date', ['date' => $invoice->paid_at->format('d.m.Y')]) }}</flux:text>
|
||||
<span class="badge ok dot">{{ $invoice->status ?? __('Bezahlt') }}</span>
|
||||
@else
|
||||
<span class="badge warn dot">{{ $invoice->status ?? __('Offen') }}</span>
|
||||
@endif
|
||||
</flux:table.cell>
|
||||
<flux:table.cell>
|
||||
<div class="space-y-0.5">
|
||||
<div class="text-[12px] text-[color:var(--color-ink-2)]">{{ $invoice->invoice_date?->format('d.m.Y') ?? '–' }}</div>
|
||||
@if ($invoice->paid_at)
|
||||
<div class="text-[11px] text-[color:var(--color-ink-3)]">{{ __('bezahlt: :date', ['date' => $invoice->paid_at->format('d.m.Y')]) }}</div>
|
||||
@endif
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
|
|
@ -291,7 +305,7 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
{{ __('Öffnen') }}
|
||||
</flux:button>
|
||||
@if ($supportsPdfGeneratedAt && $invoice->pdf_generated_at)
|
||||
<flux:badge size="sm" color="green">{{ __('erzeugt') }}</flux:badge>
|
||||
<span class="badge ok">{{ __('erzeugt') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
|
|
@ -299,16 +313,22 @@ new #[Layout('components.layouts.app'), Title('Legacy Rechnungen')] class extend
|
|||
@empty
|
||||
<flux:table.row>
|
||||
<flux:table.cell colspan="7">
|
||||
<div class="flex flex-col items-center justify-center py-10">
|
||||
<flux:icon.document-text class="size-10 text-zinc-300" />
|
||||
<flux:text class="mt-3 text-zinc-500">{{ __('Keine Legacy-Rechnungen für diese Filter gefunden.') }}</flux:text>
|
||||
<div class="flex flex-col items-center justify-center px-4 py-10 text-center">
|
||||
<div class="w-14 h-14 rounded-[6px] flex items-center justify-center mb-3
|
||||
bg-[color:var(--color-hub-soft)] border border-[color:var(--color-hub-soft-2)] text-[color:var(--color-hub)]">
|
||||
<flux:icon.document-text class="size-6" />
|
||||
</div>
|
||||
<div class="text-[14px] font-semibold text-[color:var(--color-ink)] mb-1">
|
||||
{{ __('Keine Legacy-Rechnungen für diese Filter gefunden.') }}
|
||||
</div>
|
||||
</div>
|
||||
</flux:table.cell>
|
||||
</flux:table.row>
|
||||
@endforelse
|
||||
</flux:table>
|
||||
</div>
|
||||
</flux:card>
|
||||
|
||||
<div class="border-t border-[color:var(--color-bg-rule)] p-4">
|
||||
{{ $invoices->links() }}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -86,86 +86,86 @@ new #[Layout('components.layouts.app'), Title('Newsletter Sync')] class extends
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="space-y-2">
|
||||
<flux:heading size="lg">{{ __('Newsletter Synchronisierung') }}</flux:heading>
|
||||
<flux:text class="text-zinc-500 dark:text-zinc-400">
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Newsletter') }}</span>
|
||||
@if ($syncConfig['enabled'])
|
||||
<span class="badge ok dot">{{ __('Sync aktiv') }}</span>
|
||||
@else
|
||||
<span class="badge dot">{{ __('Sync deaktiviert') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Newsletter Synchronisierung') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Vorbereitung fuer die kuenftige externe API-Anbindung. Aktuell ist nur das technische Grundgeruest aktiv.') }}
|
||||
</flux:text>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-end gap-2">
|
||||
@if ($syncConfig['enabled'])
|
||||
<flux:badge color="green" icon="check" size="sm">{{ __('Sync aktiv') }}</flux:badge>
|
||||
@else
|
||||
<flux:badge color="zinc" icon="pause" size="sm">{{ __('Sync deaktiviert') }}</flux:badge>
|
||||
@endif
|
||||
|
||||
<div class="flex gap-2">
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button size="sm" variant="ghost" icon="eye" wire:click="triggerDryRun">
|
||||
{{ __('Dry Run') }}
|
||||
</flux:button>
|
||||
|
||||
<flux:button size="sm" icon="play" wire:click="triggerTestSync">
|
||||
<flux:button size="sm" variant="primary" icon="play" wire:click="triggerTestSync">
|
||||
{{ __('Test-Sync ausfuehren') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
@if ($dryRunMessage)
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-600 dark:text-zinc-300">{{ $dryRunMessage }}</flux:text>
|
||||
</flux:card>
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-start gap-3
|
||||
bg-[color:var(--color-hub-soft)] border-[color:var(--color-hub-soft-2)] text-[color:var(--color-ink-2)]">
|
||||
<flux:icon.eye class="size-[16px] flex-shrink-0 mt-0.5 text-[color:var(--color-hub)]" />
|
||||
<div class="flex-1">{{ $dryRunMessage }}</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($syncMessage)
|
||||
<flux:card>
|
||||
<flux:text class="text-sm">{{ $syncMessage }}</flux:text>
|
||||
</flux:card>
|
||||
<div class="px-4 py-3 rounded-[5px] border text-[12.5px] flex items-center gap-2
|
||||
bg-[color:var(--color-ok-soft)] border-[color:var(--color-ok)]/30 text-[color:var(--color-gain-deep)]">
|
||||
<flux:icon.check-circle class="size-[16px] flex-shrink-0" />
|
||||
{{ $syncMessage }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Gesamt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['total'] }}</flux:text>
|
||||
</flux:card>
|
||||
{{-- ============== KPI-Reihe ============== --}}
|
||||
<section class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||
<x-portal.stat-card variant="primary" :label="__('Gesamt')" :value="number_format($stats['total'])">
|
||||
<x-slot:meta>{{ __('Subscriptions') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="ok" :label="__('Bestaetigt')" :value="number_format($stats['confirmed'])">
|
||||
<x-slot:meta>{{ __('Double Opt-in') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="warn" :label="__('Unbestaetigt')" :value="number_format($stats['pending'])">
|
||||
<x-slot:meta>{{ __('warten auf Opt-in') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
<x-portal.stat-card variant="muted" :label="__('Abgemeldet')" :value="number_format($stats['unsubscribed'])">
|
||||
<x-slot:meta>{{ __('Unsubscribed') }}</x-slot:meta>
|
||||
</x-portal.stat-card>
|
||||
</section>
|
||||
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Bestaetigt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['confirmed'] }}</flux:text>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Unbestaetigt') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['pending'] }}</flux:text>
|
||||
</flux:card>
|
||||
|
||||
<flux:card>
|
||||
<flux:text class="text-sm text-zinc-500 dark:text-zinc-400">{{ __('Abgemeldet') }}</flux:text>
|
||||
<flux:text size="xl" weight="bold">{{ $stats['unsubscribed'] }}</flux:text>
|
||||
</flux:card>
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Konfiguration') }}</span>
|
||||
</div>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="sm">{{ __('Konfiguration') }}</flux:heading>
|
||||
|
||||
<div class="mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<dl class="p-5 grid grid-cols-1 gap-4 sm:grid-cols-2 text-[12.5px]">
|
||||
<div>
|
||||
<flux:text class="text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">{{ __('Provider') }}</flux:text>
|
||||
<flux:text class="mt-1">{{ $syncConfig['provider'] }}</flux:text>
|
||||
<dt class="text-[10.5px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Provider') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)] font-mono">{{ $syncConfig['provider'] }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<flux:text class="text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">{{ __('Timeout') }}</flux:text>
|
||||
<flux:text class="mt-1">{{ $syncConfig['timeout'] }}s</flux:text>
|
||||
<dt class="text-[10.5px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Timeout') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)] tabular-nums">{{ $syncConfig['timeout'] }}s</dd>
|
||||
</div>
|
||||
<div class="sm:col-span-2">
|
||||
<flux:text class="text-xs uppercase tracking-wide text-zinc-500 dark:text-zinc-400">{{ __('Endpoint') }}</flux:text>
|
||||
<flux:text class="mt-1 break-all">{{ $syncConfig['endpoint'] }}</flux:text>
|
||||
<dt class="text-[10.5px] uppercase tracking-[0.6px] text-[color:var(--color-ink-3)] font-semibold mb-1">{{ __('Endpoint') }}</dt>
|
||||
<dd class="text-[color:var(--color-ink)] font-mono break-all">{{ $syncConfig['endpoint'] }}</dd>
|
||||
</div>
|
||||
</div>
|
||||
</flux:card>
|
||||
</dl>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,42 +12,47 @@ new #[Layout('components.layouts.app'), Title('Zahlungen')] class extends Compon
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<div class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="space-y-2">
|
||||
<flux:heading size="lg">{{ __('Zahlungen') }}</flux:heading>
|
||||
<flux:subheading>
|
||||
<div class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-wrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Finanzen') }}</span>
|
||||
<span class="badge warn">{{ __('In Vorbereitung') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Zahlungen') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[720px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Zahlungsabwicklung läuft in Phase 8 ausschließlich über Stripe – alte Zahlungsarten (Rechnung, PayPal, SPK Berlin, Cortal Consors, Bar/Post) entfallen komplett.') }}
|
||||
</flux:subheading>
|
||||
</p>
|
||||
</div>
|
||||
<flux:badge color="amber" icon="clock" size="lg">
|
||||
{{ __('In Vorbereitung') }}
|
||||
</flux:badge>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
<flux:card>
|
||||
<flux:heading size="sm" class="mb-3">{{ __('Geplant für P8') }}</flux:heading>
|
||||
<ul class="space-y-2 text-sm text-zinc-600 dark:text-zinc-300">
|
||||
<article class="panel">
|
||||
<div class="panel-head">
|
||||
<span class="section-eyebrow">{{ __('Geplant für P8') }}</span>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<ul class="space-y-3 text-[12.5px] text-[color:var(--color-ink-2)] list-none m-0">
|
||||
<li class="flex gap-2">
|
||||
<flux:icon.check-circle class="size-5 shrink-0 text-zinc-400" />
|
||||
<flux:icon.check-circle class="size-[16px] shrink-0 mt-0.5 text-[color:var(--color-ok)]" />
|
||||
<span>{{ __('Live-Anzeige aller Stripe-Zahlungen mit Filtern nach Status, Methode und Zeitraum.') }}</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<flux:icon.check-circle class="size-5 shrink-0 text-zinc-400" />
|
||||
<flux:icon.check-circle class="size-[16px] shrink-0 mt-0.5 text-[color:var(--color-ok)]" />
|
||||
<span>{{ __('Detail-Ansicht mit Stripe-Transaktions-ID, Webhook-Trail und zugeordneter Rechnung.') }}</span>
|
||||
</li>
|
||||
<li class="flex gap-2">
|
||||
<flux:icon.check-circle class="size-5 shrink-0 text-zinc-400" />
|
||||
<flux:icon.check-circle class="size-[16px] shrink-0 mt-0.5 text-[color:var(--color-ok)]" />
|
||||
<span>{{ __('Refund-Workflow direkt aus dem Admin (sofern Stripe-Berechtigung gegeben).') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<flux:separator class="my-5" />
|
||||
|
||||
<flux:text class="text-sm text-zinc-500">
|
||||
<p class="pt-4 border-t border-[color:var(--color-bg-rule)] text-[12px] text-[color:var(--color-ink-3)] m-0">
|
||||
{{ __('Datenmodell (user_payments, user_payment_options) ist bereits angelegt; die Anbindung folgt mit Stripe-Webhooks.') }}
|
||||
</flux:text>
|
||||
</flux:card>
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -42,24 +42,36 @@ new class extends Component
|
|||
}; ?>
|
||||
|
||||
<div class="px-1 py-2">
|
||||
<div class="mb-1 text-xs font-medium text-zinc-500 dark:text-zinc-400 uppercase tracking-wider px-2">
|
||||
<div class="mb-2 px-2 text-[10.5px] uppercase tracking-[0.6px] font-semibold text-[color:var(--color-ink-3)]">
|
||||
{{ __('Portal-Filter') }}
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<button
|
||||
wire:click="switchPortal('')"
|
||||
class="flex items-center gap-2 rounded px-2 py-1.5 text-sm transition-colors {{ $activePortal === '' ? 'bg-zinc-200 dark:bg-zinc-700 font-medium' : 'hover:bg-zinc-100 dark:hover:bg-zinc-800' }}"
|
||||
@class([
|
||||
'flex items-center gap-2 rounded-[4px] px-2 py-1.5 text-[12.5px] transition-colors',
|
||||
'bg-[color:var(--color-bg-elev)] font-semibold text-[color:var(--color-ink)] border border-[color:var(--color-bg-rule)]' => $activePortal === '',
|
||||
'text-[color:var(--color-ink-2)] hover:bg-[color:var(--color-bg-elev)]/60' => $activePortal !== '',
|
||||
])
|
||||
>
|
||||
<span class="h-2 w-2 rounded-full bg-zinc-400"></span>
|
||||
<span class="h-2 w-2 rounded-full bg-[color:var(--color-ink-3)]"></span>
|
||||
{{ __('Alle Portale') }}
|
||||
</button>
|
||||
@foreach ($portals as $portal)
|
||||
@if ($portal !== \App\Enums\Portal::Both)
|
||||
<button
|
||||
wire:click="switchPortal('{{ $portal->value }}')"
|
||||
class="flex items-center gap-2 rounded px-2 py-1.5 text-sm transition-colors {{ $activePortal === $portal->value ? 'bg-zinc-200 dark:bg-zinc-700 font-medium' : 'hover:bg-zinc-100 dark:hover:bg-zinc-800' }}"
|
||||
@class([
|
||||
'flex items-center gap-2 rounded-[4px] px-2 py-1.5 text-[12.5px] transition-colors',
|
||||
'bg-[color:var(--color-bg-elev)] font-semibold text-[color:var(--color-ink)] border border-[color:var(--color-bg-rule)]' => $activePortal === $portal->value,
|
||||
'text-[color:var(--color-ink-2)] hover:bg-[color:var(--color-bg-elev)]/60' => $activePortal !== $portal->value,
|
||||
])
|
||||
>
|
||||
<span class="h-2 w-2 rounded-full {{ $portal === \App\Enums\Portal::Presseecho ? 'bg-green-500' : 'bg-red-500' }}"></span>
|
||||
<span @class([
|
||||
'h-2 w-2 rounded-full',
|
||||
'bg-[color:var(--color-ok)]' => $portal === \App\Enums\Portal::Presseecho,
|
||||
'bg-[color:var(--color-err)]' => $portal !== \App\Enums\Portal::Presseecho,
|
||||
])></span>
|
||||
{{ $portal->label() }}
|
||||
</button>
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -50,23 +50,33 @@ new #[Layout('components.layouts.app'), Title('Neue Voreinstellung')] class exte
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Neue Voreinstellung') }}</flux:heading>
|
||||
<flux:subheading>{{ __('Texte, Zahlen oder JSON-Werte zentral fuer Admin-Funktionen pflegen.') }}</flux:subheading>
|
||||
<form wire:submit="save" class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Voreinstellungen') }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Neue Voreinstellung') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Texte, Zahlen oder JSON-Werte zentral fuer Admin-Funktionen pflegen.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
@include('livewire.admin.presets.partials.form-fields')
|
||||
|
||||
<flux:card>
|
||||
<div class="flex justify-end gap-3">
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
|
|
@ -74,5 +84,5 @@ new #[Layout('components.layouts.app'), Title('Neue Voreinstellung')] class exte
|
|||
{{ __('Voreinstellung erstellen') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -71,23 +71,34 @@ new #[Layout('components.layouts.app'), Title('Voreinstellung bearbeiten')] clas
|
|||
}
|
||||
}; ?>
|
||||
|
||||
<form wire:submit="save" class="space-y-6">
|
||||
<flux:card>
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<flux:heading size="lg">{{ __('Voreinstellung bearbeiten') }}</flux:heading>
|
||||
<flux:subheading>{{ __('ID') }}: {{ $id }}</flux:subheading>
|
||||
<form wire:submit="save" class="space-y-8">
|
||||
{{-- ============== PAGE HEADER ============== --}}
|
||||
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
|
||||
<div class="min-w-0">
|
||||
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
|
||||
<span class="badge hub dot">{{ __('Admin Backend') }}</span>
|
||||
<span class="eyebrow muted">{{ __('Administration · Voreinstellungen') }}</span>
|
||||
<span class="badge hub">ID #{{ $id }}</span>
|
||||
</div>
|
||||
<h1 class="text-[30px] font-bold tracking-[-0.6px] leading-[1.15] m-0 text-[color:var(--color-ink)]">
|
||||
{{ __('Voreinstellung bearbeiten') }}
|
||||
</h1>
|
||||
<p class="text-[13px] leading-[1.55] mt-2 m-0 max-w-[640px] text-[color:var(--color-ink-2)]">
|
||||
{{ __('Texte, Zahlen oder JSON-Werte zentral fuer Admin-Funktionen pflegen.') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<flux:button variant="ghost" icon="arrow-left" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
{{ __('Zurück') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</header>
|
||||
|
||||
@include('livewire.admin.presets.partials.form-fields')
|
||||
|
||||
<flux:card>
|
||||
<div class="flex justify-end gap-3">
|
||||
<article class="panel">
|
||||
<div class="p-5 flex justify-end gap-3">
|
||||
<flux:button variant="ghost" href="{{ route('admin.presets.index') }}" wire:navigate>
|
||||
{{ __('Abbrechen') }}
|
||||
</flux:button>
|
||||
|
|
@ -95,5 +106,5 @@ new #[Layout('components.layouts.app'), Title('Voreinstellung bearbeiten')] clas
|
|||
{{ __('Änderungen speichern') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</flux:card>
|
||||
</article>
|
||||
</form>
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue