columns([ TextColumn::make('id') ->label(__('admin.moderation_cases.fields.id')) ->copyable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('target_type') ->label(__('admin.moderation_cases.fields.target_type')) ->state(fn (ModerationCase $record): string => $record->targetLabel()) ->badge(), TextColumn::make('target_title') ->label(__('admin.moderation_cases.fields.target_title')) ->state(fn (ModerationCase $record): string => $record->targetTitle()) ->searchable(query: function (Builder $query, string $search): Builder { return $query->where(function (Builder $query) use ($search): void { $query ->whereHasMorph('caseable', [MealPosts::class], fn (Builder $query): Builder => $query->where('title', 'like', "%{$search}%")) ->orWhereHasMorph('caseable', [PostReviews::class], fn (Builder $query): Builder => $query->where('comment', 'like', "%{$search}%")) ->orWhereHasMorph('caseable', [User::class], fn (Builder $query): Builder => $query->where('name', 'like', "%{$search}%")); }); }), TextColumn::make('status') ->label(__('admin.moderation_cases.fields.status')) ->badge() ->sortable(), TextColumn::make('reports_count') ->label(__('admin.moderation_cases.fields.reports_count')) ->numeric() ->sortable(), TextColumn::make('threshold') ->label(__('admin.moderation_cases.fields.threshold')) ->numeric() ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('threshold_notified_at') ->label(__('admin.moderation_cases.fields.threshold_notified_at')) ->dateTime() ->sortable() ->placeholder(__('admin.moderation_cases.placeholders.empty')), TextColumn::make('assignedTo.name') ->label(__('admin.moderation_cases.fields.assigned_to')) ->placeholder(__('admin.moderation_cases.placeholders.empty')) ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('resolved_at') ->label(__('admin.moderation_cases.fields.resolved_at')) ->dateTime() ->sortable() ->placeholder(__('admin.moderation_cases.placeholders.empty')), TextColumn::make('created_at') ->label(__('admin.moderation_cases.fields.created_at')) ->dateTime() ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ SelectFilter::make('status') ->label(__('admin.moderation_cases.fields.status')) ->options(ModerationCaseStatus::class), SelectFilter::make('caseable_type') ->label(__('admin.moderation_cases.filters.target_type')) ->options([ MealPosts::class => __('admin.moderation_cases.targets.meal_post'), PostReviews::class => __('admin.moderation_cases.targets.post_review'), User::class => __('admin.moderation_cases.targets.user'), ]), ]) ->recordActions([ ViewAction::make(), EditAction::make(), Action::make('markReviewing') ->label(__('admin.moderation_cases.actions.mark_reviewing')) ->visible(fn (ModerationCase $record): bool => $record->status !== ModerationCaseStatus::REVIEWING) ->action(fn (ModerationCase $record): bool => self::markReviewing($record)), Action::make('hideMeal') ->label(__('admin.moderation_cases.actions.hide_meal')) ->color('warning') ->requiresConfirmation() ->visible(fn (ModerationCase $record): bool => $record->caseable_type === MealPosts::class && $record->caseable instanceof MealPosts && ! $record->caseable->isHidden()) ->action(fn (ModerationCase $record): bool => self::hideMeal($record)), Action::make('suspendUser') ->label(__('admin.moderation_cases.actions.suspend_user')) ->color('danger') ->requiresConfirmation() ->visible(fn (ModerationCase $record): bool => $record->caseable_type === User::class && $record->caseable instanceof User && ! $record->caseable->isSuspended()) ->action(fn (ModerationCase $record): bool => self::suspendUser($record)), Action::make('resolve') ->label(__('admin.moderation_cases.actions.resolve')) ->color('success') ->requiresConfirmation() ->visible(fn (ModerationCase $record): bool => $record->status !== ModerationCaseStatus::RESOLVED) ->action(fn (ModerationCase $record): bool => self::closeCase($record, ModerationCaseStatus::RESOLVED, ReportStatus::RESOLVED)), Action::make('reject') ->label(__('admin.moderation_cases.actions.reject')) ->color('gray') ->requiresConfirmation() ->visible(fn (ModerationCase $record): bool => $record->status !== ModerationCaseStatus::REJECTED) ->action(fn (ModerationCase $record): bool => self::closeCase($record, ModerationCaseStatus::REJECTED, ReportStatus::REJECTED)), ]) ->toolbarActions([ // ]); } private static function markReviewing(ModerationCase $record): bool { $record->forceFill([ 'status' => ModerationCaseStatus::REVIEWING, 'assigned_to' => $record->assigned_to ?: auth()->id(), ])->save(); $record->reports() ->where('status', ReportStatus::OPEN->value) ->update([ 'status' => ReportStatus::REVIEWING->value, 'reviewed_by' => auth()->id(), 'reviewed_at' => now(), ]); return true; } private static function hideMeal(ModerationCase $record): bool { $mealPost = $record->caseable; if (! $mealPost instanceof MealPosts) { return false; } $mealPost->forceFill([ 'hidden_at' => now(), 'hidden_by' => auth()->id(), 'hidden_reason' => __('admin.moderation_cases.actions.hide_meal'), ])->save(); return self::markReviewing($record); } private static function suspendUser(ModerationCase $record): bool { $user = $record->caseable; if (! $user instanceof User) { return false; } $user->forceFill([ 'suspended_at' => now(), 'suspended_by' => auth()->id(), 'suspended_reason' => __('admin.moderation_cases.actions.suspend_user'), ])->save(); return self::markReviewing($record); } private static function closeCase( ModerationCase $record, ModerationCaseStatus $caseStatus, ReportStatus $reportStatus, ): bool { $record->forceFill([ 'status' => $caseStatus, 'assigned_to' => $record->assigned_to ?: auth()->id(), 'resolved_by' => auth()->id(), 'resolved_at' => now(), ])->save(); $record->reports()->update([ 'status' => $reportStatus->value, 'reviewed_by' => auth()->id(), 'reviewed_at' => now(), ]); return true; } }