implement fe with backend
This commit is contained in:
@@ -33,6 +33,7 @@ import {
|
||||
PlayCircle,
|
||||
PieChart,
|
||||
Receipt,
|
||||
ReceiptText,
|
||||
Ruler,
|
||||
Scale,
|
||||
Settings,
|
||||
@@ -99,6 +100,20 @@ const navItems: {
|
||||
{ title: "RFQs", code: "procurement.rfqs", href: "/dashboard/procurement/rfqs", icon: FileText },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Sales",
|
||||
code: "sales",
|
||||
href: "/dashboard/sales",
|
||||
landingHref: "/dashboard/sales/invoices",
|
||||
icon: ReceiptText,
|
||||
chevron: true,
|
||||
children: [
|
||||
{ title: "Invoices", code: "sales.invoices", href: "/dashboard/sales/invoices", icon: FileText },
|
||||
{ title: "Slips", code: "sales.slips", href: "/dashboard/sales/slips", icon: ShoppingCart },
|
||||
{ title: "Free Issues", code: "sales.free-issues", href: "/dashboard/sales/free-issues", icon: PackageX },
|
||||
{ title: "Reports", code: "sales.reports", href: "/dashboard/sales/reports", icon: FileBarChart },
|
||||
],
|
||||
},
|
||||
{ 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 },
|
||||
@@ -386,14 +401,14 @@ export function AppSidebar() {
|
||||
// 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.
|
||||
//
|
||||
// "procurement", "hrm" and "production" are exempted from that check (frontend-only): no
|
||||
// "procurement", "hrm", "sales" and "production" are exempted from that check (frontend-only): no
|
||||
// role is currently seeded with NAV:procurement/NAV:hrm/NAV:production or their children
|
||||
// server-side, which would hide the whole section for everyone. Remove each bypass once roles 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
|
||||
// anything server-side.
|
||||
const bypassCodes = new Set(["procurement", "hrm", "production"])
|
||||
const bypassCodes = new Set(["procurement", "sales", "hrm", "production"])
|
||||
const visibleItems = loading
|
||||
? []
|
||||
: navItems
|
||||
|
||||
@@ -111,6 +111,7 @@ function titleFromPath(pathname: string) {
|
||||
if (pathname === "/dashboard/products/brands") return "Brands"
|
||||
if (pathname === "/dashboard/products/item-types") return "Item Types"
|
||||
if (pathname === "/dashboard/products/settings") return "Product Configuration"
|
||||
if (pathname === "/dashboard/settings/company-profile") return "Company Profile"
|
||||
if (/^\/dashboard\/products\/[^/]+$/.test(pathname)) return "Item"
|
||||
|
||||
const segment = pathname.split("/").filter(Boolean).pop() ?? "dashboard"
|
||||
|
||||
@@ -29,9 +29,9 @@ const statusStyles: Record<Order["status"], string> = {
|
||||
}
|
||||
|
||||
// Use a fixed locale to avoid hydration mismatches between server and client
|
||||
const currency = new Intl.NumberFormat("en-US", {
|
||||
const currency = new Intl.NumberFormat("en-LK", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
currency: "LKR",
|
||||
})
|
||||
|
||||
const columns: DataTableColumn<Order>[] = [
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
"use client"
|
||||
|
||||
import { ThemeProvider } from "next-themes"
|
||||
|
||||
import { TooltipProvider } from "@/components/ui/tooltip"
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<ThemeProvider attribute="class" defaultTheme="light" themes={["light", "dark", "vibrant"]} disableTransitionOnChange>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { Lightbulb, PackageCheck } from "lucide-react"
|
||||
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { SalesFreeIssueSuggestion } from "@/types/sales"
|
||||
|
||||
export function FreeIssuePromotionSuggestions({ suggestion }: { suggestion: SalesFreeIssueSuggestion | null }) {
|
||||
if (!suggestion || suggestion.lines.length === 0) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-dashed p-6 text-sm text-muted-foreground">
|
||||
No free-issue promotion suggestions were generated for this slip yet.
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border p-4">
|
||||
<div className="mb-4 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="inline-flex items-center gap-2 rounded-full bg-amber-500/10 px-3 py-1 text-xs font-semibold uppercase tracking-wide text-amber-700">
|
||||
<Lightbulb className="size-3.5" />
|
||||
Backend suggestion
|
||||
</div>
|
||||
<h2 className="mt-2 text-lg font-semibold text-foreground">Free-issue promotions</h2>
|
||||
<p className="text-sm text-muted-foreground">The server suggests reward quantities and alternate products for this slip.</p>
|
||||
</div>
|
||||
<Link href="/dashboard/sales/free-issues/new" className={cn(buttonVariants({ variant: "outline", size: "sm" }))}>
|
||||
Create free issue
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 lg:grid-cols-2">
|
||||
{suggestion.lines.map((line) => (
|
||||
<div key={line.salesSlipLineId} className="rounded-xl border bg-muted/20 p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="font-medium text-foreground">{line.itemName}</div>
|
||||
<div className="text-sm text-muted-foreground">{line.itemSku} • Qty {line.qty}</div>
|
||||
</div>
|
||||
<div className="rounded-full bg-primary/10 px-3 py-1 text-xs font-semibold text-primary">
|
||||
Buy {line.triggerQty} get {line.suggestedFreeQty} free
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex flex-wrap gap-2 text-sm">
|
||||
{line.rewardOptions.map((option, index) => (
|
||||
<span key={option.itemId} className={cn("inline-flex items-center gap-1 rounded-full border px-3 py-1", index === 0 && "border-primary bg-primary/5 text-primary") }>
|
||||
{index === 0 ? <PackageCheck className="size-3.5" /> : null}
|
||||
{option.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 text-sm text-muted-foreground">
|
||||
Suggested free qty: <span className="font-medium text-foreground">{line.suggestedFreeQty.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { SalesSlipSummary } from "@/types/sales"
|
||||
|
||||
export function FreeIssueSuggestions({ rows }: { rows: SalesSlipSummary[] }) {
|
||||
if (rows.length === 0) return null
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border p-4">
|
||||
<div className="mb-3 flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-foreground">Free issue suggestions</h2>
|
||||
<p className="text-sm text-muted-foreground">Recent free-issue slips created from the free-issues page.</p>
|
||||
</div>
|
||||
<Link href="/dashboard/sales/free-issues" className={cn(buttonVariants({ variant: "outline", size: "sm" }))}>
|
||||
Open free issues
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-3">
|
||||
{rows.map((row) => (
|
||||
<Link
|
||||
key={row.salesSlipId}
|
||||
href={`/dashboard/sales/free-issues/${row.salesSlipId}`}
|
||||
className="rounded-xl border bg-muted/20 p-3 transition-colors hover:bg-muted/40"
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="font-medium text-foreground">{row.slipNo}</div>
|
||||
<div className="text-xs uppercase tracking-wide text-muted-foreground">{row.status}</div>
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-muted-foreground">{row.customerSnapshotName}</div>
|
||||
<div className="mt-2 text-sm text-foreground">Free qty total: {row.totals.freeQtyTotal.toFixed(2)}</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user