## Summary - add the tenant review domain with tenant-scoped review library, canonical workspace review register, lifecycle actions, and review-derived executive pack export - extend review pack, operations, audit, capability, and badge infrastructure to support review composition, publication, export, and recurring review cycles - add product backlog and audit documentation updates for tenant review and semantic-clarity follow-up candidates ## Testing - `vendor/bin/sail bin pint --dirty --format agent` - `vendor/bin/sail artisan test --compact --filter="TenantReview"` - `CI=1 vendor/bin/sail artisan test --compact` ## Notes - Livewire v4+ compliant via existing Filament v5 stack - panel providers remain in `bootstrap/providers.php` via existing Laravel 12 structure; no provider registration moved to `bootstrap/app.php` - `TenantReviewResource` is not globally searchable, so the Filament edit/view global-search constraint does not apply - destructive review actions use action handlers with confirmation and policy enforcement Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #185
111 lines
5.1 KiB
PHP
111 lines
5.1 KiB
PHP
@php
|
|
$state = $getState();
|
|
$state = is_array($state) ? $state : [];
|
|
|
|
$summary = is_array($state['summary'] ?? null) ? $state['summary'] : [];
|
|
$highlights = is_array($state['highlights'] ?? null) ? $state['highlights'] : [];
|
|
$entries = is_array($state['entries'] ?? null) ? $state['entries'] : [];
|
|
$nextActions = is_array($state['next_actions'] ?? null) ? $state['next_actions'] : [];
|
|
$links = is_array($state['links'] ?? null) ? $state['links'] : [];
|
|
$disclosure = is_string($state['disclosure'] ?? null) ? $state['disclosure'] : null;
|
|
$emptyState = is_string($state['empty_state'] ?? null) ? $state['empty_state'] : null;
|
|
@endphp
|
|
|
|
<div class="space-y-3">
|
|
@if ($summary !== [])
|
|
<dl class="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
|
|
@foreach ($summary as $item)
|
|
@php
|
|
$label = is_string($item['label'] ?? null) ? $item['label'] : null;
|
|
$value = is_string($item['value'] ?? null) ? $item['value'] : null;
|
|
@endphp
|
|
|
|
@continue($label === null || $value === null)
|
|
|
|
<div class="rounded-md border border-gray-100 bg-gray-50 px-3 py-2 dark:border-gray-800 dark:bg-gray-950/60">
|
|
<dt class="text-[11px] font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">{{ $label }}</dt>
|
|
<dd class="mt-1 text-sm text-gray-900 dark:text-gray-100">{{ $value }}</dd>
|
|
</div>
|
|
@endforeach
|
|
</dl>
|
|
@endif
|
|
|
|
@if ($highlights !== [])
|
|
<ul class="space-y-1 text-sm text-gray-700 dark:text-gray-300">
|
|
@foreach ($highlights as $highlight)
|
|
@continue(! is_string($highlight) || trim($highlight) === '')
|
|
|
|
<li class="rounded-md border border-gray-100 bg-gray-50 px-3 py-2 dark:border-gray-800 dark:bg-gray-950/60">{{ $highlight }}</li>
|
|
@endforeach
|
|
</ul>
|
|
@endif
|
|
|
|
@if ($entries !== [])
|
|
<div class="space-y-2">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Key entries</div>
|
|
<div class="space-y-2">
|
|
@foreach ($entries as $entry)
|
|
@continue(! is_array($entry))
|
|
|
|
<div class="rounded-md border border-gray-100 bg-gray-50 px-3 py-3 text-sm dark:border-gray-800 dark:bg-gray-950/60">
|
|
<div class="font-medium text-gray-900 dark:text-gray-100">
|
|
{{ $entry['title'] ?? $entry['displayName'] ?? $entry['type'] ?? 'Entry' }}
|
|
</div>
|
|
|
|
@php
|
|
$detailParts = collect([
|
|
$entry['severity'] ?? null,
|
|
$entry['status'] ?? null,
|
|
$entry['governance_state'] ?? null,
|
|
$entry['outcome'] ?? null,
|
|
])->filter(fn ($value) => is_string($value) && trim($value) !== '')->map(fn (string $value) => \Illuminate\Support\Str::headline($value))->all();
|
|
@endphp
|
|
|
|
@if ($detailParts !== [])
|
|
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ implode(' · ', $detailParts) }}</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@elseif ($emptyState)
|
|
<div class="rounded-md border border-gray-100 bg-gray-50 px-3 py-2 text-sm text-gray-700 dark:border-gray-800 dark:bg-gray-950/60 dark:text-gray-300">
|
|
{{ $emptyState }}
|
|
</div>
|
|
@endif
|
|
|
|
@if ($disclosure)
|
|
<div class="rounded-md border border-amber-100 bg-amber-50 px-3 py-2 text-sm text-amber-800 dark:border-amber-900/40 dark:bg-amber-950/30 dark:text-amber-200">
|
|
{{ $disclosure }}
|
|
</div>
|
|
@endif
|
|
|
|
@if ($nextActions !== [])
|
|
<div class="space-y-2">
|
|
<div class="text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Follow-up</div>
|
|
<ul class="space-y-1 text-sm text-gray-700 dark:text-gray-300">
|
|
@foreach ($nextActions as $action)
|
|
@continue(! is_string($action) || trim($action) === '')
|
|
|
|
<li class="rounded-md border border-primary-100 bg-primary-50 px-3 py-2 dark:border-primary-900/40 dark:bg-primary-950/30">{{ $action }}</li>
|
|
@endforeach
|
|
</ul>
|
|
</div>
|
|
@endif
|
|
|
|
@if ($links !== [])
|
|
<div class="flex flex-wrap gap-2">
|
|
@foreach ($links as $link)
|
|
@continue(! is_array($link) || ! is_string($link['label'] ?? null) || ! is_string($link['url'] ?? null))
|
|
|
|
<a
|
|
class="inline-flex items-center rounded-lg border border-gray-300 px-3 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-50 dark:border-gray-700 dark:text-gray-200 dark:hover:bg-gray-900"
|
|
href="{{ $link['url'] }}"
|
|
>
|
|
{{ $link['label'] }}
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|