e7b1f270a5
- Implemented VendorOptionalFields component to handle optional vendor details (mobile, email, address) with validation. - Created useCustomerConfig hook to load optional customer field settings from Sales configuration. - Developed useProcurementConfig hook to manage Procurement configuration flags. - Introduced accessConfigApi for managing access configuration settings. - Added openingBalancesApi for handling opening balances operations. - Created procurementConfigApi for managing procurement configuration settings. - Implemented nav-routes for permission-based routing. - Developed PDF generation for purchase orders with dynamic vendor and item details. - Added purchase-order tax helpers for tax calculations and formatting. - Defined types for opening balances and related operations.
43 lines
2.0 KiB
TypeScript
43 lines
2.0 KiB
TypeScript
// Which permission opens which page: the route guard in components/auth/AccessGate.tsx uses this so a role
|
|
// can't open a screen by typing its address (the sidebar only hides the link). Derived from the sidebar's own
|
|
// list, so a page and its menu entry can never disagree; the API enforces the same codes on every request, so this
|
|
// is a courtesy that replaces a wall of "Forbidden" toasts with one clear message — never the protection.
|
|
import { navItems } from "@/components/Layouts/AppSidebar"
|
|
|
|
interface RouteRule {
|
|
prefix: string
|
|
/** Any one of these opens the page. */
|
|
codes: string[]
|
|
}
|
|
|
|
/** Pages with no menu entry of their own, but that belong to a permission anyway. */
|
|
const EXTRA_RULES: RouteRule[] = [
|
|
{ prefix: "/dashboard/settings/audit-logs", codes: ["settings.audit-logs"] },
|
|
]
|
|
|
|
function buildRules(): RouteRule[] {
|
|
const rules: RouteRule[] = [...EXTRA_RULES]
|
|
for (const item of navItems) {
|
|
// The landing page and Help are open to everyone with a role (they hold no data of their own).
|
|
if (item.code !== "dashboard" && item.code !== "help") rules.push({ prefix: item.href, codes: [item.code] })
|
|
for (const child of item.children ?? []) {
|
|
rules.push({ prefix: child.href, codes: [child.code, ...(child.altCodes ?? [])] })
|
|
}
|
|
}
|
|
// Longest prefix first, so "/dashboard/procurement/purchase-orders/approvals" beats ".../purchase-orders".
|
|
return rules.sort((a, b) => b.prefix.length - a.prefix.length)
|
|
}
|
|
|
|
let cached: RouteRule[] | null = null
|
|
|
|
/**
|
|
* The permission codes that open `pathname` (any one suffices), or null when the page is unguarded
|
|
* (the landing page, Help, and anything not in the menu: those are protected by the API alone).
|
|
*/
|
|
export function requiredCodesFor(pathname: string): string[] | null {
|
|
cached ??= buildRules()
|
|
const path = pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname
|
|
const rule = cached.find((r) => path === r.prefix || path.startsWith(r.prefix + "/"))
|
|
return rule ? rule.codes : null
|
|
}
|