Automated PR created by Codex via Gitea API. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #460
653 lines
39 KiB
PHP
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>
|