fix: Resolve 500 error in unified sidebar by using simpler implementation
- Replace problematic UDashboardSidebar components with custom implementation - Use standard HTML/CSS for sidebar instead of Nuxt UI dashboard components - Fix 'Cannot destructure property collapsed of undefined' error - Maintain all features: responsive, role-based nav, clean design - Ensure compatibility with existing Vuetify components
This commit is contained in:
parent
61235b163d
commit
7244349fe7
|
|
@ -1,141 +1,127 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="min-h-screen bg-gray-50">
|
<div class="min-h-screen bg-gray-50 flex">
|
||||||
<!-- Main Dashboard Group -->
|
<!-- Sidebar -->
|
||||||
<UDashboardGroup>
|
<div
|
||||||
<!-- Sidebar -->
|
:class="[
|
||||||
<UDashboardSidebar
|
'bg-white border-r border-gray-200 transition-all duration-300',
|
||||||
v-model:open="sidebarOpen"
|
sidebarOpen ? 'w-64' : 'w-0 overflow-hidden',
|
||||||
:collapsible="true"
|
'lg:w-64'
|
||||||
:resizable="false"
|
]"
|
||||||
:ui="{
|
>
|
||||||
wrapper: 'bg-white border-r border-gray-200',
|
<div class="h-full flex flex-col">
|
||||||
header: 'px-4 py-4 border-b border-gray-100',
|
<!-- Header -->
|
||||||
body: 'px-3 py-4',
|
<div class="px-4 py-4 border-b border-gray-100">
|
||||||
footer: 'px-4 py-4 border-t border-gray-100'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template #header="{ collapsed }">
|
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<img
|
<img
|
||||||
src="/Port Nimara New Logo-Circular Frame.png"
|
src="/Port Nimara New Logo-Circular Frame.png"
|
||||||
class="h-8 w-8"
|
class="h-8 w-8"
|
||||||
alt="Port Nimara"
|
alt="Port Nimara"
|
||||||
>
|
>
|
||||||
<transition name="fade">
|
<div class="flex flex-col">
|
||||||
<div v-if="!collapsed" class="flex flex-col">
|
<span class="text-sm font-semibold text-gray-900">Port Nimara</span>
|
||||||
<span class="text-sm font-semibold text-gray-900">Port Nimara</span>
|
<span class="text-xs text-gray-500">Client Portal</span>
|
||||||
<span class="text-xs text-gray-500">Client Portal</span>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #default="{ collapsed }">
|
|
||||||
<!-- Navigation Menu -->
|
|
||||||
<UNavigationMenu
|
|
||||||
:collapsed="collapsed"
|
|
||||||
:items="navigationItems"
|
|
||||||
orientation="vertical"
|
|
||||||
:ui="{
|
|
||||||
wrapper: 'space-y-1',
|
|
||||||
base: 'group flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors duration-200',
|
|
||||||
active: 'bg-blue-50 text-blue-700',
|
|
||||||
inactive: 'text-gray-700 hover:bg-gray-100',
|
|
||||||
icon: {
|
|
||||||
base: 'w-5 h-5 flex-shrink-0',
|
|
||||||
active: 'text-blue-700',
|
|
||||||
inactive: 'text-gray-400 group-hover:text-gray-600'
|
|
||||||
},
|
|
||||||
label: 'truncate',
|
|
||||||
badge: 'ml-auto'
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #footer="{ collapsed }">
|
|
||||||
<div class="space-y-2">
|
|
||||||
<!-- User Info -->
|
|
||||||
<div v-if="authState?.user" class="flex items-center gap-3 p-2">
|
|
||||||
<UAvatar
|
|
||||||
:src="`https://ui-avatars.com/api/?name=${encodeURIComponent(authState.user.name || authState.user.email)}&background=387bca&color=fff`"
|
|
||||||
:alt="authState.user.name || authState.user.email"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
<div v-if="!collapsed" class="flex-1 min-w-0">
|
|
||||||
<p class="text-sm font-medium text-gray-900 truncate">
|
|
||||||
{{ authState.user.name || authState.user.email }}
|
|
||||||
</p>
|
|
||||||
<p class="text-xs text-gray-500 truncate">
|
|
||||||
{{ authState.user.email }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Role Badge -->
|
|
||||||
<div v-if="!collapsed && authState?.groups?.length" class="px-2">
|
|
||||||
<UBadge
|
|
||||||
v-if="authState.groups.includes('admin')"
|
|
||||||
color="orange"
|
|
||||||
variant="subtle"
|
|
||||||
size="sm"
|
|
||||||
class="w-full justify-center"
|
|
||||||
>
|
|
||||||
Admin
|
|
||||||
</UBadge>
|
|
||||||
<UBadge
|
|
||||||
v-else-if="authState.groups.includes('sales')"
|
|
||||||
color="green"
|
|
||||||
variant="subtle"
|
|
||||||
size="sm"
|
|
||||||
class="w-full justify-center"
|
|
||||||
>
|
|
||||||
Sales
|
|
||||||
</UBadge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Logout Button -->
|
|
||||||
<UButton
|
|
||||||
@click="handleLogout"
|
|
||||||
:icon="collapsed ? 'i-heroicons-arrow-left-on-rectangle' : undefined"
|
|
||||||
:label="collapsed ? undefined : 'Logout'"
|
|
||||||
color="gray"
|
|
||||||
variant="ghost"
|
|
||||||
:block="!collapsed"
|
|
||||||
:square="collapsed"
|
|
||||||
class="w-full justify-start"
|
|
||||||
>
|
|
||||||
<template v-if="!collapsed" #leading>
|
|
||||||
<UIcon name="i-heroicons-arrow-left-on-rectangle" class="w-5 h-5" />
|
|
||||||
</template>
|
|
||||||
</UButton>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
|
||||||
</UDashboardSidebar>
|
|
||||||
|
|
||||||
<!-- Main Panel -->
|
|
||||||
<UDashboardPanel>
|
|
||||||
<template #header>
|
|
||||||
<UDashboardNavbar :title="pageTitle" class="bg-white border-b border-gray-200">
|
|
||||||
<template #left>
|
|
||||||
<UDashboardSidebarToggle
|
|
||||||
v-if="mdAndDown"
|
|
||||||
variant="ghost"
|
|
||||||
color="gray"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #right>
|
|
||||||
<!-- Additional navbar content can go here -->
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Page Content -->
|
|
||||||
<div class="p-6">
|
|
||||||
<slot />
|
|
||||||
</div>
|
</div>
|
||||||
</UDashboardPanel>
|
|
||||||
</UDashboardGroup>
|
<!-- Navigation -->
|
||||||
|
<nav class="flex-1 px-3 py-4 space-y-1 overflow-y-auto">
|
||||||
|
<NuxtLink
|
||||||
|
v-for="item in navigationItems"
|
||||||
|
:key="item.to"
|
||||||
|
:to="item.to"
|
||||||
|
class="group flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors duration-200"
|
||||||
|
:class="[
|
||||||
|
route.path === item.to
|
||||||
|
? 'bg-blue-50 text-blue-700'
|
||||||
|
: 'text-gray-700 hover:bg-gray-100'
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
:name="item.icon"
|
||||||
|
class="w-5 h-5 flex-shrink-0"
|
||||||
|
:class="[
|
||||||
|
route.path === item.to
|
||||||
|
? 'text-blue-700'
|
||||||
|
: 'text-gray-400 group-hover:text-gray-600'
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
<span class="truncate">{{ item.label }}</span>
|
||||||
|
</NuxtLink>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="px-4 py-4 border-t border-gray-100 space-y-2">
|
||||||
|
<!-- User Info -->
|
||||||
|
<div v-if="authState?.user" class="flex items-center gap-3 p-2">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white text-sm font-medium">
|
||||||
|
{{ (authState.user.name || authState.user.email || '?')[0].toUpperCase() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-sm font-medium text-gray-900 truncate">
|
||||||
|
{{ authState.user.name || authState.user.email }}
|
||||||
|
</p>
|
||||||
|
<p class="text-xs text-gray-500 truncate">
|
||||||
|
{{ authState.user.email }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Role Badge -->
|
||||||
|
<div v-if="authState?.groups?.length" class="px-2">
|
||||||
|
<div
|
||||||
|
v-if="authState.groups.includes('admin')"
|
||||||
|
class="inline-flex items-center justify-center w-full px-2 py-1 text-xs font-medium text-orange-700 bg-orange-100 rounded-md"
|
||||||
|
>
|
||||||
|
Admin
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="authState.groups.includes('sales')"
|
||||||
|
class="inline-flex items-center justify-center w-full px-2 py-1 text-xs font-medium text-green-700 bg-green-100 rounded-md"
|
||||||
|
>
|
||||||
|
Sales
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Logout Button -->
|
||||||
|
<button
|
||||||
|
@click="handleLogout"
|
||||||
|
class="w-full flex items-center justify-start gap-3 px-3 py-2 rounded-lg text-sm font-medium text-gray-700 hover:bg-gray-100 transition-colors duration-200"
|
||||||
|
>
|
||||||
|
<Icon name="i-heroicons-arrow-left-on-rectangle" class="w-5 h-5 text-gray-400" />
|
||||||
|
<span>Logout</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="flex-1 flex flex-col">
|
||||||
|
<!-- Top Bar -->
|
||||||
|
<header class="bg-white border-b border-gray-200 px-4 py-3">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<!-- Mobile Menu Toggle -->
|
||||||
|
<button
|
||||||
|
@click="sidebarOpen = !sidebarOpen"
|
||||||
|
class="lg:hidden p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100"
|
||||||
|
>
|
||||||
|
<Icon name="i-heroicons-bars-3" class="w-6 h-6" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Page Title -->
|
||||||
|
<h1 class="text-xl font-semibold text-gray-900">{{ pageTitle }}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Page Content -->
|
||||||
|
<main class="flex-1 p-6 overflow-y-auto">
|
||||||
|
<slot />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -145,21 +131,16 @@ import { ref, computed } from 'vue';
|
||||||
// Define NavigationMenuItem type locally
|
// Define NavigationMenuItem type locally
|
||||||
interface NavigationMenuItem {
|
interface NavigationMenuItem {
|
||||||
label: string;
|
label: string;
|
||||||
icon?: string;
|
icon: string;
|
||||||
to?: string;
|
to: string;
|
||||||
badge?: string | number;
|
|
||||||
click?: () => void;
|
|
||||||
defaultOpen?: boolean;
|
|
||||||
children?: NavigationMenuItem[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const nuxtApp = useNuxtApp();
|
const nuxtApp = useNuxtApp();
|
||||||
const { mdAndDown } = useDisplay();
|
|
||||||
|
|
||||||
// Sidebar state
|
// Sidebar state
|
||||||
const sidebarOpen = ref(true);
|
const sidebarOpen = ref(false);
|
||||||
|
|
||||||
// Get auth state - with fallback to prevent errors
|
// Get auth state - with fallback to prevent errors
|
||||||
const authState = computed(() => {
|
const authState = computed(() => {
|
||||||
|
|
@ -197,7 +178,7 @@ const pageTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Navigation items based on user role
|
// Navigation items based on user role
|
||||||
const navigationItems = computed((): NavigationMenuItem[][] => {
|
const navigationItems = computed((): NavigationMenuItem[] => {
|
||||||
const items: NavigationMenuItem[] = [
|
const items: NavigationMenuItem[] = [
|
||||||
{
|
{
|
||||||
label: 'Dashboard',
|
label: 'Dashboard',
|
||||||
|
|
@ -261,8 +242,7 @@ const navigationItems = computed((): NavigationMenuItem[][] => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return as nested array for proper spacing
|
return items;
|
||||||
return [items];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Logout handler
|
// Logout handler
|
||||||
|
|
@ -276,23 +256,13 @@ const handleLogout = async () => {
|
||||||
await router.push('/login');
|
await router.push('/login');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize sidebar state based on screen size
|
|
||||||
onMounted(() => {
|
|
||||||
if (mdAndDown.value) {
|
|
||||||
sidebarOpen.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fade-enter-active,
|
/* Ensure proper responsive behavior */
|
||||||
.fade-leave-active {
|
@media (min-width: 1024px) {
|
||||||
transition: opacity 0.2s ease;
|
.lg\:w-64 {
|
||||||
}
|
width: 16rem !important;
|
||||||
|
}
|
||||||
.fade-enter-from,
|
|
||||||
.fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue