TenantAtlas/apps/platform/resources/views/filament/pages/governance/governance-inbox.blade.php
ahmido 9912d94563 feat: add governance inbox resolution intake (#460)
Automated PR created by Codex via Gitea API.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #460
2026-06-20 07:46:12 +00:00

653 lines
39 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 ($this->hasReviewPublicationResolutionFocus())
<x-filament::badge color="gray" size="sm">
Status: {{ $scope['status_label'] ?? 'All active statuses' }}
</x-filament::badge>
<x-filament::badge color="gray" size="sm">
Updated: {{ $scope['updated_label'] ?? 'Any time' }}
</x-filament::badge>
@endif
@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 ($this->hasReviewPublicationResolutionFocus())
<div class="grid gap-3 lg:grid-cols-2" data-testid="governance-inbox-review-publication-filters">
<div class="space-y-2">
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Review publication status
</p>
<div class="flex flex-wrap gap-2">
@foreach ($this->reviewPublicationStatusFilters() as $filter)
<a
href="{{ $filter['url'] }}"
class="inline-flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs font-medium transition {{ $filter['active'] ? '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 ($filter['active']) aria-current="page" @endif
>
{{ $filter['label'] }}
</a>
@endforeach
</div>
</div>
<div class="space-y-2">
<p class="text-xs font-medium uppercase tracking-[0.12em] text-gray-500 dark:text-gray-400">
Updated
</p>
<div class="flex flex-wrap gap-2">
@foreach ($this->reviewPublicationUpdatedFilters() as $filter)
<a
href="{{ $filter['url'] }}"
class="inline-flex items-center gap-2 rounded-md border px-2.5 py-1 text-xs font-medium transition {{ $filter['active'] ? '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 ($filter['active']) aria-current="page" @endif
>
{{ $filter['label'] }}
</a>
@endforeach
</div>
</div>
</div>
@endif
@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>