TTMT.ManageWebGUI/src/components/sidebars/app-sidebar.tsx

135 lines
5.0 KiB
TypeScript
Raw Normal View History

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>
);
}