LinkDesk/frontend/src/components/layout/AppHeader.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>