Merge pull request 'fix: update toast messages and improve UI elements in purchase orders and stock pages' (#34) from ui-fixers-7/8 into Dev
Reviewed-on: #34
This commit was merged in pull request #34.
This commit is contained in:
@@ -195,11 +195,9 @@ export default function PurchaseOrderDetailPage() {
|
|||||||
setPo(updated)
|
setPo(updated)
|
||||||
setLines(toDraftLines(updated))
|
setLines(toDraftLines(updated))
|
||||||
toast.success("Purchase order approved", `${updated.docNo} — now ${updated.status} and locked for editing.`)
|
toast.success("Purchase order approved", `${updated.docNo} — now ${updated.status} and locked for editing.`)
|
||||||
toast.success("Purchase order approved", `${updated.docNo} — now ${updated.status} and locked for editing.`)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setSaveError(errorMessage(err))
|
setSaveError(errorMessage(err))
|
||||||
toast.error("Could not approve purchase order", errorMessage(err))
|
toast.error("Could not approve purchase order", errorMessage(err))
|
||||||
toast.error("Could not approve purchase order", errorMessage(err))
|
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitting(false)
|
setSubmitting(false)
|
||||||
}
|
}
|
||||||
@@ -286,7 +284,7 @@ export default function PurchaseOrderDetailPage() {
|
|||||||
<>
|
<>
|
||||||
<Button variant="success" size="lg" onClick={handleSubmitPo} disabled={submitting || deleting}>
|
<Button variant="success" size="lg" onClick={handleSubmitPo} disabled={submitting || deleting}>
|
||||||
<Check className="size-5" />
|
<Check className="size-5" />
|
||||||
{submitting ? "Approving…" : "Approve"}
|
{submitting ? "Approving�" : "Approve"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="destructive" size="lg" onClick={handleDelete} disabled={deleting || submitting}>
|
<Button variant="destructive" size="lg" onClick={handleDelete} disabled={deleting || submitting}>
|
||||||
<Trash2 className="size-5" />
|
<Trash2 className="size-5" />
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ function newKey() {
|
|||||||
return `poline-${keySeq}`
|
return `poline-${keySeq}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit price and tax are no longer entered at PO creation — pricing is captured at GRN
|
// Tax is still not entered at PO creation — it's captured at GRN receipt (with discount/VAT
|
||||||
// receipt (with discount/VAT there). They default to 0 here and stay off the form, but
|
// there) and stays off this form, though it remains on the payload since the backend line
|
||||||
// remain on the payload because the backend line DTO still requires them; a PO prefilled
|
// DTO still requires it. Unit price *is* entered here; a PO prefilled from an RFQ starts
|
||||||
// from an RFQ keeps its negotiated price (below).
|
// from its negotiated price (below) but stays editable.
|
||||||
function emptyLine(): DraftLine {
|
function emptyLine(): DraftLine {
|
||||||
return { key: newKey(), itemId: null, uomId: null, warehouseId: null, qty: "", unitPrice: "0", tax: "0" }
|
return { key: newKey(), itemId: null, uomId: null, warehouseId: null, qty: "", unitPrice: "0", tax: "0" }
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ function NewPurchaseOrderContent() {
|
|||||||
{!loading && (
|
{!loading && (
|
||||||
<>
|
<>
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-4">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-4">
|
||||||
<div className="flex flex-col gap-2 sm:col-span-2">
|
<div className="flex flex-col gap-2 sm:col-span-1">
|
||||||
<Label className="text-base">Vendor</Label>
|
<Label className="text-base">Vendor</Label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Select<number | null>
|
<Select<number | null>
|
||||||
@@ -406,13 +406,14 @@ function NewPurchaseOrderContent() {
|
|||||||
|
|
||||||
{lines.length > 0 && (
|
{lines.length > 0 && (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<Table className="text-base">
|
<Table className="table-fixed text-base">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="h-12 w-56 px-3 text-sm">Item</TableHead>
|
<TableHead className="h-12 w-32 px-3 text-sm">Item</TableHead>
|
||||||
<TableHead className="h-12 w-28 px-3 text-sm">UOM</TableHead>
|
<TableHead className="h-12 w-16 px-3 text-sm">UOM</TableHead>
|
||||||
<TableHead className="h-12 w-40 px-3 text-sm">Warehouse</TableHead>
|
<TableHead className="h-12 w-20 px-3 text-sm">Warehouse</TableHead>
|
||||||
<TableHead className="h-12 w-24 px-3 text-sm">Qty</TableHead>
|
<TableHead className="h-12 w-24 px-3 text-sm">Qty</TableHead>
|
||||||
|
<TableHead className="h-12 w-28 px-3 text-sm">Unit price</TableHead>
|
||||||
<TableHead className="h-12 w-10 px-3" />
|
<TableHead className="h-12 w-10 px-3" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@@ -424,11 +425,15 @@ function NewPurchaseOrderContent() {
|
|||||||
<TableRow key={line.key}>
|
<TableRow key={line.key}>
|
||||||
<TableCell className="px-3 py-3 align-top">
|
<TableCell className="px-3 py-3 align-top">
|
||||||
{requisitionId || rfqId ? (
|
{requisitionId || rfqId ? (
|
||||||
<div className="flex h-11 items-center text-base">{item ? `${item.sku} — ${item.name}` : `Item #${line.itemId}`}</div>
|
<div className="flex h-11 items-center truncate text-base" title={item ? `${item.sku} — ${item.name}` : undefined}>{item ? `${item.sku} — ${item.name}` : `Item #${line.itemId}`}</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Select<number | null> value={line.itemId} onValueChange={(v) => updateLine(line.key, { itemId: v })}>
|
<Select<number | null> value={line.itemId} onValueChange={(v) => updateLine(line.key, { itemId: v })}>
|
||||||
<SelectTrigger className="h-11! w-full text-base" aria-invalid={!!errors.itemId}>
|
<SelectTrigger
|
||||||
|
className="h-11! w-full text-base"
|
||||||
|
aria-invalid={!!errors.itemId}
|
||||||
|
title={item ? `${item.sku} — ${item.name}` : undefined}
|
||||||
|
>
|
||||||
<SelectValue placeholder="Select item" />
|
<SelectValue placeholder="Select item" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -485,6 +490,18 @@ function NewPurchaseOrderContent() {
|
|||||||
/>
|
/>
|
||||||
<FieldError errors={[errors.qty ? { message: errors.qty } : undefined]} />
|
<FieldError errors={[errors.qty ? { message: errors.qty } : undefined]} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
<TableCell className="px-3 py-3 align-top">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
step="any"
|
||||||
|
value={line.unitPrice}
|
||||||
|
aria-invalid={!!errors.unitPrice}
|
||||||
|
onChange={(e) => updateLine(line.key, { unitPrice: e.target.value })}
|
||||||
|
className="h-11 text-base"
|
||||||
|
/>
|
||||||
|
<FieldError errors={[errors.unitPrice ? { message: errors.unitPrice } : undefined]} />
|
||||||
|
</TableCell>
|
||||||
<TableCell className="px-3 py-3 align-top">
|
<TableCell className="px-3 py-3 align-top">
|
||||||
<Button type="button" variant="ghost" size="icon" onClick={() => removeLine(line.key)} aria-label="Remove line">
|
<Button type="button" variant="ghost" size="icon" onClick={() => removeLine(line.key)} aria-label="Remove line">
|
||||||
<Trash2 className="size-5" />
|
<Trash2 className="size-5" />
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export default function ItemDetailPage() {
|
|||||||
<h1 className="text-2xl font-bold text-foreground">{item.sku}</h1>
|
<h1 className="text-2xl font-bold text-foreground">{item.sku}</h1>
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className={cn("h-6 w-fit justify-center border-transparent px-2.5 text-sm", item.status === "Active" ? "bg-success/10 text-success" : "bg-muted text-muted-foreground")}
|
className={cn("h-6 w-fit justify-center border-transparent px-2.5 text-sm", item.status === "Active" ? "bg-success/10 text-success" : "bg-destructive/10 text-destructive")}
|
||||||
>
|
>
|
||||||
{item.status}
|
{item.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -257,7 +257,15 @@ export default function BrandsPage() {
|
|||||||
<TableCell className="px-3 py-3.5 text-muted-foreground">#{b.brandId}</TableCell>
|
<TableCell className="px-3 py-3.5 text-muted-foreground">#{b.brandId}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5 font-medium">{b.name}</TableCell>
|
<TableCell className="px-3 py-3.5 font-medium">{b.name}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5">
|
<TableCell className="px-3 py-3.5">
|
||||||
<Badge variant={b.status === "Active" ? "default" : "secondary"}>{b.status}</Badge>
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
"border-transparent",
|
||||||
|
b.status === "Active" ? "bg-success/10 text-success" : "bg-destructive/10 text-destructive"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{b.status}
|
||||||
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="px-3 py-3.5 text-muted-foreground">{new Date(b.createdAt).toLocaleDateString()}</TableCell>
|
<TableCell className="px-3 py-3.5 text-muted-foreground">{new Date(b.createdAt).toLocaleDateString()}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5">
|
<TableCell className="px-3 py-3.5">
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Ban, CheckCircle2, Network, Pencil, Plus } from "lucide-react"
|
|||||||
import { categoriesApi, subCategoriesApi } from "@/lib/api/categories"
|
import { categoriesApi, subCategoriesApi } from "@/lib/api/categories"
|
||||||
import { errorMessage } from "@/lib/error-map"
|
import { errorMessage } from "@/lib/error-map"
|
||||||
import { validateCategoryName } from "@/lib/validations/master-data"
|
import { validateCategoryName } from "@/lib/validations/master-data"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
import { Category, SubCategory } from "@/types/master-data"
|
import { Category, SubCategory } from "@/types/master-data"
|
||||||
|
|
||||||
import { AlertDialog, AlertDialogContent, AlertDialogTrigger } from "@/components/ui/alert-dialog"
|
import { AlertDialog, AlertDialogContent, AlertDialogTrigger } from "@/components/ui/alert-dialog"
|
||||||
@@ -181,7 +182,15 @@ export default function CategorySubCategoriesPage() {
|
|||||||
<TableCell className="px-3 py-3.5 text-muted-foreground">#{s.subCategoryId}</TableCell>
|
<TableCell className="px-3 py-3.5 text-muted-foreground">#{s.subCategoryId}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5 font-medium">{s.name}</TableCell>
|
<TableCell className="px-3 py-3.5 font-medium">{s.name}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5">
|
<TableCell className="px-3 py-3.5">
|
||||||
<Badge variant={s.status === "Active" ? "default" : "secondary"}>{s.status}</Badge>
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
"border-transparent",
|
||||||
|
s.status === "Active" ? "bg-success/10 text-success" : "bg-destructive/10 text-destructive"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{s.status}
|
||||||
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="px-3 py-3.5 text-muted-foreground">{new Date(s.createdAt).toLocaleDateString()}</TableCell>
|
<TableCell className="px-3 py-3.5 text-muted-foreground">{new Date(s.createdAt).toLocaleDateString()}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5">
|
<TableCell className="px-3 py-3.5">
|
||||||
|
|||||||
@@ -256,7 +256,15 @@ export default function CategoriesPage() {
|
|||||||
<TableCell className="px-3 py-3.5 text-muted-foreground">#{c.categoryId}</TableCell>
|
<TableCell className="px-3 py-3.5 text-muted-foreground">#{c.categoryId}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5 font-medium">{c.name}</TableCell>
|
<TableCell className="px-3 py-3.5 font-medium">{c.name}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5">
|
<TableCell className="px-3 py-3.5">
|
||||||
<Badge variant={c.status === "Active" ? "default" : "secondary"}>{c.status}</Badge>
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className={cn(
|
||||||
|
"border-transparent",
|
||||||
|
c.status === "Active" ? "bg-success/10 text-success" : "bg-destructive/10 text-destructive"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{c.status}
|
||||||
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="px-3 py-3.5 text-muted-foreground">{new Date(c.createdAt).toLocaleDateString()}</TableCell>
|
<TableCell className="px-3 py-3.5 text-muted-foreground">{new Date(c.createdAt).toLocaleDateString()}</TableCell>
|
||||||
<TableCell className="px-3 py-3.5">
|
<TableCell className="px-3 py-3.5">
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ export default function ItemsPage() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-6 w-fit justify-center border-transparent px-2.5 text-sm",
|
"h-6 w-fit justify-center border-transparent px-2.5 text-sm",
|
||||||
item.status === "Active" ? "bg-success/10 text-success" : "bg-muted text-muted-foreground"
|
item.status === "Active" ? "bg-success/10 text-success" : "bg-destructive/10 text-destructive"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.status}
|
{item.status}
|
||||||
|
|||||||
@@ -443,10 +443,10 @@ export default function NewGrnPage() {
|
|||||||
<Table className="text-base">
|
<Table className="text-base">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead className="h-12 w-96 px-3 text-sm">Item</TableHead>
|
<TableHead className="h-12 w-56 px-3 text-sm">Item</TableHead>
|
||||||
<TableHead className="h-12 w-24 px-3 text-sm">UOM</TableHead>
|
<TableHead className="h-12 w-24 px-3 text-sm">UOM</TableHead>
|
||||||
<TableHead className="h-12 w-28 px-3 text-sm">Bin</TableHead>
|
<TableHead className="h-12 w-28 px-3 text-sm">Bin</TableHead>
|
||||||
<TableHead className="h-12 w-20 px-3 text-sm">Qty</TableHead>
|
<TableHead className="h-12 w-32 px-3 text-sm">Qty</TableHead>
|
||||||
<TableHead className="h-12 w-28 px-3 text-sm">Unit cost</TableHead>
|
<TableHead className="h-12 w-28 px-3 text-sm">Unit cost</TableHead>
|
||||||
<TableHead className="h-12 w-24 px-3 text-sm">Disc %</TableHead>
|
<TableHead className="h-12 w-24 px-3 text-sm">Disc %</TableHead>
|
||||||
<TableHead className="h-12 w-24 px-3 text-sm">VAT %</TableHead>
|
<TableHead className="h-12 w-24 px-3 text-sm">VAT %</TableHead>
|
||||||
@@ -653,7 +653,7 @@ export default function NewGrnPage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!poLoading && lines.length > 0 && (
|
{!poLoading && lines.length > 0 && (
|
||||||
<div className="flex justify-end gap-6 pr-12 text-base">
|
<div className="flex justify-end gap-3 border-t border-border pt-4 text-base">
|
||||||
<span className="text-muted-foreground">Document total (incl. VAT)</span>
|
<span className="text-muted-foreground">Document total (incl. VAT)</span>
|
||||||
<span className="font-semibold tabular-nums">
|
<span className="font-semibold tabular-nums">
|
||||||
{lines.reduce((sum, l) => sum + computeLine(l).lineTotal, 0).toFixed(2)}
|
{lines.reduce((sum, l) => sum + computeLine(l).lineTotal, 0).toFixed(2)}
|
||||||
|
|||||||
@@ -1,69 +1,3 @@
|
|||||||
import Link from "next/link"
|
|
||||||
import {
|
|
||||||
AlertOctagon,
|
|
||||||
AlertTriangle,
|
|
||||||
ArrowLeftRight,
|
|
||||||
BadgeDollarSign,
|
|
||||||
ClipboardList,
|
|
||||||
PackageSearch,
|
|
||||||
ScrollText,
|
|
||||||
SlidersHorizontal,
|
|
||||||
type LucideIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
|
||||||
|
|
||||||
const areas: { title: string; description: string; href: string; icon: LucideIcon }[] = [
|
|
||||||
{
|
|
||||||
title: "Stock Enquiry",
|
|
||||||
description: "On-hand, available, on-hold, and in-transit quantities by item and warehouse.",
|
|
||||||
href: "/dashboard/stock/enquiry",
|
|
||||||
icon: PackageSearch,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Stock Ledger",
|
|
||||||
description: "The immutable, append-only movement journal — every in/out with running balance.",
|
|
||||||
href: "/dashboard/stock/ledger",
|
|
||||||
icon: ScrollText,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Valuation",
|
|
||||||
description: "FIFO cost-layer breakdown and total stock value by item and warehouse.",
|
|
||||||
href: "/dashboard/stock/valuation",
|
|
||||||
icon: BadgeDollarSign,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Transfers",
|
|
||||||
description: "Move stock between warehouses: create, dispatch, and receive (in-transit).",
|
|
||||||
href: "/dashboard/stock/transfers",
|
|
||||||
icon: ArrowLeftRight,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Adjustments",
|
|
||||||
description: "Increase, decrease, or write off stock with a mandatory reason code.",
|
|
||||||
href: "/dashboard/stock/adjustments",
|
|
||||||
icon: SlidersHorizontal,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Counts",
|
|
||||||
description: "Cycle or full physical counts — snapshot, enter counts, post variance.",
|
|
||||||
href: "/dashboard/stock/counts",
|
|
||||||
icon: ClipboardList,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Reorder Alerts",
|
|
||||||
description: "Items at or below their reorder point, with a one-click requisition.",
|
|
||||||
href: "/dashboard/stock/reorder-alerts",
|
|
||||||
icon: AlertTriangle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Wastage",
|
|
||||||
description: "Damage, theft/loss, and expiry write-offs — reason-coded adjustments with a totals report.",
|
|
||||||
href: "/dashboard/stock/wastage",
|
|
||||||
icon: AlertOctagon,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function StockHubPage() {
|
export default function StockHubPage() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
@@ -73,26 +7,6 @@ export default function StockHubPage() {
|
|||||||
FIFO-costed stock across multiple warehouses (FR-STK-01..14).
|
FIFO-costed stock across multiple warehouses (FR-STK-01..14).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
|
||||||
{areas.map((area) => (
|
|
||||||
<Link key={area.href} href={area.href}>
|
|
||||||
<Card className="h-full transition-shadow hover:shadow-md">
|
|
||||||
<CardHeader>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<div className="flex size-11 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
|
|
||||||
<area.icon className="size-5" />
|
|
||||||
</div>
|
|
||||||
<CardTitle className="text-lg">{area.title}</CardTitle>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<p className="text-base text-muted-foreground">{area.description}</p>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { useEffect, useState } from "react"
|
|||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { usePathname } from "next/navigation"
|
import { usePathname } from "next/navigation"
|
||||||
import {
|
import {
|
||||||
|
AlertOctagon,
|
||||||
|
AlertTriangle,
|
||||||
|
ArrowLeftRight,
|
||||||
Banknote,
|
Banknote,
|
||||||
BadgeDollarSign,
|
BadgeDollarSign,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
@@ -29,6 +32,7 @@ import {
|
|||||||
Menu,
|
Menu,
|
||||||
Package,
|
Package,
|
||||||
PackageCheck,
|
PackageCheck,
|
||||||
|
PackageSearch,
|
||||||
PackageX,
|
PackageX,
|
||||||
PlayCircle,
|
PlayCircle,
|
||||||
PieChart,
|
PieChart,
|
||||||
@@ -36,6 +40,7 @@ import {
|
|||||||
ReceiptText,
|
ReceiptText,
|
||||||
Ruler,
|
Ruler,
|
||||||
Scale,
|
Scale,
|
||||||
|
ScrollText,
|
||||||
Settings,
|
Settings,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
ShoppingCart,
|
ShoppingCart,
|
||||||
@@ -116,7 +121,26 @@ const navItems: {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{ title: "Receiving", code: "receiving", href: "/dashboard/receiving/grn", icon: PackageCheck, 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: "Stock",
|
||||||
|
code: "stock",
|
||||||
|
href: "/dashboard/stock",
|
||||||
|
// Clicking "Stock" itself lands on Stock Ledger — the hub page underneath has nothing on
|
||||||
|
// it (its card grid was removed once the sidebar grew these sub-items), same as Procurement.
|
||||||
|
landingHref: "/dashboard/stock/ledger",
|
||||||
|
icon: Warehouse,
|
||||||
|
chevron: true,
|
||||||
|
children: [
|
||||||
|
{ title: "Stock Ledger", code: "stock.ledger", href: "/dashboard/stock/ledger", icon: ScrollText },
|
||||||
|
{ title: "Stock Enquiry", code: "stock.enquiry", href: "/dashboard/stock/enquiry", icon: PackageSearch },
|
||||||
|
{ title: "Valuation", code: "stock.valuation", href: "/dashboard/stock/valuation", icon: BadgeDollarSign },
|
||||||
|
{ title: "Transfers", code: "stock.transfers", href: "/dashboard/stock/transfers", icon: ArrowLeftRight },
|
||||||
|
{ title: "Adjustments", code: "stock.adjustments", href: "/dashboard/stock/adjustments", icon: SlidersHorizontal },
|
||||||
|
{ title: "Counts", code: "stock.counts", href: "/dashboard/stock/counts", icon: ClipboardList },
|
||||||
|
{ title: "Reorder Alerts", code: "stock.reorder-alerts", href: "/dashboard/stock/reorder-alerts", icon: AlertTriangle },
|
||||||
|
{ title: "Wastage", code: "stock.wastage", href: "/dashboard/stock/wastage", icon: AlertOctagon },
|
||||||
|
],
|
||||||
|
},
|
||||||
{ title: "Warehouses", code: "warehouses", href: "/dashboard/warehouse", icon: Building2, 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: "Orders", code: "orders", href: "/dashboard/orders", icon: ShoppingCart, chevron: true },
|
||||||
{
|
{
|
||||||
@@ -402,14 +426,15 @@ export function AppSidebar() {
|
|||||||
// flashing the full menu to a restricted role. Once resolved, a nav item
|
// 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.
|
// is visible if its own code is granted, or (for parents) if any child is.
|
||||||
//
|
//
|
||||||
// "procurement", "hrm", "sales" and "production" are exempted from that check (frontend-only): no
|
// "procurement", "hrm", "sales", "production" and "stock" are exempted from that check
|
||||||
// role is currently seeded with NAV:procurement/NAV:hrm/NAV:production or their children
|
// (frontend-only): no role is currently seeded with NAV:procurement/NAV:hrm/NAV:production
|
||||||
// server-side, which would hide the whole section for everyone. Remove each bypass once roles are granted
|
// or their children server-side (stock's children specifically have no SubNavItem rows at
|
||||||
// the permission properly (Settings → Roles → Sidebar permissions) or a backend seed
|
// all yet), which would hide the whole section for everyone. Remove each bypass once roles
|
||||||
// grants it. This is also flagged in 02-SECURITY.md as the AR-09 sidebar-visibility
|
// are granted the permission properly (Settings → Roles → Sidebar permissions) or a backend
|
||||||
|
// seed grants it. This is also flagged in 02-SECURITY.md as the AR-09 sidebar-visibility
|
||||||
// stopgap for HRM's salary/PII data — it hides HRM from the UI but doesn't enforce
|
// stopgap for HRM's salary/PII data — it hides HRM from the UI but doesn't enforce
|
||||||
// anything server-side.
|
// anything server-side.
|
||||||
const bypassCodes = new Set(["procurement", "sales", "hrm", "production"])
|
const bypassCodes = new Set(["procurement", "sales", "hrm", "production", "stock"])
|
||||||
const visibleItems = loading
|
const visibleItems = loading
|
||||||
? []
|
? []
|
||||||
: navItems
|
: navItems
|
||||||
|
|||||||
Reference in New Issue
Block a user