Implemented the output contract and readiness semantics for review packs. Also added spec 348. Includes changes to ChooseEnvironment, CustomerReviewWorkspace, GenerateReviewPackJob and related blade views. Added comprehensive tests. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #419
206 lines
14 KiB
PHP
206 lines
14 KiB
PHP
<x-filament-panels::page>
|
|
@php
|
|
$allTenants = $this->getTenants();
|
|
$tenants = $this->getVisibleTenants($allTenants);
|
|
$workspace = app(\App\Support\Workspaces\WorkspaceContext::class)->currentWorkspace();
|
|
$environmentCount = $allTenants->count();
|
|
$visibleEnvironmentCount = $tenants->count();
|
|
$hasSearch = trim($this->search) !== '';
|
|
@endphp
|
|
|
|
@if ($allTenants->isEmpty())
|
|
{{-- Empty state --}}
|
|
<div class="mx-auto max-w-md">
|
|
<div class="rounded-xl border border-gray-200 bg-white p-8 text-center shadow-sm dark:border-white/10 dark:bg-white/5">
|
|
@if ($workspace)
|
|
<div class="mb-5 inline-flex items-center gap-1.5 rounded-full border border-gray-200 bg-gray-50 px-3 py-1 text-xs font-medium text-gray-600 dark:border-white/10 dark:bg-white/5 dark:text-gray-400">
|
|
<x-filament::icon icon="heroicon-m-building-office-2" class="h-3.5 w-3.5" />
|
|
{{ $workspace->name }}
|
|
</div>
|
|
@endif
|
|
|
|
<div class="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-full bg-primary-50 dark:bg-primary-950/30">
|
|
<x-filament::icon
|
|
icon="heroicon-o-server-stack"
|
|
class="h-7 w-7 text-primary-500 dark:text-primary-400"
|
|
/>
|
|
</div>
|
|
|
|
<h3 class="text-base font-semibold text-gray-900 dark:text-white">{{ __('localization.shell.no_active_environments') }}</h3>
|
|
<p class="mx-auto mt-2 max-w-xs text-sm text-gray-500 dark:text-gray-400">
|
|
{{ __('localization.shell.no_active_environments_description') }}
|
|
</p>
|
|
|
|
<div class="mt-6 flex flex-col items-center gap-3">
|
|
<x-filament::button
|
|
tag="a"
|
|
href="{{ $workspace ? route('admin.workspace.managed-environments.index', ['workspace' => $workspace]) : route('admin.onboarding') }}"
|
|
icon="heroicon-m-arrow-right"
|
|
size="lg"
|
|
>
|
|
{{ __('localization.shell.view_managed_environments') }}
|
|
</x-filament::button>
|
|
|
|
<a href="{{ route('filament.admin.pages.choose-workspace') }}"
|
|
class="inline-flex items-center gap-1.5 text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
|
<x-filament::icon icon="heroicon-m-arrows-right-left" class="h-4 w-4" />
|
|
{{ __('localization.shell.switch_workspace') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@else
|
|
{{-- ManagedEnvironment list --}}
|
|
<div class="mx-auto max-w-xl" data-testid="choose-environment-selector">
|
|
{{-- Header row --}}
|
|
<div class="mb-5 space-y-3">
|
|
<div class="flex min-w-0 flex-wrap items-center gap-2">
|
|
@if ($workspace)
|
|
<div class="inline-flex max-w-full min-w-0 items-center gap-1.5 rounded-full border border-gray-200 bg-gray-50 px-3 py-1 text-xs font-medium text-gray-600 dark:border-white/10 dark:bg-white/5 dark:text-gray-400">
|
|
<x-filament::icon icon="heroicon-m-building-office-2" class="h-3.5 w-3.5 shrink-0" />
|
|
<span class="min-w-0 truncate" title="{{ $workspace->name }}">{{ $workspace->name }}</span>
|
|
</div>
|
|
@endif
|
|
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
{{ trans_choice('localization.shell.environment_count', $environmentCount, ['count' => $environmentCount]) }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
<a href="{{ route('admin.onboarding') }}"
|
|
data-testid="choose-environment-add"
|
|
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm font-medium text-gray-600 shadow-sm transition-colors hover:bg-gray-50 hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:hover:bg-white/10 dark:hover:text-white dark:focus:ring-offset-gray-900">
|
|
<x-filament::icon icon="heroicon-m-plus" class="h-4 w-4" />
|
|
{{ __('localization.shell.add_environment') }}
|
|
</a>
|
|
<a href="{{ route('filament.admin.pages.choose-workspace') }}"
|
|
data-testid="choose-environment-switch-workspace"
|
|
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm font-medium text-gray-600 shadow-sm transition-colors hover:bg-gray-50 hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:hover:bg-white/10 dark:hover:text-white dark:focus:ring-offset-gray-900">
|
|
<x-filament::icon icon="heroicon-m-arrows-right-left" class="h-4 w-4" />
|
|
{{ __('localization.shell.switch_workspace') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="mb-2 text-sm text-gray-500 dark:text-gray-400">{{ __('localization.shell.choose_environment_description') }}</p>
|
|
<p class="mb-4 text-xs text-gray-500 dark:text-gray-400">{{ __('localization.shell.workspace_wide_available_without_environment') }}</p>
|
|
|
|
<div class="mb-4 space-y-2" data-testid="choose-environment-filter">
|
|
<label for="choose-environment-search" class="fi-sr-only">{{ __('localization.shell.search_environments') }}</label>
|
|
<x-filament::input.wrapper
|
|
:prefix-icon="\Filament\Support\Icons\Heroicon::MagnifyingGlass"
|
|
inline-prefix
|
|
class="w-full"
|
|
wire:target="search"
|
|
>
|
|
<x-filament::input
|
|
id="choose-environment-search"
|
|
type="search"
|
|
wire:model.live.debounce.250ms="search"
|
|
placeholder="{{ __('localization.shell.search_environments') }}"
|
|
data-testid="choose-environment-search"
|
|
inline-prefix
|
|
/>
|
|
</x-filament::input.wrapper>
|
|
|
|
@if ($hasSearch)
|
|
<div class="mt-2 flex flex-wrap items-center justify-between gap-2 text-xs text-gray-500 dark:text-gray-400">
|
|
<span>{{ __('localization.shell.environment_search_results_count', ['visible' => $visibleEnvironmentCount, 'total' => $environmentCount]) }}</span>
|
|
<button
|
|
type="button"
|
|
wire:click="$set('search', '')"
|
|
class="font-medium text-gray-600 transition hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:text-gray-300 dark:hover:text-white dark:focus:ring-offset-gray-900"
|
|
>
|
|
{{ __('localization.shell.clear_search') }}
|
|
</button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- ManagedEnvironment cards --}}
|
|
@if ($tenants->isEmpty())
|
|
<div class="rounded-xl border border-gray-200 bg-white p-6 text-center shadow-sm dark:border-white/10 dark:bg-white/5" data-testid="choose-environment-no-results">
|
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-white">{{ __('localization.shell.no_environment_search_results') }}</h3>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{{ __('localization.shell.no_environment_search_results_description') }}</p>
|
|
<button
|
|
type="button"
|
|
wire:click="$set('search', '')"
|
|
class="mt-4 inline-flex items-center gap-1.5 rounded-lg border border-gray-200 bg-white px-3 py-1.5 text-sm font-medium text-gray-600 shadow-sm transition hover:bg-gray-50 hover:text-gray-800 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:border-white/10 dark:bg-white/5 dark:text-gray-300 dark:hover:bg-white/10 dark:hover:text-white dark:focus:ring-offset-gray-900"
|
|
>
|
|
{{ __('localization.shell.clear_search') }}
|
|
</button>
|
|
</div>
|
|
@else
|
|
<div class="grid grid-cols-1 gap-3" data-testid="choose-environment-results">
|
|
@foreach ($tenants as $tenant)
|
|
@php
|
|
$presentation = $this->tenantLifecyclePresentation($tenant);
|
|
$badgeClasses = match ($presentation->badgeColor) {
|
|
'success' => 'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-200',
|
|
'warning' => 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-500/20 dark:bg-amber-500/10 dark:text-amber-200',
|
|
'danger' => 'border-rose-200 bg-rose-50 text-rose-700 dark:border-rose-500/20 dark:bg-rose-500/10 dark:text-rose-200',
|
|
default => 'border-gray-200 bg-gray-100 text-gray-700 dark:border-white/10 dark:bg-white/10 dark:text-gray-300',
|
|
};
|
|
$environmentLabel = $tenant->environment && $tenant->environment !== 'managed_environment'
|
|
? strtoupper($tenant->environment)
|
|
: null;
|
|
@endphp
|
|
<button
|
|
type="button"
|
|
wire:key="tenant-{{ $tenant->id }}"
|
|
wire:click="selectEnvironment({{ (int) $tenant->id }})"
|
|
aria-label="{{ __('localization.shell.select_environment') }}: {{ $tenant->name }}"
|
|
data-testid="choose-environment-card"
|
|
class="group relative w-full overflow-hidden rounded-xl border border-gray-200 bg-white p-4 text-left shadow-sm transition-all duration-150 hover:border-gray-300 hover:shadow-md focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:border-white/10 dark:bg-white/5 dark:hover:border-white/20 dark:focus:ring-offset-gray-900"
|
|
>
|
|
{{-- Loading overlay --}}
|
|
<div wire:loading wire:target="selectEnvironment({{ (int) $tenant->id }})"
|
|
class="absolute inset-0 z-10 flex items-center justify-center rounded-xl bg-white/80 dark:bg-gray-900/80">
|
|
<x-filament::loading-indicator class="h-5 w-5 text-primary-500" />
|
|
</div>
|
|
|
|
<div class="flex min-w-0 items-start gap-3">
|
|
<div class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-gray-100 transition-colors group-hover:bg-gray-200 dark:bg-white/10 dark:group-hover:bg-white/15">
|
|
<x-filament::icon
|
|
icon="heroicon-o-server-stack"
|
|
class="h-5 w-5 text-gray-500 group-hover:text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-300"
|
|
/>
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
<div class="min-w-0">
|
|
<h3 class="truncate text-sm font-semibold text-gray-900 dark:text-white">
|
|
{{ $tenant->name }}
|
|
</h3>
|
|
@if ($tenant->domain)
|
|
<p class="mt-0.5 truncate text-xs text-gray-500 dark:text-gray-400">
|
|
{{ $tenant->domain }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="flex max-w-full shrink-0 flex-wrap items-center gap-2 sm:max-w-48 sm:justify-end">
|
|
@if ($environmentLabel)
|
|
<span class="inline-flex max-w-full items-center truncate rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-white/10 dark:text-gray-400">
|
|
{{ $environmentLabel }}
|
|
</span>
|
|
@endif
|
|
<span class="inline-flex max-w-full items-center truncate rounded-full border px-2 py-0.5 text-xs font-medium {{ $badgeClasses }}">
|
|
{{ $presentation->label }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<p class="mt-2 text-xs leading-5 text-gray-500 dark:text-gray-400">
|
|
{{ $presentation->shortDescription }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</x-filament-panels::page>
|