176 lines
6.2 KiB
TypeScript
176 lines
6.2 KiB
TypeScript
'use client'
|
|
|
|
import Link from 'next/link'
|
|
import { usePathname } from 'next/navigation'
|
|
import { HugeiconsIcon } from '@hugeicons/react'
|
|
import { ChevronRightIcon, UserIcon } from '@hugeicons/core-free-icons'
|
|
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarGroup,
|
|
SidebarHeader,
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
SidebarMenuSub,
|
|
SidebarMenuSubButton,
|
|
SidebarMenuSubItem,
|
|
SidebarRail,
|
|
} from '@/components/ui/sidebar'
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from '@/components/ui/collapsible'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select'
|
|
import { useAuth, type Role } from '@/contexts/auth-context'
|
|
import { DASHBOARD_ITEM, NAV_GROUPS, type NavGroup, type NavItem } from '@/config/nav'
|
|
|
|
function canSee(item: NavItem, permissions: string[]): boolean {
|
|
if (!item.permission) return true
|
|
return permissions.includes('*') || permissions.includes(item.permission)
|
|
}
|
|
|
|
function isGroupVisible(group: NavGroup, permissions: string[]): boolean {
|
|
return group.items.some((item) => canSee(item, permissions))
|
|
}
|
|
|
|
export function AppSidebar() {
|
|
const { user, setRole, permissions } = useAuth()
|
|
const pathname = usePathname()
|
|
|
|
const dashboardActive = pathname === DASHBOARD_ITEM.href
|
|
|
|
return (
|
|
<Sidebar collapsible="icon">
|
|
<SidebarHeader>
|
|
<div className="flex items-center gap-2 px-2 py-1">
|
|
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground text-sm font-bold">
|
|
D
|
|
</div>
|
|
<span className="truncate font-semibold text-sidebar-foreground group-data-[collapsible=icon]:hidden">
|
|
Distribution
|
|
</span>
|
|
</div>
|
|
</SidebarHeader>
|
|
|
|
<SidebarContent className="gap-0">
|
|
{/* Dashboard — standalone, not inside a collapsible group */}
|
|
<SidebarGroup className="py-1">
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton
|
|
render={<Link href={DASHBOARD_ITEM.href} />}
|
|
isActive={dashboardActive}
|
|
tooltip={DASHBOARD_ITEM.title}
|
|
>
|
|
<HugeiconsIcon icon={DASHBOARD_ITEM.icon} size={18} />
|
|
<span>{DASHBOARD_ITEM.title}</span>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
|
|
{/* Collapsible groups */}
|
|
{NAV_GROUPS.map((group) => {
|
|
if (!isGroupVisible(group, permissions)) return null
|
|
|
|
const visibleItems = group.items.filter((item) =>
|
|
canSee(item, permissions),
|
|
)
|
|
const groupActive = visibleItems.some(
|
|
(item) =>
|
|
pathname === item.href ||
|
|
(item.href !== '/' && pathname.startsWith(item.href)),
|
|
)
|
|
|
|
return (
|
|
<Collapsible
|
|
key={group.title}
|
|
defaultOpen={false}
|
|
className="group/collapsible"
|
|
>
|
|
<SidebarGroup className="py-0">
|
|
<SidebarMenu>
|
|
{/* Group trigger — looks like a regular nav button */}
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton
|
|
render={<CollapsibleTrigger />}
|
|
isActive={groupActive}
|
|
tooltip={group.title}
|
|
>
|
|
<HugeiconsIcon icon={group.icon} size={18} />
|
|
<span>{group.title}</span>
|
|
<HugeiconsIcon
|
|
icon={ChevronRightIcon}
|
|
size={14}
|
|
className="ml-auto shrink-0 transition-transform duration-200 group-data-[open]/collapsible:rotate-90 group-data-[collapsible=icon]:hidden"
|
|
/>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
|
|
{/* Sub-items with indent line */}
|
|
<CollapsibleContent>
|
|
<SidebarMenuSub>
|
|
{visibleItems.map((item) => {
|
|
const isActive =
|
|
pathname === item.href ||
|
|
(item.href !== '/' &&
|
|
pathname.startsWith(item.href))
|
|
return (
|
|
<SidebarMenuSubItem key={item.href}>
|
|
<SidebarMenuSubButton
|
|
render={<Link href={item.href} />}
|
|
isActive={isActive}
|
|
>
|
|
<HugeiconsIcon icon={item.icon} size={15} />
|
|
<span>{item.title}</span>
|
|
</SidebarMenuSubButton>
|
|
</SidebarMenuSubItem>
|
|
)
|
|
})}
|
|
</SidebarMenuSub>
|
|
</CollapsibleContent>
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
</Collapsible>
|
|
)
|
|
})}
|
|
</SidebarContent>
|
|
|
|
<SidebarFooter>
|
|
<div className="flex items-center gap-2 px-2 py-1 group-data-[collapsible=icon]:justify-center">
|
|
<div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground">
|
|
<HugeiconsIcon icon={UserIcon} size={16} />
|
|
</div>
|
|
<div className="flex min-w-0 flex-1 flex-col group-data-[collapsible=icon]:hidden">
|
|
<span className="truncate text-sm font-medium text-sidebar-foreground">
|
|
{user.fullName}
|
|
</span>
|
|
<Select value={user.role} onValueChange={(v) => setRole(v as Role)}>
|
|
<SelectTrigger className="h-5 border-0 p-0 text-xs text-muted-foreground shadow-none focus:ring-0">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="admin">Admin</SelectItem>
|
|
<SelectItem value="warehouse">Warehouse</SelectItem>
|
|
<SelectItem value="sales">Sales</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
</SidebarFooter>
|
|
|
|
<SidebarRail />
|
|
</Sidebar>
|
|
)
|
|
}
|