TenantAtlas/apps/platform/resources/views/filament/pages/workspace-overview.blade.php
ahmido 81bb5f42c7
Some checks failed
Main Confidence / confidence (push) Failing after 55s
feat: add findings operator inbox (#258)
## Summary
- add the canonical admin-plane `My Findings` inbox at `/admin/findings/my-work`
- add the workspace overview `Assigned to me` signal and inbox-to-detail continuity
- add focused Pest coverage plus the full Spec 221 artifact bundle

## Validation
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Findings/MyWorkInboxTest.php tests/Feature/Authorization/MyWorkInboxAuthorizationTest.php tests/Feature/Dashboard/MyFindingsSignalTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/WorkspaceOverviewNavigationTest.php`
- integrated-browser smoke completed against the browser-facing `tenantatlas` runtime, including seeded positive-path and negative-path checks plus fixture cleanup

## Filament v5 Guardrails
- Livewire v4.0+ compliant
- panel provider registration remains in `apps/platform/bootstrap/providers.php`
- global search behavior is unchanged; `FindingResource` already has a View page and the new inbox is a custom page, not a searchable resource
- no destructive actions were introduced on the inbox or overview signal
- no new assets were added; the existing deploy step for `cd apps/platform && php artisan filament:assets` remains unchanged
- coverage includes the new inbox page, authorization boundaries, the workspace overview signal, and the overview CTA regression

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #258
2026-04-21 09:19:54 +00:00

163 lines
10 KiB
PHP

<x-filament-panels::page>
@php
$workspace = $overview['workspace'] ?? ['name' => 'Workspace'];
$quickActions = $overview['quick_actions'] ?? [];
$myFindingsSignal = $overview['my_findings_signal'] ?? null;
$zeroTenantState = $overview['zero_tenant_state'] ?? null;
@endphp
<div class="space-y-6">
<x-filament::section>
<div class="space-y-2">
<div class="flex flex-wrap items-center gap-2">
<span class="inline-flex items-center gap-1.5 rounded-full border border-primary-200 bg-primary-50 px-3 py-1 text-xs font-medium text-primary-700 dark:border-primary-700/60 dark:bg-primary-950/40 dark:text-primary-300">
<x-filament::icon icon="heroicon-o-home" class="h-3.5 w-3.5" />
Workspace overview
</span>
@if (filled($workspace['slug'] ?? null))
<span class="inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-50 px-3 py-1 text-xs font-medium text-gray-600 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
{{ $workspace['slug'] }}
</span>
@endif
</div>
<h1 class="text-2xl font-semibold tracking-tight text-gray-950 dark:text-white">
{{ $workspace['name'] ?? 'Workspace' }}
</h1>
<p class="max-w-2xl text-sm leading-6 text-gray-600 dark:text-gray-300">
This home stays workspace-scoped even when you were previously working in a tenant. Governance risk is still ranked ahead of execution noise, backup health stays separate from recovery evidence, and calm wording only appears when visible tenants are genuinely quiet across the checked domains.
</p>
</div>
</x-filament::section>
@if ($quickActions !== [])
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5">
@foreach ($quickActions as $action)
<a
href="{{ $action['url'] }}"
class="rounded-xl border border-gray-200 bg-white px-4 py-3 text-left transition hover:border-primary-300 hover:shadow-sm dark:border-white/10 dark:bg-white/5 dark:hover:border-primary-500/40 dark:hover:bg-white/10"
>
<div class="flex items-start gap-3">
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg {{ $action['color'] === 'primary' ? 'bg-primary-100 text-primary-700 dark:bg-primary-950/50 dark:text-primary-300' : 'bg-gray-100 text-gray-700 dark:bg-white/10 dark:text-gray-200' }}">
<x-filament::icon :icon="$action['icon']" class="h-5 w-5" />
</div>
<div class="min-w-0">
<div class="text-sm font-semibold text-gray-950 dark:text-white">
{{ $action['label'] }}
</div>
<div class="mt-1 text-xs leading-5 text-gray-600 dark:text-gray-300">
{{ $action['description'] }}
</div>
</div>
</div>
</a>
@endforeach
</div>
@endif
@if (is_array($myFindingsSignal))
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm dark:border-white/10 dark:bg-white/5">
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div class="space-y-3">
<div class="inline-flex w-fit items-center gap-2 rounded-full border border-primary-200 bg-primary-50 px-3 py-1 text-xs font-medium text-primary-700 dark:border-primary-700/60 dark:bg-primary-950/40 dark:text-primary-300">
<x-filament::icon icon="heroicon-o-clipboard-document-check" class="h-3.5 w-3.5" />
Assigned to me
</div>
<div class="space-y-1">
<h2 class="text-base font-semibold text-gray-950 dark:text-white">
{{ $myFindingsSignal['headline'] }}
</h2>
<p class="max-w-2xl text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $myFindingsSignal['description'] }}
</p>
</div>
<div class="flex flex-wrap gap-2 text-xs">
<span class="inline-flex items-center rounded-full border border-gray-200 bg-gray-50 px-3 py-1 font-medium text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
Open assigned: {{ $myFindingsSignal['open_assigned_count'] }}
</span>
<span class="inline-flex items-center rounded-full border px-3 py-1 font-medium {{ ($myFindingsSignal['overdue_assigned_count'] ?? 0) > 0 ? 'border-danger-200 bg-danger-50 text-danger-700 dark:border-danger-700/50 dark:bg-danger-950/30 dark:text-danger-200' : 'border-success-200 bg-success-50 text-success-700 dark:border-success-700/50 dark:bg-success-950/30 dark:text-success-200' }}">
Overdue: {{ $myFindingsSignal['overdue_assigned_count'] }}
</span>
<span class="inline-flex items-center rounded-full border px-3 py-1 font-medium {{ ($myFindingsSignal['is_calm'] ?? false) ? 'border-success-200 bg-success-50 text-success-700 dark:border-success-700/50 dark:bg-success-950/30 dark:text-success-200' : 'border-warning-200 bg-warning-50 text-warning-700 dark:border-warning-700/50 dark:bg-warning-950/30 dark:text-warning-200' }}">
{{ ($myFindingsSignal['is_calm'] ?? false) ? 'Calm' : 'Needs follow-up' }}
</span>
</div>
</div>
<x-filament::button
tag="a"
color="primary"
:href="$myFindingsSignal['cta_url']"
icon="heroicon-o-arrow-right"
>
{{ $myFindingsSignal['cta_label'] }}
</x-filament::button>
</div>
</section>
@endif
@if (is_array($zeroTenantState))
<section class="rounded-2xl border border-warning-200 bg-warning-50 p-5 shadow-sm dark:border-warning-700/50 dark:bg-warning-950/30">
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div class="space-y-1">
<h2 class="text-sm font-semibold text-warning-900 dark:text-warning-100">
{{ $zeroTenantState['title'] }}
</h2>
<p class="text-sm text-warning-800 dark:text-warning-200">
{{ $zeroTenantState['body'] }}
</p>
</div>
<x-filament::button
tag="a"
color="warning"
:href="$zeroTenantState['action_url']"
icon="heroicon-o-arrow-right"
>
{{ $zeroTenantState['action_label'] }}
</x-filament::button>
</div>
</section>
@endif
<div class="space-y-6">
<div class="flex flex-wrap gap-2 text-xs text-gray-600 dark:text-gray-300">
<span class="inline-flex items-center rounded-full border border-danger-200 bg-danger-50 px-3 py-1 font-medium text-danger-700 dark:border-danger-700/50 dark:bg-danger-950/30 dark:text-danger-200">
Governance risk counts affected tenants
</span>
<span class="inline-flex items-center rounded-full border border-warning-200 bg-warning-50 px-3 py-1 font-medium text-warning-700 dark:border-warning-700/50 dark:bg-warning-950/30 dark:text-warning-200">
Backup health stays separate from recovery evidence
</span>
<span class="inline-flex items-center rounded-full border border-gray-200 bg-white px-3 py-1 font-medium text-gray-600 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
Calm wording stays bounded to visible tenants and checked domains
</span>
<span class="inline-flex items-center rounded-full border border-gray-200 bg-white px-3 py-1 font-medium text-gray-600 dark:border-white/10 dark:bg-white/5 dark:text-gray-300">
Recent operations stay diagnostic
</span>
</div>
@livewire(\App\Filament\Widgets\Workspace\WorkspaceSummaryStats::class, [
'metrics' => $overview['summary_metrics'] ?? [],
], key('workspace-overview-summary-' . ($workspace['id'] ?? 'none')))
<div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
@livewire(\App\Filament\Widgets\Workspace\WorkspaceNeedsAttention::class, [
'items' => $overview['attention_items'] ?? [],
'emptyState' => $overview['attention_empty_state'] ?? [],
'triageReviewProgress' => $overview['triage_review_progress'] ?? [],
], key('workspace-overview-attention-' . ($workspace['id'] ?? 'none')))
@livewire(\App\Filament\Widgets\Workspace\WorkspaceRecentOperations::class, [
'operations' => $overview['recent_operations'] ?? [],
'emptyState' => $overview['recent_operations_empty_state'] ?? [],
], key('workspace-overview-operations-' . ($workspace['id'] ?? 'none')))
</div>
</div>
</div>
</x-filament-panels::page>