@argusoft/medplat-product-management 1.0.7 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/{BrandsPage-CdqGFmGf.js → BrandsPage-DMw2vT0u.js} +77 -78
  2. package/dist/{BrandsPage-CdqGFmGf.js.map → BrandsPage-DMw2vT0u.js.map} +1 -1
  3. package/dist/CategoriesPage-D3ihx9Gi.js +398 -0
  4. package/dist/CategoriesPage-D3ihx9Gi.js.map +1 -0
  5. package/dist/{DashboardPage-BASkXr00.js → DashboardPage-asr2XDRu.js} +13 -13
  6. package/dist/{DashboardPage-BASkXr00.js.map → DashboardPage-asr2XDRu.js.map} +1 -1
  7. package/dist/{FacilityPicker-jV-qXp1k.js → FacilityPicker-DYl_PDyt.js} +6 -8
  8. package/dist/{FacilityPicker-jV-qXp1k.js.map → FacilityPicker-DYl_PDyt.js.map} +1 -1
  9. package/dist/{InventoryPage-CTdq6txa.js → InventoryPage-DOotGZrj.js} +16 -19
  10. package/dist/{InventoryPage-CTdq6txa.js.map → InventoryPage-DOotGZrj.js.map} +1 -1
  11. package/dist/{MemberProfileCard-DwWda-tS.js → MemberProfileCard-D5CgkTdJ.js} +3 -3
  12. package/dist/{MemberProfileCard-DwWda-tS.js.map → MemberProfileCard-D5CgkTdJ.js.map} +1 -1
  13. package/dist/{PaymentCreatePage-B3bP-K01.js → PaymentCreatePage-DfvSZ-kB.js} +114 -114
  14. package/dist/{PaymentCreatePage-B3bP-K01.js.map → PaymentCreatePage-DfvSZ-kB.js.map} +1 -1
  15. package/dist/{PaymentsPage-Dh8bqXVE.js → PaymentsPage-DMNjYWAJ.js} +7 -7
  16. package/dist/{PaymentsPage-Dh8bqXVE.js.map → PaymentsPage-DMNjYWAJ.js.map} +1 -1
  17. package/dist/{PickupBatchLinesEditor-B2fLx1lU.js → PickupBatchLinesEditor-CE2RuyJT.js} +2 -2
  18. package/dist/{PickupBatchLinesEditor-B2fLx1lU.js.map → PickupBatchLinesEditor-CE2RuyJT.js.map} +1 -1
  19. package/dist/{PickupCreatePage-BeYhR45k.js → PickupCreatePage-B4O_-vE5.js} +66 -66
  20. package/dist/PickupCreatePage-B4O_-vE5.js.map +1 -0
  21. package/dist/{PickupsPage-C6lBtAoQ.js → PickupsPage-D7zyioJB.js} +15 -17
  22. package/dist/{PickupsPage-C6lBtAoQ.js.map → PickupsPage-D7zyioJB.js.map} +1 -1
  23. package/dist/{PmGuard-DrIl1sHL.js → PmGuard-BZE-y4In.js} +3 -3
  24. package/dist/{PmGuard-DrIl1sHL.js.map → PmGuard-BZE-y4In.js.map} +1 -1
  25. package/dist/{PmTable-BSV_bMJZ.js → PmTable-CwKORZGg.js} +3 -4
  26. package/dist/{PmTable-BSV_bMJZ.js.map → PmTable-CwKORZGg.js.map} +1 -1
  27. package/dist/ProductEditPage-BVlVUCtD.js +1296 -0
  28. package/dist/ProductEditPage-BVlVUCtD.js.map +1 -0
  29. package/dist/ProductsPage-DvNHIeZt.js +253 -0
  30. package/dist/ProductsPage-DvNHIeZt.js.map +1 -0
  31. package/dist/{PurchaseCreatePage-D6os7srp.js → PurchaseCreatePage-Cq_i9snz.js} +213 -186
  32. package/dist/PurchaseCreatePage-Cq_i9snz.js.map +1 -0
  33. package/dist/{PurchaseRequestDetailPage-DErqGKiq.js → PurchaseRequestDetailPage-CA9KFOfE.js} +17 -18
  34. package/dist/{PurchaseRequestDetailPage-DErqGKiq.js.map → PurchaseRequestDetailPage-CA9KFOfE.js.map} +1 -1
  35. package/dist/{PurchaseRequestsPage-CHBoRzzH.js → PurchaseRequestsPage-DS9i4o0-.js} +12 -12
  36. package/dist/{PurchaseRequestsPage-CHBoRzzH.js.map → PurchaseRequestsPage-DS9i4o0-.js.map} +1 -1
  37. package/dist/{PurchasesPage-C-2K7wH6.js → PurchasesPage-DgL5T6BY.js} +4 -4
  38. package/dist/{PurchasesPage-C-2K7wH6.js.map → PurchasesPage-DgL5T6BY.js.map} +1 -1
  39. package/dist/{SaleCreatePage-DB3_UEgy.js → SaleCreatePage-CZnIOydv.js} +26 -26
  40. package/dist/{SaleCreatePage-DB3_UEgy.js.map → SaleCreatePage-CZnIOydv.js.map} +1 -1
  41. package/dist/{SalesPage-BhBGG426.js → SalesPage-DPIRrC_O.js} +82 -82
  42. package/dist/{SalesPage-BhBGG426.js.map → SalesPage-DPIRrC_O.js.map} +1 -1
  43. package/dist/SettingsPage-BC_R6Z-Q.js +674 -0
  44. package/dist/SettingsPage-BC_R6Z-Q.js.map +1 -0
  45. package/dist/{StorageUnitsPage-oe0lXLd1.js → StorageUnitsPage-BDRkdCjW.js} +209 -206
  46. package/dist/StorageUnitsPage-BDRkdCjW.js.map +1 -0
  47. package/dist/{TransactionsPage-7dRZpZOD.js → TransactionsPage-CqWTMNU8.js} +5 -5
  48. package/dist/{TransactionsPage-7dRZpZOD.js.map → TransactionsPage-CqWTMNU8.js.map} +1 -1
  49. package/dist/{TransferCreatePage-DtkAlP9l.js → TransferCreatePage-DF7FZaTJ.js} +14 -14
  50. package/dist/{TransferCreatePage-DtkAlP9l.js.map → TransferCreatePage-DF7FZaTJ.js.map} +1 -1
  51. package/dist/{TransfersPage-BaEjbvnj.js → TransfersPage-CGrRFaBF.js} +105 -105
  52. package/dist/{TransfersPage-BaEjbvnj.js.map → TransfersPage-CGrRFaBF.js.map} +1 -1
  53. package/dist/{VialsPage-vo9Of9oC.js → VialsPage-rvvXjgZG.js} +12 -12
  54. package/dist/{VialsPage-vo9Of9oC.js.map → VialsPage-rvvXjgZG.js.map} +1 -1
  55. package/dist/{constants-ClZJY2x1.js → constants-DpXnLsAB.js} +34 -27
  56. package/dist/constants-DpXnLsAB.js.map +1 -0
  57. package/dist/format-UOyOu75i.js.map +1 -1
  58. package/dist/index.js +328 -292
  59. package/dist/index.js.map +1 -1
  60. package/dist/listValueStore-BFQtDYIR.js +36 -0
  61. package/dist/listValueStore-BFQtDYIR.js.map +1 -0
  62. package/dist/{settingsStore-BlA2GIhl.js → settingsStore-BHHOmcCP.js} +69 -72
  63. package/dist/settingsStore-BHHOmcCP.js.map +1 -0
  64. package/dist/{validation-B6Kl5Xf9.js → validation-BzlHnL-r.js} +16 -15
  65. package/dist/{validation-B6Kl5Xf9.js.map → validation-BzlHnL-r.js.map} +1 -1
  66. package/package.json +1 -1
  67. package/dist/CategoriesPage-DJM0Nalw.js +0 -392
  68. package/dist/CategoriesPage-DJM0Nalw.js.map +0 -1
  69. package/dist/PickupCreatePage-BeYhR45k.js.map +0 -1
  70. package/dist/ProductEditPage-DZTUH95T.js +0 -1295
  71. package/dist/ProductEditPage-DZTUH95T.js.map +0 -1
  72. package/dist/ProductsPage-CqOsah4_.js +0 -252
  73. package/dist/ProductsPage-CqOsah4_.js.map +0 -1
  74. package/dist/PurchaseCreatePage-D6os7srp.js.map +0 -1
  75. package/dist/SettingsPage-CBUu9DZK.js +0 -746
  76. package/dist/SettingsPage-CBUu9DZK.js.map +0 -1
  77. package/dist/StorageUnitsPage-oe0lXLd1.js.map +0 -1
  78. package/dist/constants-ClZJY2x1.js.map +0 -1
  79. package/dist/settingsStore-BlA2GIhl.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PmGuard-DrIl1sHL.js","names":[],"sources":["../src/utils/workspaces.js","../src/components/PmPageShell.jsx","../src/utils/permissions.js","../src/components/PmNav.jsx","../src/components/PmGuard.jsx"],"sourcesContent":["// Information architecture: the module is split into three \"workspaces\" that\n// match real-world roles in a clinic / store.\n//\n// Catalog — master-data setup (configured occasionally by an admin)\n// Operations — daily floor work (stock, sales, vials, payments)\n// Ledger — read-only audit trail (every stock + money movement)\n//\n// Each workspace has its own accent hue so the operator knows which\n// \"section\" of the tool they're in without explicit labels everywhere.\n// Dashboard sits outside any workspace — it's the landing page.\n\nimport SpaceDashboardRoundedIcon from '@mui/icons-material/SpaceDashboardRounded';\nimport SettingsRoundedIcon from '@mui/icons-material/SettingsRounded';\nimport LayersRoundedIcon from '@mui/icons-material/LayersRounded';\nimport CategoryRoundedIcon from '@mui/icons-material/CategoryRounded';\nimport SellRoundedIcon from '@mui/icons-material/SellRounded';\nimport Inventory2RoundedIcon from '@mui/icons-material/Inventory2Rounded';\nimport WarehouseRoundedIcon from '@mui/icons-material/WarehouseRounded';\nimport LocalShippingRoundedIcon from '@mui/icons-material/LocalShippingRounded';\nimport WidgetsRoundedIcon from '@mui/icons-material/WidgetsRounded';\nimport ShoppingCartRoundedIcon from '@mui/icons-material/ShoppingCartRounded';\nimport FactCheckRoundedIcon from '@mui/icons-material/FactCheckRounded';\nimport MoveDownRoundedIcon from '@mui/icons-material/MoveDownRounded';\nimport SwapHorizRoundedIcon from '@mui/icons-material/SwapHorizRounded';\nimport PointOfSaleRoundedIcon from '@mui/icons-material/PointOfSaleRounded';\nimport VaccinesRoundedIcon from '@mui/icons-material/VaccinesRounded';\nimport PaymentsRoundedIcon from '@mui/icons-material/PaymentsRounded';\nimport ReceiptLongRoundedIcon from '@mui/icons-material/ReceiptLongRounded';\n\n// Permission ids match the backend's api_permission rows. Treated as plain\n// strings here — there's no parallel constants table; the featureJson at\n// runtime is the source of truth for what each user has been granted.\nexport const DASHBOARD = {\n id: 'dashboard',\n label: 'Dashboard',\n path: '/ui/medplat/manage/pm',\n icon: SpaceDashboardRoundedIcon,\n accent: '#6366f1', // indigo — the \"home\" accent\n accentDark: '#4338ca',\n permissionId: 'view.manage.productManagement',\n};\n\n// Settings sits outside the three workspaces — administrative, rarely used,\n// available as its own pill on the nav row alongside Dashboard.\nexport const SETTINGS = {\n id: 'settings',\n label: 'Settings',\n path: '/ui/medplat/manage/pm/settings',\n icon: SettingsRoundedIcon,\n accent: '#64748b', // slate — quiet, non-competing\n accentDark: '#334155',\n permissionId: 'view.manage.productManagement.settings',\n};\n\nexport const WORKSPACES = [\n {\n id: 'catalog',\n label: 'Catalog',\n description: 'Define what you sell and where you keep it. Master data — configured once, used everywhere.',\n icon: LayersRoundedIcon,\n accent: '#8b5cf6', // violet\n accentDark: '#6d28d9',\n accentSoft: '#ede9fe',\n items: [\n { id: 'categories', label: 'Categories', path: '/ui/medplat/manage/pm/categories', icon: CategoryRoundedIcon, permissionId: 'view.manage.productManagement.category' },\n { id: 'brands', label: 'Brands', path: '/ui/medplat/manage/pm/brands', icon: SellRoundedIcon, permissionId: 'view.manage.productManagement.brand' },\n { id: 'products', label: 'Products', path: '/ui/medplat/manage/pm/products', icon: Inventory2RoundedIcon, permissionId: 'view.manage.productManagement.product' },\n { id: 'storage', label: 'Storage Units', path: '/ui/medplat/manage/pm/storage-units', icon: WarehouseRoundedIcon, permissionId: 'view.manage.productManagement.storageUnit' },\n ],\n },\n {\n id: 'operations',\n label: 'Operations',\n description: 'Run the day — receive stock, sell to customers, administer vaccines, collect payments.',\n icon: LocalShippingRoundedIcon,\n accent: '#10b981', // emerald\n accentDark: '#047857',\n accentSoft: '#d1fae5',\n items: [\n // Purchase tabs are mutually exclusive based on the deployment-level\n // requirePurchaseConfirmation setting:\n // confirmation ON → \"Purchase Requests\" page with My Requests +\n // Pending tabs (per-tab perm-gated). An approver\n // with only the approve perm sees just Pending.\n // confirmation OFF → \"Purchases\" page with the direct-purchase list.\n // Both surface under either view.purchase or approve.purchase — the\n // approver-only role still gets the nav entry to reach their queue.\n { id: 'purchase-requests', label: 'Purchase Requests', path: '/ui/medplat/manage/pm/purchase-requests', icon: FactCheckRoundedIcon,\n anyOfPermissionIds: ['view.manage.productManagement.purchase', 'approve.manage.productManagement.purchase'],\n purchaseApprovalGroup: true },\n { id: 'purchases', label: 'Purchases', path: '/ui/medplat/manage/pm/purchases', icon: ShoppingCartRoundedIcon,\n permissionId: 'view.manage.productManagement.purchase', hiddenWhenConfirmationOn: true },\n { id: 'inventory', label: 'Inventory', path: '/ui/medplat/manage/pm/inventory', icon: WidgetsRoundedIcon, permissionId: 'view.manage.productManagement.inventory' },\n // Pickups — user checks out facility stock onto personal inventory and\n // hands it back later. Wastage gets recorded at return if there's a gap.\n { id: 'pickups', label: 'Pickups', path: '/ui/medplat/manage/pm/pickups', icon: MoveDownRoundedIcon, permissionId: 'view.manage.productManagement.pickup' },\n // Transfers — two-step facility ↔ facility movement (sender ships,\n // receiver confirms). Permission-gated so deployments that don't use\n // transfers can leave the menu out by withholding the perm.\n { id: 'transfers', label: 'Transfers', path: '/ui/medplat/manage/pm/transfers', icon: SwapHorizRoundedIcon, permissionId: 'view.manage.productManagement.transfer' },\n // Sales + Payments items also AND with settings.manageSales; PmNav and\n // DashboardPage hide them when the deployment isn't running sales.\n { id: 'sales', label: 'Sales', path: '/ui/medplat/manage/pm/sales', icon: PointOfSaleRoundedIcon, permissionId: 'view.manage.productManagement.sale', salesGroup: true },\n { id: 'vials', label: 'Vial', path: '/ui/medplat/manage/pm/vials', icon: VaccinesRoundedIcon, permissionId: 'view.manage.productManagement.vial' },\n { id: 'payments', label: 'Payments', path: '/ui/medplat/manage/pm/payments', icon: PaymentsRoundedIcon, permissionId: 'view.manage.productManagement.payment', salesGroup: true },\n ],\n },\n {\n id: 'ledger',\n label: 'Ledger',\n description: 'Every stock and money movement, append-only, with full lot / volume / monetary detail.',\n icon: ReceiptLongRoundedIcon,\n accent: '#f59e0b', // amber\n accentDark: '#b45309',\n accentSoft: '#fef3c7',\n items: [\n { id: 'transactions', label: 'Transactions', path: '/ui/medplat/manage/pm/transactions', icon: ReceiptLongRoundedIcon, permissionId: 'view.manage.productManagement.ledger' },\n ],\n },\n];\n\n/**\n * Filter the workspace tree against (a) the current operator's PM permission\n * set and (b) the manageSales settings toggle. An item is visible iff its\n * permissionId is granted AND (manageSales is on OR it isn't in the sales\n * group). A workspace tab survives iff at least one of its items survives.\n *\n * Both PmNav and DashboardPage consume this so the two stay in lock-step.\n */\nexport function getVisibleWorkspaces(perms, settings) {\n const manageSales = settings?.manageSales !== false;\n const requirePurchaseConfirmation = settings?.requirePurchaseConfirmation === true;\n return WORKSPACES\n .map((ws) => ({\n ...ws,\n items: ws.items.filter((it) => {\n // Items can declare either a single `permissionId` or\n // `anyOfPermissionIds` for the \"user needs any of these perms\" case\n // (used by the Purchase Requests entry — visible to both viewers\n // and approvers).\n const hasPerm = it.anyOfPermissionIds\n ? !!perms?.hasAny?.(it.anyOfPermissionIds)\n : !!perms?.has?.(it.permissionId);\n return hasPerm\n && (manageSales || !it.salesGroup)\n && (requirePurchaseConfirmation || !it.purchaseApprovalGroup)\n && (!requirePurchaseConfirmation || !it.hiddenWhenConfirmationOn);\n }),\n }))\n .filter((ws) => ws.items.length > 0);\n}\n\n// Locate the workspace that owns a given pathname. The Dashboard route is\n// intentionally not in any workspace; we surface it via a dedicated pill.\nexport function findWorkspaceForPath(pathname) {\n if (!pathname) return null;\n for (const ws of WORKSPACES) {\n if (ws.items.some((it) => pathname.startsWith(it.path))) return ws;\n }\n return null;\n}\n\nexport function findItemForPath(pathname) {\n if (!pathname) return null;\n for (const ws of WORKSPACES) {\n const hit = ws.items.find((it) => pathname.startsWith(it.path));\n if (hit) return { workspace: ws, item: hit };\n }\n return null;\n}\n","import PropTypes from 'prop-types';\nimport { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport { Box, Chip, Fade, Stack, Typography } from '@mui/material';\nimport { DASHBOARD, SETTINGS, findItemForPath } from '@pm/utils/workspaces';\nimport { loadPmSettings } from '@pm/utils/settingsStore';\n\n// Page shell shared by every PM screen. Reads the active workspace from the\n// route and tints the header to match — peripheral cue for \"what section of\n// the tool am I in\". Body content fades in to soften route changes.\nexport default function PmPageShell({ title, subtitle, chips = [], actions, children }) {\n const theme = useTheme();\n const { pathname } = useLocation();\n\n // Kick off the settings preload from the shared shell, so every PM page has\n // the currency cached by the time PmMoney renders. usePmSettings inside the\n // money cell will also trigger this; calling here just shaves one render.\n useEffect(() => { void loadPmSettings().catch(() => {}); }, []);\n\n const onDashboard = pathname.startsWith(DASHBOARD.path);\n const onSettings = pathname.startsWith(SETTINGS.path);\n const hit = findItemForPath(pathname);\n const workspace = hit?.workspace;\n\n const accent = onDashboard\n ? DASHBOARD.accent\n : onSettings\n ? SETTINGS.accent\n : (workspace?.accent ?? theme.palette.primary.main);\n const accentDark = onDashboard\n ? DASHBOARD.accentDark\n : onSettings\n ? SETTINGS.accentDark\n : (workspace?.accentDark ?? theme.palette.primary.dark);\n\n return (\n <Box sx={{ position: 'relative', px: { xs: 1, md: 2 }, py: { xs: 1, md: 2 } }}>\n {/* Soft accent glow tied to the active workspace. */}\n <Box sx={{ position: 'absolute', inset: 0, pointerEvents: 'none', overflow: 'hidden' }}>\n <Box\n sx={{\n position: 'absolute',\n top: -60,\n right: -40,\n width: 260,\n height: 260,\n borderRadius: '50%',\n background: `radial-gradient(circle, ${alpha(accent, 0.18)} 0%, transparent 70%)`,\n filter: 'blur(2px)',\n }}\n />\n <Box\n sx={{\n position: 'absolute',\n top: 40,\n left: -50,\n width: 200,\n height: 200,\n borderRadius: '50%',\n background: `radial-gradient(circle, ${alpha(theme.palette.info.main, 0.08)} 0%, transparent 72%)`,\n filter: 'blur(2px)',\n }}\n />\n </Box>\n\n {/* Header card — workspace badge + title + actions. */}\n <Box\n sx={{\n position: 'relative',\n zIndex: 1,\n mb: 2,\n p: { xs: 2, md: 2.5 },\n borderRadius: 1.5,\n border: `1px solid ${alpha(accent, 0.18)}`,\n background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.98)} 0%, ${alpha(accent, 0.07)} 100%)`,\n boxShadow: `0 18px 44px ${alpha(theme.palette.common.black, 0.06)}`,\n overflow: 'hidden',\n // Thin colored bar at the left edge — instantly tells you which workspace.\n '&::before': {\n content: '\"\"',\n position: 'absolute',\n left: 0,\n top: 0,\n bottom: 0,\n width: 4,\n background: `linear-gradient(180deg, ${accent} 0%, ${accentDark} 100%)`,\n },\n }}\n >\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n justifyContent=\"space-between\"\n alignItems={{ xs: 'flex-start', md: 'center' }}\n spacing={1.5}\n >\n <Box sx={{ minWidth: 0 }}>\n {workspace || onDashboard || onSettings ? (\n <Stack direction=\"row\" alignItems=\"center\" spacing={0.75} sx={{ mb: 0.75 }}>\n {(() => {\n const Icon = onDashboard\n ? DASHBOARD.icon\n : onSettings\n ? SETTINGS.icon\n : workspace.icon;\n return Icon ? (\n <Icon sx={{ fontSize: 16, color: accentDark }} />\n ) : null;\n })()}\n <Typography\n variant=\"overline\"\n sx={{\n color: accentDark,\n fontWeight: 800,\n letterSpacing: 1.1,\n lineHeight: 1,\n }}\n >\n {onDashboard ? 'Overview' : onSettings ? 'Configuration' : workspace.label}\n </Typography>\n {hit?.item ? (\n <>\n <Box sx={{ width: 4, height: 4, borderRadius: '50%', bgcolor: alpha(accent, 0.4), mx: 0.5 }} />\n <Typography variant=\"overline\" sx={{ color: theme.palette.text.secondary, fontWeight: 700, letterSpacing: 0.8 }}>\n {hit.item.label}\n </Typography>\n </>\n ) : null}\n </Stack>\n ) : null}\n\n <Typography variant=\"h5\" sx={{ fontWeight: 800, color: theme.palette.text.primary, lineHeight: 1.2 }}>\n {title}\n </Typography>\n {subtitle ? (\n <Typography variant=\"body2\" sx={{ mt: 0.75, maxWidth: 900, color: theme.palette.text.secondary }}>\n {subtitle}\n </Typography>\n ) : null}\n {chips?.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap flexWrap=\"wrap\" sx={{ mt: 1.5 }}>\n {chips.map((chip) => (\n <Chip\n key={chip}\n label={chip}\n size=\"small\"\n sx={{\n borderRadius: 2,\n backgroundColor: alpha(accent, 0.1),\n color: accentDark,\n fontWeight: 700,\n border: `1px solid ${alpha(accent, 0.2)}`,\n }}\n />\n ))}\n </Stack>\n ) : null}\n </Box>\n {actions ? <Box>{actions}</Box> : null}\n </Stack>\n </Box>\n\n {/* Body — fade in to soften route transitions. Keyed on pathname so each\n navigation triggers a fresh animation rather than reusing prior fade. */}\n <Fade in key={pathname} timeout={360}>\n <Box\n sx={{\n position: 'relative',\n zIndex: 1,\n // Form field labels and helper/hint text default to a small, light\n // grey that's hard to read — bump the size and darken the colour\n // consistently across every PM form.\n '& .MuiInputLabel-root': {\n fontSize: '0.95rem',\n color: theme.palette.text.primary,\n },\n '& .MuiInputLabel-root.Mui-focused': {\n color: theme.palette.primary.main,\n },\n '& .MuiFormHelperText-root': {\n fontSize: '0.78rem',\n color: alpha(theme.palette.text.primary, 0.7),\n },\n }}\n >\n {children}\n </Box>\n </Fade>\n </Box>\n );\n}\n\nPmPageShell.propTypes = {\n title: PropTypes.node,\n subtitle: PropTypes.node,\n chips: PropTypes.arrayOf(PropTypes.string),\n actions: PropTypes.node,\n children: PropTypes.node,\n};\n","import { useEffect, useMemo } from 'react';\nimport { useSelector } from 'react-redux';\n\n// PM module's navigation_state in menu_config. The backend attaches a\n// featureJson onto this node whose keys are the permission_ids the current\n// user has been granted under the module — exactly the same featureJson\n// MedplatForm reads off any other menu node. Nothing about which\n// permissions exist is baked into the frontend; the source of truth is\n// whatever the backend serialised onto this node.\nconst PM_MENU_KEY = 'techo.manage.pm';\n\n/**\n * Hook returning the PM permission set for the current user.\n *\n * Reads `linearMenuItems['techo.manage.pm'].featureJson` from the\n * Authenticate slice — Protected.jsx populates that at login via\n * /api/login/menuV2. The featureJson is a JSON-string-or-object whose keys\n * are permission_ids and whose values are true/false.\n *\n * Returns:\n * has(permId) — true iff the user has been granted that permission_id\n * hasAny(ids[]) — true iff any id in the list is granted\n *\n * **Default-deny**: when the PM menu node is missing or its featureJson is\n * empty, both helpers return false. There's no dev escape hatch — devs go\n * through the same grant flow as everyone else.\n *\n * **What's actually in featureJson**: only the top-level api_permission\n * rows for menu_id 543 (those with `parent_permission_id = NULL`). The\n * `.child.get.X` / `.child.post.X` rows govern API-URL access via the\n * backend's GlobalMenuPermissionFilter but they do NOT surface to the\n * frontend. So every UI check in PM uses the parent perm\n * (`view.manage.productManagement.<page>`). The backend still 403s any\n * write the user lacks the `.child.post.X` row for — that's the actual\n * write gate. To gate buttons in the UI separately from page-visibility\n * later, mint flat write perms (e.g. `write.manage.productManagement.X`\n * with `parent_permission_id = NULL`).\n */\n/**\n * `useEffect` that only runs when the current user has the permission.\n *\n * <p>PM pages historically declared their load `useEffect` in the outer\n * component above {@code <PmGuard>}. That meant the fetch fired on mount\n * regardless of the user's perm — the guard only hid the UI after the\n * network call already went out. Wrapping the effect with this hook makes\n * the fetch wait for (or skip entirely without) the perm, matching what\n * `PmGuard` does for the rendered tree.\n *\n * @param {string|string[]} perm Permission id required to fire the effect. Array means any-of.\n * @param {() => (void|(()=>void))} fn The effect body — receives no args, may return a cleanup.\n * @param {ReadonlyArray<*>} [deps] Extra deps; the perm gate is added implicitly.\n */\nexport function usePermissionedEffect(perm, fn, deps) {\n const perms = usePmPermissions();\n const allowed = Array.isArray(perm)\n ? perms.hasAny(perm)\n : (!!perm && perms.has(perm));\n // We list `allowed` first so a perm flip re-fires the effect (e.g. a user\n // who gains the perm mid-session lands on the page fully loaded). Caller\n // deps come after — same semantics they'd get from a plain useEffect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => {\n if (!allowed) return undefined;\n return fn();\n }, [allowed, ...(deps ?? [])]);\n}\n\nexport function usePmPermissions() {\n const linearMenuItems = useSelector((state) => state.Authenticate?.linearMenuItems);\n return useMemo(() => {\n const node = linearMenuItems?.[PM_MENU_KEY];\n let grants = node?.featureJson;\n if (typeof grants === 'string') {\n try { grants = JSON.parse(grants); } catch { grants = {}; }\n }\n const grantSet = grants && typeof grants === 'object' ? grants : {};\n const has = (permId) => !!permId && grantSet[permId] === true;\n const hasAny = (perms) => Array.isArray(perms) && perms.some(has);\n return { has, hasAny };\n }, [linearMenuItems]);\n}\n","import { useMemo } from 'react';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport { Box, Stack, Tab, Tabs, Typography } from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport { DASHBOARD, SETTINGS, findWorkspaceForPath, getVisibleWorkspaces } from '@pm/utils/workspaces';\nimport { usePmSettings } from '@pm/utils/settingsStore';\nimport { usePmPermissions } from '@pm/utils/permissions';\n\n// Two-row navigation:\n// Row 1 — workspace pills: Dashboard · Catalog · Operations · Ledger\n// Row 2 — when inside a workspace, the items in that workspace as tabs,\n// accented with the workspace's hue.\n//\n// The active workspace's accent flows into both rows so the user always\n// has a peripheral cue for \"where am I in the tool\".\n//\n// Visibility is filtered by getVisibleWorkspaces() — items that the user\n// lacks permission for OR that the deployment has turned off (e.g. Sales +\n// Payments when manageSales=false) are dropped, and a workspace tab whose\n// items all got dropped disappears too.\nexport default function PmNav() {\n const navigate = useNavigate();\n const location = useLocation();\n const theme = useTheme();\n const settings = usePmSettings();\n const perms = usePmPermissions();\n\n const visibleWorkspaces = useMemo(\n () => getVisibleWorkspaces(perms, settings),\n [perms, settings],\n );\n\n const showDashboardPill = perms.has(DASHBOARD.permissionId);\n const showSettingsPill = perms.has(SETTINGS.permissionId);\n\n const onDashboard = location.pathname.startsWith(DASHBOARD.path);\n const onSettings = location.pathname.startsWith(SETTINGS.path);\n const activeWorkspace = findWorkspaceForPath(location.pathname);\n // visibleWorkspaces is already filtered; if the user landed on a workspace\n // whose items they no longer have access to (e.g. deep link), it won't be\n // here and the row-2 tab strip stays collapsed.\n const visibleActiveWorkspace = activeWorkspace\n ? visibleWorkspaces.find((ws) => ws.id === activeWorkspace.id) ?? null\n : null;\n const accent = onDashboard\n ? DASHBOARD.accent\n : onSettings\n ? SETTINGS.accent\n : (activeWorkspace?.accent ?? theme.palette.primary.main);\n\n const activeItemIndex = visibleActiveWorkspace\n ? visibleActiveWorkspace.items.findIndex((it) => location.pathname.startsWith(it.path))\n : -1;\n\n function pillSx(active, hue) {\n return {\n px: 2,\n py: 0.9,\n borderRadius: 999,\n cursor: 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n gap: 1,\n fontWeight: 700,\n fontSize: '0.875rem',\n letterSpacing: 0.2,\n transition: 'all 220ms cubic-bezier(0.4, 0, 0.2, 1)',\n color: active ? '#fff' : theme.palette.text.secondary,\n backgroundColor: active ? hue : 'transparent',\n boxShadow: active ? `0 8px 22px ${alpha(hue, 0.32)}` : 'none',\n '&:hover': {\n color: active ? '#fff' : hue,\n backgroundColor: active ? hue : alpha(hue, 0.08),\n transform: active ? 'none' : 'translateY(-1px)',\n },\n };\n }\n\n function renderPill({ id, label, icon: Icon, active, onClick, hue }) {\n return (\n <Box key={id} role=\"button\" onClick={onClick} sx={pillSx(active, hue)}>\n {Icon ? <Icon sx={{ fontSize: 18 }} /> : null}\n {label}\n </Box>\n );\n }\n\n return (\n <Box sx={{ mb: 2 }}>\n {/* Row 1 — workspace pills */}\n <Box\n sx={{\n position: 'relative',\n borderRadius: 1.5,\n p: 1,\n background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.96)} 0%, ${alpha(accent, 0.06)} 100%)`,\n border: `1px solid ${alpha(theme.palette.divider, 0.7)}`,\n boxShadow: `0 10px 28px ${alpha(theme.palette.common.black, 0.04)}`,\n }}\n >\n <Stack direction=\"row\" alignItems=\"center\" spacing={0.75} flexWrap=\"wrap\" useFlexGap>\n {showDashboardPill ? renderPill({\n id: DASHBOARD.id,\n label: DASHBOARD.label,\n icon: DASHBOARD.icon,\n active: onDashboard,\n hue: DASHBOARD.accent,\n onClick: () => navigate(DASHBOARD.path),\n }) : null}\n\n {showDashboardPill && visibleWorkspaces.length > 0 ? (\n <Box sx={{ width: 1, height: 22, mx: 0.5, bgcolor: 'divider', opacity: 0.7 }} />\n ) : null}\n\n {visibleWorkspaces.map((ws) => renderPill({\n id: ws.id,\n label: ws.label,\n icon: ws.icon,\n active: activeWorkspace?.id === ws.id,\n hue: ws.accent,\n // Tapping a workspace jumps to its first item — a deterministic\n // landing, no submenu to fight with.\n onClick: () => ws.items[0] && navigate(ws.items[0].path),\n }))}\n\n {/* Settings sits to the right, separated from the workspaces — it's\n configuration, not a workspace, and shouldn't compete visually. */}\n <Box sx={{ flexGrow: 1 }} />\n {showSettingsPill ? renderPill({\n id: SETTINGS.id,\n label: SETTINGS.label,\n icon: SETTINGS.icon,\n active: onSettings,\n hue: SETTINGS.accent,\n onClick: () => navigate(SETTINGS.path),\n }) : null}\n </Stack>\n </Box>\n\n {/* Row 2 — items within the active workspace */}\n {visibleActiveWorkspace && visibleActiveWorkspace.items.length > 0 ? (\n <Box\n sx={{\n mt: 1,\n borderRadius: 1.5,\n px: 1,\n bgcolor: alpha(visibleActiveWorkspace.accent, 0.04),\n border: `1px solid ${alpha(visibleActiveWorkspace.accent, 0.12)}`,\n }}\n >\n <Tabs\n value={activeItemIndex === -1 ? false : activeItemIndex}\n onChange={(_, idx) => navigate(visibleActiveWorkspace.items[idx].path)}\n variant=\"scrollable\"\n scrollButtons=\"auto\"\n slotProps={{\n indicator: { sx: { backgroundColor: visibleActiveWorkspace.accent, height: 3, borderRadius: 2 } },\n }}\n sx={{\n minHeight: 44,\n '& .MuiTab-root': {\n minHeight: 44,\n fontWeight: 700,\n textTransform: 'none',\n fontSize: '0.85rem',\n color: theme.palette.text.secondary,\n transition: 'color 180ms ease',\n '&:hover': { color: visibleActiveWorkspace.accentDark },\n '&.Mui-selected': { color: visibleActiveWorkspace.accentDark },\n },\n }}\n >\n {visibleActiveWorkspace.items.map((it) => {\n const Icon = it.icon;\n return (\n <Tab\n key={it.id}\n iconPosition=\"start\"\n icon={Icon ? <Icon sx={{ fontSize: 18 }} /> : undefined}\n label={<Typography component=\"span\" sx={{ fontWeight: 700 }}>{it.label}</Typography>}\n />\n );\n })}\n </Tabs>\n </Box>\n ) : null}\n </Box>\n );\n}\n","import { useNavigate } from 'react-router-dom';\nimport { useSelector } from 'react-redux';\nimport { Alert, Box, Button, LinearProgress, Stack, Typography } from '@mui/material';\nimport LockOutlinedIcon from '@mui/icons-material/LockOutlined';\nimport { usePmPermissions } from '@pm/utils/permissions';\n\n/**\n * Page-level guard. Wrap a PM page in `<PmGuard perm=\"...\">…</PmGuard>` and\n * the children only render when the current user has the perm; otherwise\n * the page shows a friendly \"no access\" panel with a back-to-dashboard\n * button.\n *\n * Default-deny: when the featureJson is missing the guard blocks. While the\n * menu tree hasn't loaded yet (first paint right after login) we show a\n * thin loader so the \"no access\" alert doesn't flash.\n */\nexport default function PmGuard({ perm, anyOf, children, title = 'You don’t have access to this page' }) {\n const perms = usePmPermissions();\n const navigate = useNavigate();\n const linearMenuItems = useSelector((state) => state.Authenticate?.linearMenuItems);\n const menuReady = linearMenuItems !== null && linearMenuItems !== undefined;\n if (!menuReady) {\n return (\n <Box sx={{ p: 3 }}>\n <LinearProgress />\n </Box>\n );\n }\n const allowed = anyOf ? perms.hasAny(anyOf) : perms.has(perm);\n if (allowed) return children;\n return (\n <Box sx={{ p: 3 }}>\n <Alert\n severity=\"warning\"\n icon={<LockOutlinedIcon />}\n sx={{ borderRadius: 1.5, alignItems: 'flex-start' }}\n >\n <Stack spacing={1.25}>\n <Typography sx={{ fontWeight: 800 }}>{title}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Your account doesn’t have the required permission to view this\n section of Product Management. Ask an admin to grant it via the\n role / user permission assignment.\n </Typography>\n <Box>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => navigate('/ui/medplat/manage/pm')}\n >\n Back to Dashboard\n </Button>\n </Box>\n </Stack>\n </Alert>\n </Box>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PmGuard-BZE-y4In.js","names":[],"sources":["../src/utils/workspaces.js","../src/components/PmPageShell.jsx","../src/utils/permissions.js","../src/components/PmNav.jsx","../src/components/PmGuard.jsx"],"sourcesContent":["// Information architecture: the module is split into three \"workspaces\" that\n// match real-world roles in a clinic / store.\n//\n// Catalog — master-data setup (configured occasionally by an admin)\n// Operations — daily floor work (stock, sales, vials, payments)\n// Ledger — read-only audit trail (every stock + money movement)\n//\n// Each workspace has its own accent hue so the operator knows which\n// \"section\" of the tool they're in without explicit labels everywhere.\n// Dashboard sits outside any workspace — it's the landing page.\n\nimport SpaceDashboardRoundedIcon from '@mui/icons-material/SpaceDashboardRounded';\nimport SettingsRoundedIcon from '@mui/icons-material/SettingsRounded';\nimport LayersRoundedIcon from '@mui/icons-material/LayersRounded';\nimport CategoryRoundedIcon from '@mui/icons-material/CategoryRounded';\nimport SellRoundedIcon from '@mui/icons-material/SellRounded';\nimport Inventory2RoundedIcon from '@mui/icons-material/Inventory2Rounded';\nimport WarehouseRoundedIcon from '@mui/icons-material/WarehouseRounded';\nimport LocalShippingRoundedIcon from '@mui/icons-material/LocalShippingRounded';\nimport WidgetsRoundedIcon from '@mui/icons-material/WidgetsRounded';\nimport ShoppingCartRoundedIcon from '@mui/icons-material/ShoppingCartRounded';\nimport FactCheckRoundedIcon from '@mui/icons-material/FactCheckRounded';\nimport MoveDownRoundedIcon from '@mui/icons-material/MoveDownRounded';\nimport SwapHorizRoundedIcon from '@mui/icons-material/SwapHorizRounded';\nimport PointOfSaleRoundedIcon from '@mui/icons-material/PointOfSaleRounded';\nimport VaccinesRoundedIcon from '@mui/icons-material/VaccinesRounded';\nimport PaymentsRoundedIcon from '@mui/icons-material/PaymentsRounded';\nimport ReceiptLongRoundedIcon from '@mui/icons-material/ReceiptLongRounded';\n\n// Permission ids match the backend's api_permission rows. Treated as plain\n// strings here — there's no parallel constants table; the featureJson at\n// runtime is the source of truth for what each user has been granted.\nexport const DASHBOARD = {\n id: 'dashboard',\n label: 'Dashboard',\n path: '/ui/medplat/manage/pm',\n icon: SpaceDashboardRoundedIcon,\n accent: '#6366f1', // indigo — the \"home\" accent\n accentDark: '#4338ca',\n permissionId: 'view.manage.productManagement',\n};\n\n// Settings sits outside the three workspaces — administrative, rarely used,\n// available as its own pill on the nav row alongside Dashboard.\nexport const SETTINGS = {\n id: 'settings',\n label: 'Settings',\n path: '/ui/medplat/manage/pm/settings',\n icon: SettingsRoundedIcon,\n accent: '#64748b', // slate — quiet, non-competing\n accentDark: '#334155',\n permissionId: 'view.manage.productManagement.settings',\n};\n\nexport const WORKSPACES = [\n {\n id: 'catalog',\n label: 'Catalog',\n description: 'Define what you sell and where you keep it. Master data — configured once, used everywhere.',\n icon: LayersRoundedIcon,\n accent: '#8b5cf6', // violet\n accentDark: '#6d28d9',\n accentSoft: '#ede9fe',\n items: [\n { id: 'categories', label: 'Categories', path: '/ui/medplat/manage/pm/categories', icon: CategoryRoundedIcon, permissionId: 'view.manage.productManagement.category' },\n { id: 'brands', label: 'Brands', path: '/ui/medplat/manage/pm/brands', icon: SellRoundedIcon, permissionId: 'view.manage.productManagement.brand' },\n { id: 'products', label: 'Products', path: '/ui/medplat/manage/pm/products', icon: Inventory2RoundedIcon, permissionId: 'view.manage.productManagement.product' },\n { id: 'storage', label: 'Storage Units', path: '/ui/medplat/manage/pm/storage-units', icon: WarehouseRoundedIcon, permissionId: 'view.manage.productManagement.storageUnit' },\n ],\n },\n {\n id: 'operations',\n label: 'Operations',\n description: 'Run the day — receive stock, sell to customers, administer vaccines, collect payments.',\n icon: LocalShippingRoundedIcon,\n accent: '#10b981', // emerald\n accentDark: '#047857',\n accentSoft: '#d1fae5',\n items: [\n // Purchase tabs are mutually exclusive based on the deployment-level\n // requirePurchaseConfirmation setting:\n // confirmation ON → \"Purchase Requests\" page with My Requests +\n // Pending tabs (per-tab perm-gated). An approver\n // with only the approve perm sees just Pending.\n // confirmation OFF → \"Purchases\" page with the direct-purchase list.\n // Both surface under either view.purchase or approve.purchase — the\n // approver-only role still gets the nav entry to reach their queue.\n { id: 'purchase-requests', label: 'Purchase Requests', path: '/ui/medplat/manage/pm/purchase-requests', icon: FactCheckRoundedIcon,\n anyOfPermissionIds: ['view.manage.productManagement.purchase', 'approve.manage.productManagement.purchase'],\n purchaseApprovalGroup: true },\n { id: 'purchases', label: 'Purchases', path: '/ui/medplat/manage/pm/purchases', icon: ShoppingCartRoundedIcon,\n permissionId: 'view.manage.productManagement.purchase', hiddenWhenConfirmationOn: true },\n { id: 'inventory', label: 'Inventory', path: '/ui/medplat/manage/pm/inventory', icon: WidgetsRoundedIcon, permissionId: 'view.manage.productManagement.inventory' },\n // Pickups — user checks out facility stock onto personal inventory and\n // hands it back later. Wastage gets recorded at return if there's a gap.\n { id: 'pickups', label: 'Pickups', path: '/ui/medplat/manage/pm/pickups', icon: MoveDownRoundedIcon, permissionId: 'view.manage.productManagement.pickup' },\n // Transfers — two-step facility ↔ facility movement (sender ships,\n // receiver confirms). Permission-gated so deployments that don't use\n // transfers can leave the menu out by withholding the perm.\n { id: 'transfers', label: 'Transfers', path: '/ui/medplat/manage/pm/transfers', icon: SwapHorizRoundedIcon, permissionId: 'view.manage.productManagement.transfer' },\n // Sales + Payments items also AND with settings.manageSales; PmNav and\n // DashboardPage hide them when the deployment isn't running sales.\n { id: 'sales', label: 'Sales', path: '/ui/medplat/manage/pm/sales', icon: PointOfSaleRoundedIcon, permissionId: 'view.manage.productManagement.sale', salesGroup: true },\n { id: 'vials', label: 'Vial', path: '/ui/medplat/manage/pm/vials', icon: VaccinesRoundedIcon, permissionId: 'view.manage.productManagement.vial' },\n { id: 'payments', label: 'Payments', path: '/ui/medplat/manage/pm/payments', icon: PaymentsRoundedIcon, permissionId: 'view.manage.productManagement.payment', salesGroup: true },\n ],\n },\n {\n id: 'ledger',\n label: 'Ledger',\n description: 'Every stock and money movement, append-only, with full lot / volume / monetary detail.',\n icon: ReceiptLongRoundedIcon,\n accent: '#f59e0b', // amber\n accentDark: '#b45309',\n accentSoft: '#fef3c7',\n items: [\n { id: 'transactions', label: 'Transactions', path: '/ui/medplat/manage/pm/transactions', icon: ReceiptLongRoundedIcon, permissionId: 'view.manage.productManagement.ledger' },\n ],\n },\n];\n\n/**\n * Filter the workspace tree against (a) the current operator's PM permission\n * set and (b) the manageSales settings toggle. An item is visible iff its\n * permissionId is granted AND (manageSales is on OR it isn't in the sales\n * group). A workspace tab survives iff at least one of its items survives.\n *\n * Both PmNav and DashboardPage consume this so the two stay in lock-step.\n */\nexport function getVisibleWorkspaces(perms, settings) {\n const manageSales = settings?.manageSales !== false;\n const requirePurchaseConfirmation = settings?.requirePurchaseConfirmation === true;\n return WORKSPACES\n .map((ws) => ({\n ...ws,\n items: ws.items.filter((it) => {\n // Items can declare either a single `permissionId` or\n // `anyOfPermissionIds` for the \"user needs any of these perms\" case\n // (used by the Purchase Requests entry — visible to both viewers\n // and approvers).\n const hasPerm = it.anyOfPermissionIds\n ? !!perms?.hasAny?.(it.anyOfPermissionIds)\n : !!perms?.has?.(it.permissionId);\n return hasPerm\n && (manageSales || !it.salesGroup)\n && (requirePurchaseConfirmation || !it.purchaseApprovalGroup)\n && (!requirePurchaseConfirmation || !it.hiddenWhenConfirmationOn);\n }),\n }))\n .filter((ws) => ws.items.length > 0);\n}\n\n// Locate the workspace that owns a given pathname. The Dashboard route is\n// intentionally not in any workspace; we surface it via a dedicated pill.\nexport function findWorkspaceForPath(pathname) {\n if (!pathname) return null;\n for (const ws of WORKSPACES) {\n if (ws.items.some((it) => pathname.startsWith(it.path))) return ws;\n }\n return null;\n}\n\nexport function findItemForPath(pathname) {\n if (!pathname) return null;\n for (const ws of WORKSPACES) {\n const hit = ws.items.find((it) => pathname.startsWith(it.path));\n if (hit) return { workspace: ws, item: hit };\n }\n return null;\n}\n","import PropTypes from 'prop-types';\nimport { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport { Box, Chip, Fade, Stack, Typography } from '@mui/material';\nimport { DASHBOARD, SETTINGS, findItemForPath } from '@pm/utils/workspaces';\nimport { loadPmSettings } from '@pm/utils/settingsStore';\n\n// Page shell shared by every PM screen. Reads the active workspace from the\n// route and tints the header to match — peripheral cue for \"what section of\n// the tool am I in\". Body content fades in to soften route changes.\nexport default function PmPageShell({ title, subtitle, chips = [], actions, children }) {\n const theme = useTheme();\n const { pathname } = useLocation();\n\n // Kick off the settings preload from the shared shell, so every PM page has\n // the currency cached by the time PmMoney renders. usePmSettings inside the\n // money cell will also trigger this; calling here just shaves one render.\n useEffect(() => { void loadPmSettings().catch(() => {}); }, []);\n\n const onDashboard = pathname.startsWith(DASHBOARD.path);\n const onSettings = pathname.startsWith(SETTINGS.path);\n const hit = findItemForPath(pathname);\n const workspace = hit?.workspace;\n\n const accent = onDashboard\n ? DASHBOARD.accent\n : onSettings\n ? SETTINGS.accent\n : (workspace?.accent ?? theme.palette.primary.main);\n const accentDark = onDashboard\n ? DASHBOARD.accentDark\n : onSettings\n ? SETTINGS.accentDark\n : (workspace?.accentDark ?? theme.palette.primary.dark);\n\n return (\n <Box sx={{ position: 'relative', px: { xs: 1, md: 2 }, py: { xs: 1, md: 2 } }}>\n {/* Soft accent glow tied to the active workspace. */}\n <Box sx={{ position: 'absolute', inset: 0, pointerEvents: 'none', overflow: 'hidden' }}>\n <Box\n sx={{\n position: 'absolute',\n top: -60,\n right: -40,\n width: 260,\n height: 260,\n borderRadius: '50%',\n background: `radial-gradient(circle, ${alpha(accent, 0.18)} 0%, transparent 70%)`,\n filter: 'blur(2px)',\n }}\n />\n <Box\n sx={{\n position: 'absolute',\n top: 40,\n left: -50,\n width: 200,\n height: 200,\n borderRadius: '50%',\n background: `radial-gradient(circle, ${alpha(theme.palette.info.main, 0.08)} 0%, transparent 72%)`,\n filter: 'blur(2px)',\n }}\n />\n </Box>\n\n {/* Header card — workspace badge + title + actions. */}\n <Box\n sx={{\n position: 'relative',\n zIndex: 1,\n mb: 2,\n p: { xs: 2, md: 2.5 },\n borderRadius: 1.5,\n border: `1px solid ${alpha(accent, 0.18)}`,\n background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.98)} 0%, ${alpha(accent, 0.07)} 100%)`,\n boxShadow: `0 18px 44px ${alpha(theme.palette.common.black, 0.06)}`,\n overflow: 'hidden',\n // Thin colored bar at the left edge — instantly tells you which workspace.\n '&::before': {\n content: '\"\"',\n position: 'absolute',\n left: 0,\n top: 0,\n bottom: 0,\n width: 4,\n background: `linear-gradient(180deg, ${accent} 0%, ${accentDark} 100%)`,\n },\n }}\n >\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n justifyContent=\"space-between\"\n alignItems={{ xs: 'flex-start', md: 'center' }}\n spacing={1.5}\n >\n <Box sx={{ minWidth: 0 }}>\n {workspace || onDashboard || onSettings ? (\n <Stack direction=\"row\" alignItems=\"center\" spacing={0.75} sx={{ mb: 0.75 }}>\n {(() => {\n const Icon = onDashboard\n ? DASHBOARD.icon\n : onSettings\n ? SETTINGS.icon\n : workspace.icon;\n return Icon ? (\n <Icon sx={{ fontSize: 16, color: accentDark }} />\n ) : null;\n })()}\n <Typography\n variant=\"overline\"\n sx={{\n color: accentDark,\n fontWeight: 800,\n letterSpacing: 1.1,\n lineHeight: 1,\n }}\n >\n {onDashboard ? 'Overview' : onSettings ? 'Configuration' : workspace.label}\n </Typography>\n {hit?.item ? (\n <>\n <Box sx={{ width: 4, height: 4, borderRadius: '50%', bgcolor: alpha(accent, 0.4), mx: 0.5 }} />\n <Typography variant=\"overline\" sx={{ color: theme.palette.text.secondary, fontWeight: 700, letterSpacing: 0.8 }}>\n {hit.item.label}\n </Typography>\n </>\n ) : null}\n </Stack>\n ) : null}\n\n <Typography variant=\"h5\" sx={{ fontWeight: 800, color: theme.palette.text.primary, lineHeight: 1.2 }}>\n {title}\n </Typography>\n {subtitle ? (\n <Typography variant=\"body2\" sx={{ mt: 0.75, maxWidth: 900, color: theme.palette.text.secondary }}>\n {subtitle}\n </Typography>\n ) : null}\n {chips?.length ? (\n <Stack direction=\"row\" spacing={1} useFlexGap flexWrap=\"wrap\" sx={{ mt: 1.5 }}>\n {chips.map((chip) => (\n <Chip\n key={chip}\n label={chip}\n size=\"small\"\n sx={{\n borderRadius: 2,\n backgroundColor: alpha(accent, 0.1),\n color: accentDark,\n fontWeight: 700,\n border: `1px solid ${alpha(accent, 0.2)}`,\n }}\n />\n ))}\n </Stack>\n ) : null}\n </Box>\n {actions ? <Box>{actions}</Box> : null}\n </Stack>\n </Box>\n\n {/* Body — fade in to soften route transitions. Keyed on pathname so each\n navigation triggers a fresh animation rather than reusing prior fade. */}\n <Fade in key={pathname} timeout={360}>\n <Box\n sx={{\n position: 'relative',\n zIndex: 1,\n // Form field labels and helper/hint text default to a small, light\n // grey that's hard to read — bump the size and darken the colour\n // consistently across every PM form.\n '& .MuiInputLabel-root': {\n fontSize: '0.95rem',\n color: theme.palette.text.primary,\n },\n '& .MuiInputLabel-root.Mui-focused': {\n color: theme.palette.primary.main,\n },\n '& .MuiFormHelperText-root': {\n fontSize: '0.78rem',\n color: alpha(theme.palette.text.primary, 0.7),\n },\n }}\n >\n {children}\n </Box>\n </Fade>\n </Box>\n );\n}\n\nPmPageShell.propTypes = {\n title: PropTypes.node,\n subtitle: PropTypes.node,\n chips: PropTypes.arrayOf(PropTypes.string),\n actions: PropTypes.node,\n children: PropTypes.node,\n};\n","import { useEffect, useMemo } from 'react';\nimport { useSelector } from 'react-redux';\n\n// PM module's navigation_state in menu_config. The backend attaches a\n// featureJson onto this node whose keys are the permission_ids the current\n// user has been granted under the module — exactly the same featureJson\n// MedplatForm reads off any other menu node. Nothing about which\n// permissions exist is baked into the frontend; the source of truth is\n// whatever the backend serialised onto this node.\nconst PM_MENU_KEY = 'techo.manage.pm';\n\n/**\n * Hook returning the PM permission set for the current user.\n *\n * Reads `linearMenuItems['techo.manage.pm'].featureJson` from the\n * Authenticate slice — Protected.jsx populates that at login via\n * /api/login/menuV2. The featureJson is a JSON-string-or-object whose keys\n * are permission_ids and whose values are true/false.\n *\n * Returns:\n * has(permId) — true iff the user has been granted that permission_id\n * hasAny(ids[]) — true iff any id in the list is granted\n *\n * **Default-deny**: when the PM menu node is missing or its featureJson is\n * empty, both helpers return false. There's no dev escape hatch — devs go\n * through the same grant flow as everyone else.\n *\n * **What's actually in featureJson**: only the top-level api_permission\n * rows for menu_id 543 (those with `parent_permission_id = NULL`). The\n * `.child.get.X` / `.child.post.X` rows govern API-URL access via the\n * backend's GlobalMenuPermissionFilter but they do NOT surface to the\n * frontend. So every UI check in PM uses the parent perm\n * (`view.manage.productManagement.<page>`). The backend still 403s any\n * write the user lacks the `.child.post.X` row for — that's the actual\n * write gate. To gate buttons in the UI separately from page-visibility\n * later, mint flat write perms (e.g. `write.manage.productManagement.X`\n * with `parent_permission_id = NULL`).\n */\n/**\n * `useEffect` that only runs when the current user has the permission.\n *\n * <p>PM pages historically declared their load `useEffect` in the outer\n * component above {@code <PmGuard>}. That meant the fetch fired on mount\n * regardless of the user's perm — the guard only hid the UI after the\n * network call already went out. Wrapping the effect with this hook makes\n * the fetch wait for (or skip entirely without) the perm, matching what\n * `PmGuard` does for the rendered tree.\n *\n * @param {string|string[]} perm Permission id required to fire the effect. Array means any-of.\n * @param {() => (void|(()=>void))} fn The effect body — receives no args, may return a cleanup.\n * @param {ReadonlyArray<*>} [deps] Extra deps; the perm gate is added implicitly.\n */\nexport function usePermissionedEffect(perm, fn, deps) {\n const perms = usePmPermissions();\n const allowed = Array.isArray(perm)\n ? perms.hasAny(perm)\n : (!!perm && perms.has(perm));\n // We list `allowed` first so a perm flip re-fires the effect (e.g. a user\n // who gains the perm mid-session lands on the page fully loaded). Caller\n // deps come after — same semantics they'd get from a plain useEffect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => {\n if (!allowed) return undefined;\n return fn();\n }, [allowed, ...(deps ?? [])]);\n}\n\nexport function usePmPermissions() {\n const linearMenuItems = useSelector((state) => state.Authenticate?.linearMenuItems);\n return useMemo(() => {\n const node = linearMenuItems?.[PM_MENU_KEY];\n let grants = node?.featureJson;\n if (typeof grants === 'string') {\n try { grants = JSON.parse(grants); } catch { grants = {}; }\n }\n const grantSet = grants && typeof grants === 'object' ? grants : {};\n const has = (permId) => !!permId && grantSet[permId] === true;\n const hasAny = (perms) => Array.isArray(perms) && perms.some(has);\n return { has, hasAny };\n }, [linearMenuItems]);\n}\n","import { useMemo } from 'react';\nimport { useLocation, useNavigate } from 'react-router-dom';\nimport { Box, Stack, Tab, Tabs, Typography } from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport { DASHBOARD, SETTINGS, findWorkspaceForPath, getVisibleWorkspaces } from '@pm/utils/workspaces';\nimport { usePmSettings } from '@pm/utils/settingsStore';\nimport { usePmPermissions } from '@pm/utils/permissions';\n\n// Two-row navigation:\n// Row 1 — workspace pills: Dashboard · Catalog · Operations · Ledger\n// Row 2 — when inside a workspace, the items in that workspace as tabs,\n// accented with the workspace's hue.\n//\n// The active workspace's accent flows into both rows so the user always\n// has a peripheral cue for \"where am I in the tool\".\n//\n// Visibility is filtered by getVisibleWorkspaces() — items that the user\n// lacks permission for OR that the deployment has turned off (e.g. Sales +\n// Payments when manageSales=false) are dropped, and a workspace tab whose\n// items all got dropped disappears too.\nexport default function PmNav() {\n const navigate = useNavigate();\n const location = useLocation();\n const theme = useTheme();\n const settings = usePmSettings();\n const perms = usePmPermissions();\n\n const visibleWorkspaces = useMemo(\n () => getVisibleWorkspaces(perms, settings),\n [perms, settings],\n );\n\n const showDashboardPill = perms.has(DASHBOARD.permissionId);\n const showSettingsPill = perms.has(SETTINGS.permissionId);\n\n const onDashboard = location.pathname.startsWith(DASHBOARD.path);\n const onSettings = location.pathname.startsWith(SETTINGS.path);\n const activeWorkspace = findWorkspaceForPath(location.pathname);\n // visibleWorkspaces is already filtered; if the user landed on a workspace\n // whose items they no longer have access to (e.g. deep link), it won't be\n // here and the row-2 tab strip stays collapsed.\n const visibleActiveWorkspace = activeWorkspace\n ? visibleWorkspaces.find((ws) => ws.id === activeWorkspace.id) ?? null\n : null;\n const accent = onDashboard\n ? DASHBOARD.accent\n : onSettings\n ? SETTINGS.accent\n : (activeWorkspace?.accent ?? theme.palette.primary.main);\n\n const activeItemIndex = visibleActiveWorkspace\n ? visibleActiveWorkspace.items.findIndex((it) => location.pathname.startsWith(it.path))\n : -1;\n\n function pillSx(active, hue) {\n return {\n px: 2,\n py: 0.9,\n borderRadius: 999,\n cursor: 'pointer',\n display: 'inline-flex',\n alignItems: 'center',\n gap: 1,\n fontWeight: 700,\n fontSize: '0.875rem',\n letterSpacing: 0.2,\n transition: 'all 220ms cubic-bezier(0.4, 0, 0.2, 1)',\n color: active ? '#fff' : theme.palette.text.secondary,\n backgroundColor: active ? hue : 'transparent',\n boxShadow: active ? `0 8px 22px ${alpha(hue, 0.32)}` : 'none',\n '&:hover': {\n color: active ? '#fff' : hue,\n backgroundColor: active ? hue : alpha(hue, 0.08),\n transform: active ? 'none' : 'translateY(-1px)',\n },\n };\n }\n\n function renderPill({ id, label, icon: Icon, active, onClick, hue }) {\n return (\n <Box key={id} role=\"button\" onClick={onClick} sx={pillSx(active, hue)}>\n {Icon ? <Icon sx={{ fontSize: 18 }} /> : null}\n {label}\n </Box>\n );\n }\n\n return (\n <Box sx={{ mb: 2 }}>\n {/* Row 1 — workspace pills */}\n <Box\n sx={{\n position: 'relative',\n borderRadius: 1.5,\n p: 1,\n background: `linear-gradient(135deg, ${alpha(theme.palette.background.paper, 0.96)} 0%, ${alpha(accent, 0.06)} 100%)`,\n border: `1px solid ${alpha(theme.palette.divider, 0.7)}`,\n boxShadow: `0 10px 28px ${alpha(theme.palette.common.black, 0.04)}`,\n }}\n >\n <Stack direction=\"row\" alignItems=\"center\" spacing={0.75} flexWrap=\"wrap\" useFlexGap>\n {showDashboardPill ? renderPill({\n id: DASHBOARD.id,\n label: DASHBOARD.label,\n icon: DASHBOARD.icon,\n active: onDashboard,\n hue: DASHBOARD.accent,\n onClick: () => navigate(DASHBOARD.path),\n }) : null}\n\n {showDashboardPill && visibleWorkspaces.length > 0 ? (\n <Box sx={{ width: 1, height: 22, mx: 0.5, bgcolor: 'divider', opacity: 0.7 }} />\n ) : null}\n\n {visibleWorkspaces.map((ws) => renderPill({\n id: ws.id,\n label: ws.label,\n icon: ws.icon,\n active: activeWorkspace?.id === ws.id,\n hue: ws.accent,\n // Tapping a workspace jumps to its first item — a deterministic\n // landing, no submenu to fight with.\n onClick: () => ws.items[0] && navigate(ws.items[0].path),\n }))}\n\n {/* Settings sits to the right, separated from the workspaces — it's\n configuration, not a workspace, and shouldn't compete visually. */}\n <Box sx={{ flexGrow: 1 }} />\n {showSettingsPill ? renderPill({\n id: SETTINGS.id,\n label: SETTINGS.label,\n icon: SETTINGS.icon,\n active: onSettings,\n hue: SETTINGS.accent,\n onClick: () => navigate(SETTINGS.path),\n }) : null}\n </Stack>\n </Box>\n\n {/* Row 2 — items within the active workspace */}\n {visibleActiveWorkspace && visibleActiveWorkspace.items.length > 0 ? (\n <Box\n sx={{\n mt: 1,\n borderRadius: 1.5,\n px: 1,\n bgcolor: alpha(visibleActiveWorkspace.accent, 0.04),\n border: `1px solid ${alpha(visibleActiveWorkspace.accent, 0.12)}`,\n }}\n >\n <Tabs\n value={activeItemIndex === -1 ? false : activeItemIndex}\n onChange={(_, idx) => navigate(visibleActiveWorkspace.items[idx].path)}\n variant=\"scrollable\"\n scrollButtons=\"auto\"\n slotProps={{\n indicator: { sx: { backgroundColor: visibleActiveWorkspace.accent, height: 3, borderRadius: 2 } },\n }}\n sx={{\n minHeight: 44,\n '& .MuiTab-root': {\n minHeight: 44,\n fontWeight: 700,\n textTransform: 'none',\n fontSize: '0.85rem',\n color: theme.palette.text.secondary,\n transition: 'color 180ms ease',\n '&:hover': { color: visibleActiveWorkspace.accentDark },\n '&.Mui-selected': { color: visibleActiveWorkspace.accentDark },\n },\n }}\n >\n {visibleActiveWorkspace.items.map((it) => {\n const Icon = it.icon;\n return (\n <Tab\n key={it.id}\n iconPosition=\"start\"\n icon={Icon ? <Icon sx={{ fontSize: 18 }} /> : undefined}\n label={<Typography component=\"span\" sx={{ fontWeight: 700 }}>{it.label}</Typography>}\n />\n );\n })}\n </Tabs>\n </Box>\n ) : null}\n </Box>\n );\n}\n","import { useNavigate } from 'react-router-dom';\nimport { useSelector } from 'react-redux';\nimport { Alert, Box, Button, LinearProgress, Stack, Typography } from '@mui/material';\nimport LockOutlinedIcon from '@mui/icons-material/LockOutlined';\nimport { usePmPermissions } from '@pm/utils/permissions';\n\n/**\n * Page-level guard. Wrap a PM page in `<PmGuard perm=\"...\">…</PmGuard>` and\n * the children only render when the current user has the perm; otherwise\n * the page shows a friendly \"no access\" panel with a back-to-dashboard\n * button.\n *\n * Default-deny: when the featureJson is missing the guard blocks. While the\n * menu tree hasn't loaded yet (first paint right after login) we show a\n * thin loader so the \"no access\" alert doesn't flash.\n */\nexport default function PmGuard({ perm, anyOf, children, title = 'You don’t have access to this page' }) {\n const perms = usePmPermissions();\n const navigate = useNavigate();\n const linearMenuItems = useSelector((state) => state.Authenticate?.linearMenuItems);\n const menuReady = linearMenuItems !== null && linearMenuItems !== undefined;\n if (!menuReady) {\n return (\n <Box sx={{ p: 3 }}>\n <LinearProgress />\n </Box>\n );\n }\n const allowed = anyOf ? perms.hasAny(anyOf) : perms.has(perm);\n if (allowed) return children;\n return (\n <Box sx={{ p: 3 }}>\n <Alert\n severity=\"warning\"\n icon={<LockOutlinedIcon />}\n sx={{ borderRadius: 1.5, alignItems: 'flex-start' }}\n >\n <Stack spacing={1.25}>\n <Typography sx={{ fontWeight: 800 }}>{title}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Your account doesn’t have the required permission to view this\n section of Product Management. Ask an admin to grant it via the\n role / user permission assignment.\n </Typography>\n <Box>\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => navigate('/ui/medplat/manage/pm')}\n >\n Back to Dashboard\n </Button>\n </Box>\n </Stack>\n </Alert>\n </Box>\n );\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { i as e, r as t } from "./settingsStore-BlA2GIhl.js";
1
+ import { i as e, r as t } from "./settingsStore-BHHOmcCP.js";
2
2
  import { useEffect as n, useState as r } from "react";
3
3
  import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
4
4
  import s from "prop-types";
@@ -451,13 +451,12 @@ W.propTypes = {
451
451
  max: s.number.isRequired
452
452
  };
453
453
  function G({ label: e, value: t, onChange: n, onBlur: r, options: i, required: o = !1, disabled: s = !1, error: l = !1, helperText: u = "", fullWidth: d = !0, size: f = "small", noOptionsText: p, getOptionDisabled: m, sx: h = {} }) {
454
- let g = i.find((e) => e.value === t) ?? null;
455
454
  return /* @__PURE__ */ a(c, {
456
455
  size: f,
457
456
  fullWidth: d,
458
457
  disabled: s,
459
458
  options: i,
460
- value: g,
459
+ value: i.find((e) => e.value === t) ?? null,
461
460
  onChange: (e, t) => n(t ? t.value : ""),
462
461
  getOptionLabel: (e) => e?.label ?? "",
463
462
  getOptionDisabled: m,
@@ -542,4 +541,4 @@ K.propTypes = {
542
541
  //#endregion
543
542
  export { U as a, B as c, H as i, k as l, W as n, G as o, K as r, z as s, V as t };
544
543
 
545
- //# sourceMappingURL=PmTable-BSV_bMJZ.js.map
544
+ //# sourceMappingURL=PmTable-CwKORZGg.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PmTable-BSV_bMJZ.js","names":[],"sources":["../src/utils/pmToast.jsx","../src/components/PmTable.jsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\n/**\n * Centered, animated toast for the Product Management module. Drop-in for the\n * old host `showToast({ message, type })`: same call shape, nicer presentation.\n *\n * - Appears in the middle of the screen with a fade/scale-in.\n * - Auto-dismisses after `duration` (default 2500ms) via a depleting progress\n * bar; hovering the toast pauses the bar (and the timer) so it can be read.\n * - Types: success | warning | error | info.\n *\n * Implementation notes: a single self-mounted React root (portal into\n * document.body) driven by a tiny module-level store, so it works from\n * imperative call sites (including non-component code) without a provider.\n */\n\nlet toasts = [];\nconst listeners = new Set();\nlet seq = 0;\nlet host = null;\n\nconst TYPE_STYLE = {\n success: { accent: '#16a34a', bg: '#f0fdf4', glyph: '✓' },\n error: { accent: '#dc2626', bg: '#fef2f2', glyph: '✕' },\n warning: { accent: '#d97706', bg: '#fffbeb', glyph: '!' },\n info: { accent: '#2563eb', bg: '#eff6ff', glyph: 'i' },\n};\n\nfunction emit() { listeners.forEach((l) => l(toasts.slice())); }\nfunction remove(id) { toasts = toasts.filter((t) => t.id !== id); emit(); }\n\nexport function showToast({ message, type = 'info', duration = 2500 } = {}) {\n ensureHost();\n const id = ++seq;\n toasts = [...toasts, { id, message, type: TYPE_STYLE[type] ? type : 'info', duration }];\n emit();\n return id;\n}\n\nexport function dismissToast(id) { remove(id); }\n\nfunction ensureHost() {\n if (host || typeof document === 'undefined') return;\n const el = document.createElement('div');\n el.setAttribute('data-pm-toast-root', '');\n document.body.appendChild(el);\n host = createRoot(el);\n host.render(<PmToastHost />);\n}\n\nconst KEYFRAMES = `\n@keyframes pmtoast-in { from { opacity: 0; transform: translateY(-32px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }\n@keyframes pmtoast-out { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(-18px) scale(0.97); } }\n@keyframes pmtoast-shrink { from { transform: scaleX(1); } to { transform: scaleX(0); } }\n`;\n\n// Anchored to the top and horizontally centred so toasts pop down from the top.\nconst WRAP_STYLE = {\n position: 'fixed', top: 0, left: 0, right: 0, zIndex: 21000, padding: '28px 16px 0',\n display: 'flex', flexDirection: 'column', alignItems: 'center',\n gap: 12, pointerEvents: 'none',\n};\nconst CARD_STYLE = {\n position: 'relative', display: 'flex', alignItems: 'center', gap: 16,\n minWidth: 360, maxWidth: 540, padding: '20px 22px',\n background: '#ffffff', borderRadius: 16, overflow: 'hidden',\n border: '1px solid rgba(15,23,42,0.06)', boxShadow: '0 24px 60px rgba(2,6,23,0.26)',\n fontFamily: 'inherit',\n};\nconst ICON_STYLE = {\n width: 40, height: 40, borderRadius: '50%', flex: 'none',\n display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 800, fontSize: 22,\n};\nconst CLOSE_STYLE = {\n border: 'none', background: 'transparent', cursor: 'pointer', color: '#94a3b8',\n fontSize: 15, lineHeight: 1, padding: 6, flex: 'none',\n};\nconst PROGRESS_TRACK = {\n position: 'absolute', left: 0, right: 0, bottom: 0, height: 4, background: 'rgba(15,23,42,0.06)',\n};\n\nfunction PmToastHost() {\n const [items, setItems] = useState(toasts.slice());\n useEffect(() => {\n listeners.add(setItems);\n setItems(toasts.slice());\n return () => listeners.delete(setItems);\n }, []);\n return (\n <>\n <style>{KEYFRAMES}</style>\n <div style={WRAP_STYLE}>\n {items.map((t) => <ToastItem key={t.id} toast={t} onClose={() => remove(t.id)} />)}\n </div>\n </>\n );\n}\n\nfunction ToastItem({ toast, onClose }) {\n const [paused, setPaused] = useState(false);\n const [leaving, setLeaving] = useState(false);\n const s = TYPE_STYLE[toast.type] || TYPE_STYLE.info;\n\n function beginClose() {\n if (leaving) return;\n setLeaving(true);\n setTimeout(onClose, 180);\n }\n\n return (\n <div\n role=\"status\"\n onMouseEnter={() => setPaused(true)}\n onMouseLeave={() => setPaused(false)}\n style={{\n ...CARD_STYLE, pointerEvents: 'auto',\n animation: leaving\n ? 'pmtoast-out 180ms ease forwards'\n : 'pmtoast-in 320ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards',\n }}\n >\n <div style={{ ...ICON_STYLE, color: s.accent, background: s.bg }}>{s.glyph}</div>\n <div style={{ flex: 1, minWidth: 0, fontSize: 15.5, fontWeight: 600, color: '#0f172a', lineHeight: 1.4 }}>\n {toast.message}\n </div>\n <button type=\"button\" aria-label=\"Dismiss\" onClick={beginClose} style={CLOSE_STYLE}>✕</button>\n <div style={PROGRESS_TRACK}>\n <div\n onAnimationEnd={beginClose}\n style={{\n height: '100%', background: s.accent, transformOrigin: 'left',\n animation: `pmtoast-shrink ${toast.duration}ms linear forwards`,\n animationPlayState: paused ? 'paused' : 'running',\n }}\n />\n </div>\n </div>\n );\n}\n","import PropTypes from 'prop-types';\nimport {\n Autocomplete,\n Box,\n Card,\n CardContent,\n Stack,\n Table,\n TableCell,\n TableContainer,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport { currencySymbol } from '@pm/utils/currency';\nimport { usePmSettings } from '@pm/utils/settingsStore';\n\n// Shared building blocks for the data-heavy PM tables. The goal is one\n// consistent look: gradient-headed table, soft hover, eyebrow + title card\n// chrome, semantic empty state, tabular-figure money cell. Every list page\n// is built from these so the visual rhythm is identical everywhere.\n\n// -------- Table -----------------------------------------------------------\n\nexport function PmTable({ children, dense = false, sx = {}, ...props }) {\n const theme = useTheme();\n return (\n <TableContainer\n sx={{\n borderRadius: 1.5,\n border: `1px solid ${alpha(theme.palette.divider, 0.55)}`,\n backgroundColor: theme.palette.background.paper,\n overflow: 'auto',\n ...sx,\n }}\n >\n <Table\n size={dense ? 'small' : 'medium'}\n {...props}\n sx={{\n '& thead .MuiTableRow-root': {\n background: `linear-gradient(180deg, ${alpha(theme.palette.grey[50], 1)} 0%, ${alpha(theme.palette.grey[100], 1)} 100%)`,\n },\n '& thead .MuiTableCell-root': {\n fontWeight: 800,\n fontSize: '0.7rem',\n textTransform: 'uppercase',\n letterSpacing: '0.65px',\n color: theme.palette.text.secondary,\n borderBottom: `1px solid ${theme.palette.divider}`,\n py: 1.25,\n whiteSpace: 'nowrap',\n position: 'sticky',\n top: 0,\n zIndex: 1,\n },\n '& tbody .MuiTableRow-root': {\n transition: 'background-color 140ms ease',\n },\n '& tbody .MuiTableRow-root:hover': {\n backgroundColor: alpha(theme.palette.primary.main, 0.04),\n },\n '& tbody .MuiTableCell-root': {\n py: 1.4,\n borderBottom: `1px solid ${alpha(theme.palette.divider, 0.5)}`,\n verticalAlign: 'middle',\n },\n '& tbody .MuiTableRow-root:last-of-type .MuiTableCell-root': {\n borderBottom: 'none',\n },\n }}\n >\n {children}\n </Table>\n </TableContainer>\n );\n}\nPmTable.propTypes = {\n children: PropTypes.node,\n dense: PropTypes.bool,\n sx: PropTypes.object,\n};\n\n// -------- Section card chrome --------------------------------------------\n\n/**\n * Card with a consistent eyebrow + title + actions header, ready to hold a\n * PmTable (or any other content). The table renders flush against the card\n * because the container handles borders/radius. Toolbar slot is for filters\n * or search inputs that sit between the title and the table.\n */\nexport function PmTableCard({\n eyebrow,\n title,\n subtitle,\n actions,\n toolbar,\n children,\n sx = {},\n}) {\n return (\n <Card\n sx={{\n borderRadius: 1.5,\n border: '1px solid',\n borderColor: 'divider',\n boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)',\n overflow: 'hidden',\n ...sx,\n }}\n >\n <CardContent sx={{ p: 0, '&:last-child': { pb: 0 } }}>\n {(title || actions) ? (\n <Box sx={{ px: 2.5, pt: 2, pb: 1.5 }}>\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n justifyContent=\"space-between\"\n alignItems={{ xs: 'flex-start', md: 'center' }}\n spacing={1.5}\n >\n <Box sx={{ minWidth: 0 }}>\n {eyebrow ? (\n <Typography\n variant=\"overline\"\n sx={{ color: 'text.secondary', fontWeight: 800, letterSpacing: 1, lineHeight: 1 }}\n >\n {eyebrow}\n </Typography>\n ) : null}\n {title ? (\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.2 }}>\n {title}\n </Typography>\n ) : null}\n {subtitle ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {subtitle}\n </Typography>\n ) : null}\n </Box>\n {actions ? <Box>{actions}</Box> : null}\n </Stack>\n </Box>\n ) : null}\n {toolbar ? <Box sx={{ px: 2.5, pb: 1.5 }}>{toolbar}</Box> : null}\n <Box sx={{ p: 2.5, pt: title || toolbar ? 0 : 2.5 }}>{children}</Box>\n </CardContent>\n </Card>\n );\n}\nPmTableCard.propTypes = {\n eyebrow: PropTypes.node,\n title: PropTypes.node,\n subtitle: PropTypes.node,\n actions: PropTypes.node,\n toolbar: PropTypes.node,\n children: PropTypes.node,\n sx: PropTypes.object,\n};\n\n// -------- Empty state row -------------------------------------------------\n\nexport function PmEmptyRow({ colSpan, title, hint, icon: Icon, action }) {\n return (\n <TableRow hover={false}>\n <TableCell colSpan={colSpan} sx={{ py: 5, textAlign: 'center', borderBottom: 'none !important' }}>\n <Stack alignItems=\"center\" spacing={1.25}>\n {Icon ? <Icon sx={{ fontSize: 44, color: 'text.disabled', opacity: 0.7 }} /> : null}\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>{title}</Typography>\n {hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ maxWidth: 360 }}>{hint}</Typography>\n ) : null}\n {action ? <Box>{action}</Box> : null}\n </Stack>\n </TableCell>\n </TableRow>\n );\n}\nPmEmptyRow.propTypes = {\n colSpan: PropTypes.number.isRequired,\n title: PropTypes.node,\n hint: PropTypes.node,\n icon: PropTypes.elementType,\n action: PropTypes.node,\n};\n\n// -------- Money cell ------------------------------------------------------\n\n/**\n * Currency display with tabular figures so numbers don't shift between\n * rows. {@code tone} can semantically colour the value — useful for\n * paid/due splits in the Sales table.\n */\nexport function PmMoney({ value, fallback = '—', size = 'normal', tone, bold = true, prefix, sx = {} }) {\n const theme = useTheme();\n const settings = usePmSettings();\n // When the caller doesn't override the prefix, pull the symbol from the\n // module settings — every PmMoney across the module then renders with the\n // currency configured for the deployment.\n const effectivePrefix = prefix ?? currencySymbol(settings?.currencyCode);\n if (value == null || value === '') {\n return <Box component=\"span\" sx={{ color: 'text.disabled', fontVariantNumeric: 'tabular-nums' }}>{fallback}</Box>;\n }\n const n = Number(value);\n if (Number.isNaN(n)) {\n return <Box component=\"span\" sx={{ color: 'text.disabled', fontVariantNumeric: 'tabular-nums' }}>{fallback}</Box>;\n }\n const color = tone === 'warning' ? theme.palette.warning.dark\n : tone === 'success' ? theme.palette.success.dark\n : tone === 'error' ? theme.palette.error.dark\n : tone === 'muted' ? theme.palette.text.secondary\n : 'inherit';\n return (\n <Box\n component=\"span\"\n sx={{\n fontWeight: bold ? (size === 'big' ? 800 : 700) : 500,\n fontSize: size === 'big' ? '1.05rem' : 'inherit',\n fontVariantNumeric: 'tabular-nums',\n color,\n ...sx,\n }}\n >\n {effectivePrefix}{n.toFixed(2)}\n </Box>\n );\n}\nPmMoney.propTypes = {\n value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n fallback: PropTypes.node,\n size: PropTypes.oneOf(['normal', 'big']),\n tone: PropTypes.oneOf(['warning', 'success', 'error', 'muted']),\n bold: PropTypes.bool,\n prefix: PropTypes.string,\n sx: PropTypes.object,\n};\n\n// -------- Reference chip --------------------------------------------------\n\n/** Monospace `#1234` chip used in left-most \"Ref\" columns. */\nexport function PmRef({ id }) {\n return (\n <Box\n component=\"span\"\n sx={{\n display: 'inline-flex',\n alignItems: 'center',\n px: 0.85,\n py: 0.25,\n borderRadius: 1,\n backgroundColor: 'grey.100',\n color: 'text.secondary',\n fontFamily: 'monospace',\n fontSize: '0.72rem',\n fontWeight: 700,\n letterSpacing: 0.4,\n lineHeight: 1.5,\n }}\n >\n #{id}\n </Box>\n );\n}\nPmRef.propTypes = {\n id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n};\n\n// -------- Field helper text with character counter -----------------------\n\n/**\n * Helper-text content that pairs a message (validation error or hint) on the\n * left with a live \"remaining/allowed\" character counter on the right. Pass\n * as a TextField's `helperText`. Renders only spans so it's valid inside the\n * FormHelperText <p>.\n */\nexport function PmFieldHelp({ message, value, max }) {\n const used = (value ?? '').length;\n const remaining = Math.max(0, max - used);\n return (\n <Box component=\"span\" sx={{ display: 'flex', width: '100%', gap: 1 }}>\n <Box component=\"span\" sx={{ flexGrow: 1 }}>{message || ''}</Box>\n <Box\n component=\"span\"\n sx={{ flexShrink: 0, fontVariantNumeric: 'tabular-nums', color: 'text.secondary' }}\n >\n {remaining}/{max}\n </Box>\n </Box>\n );\n}\nPmFieldHelp.propTypes = {\n message: PropTypes.node,\n value: PropTypes.string,\n max: PropTypes.number.isRequired,\n};\n\n// -------- Select (dropdown) ----------------------------------------------\n\n/**\n * Standard PM dropdown. Wraps MUI Autocomplete (the app-wide dropdown\n * pattern, same as FacilityPicker / CustomDropdown) so every select in the\n * module looks and behaves identically and sizes like the themed text\n * fields. Options are plain `{ value, label }`; `value`/`onChange` work with\n * the raw value so callers don't deal with option objects.\n */\nexport function PmSelect({\n label,\n value,\n onChange,\n onBlur,\n options,\n required = false,\n disabled = false,\n error = false,\n helperText = '',\n fullWidth = true,\n size = 'small',\n noOptionsText,\n // Optional (option) => bool. Lets a caller show an option that cannot be\n // chosen right now instead of hiding it, so the reason stays visible.\n getOptionDisabled,\n sx = {},\n}) {\n const selected = options.find((o) => o.value === value) ?? null;\n return (\n <Autocomplete\n size={size}\n fullWidth={fullWidth}\n disabled={disabled}\n options={options}\n value={selected}\n onChange={(_, opt) => onChange(opt ? opt.value : '')}\n getOptionLabel={(o) => o?.label ?? ''}\n getOptionDisabled={getOptionDisabled}\n isOptionEqualToValue={(o, v) => o?.value === v?.value}\n disableClearable={required}\n noOptionsText={noOptionsText}\n sx={sx}\n renderInput={(params) => (\n <TextField\n {...params}\n label={label}\n required={required}\n error={error}\n helperText={helperText}\n onBlur={onBlur}\n />\n )}\n />\n );\n}\nPmSelect.propTypes = {\n label: PropTypes.node,\n value: PropTypes.any,\n onChange: PropTypes.func.isRequired,\n onBlur: PropTypes.func,\n options: PropTypes.arrayOf(PropTypes.shape({\n value: PropTypes.any,\n label: PropTypes.node,\n })).isRequired,\n required: PropTypes.bool,\n disabled: PropTypes.bool,\n error: PropTypes.bool,\n helperText: PropTypes.node,\n fullWidth: PropTypes.bool,\n size: PropTypes.string,\n noOptionsText: PropTypes.node,\n getOptionDisabled: PropTypes.func,\n sx: PropTypes.object,\n};\n\n// -------- Form section helper --------------------------------------------\n\n/**\n * Wraps a chunk of form fields with a consistent eyebrow + title + hint\n * header. Used inside create/edit pages so every section has the same\n * vertical rhythm.\n */\nexport function PmFormSection({ eyebrow, title, hint, children, sx = {} }) {\n return (\n <Box sx={sx}>\n {(eyebrow || title || hint) ? (\n <Box sx={{ mb: 1.75 }}>\n {eyebrow ? (\n <Typography\n variant=\"overline\"\n sx={{ color: 'text.secondary', fontWeight: 800, letterSpacing: 1, lineHeight: 1, display: 'block' }}\n >\n {eyebrow}\n </Typography>\n ) : null}\n {title ? (\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.25 }}>{title}</Typography>\n ) : null}\n {hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ display: 'block', mt: 0.5 }}>\n {hint}\n </Typography>\n ) : null}\n </Box>\n ) : null}\n {children}\n </Box>\n );\n}\nPmFormSection.propTypes = {\n eyebrow: PropTypes.node,\n title: PropTypes.node,\n hint: PropTypes.node,\n children: PropTypes.node,\n sx: PropTypes.object,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PmTable-CwKORZGg.js","names":[],"sources":["../src/utils/pmToast.jsx","../src/components/PmTable.jsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { createRoot } from 'react-dom/client';\n\n/**\n * Centered, animated toast for the Product Management module. Drop-in for the\n * old host `showToast({ message, type })`: same call shape, nicer presentation.\n *\n * - Appears in the middle of the screen with a fade/scale-in.\n * - Auto-dismisses after `duration` (default 2500ms) via a depleting progress\n * bar; hovering the toast pauses the bar (and the timer) so it can be read.\n * - Types: success | warning | error | info.\n *\n * Implementation notes: a single self-mounted React root (portal into\n * document.body) driven by a tiny module-level store, so it works from\n * imperative call sites (including non-component code) without a provider.\n */\n\nlet toasts = [];\nconst listeners = new Set();\nlet seq = 0;\nlet host = null;\n\nconst TYPE_STYLE = {\n success: { accent: '#16a34a', bg: '#f0fdf4', glyph: '✓' },\n error: { accent: '#dc2626', bg: '#fef2f2', glyph: '✕' },\n warning: { accent: '#d97706', bg: '#fffbeb', glyph: '!' },\n info: { accent: '#2563eb', bg: '#eff6ff', glyph: 'i' },\n};\n\nfunction emit() { listeners.forEach((l) => l(toasts.slice())); }\nfunction remove(id) { toasts = toasts.filter((t) => t.id !== id); emit(); }\n\nexport function showToast({ message, type = 'info', duration = 2500 } = {}) {\n ensureHost();\n const id = ++seq;\n toasts = [...toasts, { id, message, type: TYPE_STYLE[type] ? type : 'info', duration }];\n emit();\n return id;\n}\n\nexport function dismissToast(id) { remove(id); }\n\nfunction ensureHost() {\n if (host || typeof document === 'undefined') return;\n const el = document.createElement('div');\n el.setAttribute('data-pm-toast-root', '');\n document.body.appendChild(el);\n host = createRoot(el);\n host.render(<PmToastHost />);\n}\n\nconst KEYFRAMES = `\n@keyframes pmtoast-in { from { opacity: 0; transform: translateY(-32px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } }\n@keyframes pmtoast-out { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(-18px) scale(0.97); } }\n@keyframes pmtoast-shrink { from { transform: scaleX(1); } to { transform: scaleX(0); } }\n`;\n\n// Anchored to the top and horizontally centred so toasts pop down from the top.\nconst WRAP_STYLE = {\n position: 'fixed', top: 0, left: 0, right: 0, zIndex: 21000, padding: '28px 16px 0',\n display: 'flex', flexDirection: 'column', alignItems: 'center',\n gap: 12, pointerEvents: 'none',\n};\nconst CARD_STYLE = {\n position: 'relative', display: 'flex', alignItems: 'center', gap: 16,\n minWidth: 360, maxWidth: 540, padding: '20px 22px',\n background: '#ffffff', borderRadius: 16, overflow: 'hidden',\n border: '1px solid rgba(15,23,42,0.06)', boxShadow: '0 24px 60px rgba(2,6,23,0.26)',\n fontFamily: 'inherit',\n};\nconst ICON_STYLE = {\n width: 40, height: 40, borderRadius: '50%', flex: 'none',\n display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 800, fontSize: 22,\n};\nconst CLOSE_STYLE = {\n border: 'none', background: 'transparent', cursor: 'pointer', color: '#94a3b8',\n fontSize: 15, lineHeight: 1, padding: 6, flex: 'none',\n};\nconst PROGRESS_TRACK = {\n position: 'absolute', left: 0, right: 0, bottom: 0, height: 4, background: 'rgba(15,23,42,0.06)',\n};\n\nfunction PmToastHost() {\n const [items, setItems] = useState(toasts.slice());\n useEffect(() => {\n listeners.add(setItems);\n setItems(toasts.slice());\n return () => listeners.delete(setItems);\n }, []);\n return (\n <>\n <style>{KEYFRAMES}</style>\n <div style={WRAP_STYLE}>\n {items.map((t) => <ToastItem key={t.id} toast={t} onClose={() => remove(t.id)} />)}\n </div>\n </>\n );\n}\n\nfunction ToastItem({ toast, onClose }) {\n const [paused, setPaused] = useState(false);\n const [leaving, setLeaving] = useState(false);\n const s = TYPE_STYLE[toast.type] || TYPE_STYLE.info;\n\n function beginClose() {\n if (leaving) return;\n setLeaving(true);\n setTimeout(onClose, 180);\n }\n\n return (\n <div\n role=\"status\"\n onMouseEnter={() => setPaused(true)}\n onMouseLeave={() => setPaused(false)}\n style={{\n ...CARD_STYLE, pointerEvents: 'auto',\n animation: leaving\n ? 'pmtoast-out 180ms ease forwards'\n : 'pmtoast-in 320ms cubic-bezier(0.34, 1.56, 0.64, 1) forwards',\n }}\n >\n <div style={{ ...ICON_STYLE, color: s.accent, background: s.bg }}>{s.glyph}</div>\n <div style={{ flex: 1, minWidth: 0, fontSize: 15.5, fontWeight: 600, color: '#0f172a', lineHeight: 1.4 }}>\n {toast.message}\n </div>\n <button type=\"button\" aria-label=\"Dismiss\" onClick={beginClose} style={CLOSE_STYLE}>✕</button>\n <div style={PROGRESS_TRACK}>\n <div\n onAnimationEnd={beginClose}\n style={{\n height: '100%', background: s.accent, transformOrigin: 'left',\n animation: `pmtoast-shrink ${toast.duration}ms linear forwards`,\n animationPlayState: paused ? 'paused' : 'running',\n }}\n />\n </div>\n </div>\n );\n}\n","import PropTypes from 'prop-types';\nimport {\n Autocomplete,\n Box,\n Card,\n CardContent,\n Stack,\n Table,\n TableCell,\n TableContainer,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport { currencySymbol } from '@pm/utils/currency';\nimport { usePmSettings } from '@pm/utils/settingsStore';\n\n// Shared building blocks for the data-heavy PM tables. The goal is one\n// consistent look: gradient-headed table, soft hover, eyebrow + title card\n// chrome, semantic empty state, tabular-figure money cell. Every list page\n// is built from these so the visual rhythm is identical everywhere.\n\n// -------- Table -----------------------------------------------------------\n\nexport function PmTable({ children, dense = false, sx = {}, ...props }) {\n const theme = useTheme();\n return (\n <TableContainer\n sx={{\n borderRadius: 1.5,\n border: `1px solid ${alpha(theme.palette.divider, 0.55)}`,\n backgroundColor: theme.palette.background.paper,\n overflow: 'auto',\n ...sx,\n }}\n >\n <Table\n size={dense ? 'small' : 'medium'}\n {...props}\n sx={{\n '& thead .MuiTableRow-root': {\n background: `linear-gradient(180deg, ${alpha(theme.palette.grey[50], 1)} 0%, ${alpha(theme.palette.grey[100], 1)} 100%)`,\n },\n '& thead .MuiTableCell-root': {\n fontWeight: 800,\n fontSize: '0.7rem',\n textTransform: 'uppercase',\n letterSpacing: '0.65px',\n color: theme.palette.text.secondary,\n borderBottom: `1px solid ${theme.palette.divider}`,\n py: 1.25,\n whiteSpace: 'nowrap',\n position: 'sticky',\n top: 0,\n zIndex: 1,\n },\n '& tbody .MuiTableRow-root': {\n transition: 'background-color 140ms ease',\n },\n '& tbody .MuiTableRow-root:hover': {\n backgroundColor: alpha(theme.palette.primary.main, 0.04),\n },\n '& tbody .MuiTableCell-root': {\n py: 1.4,\n borderBottom: `1px solid ${alpha(theme.palette.divider, 0.5)}`,\n verticalAlign: 'middle',\n },\n '& tbody .MuiTableRow-root:last-of-type .MuiTableCell-root': {\n borderBottom: 'none',\n },\n }}\n >\n {children}\n </Table>\n </TableContainer>\n );\n}\nPmTable.propTypes = {\n children: PropTypes.node,\n dense: PropTypes.bool,\n sx: PropTypes.object,\n};\n\n// -------- Section card chrome --------------------------------------------\n\n/**\n * Card with a consistent eyebrow + title + actions header, ready to hold a\n * PmTable (or any other content). The table renders flush against the card\n * because the container handles borders/radius. Toolbar slot is for filters\n * or search inputs that sit between the title and the table.\n */\nexport function PmTableCard({\n eyebrow,\n title,\n subtitle,\n actions,\n toolbar,\n children,\n sx = {},\n}) {\n return (\n <Card\n sx={{\n borderRadius: 1.5,\n border: '1px solid',\n borderColor: 'divider',\n boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)',\n overflow: 'hidden',\n ...sx,\n }}\n >\n <CardContent sx={{ p: 0, '&:last-child': { pb: 0 } }}>\n {(title || actions) ? (\n <Box sx={{ px: 2.5, pt: 2, pb: 1.5 }}>\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n justifyContent=\"space-between\"\n alignItems={{ xs: 'flex-start', md: 'center' }}\n spacing={1.5}\n >\n <Box sx={{ minWidth: 0 }}>\n {eyebrow ? (\n <Typography\n variant=\"overline\"\n sx={{ color: 'text.secondary', fontWeight: 800, letterSpacing: 1, lineHeight: 1 }}\n >\n {eyebrow}\n </Typography>\n ) : null}\n {title ? (\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.2 }}>\n {title}\n </Typography>\n ) : null}\n {subtitle ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {subtitle}\n </Typography>\n ) : null}\n </Box>\n {actions ? <Box>{actions}</Box> : null}\n </Stack>\n </Box>\n ) : null}\n {toolbar ? <Box sx={{ px: 2.5, pb: 1.5 }}>{toolbar}</Box> : null}\n <Box sx={{ p: 2.5, pt: title || toolbar ? 0 : 2.5 }}>{children}</Box>\n </CardContent>\n </Card>\n );\n}\nPmTableCard.propTypes = {\n eyebrow: PropTypes.node,\n title: PropTypes.node,\n subtitle: PropTypes.node,\n actions: PropTypes.node,\n toolbar: PropTypes.node,\n children: PropTypes.node,\n sx: PropTypes.object,\n};\n\n// -------- Empty state row -------------------------------------------------\n\nexport function PmEmptyRow({ colSpan, title, hint, icon: Icon, action }) {\n return (\n <TableRow hover={false}>\n <TableCell colSpan={colSpan} sx={{ py: 5, textAlign: 'center', borderBottom: 'none !important' }}>\n <Stack alignItems=\"center\" spacing={1.25}>\n {Icon ? <Icon sx={{ fontSize: 44, color: 'text.disabled', opacity: 0.7 }} /> : null}\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>{title}</Typography>\n {hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ maxWidth: 360 }}>{hint}</Typography>\n ) : null}\n {action ? <Box>{action}</Box> : null}\n </Stack>\n </TableCell>\n </TableRow>\n );\n}\nPmEmptyRow.propTypes = {\n colSpan: PropTypes.number.isRequired,\n title: PropTypes.node,\n hint: PropTypes.node,\n icon: PropTypes.elementType,\n action: PropTypes.node,\n};\n\n// -------- Money cell ------------------------------------------------------\n\n/**\n * Currency display with tabular figures so numbers don't shift between\n * rows. {@code tone} can semantically colour the value — useful for\n * paid/due splits in the Sales table.\n */\nexport function PmMoney({ value, fallback = '—', size = 'normal', tone, bold = true, prefix, sx = {} }) {\n const theme = useTheme();\n const settings = usePmSettings();\n // When the caller doesn't override the prefix, pull the symbol from the\n // module settings — every PmMoney across the module then renders with the\n // currency configured for the deployment.\n const effectivePrefix = prefix ?? currencySymbol(settings?.currencyCode);\n if (value == null || value === '') {\n return <Box component=\"span\" sx={{ color: 'text.disabled', fontVariantNumeric: 'tabular-nums' }}>{fallback}</Box>;\n }\n const n = Number(value);\n if (Number.isNaN(n)) {\n return <Box component=\"span\" sx={{ color: 'text.disabled', fontVariantNumeric: 'tabular-nums' }}>{fallback}</Box>;\n }\n const color = tone === 'warning' ? theme.palette.warning.dark\n : tone === 'success' ? theme.palette.success.dark\n : tone === 'error' ? theme.palette.error.dark\n : tone === 'muted' ? theme.palette.text.secondary\n : 'inherit';\n return (\n <Box\n component=\"span\"\n sx={{\n fontWeight: bold ? (size === 'big' ? 800 : 700) : 500,\n fontSize: size === 'big' ? '1.05rem' : 'inherit',\n fontVariantNumeric: 'tabular-nums',\n color,\n ...sx,\n }}\n >\n {effectivePrefix}{n.toFixed(2)}\n </Box>\n );\n}\nPmMoney.propTypes = {\n value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n fallback: PropTypes.node,\n size: PropTypes.oneOf(['normal', 'big']),\n tone: PropTypes.oneOf(['warning', 'success', 'error', 'muted']),\n bold: PropTypes.bool,\n prefix: PropTypes.string,\n sx: PropTypes.object,\n};\n\n// -------- Reference chip --------------------------------------------------\n\n/** Monospace `#1234` chip used in left-most \"Ref\" columns. */\nexport function PmRef({ id }) {\n return (\n <Box\n component=\"span\"\n sx={{\n display: 'inline-flex',\n alignItems: 'center',\n px: 0.85,\n py: 0.25,\n borderRadius: 1,\n backgroundColor: 'grey.100',\n color: 'text.secondary',\n fontFamily: 'monospace',\n fontSize: '0.72rem',\n fontWeight: 700,\n letterSpacing: 0.4,\n lineHeight: 1.5,\n }}\n >\n #{id}\n </Box>\n );\n}\nPmRef.propTypes = {\n id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),\n};\n\n// -------- Field helper text with character counter -----------------------\n\n/**\n * Helper-text content that pairs a message (validation error or hint) on the\n * left with a live \"remaining/allowed\" character counter on the right. Pass\n * as a TextField's `helperText`. Renders only spans so it's valid inside the\n * FormHelperText <p>.\n */\nexport function PmFieldHelp({ message, value, max }) {\n const used = (value ?? '').length;\n const remaining = Math.max(0, max - used);\n return (\n <Box component=\"span\" sx={{ display: 'flex', width: '100%', gap: 1 }}>\n <Box component=\"span\" sx={{ flexGrow: 1 }}>{message || ''}</Box>\n <Box\n component=\"span\"\n sx={{ flexShrink: 0, fontVariantNumeric: 'tabular-nums', color: 'text.secondary' }}\n >\n {remaining}/{max}\n </Box>\n </Box>\n );\n}\nPmFieldHelp.propTypes = {\n message: PropTypes.node,\n value: PropTypes.string,\n max: PropTypes.number.isRequired,\n};\n\n// -------- Select (dropdown) ----------------------------------------------\n\n/**\n * Standard PM dropdown. Wraps MUI Autocomplete (the app-wide dropdown\n * pattern, same as FacilityPicker / CustomDropdown) so every select in the\n * module looks and behaves identically and sizes like the themed text\n * fields. Options are plain `{ value, label }`; `value`/`onChange` work with\n * the raw value so callers don't deal with option objects.\n */\nexport function PmSelect({\n label,\n value,\n onChange,\n onBlur,\n options,\n required = false,\n disabled = false,\n error = false,\n helperText = '',\n fullWidth = true,\n size = 'small',\n noOptionsText,\n // Optional (option) => bool. Lets a caller show an option that cannot be\n // chosen right now instead of hiding it, so the reason stays visible.\n getOptionDisabled,\n sx = {},\n}) {\n const selected = options.find((o) => o.value === value) ?? null;\n return (\n <Autocomplete\n size={size}\n fullWidth={fullWidth}\n disabled={disabled}\n options={options}\n value={selected}\n onChange={(_, opt) => onChange(opt ? opt.value : '')}\n getOptionLabel={(o) => o?.label ?? ''}\n getOptionDisabled={getOptionDisabled}\n isOptionEqualToValue={(o, v) => o?.value === v?.value}\n disableClearable={required}\n noOptionsText={noOptionsText}\n sx={sx}\n renderInput={(params) => (\n <TextField\n {...params}\n label={label}\n required={required}\n error={error}\n helperText={helperText}\n onBlur={onBlur}\n />\n )}\n />\n );\n}\nPmSelect.propTypes = {\n label: PropTypes.node,\n value: PropTypes.any,\n onChange: PropTypes.func.isRequired,\n onBlur: PropTypes.func,\n options: PropTypes.arrayOf(PropTypes.shape({\n value: PropTypes.any,\n label: PropTypes.node,\n })).isRequired,\n required: PropTypes.bool,\n disabled: PropTypes.bool,\n error: PropTypes.bool,\n helperText: PropTypes.node,\n fullWidth: PropTypes.bool,\n size: PropTypes.string,\n noOptionsText: PropTypes.node,\n getOptionDisabled: PropTypes.func,\n sx: PropTypes.object,\n};\n\n// -------- Form section helper --------------------------------------------\n\n/**\n * Wraps a chunk of form fields with a consistent eyebrow + title + hint\n * header. Used inside create/edit pages so every section has the same\n * vertical rhythm.\n */\nexport function PmFormSection({ eyebrow, title, hint, children, sx = {} }) {\n return (\n <Box sx={sx}>\n {(eyebrow || title || hint) ? (\n <Box sx={{ mb: 1.75 }}>\n {eyebrow ? (\n <Typography\n variant=\"overline\"\n sx={{ color: 'text.secondary', fontWeight: 800, letterSpacing: 1, lineHeight: 1, display: 'block' }}\n >\n {eyebrow}\n </Typography>\n ) : null}\n {title ? (\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.25 }}>{title}</Typography>\n ) : null}\n {hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ display: 'block', mt: 0.5 }}>\n {hint}\n </Typography>\n ) : null}\n </Box>\n ) : null}\n {children}\n </Box>\n );\n}\nPmFormSection.propTypes = {\n eyebrow: PropTypes.node,\n title: PropTypes.node,\n hint: PropTypes.node,\n children: PropTypes.node,\n sx: PropTypes.object,\n};\n"],"mappings":"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"}