TenantAtlas/apps/platform/resources/views/filament/pages/governance/governance-inbox.blade.php
ahmido 8cffdbdb2c feat: governance inbox final operator workflow (spec 346) (#418)
Implemented the final operator workflow for the Governance Inbox. This includes refactoring the inbox page, updating finding resources, adding UI enforcement policies, updating related blade views, and adding comprehensive tests for operator workflow and scope contracts.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #418
2026-06-02 14:58:39 +00:00

603 lines
36 KiB
PHP

<x-filament-panels::page>
@php
$scope = $this->appliedScope();
$summary = $this->operatorSummary();
$lanes = $this->laneGroups();
$emptyState = $this->calmEmptyState();
$recentlyResolved = $this->recentlyResolved();
$sections = $this->sections();
$diagnostics = $this->diagnosticsPanel();
@endphp
<div class="space-y-6">
<section
class="rounded-2xl border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-800 dark:bg-gray-900"
data-testid="governance-inbox-operator-summary"
>
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div class="space-y-3">
<div class="space-y-1">
<p class="text-xs font-medium uppercase tracking-[0.18em] text-gray-500 dark:text-gray-400">
Open governance work
</p>
<h2 class="text-2xl font-semibold text-gray-950 dark:text-white">
{{ $summary['headline'] }}
</h2>
</div>
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
The inbox turns current findings, accepted-risk records, evidence gaps, review follow-up, and blocked operations into one operator queue.
</p>
</div>
<div class="flex flex-wrap gap-2 text-sm text-gray-600 dark:text-gray-300" data-testid="governance-inbox-summary-context">
@if (filled($scope['workspace_label'] ?? null))
<x-filament::badge color="gray" size="sm">
Workspace: {{ $scope['workspace_label'] }}
</x-filament::badge>
@endif
<x-filament::badge color="gray" size="sm">
Source focus: {{ $scope['family_label'] ?? 'All source families' }}
</x-filament::badge>
@if (filled($scope['tenant_label'] ?? null))
<x-filament::badge color="warning" size="sm">
Environment: {{ $scope['tenant_label'] }}
</x-filament::badge>
@endif
</div>
</div>
@if ($this->hasTenantPrefilter())
<div class="mt-4">
@include('filament.partials.workspace-hub-environment-filter-chip', [
'label' => $scope['tenant_label'] ?? null,
'clearUrl' => $this->pageUrl(['environment_id' => null, 'family' => null]),
])
</div>
@endif
@if (is_array($summary['next_recommended_item'] ?? null))
@php
$nextItem = $summary['next_recommended_item'];
$nextLaneColor = match ($nextItem['lane_key'] ?? null) {
'needs_triage', 'risk_exception_review', 'evidence_required' => 'warning',
'blocked' => 'danger',
'requires_decision' => 'info',
default => 'gray',
};
@endphp
<div
class="mt-4 rounded-xl border border-primary-200 bg-primary-50/60 p-4 dark:border-primary-800/70 dark:bg-primary-950/20"
data-testid="governance-inbox-next-action"
>
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div class="min-w-0 space-y-3">
<div class="space-y-1">
<p class="text-xs font-medium uppercase tracking-[0.12em] text-primary-700 dark:text-primary-300">
Next recommended action
</p>
<h3 class="text-base font-semibold text-gray-950 dark:text-white">
{{ $nextItem['headline'] }}
</h3>
</div>
<div class="flex flex-wrap gap-2 text-xs font-medium">
<x-filament::badge :color="$nextLaneColor" size="sm">
{{ $nextItem['lane_label'] }}
</x-filament::badge>
<x-filament::badge color="gray" size="sm">
{{ $nextItem['status_label'] }}
</x-filament::badge>
<x-filament::badge color="gray" size="sm">
{{ $nextItem['environment_label'] }}
</x-filament::badge>
</div>
<dl class="grid grid-cols-1 gap-3 text-sm sm:grid-cols-2">
<div>
<dt class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
{{ $nextItem['reason_heading'] }}
</dt>
<dd class="mt-1 leading-6 text-gray-800 dark:text-gray-100">
{{ $nextItem['reason_label'] }}
</dd>
</div>
<div>
<dt class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Impact
</dt>
<dd class="mt-1 leading-6 text-gray-800 dark:text-gray-100">
{{ $nextItem['impact_label'] }}
</dd>
</div>
</dl>
</div>
<div class="flex shrink-0 flex-col gap-2 sm:flex-row lg:flex-col">
<x-filament::button tag="a" href="{{ $nextItem['primary_action']['url'] }}" icon="heroicon-o-arrow-top-right-on-square">
{{ $nextItem['primary_action']['label'] }}
</x-filament::button>
@if (filled($nextItem['lane_url'] ?? null))
<x-filament::button tag="a" color="gray" href="{{ $nextItem['lane_url'] }}">
View lane
</x-filament::button>
@endif
</div>
</div>
</div>
@endif
@if (($summary['active_counts'] ?? []) !== [])
<div class="mt-4 grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3" data-testid="governance-inbox-summary-active-counts">
@foreach ($summary['active_counts'] as $countCard)
<div class="rounded-xl border border-gray-200 bg-white p-3 dark:border-gray-800 dark:bg-gray-950/40">
<div class="flex items-start justify-between gap-3">
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
{{ $countCard['label'] }}
</p>
<x-filament::badge color="gray" size="xs">
{{ $countCard['count'] }}
</x-filament::badge>
</div>
<p class="mt-2 text-sm leading-5 text-gray-600 dark:text-gray-300">
{{ $countCard['description'] }}
</p>
</div>
@endforeach
</div>
@endif
@if (($summary['clear_counts'] ?? []) !== [])
<div class="mt-3 flex flex-wrap gap-2" data-testid="governance-inbox-summary-clear-counts">
@foreach ($summary['clear_counts'] as $countCard)
<x-filament::badge color="gray" size="sm">
{{ $countCard['label'] }}
·
{{ $countCard['chip_label'] }}
</x-filament::badge>
@endforeach
</div>
@endif
</section>
@if ($lanes === [])
<section
class="rounded-2xl border border-dashed border-gray-300 bg-gray-50 p-6 dark:border-gray-700 dark:bg-gray-950/40"
data-testid="governance-inbox-empty-state"
>
<h2 class="text-lg font-semibold text-gray-950 dark:text-white">
{{ $emptyState['title'] }}
</h2>
<p class="mt-2 max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $emptyState['body'] }}
</p>
@if (filled($emptyState['action_label'] ?? null) && filled($emptyState['action_url'] ?? null))
<div class="mt-4">
<x-filament::button tag="a" color="gray" href="{{ $emptyState['action_url'] }}">
{{ $emptyState['action_label'] }}
</x-filament::button>
</div>
@endif
</section>
@else
<section
class="space-y-4"
data-testid="governance-inbox-lanes"
>
<div class="space-y-1">
<h2 class="text-lg font-semibold text-gray-950 dark:text-white">Primary inbox lanes</h2>
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
Active operator work stays grouped by the next path needed to clear it. Supporting source context stays below the fold.
</p>
</div>
@foreach ($lanes as $lane)
<section
id="{{ $lane['anchor_id'] }}"
class="scroll-mt-24 rounded-2xl border border-gray-200 bg-white p-4 shadow-sm target:ring-2 target:ring-primary-500 target:ring-offset-2 target:ring-offset-gray-50 dark:border-gray-800 dark:bg-gray-900 dark:target:ring-offset-gray-950 sm:p-5"
data-testid="governance-inbox-lane-{{ $lane['key'] }}"
>
<div class="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div class="space-y-2">
<div class="flex flex-wrap items-center gap-2">
<h3 class="text-lg font-semibold text-gray-950 dark:text-white">{{ $lane['label'] }}</h3>
<x-filament::badge color="gray" size="xs">
{{ $lane['count'] }}
</x-filament::badge>
</div>
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $lane['description'] }}
</p>
</div>
</div>
@if ($lane['count'] === 0)
<div class="mt-4 rounded-xl border border-dashed border-gray-300 bg-gray-50 p-4 text-sm leading-6 text-gray-600 dark:border-gray-700 dark:bg-gray-950/50 dark:text-gray-300">
{{ $lane['empty_state'] }}
</div>
@else
<ul class="mt-4 grid gap-3">
@foreach ($lane['items'] as $item)
<li
class="rounded-xl border border-gray-200 p-3 dark:border-gray-800 sm:p-4"
data-testid="governance-inbox-item-{{ $lane['key'] }}-{{ $loop->iteration }}"
>
@php
$itemLaneColor = match ($item['lane_key'] ?? $lane['key'] ?? null) {
'needs_triage', 'risk_exception_review', 'evidence_required' => 'warning',
'blocked' => 'danger',
'requires_decision' => 'info',
default => 'gray',
};
@endphp
<div class="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div class="min-w-0 space-y-2">
<div class="flex flex-wrap items-center gap-2">
<x-filament::badge :color="$itemLaneColor" size="sm">
{{ $item['lane_label'] }}
</x-filament::badge>
<x-filament::badge color="gray" size="sm">
{{ $item['status_label'] }}
</x-filament::badge>
<x-filament::badge color="gray" size="sm">
{{ $item['environment_label'] }}
</x-filament::badge>
</div>
<h4 class="text-base font-semibold text-gray-950 dark:text-white">
{{ $item['title'] }}
</h4>
@if (filled($item['context_label'] ?? null))
<p class="text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $item['context_label'] }}
</p>
@endif
</div>
@if (is_array($item['primary_action'] ?? null))
<x-filament::button
tag="a"
href="{{ $item['primary_action']['url'] }}"
icon="heroicon-o-arrow-top-right-on-square"
>
{{ $item['primary_action']['label'] }}
</x-filament::button>
@endif
</div>
<dl class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2">
<div class="rounded-lg border border-gray-200 p-3 dark:border-gray-800">
<dt class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
{{ $item['reason_heading'] }}
</dt>
<dd class="mt-1 text-sm leading-6 text-gray-800 dark:text-gray-100">
{{ $item['reason_label'] }}
</dd>
</div>
<div class="rounded-lg border border-gray-200 p-3 dark:border-gray-800">
<dt class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Impact
</dt>
<dd class="mt-1 text-sm leading-6 text-gray-800 dark:text-gray-100">
{{ $item['impact_label'] }}
</dd>
</div>
</dl>
<details class="mt-3 rounded-lg border border-gray-200 p-3 dark:border-gray-800">
<summary class="cursor-pointer text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
More context
</summary>
<div class="mt-3 grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-4">
<div>
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Source
</p>
<p class="mt-1 text-sm leading-6 text-gray-800 dark:text-gray-100">
{{ $item['source_label'] }}
</p>
</div>
<div>
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Owner / due
</p>
<p class="mt-1 text-sm leading-6 text-gray-800 dark:text-gray-100">
{{ $item['owner_label'] }} · {{ $item['due_label'] }}
</p>
</div>
<div>
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Evidence
</p>
<p class="mt-1 text-sm leading-6 text-gray-800 dark:text-gray-100">
{{ $item['evidence_label'] }}
</p>
</div>
<div>
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Accepted risk / decision
</p>
<p class="mt-1 text-sm leading-6 text-gray-800 dark:text-gray-100">
{{ $item['exception_label'] }}
</p>
</div>
</div>
@if (($item['linked_records'] ?? []) !== [] || ($item['secondary_actions'] ?? []) !== [])
<div class="mt-3 grid grid-cols-1 gap-3 xl:grid-cols-2">
@if (($item['linked_records'] ?? []) !== [])
<div class="rounded-lg border border-gray-200 p-3 dark:border-gray-800">
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Linked records
</p>
<div class="mt-2 flex flex-wrap gap-2">
@foreach ($item['linked_records'] as $record)
<x-filament::link
href="{{ $record['url'] }}"
color="gray"
icon="heroicon-m-arrow-top-right-on-square"
icon-position="after"
size="sm"
>
{{ $record['label'] }}
</x-filament::link>
@endforeach
</div>
</div>
@endif
@if (($item['secondary_actions'] ?? []) !== [])
<div class="rounded-lg border border-gray-200 p-3 dark:border-gray-800">
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Secondary actions
</p>
<div class="mt-2 flex flex-wrap gap-2">
@foreach ($item['secondary_actions'] as $action)
<x-filament::link
href="{{ $action['url'] }}"
icon="heroicon-m-arrow-top-right-on-square"
icon-position="after"
size="sm"
>
{{ $action['label'] }}
</x-filament::link>
@endforeach
</div>
</div>
@endif
</div>
@endif
</details>
</li>
@endforeach
</ul>
@endif
</section>
@endforeach
</section>
@endif
@if ($recentlyResolved !== null)
<details
class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-900"
data-testid="governance-inbox-recently-resolved"
>
<summary class="cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-200">
{{ $recentlyResolved['label'] }} · {{ $recentlyResolved['count'] }}
</summary>
<div class="mt-4 space-y-4">
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $recentlyResolved['summary'] }}
</p>
@if (($recentlyResolved['rows'] ?? []) !== [])
<ul class="grid gap-3">
@foreach ($recentlyResolved['rows'] as $row)
<li class="rounded-xl border border-gray-200 p-4 dark:border-gray-800">
<p class="text-sm font-semibold text-gray-950 dark:text-white">{{ $row['title'] }}</p>
<p class="mt-1 text-sm leading-6 text-gray-600 dark:text-gray-300">{{ $row['reason'] }}</p>
</li>
@endforeach
</ul>
@endif
<x-filament::button tag="a" color="gray" href="{{ $recentlyResolved['open_url'] }}">
{{ $recentlyResolved['open_label'] }}
</x-filament::button>
</div>
</details>
@endif
<details
id="source-detail"
class="scroll-mt-24 rounded-2xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-900"
data-testid="governance-inbox-source-detail"
@if ($this->family !== null) open @endif
>
<summary class="cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-200">
Source detail
</summary>
<div class="mt-4 space-y-4">
<div class="space-y-1">
<h2 class="text-base font-semibold text-gray-950 dark:text-white">Source-family context</h2>
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
Existing family slices remain available for drill-through and source truth checks, but they no longer dominate the first screen.
</p>
</div>
<div class="flex flex-wrap gap-2" data-testid="governance-inbox-family-filters">
<a
href="{{ $this->pageUrl(['family' => null]) }}#source-detail"
class="inline-flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs font-medium transition {{ $this->family === null ? 'border-primary-300 bg-primary-50 text-primary-700 dark:border-primary-700/60 dark:bg-primary-950/40 dark:text-primary-300' : 'border-gray-200 text-gray-700 hover:border-gray-300 dark:border-gray-700 dark:text-gray-200 dark:hover:border-gray-600' }}"
@if ($this->family === null) aria-current="page" @endif
>
All source families
<x-filament::badge color="gray" size="xs">
{{ $scope['total_count'] ?? 0 }}
</x-filament::badge>
</a>
@foreach ($this->availableFamilies() as $family)
<a
href="{{ $this->pageUrl(['family' => $family['key']]) }}#source-detail"
class="inline-flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs font-medium transition {{ $this->isActiveFamily($family['key']) ? 'border-primary-300 bg-primary-50 text-primary-700 dark:border-primary-700/60 dark:bg-primary-950/40 dark:text-primary-300' : 'border-gray-200 text-gray-700 hover:border-gray-300 dark:border-gray-700 dark:text-gray-200 dark:hover:border-gray-600' }}"
@if ($this->isActiveFamily($family['key'])) aria-current="page" @endif
>
{{ $family['label'] }}
<x-filament::badge color="gray" size="xs">
{{ $family['count'] }}
</x-filament::badge>
</a>
@endforeach
</div>
@if ($sections !== [])
<div class="space-y-4">
@foreach ($sections as $section)
<div class="rounded-xl border border-gray-200 p-4 dark:border-gray-800">
<div class="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div class="space-y-2">
<div class="flex flex-wrap items-center gap-2">
<h3 class="text-sm font-semibold text-gray-950 dark:text-white">{{ $section['label'] }}</h3>
<x-filament::badge color="gray" size="xs">
{{ $section['count'] }}
</x-filament::badge>
</div>
<p class="text-sm leading-6 text-gray-600 dark:text-gray-300">{{ $section['summary'] }}</p>
</div>
@if (is_array($section['dominant_action'] ?? null))
<x-filament::button tag="a" color="gray" size="sm" href="{{ $section['dominant_action']['url'] }}">
{{ $section['dominant_action']['label'] }}
</x-filament::button>
@endif
</div>
@if ($section['count'] === 0)
<div class="mt-4 rounded-lg border border-dashed border-gray-300 bg-gray-50 p-4 text-sm leading-6 text-gray-600 dark:border-gray-700 dark:bg-gray-950/50 dark:text-gray-300">
{{ $section['empty_state'] }}
</div>
@else
<ul class="mt-4 grid gap-3">
@foreach ($section['entries'] as $entry)
<li class="rounded-lg border border-gray-200 p-3 dark:border-gray-800">
<div class="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div class="space-y-1.5">
@if (filled($entry['tenant_label'] ?? null))
<div class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
{{ $entry['tenant_label'] }}
</div>
@endif
<div class="flex flex-wrap items-center gap-2">
@if (filled($entry['destination_url'] ?? null))
<a
href="{{ $entry['destination_url'] }}"
class="text-sm font-semibold text-gray-950 hover:text-primary-600 dark:text-white dark:hover:text-primary-300"
>
{{ $entry['headline'] }}
</a>
@else
<span class="text-sm font-semibold text-gray-950 dark:text-white">
{{ $entry['headline'] }}
</span>
@endif
<x-filament::badge color="gray" size="sm">
{{ $entry['status_label'] }}
</x-filament::badge>
</div>
@if (filled($entry['subline'] ?? null))
<p class="text-sm leading-6 text-gray-600 dark:text-gray-300">{{ $entry['subline'] }}</p>
@endif
</div>
@if (filled($entry['destination_url'] ?? null))
<x-filament::button tag="a" color="gray" size="sm" href="{{ $entry['destination_url'] }}">
Open source
</x-filament::button>
@endif
</div>
</li>
@endforeach
</ul>
@endif
</div>
@endforeach
</div>
@endif
</div>
</details>
<details
class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm dark:border-gray-800 dark:bg-gray-900"
data-testid="governance-inbox-diagnostics"
>
<summary class="cursor-pointer text-sm font-medium text-gray-700 dark:text-gray-200">
{{ $diagnostics['label'] }} · {{ $diagnostics['state'] }}
</summary>
<p class="mt-4 max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
{{ $diagnostics['body'] }}
</p>
</details>
</div>
<script>
(() => {
const scrollToGovernanceInboxTarget = () => {
const hash = window.location.hash;
if (hash === '#source-detail') {
const sourceDetail = document.getElementById('source-detail');
if (! sourceDetail) {
return;
}
sourceDetail.open = true;
window.setTimeout(() => sourceDetail.scrollIntoView({ block: 'start' }), 50);
return;
}
if (! hash.startsWith('#lane-')) {
return;
}
const target = document.getElementById(hash.slice(1));
if (! target) {
return;
}
window.setTimeout(() => target.scrollIntoView({ block: 'start' }), 50);
};
document.addEventListener('DOMContentLoaded', scrollToGovernanceInboxTarget, { once: true });
document.addEventListener('livewire:navigated', scrollToGovernanceInboxTarget);
window.addEventListener('hashchange', scrollToGovernanceInboxTarget);
scrollToGovernanceInboxTarget();
})();
</script>
</x-filament-panels::page>