add rbac
This commit is contained in:
@@ -16,57 +16,77 @@ import {
|
||||
PackageCheck,
|
||||
Ruler,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
ShoppingCart,
|
||||
SlidersHorizontal,
|
||||
SwatchBook,
|
||||
Tag,
|
||||
Truck,
|
||||
Users,
|
||||
Warehouse,
|
||||
X,
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useAuth } from "@/components/auth/AuthProvider"
|
||||
|
||||
// `code` must match the seeded NavItem/SubNavItem codes in ERPCore
|
||||
// (Infra/Persistence/Configurations/NavItemConfiguration.cs / SubNavItemConfiguration.cs)
|
||||
// so role-based filtering (via GET /auth/me's navCodes) can match entries here.
|
||||
const navItems: {
|
||||
title: string
|
||||
code: string
|
||||
href: string
|
||||
icon: LucideIcon
|
||||
chevron?: boolean
|
||||
children?: { title: string; href: string; icon: LucideIcon }[]
|
||||
children?: { title: string; code: string; href: string; icon: LucideIcon }[]
|
||||
}[] = [
|
||||
{ title: "Dashboard", href: "/dashboard", icon: LayoutGrid },
|
||||
{ title: "Dashboard", code: "dashboard", href: "/dashboard", icon: LayoutGrid },
|
||||
{
|
||||
title: "Products",
|
||||
code: "products",
|
||||
href: "/dashboard/products",
|
||||
icon: Package,
|
||||
chevron: true,
|
||||
children: [
|
||||
{ title: "Item", href: "/dashboard/products", icon: Boxes },
|
||||
{ title: "Category", href: "/dashboard/products/categories", icon: ListTree },
|
||||
{ title: "Brand", href: "/dashboard/products/brands", icon: Tag },
|
||||
{ title: "Item Type", href: "/dashboard/products/item-types", icon: SwatchBook },
|
||||
{ title: "UOM", href: "/dashboard/products/uoms", icon: Ruler },
|
||||
{ title: "Configuration", href: "/dashboard/products/settings", icon: SlidersHorizontal },
|
||||
{ title: "Item", code: "products.item", href: "/dashboard/products", icon: Boxes },
|
||||
{ title: "Category", code: "products.category", href: "/dashboard/products/categories", icon: ListTree },
|
||||
{ title: "Brand", code: "products.brand", href: "/dashboard/products/brands", icon: Tag },
|
||||
{ title: "Item Type", code: "products.item-type", href: "/dashboard/products/item-types", icon: SwatchBook },
|
||||
{ title: "UOM", code: "products.uom", href: "/dashboard/products/uoms", icon: Ruler },
|
||||
{ title: "Configuration", code: "products.configuration", href: "/dashboard/products/settings", icon: SlidersHorizontal },
|
||||
],
|
||||
},
|
||||
{ title: "Vendors", href: "/dashboard/vendors", icon: Truck, chevron: true },
|
||||
{ title: "Procurement", href: "/dashboard/procurement", icon: ClipboardList, chevron: true },
|
||||
{ title: "Receiving", href: "/dashboard/receiving/grn", icon: PackageCheck, chevron: true },
|
||||
{ title: "Stock", href: "/dashboard/stock", icon: Warehouse, chevron: true },
|
||||
{ title: "Warehouses", href: "/dashboard/warehouse", icon: Building2, chevron: true },
|
||||
{ title: "Orders", href: "/dashboard/orders", icon: ShoppingCart, chevron: true },
|
||||
{ title: "Settings", href: "/dashboard/settings", icon: Settings, chevron: true },
|
||||
{ title: "Help", href: "/dashboard/help", icon: HelpCircle },
|
||||
{ title: "Vendors", code: "vendors", href: "/dashboard/vendors", icon: Truck, chevron: true },
|
||||
{ title: "Procurement", code: "procurement", href: "/dashboard/procurement", icon: ClipboardList, chevron: true },
|
||||
{ title: "Receiving", code: "receiving", href: "/dashboard/receiving/grn", icon: PackageCheck, chevron: true },
|
||||
{ title: "Stock", code: "stock", href: "/dashboard/stock", icon: Warehouse, chevron: true },
|
||||
{ title: "Warehouses", code: "warehouses", href: "/dashboard/warehouse", icon: Building2, chevron: true },
|
||||
{ title: "Orders", code: "orders", href: "/dashboard/orders", icon: ShoppingCart, chevron: true },
|
||||
{
|
||||
title: "Settings",
|
||||
code: "settings",
|
||||
href: "/dashboard/settings",
|
||||
icon: Settings,
|
||||
chevron: true,
|
||||
children: [
|
||||
{ title: "Roles", code: "settings.roles", href: "/dashboard/settings/roles", icon: ShieldCheck },
|
||||
{ title: "Users", code: "settings.users", href: "/dashboard/settings/users", icon: Users },
|
||||
],
|
||||
},
|
||||
{ title: "Help", code: "help", href: "/dashboard/help", icon: HelpCircle },
|
||||
]
|
||||
|
||||
function SidebarContent({
|
||||
items,
|
||||
collapsed,
|
||||
onCollapse,
|
||||
onClose,
|
||||
pathname,
|
||||
isMobile,
|
||||
}: {
|
||||
items: typeof navItems
|
||||
collapsed: boolean
|
||||
onCollapse: () => void
|
||||
onClose?: () => void
|
||||
@@ -110,7 +130,7 @@ function SidebarContent({
|
||||
|
||||
{/* Nav items */}
|
||||
<ul className="flex flex-col gap-1">
|
||||
{navItems.map((item) => {
|
||||
{items.map((item) => {
|
||||
const isActive =
|
||||
item.href === "/dashboard" ? pathname === item.href : pathname.startsWith(item.href)
|
||||
|
||||
@@ -195,6 +215,19 @@ export function AppSidebar() {
|
||||
const pathname = usePathname() || "/"
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [mobileOpen, setMobileOpen] = useState(false)
|
||||
const { navCodes, loading } = useAuth()
|
||||
|
||||
// While /auth/me hasn't resolved yet, show nothing rather than briefly
|
||||
// flashing the full menu to a restricted role. Once resolved, a nav item
|
||||
// is visible if its own code is granted, or (for parents) if any child is.
|
||||
const visibleItems = loading
|
||||
? []
|
||||
: navItems
|
||||
.filter((item) => navCodes.includes(item.code) || item.children?.some((c) => navCodes.includes(c.code)))
|
||||
.map((item) => ({
|
||||
...item,
|
||||
children: item.children?.filter((c) => navCodes.includes(c.code)),
|
||||
}))
|
||||
|
||||
// Close mobile menu on route change
|
||||
useEffect(() => {
|
||||
@@ -213,6 +246,7 @@ export function AppSidebar() {
|
||||
{/* ── Desktop sidebar ─────────────────────────────── */}
|
||||
<div className="my-3 hidden lg:my-4 lg:flex">
|
||||
<SidebarContent
|
||||
items={visibleItems}
|
||||
collapsed={collapsed}
|
||||
onCollapse={() => setCollapsed((v) => !v)}
|
||||
pathname={pathname}
|
||||
@@ -247,6 +281,7 @@ export function AppSidebar() {
|
||||
)}
|
||||
>
|
||||
<SidebarContent
|
||||
items={visibleItems}
|
||||
collapsed={false}
|
||||
onCollapse={() => {}}
|
||||
onClose={() => setMobileOpen(false)}
|
||||
|
||||
Reference in New Issue
Block a user