defaultSort('created_at', 'desc') ->columns([ TextColumn::make('name') ->label(__('panel.applications.fields.name')) ->weight('medium') ->description(fn (Application $record) => $record->email) ->searchable(['name', 'email']), TextColumn::make('kind') ->label(__('panel.applications.fields.kind')) ->badge() ->sortable(), TextColumn::make('call_title') ->label(__('panel.applications.fields.subject')) ->state(fn (Application $record) => $record->subject()) ->color(fn (Application $record) => $record->call_id ? null : 'gray') ->wrap() ->toggleable(), TextColumn::make('hours_per_week') ->label(__('panel.applications.fields.hours_per_week')) ->badge() ->color('info') ->formatStateUsing(fn (?int $state) => $state ? "{$state} h" : '—') ->toggleable(), TextColumn::make('status') ->label(__('panel.applications.fields.status')) ->badge() ->sortable(), TextColumn::make('created_at') ->label(__('panel.applications.fields.received_at')) ->since() ->dateTimeTooltip() ->sortable(), TextColumn::make('handler.name') ->label(__('panel.applications.fields.handled_by')) ->placeholder('—') ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ // No default here: the tabs above already decide what is in // view, and a filter with a default would silently empty the // spam tab. SelectFilter::make('status') ->label(__('panel.applications.fields.status')) ->options(ApplicationStatus::options()) ->multiple(), SelectFilter::make('kind') ->label(__('panel.applications.fields.kind')) ->options(ApplicationKind::options()), SelectFilter::make('call') ->label(__('panel.applications.fields.call')) ->relationship('call', 'title->'.config('locales.default')) ->searchable() ->preload(), TrashedFilter::make(), ]) ->recordActions([ ViewAction::make(), ActionGroup::make(static::statusActions()) ->label(__('panel.applications.actions.group')) ->icon('heroicon-m-ellipsis-horizontal'), ]) ->toolbarActions([ BulkActionGroup::make([ BulkAction::make('mark_spam') ->label(__('panel.applications.actions.spam')) ->icon('heroicon-o-no-symbol') ->color('danger') ->requiresConfirmation() ->authorize(fn () => (bool) Auth::user()?->can('applications.update')) ->action(fn (Collection $records) => $records->each( fn (Application $record) => $record->markAs(ApplicationStatus::Spam, Auth::user()), )) ->deselectRecordsAfterCompletion(), DeleteBulkAction::make(), ForceDeleteBulkAction::make(), RestoreBulkAction::make(), ]), ]) ->emptyStateHeading(__('panel.applications.empty.heading')) ->emptyStateDescription(__('panel.applications.empty.description')) ->emptyStateIcon('heroicon-o-inbox'); } /** * The status transitions, shared by the list and the record page so the * two cannot drift apart. * * @return array */ public static function statusActions(): array { $styles = [ ApplicationStatus::Contacted->value => ['heroicon-o-chat-bubble-left-right', 'info'], ApplicationStatus::Accepted->value => ['heroicon-o-check-circle', 'success'], ApplicationStatus::Declined->value => ['heroicon-o-x-circle', 'gray'], ApplicationStatus::Spam->value => ['heroicon-o-no-symbol', 'danger'], ]; $actions = []; foreach ($styles as $status => [$icon, $color]) { $target = ApplicationStatus::from($status); $actions[] = Action::make("mark_{$status}") ->label(__("panel.applications.actions.{$status}")) ->icon($icon) ->color($color) ->requiresConfirmation($target === ApplicationStatus::Spam) ->authorize('update') ->visible(fn (Application $record) => $record->status !== $target) ->action(fn (Application $record) => $record->markAs($target, Auth::user())); } return $actions; } }