feat: moderation
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources\ModerationCases\Tables;
|
||||
|
||||
use App\Enums\ModerationCaseStatus;
|
||||
use App\Enums\ReportStatus;
|
||||
use App\Models\MealPosts;
|
||||
use App\Models\ModerationCase;
|
||||
use App\Models\User;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\EditAction;
|
||||
use Filament\Actions\ViewAction;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class ModerationCasesTable
|
||||
{
|
||||
public static function configure(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->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', [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'),
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user