presseportale/dev/frontend/hub-flux/19-PHASE-7-PRESS-RELEASE-FORM.md
Kevin Adametz d2ba22c0cf
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
create PM v0.5
2026-05-20 19:14:39 +02:00

240 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 06 sind dort abgeschlossen).
**Status**: 🟡 in Planung · **Aufwand**: 23 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` 4090 Zeichen empfohlen)
3. **Untertitel** (optional, Counter-Pille 0/200)
4. **Fließtext** (`<flux:editor>` mit reduzierter Toolbar,
Counter-Pille 6003500 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, 5255
- `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.