- Replace flat div cards with semantic <button> elements - Add workspace icon (building-office-2) per card - Add role-specific icons (shield, cog, wrench, eye) - Use primary color scheme for 'Last used' highlight (was amber) - Add loading spinner overlay on workspace selection - Add hover arrow indicator + shadow elevation on hover - Add focus ring for keyboard accessibility - Better empty state with icon + actionable copy - Proper dark mode support throughout - Responsive grid (1→2→3 columns) - Manage workspaces link with cog icon
156 lines
8.1 KiB
PHP
156 lines
8.1 KiB
PHP
<x-filament-panels::page>
|
|
@php
|
|
$workspaces = $this->getWorkspaces();
|
|
$workspaceRoles = $this->workspaceRoles;
|
|
|
|
$user = auth()->user();
|
|
$recommendedWorkspaceId = $user instanceof \App\Models\User ? (int) ($user->last_workspace_id ?? 0) : 0;
|
|
|
|
if ($recommendedWorkspaceId > 0) {
|
|
[$recommended, $other] = $workspaces->partition(fn ($workspace) => (int) $workspace->id === $recommendedWorkspaceId);
|
|
$workspaces = $recommended->concat($other)->values();
|
|
}
|
|
|
|
$roleColorMap = [
|
|
'owner' => 'primary',
|
|
'manager' => 'info',
|
|
'operator' => 'gray',
|
|
'readonly' => 'gray',
|
|
];
|
|
|
|
$roleIconMap = [
|
|
'owner' => 'heroicon-m-shield-check',
|
|
'manager' => 'heroicon-m-cog-6-tooth',
|
|
'operator' => 'heroicon-m-wrench-screwdriver',
|
|
'readonly' => 'heroicon-m-eye',
|
|
];
|
|
|
|
$canManageWorkspaces = false;
|
|
if ($user instanceof \App\Models\User && $workspaces->count() > 0) {
|
|
foreach ($workspaces as $ws) {
|
|
if (($workspaceRoles[(int) $ws->id] ?? null) === 'owner') {
|
|
$canManageWorkspaces = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
@endphp
|
|
|
|
@if ($workspaces->isEmpty())
|
|
<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">
|
|
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-gray-100 dark:bg-white/10">
|
|
<x-filament::icon
|
|
icon="heroicon-o-building-office-2"
|
|
class="h-6 w-6 text-gray-400 dark:text-gray-500"
|
|
/>
|
|
</div>
|
|
<h3 class="text-base font-semibold text-gray-900 dark:text-white">No workspaces available</h3>
|
|
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
|
You don't have access to any workspace yet. Contact your administrator to get started.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
@else
|
|
<div class="mx-auto max-w-3xl">
|
|
<p class="mb-6 text-center text-sm text-gray-500 dark:text-gray-400">
|
|
Select a workspace to continue.
|
|
</p>
|
|
|
|
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-{{ min($workspaces->count(), 3) }}">
|
|
@foreach ($workspaces as $workspace)
|
|
@php
|
|
$isRecommended = $recommendedWorkspaceId > 0 && (int) $workspace->id === $recommendedWorkspaceId;
|
|
$role = $workspaceRoles[(int) $workspace->id] ?? null;
|
|
$tenantCount = (int) ($workspace->tenants_count ?? 0);
|
|
@endphp
|
|
|
|
<button
|
|
type="button"
|
|
wire:key="workspace-{{ $workspace->id }}"
|
|
wire:click="selectWorkspace({{ (int) $workspace->id }})"
|
|
@class([
|
|
'group relative flex flex-col rounded-xl border p-5 text-left transition-all duration-150',
|
|
'focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900',
|
|
// Recommended (last used)
|
|
'border-primary-300 bg-primary-50/50 shadow-sm ring-1 ring-primary-200 hover:border-primary-400 hover:shadow-md dark:border-primary-500/40 dark:bg-primary-950/20 dark:ring-primary-500/20 dark:hover:border-primary-400/60' => $isRecommended,
|
|
// Default
|
|
'border-gray-200 bg-white shadow-sm hover:border-gray-300 hover:shadow-md dark:border-white/10 dark:bg-white/5 dark:hover:border-white/20' => ! $isRecommended,
|
|
])
|
|
>
|
|
{{-- Loading overlay --}}
|
|
<div wire:loading wire:target="selectWorkspace({{ (int) $workspace->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>
|
|
|
|
{{-- Header: icon + name --}}
|
|
<div class="flex items-start gap-3">
|
|
<div @class([
|
|
'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg',
|
|
'bg-primary-100 dark:bg-primary-900/50' => $isRecommended,
|
|
'bg-gray-100 group-hover:bg-gray-200 dark:bg-white/10 dark:group-hover:bg-white/15' => ! $isRecommended,
|
|
])>
|
|
<x-filament::icon
|
|
icon="heroicon-o-building-office-2"
|
|
@class([
|
|
'h-5 w-5',
|
|
'text-primary-600 dark:text-primary-400' => $isRecommended,
|
|
'text-gray-500 group-hover:text-gray-600 dark:text-gray-400 dark:group-hover:text-gray-300' => ! $isRecommended,
|
|
])
|
|
/>
|
|
</div>
|
|
<div class="min-w-0 flex-1">
|
|
<h3 class="truncate text-sm font-semibold text-gray-900 dark:text-white">
|
|
{{ $workspace->name }}
|
|
</h3>
|
|
@if ($role)
|
|
<div class="mt-0.5 flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
|
|
<x-filament::icon
|
|
:icon="$roleIconMap[$role] ?? 'heroicon-m-user'"
|
|
class="h-3.5 w-3.5"
|
|
/>
|
|
{{ ucfirst($role) }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Metadata --}}
|
|
<div class="mt-4 flex items-center justify-between border-t border-gray-100 pt-3 dark:border-white/5">
|
|
<div class="flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-400">
|
|
<x-filament::icon icon="heroicon-m-server-stack" class="h-3.5 w-3.5" />
|
|
{{ $tenantCount }} {{ \Illuminate\Support\Str::plural('tenant', $tenantCount) }}
|
|
</div>
|
|
|
|
@if ($isRecommended)
|
|
<x-filament::badge color="primary" size="sm">
|
|
Last used
|
|
</x-filament::badge>
|
|
@endif
|
|
</div>
|
|
|
|
{{-- Hover arrow indicator --}}
|
|
<div class="absolute right-4 top-5 opacity-0 transition-opacity group-hover:opacity-100">
|
|
<x-filament::icon
|
|
icon="heroicon-m-arrow-right"
|
|
class="h-4 w-4 text-gray-400 dark:text-gray-500"
|
|
/>
|
|
</div>
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
|
|
@if ($canManageWorkspaces)
|
|
<div class="mt-6 flex justify-center">
|
|
<a href="/admin/workspaces"
|
|
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-cog-6-tooth" class="h-4 w-4" />
|
|
Manage workspaces
|
|
</a>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</x-filament-panels::page>
|