query( NotificationLog::query()->latest() ) ->columns([ TextColumn::make('created_at') ->label(__('Date')) ->dateTime('M d, H:i') ->sortable(), TextColumn::make('type') ->label(__('Type')) ->badge() ->formatStateUsing(fn (string $state): string => match ($state) { 'ssl_errors' => __('SSL'), 'backup_failures' => __('Backup'), 'disk_quota' => __('Quota'), 'login_failures' => __('Login'), 'ssh_logins' => __('SSH'), 'system_updates' => __('Updates'), 'service_health' => __('Health'), 'high_load' => __('Load'), 'test' => __('Test'), default => ucfirst(str_replace('_', ' ', $state)), }) ->color(fn (string $state): string => match ($state) { 'ssl_errors' => 'warning', 'backup_failures' => 'danger', 'disk_quota' => 'warning', 'login_failures' => 'danger', 'ssh_logins' => 'info', 'system_updates' => 'info', 'service_health' => 'primary', 'high_load' => 'danger', 'test' => 'success', default => 'gray', }), TextColumn::make('subject') ->label(__('Subject')) ->limit(40) ->tooltip(fn (NotificationLog $record): string => $record->subject) ->searchable(), TextColumn::make('recipients') ->label(__('Recipients')) ->formatStateUsing(function ($state): string { if (is_string($state)) { $state = json_decode($state, true) ?? []; } return is_array($state) && count($state) > 0 ? implode(', ', $state) : '-'; }) ->limit(30) ->color('gray'), IconColumn::make('status') ->label(__('Status')) ->icon(fn (string $state): string => match ($state) { 'sent' => 'heroicon-o-check-circle', 'failed' => 'heroicon-o-x-circle', 'skipped' => 'heroicon-o-minus-circle', default => 'heroicon-o-question-mark-circle', }) ->color(fn (string $state): string => match ($state) { 'sent' => 'success', 'failed' => 'danger', 'skipped' => 'gray', default => 'gray', }) ->tooltip(fn (NotificationLog $record): ?string => $record->error), ]) ->filters([ SelectFilter::make('status') ->label(__('Status')) ->options([ 'sent' => __('Sent'), 'failed' => __('Failed'), 'skipped' => __('Skipped'), ]), SelectFilter::make('type') ->label(__('Type')) ->options([ 'ssl_errors' => __('SSL Certificate'), 'backup_failures' => __('Backup'), 'disk_quota' => __('Disk Quota'), 'login_failures' => __('Login Failure'), 'ssh_logins' => __('SSH Login'), 'system_updates' => __('System Updates'), 'service_health' => __('Service Health'), 'high_load' => __('High Load'), 'test' => __('Test'), ]), ]) ->actions([ Action::make('view') ->label(__('View')) ->icon('heroicon-o-eye') ->color('gray') ->modalHeading(fn (NotificationLog $record): string => $record->subject) ->modalContent(fn (NotificationLog $record) => view('filament.admin.components.notification-log-detail', ['record' => $record])) ->modalSubmitAction(false) ->modalCancelActionLabel(__('Close')), ]) ->headerActions([ Action::make('cleanup') ->label(__('Clear Old Logs')) ->icon('heroicon-o-trash') ->color('danger') ->requiresConfirmation() ->modalHeading(__('Clear Old Notification Logs')) ->modalDescription(__('This will delete all notification logs older than 30 days. This action cannot be undone.')) ->action(function (): void { $deleted = NotificationLog::cleanup(30); Notification::make() ->title(__('Logs cleaned up')) ->body(__(':count old log entries deleted', ['count' => $deleted])) ->success() ->send(); $this->resetTable(); }), ]) ->defaultSort('created_at', 'desc') ->paginated([10, 25, 50]) ->striped() ->emptyStateHeading(__('No notification logs')) ->emptyStateDescription(__('Sent notifications will appear here.')) ->emptyStateIcon('heroicon-o-bell-slash'); } public function render() { return $this->getTable()->render(); } }