Files
fotospiel-app/app/Filament/Resources/TenantAnnouncementResource.php
Codex Agent 8f13465415
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Implement tenant announcements and audit log fixes
2026-01-02 14:19:46 +01:00

256 lines
10 KiB
PHP

<?php
namespace App\Filament\Resources;
use App\Enums\TenantAnnouncementAudience;
use App\Enums\TenantAnnouncementSegment;
use App\Enums\TenantAnnouncementStatus;
use App\Filament\Clusters\RareAdmin\RareAdminCluster;
use App\Filament\Resources\TenantAnnouncementResource\Pages;
use App\Models\TenantAnnouncement;
use App\Services\Audit\SuperAdminAuditLogger;
use BackedEnum;
use Filament\Forms\Components\CheckboxList;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Resources\Resource;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Components\Utilities\Get;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use UnitEnum;
class TenantAnnouncementResource extends Resource
{
protected static ?string $model = TenantAnnouncement::class;
protected static ?string $cluster = RareAdminCluster::class;
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-megaphone';
protected static ?string $recordTitleAttribute = 'title';
protected static ?int $navigationSort = 70;
public static function getNavigationGroup(): UnitEnum|string|null
{
return __('admin.nav.platform');
}
public static function form(Schema $schema): Schema
{
$statusOptions = collect(TenantAnnouncementStatus::cases())
->mapWithKeys(fn (TenantAnnouncementStatus $status) => [$status->value => $status->label()])
->all();
$audienceOptions = collect(TenantAnnouncementAudience::cases())
->mapWithKeys(fn (TenantAnnouncementAudience $audience) => [$audience->value => $audience->label()])
->all();
$segmentOptions = collect(TenantAnnouncementSegment::cases())
->mapWithKeys(fn (TenantAnnouncementSegment $segment) => [$segment->value => $segment->label()])
->all();
return $schema
->schema([
Section::make('Inhalt')
->schema([
TextInput::make('title')
->label('Titel')
->required()
->maxLength(160),
Textarea::make('body')
->label('Text')
->rows(6)
->required()
->columnSpanFull(),
TextInput::make('cta_label')
->label('CTA-Label')
->maxLength(160),
TextInput::make('cta_url')
->label('CTA-Link')
->maxLength(255)
->url()
->nullable(),
])
->columns(2),
Section::make('Zielgruppe')
->schema([
Select::make('audience')
->label('Zielgruppe')
->options($audienceOptions)
->default(TenantAnnouncementAudience::ALL->value)
->live()
->required(),
Select::make('tenants')
->label('Mandanten')
->relationship('tenants', 'name')
->multiple()
->preload()
->searchable()
->visible(fn (Get $get): bool => $get('audience') === TenantAnnouncementAudience::TENANTS->value)
->dehydrated(fn (Get $get): bool => $get('audience') === TenantAnnouncementAudience::TENANTS->value)
->required(fn (Get $get): bool => $get('audience') === TenantAnnouncementAudience::TENANTS->value)
->columnSpanFull(),
CheckboxList::make('segments')
->label('Segmente')
->options($segmentOptions)
->columns(2)
->default([])
->visible(fn (Get $get): bool => $get('audience') === TenantAnnouncementAudience::SEGMENTS->value)
->dehydrated(fn (Get $get): bool => $get('audience') === TenantAnnouncementAudience::SEGMENTS->value)
->required(fn (Get $get): bool => $get('audience') === TenantAnnouncementAudience::SEGMENTS->value)
->columnSpanFull(),
Toggle::make('email_enabled')
->label('E-Mail versenden')
->default(true),
])
->columns(2),
Section::make('Zeitplan')
->schema([
Select::make('status')
->label('Status')
->options($statusOptions)
->default(TenantAnnouncementStatus::DRAFT->value)
->live()
->required(),
DateTimePicker::make('starts_at')
->label('Startet am')
->seconds(false)
->nullable()
->required(fn (Get $get): bool => $get('status') === TenantAnnouncementStatus::SCHEDULED->value),
DateTimePicker::make('ends_at')
->label('Endet am')
->seconds(false)
->nullable(),
])
->columns(2),
])
->columns(1);
}
public static function table(Table $table): Table
{
$statusOptions = collect(TenantAnnouncementStatus::cases())
->mapWithKeys(fn (TenantAnnouncementStatus $status) => [$status->value => $status->label()])
->all();
$audienceOptions = collect(TenantAnnouncementAudience::cases())
->mapWithKeys(fn (TenantAnnouncementAudience $audience) => [$audience->value => $audience->label()])
->all();
return $table
->columns([
Tables\Columns\TextColumn::make('title')
->label('Titel')
->searchable()
->sortable()
->limit(50),
Tables\Columns\TextColumn::make('status')
->label('Status')
->badge()
->formatStateUsing(function ($state): string {
if ($state instanceof TenantAnnouncementStatus) {
return $state->label();
}
return TenantAnnouncementStatus::tryFrom((string) $state)?->label() ?? (string) $state;
})
->sortable(),
Tables\Columns\TextColumn::make('audience')
->label('Zielgruppe')
->badge()
->formatStateUsing(function ($state): string {
if ($state instanceof TenantAnnouncementAudience) {
return $state->label();
}
return TenantAnnouncementAudience::tryFrom((string) $state)?->label() ?? (string) $state;
})
->sortable(),
Tables\Columns\IconColumn::make('email_enabled')
->label('E-Mail')
->boolean(),
Tables\Columns\TextColumn::make('starts_at')
->label('Start')
->dateTime()
->sortable(),
Tables\Columns\TextColumn::make('ends_at')
->label('Ende')
->dateTime()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
Tables\Columns\TextColumn::make('updated_at')
->label('Aktualisiert')
->since()
->sortable()
->toggleable(isToggledHiddenByDefault: true),
])
->filters([
Tables\Filters\SelectFilter::make('status')
->label('Status')
->options($statusOptions),
Tables\Filters\SelectFilter::make('audience')
->label('Zielgruppe')
->options($audienceOptions),
])
->actions([
Tables\Actions\EditAction::make()
->after(fn (array $data, TenantAnnouncement $record) => app(SuperAdminAuditLogger::class)->recordModelMutation(
'updated',
$record,
SuperAdminAuditLogger::fieldsMetadata($data),
static::class
)),
])
->bulkActions([
Tables\Actions\DeleteBulkAction::make()
->after(function (Collection $records): void {
$logger = app(SuperAdminAuditLogger::class);
foreach ($records as $record) {
$logger->recordModelMutation(
'deleted',
$record,
source: static::class
);
}
}),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListTenantAnnouncements::route('/'),
'create' => Pages\CreateTenantAnnouncement::route('/create'),
'edit' => Pages\EditTenantAnnouncement::route('/{record}/edit'),
];
}
public static function mutateFormDataBeforeCreate(array $data): array
{
if ($userId = Auth::id()) {
$data['created_by'] = $userId;
$data['updated_by'] = $userId;
}
return $data;
}
public static function mutateFormDataBeforeSave(array $data): array
{
if ($userId = Auth::id()) {
$data['updated_by'] = $userId;
}
return $data;
}
}