|null */ private ?array $authorizedTenants = null; /** * @var array|null */ private ?array $visibleFindingTenants = null; /** * @var array|null */ private ?array $reviewTenants = null; /** * @var array|null */ private ?array $inboxPayload = null; /** * @var array|null */ private ?array $unfilteredInboxPayload = null; private ?Workspace $workspace = null; private ?bool $visibleAlertsFamily = null; public ?int $tenantId = null; public ?string $family = null; public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration { return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport) ->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the workspace decision surface calm and read-only.') ->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value) ->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The governance inbox routes into existing source surfaces instead of exposing row-level secondary actions.') ->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The governance inbox does not expose bulk actions.') ->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty states stay calm and capability-safe when no visible attention exists.') ->exempt(ActionSurfaceSlot::DetailHeader, 'The governance inbox owns no local detail surface in v1.'); } public function mount(): void { $this->authorizeWorkspaceMembership(); $this->applyRequestedTenantPrefilter(); $this->family = $this->resolveRequestedFamily(); $this->ensureAtLeastOneVisibleFamily(); $this->ensureRequestedFamilyIsVisible(); } /** * @return array */ public function appliedScope(): array { $selectedTenant = $this->selectedTenant(); $availableFamilies = collect($this->availableFamilies()) ->keyBy('key'); return [ 'workspace_label' => $this->workspace()?->name, 'tenant_label' => $selectedTenant?->name, 'tenant_prefilter_source' => $selectedTenant instanceof Tenant ? 'explicit_filter' : 'none', 'family_key' => $this->family, 'family_label' => $this->family !== null ? ($availableFamilies->get($this->family)['label'] ?? Str::headline($this->family)) : 'All attention', 'total_count' => (int) ($this->inboxPayload()['total_count'] ?? 0), ]; } /** * @return list */ public function availableFamilies(): array { return $this->inboxPayload()['available_families'] ?? []; } /** * @return list> */ public function sections(): array { return $this->inboxPayload()['sections'] ?? []; } /** * @return array */ public function calmEmptyState(): array { if ($this->tenantFilterAloneExcludesRows()) { return [ 'title' => 'This tenant filter is hiding other visible attention', 'body' => 'The current tenant scope is calm, but other visible tenants in this workspace still have governance attention.', 'action_label' => 'Clear tenant filter', 'action_url' => $this->pageUrl(['tenant' => null]), ]; } return [ 'title' => 'No visible governance attention right now', 'body' => 'The current workspace scope is calm across the visible governance families.', 'action_label' => null, 'action_url' => null, ]; } public function hasTenantPrefilter(): bool { return $this->selectedTenant() instanceof Tenant; } public function isActiveFamily(?string $familyKey): bool { return $this->family === $familyKey; } public function pageUrl(array $overrides = []): string { $selectedTenant = $this->selectedTenant(); $resolvedTenant = array_key_exists('tenant', $overrides) ? $overrides['tenant'] : ($selectedTenant?->getKey() !== null ? (string) $selectedTenant->getKey() : null); $resolvedFamily = array_key_exists('family', $overrides) ? $overrides['family'] : $this->family; return static::getUrl( panel: 'admin', parameters: array_filter([ 'tenant_id' => is_string($resolvedTenant) && $resolvedTenant !== '' ? $resolvedTenant : null, 'family' => is_string($resolvedFamily) && $resolvedFamily !== '' ? $resolvedFamily : null, ], static fn (mixed $value): bool => $value !== null && $value !== ''), ); } public function navigationContext(): CanonicalNavigationContext { return new CanonicalNavigationContext( sourceSurface: 'governance.inbox', canonicalRouteName: static::getRouteName(Filament::getPanel('admin')), tenantId: $this->tenantId, backLinkLabel: 'Back to governance inbox', backLinkUrl: $this->pageUrl(), ); } private function authorizeWorkspaceMembership(): void { $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User) { abort(403); } if (! $workspace instanceof Workspace) { throw new NotFoundHttpException; } $resolver = app(WorkspaceCapabilityResolver::class); if (! $resolver->isMember($user, $workspace)) { throw new NotFoundHttpException; } } private function ensureAtLeastOneVisibleFamily(): void { if ( $this->hasVisibleOperationsFamily() || $this->visibleFindingTenants() !== [] || $this->reviewTenants() !== [] || $this->hasVisibleAlertsFamily() ) { return; } abort(403); } private function ensureRequestedFamilyIsVisible(): void { if ($this->family === null) { return; } if (in_array($this->family, collect($this->availableFamilies())->pluck('key')->all(), true)) { return; } throw new NotFoundHttpException; } private function hasVisibleOperationsFamily(): bool { return $this->authorizedTenants() !== []; } private function hasVisibleAlertsFamily(): bool { if (is_bool($this->visibleAlertsFamily)) { return $this->visibleAlertsFamily; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->visibleAlertsFamily = false; } return $this->visibleAlertsFamily = app(WorkspaceCapabilityResolver::class)->can($user, $workspace, Capabilities::ALERTS_VIEW); } /** * @return array */ private function visibleFindingTenants(): array { if ($this->visibleFindingTenants !== null) { return $this->visibleFindingTenants; } $user = auth()->user(); $tenants = $this->authorizedTenants(); if (! $user instanceof User || $tenants === []) { return $this->visibleFindingTenants = []; } $resolver = app(CapabilityResolver::class); $resolver->primeMemberships( $user, array_map(static fn (Tenant $tenant): int => (int) $tenant->getKey(), $tenants), ); return $this->visibleFindingTenants = array_values(array_filter( $tenants, fn (Tenant $tenant): bool => $resolver->can($user, $tenant, Capabilities::TENANT_FINDINGS_VIEW), )); } /** * @return array */ private function reviewTenants(): array { if ($this->reviewTenants !== null) { return $this->reviewTenants; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->reviewTenants = []; } $service = app(TenantReviewRegisterService::class); if (! $service->canAccessWorkspace($user, $workspace)) { return $this->reviewTenants = []; } return $this->reviewTenants = $service->authorizedTenants($user, $workspace); } /** * @return array */ private function authorizedTenants(): array { if ($this->authorizedTenants !== null) { return $this->authorizedTenants; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->authorizedTenants = []; } return $this->authorizedTenants = $user->tenants() ->where('tenants.workspace_id', (int) $workspace->getKey()) ->where('tenants.status', 'active') ->orderBy('tenants.name') ->get(['tenants.id', 'tenants.name', 'tenants.external_id', 'tenants.workspace_id']) ->all(); } private function applyRequestedTenantPrefilter(): void { $requestedTenant = request()->query('tenant_id', request()->query('tenant')); if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) { return; } foreach ($this->authorizedTenants() as $tenant) { if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) { continue; } $this->tenantId = (int) $tenant->getKey(); return; } throw new NotFoundHttpException; } private function resolveRequestedFamily(): ?string { $family = request()->query('family'); if (! is_string($family)) { return null; } return in_array($family, [ 'assigned_findings', 'intake_findings', 'stale_operations', 'alert_delivery_failures', 'review_follow_up', ], true) ? $family : null; } 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 array */ private function inboxPayload(): array { if (is_array($this->inboxPayload)) { return $this->inboxPayload; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->inboxPayload = [ 'sections' => [], 'available_families' => [], 'family_counts' => [], 'total_count' => 0, ]; } return $this->inboxPayload = app(GovernanceInboxSectionBuilder::class)->build( user: $user, workspace: $workspace, authorizedTenants: $this->authorizedTenants(), visibleFindingTenants: $this->visibleFindingTenants(), reviewTenants: $this->reviewTenants(), canViewAlerts: $this->hasVisibleAlertsFamily(), selectedTenant: $this->selectedTenant(), selectedFamily: $this->family, navigationContext: $this->navigationContext(), ); } /** * @return array */ private function unfilteredInboxPayload(): array { if (is_array($this->unfilteredInboxPayload)) { return $this->unfilteredInboxPayload; } $user = auth()->user(); $workspace = $this->workspace(); if (! $user instanceof User || ! $workspace instanceof Workspace) { return $this->unfilteredInboxPayload = [ 'sections' => [], 'available_families' => [], 'family_counts' => [], 'total_count' => 0, ]; } return $this->unfilteredInboxPayload = app(GovernanceInboxSectionBuilder::class)->build( user: $user, workspace: $workspace, authorizedTenants: $this->authorizedTenants(), visibleFindingTenants: $this->visibleFindingTenants(), reviewTenants: $this->reviewTenants(), canViewAlerts: $this->hasVisibleAlertsFamily(), selectedTenant: null, selectedFamily: null, navigationContext: $this->navigationContext(), ); } private function selectedTenant(): ?Tenant { if (! is_int($this->tenantId)) { return null; } foreach ($this->authorizedTenants() as $tenant) { if ((int) $tenant->getKey() === $this->tenantId) { return $tenant; } } return null; } private function tenantFilterAloneExcludesRows(): bool { if (! is_int($this->tenantId) || $this->family !== null) { return false; } if ($this->sections() !== []) { return false; } return (int) ($this->unfilteredInboxPayload()['total_count'] ?? 0) > 0; } }