feat(spec-077): topbar workspace+tenant dropdowns
This commit is contained in:
parent
b07313cfe1
commit
ffbf342d52
@ -2,6 +2,9 @@
|
||||
use App\Filament\Pages\ChooseWorkspace;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Models\WorkspaceMembership;
|
||||
use App\Services\Auth\WorkspaceRoleCapabilityMap;
|
||||
use App\Support\Auth\Capabilities;
|
||||
use App\Support\Workspaces\WorkspaceContext;
|
||||
use Filament\Facades\Filament;
|
||||
|
||||
@ -12,9 +15,29 @@
|
||||
|
||||
$user = auth()->user();
|
||||
|
||||
$canSeeAllWorkspaceTenants = false;
|
||||
if ($user instanceof User && $workspace) {
|
||||
$roles = WorkspaceRoleCapabilityMap::rolesWithCapability(Capabilities::WORKSPACE_MEMBERSHIP_MANAGE);
|
||||
|
||||
$canSeeAllWorkspaceTenants = WorkspaceMembership::query()
|
||||
->where('workspace_id', (int) $workspace->getKey())
|
||||
->where('user_id', (int) $user->getKey())
|
||||
->whereIn('role', $roles)
|
||||
->exists();
|
||||
}
|
||||
|
||||
$tenants = collect();
|
||||
if ($user instanceof User) {
|
||||
$tenants = collect($user->getTenants(Filament::getCurrentOrDefaultPanel()));
|
||||
if ($user instanceof User && $workspace) {
|
||||
if ($canSeeAllWorkspaceTenants) {
|
||||
$tenants = Tenant::query()
|
||||
->where('workspace_id', (int) $workspace->getKey())
|
||||
->orderBy('name')
|
||||
->get();
|
||||
} else {
|
||||
$tenants = collect($user->getTenants(Filament::getCurrentOrDefaultPanel()))
|
||||
->filter(fn ($tenant): bool => $tenant instanceof Tenant && (int) $tenant->workspace_id === (int) $workspace->getKey())
|
||||
->values();
|
||||
}
|
||||
}
|
||||
|
||||
$currentTenant = Filament::getTenant();
|
||||
@ -25,41 +48,72 @@
|
||||
@endphp
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<a
|
||||
href="{{ ChooseWorkspace::getUrl() }}"
|
||||
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100"
|
||||
>
|
||||
<span class="font-medium">Workspace:</span>
|
||||
<span>{{ $workspace?->name ?? '—' }}</span>
|
||||
</a>
|
||||
<x-filament::dropdown placement="bottom-start" teleport>
|
||||
<x-slot name="trigger">
|
||||
<x-filament::button
|
||||
color="gray"
|
||||
outlined
|
||||
size="sm"
|
||||
icon="heroicon-o-squares-2x2"
|
||||
>
|
||||
{{ $workspace?->name ?? 'Select workspace' }}
|
||||
</x-filament::button>
|
||||
</x-slot>
|
||||
|
||||
<x-filament::dropdown.list>
|
||||
<a
|
||||
href="{{ ChooseWorkspace::getUrl() }}"
|
||||
class="block px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
>
|
||||
Switch workspace
|
||||
</a>
|
||||
|
||||
@if ($canSeeAllWorkspaceTenants)
|
||||
<a
|
||||
href="{{ route('filament.admin.resources.workspaces.index') }}"
|
||||
class="block px-3 py-2 text-sm hover:bg-gray-50 dark:hover:bg-gray-800"
|
||||
>
|
||||
Manage workspaces
|
||||
</a>
|
||||
@endif
|
||||
</x-filament::dropdown.list>
|
||||
</x-filament::dropdown>
|
||||
|
||||
<div class="h-4 w-px bg-gray-200 dark:bg-gray-700"></div>
|
||||
|
||||
<x-filament::dropdown placement="bottom-start" teleport>
|
||||
<x-slot name="trigger">
|
||||
<button
|
||||
type="button"
|
||||
class="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-gray-100"
|
||||
<x-filament::button
|
||||
color="gray"
|
||||
outlined
|
||||
size="sm"
|
||||
icon="heroicon-o-building-office-2"
|
||||
>
|
||||
<span class="font-medium">Tenant:</span>
|
||||
<span>{{ $currentTenantName ?? '—' }}</span>
|
||||
</button>
|
||||
{{ $currentTenantName ?? 'Select tenant' }}
|
||||
</x-filament::button>
|
||||
</x-slot>
|
||||
|
||||
<x-filament::dropdown.list>
|
||||
<div class="px-3 py-2 space-y-2" x-data="{ query: '' }">
|
||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">Tenant context</div>
|
||||
<div class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
Tenant context
|
||||
@if ($canSeeAllWorkspaceTenants)
|
||||
<span class="text-gray-400">· all workspace tenants</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if (! $workspace)
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">Choose a workspace first.</div>
|
||||
@elseif ($tenants->isEmpty())
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">No tenants you can access in this workspace.</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ $canSeeAllWorkspaceTenants ? 'No tenants exist in this workspace.' : 'No tenants you can access in this workspace.' }}
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-2">
|
||||
<input
|
||||
type="text"
|
||||
class="fi-input fi-text-input w-full"
|
||||
placeholder="Select tenant…"
|
||||
placeholder="Search tenants…"
|
||||
x-model="query"
|
||||
/>
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
$tenant = Tenant::factory()->create(['status' => 'active']);
|
||||
[$user, $tenant] = createUserWithTenant($tenant, role: 'owner');
|
||||
|
||||
$workspaceName = $tenant->workspace?->name;
|
||||
|
||||
Filament::setTenant(null, true);
|
||||
|
||||
$this->actingAs($user)
|
||||
@ -22,9 +24,10 @@
|
||||
])
|
||||
->get('/admin/operations')
|
||||
->assertOk()
|
||||
->assertSee('Workspace:')
|
||||
->assertSee('Tenant:')
|
||||
->assertSee('Select tenant…')
|
||||
->assertSee($workspaceName ?? 'Select workspace')
|
||||
->assertSee('Select tenant')
|
||||
->assertSee('Search tenants…')
|
||||
->assertSee('Switch workspace')
|
||||
->assertSee('admin/select-tenant')
|
||||
->assertSee('Clear tenant context')
|
||||
->assertSee($tenant->getFilamentName());
|
||||
@ -39,7 +42,7 @@
|
||||
|
||||
it('filters the header tenant picker to tenants the user can access', function (): void {
|
||||
$tenantA = Tenant::factory()->create(['status' => 'active']);
|
||||
[$user, $tenantA] = createUserWithTenant($tenantA, role: 'owner');
|
||||
[$user, $tenantA] = createUserWithTenant($tenantA, role: 'owner', workspaceRole: 'readonly');
|
||||
|
||||
$tenantB = Tenant::factory()->create([
|
||||
'status' => 'active',
|
||||
@ -59,6 +62,28 @@
|
||||
->assertDontSee($tenantB->getFilamentName());
|
||||
});
|
||||
|
||||
it('shows all workspace tenants in the header tenant picker for workspace owners', function (): void {
|
||||
$tenantA = Tenant::factory()->create(['status' => 'active']);
|
||||
[$user, $tenantA] = createUserWithTenant($tenantA, role: 'owner', workspaceRole: 'owner');
|
||||
|
||||
$tenantB = Tenant::factory()->create([
|
||||
'status' => 'active',
|
||||
'workspace_id' => (int) $tenantA->workspace_id,
|
||||
'name' => 'ZZZ-UNASSIGNED-TENANT-NAME-12345',
|
||||
]);
|
||||
|
||||
Filament::setTenant(null, true);
|
||||
|
||||
$this->actingAs($user)
|
||||
->withSession([
|
||||
WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id,
|
||||
])
|
||||
->get('/admin/operations')
|
||||
->assertOk()
|
||||
->assertSee($tenantA->getFilamentName())
|
||||
->assertSee($tenantB->getFilamentName());
|
||||
});
|
||||
|
||||
it('does not implicitly switch tenant when opening canonical operation deep links', function (): void {
|
||||
$tenantA = Tenant::factory()->create(['status' => 'active']);
|
||||
[$user, $tenantA] = createUserWithTenant($tenantA, role: 'owner');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user