feat: Restructure sidebar nav into section-grouped menu
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
Replaces flat 25-item navItems array with 6 labeled sections: Dashboard, Server, Plugin Configs, Operations, Monitoring, Management. Section headers only render when at least one item is visible to the user's permissions. Platform Admin section restyled to match. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -44,34 +44,67 @@ const auth = useAuthStore()
|
|||||||
const server = useServerStore()
|
const server = useServerStore()
|
||||||
const sidebarOpen = ref(false)
|
const sidebarOpen = ref(false)
|
||||||
|
|
||||||
const navItems = [
|
type NavItem = { name: string; path: string; icon: any; permission: string | null }
|
||||||
{ name: 'Dashboard', path: '/', icon: LayoutDashboard, permission: null },
|
type NavSection = { label: string; items: NavItem[] }
|
||||||
{ name: 'Server', path: '/server', icon: Server, permission: 'server.view' },
|
|
||||||
{ name: 'Console', path: '/console', icon: Terminal, permission: 'console.view' },
|
const navSections: NavSection[] = [
|
||||||
{ name: 'Players', path: '/players', icon: Users, permission: 'players.view' },
|
{
|
||||||
{ name: 'Plugins', path: '/plugins', icon: Puzzle, permission: 'plugins.view' },
|
label: '',
|
||||||
{ name: 'File Manager', path: '/files', icon: FolderOpen, permission: 'files.view' },
|
items: [
|
||||||
{ name: 'Loot Builder', path: '/loot-builder', icon: Crosshair, permission: 'loot.view' },
|
{ name: 'Dashboard', path: '/', icon: LayoutDashboard, permission: null },
|
||||||
{ name: 'Teleport Config', path: '/teleport-config', icon: Navigation2, permission: 'teleport.view' },
|
],
|
||||||
{ name: 'Gather Rates', path: '/gather-manager', icon: Pickaxe, permission: 'gather.view' },
|
},
|
||||||
{ name: 'Auto Doors', path: '/autodoors', icon: DoorOpen, permission: 'autodoors.view' },
|
{
|
||||||
{ name: 'Kits', path: '/kits', icon: Gift, permission: 'kits.view' },
|
label: 'Server',
|
||||||
{ name: 'Furnace Splitter', path: '/furnace-splitter', icon: Flame, permission: 'furnacesplitter.view' },
|
items: [
|
||||||
{ name: 'Better Chat', path: '/better-chat', icon: MessageSquare, permission: 'betterchat.view' },
|
{ name: 'Server', path: '/server', icon: Server, permission: 'server.view' },
|
||||||
{ name: 'Timed Execute', path: '/timed-execute', icon: Clock, permission: 'timedexecute.view' },
|
{ name: 'Console', path: '/console', icon: Terminal, permission: 'console.view' },
|
||||||
{ name: 'Raidable Bases', path: '/raidable-bases', icon: Swords, permission: 'raidablebases.view' },
|
{ name: 'Players', path: '/players', icon: Users, permission: 'players.view' },
|
||||||
{ name: 'Auto-Wiper', path: '/wipes', icon: RefreshCw, permission: 'wipes.view' },
|
{ name: 'Plugins', path: '/plugins', icon: Puzzle, permission: 'plugins.view' },
|
||||||
{ name: 'Maps', path: '/maps', icon: Map, permission: 'maps.view' },
|
{ name: 'File Manager', path: '/files', icon: FolderOpen, permission: 'files.view' },
|
||||||
{ name: 'Chat Log', path: '/chat', icon: MessageSquare, permission: 'chat.view' },
|
],
|
||||||
{ name: 'Analytics', path: '/analytics', icon: BarChart3, permission: 'analytics.view' },
|
},
|
||||||
{ name: 'Schedules', path: '/schedules', icon: Clock, permission: 'schedules.view' },
|
{
|
||||||
{ name: 'Alerts', path: '/alerts', icon: AlertTriangle, permission: 'alerts.view' },
|
label: 'Plugin Configs',
|
||||||
{ name: 'Notifications', path: '/notifications', icon: Bell, permission: 'notifications.view' },
|
items: [
|
||||||
{ name: 'Team', path: '/team', icon: UserPlus, permission: null },
|
{ name: 'Loot Builder', path: '/loot-builder', icon: Crosshair, permission: 'loot.view' },
|
||||||
{ name: 'Store', path: '/store/config', icon: ShoppingBag, permission: 'store.view' },
|
{ name: 'Teleport', path: '/teleport-config', icon: Navigation2, permission: 'teleport.view' },
|
||||||
{ name: 'Modules', path: '/modules', icon: Package, permission: 'modules.view' },
|
{ name: 'Gather Rates', path: '/gather-manager', icon: Pickaxe, permission: 'gather.view' },
|
||||||
{ name: 'Changelog', path: '/changelog', icon: FileText, permission: 'changelog.view' },
|
{ name: 'Auto Doors', path: '/autodoors', icon: DoorOpen, permission: 'autodoors.view' },
|
||||||
{ name: 'Settings', path: '/settings', icon: Settings, permission: 'settings.view' },
|
{ name: 'Kits', path: '/kits', icon: Gift, permission: 'kits.view' },
|
||||||
|
{ name: 'Furnace Splitter', path: '/furnace-splitter', icon: Flame, permission: 'furnacesplitter.view' },
|
||||||
|
{ name: 'Better Chat', path: '/better-chat', icon: MessageSquare, permission: 'betterchat.view' },
|
||||||
|
{ name: 'Timed Execute', path: '/timed-execute', icon: Clock, permission: 'timedexecute.view' },
|
||||||
|
{ name: 'Raidable Bases', path: '/raidable-bases', icon: Swords, permission: 'raidablebases.view' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Operations',
|
||||||
|
items: [
|
||||||
|
{ name: 'Auto-Wiper', path: '/wipes', icon: RefreshCw, permission: 'wipes.view' },
|
||||||
|
{ name: 'Maps', path: '/maps', icon: Map, permission: 'maps.view' },
|
||||||
|
{ name: 'Schedules', path: '/schedules', icon: Clock, permission: 'schedules.view' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Monitoring',
|
||||||
|
items: [
|
||||||
|
{ name: 'Chat Log', path: '/chat', icon: MessageSquare, permission: 'chat.view' },
|
||||||
|
{ name: 'Analytics', path: '/analytics', icon: BarChart3, permission: 'analytics.view' },
|
||||||
|
{ name: 'Alerts', path: '/alerts', icon: AlertTriangle, permission: 'alerts.view' },
|
||||||
|
{ name: 'Notifications', path: '/notifications', icon: Bell, permission: 'notifications.view' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Management',
|
||||||
|
items: [
|
||||||
|
{ name: 'Team', path: '/team', icon: UserPlus, permission: null },
|
||||||
|
{ name: 'Store', path: '/store/config', icon: ShoppingBag, permission: 'store.view' },
|
||||||
|
{ name: 'Modules', path: '/modules', icon: Package, permission: 'modules.view' },
|
||||||
|
{ name: 'Changelog', path: '/changelog', icon: FileText, permission: 'changelog.view' },
|
||||||
|
{ name: 'Settings', path: '/settings', icon: Settings, permission: 'settings.view' },
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const adminNavItems = [
|
const adminNavItems = [
|
||||||
@@ -96,10 +129,14 @@ function closeSidebar() {
|
|||||||
sidebarOpen.value = false
|
sidebarOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function canShowNavItem(item: typeof navItems[0]): boolean {
|
function canShowNavItem(item: NavItem): boolean {
|
||||||
if (!item.permission) return true
|
if (!item.permission) return true
|
||||||
return auth.hasPermission(item.permission)
|
return auth.hasPermission(item.permission)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasVisibleItems(section: NavSection): boolean {
|
||||||
|
return section.items.some(canShowNavItem)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -162,29 +199,35 @@ function canShowNavItem(item: typeof navItems[0]): boolean {
|
|||||||
|
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav class="flex-1 overflow-y-auto py-2">
|
<nav class="flex-1 overflow-y-auto py-2">
|
||||||
<RouterLink
|
<template v-for="section in navSections" :key="section.label">
|
||||||
v-for="item in navItems"
|
<template v-if="hasVisibleItems(section)">
|
||||||
v-show="canShowNavItem(item)"
|
<!-- Section Header -->
|
||||||
:key="item.path"
|
<div v-if="section.label" class="mt-4 mb-1 px-4">
|
||||||
:to="item.path"
|
<span class="text-[10px] font-semibold uppercase tracking-widest text-neutral-500">{{ section.label }}</span>
|
||||||
@click="closeSidebar"
|
</div>
|
||||||
class="flex items-center gap-3 px-4 py-2 mx-2 rounded-lg text-sm transition-colors"
|
|
||||||
:class="isActive(item.path)
|
<!-- Section Items -->
|
||||||
? 'bg-oxide-500/10 text-oxide-400'
|
<RouterLink
|
||||||
: 'text-neutral-400 hover:bg-neutral-800 hover:text-neutral-200'"
|
v-for="item in section.items"
|
||||||
>
|
v-show="canShowNavItem(item)"
|
||||||
<component :is="item.icon" class="w-4 h-4" />
|
:key="item.path"
|
||||||
{{ item.name }}
|
:to="item.path"
|
||||||
</RouterLink>
|
@click="closeSidebar"
|
||||||
|
class="flex items-center gap-3 px-4 py-2 mx-2 rounded-lg text-sm transition-colors"
|
||||||
|
:class="isActive(item.path)
|
||||||
|
? 'bg-oxide-500/10 text-oxide-400'
|
||||||
|
: 'text-neutral-400 hover:bg-neutral-800 hover:text-neutral-200'"
|
||||||
|
>
|
||||||
|
<component :is="item.icon" class="w-4 h-4" />
|
||||||
|
{{ item.name }}
|
||||||
|
</RouterLink>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Platform Admin Section (super-admin only) -->
|
<!-- Platform Admin Section (super-admin only) -->
|
||||||
<template v-if="auth.isSuperAdmin">
|
<template v-if="auth.isSuperAdmin">
|
||||||
<div class="mt-4 mb-2 px-4">
|
<div class="mt-4 mb-1 px-4">
|
||||||
<div class="flex items-center gap-2">
|
<span class="text-[10px] font-semibold uppercase tracking-widest text-oxide-500">Platform</span>
|
||||||
<div class="flex-1 border-t border-neutral-700" />
|
|
||||||
<span class="text-[10px] font-semibold uppercase tracking-widest text-oxide-500">Platform</span>
|
|
||||||
<div class="flex-1 border-t border-neutral-700" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
v-for="item in adminNavItems"
|
v-for="item in adminNavItems"
|
||||||
|
|||||||
Reference in New Issue
Block a user