|null */ private ?array $visibleTenants = null; private ?Workspace $workspace = null; public string $reasonFilter = FindingAssignmentHygieneService::FILTER_ALL; public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport) ->satisfy(ActionSurfaceSlot::ListHeader, 'Header controls keep the hygiene scope fixed and expose only fixed reason views plus tenant-prefilter recovery when needed.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The hygiene report stays read-only and exposes row click as the only inspect path.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The hygiene report does not expose bulk actions.') ->satisfy(ActionSurfaceSlot::ListEmptyState, 'The empty state stays calm and only offers a tenant-prefilter reset when the active tenant filter hides otherwise visible issues.') ->exempt(ActionSurfaceSlot::DetailHeader, 'Repair remains on the existing tenant finding detail surface.'); } public function mount(): void { $this->reasonFilter = $this->resolveRequestedReasonFilter(); $this->authorizePageAccess(); app(CanonicalAdminTenantFilterState::class)->sync( $this->getTableFiltersSessionKey(), [], request(), ); $this->applyRequestedTenantPrefilter(); $this->mountInteractsWithTable(); $this->normalizeTenantFilterState(); } protected function getHeaderActions(): array { return [ Action::make('clear_tenant_filter') ->label('Clear tenant filter') ->icon('heroicon-o-x-mark') ->color('gray') ->visible(fn (): bool => $this->currentTenantFilterId() !== null) ->action(fn (): mixed => $this->clearTenantFilter()), ]; } public function table(Table $table): Table { return $table ->query(fn (): Builder => $this->issueBaseQuery()) ->paginated(TablePaginationProfiles::customPage()) ->persistFiltersInSession() ->columns([ TextColumn::make('tenant.name') ->label('Tenant'), TextColumn::make('subject_display_name') ->label('Finding') ->state(fn (Finding $record): string => $record->resolvedSubjectDisplayName() ?? 'Finding #'.$record->getKey()) ->wrap(), TextColumn::make('owner') ->label('Owner') ->state(fn (Finding $record): string => FindingResource::accountableOwnerDisplayFor($record)), TextColumn::make('assignee') ->label('Assignee') ->state(fn (Finding $record): string => FindingResource::activeAssigneeDisplayFor($record)) ->description(fn (Finding $record): ?string => $this->assigneeContext($record)), TextColumn::make('due_at') ->label('Due') ->dateTime() ->placeholder('—') ->description(fn (Finding $record): ?string => FindingExceptionResource::relativeTimeDescription($record->due_at) ?? FindingResource::dueAttentionLabelFor($record)), TextColumn::make('hygiene_reasons') ->label('Hygiene reason') ->state(fn (Finding $record): string => implode(', ', $this->hygieneService()->reasonLabelsFor($record))) ->wrap(), TextColumn::make('last_workflow_activity') ->label('Last workflow activity') ->state(fn (Finding $record): mixed => $this->hygieneService()->lastWorkflowActivityAt($record)) ->dateTime() ->placeholder('—') ->description(fn (Finding $record): ?string => FindingExceptionResource::relativeTimeDescription($this->hygieneService()->lastWorkflowActivityAt($record))), ]) ->filters([ SelectFilter::make('tenant_id') ->label('Tenant') ->options(fn (): array => $this->tenantFilterOptions()) ->searchable(), ]) ->actions([]) ->bulkActions([]) ->recordUrl(fn (Finding $record): string => $this->findingDetailUrl($record)) ->emptyStateHeading(fn (): string => $this->emptyState()['title']) ->emptyStateDescription(fn (): string => $this->emptyState()['body']) ->emptyStateIcon(fn (): string => $this->emptyState()['icon']) ->emptyStateActions($this->emptyStateActions()); } /** * @return array */ public function appliedScope(): array { $tenant = $this->filteredTenant(); return [ 'workspace_scoped' => true, 'fixed_scope' => 'visible_findings_hygiene_only', 'reason_filter' => $this->currentReasonFilter(), 'reason_filter_label' => $this->hygieneService()->filterLabel($this->currentReasonFilter()), 'tenant_prefilter_source' => $this->tenantPrefilterSource(), 'tenant_label' => $tenant?->name, ]; } /** * @return array> */ public function availableFilters(): array { return [ [ 'key' => 'hygiene_scope', 'label' => 'Findings hygiene only', 'fixed' => true, 'options' => [], ], [ 'key' => 'tenant', 'label' => 'Tenant', 'fixed' => false, 'options' => collect($this->visibleTenants()) ->map(fn (Tenant $tenant): array => [ 'value' => (string) $tenant->getKey(), 'label' => (string) $tenant->name, ]) ->values() ->all(), ], ]; } /** * @return array> */ public function availableReasonFilters(): array { $summary = $this->summaryCounts(); $currentFilter = $this->currentReasonFilter(); return [ [ 'key' => FindingAssignmentHygieneService::FILTER_ALL, 'label' => 'All issues', 'active' => $currentFilter === FindingAssignmentHygieneService::FILTER_ALL, 'badge_count' => $summary['unique_issue_count'], 'url' => $this->reportUrl(['reason' => null]), ], [ 'key' => FindingAssignmentHygieneService::REASON_BROKEN_ASSIGNMENT, 'label' => 'Broken assignment', 'active' => $currentFilter === FindingAssignmentHygieneService::REASON_BROKEN_ASSIGNMENT, 'badge_count' => $summary['broken_assignment_count'], 'url' => $this->reportUrl(['reason' => FindingAssignmentHygieneService::REASON_BROKEN_ASSIGNMENT]), ], [ 'key' => FindingAssignmentHygieneService::REASON_STALE_IN_PROGRESS, 'label' => 'Stale in progress', 'active' => $currentFilter === FindingAssignmentHygieneService::REASON_STALE_IN_PROGRESS, 'badge_count' => $summary['stale_in_progress_count'], 'url' => $this->reportUrl(['reason' => FindingAssignmentHygieneService::REASON_STALE_IN_PROGRESS]), ], ]; } /** * @return array{unique_issue_count: int, broken_assignment_count: int, stale_in_progress_count: int} */ public function summaryCounts(): array { $workspace = $this->workspace(); $user = auth()->user(); if (! $workspace instanceof Workspace || ! $user instanceof User) { return [ 'unique_issue_count' => 0, 'broken_assignment_count' => 0, 'stale_in_progress_count' => 0, ]; } return $this->hygieneService()->summary( $workspace, $user, $this->currentTenantFilterId(), ); } /** * @return array */ public function emptyState(): array { if ($this->tenantFilterAloneExcludesRows()) { return [ 'title' => 'No hygiene issues match this tenant scope', 'body' => 'Your current tenant filter is hiding hygiene issues that are still visible elsewhere in this workspace.', 'icon' => 'heroicon-o-funnel', 'action_name' => 'clear_tenant_filter_empty', 'action_label' => 'Clear tenant filter', 'action_kind' => 'clear_tenant_filter', ]; } if ($this->reasonFilterAloneExcludesRows()) { return [ 'title' => 'No findings match this hygiene reason', 'body' => 'The current fixed reason view is narrower than the visible issue set in this workspace.', 'icon' => 'heroicon-o-adjustments-horizontal', ]; } return [ 'title' => 'No visible hygiene issues right now', 'body' => 'Visible broken assignments and stale in-progress work are currently calm across the entitled tenant scope.', 'icon' => 'heroicon-o-wrench-screwdriver', ]; } public function updatedTableFilters(): void { $this->normalizeTenantFilterState(); } public function clearTenantFilter(): void { $this->removeTableFilter('tenant_id'); $this->resetTable(); } /** * @return array */ public function visibleTenants(): array { if ($this->visibleTenants !== null) { return $this->visibleTenants; } $workspace = $this->workspace(); $user = auth()->user(); if (! $workspace instanceof Workspace || ! $user instanceof User) { return $this->visibleTenants = []; } return $this->visibleTenants = $this->hygieneService()->visibleTenants($workspace, $user); } private function authorizePageAccess(): void { $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User) { abort(403); } if (! $workspace instanceof Workspace) { throw new NotFoundHttpException; } if (! app(WorkspaceCapabilityResolver::class)->isMember($user, $workspace)) { throw new NotFoundHttpException; } } private function workspace(): ?Workspace { if ($this->workspace instanceof Workspace) { return $this->workspace; } $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request()); if (! is_int($workspaceId)) { return null; } return $this->workspace = Workspace::query()->whereKey($workspaceId)->first(); } /** * @return Builder */ private function issueBaseQuery(): Builder { $workspace = $this->workspace(); $user = auth()->user(); if (! $workspace instanceof Workspace || ! $user instanceof User) { return Finding::query()->whereRaw('1 = 0'); } return $this->hygieneService()->issueQuery( $workspace, $user, tenantId: null, reasonFilter: $this->currentReasonFilter(), applyOrdering: true, ); } /** * @return Builder */ private function filteredIssueQuery(bool $includeTenantFilter = true, ?string $reasonFilter = null): Builder { $workspace = $this->workspace(); $user = auth()->user(); if (! $workspace instanceof Workspace || ! $user instanceof User) { return Finding::query()->whereRaw('1 = 0'); } return $this->hygieneService()->issueQuery( $workspace, $user, tenantId: $includeTenantFilter ? $this->currentTenantFilterId() : null, reasonFilter: $reasonFilter ?? $this->currentReasonFilter(), applyOrdering: true, ); } /** * @return array */ private function tenantFilterOptions(): array { return collect($this->visibleTenants()) ->mapWithKeys(static fn (Tenant $tenant): array => [ (string) $tenant->getKey() => (string) $tenant->name, ]) ->all(); } private function applyRequestedTenantPrefilter(): void { $requestedTenant = request()->query('tenant'); if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) { return; } foreach ($this->visibleTenants() as $tenant) { if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) { continue; } $this->tableFilters['tenant_id']['value'] = (string) $tenant->getKey(); $this->tableDeferredFilters['tenant_id']['value'] = (string) $tenant->getKey(); return; } } private function normalizeTenantFilterState(): void { $configuredTenantFilter = data_get($this->currentFiltersState(), 'tenant_id.value'); if ($configuredTenantFilter === null || $configuredTenantFilter === '') { return; } if ($this->currentTenantFilterId() !== null) { return; } $this->removeTableFilter('tenant_id'); } /** * @return array */ private function currentFiltersState(): array { $persisted = session()->get($this->getTableFiltersSessionKey(), []); return array_replace_recursive( is_array($persisted) ? $persisted : [], $this->tableFilters ?? [], ); } private function currentTenantFilterId(): ?int { $tenantFilter = data_get($this->currentFiltersState(), 'tenant_id.value'); if (! is_numeric($tenantFilter)) { return null; } $tenantId = (int) $tenantFilter; foreach ($this->visibleTenants() as $tenant) { if ((int) $tenant->getKey() === $tenantId) { return $tenantId; } } return null; } private function filteredTenant(): ?Tenant { $tenantId = $this->currentTenantFilterId(); if (! is_int($tenantId)) { return null; } foreach ($this->visibleTenants() as $tenant) { if ((int) $tenant->getKey() === $tenantId) { return $tenant; } } return null; } private function activeVisibleTenant(): ?Tenant { $activeTenant = app(OperateHubShell::class)->activeEntitledTenant(request()); if (! $activeTenant instanceof Tenant) { return null; } foreach ($this->visibleTenants() as $tenant) { if ($tenant->is($activeTenant)) { return $tenant; } } return null; } private function tenantPrefilterSource(): string { $tenant = $this->filteredTenant(); if (! $tenant instanceof Tenant) { return 'none'; } $activeTenant = $this->activeVisibleTenant(); if ($activeTenant instanceof Tenant && $activeTenant->is($tenant)) { return 'active_tenant_context'; } return 'explicit_filter'; } private function assigneeContext(Finding $record): ?string { if (! $this->hygieneService()->recordHasBrokenAssignment($record)) { return null; } if ($record->assigneeUser?->trashed()) { return 'Soft-deleted user'; } return 'No current tenant membership'; } private function tenantFilterAloneExcludesRows(): bool { if ($this->currentTenantFilterId() === null) { return false; } if ((clone $this->filteredIssueQuery())->exists()) { return false; } return (clone $this->filteredIssueQuery(includeTenantFilter: false))->exists(); } private function reasonFilterAloneExcludesRows(): bool { if ($this->currentReasonFilter() === FindingAssignmentHygieneService::FILTER_ALL) { return false; } if ((clone $this->filteredIssueQuery())->exists()) { return false; } return (clone $this->filteredIssueQuery(includeTenantFilter: true, reasonFilter: FindingAssignmentHygieneService::FILTER_ALL))->exists(); } private function findingDetailUrl(Finding $record): string { $tenant = $record->tenant; if (! $tenant instanceof Tenant) { return '#'; } $url = FindingResource::getUrl('view', ['record' => $record], panel: 'tenant', tenant: $tenant); return $this->appendQuery($url, $this->navigationContext()->toQuery()); } private function navigationContext(): CanonicalNavigationContext { return new CanonicalNavigationContext( sourceSurface: 'findings.hygiene', canonicalRouteName: static::getRouteName(Filament::getPanel('admin')), tenantId: $this->currentTenantFilterId(), backLinkLabel: 'Back to findings hygiene', backLinkUrl: $this->reportUrl(), ); } private function reportUrl(array $overrides = []): string { $resolvedTenant = array_key_exists('tenant', $overrides) ? $overrides['tenant'] : $this->filteredTenant()?->external_id; $resolvedReason = array_key_exists('reason', $overrides) ? $overrides['reason'] : $this->currentReasonFilter(); return static::getUrl( panel: 'admin', parameters: array_filter([ 'tenant' => is_string($resolvedTenant) && $resolvedTenant !== '' ? $resolvedTenant : null, 'reason' => is_string($resolvedReason) && $resolvedReason !== FindingAssignmentHygieneService::FILTER_ALL ? $resolvedReason : null, ], static fn (mixed $value): bool => $value !== null && $value !== ''), ); } private function resolveRequestedReasonFilter(): string { $requestedFilter = request()->query('reason'); $availableFilters = $this->hygieneService()->filterOptions(); return is_string($requestedFilter) && array_key_exists($requestedFilter, $availableFilters) ? $requestedFilter : FindingAssignmentHygieneService::FILTER_ALL; } private function currentReasonFilter(): string { $availableFilters = $this->hygieneService()->filterOptions(); return array_key_exists($this->reasonFilter, $availableFilters) ? $this->reasonFilter : FindingAssignmentHygieneService::FILTER_ALL; } /** * @return array */ private function emptyStateActions(): array { $emptyState = $this->emptyState(); if (($emptyState['action_kind'] ?? null) !== 'clear_tenant_filter') { return []; } return [ Action::make((string) $emptyState['action_name']) ->label((string) $emptyState['action_label']) ->icon('heroicon-o-arrow-right') ->color('gray') ->action(fn (): mixed => $this->clearTenantFilter()), ]; } /** * @param array $query */ private function appendQuery(string $url, array $query): string { if ($query === []) { return $url; } return $url.(str_contains($url, '?') ? '&' : '?').http_build_query($query); } private function hygieneService(): FindingAssignmentHygieneService { return app(FindingAssignmentHygieneService::class); } }