feat: Restructure sidebar nav into section-grouped menu
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:
Vantz Stockwell
2026-02-22 02:56:39 -05:00
parent 7d5966839a
commit d15ea28e8f

View File

@@ -44,34 +44,67 @@ const auth = useAuthStore()
const server = useServerStore()
const sidebarOpen = ref(false)
const navItems = [
{ name: 'Dashboard', path: '/', icon: LayoutDashboard, permission: null },
{ name: 'Server', path: '/server', icon: Server, permission: 'server.view' },
{ name: 'Console', path: '/console', icon: Terminal, permission: 'console.view' },
{ name: 'Players', path: '/players', icon: Users, permission: 'players.view' },
{ name: 'Plugins', path: '/plugins', icon: Puzzle, permission: 'plugins.view' },
{ name: 'File Manager', path: '/files', icon: FolderOpen, permission: 'files.view' },
{ name: 'Loot Builder', path: '/loot-builder', icon: Crosshair, permission: 'loot.view' },
{ 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' },
{ 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' },
{ name: 'Auto-Wiper', path: '/wipes', icon: RefreshCw, permission: 'wipes.view' },
{ name: 'Maps', path: '/maps', icon: Map, permission: 'maps.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' },
{ name: 'Notifications', path: '/notifications', icon: Bell, permission: 'notifications.view' },
{ 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' },
type NavItem = { name: string; path: string; icon: any; permission: string | null }
type NavSection = { label: string; items: NavItem[] }
const navSections: NavSection[] = [
{
label: '',
items: [
{ name: 'Dashboard', path: '/', icon: LayoutDashboard, permission: null },
],
},
{
label: 'Server',
items: [
{ name: 'Server', path: '/server', icon: Server, permission: 'server.view' },
{ name: 'Console', path: '/console', icon: Terminal, permission: 'console.view' },
{ name: 'Players', path: '/players', icon: Users, permission: 'players.view' },
{ name: 'Plugins', path: '/plugins', icon: Puzzle, permission: 'plugins.view' },
{ name: 'File Manager', path: '/files', icon: FolderOpen, permission: 'files.view' },
],
},
{
label: 'Plugin Configs',
items: [
{ name: 'Loot Builder', path: '/loot-builder', icon: Crosshair, permission: 'loot.view' },
{ name: 'Teleport', 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' },
{ 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 = [
@@ -96,10 +129,14 @@ function closeSidebar() {
sidebarOpen.value = false
}
function canShowNavItem(item: typeof navItems[0]): boolean {
function canShowNavItem(item: NavItem): boolean {
if (!item.permission) return true
return auth.hasPermission(item.permission)
}
function hasVisibleItems(section: NavSection): boolean {
return section.items.some(canShowNavItem)
}
</script>
<template>
@@ -162,29 +199,35 @@ function canShowNavItem(item: typeof navItems[0]): boolean {
<!-- Navigation -->
<nav class="flex-1 overflow-y-auto py-2">
<RouterLink
v-for="item in navItems"
v-show="canShowNavItem(item)"
:key="item.path"
:to="item.path"
@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 v-for="section in navSections" :key="section.label">
<template v-if="hasVisibleItems(section)">
<!-- Section Header -->
<div v-if="section.label" class="mt-4 mb-1 px-4">
<span class="text-[10px] font-semibold uppercase tracking-widest text-neutral-500">{{ section.label }}</span>
</div>
<!-- Section Items -->
<RouterLink
v-for="item in section.items"
v-show="canShowNavItem(item)"
:key="item.path"
:to="item.path"
@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) -->
<template v-if="auth.isSuperAdmin">
<div class="mt-4 mb-2 px-4">
<div class="flex items-center gap-2">
<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 class="mt-4 mb-1 px-4">
<span class="text-[10px] font-semibold uppercase tracking-widest text-oxide-500">Platform</span>
</div>
<RouterLink
v-for="item in adminNavItems"