Gebuendelt, da ueber gemeinsame lang-/Service-Dateien verschraenkt: - Kontakt-Nachrichten: contact_messages-Tabelle + Model/Enums/Factory, Admin-Queue (livewire/admin/contact-messages), ContactController persistiert das Anliegen zusaetzlich zur Brand-Mail - Page-Data-Cache (Services/Web/*PageData) + web-archive-Indizes auf press_releases - Validierungs-Feedback + Company-Portal-Restriction (Feature-Tests) - i18n-Auslagerung lang/de|en (auth, passwords, validation, static) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
155 lines
4.9 KiB
PHP
155 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Concerns;
|
|
|
|
use Carbon\Carbon;
|
|
use Flux\Flux;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
/**
|
|
* Termin-/Scheduling-Logik der PM-Editoren (Admin + Customer, Erstellen +
|
|
* Bearbeiten). Single Source of Truth für die Veröffentlichungsplanung.
|
|
*
|
|
* Die nutzende Livewire-Komponente muss bereitstellen:
|
|
* - string $publishMode ('now' | 'scheduled')
|
|
* - ?string $scheduledDate ('Y-m-d')
|
|
* - ?string $scheduledTime ('H:i')
|
|
* - ?string $scheduledAt ('Y-m-dTH:i', abgeleitet aus Datum + Zeit)
|
|
* - array formRules() Validierungsregeln (enthält 'scheduledAt')
|
|
*/
|
|
trait InteractsWithPressReleaseScheduling
|
|
{
|
|
public function updatedPublishMode(): void
|
|
{
|
|
$this->syncScheduledAt();
|
|
|
|
if ($this->publishMode === 'now') {
|
|
$this->scheduledDate = null;
|
|
$this->scheduledTime = null;
|
|
$this->scheduledAt = null;
|
|
$this->resetErrorBag(['scheduledDate', 'scheduledTime', 'scheduledAt']);
|
|
}
|
|
}
|
|
|
|
public function updatedScheduledDate(): void
|
|
{
|
|
$this->syncScheduledAt();
|
|
$this->validateScheduledAtWhenReady();
|
|
}
|
|
|
|
public function updatedScheduledTime(): void
|
|
{
|
|
$this->syncScheduledAt();
|
|
$this->validateScheduledAtWhenReady();
|
|
}
|
|
|
|
/**
|
|
* Live-Re-Validation: sobald für ein Property bereits ein Error im Bag
|
|
* liegt, wird es bei jeder Änderung neu geprüft. Die Termin-Synchronisierung
|
|
* liegt vollständig in den spezifischen `updated{PublishMode,ScheduledDate,
|
|
* ScheduledTime}`-Hooks; hier bleibt nur die generische Re-Validierung.
|
|
*/
|
|
public function updated(string $property): void
|
|
{
|
|
if (! $this->getErrorBag()->has($property)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$this->validateOnly($property, $this->formRules());
|
|
} catch (ValidationException) {
|
|
// Feld bleibt invalid — Error-Bag wird automatisch befüllt.
|
|
}
|
|
}
|
|
|
|
protected function syncScheduledAt(): void
|
|
{
|
|
if ($this->publishMode !== 'scheduled') {
|
|
$this->scheduledAt = null;
|
|
|
|
return;
|
|
}
|
|
|
|
if (blank($this->scheduledDate) && blank($this->scheduledTime) && filled($this->scheduledAt)) {
|
|
$scheduledAt = Carbon::parse($this->scheduledAt);
|
|
$this->scheduledDate = $scheduledAt->format('Y-m-d');
|
|
$this->scheduledTime = $scheduledAt->format('H:i');
|
|
|
|
return;
|
|
}
|
|
|
|
if (blank($this->scheduledDate) || blank($this->scheduledTime)) {
|
|
$this->scheduledAt = null;
|
|
|
|
return;
|
|
}
|
|
|
|
$this->scheduledAt = "{$this->scheduledDate}T{$this->scheduledTime}";
|
|
}
|
|
|
|
/**
|
|
* Parst den erfassten Termin in der Anwendungs-Zeitzone (Europe/Berlin).
|
|
* Null, wenn kein Termin gesetzt ist.
|
|
*/
|
|
protected function resolveScheduledAt(): ?Carbon
|
|
{
|
|
if (blank($this->scheduledAt)) {
|
|
return null;
|
|
}
|
|
|
|
return Carbon::parse($this->scheduledAt);
|
|
}
|
|
|
|
protected function validateScheduledAtWhenReady(): void
|
|
{
|
|
if (blank($this->scheduledAt)) {
|
|
return;
|
|
}
|
|
|
|
$this->resetErrorBag('scheduledAt');
|
|
|
|
$scheduledAt = $this->resolveScheduledAt();
|
|
|
|
if ($scheduledAt === null || $scheduledAt->lessThanOrEqualTo(now()->addMinutes(5))) {
|
|
$this->addError('scheduledAt', __('Der Veröffentlichungstermin muss mindestens 5 Minuten in der Zukunft liegen.'));
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$this->validateOnly('scheduledAt', $this->formRules());
|
|
} catch (ValidationException) {
|
|
// Termin bleibt invalid — Error-Bag wird automatisch befüllt.
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Toast mit Sammelhinweis nach fehlgeschlagener Validierung. Nennt die
|
|
* betroffenen Felder beim Namen (aus validation.attributes), damit der
|
|
* User nicht raten muss, wo es hakt. Die einzelnen Feld-Errors werden
|
|
* weiterhin direkt am Input angezeigt, zusätzlich rendert
|
|
* [[x-portal.pr-error-summary]] eine persistente Übersicht im Formular.
|
|
*/
|
|
protected function notifyValidationError(?ValidationException $exception = null): void
|
|
{
|
|
$fieldKeys = $exception
|
|
? array_keys($exception->errors())
|
|
: $this->getErrorBag()->keys();
|
|
|
|
$fieldLabels = collect($fieldKeys)
|
|
->map(fn (string $key): string => trans()->has('validation.attributes.'.$key)
|
|
? __('validation.attributes.'.$key)
|
|
: $key)
|
|
->unique()
|
|
->values();
|
|
|
|
Flux::toast(
|
|
heading: __('Bitte Eingaben prüfen'),
|
|
text: $fieldLabels->isNotEmpty()
|
|
? __('Bitte prüfe: :fields', ['fields' => $fieldLabels->join(', ')])
|
|
: __('Ein Feld benötigt deine Aufmerksamkeit.'),
|
|
variant: 'danger',
|
|
duration: 8000,
|
|
);
|
|
}
|
|
}
|