2025-09-26 17:56:55 +07:00
|
|
|
import type React from "react";
|
2025-08-11 23:21:36 +07:00
|
|
|
import { Link } from "@tanstack/react-router";
|
2025-09-26 17:56:55 +07:00
|
|
|
import { Building2, Cpu } from "lucide-react";
|
2025-08-11 23:21:36 +07:00
|
|
|
import {
|
|
|
|
|
Sidebar,
|
|
|
|
|
SidebarContent,
|
|
|
|
|
SidebarFooter,
|
|
|
|
|
SidebarGroup,
|
|
|
|
|
SidebarGroupContent,
|
|
|
|
|
SidebarGroupLabel,
|
|
|
|
|
SidebarHeader,
|
|
|
|
|
SidebarMenu,
|
|
|
|
|
SidebarMenuButton,
|
|
|
|
|
SidebarMenuItem,
|
|
|
|
|
} from "@/components/ui/sidebar";
|
2025-09-26 17:56:55 +07:00
|
|
|
import { cn } from "@/lib/utils";
|
2026-03-04 14:41:34 +07:00
|
|
|
import { appSidebarSection } from "@/types/app-sidebar";
|
|
|
|
|
import { PermissionEnum } from "@/types/permission";
|
|
|
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
|
|
|
import { useMemo } from "react";
|
2025-08-11 23:21:36 +07:00
|
|
|
|
2026-03-04 14:41:34 +07:00
|
|
|
type SidebarItem = {
|
2025-08-11 23:21:36 +07:00
|
|
|
title: string;
|
2026-03-04 14:41:34 +07:00
|
|
|
url: string;
|
|
|
|
|
code?: number;
|
2025-08-11 23:21:36 +07:00
|
|
|
icon: React.ElementType;
|
2026-03-04 14:41:34 +07:00
|
|
|
permissions?: PermissionEnum[];
|
2025-08-11 23:21:36 +07:00
|
|
|
};
|
|
|
|
|
|
2026-03-04 14:41:34 +07:00
|
|
|
type SidebarSection = {
|
|
|
|
|
title: string;
|
|
|
|
|
items: SidebarItem[];
|
2025-08-11 23:21:36 +07:00
|
|
|
};
|
|
|
|
|
|
2026-03-04 14:41:34 +07:00
|
|
|
export function AppSidebar() {
|
|
|
|
|
const { hasPermission, acs } = useAuth();
|
|
|
|
|
|
|
|
|
|
// Check if user is admin (has ALLOW_ALL permission)
|
|
|
|
|
const isAdmin = acs.includes(PermissionEnum.ALLOW_ALL);
|
|
|
|
|
|
|
|
|
|
// Check if user has any of the required permissions
|
|
|
|
|
const checkPermissions = (permissions?: PermissionEnum[]) => {
|
|
|
|
|
// No permissions defined = show to everyone
|
|
|
|
|
if (!permissions || permissions.length === 0) return true;
|
|
|
|
|
// Item marked as ALLOW_ALL = show to everyone
|
|
|
|
|
if (permissions.includes(PermissionEnum.ALLOW_ALL)) return true;
|
|
|
|
|
// Admin users see everything
|
|
|
|
|
if (isAdmin) return true;
|
|
|
|
|
// Check if user has any of the required permissions
|
|
|
|
|
return permissions.some((permission) => hasPermission(permission));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Filter sidebar sections and items based on permissions
|
|
|
|
|
const filteredNavMain = useMemo(() => {
|
|
|
|
|
return appSidebarSection.navMain
|
|
|
|
|
.map((section) => ({
|
|
|
|
|
...section,
|
|
|
|
|
items: section.items.filter((item) => checkPermissions(item.permissions)),
|
|
|
|
|
}))
|
|
|
|
|
.filter((section) => section.items.length > 0) as SidebarSection[];
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [acs]);
|
|
|
|
|
|
2025-08-11 23:21:36 +07:00
|
|
|
return (
|
2025-09-26 17:56:55 +07:00
|
|
|
<Sidebar
|
|
|
|
|
collapsible="icon"
|
|
|
|
|
className="border-r border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
|
|
|
|
|
>
|
|
|
|
|
<SidebarHeader className="border-b border-border/40 p-6">
|
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
|
<div className="flex aspect-square size-10 items-center justify-center rounded-xl bg-gradient-to-br from-gray-900 to-black text-white shadow-lg ring-1 ring-gray-800/30">
|
|
|
|
|
<Building2 className="size-5" />
|
2025-08-11 23:21:36 +07:00
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-0.5 leading-none">
|
2025-09-26 17:56:55 +07:00
|
|
|
<span className="font-bold text-base tracking-tight bg-gradient-to-r from-foreground to-foreground/80 bg-clip-text">
|
|
|
|
|
TTMT Computer Management
|
|
|
|
|
</span>
|
|
|
|
|
<span className="text-xs text-muted-foreground font-medium flex items-center gap-1">
|
|
|
|
|
<Cpu className="size-3" />
|
|
|
|
|
v1.0.0
|
|
|
|
|
</span>
|
2025-08-11 23:21:36 +07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</SidebarHeader>
|
2025-09-26 17:56:55 +07:00
|
|
|
|
|
|
|
|
<SidebarContent className="p-4">
|
2026-03-04 14:41:34 +07:00
|
|
|
{filteredNavMain.map((section) => (
|
|
|
|
|
<SidebarGroup key={section.title}>
|
|
|
|
|
<SidebarGroupLabel className="text-xs font-semibold text-muted-foreground/80 uppercase tracking-wider mb-2">
|
|
|
|
|
{section.title}
|
|
|
|
|
</SidebarGroupLabel>
|
|
|
|
|
<SidebarGroupContent>
|
|
|
|
|
<SidebarMenu className="space-y-1">
|
|
|
|
|
{section.items.map((item) => (
|
|
|
|
|
<SidebarMenuItem key={item.title}>
|
|
|
|
|
<SidebarMenuButton
|
|
|
|
|
asChild
|
|
|
|
|
tooltip={item.title}
|
|
|
|
|
className={cn(
|
|
|
|
|
"w-full justify-start gap-3 px-4 py-3 rounded-xl",
|
|
|
|
|
"hover:bg-accent/60 hover:text-accent-foreground hover:shadow-sm",
|
|
|
|
|
"transition-all duration-200 ease-in-out",
|
|
|
|
|
"group relative overflow-hidden",
|
|
|
|
|
"data-[active=true]:bg-primary data-[active=true]:text-primary-foreground",
|
|
|
|
|
"data-[active=true]:shadow-md data-[active=true]:ring-1 data-[active=true]:ring-primary/20"
|
|
|
|
|
)}
|
2025-09-26 17:56:55 +07:00
|
|
|
>
|
2026-03-04 14:41:34 +07:00
|
|
|
<Link
|
|
|
|
|
href={item.url}
|
|
|
|
|
to={item.url}
|
|
|
|
|
className="flex items-center gap-3 w-full"
|
|
|
|
|
>
|
|
|
|
|
<item.icon className="size-5 shrink-0 transition-all duration-200 group-hover:scale-110 group-hover:text-primary" />
|
|
|
|
|
<span className="font-medium text-sm truncate">
|
|
|
|
|
{item.title}
|
|
|
|
|
</span>
|
|
|
|
|
</Link>
|
|
|
|
|
</SidebarMenuButton>
|
|
|
|
|
</SidebarMenuItem>
|
|
|
|
|
))}
|
|
|
|
|
</SidebarMenu>
|
|
|
|
|
</SidebarGroupContent>
|
|
|
|
|
</SidebarGroup>
|
|
|
|
|
))}
|
2025-08-11 23:21:36 +07:00
|
|
|
</SidebarContent>
|
2025-09-26 17:56:55 +07:00
|
|
|
|
|
|
|
|
<SidebarFooter className="border-t border-border/40 p-4 space-y-3">
|
|
|
|
|
<div className="px-2 text-xs text-muted-foreground/60 font-medium">
|
|
|
|
|
© 2025 NAVIS Centre
|
2025-08-11 23:21:36 +07:00
|
|
|
</div>
|
|
|
|
|
</SidebarFooter>
|
|
|
|
|
</Sidebar>
|
|
|
|
|
);
|
|
|
|
|
}
|