## 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
184 lines
7.0 KiB
PHP
184 lines
7.0 KiB
PHP
@php
|
|
use App\Support\Badges\BadgeCatalog;
|
|
use App\Support\Badges\BadgeDomain;
|
|
use App\Support\ReviewPackStatus;
|
|
|
|
/** @var ?\App\Models\Tenant $tenant */
|
|
/** @var ?\App\Models\ReviewPack $pack */
|
|
/** @var ?ReviewPackStatus $statusEnum */
|
|
/** @var ?string $pollingInterval */
|
|
/** @var bool $canView */
|
|
/** @var bool $canManage */
|
|
/** @var ?string $downloadUrl */
|
|
/** @var ?string $failedReason */
|
|
/** @var ?string $reviewUrl */
|
|
|
|
$badgeSpec = $statusEnum ? BadgeCatalog::spec(BadgeDomain::ReviewPackStatus, $statusEnum->value) : null;
|
|
@endphp
|
|
|
|
<div
|
|
@if ($pollingInterval)
|
|
wire:poll.{{ $pollingInterval }}
|
|
@endif
|
|
>
|
|
<x-filament::section heading="Review Pack">
|
|
@if (! $pack)
|
|
{{-- State 1: No pack --}}
|
|
<div class="flex flex-col items-center gap-3 py-4 text-center">
|
|
<x-heroicon-o-document-arrow-down class="h-8 w-8 text-gray-400 dark:text-gray-500" />
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
No review pack generated yet.
|
|
</div>
|
|
|
|
@if ($canManage)
|
|
<x-filament::button
|
|
size="sm"
|
|
wire:click="generatePack"
|
|
wire:loading.attr="disabled"
|
|
>
|
|
Generate pack
|
|
</x-filament::button>
|
|
@endif
|
|
</div>
|
|
@elseif ($statusEnum === ReviewPackStatus::Queued || $statusEnum === ReviewPackStatus::Generating)
|
|
{{-- State 2: Queued / Generating --}}
|
|
<div class="flex flex-col gap-3">
|
|
<div class="flex items-center gap-2">
|
|
<x-filament::badge
|
|
:color="$badgeSpec?->color"
|
|
:icon="$badgeSpec?->icon"
|
|
>
|
|
{{ $badgeSpec?->label ?? '—' }}
|
|
</x-filament::badge>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400">
|
|
<x-filament::loading-indicator class="h-4 w-4" />
|
|
Generation in progress…
|
|
</div>
|
|
|
|
<div class="text-xs text-gray-400 dark:text-gray-500">
|
|
Started {{ $pack->created_at?->diffForHumans() ?? '—' }}
|
|
</div>
|
|
</div>
|
|
@elseif ($statusEnum === ReviewPackStatus::Ready)
|
|
{{-- State 3: Ready --}}
|
|
<div class="flex flex-col gap-3">
|
|
<div class="flex items-center gap-2">
|
|
<x-filament::badge
|
|
:color="$badgeSpec?->color"
|
|
:icon="$badgeSpec?->icon"
|
|
>
|
|
{{ $badgeSpec?->label ?? '—' }}
|
|
</x-filament::badge>
|
|
</div>
|
|
|
|
<dl class="grid grid-cols-2 gap-x-4 gap-y-1 text-sm">
|
|
<dt class="text-gray-500 dark:text-gray-400">Generated</dt>
|
|
<dd>{{ $pack->generated_at?->format('M j, Y H:i') ?? '—' }}</dd>
|
|
|
|
<dt class="text-gray-500 dark:text-gray-400">Expires</dt>
|
|
<dd>{{ $pack->expires_at?->format('M j, Y') ?? '—' }}</dd>
|
|
|
|
<dt class="text-gray-500 dark:text-gray-400">Size</dt>
|
|
<dd>{{ $pack->file_size ? Number::fileSize($pack->file_size) : '—' }}</dd>
|
|
</dl>
|
|
|
|
<div class="flex items-center gap-2">
|
|
@if ($canView && $downloadUrl)
|
|
<x-filament::button
|
|
size="sm"
|
|
tag="a"
|
|
:href="$downloadUrl"
|
|
target="_blank"
|
|
icon="heroicon-o-arrow-down-tray"
|
|
>
|
|
Download
|
|
</x-filament::button>
|
|
@endif
|
|
|
|
@if ($canView && $reviewUrl)
|
|
<x-filament::button
|
|
size="sm"
|
|
color="gray"
|
|
tag="a"
|
|
:href="$reviewUrl"
|
|
>
|
|
View review
|
|
</x-filament::button>
|
|
@endif
|
|
|
|
@if ($canManage)
|
|
<x-filament::button
|
|
size="sm"
|
|
color="gray"
|
|
wire:click="generatePack"
|
|
wire:loading.attr="disabled"
|
|
>
|
|
Generate new
|
|
</x-filament::button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
@elseif ($statusEnum === ReviewPackStatus::Failed)
|
|
{{-- State 4: Failed --}}
|
|
<div class="flex flex-col gap-3">
|
|
<div class="flex items-center gap-2">
|
|
<x-filament::badge
|
|
:color="$badgeSpec?->color"
|
|
:icon="$badgeSpec?->icon"
|
|
>
|
|
{{ $badgeSpec?->label ?? '—' }}
|
|
</x-filament::badge>
|
|
</div>
|
|
|
|
@if ($failedReason)
|
|
<div class="text-sm text-danger-600 dark:text-danger-400">
|
|
{{ $failedReason }}
|
|
</div>
|
|
@endif
|
|
|
|
<div class="text-xs text-gray-400 dark:text-gray-500">
|
|
{{ $pack->updated_at?->diffForHumans() ?? '—' }}
|
|
</div>
|
|
|
|
@if ($canManage)
|
|
<x-filament::button
|
|
size="sm"
|
|
wire:click="generatePack"
|
|
wire:loading.attr="disabled"
|
|
>
|
|
Retry
|
|
</x-filament::button>
|
|
@endif
|
|
</div>
|
|
@elseif ($statusEnum === ReviewPackStatus::Expired)
|
|
{{-- State 5: Expired --}}
|
|
<div class="flex flex-col gap-3">
|
|
<div class="flex items-center gap-2">
|
|
<x-filament::badge
|
|
:color="$badgeSpec?->color"
|
|
:icon="$badgeSpec?->icon"
|
|
>
|
|
{{ $badgeSpec?->label ?? '—' }}
|
|
</x-filament::badge>
|
|
</div>
|
|
|
|
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
Expired {{ $pack->expires_at?->diffForHumans() ?? '—' }}
|
|
</div>
|
|
|
|
@if ($canManage)
|
|
<x-filament::button
|
|
size="sm"
|
|
wire:click="generatePack"
|
|
wire:loading.attr="disabled"
|
|
>
|
|
Generate new
|
|
</x-filament::button>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</x-filament::section>
|
|
</div>
|