240 lines
9.7 KiB
Markdown
240 lines
9.7 KiB
Markdown
# Phase 7 — Press-Release-Form-Refactor
|
||
|
||
> Großes Modul-Refactor: das zentrale „Neue Pressemitteilung"-Form
|
||
> wird auf das Mockup `User Neue Mitteilung presseportale.html` gehoben.
|
||
> Bekommt deshalb eine eigene Phase außerhalb der bisherigen
|
||
> `hub-flux`-Roadmap (Phase 0–6 sind dort abgeschlossen).
|
||
|
||
**Status**: 🟡 in Planung · **Aufwand**: 2–3 Tage · **Risiko**: mittel
|
||
(Datenmodell-Erweiterung, Editor-Format-Migration, Composer-Dependency)
|
||
|
||
---
|
||
|
||
## Ausgangslage
|
||
|
||
| Datei | Status |
|
||
|---|---|
|
||
| `resources/views/livewire/customer/press-releases/create.blade.php` | nur 1-Spalter, `<flux:textarea>`, fehlende Felder |
|
||
| `resources/views/livewire/customer/press-releases/edit.blade.php` | gleicher Stand, plus Image-Manager |
|
||
| `resources/views/livewire/admin/press-releases/create.blade.php` | Admin-Variante, dünner |
|
||
| `resources/views/livewire/admin/press-releases/edit.blade.php` | Admin-Variante |
|
||
|
||
Das Mockup verlangt einen 2-Spalter mit eigener
|
||
Settings-Sidebar (Status & Submit, Portal, Pressekontakt, Tags,
|
||
Veröffentlichung, SEO). Linke Spalte: Firma-Selector, Titel,
|
||
Untertitel, Editor, Medien, Anhänge, Boilerplate.
|
||
|
||
---
|
||
|
||
## Entscheidungen (vom User abgesegnet)
|
||
|
||
| Frage | Entscheidung |
|
||
|---|---|
|
||
| Scope | **full** — Anhänge-Tabelle + Schema-Vorbereitung für Scheduling/Embargo |
|
||
| Portal-Auswahl | **read-only** — Portal kommt immer aus der Firma; UI zeigt nur Badge |
|
||
| HTML-Sanitizer | **`mews/purifier`** — explizit approved, wird in 7B installiert |
|
||
| Pressekontakt | **genau 1 pro PM**, Single-Select aus Firmen-Kontakten; Datenmodell bleibt n:m-Pivot, Validation erzwingt `count == 1` |
|
||
| Admin-Forms | **mitziehen** in 7C+7D, gleiches Layout + Admin-only Felder |
|
||
| Default-Kontakt | **erster Firmen-Kontakt alphabetisch** (keine Schema-Änderung) |
|
||
|
||
---
|
||
|
||
## Päckchen-Aufteilung
|
||
|
||
### 7A — Migrations + Models
|
||
|
||
**Scope:**
|
||
- `press_releases.subtitle` (string 255, nullable)
|
||
- `press_releases.boilerplate_override` (text, nullable) — pro PM überschreibbare Firmen-Boilerplate
|
||
- `press_releases.scheduled_at` (timestamp, nullable) — Schema da, UI „bald"
|
||
- `press_releases.embargo_at` (timestamp, nullable) — Schema da, UI „bald"
|
||
- `companies.boilerplate` (text, nullable) — Firmenprofil-Boilerplate
|
||
- Neue Tabelle `press_release_attachments` analog `press_release_images`
|
||
(`disk`, `path`, `original_name`, `mime`, `size`, `sort_order`,
|
||
`title`, `description`, `legacy_portal`, `legacy_id`, soft-deletes,
|
||
timestamps).
|
||
- Models: `PressRelease`, `Company`, neues `PressReleaseAttachment`
|
||
+ Factory + Relationen + Casts.
|
||
- Bestehende Tests müssen grün bleiben (alle Felder nullable,
|
||
keine Verhaltensänderung).
|
||
|
||
**Akzeptanz**
|
||
- [ ] Migration up/down sauber
|
||
- [ ] `php artisan test --compact` grün (Baseline)
|
||
- [ ] `php artisan db:show press_releases / companies / press_release_attachments` zeigt neue Spalten
|
||
|
||
### 7B — Editor-Integration
|
||
|
||
**Scope:**
|
||
- `composer require mews/purifier` (explizite Approval einholen)
|
||
- Service `App\Services\PressRelease\PressReleaseHtmlSanitizer`
|
||
(Allowlist: `p,br,h2,h3,strong,em,u,ul,ol,li,blockquote,a[href|rel|target]`)
|
||
- Create/Edit-Form: `<flux:textarea>` → `<flux:editor>` mit
|
||
reduzierter Toolbar:
|
||
```
|
||
toolbar="heading | bold italic | bullet ordered blockquote | link | undo redo"
|
||
```
|
||
- Save-Pfad: `$this->text = $sanitizer->clean($this->text)`
|
||
- Display-Pfad (`show.blade.php`, Portal-Detail-Seiten,
|
||
`PressReleaseResource`):
|
||
- Wenn `text` HTML-Tags enthält → `{!! $clean !!}` (sanitized)
|
||
- Wenn nicht (legacy) → `{!! nl2br(e($text)) !!}`
|
||
- Helper `PressRelease::renderedText(): HtmlString`
|
||
- API: `PressReleaseResource` liefert weiterhin String (HTML),
|
||
Doku-Update `_docs/api/v1.yml` und `dev/migration 2026/07-API-MIGRATION.md`.
|
||
|
||
**Akzeptanz**
|
||
- [ ] `mews/purifier` in `composer.json`
|
||
- [ ] Alle bestehenden Plain-Text-PMs werden korrekt angezeigt
|
||
- [ ] Neue HTML-PMs werden korrekt sanitized gespeichert
|
||
- [ ] Pest-Test: `<script>`-Tag wird bei Save gestrippt
|
||
- [ ] Pest-Test: legacy plain text → `<p>`/`<br>` Rendering
|
||
|
||
### 7C — Customer-Create-Form (UI)
|
||
|
||
**Scope:**
|
||
- Page-Header wie Mockup (Crumb-Trail + Topbar mit Autosave-Status,
|
||
„Speichern", „Vorschau-bald")
|
||
- 2-Spalter `grid-cols-[1fr,360px]`
|
||
- Linke Spalte (Schreibfläche):
|
||
1. **Firma-Selector** als kompakte Inline-Pille (`flux:dropdown`)
|
||
mit „Neue Firma anlegen"-Link
|
||
2. **Titel** (`<flux:input>` mit Title-Font, Counter-Pille
|
||
`meter good/warn` 40–90 Zeichen empfohlen)
|
||
3. **Untertitel** (optional, Counter-Pille 0/200)
|
||
4. **Fließtext** (`<flux:editor>` mit reduzierter Toolbar,
|
||
Counter-Pille 600–3500 Z., KI-Lektorat-bald-Hint)
|
||
5. **Medien** (bestehende `press-release-images-manager`-Komponente
|
||
im neuen Tile-Style anpassen — Titelbild-Flag,
|
||
Caption/Alt-Text Pflicht-Hint)
|
||
6. **Anhänge** (neue Livewire-Komponente in 7E)
|
||
7. **Boilerplate** (Read-only-Box mit Toggle „Für diese PM
|
||
überschreiben" → öffnet `<flux:textarea>`)
|
||
- Rechte Spalte (Settings-Sidebar, sticky):
|
||
1. **Status & Absenden** (Pre-Submit-Checkliste aus
|
||
Pflichtfeldern, primärer Submit „Zur Prüfung senden")
|
||
2. **Portal** (read-only-Badge aus Firma)
|
||
3. **Pressekontakt** (Single-Select aus Firmen-Kontakten,
|
||
Pflichtfeld, Warn-Hint falls Telefon fehlt)
|
||
4. **Themen-Tags** (Chip-Eingabe, parst `keywords` als
|
||
Komma-getrennt, max 5 Tags, Vorschläge aus Firma)
|
||
5. **Veröffentlichung** (RadioGroup: „Sofort nach Freigabe"
|
||
aktiv, „Geplanter Termin" als `bald`-Badge)
|
||
6. **SEO** (Collapsed, „automatisch aus Titel" als `bald`-Hint)
|
||
7. **Phase-2-Footer-Card** wie im Mockup
|
||
- Validation:
|
||
- `title`: required, 5–255
|
||
- `subtitle`: nullable, max 255
|
||
- `text`: required, min 50 (HTML-stripped)
|
||
- `company_id`: required, muss zur User-Firma gehören
|
||
- `category_id`: required
|
||
- `contact_id`: required, muss zur Firma gehören
|
||
- `keywords`: nullable, max 5 Tags
|
||
- `boilerplate_override`: nullable
|
||
- Pre-Submit-Check (Read-only-Anzeige, blockiert nicht):
|
||
- Titel vorhanden + Länge ok → ok
|
||
- Fließtext min 600 Z. → ok / sonst warn
|
||
- Firma gewählt → ok
|
||
- Mind. 1 Bild + Titelbild gesetzt → ok / sonst warn
|
||
- Mind. 1 Tag → ok / sonst warn
|
||
- Pressekontakt mit Telefon → ok / sonst warn
|
||
|
||
**Was NICHT ins 7C kommt:**
|
||
- Anhänge-Manager UI (kommt in 7E)
|
||
- Scheduling/Embargo-Logik (kommt in 7F)
|
||
- Autosave („alle paar Sek.") — erstmal nur Status-Anzeige
|
||
(„Manuell gespeichert vor X") via Livewire-Event nach `save()`;
|
||
echtes Autosave optional in 7F
|
||
|
||
**Akzeptanz**
|
||
- [ ] Bestehender Test `CustomerCompanyContextTest > customer press releases create …` grün
|
||
- [ ] Neuer Test: Create mit allen Pflichtfeldern → PR persistiert
|
||
- [ ] Neuer Test: Create ohne Pressekontakt → Validation-Fehler
|
||
- [ ] Pint + Build grün
|
||
|
||
### 7D — Customer-Edit-Form
|
||
|
||
**Scope:**
|
||
- Gleiches Layout wie 7C
|
||
- Submit-Sektion zeigt aktuellen Status (Entwurf/Abgelehnt) +
|
||
ggf. letzten Reject-Grund
|
||
- Image-Manager und Attachments-Manager weiterhin verfügbar
|
||
- Form ist nur editierbar, wenn `status in [draft, rejected]`
|
||
(bestehende Policy bleibt)
|
||
|
||
**Akzeptanz**
|
||
- [ ] Edit-Test (Update aller neuen Felder) grün
|
||
- [ ] Reject-Reason-Anzeige bleibt sichtbar
|
||
- [ ] Bestehende Tests bleiben grün
|
||
|
||
### 7E — Anhänge-Manager
|
||
|
||
**Scope:**
|
||
- Neue Volt-Komponente
|
||
`resources/views/livewire/components/press-release-attachments-manager.blade.php`
|
||
analog `press-release-images-manager.blade.php`
|
||
- Methoden: `upload`, `remove`, `moveUp`, `moveDown`
|
||
- Storage via `Storage::disk('public')` unter
|
||
`press-release-attachments/{press_release_id}/`
|
||
- Validation: PDF/DOCX/XLSX/PPTX, max. 25 MB pro Datei
|
||
- Tile-Layout wie im Mockup (PDF-Badge + Filename + Größe + Aktionen)
|
||
- Berechtigung wie beim Image-Manager (Policy `update` auf PR
|
||
+ Status `draft|rejected` für Customer)
|
||
|
||
**Akzeptanz**
|
||
- [ ] Upload-Test
|
||
- [ ] Remove-Test
|
||
- [ ] Reorder-Test
|
||
- [ ] Datei-Type-Validation grün
|
||
|
||
### 7F — Scheduling + Embargo (Schema vorhanden, UI aktivieren)
|
||
|
||
**Scope:**
|
||
- UI-Elemente aus 7C (Veröffentlichung-RadioGroup,
|
||
Embargo-Checkbox + Date-Picker) aktivieren
|
||
- `bald`-Badges entfernen
|
||
- Service-Logik in `PressReleaseService`:
|
||
- bei `submitForReview` mit `scheduled_at` → Status bleibt
|
||
`review`, beim `publish` durch Admin/Job wird `published_at`
|
||
auf `scheduled_at` gesetzt
|
||
- bei `embargo_at` → öffentliche Anzeige erst ab `embargo_at`
|
||
- Background-Job (`PublishScheduledPressReleases`) für die
|
||
Veröffentlichung um `scheduled_at`
|
||
- Test: Geplante PM wird nicht vor Termin öffentlich
|
||
- Test: Embargo-Filter im Portal-Index
|
||
|
||
**Hinweis:** 7F ist optional und kann nach 7C/D/E in eine eigene
|
||
kleine Sub-Phase ausgelagert werden, weil es einen Background-Job
|
||
einführt und der Test-Umfang separat sauber bleibt.
|
||
|
||
---
|
||
|
||
## Risiken & Mitigation
|
||
|
||
| Risiko | Mitigation |
|
||
|---|---|
|
||
| Editor-HTML bricht alte Anzeigen | Helper `renderedText()` mit Fallback auf `nl2br(e(…))` |
|
||
| Composer-Dependency `mews/purifier` ungeprüft | Vor Install explizite User-Approval; alternative `decide_later` möglich |
|
||
| Pressekontakt-Pivot mit > 1 Eintrag in DB | Migrations-Skript prüft & loggt; Validation in Save erzwingt 1 |
|
||
| Test-Suite-Regression | Jedes Päckchen einzeln + `php artisan test --compact` zwischen den Päckchen |
|
||
|
||
## Out of Scope (bewusst nicht in Phase 7)
|
||
|
||
- KI-Titel-Optimierung (Phase 8)
|
||
- KI-Lektorat (Phase 8)
|
||
- KI-Bildgenerierung (Phase 8)
|
||
- Mehrfach-Portal-Publishing (Phase 2 laut Mockup-Disclaimer)
|
||
- Versionshistorie (Phase 2 laut Mockup-Disclaimer)
|
||
- Portal-Vorschau (Phase 2)
|
||
- Echtes Autosave alle paar Sekunden (Polish, in 7F optional)
|
||
|
||
## Reihenfolge
|
||
|
||
1. **7A** Migrations + Models
|
||
2. **7B** Editor + Sanitizer
|
||
3. **7C** Customer-Create-Form
|
||
4. **7D** Customer-Edit-Form
|
||
5. **7E** Anhänge-Manager
|
||
6. **7F** Scheduling/Embargo (optional, eigene Sub-Phase)
|
||
|
||
Nach jedem Päckchen: User-Approval + Test-Run + Build + Pint + PROGRESS-Update.
|