feat: add brands and variant categories management

- Implemented CRUD operations for brands and variant categories in the API.
- Created UI components for managing brands and variant categories, including listing, creating, editing, and deleting.
- Enhanced the sidebar navigation to include links for brands and variant categories.
- Updated the categories API to support pagination and filtering.
- Added validation for brand and variant category names.
- Integrated toast notifications for user feedback on actions.
This commit is contained in:
2026-07-15 18:11:35 +05:30
parent 0e4bcf174b
commit c9a84e235b
14 changed files with 1438 additions and 194 deletions
@@ -4,16 +4,20 @@ import { useEffect, useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
Boxes,
Building2,
ChevronRight,
ClipboardList,
HelpCircle,
LayoutGrid,
ListTree,
Menu,
Package,
PackageCheck,
Settings,
ShoppingCart,
SwatchBook,
Tag,
Truck,
Warehouse,
X,
@@ -22,9 +26,26 @@ import {
import { cn } from "@/lib/utils"
const navItems: { title: string; href: string; icon: LucideIcon; chevron?: boolean }[] = [
const navItems: {
title: string
href: string
icon: LucideIcon
chevron?: boolean
children?: { title: string; href: string; icon: LucideIcon }[]
}[] = [
{ title: "Dashboard", href: "/dashboard", icon: LayoutGrid },
{ title: "Products", href: "/dashboard/products", icon: Package, chevron: true },
{
title: "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: "Variant", href: "/dashboard/products/variants", icon: SwatchBook },
],
},
{ 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 },
@@ -109,12 +130,47 @@ function SidebarContent({
{(!collapsed || isMobile) && (
<>
<span className="flex-1">{item.title}</span>
{item.chevron && !isActive && (
{item.chevron && !item.children && !isActive && (
<ChevronRight className="size-4 shrink-0 text-slate-300" />
)}
</>
)}
</Link>
{item.children && (!collapsed || isMobile) && (
<ul className="mt-1 flex flex-col gap-0.5 pl-11">
{(() => {
// Longest-matching href wins so a shared prefix (e.g. "Item" and
// "Category" both live under /dashboard/products) doesn't light up
// more than one sub-item at once.
const activeChild = [...item.children]
.filter((c) => pathname === c.href || pathname.startsWith(`${c.href}/`))
.sort((a, b) => b.href.length - a.href.length)[0]
return item.children.map((child) => {
const childActive = child.href === activeChild?.href
return (
<li key={child.href}>
<Link
href={child.href}
onClick={onClose}
className={cn(
"flex items-center gap-2.5 rounded-xl px-3 py-2 text-sm font-medium transition-colors",
childActive
? "bg-indigo-50 text-indigo-600"
: "text-slate-500 hover:bg-slate-50 hover:text-slate-700"
)}
>
<child.icon
className={cn("size-4 shrink-0", childActive ? "text-indigo-600" : "text-slate-400")}
/>
{child.title}
</Link>
</li>
)
})
})()}
</ul>
)}
</li>
)
})}