Add PriorityAlertsPanel component to the dashboard, update DashboardController with alert logic, and apply misc UI fixes across sidebar, forms, and pages. Includes epic-1 retrospective and sprint status update. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
168 lines
6.4 KiB
Vue
168 lines
6.4 KiB
Vue
<script setup lang="ts">
|
|
import { router, usePage } from '@inertiajs/vue3';
|
|
import { BoxSelect, Check, ChevronsUpDown } from 'lucide-vue-next';
|
|
import { computed, ref } from 'vue';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import {
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
useSidebar,
|
|
} from '@/components/ui/sidebar';
|
|
|
|
const page = usePage();
|
|
const { isMobile } = useSidebar();
|
|
|
|
const workspaces = computed(() => page.props.auth?.workspaces ?? []);
|
|
const currentWorkspace = computed(
|
|
() => page.props.auth?.currentWorkspace ?? null,
|
|
);
|
|
const workspaceSwitchUrl = computed(
|
|
() => page.props.auth?.workspaceSwitchUrl ?? '',
|
|
);
|
|
const canSwitch = computed(() => workspaces.value.length > 1);
|
|
|
|
const isSwitching = ref(false);
|
|
|
|
function getInitial(name: string): string {
|
|
return name.charAt(0).toUpperCase();
|
|
}
|
|
|
|
function switchWorkspace(workspace: { id: number }) {
|
|
if (isSwitching.value || workspace.id === currentWorkspace.value?.id) {
|
|
return;
|
|
}
|
|
|
|
isSwitching.value = true;
|
|
router.post(
|
|
workspaceSwitchUrl.value,
|
|
{ workspace_id: workspace.id },
|
|
{
|
|
preserveState: false,
|
|
onFinish: () => {
|
|
isSwitching.value = false;
|
|
},
|
|
},
|
|
);
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<SidebarMenu v-if="workspaces.length > 0">
|
|
<SidebarMenuItem>
|
|
<!-- Multi-workspace: show dropdown -->
|
|
<DropdownMenu v-if="canSwitch">
|
|
<DropdownMenuTrigger as-child :disabled="isSwitching">
|
|
<SidebarMenuButton
|
|
size="lg"
|
|
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
>
|
|
<div
|
|
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sm font-semibold text-sidebar-primary-foreground"
|
|
>
|
|
{{
|
|
currentWorkspace
|
|
? getInitial(currentWorkspace.name)
|
|
: '?'
|
|
}}
|
|
</div>
|
|
<div
|
|
class="grid flex-1 text-left text-sm leading-tight"
|
|
>
|
|
<span class="truncate font-medium">
|
|
{{
|
|
currentWorkspace?.name ?? 'Select workspace'
|
|
}}
|
|
</span>
|
|
<span
|
|
v-if="currentWorkspace"
|
|
class="truncate text-xs text-muted-foreground"
|
|
>
|
|
{{ currentWorkspace.slug }}
|
|
</span>
|
|
</div>
|
|
<ChevronsUpDown class="ml-auto" />
|
|
</SidebarMenuButton>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
class="w-[--reka-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
|
align="start"
|
|
:side="isMobile ? 'bottom' : 'right'"
|
|
:side-offset="4"
|
|
>
|
|
<DropdownMenuLabel class="text-xs text-muted-foreground">
|
|
Workspaces
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuItem
|
|
v-for="workspace in workspaces"
|
|
:key="workspace.id"
|
|
class="gap-2 p-2"
|
|
:disabled="isSwitching"
|
|
@click="switchWorkspace(workspace)"
|
|
>
|
|
<div
|
|
class="flex size-6 items-center justify-center rounded-sm border text-xs font-semibold"
|
|
>
|
|
{{ getInitial(workspace.name) }}
|
|
</div>
|
|
<div class="flex flex-1 flex-col">
|
|
<span>{{ workspace.name }}</span>
|
|
<span class="text-xs text-muted-foreground">{{
|
|
workspace.slug
|
|
}}</span>
|
|
</div>
|
|
<Check
|
|
v-if="currentWorkspace?.id === workspace.id"
|
|
class="ml-auto size-4 text-primary"
|
|
/>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<!-- Single workspace: static display -->
|
|
<SidebarMenuButton v-else size="lg">
|
|
<div
|
|
class="flex aspect-square size-8 items-center justify-center rounded-lg bg-sidebar-primary text-sm font-semibold text-sidebar-primary-foreground"
|
|
>
|
|
{{
|
|
currentWorkspace
|
|
? getInitial(currentWorkspace.name)
|
|
: '?'
|
|
}}
|
|
</div>
|
|
<div class="grid flex-1 text-left text-sm leading-tight">
|
|
<span class="truncate font-medium">
|
|
{{ currentWorkspace?.name ?? 'Select workspace' }}
|
|
</span>
|
|
<span
|
|
v-if="currentWorkspace"
|
|
class="truncate text-xs text-muted-foreground"
|
|
>
|
|
{{ currentWorkspace.slug }}
|
|
</span>
|
|
</div>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
<SidebarMenu v-else>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton size="lg" as-child>
|
|
<div class="flex items-center gap-2 text-muted-foreground">
|
|
<div
|
|
class="flex aspect-square size-8 items-center justify-center rounded-lg border border-dashed"
|
|
>
|
|
<BoxSelect class="size-4" />
|
|
</div>
|
|
<span class="text-sm">No workspaces found</span>
|
|
</div>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</template>
|