TenantAtlas/apps/platform/resources/views/filament/pages/baseline-compare-matrix.blade.php
ahmido 65e10a2020 Spec 190: tighten baseline compare matrix scanability (#222)
## Summary
- tighten the baseline compare matrix working surface with active filter scope summaries and clearer visible-set disclosure
- improve matrix scanability with a sticky subject column, calmer attention-first cell styling, and Filament form-based filter controls
- replace the misleading perpetual refresh loading state with a passive auto-refresh note and add focused regression coverage

## Testing
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/BaselineCompareMatrixPageTest.php`

## Notes
- this PR only contains the Spec 190 implementation changes on `190-baseline-compare-matrix`
- follow-up spec drafting for high-density operator mode was intentionally left out of this PR

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #222
2026-04-11 12:32:10 +00:00

564 lines
40 KiB
PHP

<x-filament::page>
@if (($hasActiveRuns ?? false) === true)
<div wire:poll.5s="pollMatrix"></div>
@endif
@php
$reference = is_array($reference ?? null) ? $reference : [];
$tenantSummaries = is_array($tenantSummaries ?? null) ? $tenantSummaries : [];
$rows = is_array($rows ?? null) ? $rows : [];
$policyTypeOptions = is_array($policyTypeOptions ?? null) ? $policyTypeOptions : [];
$stateOptions = is_array($stateOptions ?? null) ? $stateOptions : [];
$severityOptions = is_array($severityOptions ?? null) ? $severityOptions : [];
$tenantSortOptions = is_array($tenantSortOptions ?? null) ? $tenantSortOptions : [];
$subjectSortOptions = is_array($subjectSortOptions ?? null) ? $subjectSortOptions : [];
$stateLegend = is_array($stateLegend ?? null) ? $stateLegend : [];
$freshnessLegend = is_array($freshnessLegend ?? null) ? $freshnessLegend : [];
$trustLegend = is_array($trustLegend ?? null) ? $trustLegend : [];
$emptyState = is_array($emptyState ?? null) ? $emptyState : null;
$currentFilters = is_array($currentFilters ?? null) ? $currentFilters : [];
$referenceReady = ($reference['referenceState'] ?? null) === 'ready';
$matrixSourceNavigation = is_array($navigationContext ?? null) ? $navigationContext : null;
$activeFilterCount = $this->activeFilterCount();
$activeFilterSummary = $this->activeFilterSummary();
$hiddenAssignedTenantCount = max(0, (int) ($reference['assignedTenantCount'] ?? 0) - (int) ($reference['visibleTenantCount'] ?? 0));
$stateBadge = static fn (mixed $value) => \App\Support\Badges\BadgeCatalog::spec(\App\Support\Badges\BadgeDomain::BaselineCompareMatrixState, $value);
$freshnessBadge = static fn (mixed $value) => \App\Support\Badges\BadgeCatalog::spec(\App\Support\Badges\BadgeDomain::BaselineCompareMatrixFreshness, $value);
$trustBadge = static fn (mixed $value) => \App\Support\Badges\BadgeCatalog::spec(\App\Support\Badges\BadgeDomain::BaselineCompareMatrixTrust, $value);
$severityBadge = static fn (mixed $value) => \App\Support\Badges\BadgeCatalog::spec(\App\Support\Badges\BadgeDomain::FindingSeverity, $value);
$profileStatusBadge = static fn (mixed $value) => \App\Support\Badges\BadgeCatalog::spec(\App\Support\Badges\BadgeDomain::BaselineProfileStatus, $value);
$profileStatusSpec = $profileStatusBadge($reference['baselineStatus'] ?? null);
@endphp
<x-filament::section heading="Reference overview">
<x-slot name="description">
Compare assigned tenants is simulation only. It reuses the existing tenant-owned baseline compare path for the visible assigned set and does not create a workspace umbrella run.
</x-slot>
<div class="grid gap-4 xl:grid-cols-[minmax(0,1.5fr)_minmax(0,1fr)]">
<div class="space-y-4">
<div class="flex flex-wrap items-center gap-2">
<x-filament::badge :color="$profileStatusSpec->color" :icon="$profileStatusSpec->icon" size="sm">
{{ $profileStatusSpec->label }}
</x-filament::badge>
@if ($referenceReady)
<x-filament::badge color="success" icon="heroicon-m-check-badge" size="sm">
Reference snapshot ready
</x-filament::badge>
@else
<x-filament::badge color="warning" icon="heroicon-m-exclamation-triangle" size="sm">
Reference snapshot blocked
</x-filament::badge>
@endif
@if (filled($reference['referenceSnapshotId'] ?? null))
<x-filament::badge color="gray" size="sm">
Snapshot #{{ (int) $reference['referenceSnapshotId'] }}
</x-filament::badge>
@endif
@if ($hiddenAssignedTenantCount > 0)
<x-filament::badge color="info" icon="heroicon-m-eye-slash" size="sm">
{{ $hiddenAssignedTenantCount }} hidden by access scope
</x-filament::badge>
@endif
</div>
<div class="space-y-1">
<h2 class="text-xl font-semibold text-gray-950 dark:text-white" data-testid="baseline-compare-matrix-profile">
{{ $reference['baselineProfileName'] ?? ($profile->name ?? 'Baseline compare matrix') }}
</h2>
<p class="text-sm text-gray-600 dark:text-gray-300">
Assigned tenants: {{ (int) ($reference['assignedTenantCount'] ?? 0) }}.
Visible tenants: {{ (int) ($reference['visibleTenantCount'] ?? 0) }}.
@if (filled($reference['referenceSnapshotCapturedAt'] ?? null))
Reference captured {{ \Illuminate\Support\Carbon::parse($reference['referenceSnapshotCapturedAt'])->diffForHumans() }}.
@endif
</p>
@if (filled($reference['referenceReasonCode'] ?? null))
<p class="text-sm text-warning-700 dark:text-warning-300">
Reference reason: {{ \Illuminate\Support\Str::headline(str_replace('.', ' ', (string) $reference['referenceReasonCode'])) }}
</p>
@endif
@if ($hiddenAssignedTenantCount > 0)
<p class="text-sm text-gray-500 dark:text-gray-400">
Showing only the visible assigned set for your current access scope. Hidden tenants are excluded from summaries, rows, and drilldowns.
</p>
@endif
</div>
</div>
<dl class="grid gap-3 sm:grid-cols-2">
<div class="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm dark:border-gray-800 dark:bg-gray-900/70">
<dt class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Visible tenants</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-950 dark:text-white">
{{ (int) ($reference['visibleTenantCount'] ?? 0) }}
</dd>
</div>
<div class="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm dark:border-gray-800 dark:bg-gray-900/70">
<dt class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Rendered subjects</dt>
<dd class="mt-1 text-3xl font-semibold text-gray-950 dark:text-white">
{{ count($rows) }}
</dd>
</div>
<div class="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm dark:border-gray-800 dark:bg-gray-900/70">
<dt class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Tenant sort</dt>
<dd class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
{{ $tenantSortOptions[$currentFilters['tenant_sort'] ?? 'tenant_name'] ?? 'Tenant name' }}
</dd>
</div>
<div class="rounded-xl border border-gray-200 bg-white px-4 py-3 shadow-sm dark:border-gray-800 dark:bg-gray-900/70">
<dt class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Subject sort</dt>
<dd class="mt-1 text-sm font-medium text-gray-900 dark:text-white">
{{ $subjectSortOptions[$currentFilters['subject_sort'] ?? 'deviation_breadth'] ?? 'Deviation breadth' }}
</dd>
</div>
</dl>
</div>
</x-filament::section>
<x-filament::section heading="Filters">
<x-slot name="description">
Narrow the matrix by policy type, technical state, severity, or one focused subject. Only the visible tenant set contributes to the rendered counts, rows, and drilldowns.
</x-slot>
<div class="space-y-4" data-testid="baseline-compare-matrix-filters">
<div class="rounded-2xl border border-gray-200 bg-gray-50/80 px-4 py-3 shadow-sm dark:border-gray-800 dark:bg-gray-900/50" data-testid="matrix-active-filters">
<div class="flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between">
<div class="space-y-1 min-w-0">
<div class="text-sm font-semibold text-gray-950 dark:text-white">Current matrix scope</div>
<p class="text-sm leading-6 text-gray-600 dark:text-gray-300">
@if ($activeFilterCount === 0)
No narrowing filters are active. Showing every visible subject and tenant in the current baseline scope.
@else
{{ $activeFilterCount }} active {{ \Illuminate\Support\Str::plural('filter', $activeFilterCount) }} are narrowing the matrix before you scan drift and follow-up links.
@endif
</p>
</div>
<div class="flex flex-wrap items-center gap-2 lg:justify-end">
<x-filament::badge :color="$activeFilterCount === 0 ? 'gray' : 'info'" icon="heroicon-m-funnel" size="sm">
@if ($activeFilterCount === 0)
All visible results
@else
{{ $activeFilterCount }} active {{ \Illuminate\Support\Str::plural('filter', $activeFilterCount) }}
@endif
</x-filament::badge>
@if ($hiddenAssignedTenantCount > 0)
<x-filament::badge color="gray" size="sm">
Visible-set only
</x-filament::badge>
@endif
</div>
</div>
@if ($activeFilterSummary !== [])
<div class="mt-2 flex flex-wrap gap-2">
@foreach ($activeFilterSummary as $label => $value)
<x-filament::badge color="info" size="sm">
{{ $label }}: {{ $value }}
</x-filament::badge>
@endforeach
</div>
@endif
</div>
<form wire:submit.prevent="refreshMatrix">
{{ $this->form }}
</form>
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
<div class="flex-1 rounded-xl border border-dashed border-gray-300 bg-gray-50 px-4 py-2.5 dark:border-gray-700 dark:bg-gray-900/40">
<div class="flex flex-wrap items-center gap-2 text-sm">
<span class="font-semibold text-gray-950 dark:text-white">Focused subject</span>
@if (filled($currentFilters['subject_key'] ?? null))
<x-filament::badge color="info" icon="heroicon-m-funnel" size="sm">
{{ $currentFilters['subject_key'] }}
</x-filament::badge>
<x-filament::button tag="a" :href="$this->clearSubjectFocusUrl()" color="gray" size="sm">
Clear subject focus
</x-filament::button>
@else
<span class="text-gray-500 dark:text-gray-400">None set yet. Use Focus subject from a row when you want a subject-first drilldown.</span>
@endif
</div>
</div>
<div class="flex flex-wrap items-center gap-3 lg:shrink-0">
@if ($activeFilterCount > 0)
<x-filament::button tag="a" :href="\App\Filament\Resources\BaselineProfileResource::compareMatrixUrl($profile)" color="gray" size="sm">
Clear all filters
</x-filament::button>
@else
<x-filament::badge color="gray" size="sm">
No filter reset needed
</x-filament::badge>
@endif
</div>
</div>
<x-filament-actions::modals />
</div>
</x-filament::section>
<div class="relative" data-testid="baseline-compare-matrix-results">
@if (($hasActiveRuns ?? false) === true)
<div class="mb-2 flex justify-end" data-testid="baseline-compare-matrix-auto-refresh-note">
<div class="flex items-center gap-2 rounded-full border border-gray-200 bg-white/95 px-3 py-1.5 text-xs shadow-sm dark:border-gray-800 dark:bg-gray-900/95">
<x-filament::badge color="info" icon="heroicon-m-arrow-path" size="sm">
Auto-refresh every 5 seconds while compare runs are queued or running.
</x-filament::badge>
</div>
</div>
@endif
<div class="space-y-6">
<x-filament::section heading="Matrix signal legend">
<div class="grid gap-3 xl:grid-cols-3">
<div class="rounded-xl border border-gray-200 bg-gray-50/70 px-3 py-2 dark:border-gray-800 dark:bg-gray-900/40">
<div class="flex flex-wrap items-center gap-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">State legend</div>
@foreach ($stateLegend as $item)
<x-filament::badge :color="$item['color']" :icon="$item['icon']" size="sm">
{{ $item['label'] }}
</x-filament::badge>
@endforeach
</div>
</div>
<div class="rounded-xl border border-gray-200 bg-gray-50/70 px-3 py-2 dark:border-gray-800 dark:bg-gray-900/40">
<div class="flex flex-wrap items-center gap-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Freshness legend</div>
@foreach ($freshnessLegend as $item)
<x-filament::badge :color="$item['color']" :icon="$item['icon']" size="sm">
{{ $item['label'] }}
</x-filament::badge>
@endforeach
</div>
</div>
<div class="rounded-xl border border-gray-200 bg-gray-50/70 px-3 py-2 dark:border-gray-800 dark:bg-gray-900/40">
<div class="flex flex-wrap items-center gap-2">
<div class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Trust legend</div>
@foreach ($trustLegend as $item)
<x-filament::badge :color="$item['color']" :icon="$item['icon']" size="sm">
{{ $item['label'] }}
</x-filament::badge>
@endforeach
</div>
</div>
</div>
</x-filament::section>
@if ($emptyState !== null)
<x-filament::section>
<div class="rounded-2xl border border-dashed border-gray-300 bg-gray-50 px-6 py-8 dark:border-gray-700 dark:bg-gray-900/40">
<div class="space-y-2" data-testid="baseline-compare-matrix-empty-state">
<h3 class="text-lg font-semibold text-gray-950 dark:text-white">{{ $emptyState['title'] ?? 'Nothing to show' }}</h3>
<p class="text-sm text-gray-600 dark:text-gray-300">{{ $emptyState['body'] ?? 'Adjust the current inputs and try again.' }}</p>
</div>
</div>
</x-filament::section>
@else
<x-filament::section heading="Tenant summaries">
<x-slot name="description">
Tenant-level freshness, trust, and breadth stay visible before you scan the subject-by-tenant body.
</x-slot>
<div class="grid gap-4 md:grid-cols-2 2xl:grid-cols-3">
@foreach ($tenantSummaries as $tenantSummary)
@php
$freshnessSpec = $freshnessBadge($tenantSummary['freshnessState'] ?? null);
$trustSpec = $trustBadge($tenantSummary['trustLevel'] ?? null);
$tenantSeveritySpec = filled($tenantSummary['maxSeverity'] ?? null) ? $severityBadge($tenantSummary['maxSeverity']) : null;
$tenantCompareUrl = $this->tenantCompareUrl((int) $tenantSummary['tenantId']);
$tenantRunUrl = filled($tenantSummary['compareRunId'] ?? null)
? $this->runUrl((int) $tenantSummary['compareRunId'], (int) $tenantSummary['tenantId'])
: null;
@endphp
<div class="rounded-2xl border border-gray-200 bg-white p-4 shadow-sm dark:border-gray-800 dark:bg-gray-900/70">
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="space-y-1">
<h3 class="text-base font-semibold text-gray-950 dark:text-white">{{ $tenantSummary['tenantName'] }}</h3>
<div class="flex flex-wrap gap-2">
<x-filament::badge :color="$freshnessSpec->color" :icon="$freshnessSpec->icon" size="sm">
{{ $freshnessSpec->label }}
</x-filament::badge>
<x-filament::badge :color="$trustSpec->color" :icon="$trustSpec->icon" size="sm">
{{ $trustSpec->label }}
</x-filament::badge>
@if ($tenantSeveritySpec)
<x-filament::badge :color="$tenantSeveritySpec->color" :icon="$tenantSeveritySpec->icon" size="sm">
{{ $tenantSeveritySpec->label }}
</x-filament::badge>
@endif
</div>
</div>
<div class="text-right text-xs text-gray-500 dark:text-gray-400">
@if (filled($tenantSummary['lastComparedAt'] ?? null))
Compared {{ \Illuminate\Support\Carbon::parse($tenantSummary['lastComparedAt'])->diffForHumans() }}
@else
No completed compare yet
@endif
</div>
</div>
<dl class="mt-4 grid grid-cols-2 gap-3 text-sm">
<div>
<dt class="text-gray-500 dark:text-gray-400">Aligned</dt>
<dd class="font-semibold text-gray-950 dark:text-white">{{ (int) ($tenantSummary['matchedCount'] ?? 0) }}</dd>
</div>
<div>
<dt class="text-gray-500 dark:text-gray-400">Drift</dt>
<dd class="font-semibold text-gray-950 dark:text-white">{{ (int) ($tenantSummary['differingCount'] ?? 0) }}</dd>
</div>
<div>
<dt class="text-gray-500 dark:text-gray-400">Missing</dt>
<dd class="font-semibold text-gray-950 dark:text-white">{{ (int) ($tenantSummary['missingCount'] ?? 0) }}</dd>
</div>
<div>
<dt class="text-gray-500 dark:text-gray-400">Ambiguous / not compared</dt>
<dd class="font-semibold text-gray-950 dark:text-white">
{{ (int) ($tenantSummary['ambiguousCount'] ?? 0) + (int) ($tenantSummary['notComparedCount'] ?? 0) }}
</dd>
</div>
</dl>
<div class="mt-4 flex flex-wrap items-center gap-3 text-sm">
@if ($tenantCompareUrl)
<x-filament::link :href="$tenantCompareUrl" size="sm">
Open tenant compare
</x-filament::link>
@endif
@if ($tenantRunUrl)
<x-filament::link :href="$tenantRunUrl" color="gray" size="sm">
Open latest run
</x-filament::link>
@endif
</div>
</div>
@endforeach
</div>
</x-filament::section>
<x-filament::section heading="Subject-by-tenant matrix">
<x-slot name="description">
Row click is intentionally disabled. The subject column stays pinned while you scan across visible tenants.
</x-slot>
<div class="overflow-x-auto rounded-2xl" data-testid="baseline-compare-matrix-grid">
<div class="min-w-[72rem] overflow-hidden rounded-2xl border border-gray-200 dark:border-gray-800">
<table class="min-w-full border-separate border-spacing-0">
<thead class="bg-gray-50 dark:bg-gray-950/70">
<tr>
<th class="sticky left-0 z-20 w-[26rem] border-r border-gray-200 bg-gray-50 px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-gray-500 dark:border-gray-800 dark:bg-gray-950/70 dark:text-gray-400">
Baseline subject
</th>
@foreach ($tenantSummaries as $tenantSummary)
@php
$freshnessSpec = $freshnessBadge($tenantSummary['freshnessState'] ?? null);
@endphp
<th class="min-w-72 px-4 py-3 text-left align-top text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">
<div class="space-y-2">
<div class="text-sm font-semibold normal-case text-gray-950 dark:text-white">{{ $tenantSummary['tenantName'] }}</div>
<div class="flex flex-wrap gap-2">
<x-filament::badge :color="$freshnessSpec->color" :icon="$freshnessSpec->icon" size="sm">
{{ $freshnessSpec->label }}
</x-filament::badge>
</div>
</div>
</th>
@endforeach
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-800">
@foreach ($rows as $row)
@php
$subject = is_array($row['subject'] ?? null) ? $row['subject'] : [];
$cells = is_array($row['cells'] ?? null) ? $row['cells'] : [];
$subjectSeveritySpec = filled($subject['maxSeverity'] ?? null) ? $severityBadge($subject['maxSeverity']) : null;
$subjectTrustSpec = $trustBadge($subject['trustLevel'] ?? null);
$rowKey = (string) ($subject['subjectKey'] ?? 'subject-'.$loop->index);
$rowSurfaceClasses = $loop->even
? 'bg-gray-50/70 dark:bg-gray-950/20'
: 'bg-white dark:bg-gray-900/60';
@endphp
<tr wire:key="baseline-compare-matrix-row-{{ $rowKey }}" data-testid="baseline-compare-matrix-row" class="group transition-colors hover:bg-primary-50/30 dark:hover:bg-primary-950/10 {{ $rowSurfaceClasses }}">
<td class="sticky left-0 z-10 border-r border-gray-200 px-4 py-4 align-top dark:border-gray-800 {{ $rowSurfaceClasses }}">
<div class="space-y-3">
<div class="space-y-1">
<div class="text-sm font-semibold text-gray-950 dark:text-white">
{{ $subject['displayName'] ?? $subject['subjectKey'] ?? 'Subject' }}
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
{{ $subject['policyType'] ?? 'Unknown policy type' }}
</div>
@if (filled($subject['baselineExternalId'] ?? null))
<div class="text-xs text-gray-500 dark:text-gray-400">
Reference ID: {{ $subject['baselineExternalId'] }}
</div>
@endif
</div>
<div class="flex flex-wrap gap-2">
<x-filament::badge color="gray" size="sm">
Drift breadth {{ (int) ($subject['deviationBreadth'] ?? 0) }}
</x-filament::badge>
<x-filament::badge color="gray" size="sm">
Missing {{ (int) ($subject['missingBreadth'] ?? 0) }}
</x-filament::badge>
<x-filament::badge color="gray" size="sm">
Ambiguous {{ (int) ($subject['ambiguousBreadth'] ?? 0) }}
</x-filament::badge>
<x-filament::badge :color="$subjectTrustSpec->color" :icon="$subjectTrustSpec->icon" size="sm">
{{ $subjectTrustSpec->label }}
</x-filament::badge>
@if ($subjectSeveritySpec)
<x-filament::badge :color="$subjectSeveritySpec->color" :icon="$subjectSeveritySpec->icon" size="sm">
{{ $subjectSeveritySpec->label }}
</x-filament::badge>
@endif
</div>
@if (filled($subject['subjectKey'] ?? null))
<div class="flex flex-wrap gap-3 text-sm">
<x-filament::link :href="$this->filterUrl(['subject_key' => $subject['subjectKey']])" color="gray" size="sm" data-testid="matrix-focus-subject">
Focus subject
</x-filament::link>
</div>
@endif
</div>
</td>
@foreach ($cells as $cell)
@php
$cellStateSpec = $stateBadge($cell['state'] ?? null);
$cellTrustSpec = $trustBadge($cell['trustLevel'] ?? null);
$cellSeveritySpec = filled($cell['severity'] ?? null) ? $severityBadge($cell['severity']) : null;
$cellState = (string) ($cell['state'] ?? '');
$cellSeverity = is_string($cell['severity'] ?? null) ? (string) $cell['severity'] : null;
$cellNeedsAttention = in_array($cellState, ['differ', 'missing', 'ambiguous'], true)
|| in_array($cellSeverity, ['critical', 'high'], true);
$cellNeedsRefresh = in_array($cellState, ['stale_result', 'not_compared'], true);
$cellLooksHealthy = $cellState === 'match' && $cellNeedsAttention === false && $cellNeedsRefresh === false;
$cellSurfaceClasses = $cellNeedsAttention
? 'border-danger-300 bg-danger-50/80 ring-1 ring-danger-200/70 dark:border-danger-900/70 dark:bg-danger-950/15 dark:ring-danger-900/40'
: ($cellNeedsRefresh
? 'border-warning-300 bg-warning-50/80 ring-1 ring-warning-200/70 dark:border-warning-900/70 dark:bg-warning-950/15 dark:ring-warning-900/40'
: ($cellLooksHealthy
? 'border-success-200 bg-success-50/70 dark:border-success-900/60 dark:bg-success-950/10'
: 'border-gray-200 bg-gray-50 dark:border-gray-800 dark:bg-gray-950/40'));
$cellPriorityLabel = $cellNeedsAttention
? 'Needs attention'
: ($cellNeedsRefresh ? 'Refresh recommended' : ($cellLooksHealthy ? 'Aligned' : 'Review'));
$cellPriorityClasses = $cellNeedsAttention
? 'bg-danger-100 text-danger-700 dark:bg-danger-950/40 dark:text-danger-300'
: ($cellNeedsRefresh
? 'bg-warning-100 text-warning-700 dark:bg-warning-950/40 dark:text-warning-300'
: 'bg-success-100 text-success-700 dark:bg-success-950/40 dark:text-success-300');
$subjectKey = $subject['subjectKey'] ?? ($cell['subjectKey'] ?? null);
$tenantId = (int) ($cell['tenantId'] ?? 0);
$tenantCompareUrl = $tenantId > 0 ? $this->tenantCompareUrl($tenantId, $subjectKey) : null;
$cellFindingUrl = ($tenantId > 0 && filled($cell['findingId'] ?? null))
? $this->findingUrl($tenantId, (int) $cell['findingId'], $subjectKey)
: null;
$cellRunUrl = filled($cell['compareRunId'] ?? null)
? $this->runUrl((int) $cell['compareRunId'], $tenantId, $subjectKey)
: null;
@endphp
<td wire:key="baseline-compare-matrix-cell-{{ $rowKey }}-{{ $tenantId > 0 ? $tenantId : $loop->index }}" class="px-4 py-4 align-top">
<div class="space-y-3 rounded-xl border p-3 transition-colors group-hover:border-primary-200 group-hover:bg-white dark:group-hover:border-primary-900 dark:group-hover:bg-gray-950/60 {{ $cellSurfaceClasses }}">
<div class="flex flex-wrap items-start justify-between gap-2">
<div class="flex flex-wrap gap-2">
<x-filament::badge :color="$cellStateSpec->color" :icon="$cellStateSpec->icon" size="sm">
{{ $cellStateSpec->label }}
</x-filament::badge>
@if ($cellSeveritySpec)
<x-filament::badge :color="$cellSeveritySpec->color" :icon="$cellSeveritySpec->icon" size="sm">
{{ $cellSeveritySpec->label }}
</x-filament::badge>
@endif
</div>
<span class="inline-flex items-center rounded-full px-2 py-1 text-[11px] font-semibold uppercase tracking-wide {{ $cellPriorityClasses }}">
{{ $cellPriorityLabel }}
</span>
</div>
<div class="flex flex-wrap gap-2">
<x-filament::badge :color="$cellTrustSpec->color" :icon="$cellTrustSpec->icon" size="sm">
{{ $cellTrustSpec->label }}
</x-filament::badge>
</div>
<div class="space-y-1 text-xs text-gray-600 dark:text-gray-300">
@if (filled($cell['reasonCode'] ?? null))
<div>
Reason: {{ \Illuminate\Support\Str::headline(str_replace('_', ' ', (string) $cell['reasonCode'])) }}
</div>
@endif
@if (filled($cell['lastComparedAt'] ?? null))
<div>
Compared {{ \Illuminate\Support\Carbon::parse($cell['lastComparedAt'])->diffForHumans() }}
</div>
@endif
@if (($cell['policyTypeCovered'] ?? true) === false)
<div>Policy type coverage was not proven in the latest compare run.</div>
@endif
</div>
<div class="flex flex-wrap gap-3 text-sm">
@if ($cellFindingUrl)
<x-filament::link :href="$cellFindingUrl" size="sm">
Open finding
</x-filament::link>
@elseif ($tenantCompareUrl)
<x-filament::link :href="$tenantCompareUrl" size="sm">
Open tenant compare
</x-filament::link>
@endif
@if ($cellRunUrl)
<x-filament::link :href="$cellRunUrl" color="gray" size="sm">
Open run
</x-filament::link>
@endif
</div>
</div>
</td>
@endforeach
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</x-filament::section>
@endif
</div>
</div>
</x-filament::page>