160 lines
5.2 KiB
Vue
160 lines
5.2 KiB
Vue
<template>
|
|
<header class="flex h-16 shrink-0 items-center gap-2 border-b px-4">
|
|
<!-- Sidebar Toggle -->
|
|
<SidebarTrigger class="-ml-1" />
|
|
<Separator orientation="vertical" class="mr-2 h-4" />
|
|
|
|
<!-- Breadcrumb Navigation -->
|
|
<Breadcrumb class="flex-1">
|
|
<BreadcrumbList>
|
|
<BreadcrumbItem v-for="(crumb, index) in breadcrumbs" :key="index">
|
|
<BreadcrumbLink v-if="crumb.href" :href="crumb.href">
|
|
{{ crumb.label }}
|
|
</BreadcrumbLink>
|
|
<BreadcrumbPage v-else :class="{ 'font-semibold': crumb.isActive }">
|
|
{{ crumb.label }}
|
|
</BreadcrumbPage>
|
|
<BreadcrumbSeparator v-if="index < breadcrumbs.length - 1" />
|
|
</BreadcrumbItem>
|
|
</BreadcrumbList>
|
|
</Breadcrumb>
|
|
|
|
<!-- Header Actions -->
|
|
<div class="flex items-center gap-2">
|
|
<!-- Theme Toggle -->
|
|
<ThemeToggle />
|
|
|
|
<!-- Notifications -->
|
|
<NotificationCenter />
|
|
|
|
<!-- User Menu -->
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger as-child>
|
|
<Button variant="ghost" class="relative h-10 w-10 rounded-full">
|
|
<Avatar class="h-10 w-10">
|
|
<AvatarImage
|
|
v-if="user?.avatar_url"
|
|
:src="getAvatarUrl(user.avatar_url, user.first_name, user.last_name)"
|
|
/>
|
|
<AvatarImage
|
|
v-else
|
|
:src="getInitialsAvatarUrl(user.first_name, user.last_name)"
|
|
/>
|
|
<AvatarFallback>{{ userInitials }}</AvatarFallback>
|
|
</Avatar>
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end" class="w-56">
|
|
<DropdownMenuLabel>
|
|
<div class="flex flex-col space-y-1">
|
|
<p class="text-sm font-medium leading-none">{{ user?.first_name }} {{ user?.last_name }}</p>
|
|
<p class="text-xs leading-none text-muted-foreground">{{ user?.email }}</p>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem as-child>
|
|
<router-link to="/profile" class="flex items-center">
|
|
<User class="mr-2 h-4 w-4" />
|
|
Profile
|
|
</router-link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem as-child>
|
|
<router-link to="/settings" class="flex items-center">
|
|
<Settings class="mr-2 h-4 w-4" />
|
|
Settings
|
|
</router-link>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem @click="handleLogout" class="text-destructive">
|
|
<LogOut class="mr-2 h-4 w-4" />
|
|
Log out
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</header>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { SidebarTrigger } from '@/components/ui/sidebar'
|
|
import { Separator } from '@/components/ui/separator'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu'
|
|
import {
|
|
Breadcrumb,
|
|
BreadcrumbItem,
|
|
BreadcrumbLink,
|
|
BreadcrumbList,
|
|
BreadcrumbPage,
|
|
BreadcrumbSeparator,
|
|
} from '@/components/ui/breadcrumb'
|
|
import { User, Settings, LogOut } from 'lucide-vue-next'
|
|
|
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import { BreadcrumbService, type BreadcrumbItem as BreadcrumbData } from '@/services/breadcrumb'
|
|
import ThemeToggle from '@/components/ui/theme/ThemeToggle.vue'
|
|
import NotificationCenter from './NotificationCenter.vue'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const authStore = useAuthStore()
|
|
|
|
const user = computed(() => authStore.user)
|
|
|
|
const userInitials = computed(() => {
|
|
if (!user.value) return '?'
|
|
return `${user.value.first_name.charAt(0)}${user.value.last_name.charAt(0)}`.toUpperCase()
|
|
})
|
|
|
|
import { useAvatarUrl } from '@/composables/useAvatarUrl'
|
|
|
|
const { getAvatarUrl } = useAvatarUrl()
|
|
|
|
// Generate breadcrumbs based on current route with enhanced context
|
|
const breadcrumbs = ref<BreadcrumbData[]>([])
|
|
|
|
const updateBreadcrumbs = async () => {
|
|
try {
|
|
breadcrumbs.value = await BreadcrumbService.generateBreadcrumbs(route)
|
|
} catch (error) {
|
|
console.error('Failed to generate breadcrumbs:', error)
|
|
// Fallback to simple breadcrumbs
|
|
const pathSegments = route.path.split('/').filter(Boolean)
|
|
const crumbs = [{ label: 'Home', href: '/' }]
|
|
|
|
let currentPath = ''
|
|
pathSegments.forEach((segment, index) => {
|
|
currentPath += `/${segment}`
|
|
const isLast = index === pathSegments.length - 1
|
|
|
|
const label = segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ')
|
|
|
|
crumbs.push({
|
|
label,
|
|
href: isLast ? undefined : currentPath,
|
|
isActive: isLast
|
|
})
|
|
})
|
|
|
|
breadcrumbs.value = crumbs
|
|
}
|
|
}
|
|
|
|
// Watch for route changes to update breadcrumbs
|
|
watch(route, updateBreadcrumbs, { immediate: true })
|
|
|
|
const handleLogout = async () => {
|
|
await authStore.logout()
|
|
router.push('/login')
|
|
}
|
|
</script> |