Files
core/src/Privacy/RelationManagers/UserRelationManager.php
T

113 lines
5.2 KiB
PHP

<?php
namespace Modules\Core\Privacy\RelationManagers;
use Filament\Actions\Action;
use Filament\Forms\Components\Checkbox;
use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Notifications\Notification;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Model;
use Modules\Core\Customer\RelationManagers\UserRelationManager as CoreUserRelationManager;
use Modules\Core\Privacy\Models\DataErasureRequest;
use Modules\Core\Privacy\Services\PrivacyService;
/**
* Extends core's own Customer -> User relation manager to add:
* - a "Privacy Requests" row action, since a User's erasure/export requests
* can't be shown as a nested relation manager two levels deep (Customer ->
* User -> Requests isn't a shape Filament relation managers support) — a
* modal listing that specific User's requests is the practical alternative.
* - a "Request Erasure" row action — the User-scoped panel entry point,
* mirroring Modules\Core\Privacy\Filament\Extensions\
* CustomerErasureActionsExtension on the Customer side, including the same
* "erase immediately" checkbox for a staff-triggered urgent request.
* See docs/privacy.md "User-scope vs Customer-scope".
*/
class UserRelationManager extends CoreUserRelationManager
{
public function getDefaultTable(Table $table): Table
{
$table = parent::getDefaultTable($table);
return $table->recordActions([
...$table->getActions(),
Action::make('privacyRequests')
->label('Privacy Requests')
->icon('heroicon-o-shield-exclamation')
->modalHeading(fn (Model $record) => "Privacy requests for {$record->name}")
->modalSubmitAction(false)
->modalCancelActionLabel('Close')
->schema(fn (Model $record) => $this->requestsInfolist($record)),
Action::make('requestErasure')
->label('Request Erasure')
->icon('heroicon-o-shield-exclamation')
->color('danger')
->requiresConfirmation()
->modalDescription('Opens a cancellable grace-period erasure request for this individual — deactivates their login and detaches them from every linked Customer account once it completes. No Customer account\'s own data is affected.')
->schema([
Checkbox::make('immediate')
->label('Erase immediately (skip the 30-day grace period)')
->helperText('Staff-only, for a formal legal request or regulator inquiry that genuinely requires urgency — not a routine deletion. Runs synchronously, cannot be cancelled once submitted.')
->default(false),
])
->action(function (Model $record, array $data) {
$privacyService = app(PrivacyService::class);
$staff = auth('staff')->user();
if ($data['immediate']) {
$privacyService->requestImmediateErasureForUser($record, $staff);
Notification::make()
->title('User erased')
->body('Erasure ran immediately — see the Erasure Requests list for the outcome.')
->success()
->send();
return;
}
$privacyService->requestErasureForUser($record, $staff);
Notification::make()
->title('Erasure requested')
->body('The grace period starts now, and this user\'s login is deactivated immediately — see the Erasure Requests list.')
->success()
->send();
}),
]);
}
private function requestsInfolist(Model $record): array
{
return [
TextEntry::make('erasure_heading')
->label('')
->state('Erasure Requests'),
RepeatableEntry::make('erasureRequests')
->label('')
->state(fn () => $record->erasureRequests()->latest()->get())
->schema([
TextEntry::make('status')->formatStateUsing(fn ($state) => ucfirst($state->value)),
TextEntry::make('scheduled_for')->dateTime(),
TextEntry::make('requestedBy')->label('Requested by')->state(
fn (DataErasureRequest $record) => DataErasureRequest::displayNameFor($record->requestedBy)
),
])
->columns(3),
TextEntry::make('export_heading')
->label('')
->state('Export Requests'),
RepeatableEntry::make('exportRequests')
->label('')
->state(fn () => $record->exportRequests()->latest()->get())
->schema([
TextEntry::make('status')->formatStateUsing(fn ($state) => ucfirst($state->value)),
TextEntry::make('created_at')->label('Requested at')->dateTime(),
])
->columns(2),
];
}
}