label(__('panel.reviews.actions.request')) ->icon('heroicon-o-hand-raised') ->color('warning') ->visible(fn (Model $record) => ! $record->isAwaitingReview() && ! $record->isPublishedNow() && Auth::user()?->can('update', $record) && Auth::user()?->cannot(ReviewTarget::permission($record))) ->modalHeading(__('panel.reviews.actions.request_heading')) ->modalDescription(__('panel.reviews.actions.request_hint')) ->schema([ Select::make('reviewer_id') ->label(__('panel.reviews.fields.reviewer')) ->helperText(__('panel.reviews.fields.reviewer_hint')) ->options(fn (Model $record) => self::reviewers($record)) // Nobody in particular is a real answer, and the common // one: the author usually has no idea who is free. ->placeholder(__('panel.reviews.anyone')), Textarea::make('note') ->label(__('panel.reviews.fields.note')) ->helperText(__('panel.reviews.fields.note_hint')) ->rows(3) ->maxLength(2000), ]) ->action(function (Model $record, array $data): void { $record->requestReview( $data['reviewer_id'] ? User::find($data['reviewer_id']) : null, $data['note'] ?: null, ); Notification::make() ->title(__('panel.reviews.requested')) ->success() ->send(); }); } /** * The same "this is ready", for a whole selection at once. * * The visibility check reads the raw permission string rather than * asking the model's policy, and deliberately so: `update` on a page is * not a plain permission check, it also lets an author edit their own * draft without the blanket ability — a nuance that means something for * one record and nothing for a bulk action, and a policy call with no * record to test it against does not even have a record to fall back to. * The permission behind the ability is what a role actually holds, which * is the question a button's visibility needs answered. * * What does vary per record is whether asking makes sense at all, so the * action itself quietly skips anything already awaiting review or * already live rather than erroring on it — a selection made across a * whole page of mixed states is the normal case, not an edge one. * * @param class-string $model */ public static function requestBulk(string $model): BulkAction { $table = (new $model)->getTable(); return BulkAction::make('request_review_bulk') ->label(__('panel.reviews.actions.request_bulk')) ->icon('heroicon-o-hand-raised') ->color('warning') ->visible(fn () => (Auth::user()?->can("{$table}.update") ?? false) && (Auth::user()?->cannot("{$table}.publish") ?? true)) ->deselectRecordsAfterCompletion() ->action(function (Collection $records): void { $requested = 0; foreach ($records as $record) { if ($record->isAwaitingReview() || $record->isPublishedNow()) { continue; } $record->requestReview(); $requested++; } Notification::make() ->title(trans_choice('panel.reviews.requested_count', $requested, ['count' => $requested])) ->success() ->send(); }); } /** * "Not yet, and here is why." The reason is required: work handed back * without one is what turns a review step into something people route * around. */ public static function returnForChanges(): Action { return Action::make('return_review') ->label(__('panel.reviews.actions.return')) ->icon('heroicon-o-arrow-uturn-left') ->color('danger') ->visible(fn (Model $record) => $record->isAwaitingReview() && Auth::user()?->can(ReviewTarget::permission($record))) ->modalHeading(__('panel.reviews.actions.return_heading')) ->schema([ Textarea::make('note') ->label(__('panel.reviews.fields.reason')) ->helperText(__('panel.reviews.fields.reason_hint')) ->rows(3) ->required() ->maxLength(2000), ]) ->action(function (Model $record, array $data): void { $record->returnForChanges($data['note']); Notification::make() ->title(__('panel.reviews.returned')) ->success() ->send(); }); } /** * Who could actually answer this, as a list to choose from. * * Resolved through `can()` rather than a permission query so super admins * appear: they hold no explicit permissions, only the gate that grants * them everything, and leaving them out of the list would hide the person * most likely to be able to help. * * @return array */ protected static function reviewers(Model $record): array { $permission = ReviewTarget::permission($record); return User::query() ->where('is_active', true) ->whereKeyNot(Auth::id()) ->orderBy('name') ->get() ->filter(fn (User $user) => $user->can($permission)) ->pluck('name', 'id') ->all(); } }