diff --git a/Frontend/erp-system/app/dashboard/procurement/purchase-orders/new/page.tsx b/Frontend/erp-system/app/dashboard/procurement/purchase-orders/new/page.tsx index ae5672e..411799d 100644 --- a/Frontend/erp-system/app/dashboard/procurement/purchase-orders/new/page.tsx +++ b/Frontend/erp-system/app/dashboard/procurement/purchase-orders/new/page.tsx @@ -43,8 +43,12 @@ function newKey() { return `poline-${keySeq}` } +// Unit price and tax are no longer entered at PO creation — pricing is captured at GRN +// receipt (with discount/VAT there). They default to 0 here and stay off the form, but +// remain on the payload because the backend line DTO still requires them; a PO prefilled +// from an RFQ keeps its negotiated price (below). function emptyLine(): DraftLine { - return { key: newKey(), itemId: null, uomId: null, warehouseId: null, qty: "", unitPrice: "", tax: "0.18" } + return { key: newKey(), itemId: null, uomId: null, warehouseId: null, qty: "", unitPrice: "0", tax: "0" } } function NewPurchaseOrderContent() { @@ -98,8 +102,8 @@ function NewPurchaseOrderContent() { uomId: null, warehouseId: null, qty: String(l.qty), - unitPrice: "", - tax: "0.18", + unitPrice: "0", + tax: "0", }) ) ) @@ -124,8 +128,8 @@ function NewPurchaseOrderContent() { uomId: null, warehouseId: null, qty: String(l.qty), - unitPrice: cell ? String(cell.unitPrice) : "", - tax: "0.18", + unitPrice: cell ? String(cell.unitPrice) : "0", + tax: "0", } }) ) @@ -278,8 +282,6 @@ function NewPurchaseOrderContent() { UOM Warehouse Qty - Unit price - Tax @@ -352,30 +354,6 @@ function NewPurchaseOrderContent() { /> - - updateLine(line.key, { unitPrice: e.target.value })} - className="h-11 text-base" - /> - - - - updateLine(line.key, { tax: e.target.value })} - className="h-11 text-base" - /> - - removeLine(line.key)} aria-label="Remove line"> diff --git a/Frontend/erp-system/components/Layouts/AppSidebar.tsx b/Frontend/erp-system/components/Layouts/AppSidebar.tsx index ca61060..1b9c7ed 100644 --- a/Frontend/erp-system/components/Layouts/AppSidebar.tsx +++ b/Frontend/erp-system/components/Layouts/AppSidebar.tsx @@ -107,18 +107,36 @@ function SidebarContent({ pathname: string isMobile: boolean }) { + const iconOnly = !isMobile && collapsed + + // Which parent menus are open. Starts with the parent that owns the active + // route auto-expanded; user toggles are preserved across navigation. + const [expanded, setExpanded] = useState>({}) + + useEffect(() => { + const parent = items.find((i) => + i.children?.some((c) => pathname === c.href || pathname.startsWith(`${c.href}/`)) + ) + if (parent) { + setExpanded((prev) => (prev[parent.code] ? prev : { ...prev, [parent.code]: true })) + } + }, [pathname, items]) + + const toggleExpand = (code: string) => + setExpanded((prev) => ({ ...prev, [code]: !prev[code] })) + return ( {/* Header */} @@ -127,7 +145,7 @@ function SidebarContent({ - {(!collapsed || isMobile) && ( + {!iconOnly && ( Hexa ERP )} @@ -142,79 +160,116 @@ function SidebarContent({ - {/* Nav items */} - + {/* Nav items — scrolls internally when it overflows, without a visible + scrollbar so the rounded panel stays clean. */} + {items.map((item) => { const isActive = item.href === "/dashboard" ? pathname === item.href : pathname.startsWith(item.href) + const hasChildren = !!item.children?.length && !iconOnly + const isOpen = !!expanded[item.code] return ( - - - {(!collapsed || isMobile) && ( - <> - {item.title} - {item.chevron && !item.children && !isActive && ( - - )} - > - )} - + + + {!iconOnly && ( + <> + {item.title} + {item.chevron && !hasChildren && ( + + )} + > + )} + - {item.children && (!collapsed || isMobile) && ( - - {(() => { - // 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 ( - - - - {child.title} - - - ) - }) - })()} - + {hasChildren && ( + toggleExpand(item.code)} + aria-label={isOpen ? `Collapse ${item.title}` : `Expand ${item.title}`} + aria-expanded={isOpen} + className={cn( + "mr-2 flex size-7 shrink-0 items-center justify-center rounded-lg transition-colors hover:bg-white/60", + isActive ? "text-indigo-500" : "text-slate-400" + )} + > + + + )} + + + {hasChildren && ( + + + + {(() => { + // 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 ( + + + + {child.title} + + + ) + }) + })()} + + + )} ) })} - +