presseportale/dev/frontend/hub-flux/19-PHASE-7-PRESS-RELEASE-FORM.md
Kevin Adametz e8c47b7553
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
22-05-2026 Optimierung der User und Admin Panels
2026-05-22 11:18:59 +02:00

11 KiB
Raw Blame History

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: abgeschlossen · 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

Was umgesetzt wurde:

  • UI: Customer Create + Edit haben eine zweite Radio-Option „Geplanter Termin" mit <flux:input type="datetime-local"> und einen separaten Embargo-Switch + Date-Picker. bald-Badge im Veröffentlichungs-Block entfernt.
  • Validation: scheduled_at muss min. 5 Min in der Zukunft liegen (passend zum Scheduler-Intervall), embargo_at muss in der Zukunft liegen. Beide Felder werden nur validiert, wenn der jeweilige Toggle aktiv ist.
  • Save: scheduled_at + embargo_at werden in den Forms bei Create + Update persistiert (Customer Variante; Admin bleibt vorerst ohne UI).
  • Service: PressReleaseService::publish() nutzt jetzt resolvePublishedAt():
    1. bereits gesetztes published_at bleibt unangetastet
    2. sonst scheduled_at (falls vorhanden)
    3. embargo_at verschiebt zusätzlich nach hinten
    4. Fallback now() Damit greift der vorhandene Sichtbarkeits-Filter where('published_at', '<=', now()) in routes/web.php automatisch — keine zusätzlichen Embargo-Filter nötig.
  • Background-Command press-releases:publish-scheduled (App\Console\Commands\PublishScheduledPressReleases) findet alle Review-PRs mit scheduled_at <= now, publisht sie via Service (source: 'scheduler' im Status-Log) und respektiert weiterhin Blacklist + Mail-Benachrichtigung. Optionen: --dry-run, --limit=N.
  • Scheduler-Eintrag in routes/console.php: alle 5 Min, withoutOverlapping(), runInBackground().

Tests:

  • tests/Feature/PressReleaseSchedulingTest.php — 11 Tests für Service + Command (resolvePublishedAt-Matrix, Source-Log, Command-Dry-Run, Command-Limit).
  • tests/Feature/CustomerPressReleaseSchedulingFormTest.php — 5 Tests für die Form (Persistierung, Past-Date-Validation für beide Felder, Now-Mode setzt scheduled_at zurück, Edit-Hydration).

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.