feat: add warehouse and bin management API with mock data implementation

- Implemented warehouses API with methods for listing, creating, and managing bins.
- Added wastage API to handle stock write-offs and integrate with stock adjustments.
- Created auth token management for storing and retrieving access tokens.
- Developed error mapping for consistent user-facing error messages.
- Introduced client-side validations for GRN, master data, and procurement processes.
- Defined common types for pagination, problem details, and various master data entities.
- Established procurement and stock management types to support frontend functionality.
This commit is contained in:
2026-07-13 17:11:40 +05:30
parent 4e84a15db7
commit 7ac30bb454
79 changed files with 14042 additions and 44 deletions
@@ -4,13 +4,18 @@ import { useEffect, useState } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import {
Building2,
ChevronRight,
ClipboardList,
HelpCircle,
LayoutGrid,
Menu,
Package,
PackageCheck,
Settings,
ShoppingCart,
Truck,
Warehouse,
X,
type LucideIcon,
} from "lucide-react"
@@ -20,6 +25,11 @@ import { cn } from "@/lib/utils"
const navItems: { title: string; href: string; icon: LucideIcon; chevron?: boolean }[] = [
{ title: "Dashboard", href: "/dashboard", icon: LayoutGrid },
{ title: "Products", href: "/dashboard/products", icon: Package, chevron: true },
{ 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 },
@@ -0,0 +1,84 @@
"use client"
import { Fragment } from "react"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "@/components/ui/breadcrumb"
// Per-segment display labels. Deliberately separate from Header.tsx's
// titleFromPath (which resolves one *full* path to a page title) — a
// breadcrumb needs a label per *segment*, including grouping segments that
// have no page of their own (e.g. "receiving", "grn").
const SEGMENT_LABELS: Record<string, string> = {
dashboard: "Dashboard",
products: "Items",
uoms: "UOMs",
categories: "Categories",
new: "New",
edit: "Edit",
vendors: "Vendors",
warehouse: "Warehouses",
receiving: "Receiving",
grn: "Goods Receipt Notes",
stock: "Stock",
enquiry: "Enquiry",
ledger: "Ledger",
valuation: "Valuation",
transfers: "Transfers",
adjustments: "Adjustments",
counts: "Counts",
"reorder-alerts": "Reorder Alerts",
wastage: "Wastage",
procurement: "Procurement",
requisitions: "Requisitions",
rfqs: "RFQs",
"purchase-orders": "Purchase Orders",
"purchase-returns": "Purchase Returns",
}
function labelFor(segment: string): string {
if (SEGMENT_LABELS[segment]) return SEGMENT_LABELS[segment]
if (/^[0-9]+$/.test(segment)) return `#${segment}`
return segment.charAt(0).toUpperCase() + segment.slice(1)
}
const linkClass =
"truncate transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
export function Breadcrumbs() {
const pathname = usePathname() || "/dashboard"
const segments = pathname.split("/").filter(Boolean)
// Nothing to show above the dashboard root itself.
if (segments.length <= 1) return null
const crumbs = segments.map((segment, i) => ({
href: "/" + segments.slice(0, i + 1).join("/"),
label: labelFor(segment),
}))
return (
<Breadcrumb className="mb-4">
<BreadcrumbList>
{crumbs.map((crumb, i) => {
const isLast = i === crumbs.length - 1
return (
<Fragment key={crumb.href}>
<BreadcrumbItem>
{isLast ? (
<BreadcrumbPage>{crumb.label}</BreadcrumbPage>
) : (
<Link href={crumb.href} className={linkClass}>
{crumb.label}
</Link>
)}
</BreadcrumbItem>
{!isLast && <BreadcrumbSeparator />}
</Fragment>
)
})}
</BreadcrumbList>
</Breadcrumb>
)
}
@@ -17,7 +17,60 @@ import {
} from "@/components/ui/dropdown-menu"
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
const PROCUREMENT_TITLES: Record<string, string> = {
"/dashboard/procurement": "Procurement",
"/dashboard/procurement/requisitions": "Requisitions",
"/dashboard/procurement/requisitions/new": "New Requisition",
"/dashboard/procurement/rfqs": "RFQs",
"/dashboard/procurement/rfqs/new": "New RFQ",
"/dashboard/procurement/purchase-orders": "Purchase Orders",
"/dashboard/procurement/purchase-orders/new": "New Purchase Order",
"/dashboard/procurement/purchase-returns": "Purchase Returns",
"/dashboard/procurement/purchase-returns/new": "New Purchase Return",
}
const STOCK_TITLES: Record<string, string> = {
"/dashboard/stock": "Stock Management",
"/dashboard/stock/enquiry": "Stock Enquiry",
"/dashboard/stock/ledger": "Stock Ledger",
"/dashboard/stock/valuation": "Valuation",
"/dashboard/stock/transfers": "Stock Transfers",
"/dashboard/stock/transfers/new": "New Transfer",
"/dashboard/stock/adjustments": "Stock Adjustments",
"/dashboard/stock/adjustments/new": "New Adjustment",
"/dashboard/stock/counts": "Stock Counts",
"/dashboard/stock/counts/new": "New Count",
"/dashboard/stock/reorder-alerts": "Reorder Alerts",
"/dashboard/stock/wastage": "Wastage",
"/dashboard/stock/wastage/new": "Record Wastage",
}
function titleFromPath(pathname: string) {
if (pathname === "/dashboard/receiving/grn") return "Goods Receipt Notes"
if (pathname === "/dashboard/receiving/grn/new") return "Create Goods Receipt Note"
if (/^\/dashboard\/receiving\/grn\/[^/]+$/.test(pathname)) return "Goods Receipt Note"
if (STOCK_TITLES[pathname]) return STOCK_TITLES[pathname]
if (/^\/dashboard\/stock\/transfers\/[^/]+$/.test(pathname)) return "Stock Transfer"
if (/^\/dashboard\/stock\/counts\/[^/]+$/.test(pathname)) return "Stock Count"
if (PROCUREMENT_TITLES[pathname]) return PROCUREMENT_TITLES[pathname]
if (/^\/dashboard\/procurement\/requisitions\/[^/]+$/.test(pathname)) return "Requisition"
if (/^\/dashboard\/procurement\/rfqs\/[^/]+$/.test(pathname)) return "RFQ"
if (/^\/dashboard\/procurement\/purchase-orders\/[^/]+$/.test(pathname)) return "Purchase Order"
if (pathname === "/dashboard/warehouse") return "Warehouses"
if (/^\/dashboard\/warehouse\/[^/]+$/.test(pathname)) return "Warehouse"
if (pathname === "/dashboard/vendors") return "Vendors"
if (/^\/dashboard\/vendors\/[^/]+$/.test(pathname)) return "View Vendor"
if (pathname === "/dashboard/products") return "Items"
if (pathname === "/dashboard/products/new") return "New Item"
if (pathname === "/dashboard/products/uoms") return "Units of Measure"
if (pathname === "/dashboard/products/categories") return "Categories"
if (/^\/dashboard\/products\/[^/]+$/.test(pathname)) return "Item"
const segment = pathname.split("/").filter(Boolean).pop() ?? "dashboard"
return segment.charAt(0).toUpperCase() + segment.slice(1)
}