283 lines
18 KiB
PHP
283 lines
18 KiB
PHP
@php
|
|
$overviewSecondaryListStackClasses = 'flex flex-col gap-2';
|
|
$overviewSecondaryListRowBaseClasses = 'min-w-0 rounded-xl border p-4 shadow-sm';
|
|
$overviewSecondaryListRowSurfaceClasses = 'border-gray-200 bg-white/80 dark:border-white/10 dark:bg-white/5';
|
|
$overviewSecondaryListInteractiveClasses = 'transition duration-150 hover:shadow-md hover:ring-1 hover:ring-gray-950/5 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 dark:hover:ring-white/10';
|
|
@endphp
|
|
|
|
<div
|
|
@if ($pollingInterval)
|
|
wire:poll.{{ $pollingInterval }}
|
|
@endif
|
|
data-testid="tenant-dashboard-overview"
|
|
class="grid w-full min-w-0 gap-6 xl:grid-cols-12"
|
|
style="grid-column: 1 / -1;"
|
|
>
|
|
<!-- Left Column (Main) -->
|
|
<div data-testid="tenant-dashboard-overview-main" class="flex w-full min-w-0 flex-col gap-6 xl:col-span-8">
|
|
<!-- Recommended Actions -->
|
|
<x-filament::section :heading="__('localization.dashboard.overview.section_recommended_actions')">
|
|
@if ($recommendedActions === [])
|
|
<div data-testid="tenant-dashboard-recommended-actions-empty" class="rounded-xl border border-success-200 bg-success-50/80 p-5 dark:border-success-800 dark:bg-success-500/10">
|
|
<div class="text-sm font-semibold text-success-700 dark:text-success-300">{{ __('localization.dashboard.overview.empty_recommended_actions_headline') }}</div>
|
|
<p class="mt-2 text-sm leading-6 text-success-700/90 dark:text-success-200/90">
|
|
{{ __('localization.dashboard.overview.empty_recommended_actions_summary') }}
|
|
</p>
|
|
</div>
|
|
@else
|
|
<div data-testid="tenant-dashboard-recommended-actions" class="grid min-w-0 gap-4">
|
|
@foreach (array_slice($recommendedActions, 0, 3) as $index => $action)
|
|
<div data-testid="tenant-dashboard-recommended-action" data-action-key="{{ $action['key'] }}" class="flex min-w-0 items-start gap-4 rounded-xl border border-gray-200 bg-white p-5 shadow-sm dark:border-white/10 dark:bg-white/5 max-sm:flex-col max-sm:items-stretch">
|
|
<div data-testid="tenant-dashboard-recommended-action-priority" class="flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-gray-200 bg-gray-50 text-sm font-semibold text-gray-700 dark:border-white/10 dark:bg-white/5 dark:text-gray-200">
|
|
{{ $index + 1 }}
|
|
</div>
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-center gap-2">
|
|
@if (filled($action['icon'] ?? null))
|
|
<x-filament::icon
|
|
data-testid="tenant-dashboard-recommended-action-icon"
|
|
data-action-key="{{ $action['key'] }}"
|
|
data-icon="{{ $action['icon'] }}"
|
|
:icon="$action['icon']"
|
|
class="h-4 w-4 shrink-0 text-gray-400 dark:text-gray-500"
|
|
/>
|
|
@endif
|
|
|
|
<h3 class="min-w-0 text-sm font-semibold text-gray-950 dark:text-white">{{ $action['title'] }}</h3>
|
|
</div>
|
|
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
|
<span class="font-medium text-gray-700 dark:text-gray-300">Reason:</span> {{ $action['reason'] }}
|
|
</p>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-500">
|
|
<span class="font-medium text-gray-700 dark:text-gray-300">Impact:</span> {{ $action['impact'] }}
|
|
</p>
|
|
</div>
|
|
@if (filled($action['actionUrl'] ?? null))
|
|
<div class="shrink-0 max-sm:ml-0 sm:ml-4">
|
|
<x-filament::button data-testid="tenant-dashboard-secondary-action" :href="$action['actionUrl']" tag="a" color="gray" size="sm">
|
|
{{ $action['actionLabel'] ?? 'Review' }}
|
|
</x-filament::button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</x-filament::section>
|
|
|
|
<!-- Governance Status -->
|
|
<x-filament::section :heading="__('localization.dashboard.overview.section_governance_status')">
|
|
<div class="{{ $overviewSecondaryListStackClasses }}">
|
|
@foreach ($governanceStatus as $status)
|
|
@php
|
|
$isGovernanceStatusInteractive = filled($status['actionUrl'] ?? null);
|
|
$governanceStatusClasses = $isGovernanceStatusInteractive
|
|
? "{$overviewSecondaryListRowBaseClasses} {$overviewSecondaryListRowSurfaceClasses} {$overviewSecondaryListInteractiveClasses} flex items-start justify-between gap-4"
|
|
: "{$overviewSecondaryListRowBaseClasses} {$overviewSecondaryListRowSurfaceClasses} flex items-start justify-between gap-4";
|
|
@endphp
|
|
|
|
@if ($isGovernanceStatusInteractive)
|
|
<a
|
|
data-testid="tenant-dashboard-governance-status"
|
|
data-overview-row-style="secondary-list-row"
|
|
data-status-key="{{ $status['key'] ?? '' }}"
|
|
data-governance-interactive="true"
|
|
href="{{ $status['actionUrl'] }}"
|
|
class="{{ $governanceStatusClasses }}"
|
|
>
|
|
<div class="min-w-0">
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
@if (filled($status['icon'] ?? null))
|
|
<x-filament::icon
|
|
data-testid="tenant-dashboard-governance-status-icon"
|
|
data-status-key="{{ $status['key'] ?? '' }}"
|
|
data-icon="{{ $status['icon'] }}"
|
|
:icon="$status['icon']"
|
|
class="h-4 w-4 shrink-0 text-gray-400 dark:text-gray-500"
|
|
/>
|
|
@endif
|
|
|
|
<div class="truncate text-sm font-semibold text-gray-900 dark:text-white">{{ $status['label'] }}</div>
|
|
</div>
|
|
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ $status['description'] }}</div>
|
|
</div>
|
|
<div class="ml-4 shrink-0 pt-0.5">
|
|
<x-filament::badge :color="$status['tone']">{{ $status['value'] }}</x-filament::badge>
|
|
</div>
|
|
</a>
|
|
@else
|
|
<div
|
|
data-testid="tenant-dashboard-governance-status"
|
|
data-overview-row-style="secondary-list-row"
|
|
data-status-key="{{ $status['key'] ?? '' }}"
|
|
data-governance-interactive="false"
|
|
class="{{ $governanceStatusClasses }}"
|
|
>
|
|
<div class="min-w-0">
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
@if (filled($status['icon'] ?? null))
|
|
<x-filament::icon
|
|
data-testid="tenant-dashboard-governance-status-icon"
|
|
data-status-key="{{ $status['key'] ?? '' }}"
|
|
data-icon="{{ $status['icon'] }}"
|
|
:icon="$status['icon']"
|
|
class="h-4 w-4 shrink-0 text-gray-400 dark:text-gray-500"
|
|
/>
|
|
@endif
|
|
|
|
<div class="truncate text-sm font-semibold text-gray-900 dark:text-white">{{ $status['label'] }}</div>
|
|
</div>
|
|
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ $status['description'] }}</div>
|
|
</div>
|
|
<div class="ml-4 shrink-0 pt-0.5">
|
|
<x-filament::badge :color="$status['tone']">{{ $status['value'] }}</x-filament::badge>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
</x-filament::section>
|
|
|
|
<!-- Recent Operations -->
|
|
<x-filament::section :heading="__('localization.dashboard.overview.section_recent_operations')">
|
|
@if ($recentOperations === [])
|
|
<div data-testid="tenant-dashboard-recent-operations-empty" class="rounded-xl border border-gray-200 bg-gray-50 p-5 dark:border-white/10 dark:bg-white/5">
|
|
<div class="text-sm font-semibold text-gray-950 dark:text-white">{{ __('localization.dashboard.overview.empty_recent_operations_headline') }}</div>
|
|
<p class="mt-2 text-sm leading-6 text-gray-600 dark:text-gray-400">
|
|
{{ __('localization.dashboard.overview.empty_recent_operations_summary') }}
|
|
</p>
|
|
</div>
|
|
@else
|
|
<div class="flex flex-col gap-3">
|
|
@foreach (array_slice($recentOperations, 0, 4) as $operation)
|
|
@php
|
|
$operationTone = match ($operation['outcomeTone']) {
|
|
'danger' => 'border-danger-200 bg-danger-50/10 dark:border-danger-800 dark:bg-danger-500/5',
|
|
'warning' => 'border-warning-200 bg-warning-50/10 dark:border-warning-800 dark:bg-warning-500/5',
|
|
default => $overviewSecondaryListRowSurfaceClasses,
|
|
};
|
|
@endphp
|
|
<a
|
|
data-testid="tenant-dashboard-recent-operation"
|
|
data-overview-row-style="secondary-list-row"
|
|
href="{{ $operation['url'] }}"
|
|
class="{{ $overviewSecondaryListRowBaseClasses }} {{ $overviewSecondaryListInteractiveClasses }} {{ $operationTone }}"
|
|
>
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div class="min-w-0">
|
|
<div class="flex items-center gap-2">
|
|
@if (filled($operation['icon'] ?? null))
|
|
<x-filament::icon
|
|
data-testid="tenant-dashboard-recent-operation-icon"
|
|
data-operation-id="{{ $operation['id'] }}"
|
|
data-icon="{{ $operation['icon'] }}"
|
|
:icon="$operation['icon']"
|
|
class="h-4 w-4 shrink-0 text-gray-400 dark:text-gray-500"
|
|
/>
|
|
@endif
|
|
|
|
<div class="text-sm font-semibold text-gray-950 dark:text-white">{{ $operation['type'] }}</div>
|
|
<x-filament::badge :color="$operation['statusTone']">{{ $operation['statusLabel'] }}</x-filament::badge>
|
|
<x-filament::badge :color="$operation['outcomeTone']">{{ $operation['outcomeLabel'] }}</x-filament::badge>
|
|
</div>
|
|
<div class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
|
{{ $operation['summary'] }}
|
|
</div>
|
|
</div>
|
|
<div class="shrink-0 text-xs font-medium text-gray-500 dark:text-gray-400">
|
|
@if ($operation['createdAt']) {{ $operation['createdAt'] }} @endif
|
|
</div>
|
|
</div>
|
|
</a>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
</x-filament::section>
|
|
</div>
|
|
|
|
<!-- Right Column (Aside) -->
|
|
<div data-testid="tenant-dashboard-overview-aside" class="flex w-full min-w-0 flex-col gap-6 xl:col-span-4">
|
|
@foreach ($readinessCards as $card)
|
|
@php
|
|
$cardMeta = array_values(array_filter($card['meta'] ?? []));
|
|
$headline = $card['headline'] ?? null;
|
|
$cardProgress = array_values(array_filter($card['progress'] ?? []));
|
|
@endphp
|
|
<div data-testid="tenant-dashboard-readiness-card" data-readiness-key="{{ $card['key'] }}" class="min-w-0 rounded-xl border border-gray-200 bg-white p-4 shadow-sm dark:border-white/10 dark:bg-white/5">
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div class="min-w-0">
|
|
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ $card['title'] }}</div>
|
|
@if (filled($headline))
|
|
<div class="mt-1 text-base font-semibold text-gray-950 dark:text-white">{{ $headline }}</div>
|
|
@else
|
|
<div class="mt-1 text-base font-semibold text-gray-950 dark:text-white">{{ $card['status'] }}</div>
|
|
@endif
|
|
</div>
|
|
<x-filament::badge :color="$card['tone']">{{ $card['status'] }}</x-filament::badge>
|
|
</div>
|
|
|
|
<p class="mt-3 text-sm leading-6 text-gray-600 dark:text-gray-400">{{ $card['body'] }}</p>
|
|
|
|
@if ($cardProgress !== [])
|
|
<div class="mt-3 space-y-3">
|
|
@foreach ($cardProgress as $progress)
|
|
@php
|
|
$progressBarColor = match ($progress['tone'] ?? 'primary') {
|
|
'success' => 'var(--success-500)',
|
|
'warning' => 'var(--warning-500)',
|
|
'danger' => 'var(--danger-500)',
|
|
default => 'var(--primary-500)',
|
|
};
|
|
@endphp
|
|
|
|
<div data-progress-key="{{ $progress['key'] }}" class="space-y-1.5">
|
|
<div class="flex items-center justify-between gap-3 text-xs">
|
|
<span class="truncate text-gray-500 dark:text-gray-400">{{ $progress['label'] }}</span>
|
|
<span class="shrink-0 font-medium text-gray-700 dark:text-gray-200">{{ $progress['valueLabel'] }}</span>
|
|
</div>
|
|
|
|
<div class="overflow-hidden rounded-full bg-gray-100 dark:bg-white/10" style="height: 0.5rem;">
|
|
<div
|
|
class="block h-full rounded-full"
|
|
role="progressbar"
|
|
aria-label="{{ $progress['label'] }}"
|
|
aria-valuemin="0"
|
|
aria-valuemax="100"
|
|
aria-valuenow="{{ $progress['percent'] }}"
|
|
aria-valuetext="{{ $progress['valueLabel'] }}"
|
|
style="width: {{ $progress['percent'] }}%; background-color: {{ $progressBarColor }};"
|
|
></div>
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if ($cardMeta !== [])
|
|
<div class="mt-3 grid gap-2 text-xs">
|
|
@foreach ($cardMeta as $item)
|
|
<div class="flex items-center justify-between gap-3 rounded-lg bg-gray-50/80 px-3 py-2 text-gray-600 dark:bg-white/5 dark:text-gray-300">
|
|
<span class="truncate text-gray-500 dark:text-gray-400">{{ $item['label'] }}</span>
|
|
<span class="shrink-0 font-medium text-gray-700 dark:text-gray-200">{{ $item['value'] }}</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
@endif
|
|
|
|
@if (filled($card['actionLabel'] ?? null))
|
|
<div class="mt-4">
|
|
@if (filled($card['actionUrl'] ?? null))
|
|
<x-filament::button data-testid="tenant-dashboard-secondary-action" tag="a" :href="$card['actionUrl']" size="sm" color="gray">
|
|
{{ $card['actionLabel'] }}
|
|
</x-filament::button>
|
|
@else
|
|
<x-filament::button data-testid="tenant-dashboard-secondary-action" size="sm" color="gray" disabled>
|
|
{{ $card['actionLabel'] }}
|
|
</x-filament::button>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div> |