ui fixers in 10/16
This commit is contained in:
@@ -46,7 +46,6 @@ import {
|
||||
Receipt,
|
||||
ReceiptText,
|
||||
Scale,
|
||||
ScanLine,
|
||||
ScrollText,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
@@ -62,12 +61,8 @@ import {
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
import { approvalsApi } from "@/lib/api/approvals"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { useAuth } from "@/components/auth/AuthProvider"
|
||||
import { useTrackingSettings } from "@/hooks/use-tracking-settings"
|
||||
|
||||
const SERIALS_HREF = "/dashboard/stock/serials"
|
||||
|
||||
// `code` must match the seeded NavItem/SubNavItem codes in ERPCore
|
||||
// (Infra/Persistence/Configurations/NavItemConfiguration.cs / SubNavItemConfiguration.cs)
|
||||
@@ -109,8 +104,6 @@ const navItems: {
|
||||
{ title: "Price Adjustments", code: "products.price-adjustments", href: "/dashboard/products/price-adjustments", icon: Tag },
|
||||
],
|
||||
},
|
||||
// Open to everyone signed in: the inbox only lists what the user may approve and their own requests.
|
||||
{ title: "Approvals", code: "approvals", href: "/dashboard/approvals", icon: ClipboardCheck },
|
||||
{
|
||||
title: "Procurement",
|
||||
code: "procurement",
|
||||
@@ -159,9 +152,6 @@ const navItems: {
|
||||
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 },
|
||||
// Serial register and batch expiry are enquiries — they reuse stock.enquiry's permission code.
|
||||
{ title: "Serials", code: "stock.enquiry", href: "/dashboard/stock/serials", icon: ScanLine },
|
||||
{ title: "Batch Expiry", code: "stock.enquiry", href: "/dashboard/stock/batches", icon: CalendarClock },
|
||||
{ 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 },
|
||||
@@ -250,7 +240,6 @@ function SidebarContent({
|
||||
onClose,
|
||||
pathname,
|
||||
isMobile,
|
||||
approvalCount,
|
||||
}: {
|
||||
items: typeof navItems
|
||||
collapsed: boolean
|
||||
@@ -258,8 +247,6 @@ function SidebarContent({
|
||||
onClose?: () => void
|
||||
pathname: string
|
||||
isMobile: boolean
|
||||
/** Requests the user can approve — the badge on Approvals. */
|
||||
approvalCount: number
|
||||
}) {
|
||||
const iconOnly = !isMobile && collapsed
|
||||
|
||||
@@ -381,11 +368,6 @@ function SidebarContent({
|
||||
className={cn("size-3.5 shrink-0", isActive ? "text-primary" : "text-muted-foreground")}
|
||||
/>
|
||||
{!iconOnly && <span className="flex-1">{item.title}</span>}
|
||||
{item.code === "approvals" && approvalCount > 0 && (
|
||||
<span className="ml-auto rounded-full bg-primary px-1.5 text-[10px] font-semibold leading-4 text-primary-foreground tabular-nums">
|
||||
{approvalCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
@@ -453,8 +435,6 @@ export function AppSidebar() {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const [mobileOpen, setMobileOpen] = useState(false)
|
||||
const { navCodes, loading } = useAuth()
|
||||
// The Serials register is hidden while serial numbers are switched off in Product Configuration.
|
||||
const { serialNumbersEnabled } = useTrackingSettings()
|
||||
|
||||
// While /auth/me hasn't resolved yet, show nothing rather than briefly
|
||||
// flashing the full menu to a restricted role. Once resolved, a nav item
|
||||
@@ -468,16 +448,8 @@ export function AppSidebar() {
|
||||
// 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.
|
||||
// "approvals" is not a permission at all — the inbox is for everyone (see its nav entry).
|
||||
const bypassCodes = new Set(["procurement", "sales", "sales-returns", "hrm", "production", "stock", "approvals"])
|
||||
const bypassCodes = new Set(["procurement", "sales", "sales-returns", "hrm", "production", "stock"])
|
||||
|
||||
// Requests waiting for this user — the Approvals badge. Refreshed on navigation, which is when
|
||||
// someone has most likely just approved or submitted something.
|
||||
const [approvalCount, setApprovalCount] = useState(0)
|
||||
useEffect(() => {
|
||||
if (loading) return
|
||||
approvalsApi.pending().then((p) => setApprovalCount(p.actionableCount)).catch(() => setApprovalCount(0))
|
||||
}, [loading, pathname])
|
||||
const visibleItems = loading
|
||||
? []
|
||||
: navItems
|
||||
@@ -492,7 +464,7 @@ export function AppSidebar() {
|
||||
children: (bypassCodes.has(item.code)
|
||||
? item.children
|
||||
: item.children?.filter((c) => navCodes.includes(c.code) || c.altCodes?.some((ac) => navCodes.includes(ac)))
|
||||
)?.filter((c) => !(serialNumbersEnabled === false && c.href === SERIALS_HREF)),
|
||||
),
|
||||
}))
|
||||
|
||||
// Close mobile menu on route change
|
||||
@@ -517,7 +489,6 @@ export function AppSidebar() {
|
||||
onCollapse={() => setCollapsed((v) => !v)}
|
||||
pathname={pathname}
|
||||
isMobile={false}
|
||||
approvalCount={approvalCount}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -554,7 +525,6 @@ export function AppSidebar() {
|
||||
onClose={() => setMobileOpen(false)}
|
||||
pathname={pathname}
|
||||
isMobile={true}
|
||||
approvalCount={approvalCount}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -20,7 +20,7 @@ const SEGMENT_LABELS: Record<string, string> = {
|
||||
vendors: "Vendors",
|
||||
warehouse: "Warehouses",
|
||||
receiving: "Receiving",
|
||||
grn: "Goods Receipt Notes",
|
||||
grn: "Goods Received Notes",
|
||||
stock: "Stock",
|
||||
enquiry: "Enquiry",
|
||||
ledger: "Ledger",
|
||||
@@ -40,9 +40,11 @@ const SEGMENT_LABELS: Record<string, string> = {
|
||||
"purchase-invoices": "Purchase Invoices",
|
||||
"purchase-returns": "Purchase Returns",
|
||||
production: "Production",
|
||||
hrm: "HRM",
|
||||
templates: "Templates",
|
||||
runs: "Runs",
|
||||
invoices: "Sales",
|
||||
"day-end": "Approved Invoices",
|
||||
}
|
||||
|
||||
function labelFor(segment: string): string {
|
||||
@@ -51,6 +53,17 @@ function labelFor(segment: string): string {
|
||||
return segment.charAt(0).toUpperCase() + segment.slice(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Grouping paths with no page of their own (no page.tsx), mapped to where their menu lands — the
|
||||
* same `landingHref`s as the sidebar. Linking the raw path would 404. (HRM has its own hub page now.)
|
||||
*/
|
||||
const GROUP_LANDING: Record<string, string> = {
|
||||
"/dashboard/production": "/dashboard/production/runs",
|
||||
"/dashboard/receiving": "/dashboard/receiving/grn",
|
||||
"/dashboard/sales/bundles": "/dashboard/sales/bundles/templates",
|
||||
"/dashboard/help/tickets": "/dashboard/help",
|
||||
}
|
||||
|
||||
const linkClass =
|
||||
"truncate transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded-sm"
|
||||
|
||||
@@ -61,10 +74,10 @@ export function Breadcrumbs() {
|
||||
// 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),
|
||||
}))
|
||||
const crumbs = segments.map((segment, i) => {
|
||||
const path = "/" + segments.slice(0, i + 1).join("/")
|
||||
return { key: path, href: GROUP_LANDING[path] ?? path, label: labelFor(segment) }
|
||||
})
|
||||
|
||||
return (
|
||||
<Breadcrumb className="mb-4 print:hidden">
|
||||
@@ -72,7 +85,7 @@ export function Breadcrumbs() {
|
||||
{crumbs.map((crumb, i) => {
|
||||
const isLast = i === crumbs.length - 1
|
||||
return (
|
||||
<Fragment key={crumb.href}>
|
||||
<Fragment key={crumb.key}>
|
||||
<BreadcrumbItem>
|
||||
{isLast ? (
|
||||
<BreadcrumbPage>{crumb.label}</BreadcrumbPage>
|
||||
|
||||
@@ -108,10 +108,12 @@ function titleFromPath(pathname: string) {
|
||||
if (pathname === "/dashboard/sales/invoices/new") return "New Sale"
|
||||
if (pathname === "/dashboard/sales/warranties") return "Warranties"
|
||||
if (pathname === "/dashboard/sales/day-end-report") return "Day-End Report"
|
||||
if (pathname === "/dashboard/sales/day-end") return "Approved Invoices"
|
||||
if (pathname === "/dashboard/hrm") return "HRM"
|
||||
|
||||
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 (pathname === "/dashboard/receiving/grn") return "Goods Received Notes"
|
||||
if (pathname === "/dashboard/receiving/grn/new") return "Create Goods Received Note"
|
||||
if (/^\/dashboard\/receiving\/grn\/[^/]+$/.test(pathname)) return "Goods Received Note"
|
||||
|
||||
if (LEDGER_TITLES[pathname]) return LEDGER_TITLES[pathname]
|
||||
|
||||
@@ -336,7 +338,8 @@ export function Header() {
|
||||
<Building2 className="size-3.5" />
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{/* Open below the pill (not over it), left edges lined up; at least 14rem wide, or the pill's width if that is wider. */}
|
||||
<SelectContent alignItemWithTrigger={false} side="bottom" align="start" sideOffset={6} className="w-auto min-w-[max(var(--anchor-width),14rem)] p-1">
|
||||
<SelectItem value={ALL_BRANCHES}>All Branches</SelectItem>
|
||||
{branches.map((b) => (
|
||||
<SelectItem key={b.branchId} value={String(b.branchId)}>
|
||||
|
||||
Reference in New Issue
Block a user