@argusoft/medplat-product-management 1.0.7 → 1.0.8
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.
- package/dist/{BrandsPage-CdqGFmGf.js → BrandsPage-DmRW5bhm.js} +77 -78
- package/dist/{BrandsPage-CdqGFmGf.js.map → BrandsPage-DmRW5bhm.js.map} +1 -1
- package/dist/{CategoriesPage-DJM0Nalw.js → CategoriesPage-BsS5IQh3.js} +6 -7
- package/dist/{CategoriesPage-DJM0Nalw.js.map → CategoriesPage-BsS5IQh3.js.map} +1 -1
- package/dist/{DashboardPage-BASkXr00.js → DashboardPage-C3bKc5na.js} +3 -3
- package/dist/{DashboardPage-BASkXr00.js.map → DashboardPage-C3bKc5na.js.map} +1 -1
- package/dist/{FacilityPicker-jV-qXp1k.js → FacilityPicker-BiZeupmP.js} +6 -8
- package/dist/{FacilityPicker-jV-qXp1k.js.map → FacilityPicker-BiZeupmP.js.map} +1 -1
- package/dist/{InventoryPage-CTdq6txa.js → InventoryPage-C_AxSy7A.js} +9 -12
- package/dist/{InventoryPage-CTdq6txa.js.map → InventoryPage-C_AxSy7A.js.map} +1 -1
- package/dist/{MemberProfileCard-DwWda-tS.js → MemberProfileCard-BdUNgHRB.js} +3 -3
- package/dist/{MemberProfileCard-DwWda-tS.js.map → MemberProfileCard-BdUNgHRB.js.map} +1 -1
- package/dist/{PaymentCreatePage-B3bP-K01.js → PaymentCreatePage-CIVe0P0p.js} +114 -114
- package/dist/{PaymentCreatePage-B3bP-K01.js.map → PaymentCreatePage-CIVe0P0p.js.map} +1 -1
- package/dist/{PaymentsPage-Dh8bqXVE.js → PaymentsPage-BvPe5Q9F.js} +4 -4
- package/dist/{PaymentsPage-Dh8bqXVE.js.map → PaymentsPage-BvPe5Q9F.js.map} +1 -1
- package/dist/{PickupBatchLinesEditor-B2fLx1lU.js → PickupBatchLinesEditor-DHmcAG23.js} +2 -2
- package/dist/{PickupBatchLinesEditor-B2fLx1lU.js.map → PickupBatchLinesEditor-DHmcAG23.js.map} +1 -1
- package/dist/{PickupCreatePage-BeYhR45k.js → PickupCreatePage-CB0Sl0Hb.js} +58 -58
- package/dist/PickupCreatePage-CB0Sl0Hb.js.map +1 -0
- package/dist/{PickupsPage-C6lBtAoQ.js → PickupsPage-Ctpghavs.js} +7 -9
- package/dist/{PickupsPage-C6lBtAoQ.js.map → PickupsPage-Ctpghavs.js.map} +1 -1
- package/dist/{PmGuard-DrIl1sHL.js → PmGuard-b10TGXEi.js} +3 -3
- package/dist/{PmGuard-DrIl1sHL.js.map → PmGuard-b10TGXEi.js.map} +1 -1
- package/dist/{PmTable-BSV_bMJZ.js → PmTable-_GbScaa4.js} +3 -4
- package/dist/{PmTable-BSV_bMJZ.js.map → PmTable-_GbScaa4.js.map} +1 -1
- package/dist/{ProductEditPage-DZTUH95T.js → ProductEditPage-C1thoKHu.js} +8 -9
- package/dist/{ProductEditPage-DZTUH95T.js.map → ProductEditPage-C1thoKHu.js.map} +1 -1
- package/dist/{ProductsPage-CqOsah4_.js → ProductsPage-CupVmOD4.js} +4 -4
- package/dist/{ProductsPage-CqOsah4_.js.map → ProductsPage-CupVmOD4.js.map} +1 -1
- package/dist/{PurchaseCreatePage-D6os7srp.js → PurchaseCreatePage-YD3NmhPQ.js} +6 -6
- package/dist/{PurchaseCreatePage-D6os7srp.js.map → PurchaseCreatePage-YD3NmhPQ.js.map} +1 -1
- package/dist/{PurchaseRequestDetailPage-DErqGKiq.js → PurchaseRequestDetailPage-Dg_MRY_N.js} +6 -7
- package/dist/{PurchaseRequestDetailPage-DErqGKiq.js.map → PurchaseRequestDetailPage-Dg_MRY_N.js.map} +1 -1
- package/dist/{PurchaseRequestsPage-CHBoRzzH.js → PurchaseRequestsPage-DPAPzpmm.js} +4 -4
- package/dist/{PurchaseRequestsPage-CHBoRzzH.js.map → PurchaseRequestsPage-DPAPzpmm.js.map} +1 -1
- package/dist/{PurchasesPage-C-2K7wH6.js → PurchasesPage-It1GHiXl.js} +4 -4
- package/dist/{PurchasesPage-C-2K7wH6.js.map → PurchasesPage-It1GHiXl.js.map} +1 -1
- package/dist/{SaleCreatePage-DB3_UEgy.js → SaleCreatePage-C9kFmTvm.js} +20 -20
- package/dist/{SaleCreatePage-DB3_UEgy.js.map → SaleCreatePage-C9kFmTvm.js.map} +1 -1
- package/dist/{SalesPage-BhBGG426.js → SalesPage-D18DCseD.js} +74 -74
- package/dist/{SalesPage-BhBGG426.js.map → SalesPage-D18DCseD.js.map} +1 -1
- package/dist/{SettingsPage-CBUu9DZK.js → SettingsPage-VsxI-ano.js} +4 -4
- package/dist/{SettingsPage-CBUu9DZK.js.map → SettingsPage-VsxI-ano.js.map} +1 -1
- package/dist/{StorageUnitsPage-oe0lXLd1.js → StorageUnitsPage-7EqwuMOI.js} +7 -8
- package/dist/{StorageUnitsPage-oe0lXLd1.js.map → StorageUnitsPage-7EqwuMOI.js.map} +1 -1
- package/dist/{TransactionsPage-7dRZpZOD.js → TransactionsPage-DrH53pVS.js} +4 -4
- package/dist/{TransactionsPage-7dRZpZOD.js.map → TransactionsPage-DrH53pVS.js.map} +1 -1
- package/dist/{TransferCreatePage-DtkAlP9l.js → TransferCreatePage-CuvoMrYH.js} +4 -4
- package/dist/{TransferCreatePage-DtkAlP9l.js.map → TransferCreatePage-CuvoMrYH.js.map} +1 -1
- package/dist/{TransfersPage-BaEjbvnj.js → TransfersPage-Cf701t8s.js} +4 -4
- package/dist/{TransfersPage-BaEjbvnj.js.map → TransfersPage-Cf701t8s.js.map} +1 -1
- package/dist/{VialsPage-vo9Of9oC.js → VialsPage-8oAc1yFv.js} +5 -5
- package/dist/{VialsPage-vo9Of9oC.js.map → VialsPage-8oAc1yFv.js.map} +1 -1
- package/dist/constants-ClZJY2x1.js.map +1 -1
- package/dist/format-UOyOu75i.js.map +1 -1
- package/dist/index.js +253 -240
- package/dist/index.js.map +1 -1
- package/dist/{settingsStore-BlA2GIhl.js → settingsStore-BifOsjlB.js} +2 -2
- package/dist/{settingsStore-BlA2GIhl.js.map → settingsStore-BifOsjlB.js.map} +1 -1
- package/dist/{validation-B6Kl5Xf9.js → validation-BzlHnL-r.js} +16 -15
- package/dist/{validation-B6Kl5Xf9.js.map → validation-BzlHnL-r.js.map} +1 -1
- package/package.json +1 -1
- package/dist/PickupCreatePage-BeYhR45k.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { H as e, L as t, R as n, T as r, W as i, _ as a, g as o, i as s, j as c, r as l } from "./settingsStore-
|
|
1
|
+
import { H as e, L as t, R as n, T as r, W as i, _ as a, g as o, i as s, j as c, r as l } from "./settingsStore-BifOsjlB.js";
|
|
2
2
|
import { n as u, r as d } from "./format-UOyOu75i.js";
|
|
3
|
-
import { a as f, i as p, n as m, o as h, r as g, t as _ } from "./PmGuard-
|
|
3
|
+
import { a as f, i as p, n as m, o as h, r as g, t as _ } from "./PmGuard-b10TGXEi.js";
|
|
4
4
|
import { useState as v } from "react";
|
|
5
5
|
import { jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
6
6
|
import x from "prop-types";
|
|
@@ -433,4 +433,4 @@ function z() {
|
|
|
433
433
|
//#endregion
|
|
434
434
|
export { z as default };
|
|
435
435
|
|
|
436
|
-
//# sourceMappingURL=DashboardPage-
|
|
436
|
+
//# sourceMappingURL=DashboardPage-C3bKc5na.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage-BASkXr00.js","names":[],"sources":["../src/views/DashboardPage.jsx"],"sourcesContent":["import { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Box,\n Card,\n CardContent,\n Chip,\n Grid,\n Grow,\n LinearProgress,\n Skeleton,\n Stack,\n Typography,\n} from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport ArrowForwardRoundedIcon from '@mui/icons-material/ArrowForwardRounded';\nimport {\n getCategories,\n getBrands,\n getProducts,\n listBatches,\n listOpenVials,\n listSales,\n listPayments,\n listTransactions,\n} from '@pm/services/pmApi';\nimport PmPageShell from '@pm/components/PmPageShell';\nimport PmNav from '@pm/components/PmNav';\nimport PmGuard from '@pm/components/PmGuard';\nimport { getVisibleWorkspaces } from '@pm/utils/workspaces';\nimport { usePermissionedEffect, usePmPermissions } from '@pm/utils/permissions';\nimport { formatDateTime, formatNumber } from '@pm/utils/format';\nimport { currencySymbol } from '@pm/utils/currency';\nimport { usePmSettings } from '@pm/utils/settingsStore';\n\n// Color hint per ledger transaction type — keeps the recent-activity feed\n// glanceable.\nconst TXN_COLOR = {\n RECEIPT: 'success',\n SALE: 'primary',\n PAYMENT: 'info',\n VIAL_OPENED: 'info',\n ADMINISTRATION: 'info',\n WASTAGE: 'warning',\n EXPIRY: 'error',\n QUARANTINE: 'error',\n RECALL: 'error',\n UNQUARANTINE: 'success',\n TRANSFER_OUT: 'default',\n TRANSFER_IN: 'default',\n};\n\nfunction MetricCell({ label, value, loading }) {\n return (\n <Box>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ fontWeight: 700, letterSpacing: 0.5 }}>\n {label.toUpperCase()}\n </Typography>\n {loading ? (\n <Skeleton width={48} height={32} />\n ) : (\n <Typography variant=\"h5\" sx={{ fontWeight: 800, fontVariantNumeric: 'tabular-nums', lineHeight: 1.1, mt: 0.25 }}>\n {value}\n </Typography>\n )}\n </Box>\n );\n}\nMetricCell.propTypes = {\n label: PropTypes.string,\n value: PropTypes.node,\n loading: PropTypes.bool,\n};\n\nfunction WorkspaceCard({ workspace, metrics, loading, onItemClick }) {\n const theme = useTheme();\n const Icon = workspace.icon;\n\n return (\n <Card\n sx={{\n borderRadius: 1.5,\n height: '100%',\n position: 'relative',\n overflow: 'hidden',\n border: `1px solid ${alpha(workspace.accent, 0.18)}`,\n background: `linear-gradient(180deg, ${alpha(workspace.accent, 0.06)} 0%, ${alpha(theme.palette.background.paper, 1)} 35%)`,\n boxShadow: `0 16px 36px ${alpha(workspace.accent, 0.08)}`,\n transition: 'transform 240ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 240ms',\n '&:hover': {\n transform: 'translateY(-3px)',\n boxShadow: `0 22px 50px ${alpha(workspace.accent, 0.18)}`,\n },\n // Accent strip across the very top.\n '&::before': {\n content: '\"\"',\n position: 'absolute',\n left: 0,\n right: 0,\n top: 0,\n height: 4,\n background: `linear-gradient(90deg, ${workspace.accent} 0%, ${workspace.accentDark} 100%)`,\n },\n }}\n >\n <CardContent sx={{ pt: 3, display: 'flex', flexDirection: 'column', height: '100%' }}>\n <Stack direction=\"row\" alignItems=\"center\" spacing={1.5}>\n <Box\n sx={{\n width: 44,\n height: 44,\n borderRadius: 1.5,\n display: 'grid',\n placeItems: 'center',\n color: '#fff',\n background: `linear-gradient(135deg, ${workspace.accent} 0%, ${workspace.accentDark} 100%)`,\n boxShadow: `0 8px 18px ${alpha(workspace.accent, 0.3)}`,\n }}\n >\n <Icon sx={{ fontSize: 24 }} />\n </Box>\n <Box sx={{ flexGrow: 1, minWidth: 0 }}>\n <Typography variant=\"overline\" sx={{ color: workspace.accentDark, fontWeight: 800, letterSpacing: 1.1, lineHeight: 1 }}>\n Workspace\n </Typography>\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.2 }}>\n {workspace.label}\n </Typography>\n </Box>\n </Stack>\n\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ mt: 1.5, mb: 2 }}>\n {workspace.description}\n </Typography>\n\n {/* Quick stats */}\n {metrics?.length ? (\n <Stack direction=\"row\" spacing={3} sx={{ mb: 2.5 }}>\n {metrics.map((m) => (\n <MetricCell key={m.label} label={m.label} value={m.value} loading={loading} />\n ))}\n </Stack>\n ) : null}\n\n {/* Sub-item quick access — clickable chips */}\n <Box sx={{ mt: 'auto' }}>\n <Stack direction=\"row\" spacing={1} flexWrap=\"wrap\" useFlexGap>\n {workspace.items.map((it) => {\n const ItemIcon = it.icon;\n return (\n <Chip\n key={it.id}\n icon={ItemIcon ? <ItemIcon sx={{ fontSize: '1rem !important', color: `${workspace.accentDark} !important` }} /> : undefined}\n label={it.label}\n onClick={() => onItemClick(it)}\n sx={{\n borderRadius: 2,\n fontWeight: 700,\n backgroundColor: alpha(workspace.accent, 0.08),\n color: workspace.accentDark,\n border: `1px solid ${alpha(workspace.accent, 0.18)}`,\n transition: 'all 180ms ease',\n '&:hover': {\n backgroundColor: alpha(workspace.accent, 0.18),\n transform: 'translateY(-1px)',\n },\n }}\n />\n );\n })}\n </Stack>\n </Box>\n </CardContent>\n </Card>\n );\n}\nWorkspaceCard.propTypes = {\n workspace: PropTypes.object.isRequired,\n metrics: PropTypes.array,\n loading: PropTypes.bool,\n onItemClick: PropTypes.func.isRequired,\n};\n\nexport default function DashboardPage() {\n const navigate = useNavigate();\n const theme = useTheme();\n const settings = usePmSettings();\n const perms = usePmPermissions();\n const cur = currencySymbol(settings?.currencyCode);\n // Same filter PmNav uses — only workspaces with at least one accessible\n // (perm-granted, not-hidden-by-settings) item show as cards.\n const visibleWorkspaces = getVisibleWorkspaces(perms, settings);\n const showLedgerActivity = perms.has('view.manage.productManagement.ledger');\n const [loading, setLoading] = useState(true);\n const [data, setData] = useState({\n categories: 0, brands: 0, products: 0,\n batches: 0, openVials: 0, sales: 0, payments: 0,\n txns: [],\n });\n\n async function load() {\n setLoading(true);\n try {\n // Each fetch is gated by its own view perm. A user with only the\n // ledger perm fires the transactions call alone — every other tile\n // stays at zero and the page never touches the corresponding API.\n // .catch returns [] so a backend 403 (e.g. perm just revoked) still\n // renders the dashboard with zeroes rather than crashing.\n const gate = (perm, fn) => perms.has(perm) ? fn().catch(() => []) : Promise.resolve([]);\n const [cats, brands, products, batches, vials, sales, payments, txns] = await Promise.all([\n gate('view.manage.productManagement.category', () => getCategories()),\n gate('view.manage.productManagement.brand', () => getBrands()),\n gate('view.manage.productManagement.product', () => getProducts()),\n gate('view.manage.productManagement.inventory', () => listBatches()),\n gate('view.manage.productManagement.vial', () => listOpenVials()),\n gate('view.manage.productManagement.sale', () => listSales(0, 100)),\n gate('view.manage.productManagement.payment', () => listPayments(0, 100)),\n gate('view.manage.productManagement.ledger', () => listTransactions(0, 8)),\n ]);\n setData({\n categories: cats.length,\n brands: brands.length,\n products: products.length,\n batches: batches.length,\n openVials: vials.length,\n sales: sales.length,\n payments: payments.length,\n txns,\n });\n } finally {\n setLoading(false);\n }\n }\n\n // Root module perm gates the dashboard's entire fetch pass; each call\n // inside `load()` does its own per-feature perm check on top.\n usePermissionedEffect('view.manage.productManagement', () => {\n void load();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const metricsByWorkspace = {\n catalog: [\n { label: 'Categories', value: data.categories },\n { label: 'Brands', value: data.brands },\n { label: 'Products', value: data.products },\n ],\n operations: [\n { label: 'Batches', value: data.batches },\n { label: 'Open vials', value: data.openVials },\n { label: 'Sales', value: data.sales },\n ],\n ledger: [\n { label: 'Recent', value: data.txns.length },\n ],\n };\n\n return (\n <Box>\n <PmNav />\n <PmGuard perm={'view.manage.productManagement'}>\n <PmPageShell\n title=\"Product Management\"\n subtitle=\"Choose where to start. Use Catalog to set up, Operations for daily work, and Ledger to review history.\"\n chips={['Generic', 'Vaccine-aware', 'Ledger-driven']}\n >\n {loading ? <LinearProgress sx={{ mb: 2, borderRadius: 1 }} /> : null}\n\n <Grid container spacing={2.5}>\n {visibleWorkspaces.map((ws, idx) => (\n <Grid item xs={12} md={4} key={ws.id}>\n <Grow in timeout={420} style={{ transitionDelay: `${idx * 80}ms` }}>\n <Box sx={{ height: '100%' }}>\n <WorkspaceCard\n workspace={ws}\n metrics={metricsByWorkspace[ws.id]}\n loading={loading}\n onItemClick={(it) => navigate(it.path)}\n />\n </Box>\n </Grow>\n </Grid>\n ))}\n </Grid>\n\n {/* Recent stock activity — last 8 ledger entries with semantic colour.\n Hidden when the user can't access the ledger; the underlying\n transactions API would 403 anyway. */}\n {showLedgerActivity ? (\n <Card sx={{ borderRadius: 1.5, mt: 3, boxShadow: `0 12px 32px ${alpha(theme.palette.common.black, 0.05)}` }}>\n <CardContent>\n <Stack direction=\"row\" justifyContent=\"space-between\" alignItems=\"center\" sx={{ mb: 1.5 }}>\n <Box>\n <Typography variant=\"overline\" sx={{ color: 'text.secondary', fontWeight: 800, letterSpacing: 1 }}>\n Stock Ledger\n </Typography>\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.2 }}>\n Recent activity\n </Typography>\n </Box>\n <Chip\n clickable\n icon={<ArrowForwardRoundedIcon sx={{ fontSize: '1rem !important' }} />}\n label=\"View ledger\"\n onClick={() => navigate('/ui/medplat/manage/pm/transactions')}\n sx={{\n borderRadius: 2, fontWeight: 700,\n bgcolor: alpha(theme.palette.primary.main, 0.08),\n color: 'primary.dark',\n '&:hover': { bgcolor: alpha(theme.palette.primary.main, 0.16) },\n }}\n />\n </Stack>\n\n {data.txns.length === 0 && !loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">No activity yet — record a purchase or a sale to see entries here.</Typography>\n ) : (\n <Stack divider={<Box sx={{ borderTop: `1px dashed ${theme.palette.divider}` }} />} spacing={0}>\n {(loading ? Array.from({ length: 4 }) : data.txns).map((t, i) => (\n <Box key={t?.id ?? i} sx={{\n display: 'flex', justifyContent: 'space-between', alignItems: 'center',\n py: 1.25, gap: 2,\n }}>\n <Stack direction=\"row\" alignItems=\"center\" spacing={1.5} sx={{ minWidth: 0 }}>\n {loading ? (\n <Skeleton variant=\"rounded\" width={92} height={22} />\n ) : (\n <Chip\n size=\"small\"\n label={t.transactionType}\n color={TXN_COLOR[t.transactionType] || 'default'}\n sx={{ fontWeight: 700, borderRadius: 1.5, height: 22 }}\n />\n )}\n <Box sx={{ minWidth: 0 }}>\n {loading ? (\n <Skeleton width={220} height={20} />\n ) : (\n <Typography variant=\"body2\" sx={{ fontWeight: 700, lineHeight: 1.2 }} noWrap>\n {t.productName || '—'}\n {t.lotNumber ? (\n <Typography component=\"span\" variant=\"body2\" color=\"text.secondary\" sx={{ fontFamily: 'monospace', ml: 1 }}>\n lot {t.lotNumber}\n </Typography>\n ) : null}\n </Typography>\n )}\n {loading ? (\n <Skeleton width={120} height={16} />\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {formatDateTime(t.transactionTime)}\n </Typography>\n )}\n </Box>\n </Stack>\n\n <Box sx={{ textAlign: 'right', whiteSpace: 'nowrap' }}>\n {loading ? (\n <Skeleton width={70} height={20} />\n ) : (\n <Typography variant=\"body2\" sx={{ fontWeight: 800, fontVariantNumeric: 'tabular-nums' }}>\n {t.quantity != null ? `qty ${t.quantity}` : ''}\n {t.volumeMl != null ? `${t.quantity != null ? ' · ' : ''}${formatNumber(t.volumeMl)} ml` : ''}\n {t.monetaryAmount != null ? `${(t.quantity != null || t.volumeMl != null) ? ' · ' : ''}${cur}${formatNumber(t.monetaryAmount)}` : ''}\n </Typography>\n )}\n </Box>\n </Box>\n ))}\n </Stack>\n )}\n </CardContent>\n </Card>\n ) : null}\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardPage-C3bKc5na.js","names":[],"sources":["../src/views/DashboardPage.jsx"],"sourcesContent":["import { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Box,\n Card,\n CardContent,\n Chip,\n Grid,\n Grow,\n LinearProgress,\n Skeleton,\n Stack,\n Typography,\n} from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport ArrowForwardRoundedIcon from '@mui/icons-material/ArrowForwardRounded';\nimport {\n getCategories,\n getBrands,\n getProducts,\n listBatches,\n listOpenVials,\n listSales,\n listPayments,\n listTransactions,\n} from '@pm/services/pmApi';\nimport PmPageShell from '@pm/components/PmPageShell';\nimport PmNav from '@pm/components/PmNav';\nimport PmGuard from '@pm/components/PmGuard';\nimport { getVisibleWorkspaces } from '@pm/utils/workspaces';\nimport { usePermissionedEffect, usePmPermissions } from '@pm/utils/permissions';\nimport { formatDateTime, formatNumber } from '@pm/utils/format';\nimport { currencySymbol } from '@pm/utils/currency';\nimport { usePmSettings } from '@pm/utils/settingsStore';\n\n// Color hint per ledger transaction type — keeps the recent-activity feed\n// glanceable.\nconst TXN_COLOR = {\n RECEIPT: 'success',\n SALE: 'primary',\n PAYMENT: 'info',\n VIAL_OPENED: 'info',\n ADMINISTRATION: 'info',\n WASTAGE: 'warning',\n EXPIRY: 'error',\n QUARANTINE: 'error',\n RECALL: 'error',\n UNQUARANTINE: 'success',\n TRANSFER_OUT: 'default',\n TRANSFER_IN: 'default',\n};\n\nfunction MetricCell({ label, value, loading }) {\n return (\n <Box>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ fontWeight: 700, letterSpacing: 0.5 }}>\n {label.toUpperCase()}\n </Typography>\n {loading ? (\n <Skeleton width={48} height={32} />\n ) : (\n <Typography variant=\"h5\" sx={{ fontWeight: 800, fontVariantNumeric: 'tabular-nums', lineHeight: 1.1, mt: 0.25 }}>\n {value}\n </Typography>\n )}\n </Box>\n );\n}\nMetricCell.propTypes = {\n label: PropTypes.string,\n value: PropTypes.node,\n loading: PropTypes.bool,\n};\n\nfunction WorkspaceCard({ workspace, metrics, loading, onItemClick }) {\n const theme = useTheme();\n const Icon = workspace.icon;\n\n return (\n <Card\n sx={{\n borderRadius: 1.5,\n height: '100%',\n position: 'relative',\n overflow: 'hidden',\n border: `1px solid ${alpha(workspace.accent, 0.18)}`,\n background: `linear-gradient(180deg, ${alpha(workspace.accent, 0.06)} 0%, ${alpha(theme.palette.background.paper, 1)} 35%)`,\n boxShadow: `0 16px 36px ${alpha(workspace.accent, 0.08)}`,\n transition: 'transform 240ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 240ms',\n '&:hover': {\n transform: 'translateY(-3px)',\n boxShadow: `0 22px 50px ${alpha(workspace.accent, 0.18)}`,\n },\n // Accent strip across the very top.\n '&::before': {\n content: '\"\"',\n position: 'absolute',\n left: 0,\n right: 0,\n top: 0,\n height: 4,\n background: `linear-gradient(90deg, ${workspace.accent} 0%, ${workspace.accentDark} 100%)`,\n },\n }}\n >\n <CardContent sx={{ pt: 3, display: 'flex', flexDirection: 'column', height: '100%' }}>\n <Stack direction=\"row\" alignItems=\"center\" spacing={1.5}>\n <Box\n sx={{\n width: 44,\n height: 44,\n borderRadius: 1.5,\n display: 'grid',\n placeItems: 'center',\n color: '#fff',\n background: `linear-gradient(135deg, ${workspace.accent} 0%, ${workspace.accentDark} 100%)`,\n boxShadow: `0 8px 18px ${alpha(workspace.accent, 0.3)}`,\n }}\n >\n <Icon sx={{ fontSize: 24 }} />\n </Box>\n <Box sx={{ flexGrow: 1, minWidth: 0 }}>\n <Typography variant=\"overline\" sx={{ color: workspace.accentDark, fontWeight: 800, letterSpacing: 1.1, lineHeight: 1 }}>\n Workspace\n </Typography>\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.2 }}>\n {workspace.label}\n </Typography>\n </Box>\n </Stack>\n\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ mt: 1.5, mb: 2 }}>\n {workspace.description}\n </Typography>\n\n {/* Quick stats */}\n {metrics?.length ? (\n <Stack direction=\"row\" spacing={3} sx={{ mb: 2.5 }}>\n {metrics.map((m) => (\n <MetricCell key={m.label} label={m.label} value={m.value} loading={loading} />\n ))}\n </Stack>\n ) : null}\n\n {/* Sub-item quick access — clickable chips */}\n <Box sx={{ mt: 'auto' }}>\n <Stack direction=\"row\" spacing={1} flexWrap=\"wrap\" useFlexGap>\n {workspace.items.map((it) => {\n const ItemIcon = it.icon;\n return (\n <Chip\n key={it.id}\n icon={ItemIcon ? <ItemIcon sx={{ fontSize: '1rem !important', color: `${workspace.accentDark} !important` }} /> : undefined}\n label={it.label}\n onClick={() => onItemClick(it)}\n sx={{\n borderRadius: 2,\n fontWeight: 700,\n backgroundColor: alpha(workspace.accent, 0.08),\n color: workspace.accentDark,\n border: `1px solid ${alpha(workspace.accent, 0.18)}`,\n transition: 'all 180ms ease',\n '&:hover': {\n backgroundColor: alpha(workspace.accent, 0.18),\n transform: 'translateY(-1px)',\n },\n }}\n />\n );\n })}\n </Stack>\n </Box>\n </CardContent>\n </Card>\n );\n}\nWorkspaceCard.propTypes = {\n workspace: PropTypes.object.isRequired,\n metrics: PropTypes.array,\n loading: PropTypes.bool,\n onItemClick: PropTypes.func.isRequired,\n};\n\nexport default function DashboardPage() {\n const navigate = useNavigate();\n const theme = useTheme();\n const settings = usePmSettings();\n const perms = usePmPermissions();\n const cur = currencySymbol(settings?.currencyCode);\n // Same filter PmNav uses — only workspaces with at least one accessible\n // (perm-granted, not-hidden-by-settings) item show as cards.\n const visibleWorkspaces = getVisibleWorkspaces(perms, settings);\n const showLedgerActivity = perms.has('view.manage.productManagement.ledger');\n const [loading, setLoading] = useState(true);\n const [data, setData] = useState({\n categories: 0, brands: 0, products: 0,\n batches: 0, openVials: 0, sales: 0, payments: 0,\n txns: [],\n });\n\n async function load() {\n setLoading(true);\n try {\n // Each fetch is gated by its own view perm. A user with only the\n // ledger perm fires the transactions call alone — every other tile\n // stays at zero and the page never touches the corresponding API.\n // .catch returns [] so a backend 403 (e.g. perm just revoked) still\n // renders the dashboard with zeroes rather than crashing.\n const gate = (perm, fn) => perms.has(perm) ? fn().catch(() => []) : Promise.resolve([]);\n const [cats, brands, products, batches, vials, sales, payments, txns] = await Promise.all([\n gate('view.manage.productManagement.category', () => getCategories()),\n gate('view.manage.productManagement.brand', () => getBrands()),\n gate('view.manage.productManagement.product', () => getProducts()),\n gate('view.manage.productManagement.inventory', () => listBatches()),\n gate('view.manage.productManagement.vial', () => listOpenVials()),\n gate('view.manage.productManagement.sale', () => listSales(0, 100)),\n gate('view.manage.productManagement.payment', () => listPayments(0, 100)),\n gate('view.manage.productManagement.ledger', () => listTransactions(0, 8)),\n ]);\n setData({\n categories: cats.length,\n brands: brands.length,\n products: products.length,\n batches: batches.length,\n openVials: vials.length,\n sales: sales.length,\n payments: payments.length,\n txns,\n });\n } finally {\n setLoading(false);\n }\n }\n\n // Root module perm gates the dashboard's entire fetch pass; each call\n // inside `load()` does its own per-feature perm check on top.\n usePermissionedEffect('view.manage.productManagement', () => {\n void load();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n const metricsByWorkspace = {\n catalog: [\n { label: 'Categories', value: data.categories },\n { label: 'Brands', value: data.brands },\n { label: 'Products', value: data.products },\n ],\n operations: [\n { label: 'Batches', value: data.batches },\n { label: 'Open vials', value: data.openVials },\n { label: 'Sales', value: data.sales },\n ],\n ledger: [\n { label: 'Recent', value: data.txns.length },\n ],\n };\n\n return (\n <Box>\n <PmNav />\n <PmGuard perm={'view.manage.productManagement'}>\n <PmPageShell\n title=\"Product Management\"\n subtitle=\"Choose where to start. Use Catalog to set up, Operations for daily work, and Ledger to review history.\"\n chips={['Generic', 'Vaccine-aware', 'Ledger-driven']}\n >\n {loading ? <LinearProgress sx={{ mb: 2, borderRadius: 1 }} /> : null}\n\n <Grid container spacing={2.5}>\n {visibleWorkspaces.map((ws, idx) => (\n <Grid item xs={12} md={4} key={ws.id}>\n <Grow in timeout={420} style={{ transitionDelay: `${idx * 80}ms` }}>\n <Box sx={{ height: '100%' }}>\n <WorkspaceCard\n workspace={ws}\n metrics={metricsByWorkspace[ws.id]}\n loading={loading}\n onItemClick={(it) => navigate(it.path)}\n />\n </Box>\n </Grow>\n </Grid>\n ))}\n </Grid>\n\n {/* Recent stock activity — last 8 ledger entries with semantic colour.\n Hidden when the user can't access the ledger; the underlying\n transactions API would 403 anyway. */}\n {showLedgerActivity ? (\n <Card sx={{ borderRadius: 1.5, mt: 3, boxShadow: `0 12px 32px ${alpha(theme.palette.common.black, 0.05)}` }}>\n <CardContent>\n <Stack direction=\"row\" justifyContent=\"space-between\" alignItems=\"center\" sx={{ mb: 1.5 }}>\n <Box>\n <Typography variant=\"overline\" sx={{ color: 'text.secondary', fontWeight: 800, letterSpacing: 1 }}>\n Stock Ledger\n </Typography>\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.2 }}>\n Recent activity\n </Typography>\n </Box>\n <Chip\n clickable\n icon={<ArrowForwardRoundedIcon sx={{ fontSize: '1rem !important' }} />}\n label=\"View ledger\"\n onClick={() => navigate('/ui/medplat/manage/pm/transactions')}\n sx={{\n borderRadius: 2, fontWeight: 700,\n bgcolor: alpha(theme.palette.primary.main, 0.08),\n color: 'primary.dark',\n '&:hover': { bgcolor: alpha(theme.palette.primary.main, 0.16) },\n }}\n />\n </Stack>\n\n {data.txns.length === 0 && !loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">No activity yet — record a purchase or a sale to see entries here.</Typography>\n ) : (\n <Stack divider={<Box sx={{ borderTop: `1px dashed ${theme.palette.divider}` }} />} spacing={0}>\n {(loading ? Array.from({ length: 4 }) : data.txns).map((t, i) => (\n <Box key={t?.id ?? i} sx={{\n display: 'flex', justifyContent: 'space-between', alignItems: 'center',\n py: 1.25, gap: 2,\n }}>\n <Stack direction=\"row\" alignItems=\"center\" spacing={1.5} sx={{ minWidth: 0 }}>\n {loading ? (\n <Skeleton variant=\"rounded\" width={92} height={22} />\n ) : (\n <Chip\n size=\"small\"\n label={t.transactionType}\n color={TXN_COLOR[t.transactionType] || 'default'}\n sx={{ fontWeight: 700, borderRadius: 1.5, height: 22 }}\n />\n )}\n <Box sx={{ minWidth: 0 }}>\n {loading ? (\n <Skeleton width={220} height={20} />\n ) : (\n <Typography variant=\"body2\" sx={{ fontWeight: 700, lineHeight: 1.2 }} noWrap>\n {t.productName || '—'}\n {t.lotNumber ? (\n <Typography component=\"span\" variant=\"body2\" color=\"text.secondary\" sx={{ fontFamily: 'monospace', ml: 1 }}>\n lot {t.lotNumber}\n </Typography>\n ) : null}\n </Typography>\n )}\n {loading ? (\n <Skeleton width={120} height={16} />\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {formatDateTime(t.transactionTime)}\n </Typography>\n )}\n </Box>\n </Stack>\n\n <Box sx={{ textAlign: 'right', whiteSpace: 'nowrap' }}>\n {loading ? (\n <Skeleton width={70} height={20} />\n ) : (\n <Typography variant=\"body2\" sx={{ fontWeight: 800, fontVariantNumeric: 'tabular-nums' }}>\n {t.quantity != null ? `qty ${t.quantity}` : ''}\n {t.volumeMl != null ? `${t.quantity != null ? ' · ' : ''}${formatNumber(t.volumeMl)} ml` : ''}\n {t.monetaryAmount != null ? `${(t.quantity != null || t.volumeMl != null) ? ' · ' : ''}${cur}${formatNumber(t.monetaryAmount)}` : ''}\n </Typography>\n )}\n </Box>\n </Box>\n ))}\n </Stack>\n )}\n </CardContent>\n </Card>\n ) : null}\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { P as e, v as t } from "./settingsStore-
|
|
1
|
+
import { P as e, v as t } from "./settingsStore-BifOsjlB.js";
|
|
2
2
|
import { useEffect as n, useMemo as r, useState as i } from "react";
|
|
3
3
|
import { Fragment as a, jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
4
4
|
import { Autocomplete as c, CircularProgress as l, TextField as u } from "@mui/material";
|
|
5
5
|
//#region src/components/FacilityPicker.jsx
|
|
6
6
|
function d({ value: d, onChange: f, label: p = "Facility", required: m = !1, disabled: h = !1, error: g = !1, helperText: _ = "", onBlur: v, size: y = "small", fullWidth: b = !0 }) {
|
|
7
7
|
let [x, S] = i(!1), [C, w] = i([]), [T, E] = i(!1), [D, O] = i(!1), [k, A] = i(null);
|
|
8
|
-
n(() => {
|
|
8
|
+
return n(() => {
|
|
9
9
|
if (!d) {
|
|
10
10
|
A(null);
|
|
11
11
|
return;
|
|
@@ -40,9 +40,7 @@ function d({ value: d, onChange: f, label: p = "Facility", required: m = !1, dis
|
|
|
40
40
|
})(), () => {
|
|
41
41
|
t = !0;
|
|
42
42
|
};
|
|
43
|
-
}, [x, D])
|
|
44
|
-
let j = r(() => !k || C.some((e) => e.id === k.id) ? C : [k, ...C], [C, k]), M = T ? "Loading…" : D && C.length === 0 ? "No facilities assigned to your account" : "No matches";
|
|
45
|
-
return /* @__PURE__ */ o(c, {
|
|
43
|
+
}, [x, D]), /* @__PURE__ */ o(c, {
|
|
46
44
|
size: y,
|
|
47
45
|
fullWidth: b,
|
|
48
46
|
disabled: h,
|
|
@@ -52,12 +50,12 @@ function d({ value: d, onChange: f, label: p = "Facility", required: m = !1, dis
|
|
|
52
50
|
value: k,
|
|
53
51
|
isOptionEqualToValue: (e, t) => e?.id === t?.id,
|
|
54
52
|
getOptionLabel: (e) => e?.name ?? "",
|
|
55
|
-
options:
|
|
53
|
+
options: r(() => !k || C.some((e) => e.id === k.id) ? C : [k, ...C], [C, k]),
|
|
56
54
|
loading: T,
|
|
57
55
|
onChange: (e, t) => {
|
|
58
56
|
A(t ?? null), f?.(t?.id ?? "");
|
|
59
57
|
},
|
|
60
|
-
noOptionsText:
|
|
58
|
+
noOptionsText: T ? "Loading…" : D && C.length === 0 ? "No facilities assigned to your account" : "No matches",
|
|
61
59
|
renderInput: (e) => /* @__PURE__ */ o(u, {
|
|
62
60
|
...e,
|
|
63
61
|
required: m,
|
|
@@ -78,4 +76,4 @@ function d({ value: d, onChange: f, label: p = "Facility", required: m = !1, dis
|
|
|
78
76
|
//#endregion
|
|
79
77
|
export { d as t };
|
|
80
78
|
|
|
81
|
-
//# sourceMappingURL=FacilityPicker-
|
|
79
|
+
//# sourceMappingURL=FacilityPicker-BiZeupmP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FacilityPicker-
|
|
1
|
+
{"version":3,"file":"FacilityPicker-BiZeupmP.js","names":[],"sources":["../src/components/FacilityPicker.jsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Autocomplete, CircularProgress, TextField } from '@mui/material';\nimport { getFacilitiesByIds, listFacilities } from '@pm/services/pmApi';\n\n// Synchronous facility picker. The backend now scopes /pm/facilities to the\n// current operator's assigned set (joined off user_health_infrastructure),\n// which is small — typically a handful of facilities — so we just fetch the\n// whole list once and let MUI's default client-side filter handle search.\n//\n// `value` is the facility id (string). Editing a saved row whose facility is\n// no longer in the assigned set (e.g. assignment was revoked) is supported\n// via /facilities/by-ids hydration so the picker still shows a name.\nexport default function FacilityPicker({\n value,\n onChange,\n label = 'Facility',\n required = false,\n disabled = false,\n error = false,\n helperText = '',\n onBlur,\n size = 'small',\n fullWidth = true,\n}) {\n const [open, setOpen] = useState(false);\n const [facilities, setFacilities] = useState([]);\n const [loading, setLoading] = useState(false);\n const [loaded, setLoaded] = useState(false);\n const [selectedFacility, setSelectedFacility] = useState(null);\n\n // Hydrate the selected facility's name when `value` changes. Tries the\n // already-loaded assigned list first, then /by-ids as a fallback so a\n // facility that's no longer assigned still resolves to a name on edit.\n useEffect(() => {\n if (!value) {\n setSelectedFacility(null);\n return undefined;\n }\n if (selectedFacility?.id === value) return undefined;\n const fromAssigned = facilities.find((f) => f.id === value);\n if (fromAssigned) {\n setSelectedFacility(fromAssigned);\n return undefined;\n }\n let cancelled = false;\n (async () => {\n try {\n const found = await getFacilitiesByIds([value]);\n if (!cancelled && found[0]) setSelectedFacility(found[0]);\n } catch {\n // Leave the picker showing a raw id rather than crashing — operator\n // can still re-pick.\n }\n })();\n return () => { cancelled = true; };\n // facilities is intentionally excluded from the dep array so the picker\n // doesn't re-hydrate every time the list loads; the in-list lookup runs\n // synchronously on each render anyway.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value]);\n\n // Load the assigned-facility list once on first open. The list isn't huge\n // and the backend scopes it; no point in pre-fetching at mount.\n useEffect(() => {\n if (!open || loaded) return undefined;\n let cancelled = false;\n setLoading(true);\n (async () => {\n try {\n const rows = await listFacilities();\n if (!cancelled) setFacilities(rows);\n } catch {\n if (!cancelled) setFacilities([]);\n } finally {\n if (!cancelled) {\n setLoading(false);\n setLoaded(true);\n }\n }\n })();\n return () => { cancelled = true; };\n }, [open, loaded]);\n\n // Make sure the currently-selected facility is in the option set so the\n // Autocomplete can render its label without an out-of-range warning.\n const displayedOptions = useMemo(() => {\n if (!selectedFacility) return facilities;\n if (facilities.some((o) => o.id === selectedFacility.id)) return facilities;\n return [selectedFacility, ...facilities];\n }, [facilities, selectedFacility]);\n\n const noOptionsText = loading\n ? 'Loading…'\n : loaded && facilities.length === 0\n ? 'No facilities assigned to your account'\n : 'No matches';\n\n const defaultHelper = 'Only facilities assigned to your account are shown. Type to filter.';\n\n return (\n <Autocomplete\n size={size}\n fullWidth={fullWidth}\n disabled={disabled}\n open={open}\n onOpen={() => setOpen(true)}\n onClose={() => setOpen(false)}\n value={selectedFacility}\n isOptionEqualToValue={(a, b) => a?.id === b?.id}\n getOptionLabel={(o) => o?.name ?? ''}\n options={displayedOptions}\n loading={loading}\n onChange={(_, next) => {\n setSelectedFacility(next ?? null);\n onChange?.(next?.id ?? '');\n }}\n noOptionsText={noOptionsText}\n renderInput={(params) => (\n <TextField\n {...params}\n required={required}\n label={label}\n error={error}\n helperText={helperText || defaultHelper}\n onBlur={onBlur}\n InputProps={{\n ...params.InputProps,\n endAdornment: (\n <>\n {loading ? <CircularProgress color=\"inherit\" size={16} /> : null}\n {params.InputProps.endAdornment}\n </>\n ),\n }}\n />\n )}\n />\n );\n}\n"],"mappings":";;;;;AAYA,SAAwB,EAAe,EACrC,UACA,aACA,WAAQ,YACR,cAAW,IACX,cAAW,IACX,WAAQ,IACR,gBAAa,IACb,WACA,UAAO,SACP,eAAY,MACX;CACD,IAAM,CAAC,GAAM,KAAW,EAAS,EAAK,GAChC,CAAC,GAAY,KAAiB,EAAS,CAAC,CAAC,GACzC,CAAC,GAAS,KAAc,EAAS,EAAK,GACtC,CAAC,GAAQ,KAAa,EAAS,EAAK,GACpC,CAAC,GAAkB,KAAuB,EAAS,IAAI;CAuE7D,OAlEA,QAAgB;EACd,IAAI,CAAC,GAAO;GACV,EAAoB,IAAI;GACxB;EACF;EACA,IAAI,GAAkB,OAAO,GAAO;EACpC,IAAM,IAAe,EAAW,MAAM,MAAM,EAAE,OAAO,CAAK;EAC1D,IAAI,GAAc;GAChB,EAAoB,CAAY;GAChC;EACF;EACA,IAAI,IAAY;EAUhB,QATC,YAAY;GACX,IAAI;IACF,IAAM,IAAQ,MAAM,EAAmB,CAAC,CAAK,CAAC;IAC9C,AAAI,CAAC,KAAa,EAAM,MAAI,EAAoB,EAAM,EAAE;GAC1D,QAAQ,CAGR;EACF,GAAG,SACU;GAAE,IAAY;EAAM;CAKnC,GAAG,CAAC,CAAK,CAAC,GAIV,QAAgB;EACd,IAAI,CAAC,KAAQ,GAAQ;EACrB,IAAI,IAAY;EAehB,OAdA,EAAW,EAAI,IACd,YAAY;GACX,IAAI;IACF,IAAM,IAAO,MAAM,EAAe;IAClC,AAAK,KAAW,EAAc,CAAI;GACpC,QAAQ;IACN,AAAK,KAAW,EAAc,CAAC,CAAC;GAClC,UAAU;IACR,AAAK,MACH,EAAW,EAAK,GAChB,EAAU,EAAI;GAElB;EACF,GAAG,SACU;GAAE,IAAY;EAAM;CACnC,GAAG,CAAC,GAAM,CAAM,CAAC,GAmBf,kBAAC,GAAD;EACQ;EACK;EACD;EACJ;EACN,cAAc,EAAQ,EAAI;EAC1B,eAAe,EAAQ,EAAK;EAC5B,OAAO;EACP,uBAAuB,GAAG,MAAM,GAAG,OAAO,GAAG;EAC7C,iBAAiB,MAAM,GAAG,QAAQ;EAClC,SAzBqB,QACnB,CAAC,KACD,EAAW,MAAM,MAAM,EAAE,OAAO,EAAiB,EAAE,IAAU,IAC1D,CAAC,GAAkB,GAAG,CAAU,GACtC,CAAC,GAAY,CAAgB,CAqBnB;EACA;EACT,WAAW,GAAG,MAAS;GAErB,AADA,EAAoB,KAAQ,IAAI,GAChC,IAAW,GAAM,MAAM,EAAE;EAC3B;EACe,eAzBG,IAClB,aACA,KAAU,EAAW,WAAW,IAC9B,2CACA;EAsBF,cAAc,MACZ,kBAAC,GAAD;GACE,GAAI;GACM;GACH;GACA;GACP,YAAY,KAAc;GAClB;GACR,YAAY;IACV,GAAG,EAAO;IACV,cACE,kBAAA,GAAA,EAAA,UAAA,CACG,IAAU,kBAAC,GAAD;KAAkB,OAAM;KAAU,MAAM;IAAK,CAAA,IAAI,MAC3D,EAAO,WAAW,YACnB,EAAA,CAAA;GAEN;EACD,CAAA;CAEJ,CAAA;AAEL"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Y as e, dt as t, it as n, j as r } from "./settingsStore-
|
|
2
|
-
import { a as ee, c as te, l as i, o as a, s as ne, t as re } from "./PmTable-
|
|
1
|
+
import { Y as e, dt as t, it as n, j as r } from "./settingsStore-BifOsjlB.js";
|
|
2
|
+
import { a as ee, c as te, l as i, o as a, s as ne, t as re } from "./PmTable-_GbScaa4.js";
|
|
3
3
|
import { C as o, a as ie, b as ae, l as s, n as oe } from "./constants-ClZJY2x1.js";
|
|
4
4
|
import { a as se, i as c, t as ce } from "./format-UOyOu75i.js";
|
|
5
|
-
import { m as l } from "./validation-
|
|
6
|
-
import { a as le, i as ue, n as de, r as fe, t as pe } from "./PmGuard-
|
|
5
|
+
import { m as l } from "./validation-BzlHnL-r.js";
|
|
6
|
+
import { a as le, i as ue, n as de, r as fe, t as pe } from "./PmGuard-b10TGXEi.js";
|
|
7
7
|
import { useMemo as me, useState as u } from "react";
|
|
8
8
|
import { jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
9
9
|
import { Alert as he, Box as p, Button as m, Chip as h, Dialog as g, DialogActions as _, DialogContent as v, DialogTitle as y, InputAdornment as ge, LinearProgress as _e, Skeleton as ve, Stack as b, TableBody as ye, TableCell as x, TableHead as be, TableRow as S, TextField as C, Tooltip as xe, Typography as w } from "@mui/material";
|
|
@@ -17,7 +17,7 @@ import O from "@mui/icons-material/ReportRounded";
|
|
|
17
17
|
import k from "@mui/icons-material/SearchRounded";
|
|
18
18
|
//#region src/views/InventoryPage.jsx
|
|
19
19
|
function A(e) {
|
|
20
|
-
return e ? e.status === "EXPIRED"
|
|
20
|
+
return e ? e.status === "EXPIRED" ? !0 : c(e.expiryDate) : !1;
|
|
21
21
|
}
|
|
22
22
|
var Ee = [
|
|
23
23
|
{
|
|
@@ -133,9 +133,8 @@ function j() {
|
|
|
133
133
|
comments: ""
|
|
134
134
|
}), await K();
|
|
135
135
|
} catch (e) {
|
|
136
|
-
let t = e?.response?.data?.message ?? e?.message ?? "Failed to record wastage";
|
|
137
136
|
i({
|
|
138
|
-
message:
|
|
137
|
+
message: e?.response?.data?.message ?? e?.message ?? "Failed to record wastage",
|
|
139
138
|
type: "error"
|
|
140
139
|
});
|
|
141
140
|
} finally {
|
|
@@ -160,9 +159,8 @@ function j() {
|
|
|
160
159
|
notes: ""
|
|
161
160
|
}), await K();
|
|
162
161
|
} catch (e) {
|
|
163
|
-
let t = e?.response?.data?.message ?? e?.message ?? "Failed to quarantine";
|
|
164
162
|
i({
|
|
165
|
-
message:
|
|
163
|
+
message: e?.response?.data?.message ?? e?.message ?? "Failed to quarantine",
|
|
166
164
|
type: "error"
|
|
167
165
|
});
|
|
168
166
|
}
|
|
@@ -177,9 +175,8 @@ function j() {
|
|
|
177
175
|
type: "success"
|
|
178
176
|
}), await K();
|
|
179
177
|
} catch (e) {
|
|
180
|
-
let t = e?.response?.data?.message ?? e?.message ?? "Failed to unquarantine";
|
|
181
178
|
i({
|
|
182
|
-
message:
|
|
179
|
+
message: e?.response?.data?.message ?? e?.message ?? "Failed to unquarantine",
|
|
183
180
|
type: "error"
|
|
184
181
|
});
|
|
185
182
|
}
|
|
@@ -655,4 +652,4 @@ function j() {
|
|
|
655
652
|
//#endregion
|
|
656
653
|
export { j as default };
|
|
657
654
|
|
|
658
|
-
//# sourceMappingURL=InventoryPage-
|
|
655
|
+
//# sourceMappingURL=InventoryPage-C_AxSy7A.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InventoryPage-CTdq6txa.js","names":[],"sources":["../src/views/InventoryPage.jsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n Alert,\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n InputAdornment,\n LinearProgress,\n Skeleton,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Tooltip,\n Typography,\n} from \"@mui/material\";\nimport { alpha, useTheme } from \"@mui/material/styles\";\nimport WidgetsRoundedIcon from \"@mui/icons-material/WidgetsRounded\";\nimport { PmEmptyRow, PmRef, PmSelect, PmTable, PmTableCard } from \"@pm/components/PmTable\";\nimport RefreshRoundedIcon from \"@mui/icons-material/RefreshRounded\";\nimport LockRoundedIcon from \"@mui/icons-material/LockRounded\";\nimport LockOpenRoundedIcon from \"@mui/icons-material/LockOpenRounded\";\nimport ReportRoundedIcon from \"@mui/icons-material/ReportRounded\";\nimport SearchRoundedIcon from \"@mui/icons-material/SearchRounded\";\nimport { showToast } from '@pm/utils/pmToast';\nimport PmPageShell from \"@pm/components/PmPageShell\";\nimport PmNav from \"@pm/components/PmNav\";\nimport PmGuard from \"@pm/components/PmGuard\";\nimport { usePermissionedEffect, usePmPermissions } from \"@pm/utils/permissions\";\nimport {\n listBatches,\n quarantineBatch,\n unquarantineBatch,\n wasteBatch,\n} from \"@pm/services/pmApi\";\nimport { BATCH_STATUSES, INVENTORY_OWNER_TYPES, QUARANTINE_REASONS, WASTAGE_REASONS, labelOf } from \"@pm/utils/constants\";\nimport { formatDate, isPastExpiry, statusColor } from \"@pm/utils/format\";\nimport { validateRequired } from \"@pm/utils/validation\";\n\n// Shared \"is this batch past its printed expiry?\" check. Used for both the\n// per-row pill and the \"Not yet expired\" filter so they can never disagree.\n// A batch whose expiryDate equals today is still valid — only the day AFTER\n// the printed expiry counts as expired (matches the unit packaging rule).\nfunction isBatchExpired(b) {\n if (!b) return false;\n if (b.status === 'EXPIRED') return true;\n return isPastExpiry(b.expiryDate);\n}\n\n// Scenario-based \"Show\" filter. Each option isolates one situation an operator\n// looks for, replacing the old status-dropdown + overlapping chips.\nconst INVENTORY_VIEWS = [\n { value: 'ACTIVE', label: 'Active stock' },\n { value: 'IN_USE', label: 'In use' },\n { value: 'USED_UP', label: 'Used up' },\n { value: 'EXPIRED', label: 'Expired' },\n { value: 'QUARANTINED', label: 'Quarantined' },\n { value: 'WASTAGE', label: 'Has wastage' },\n { value: 'ALL', label: 'All batches' },\n];\n\nfunction matchesInventoryView(b, view) {\n const expired = isBatchExpired(b);\n const qty = Number(b.qtyRemaining ?? 0);\n switch (view) {\n case 'ACTIVE': return qty > 0 && !expired && b.isQuarantined !== true;\n case 'IN_USE': return b.status === 'IN_USE';\n case 'USED_UP': return b.status === 'EXHAUSTED';\n case 'EXPIRED': return expired;\n case 'QUARANTINED': return b.isQuarantined === true || b.status === 'QUARANTINED';\n case 'WASTAGE': return Number(b.wastedQty ?? 0) > 0;\n case 'ALL':\n default: return true;\n }\n}\n\nexport default function InventoryPage() {\n const perms = usePmPermissions();\n const canWrite = perms.has('update.manage.productManagement.inventory');\n const [items, setItems] = useState([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(\"\");\n const [quarantineDialog, setQuarantineDialog] = useState({\n open: false,\n batch: null,\n reason: \"OTHER\",\n notes: \"\",\n });\n // Batch-level wastage dialog — write off a quantity as wastage (any product).\n const [wastageDialog, setWastageDialog] = useState({\n open: false,\n batch: null,\n qty: 1,\n reason: \"DAMAGED\",\n comments: \"\",\n });\n const [submittingWastage, setSubmittingWastage] = useState(false);\n const theme = useTheme();\n const [search, setSearch] = useState('');\n // One scenario-based \"Show\" filter instead of a status dropdown + several\n // overlapping chips. Each option isolates one situation the operator cares\n // about; defaults to Active stock so the list is scannable out of the box.\n const [view, setView] = useState('ACTIVE');\n\n async function load() {\n setLoading(true);\n setError(\"\");\n try {\n setItems(await listBatches());\n } catch (err) {\n const message =\n err?.response?.data?.message ??\n err?.message ??\n \"Failed to load inventory\";\n setError(message);\n showToast({ message, type: \"error\" });\n } finally {\n setLoading(false);\n }\n }\n\n usePermissionedEffect('view.manage.productManagement.inventory', () => {\n void load();\n }, []);\n\n // Filters apply in-memory over whatever the list endpoint returned. Search\n // matches product name + lot (case-insensitive); the Show filter isolates one\n // scenario.\n const filteredItems = useMemo(() => {\n const q = search.trim().toLowerCase();\n return items.filter((b) => {\n if (q) {\n const hay = `${b.productName ?? ''} ${b.lotNumber ?? ''}`.toLowerCase();\n if (!hay.includes(q)) return false;\n }\n return matchesInventoryView(b, view);\n });\n }, [items, search, view]);\n\n const hasFilter = search.trim() !== '' || view !== 'ALL';\n\n function clearFilters() {\n setSearch('');\n setView('ALL');\n }\n\n function openWastage(batch) {\n setWastageDialog({\n open: true,\n batch,\n qty: batch.qtyRemaining ?? 1,\n reason: \"DAMAGED\",\n comments: \"\",\n });\n }\n\n const wastageQtyNum = Number(wastageDialog.qty);\n const wastageMax = Number(wastageDialog.batch?.qtyRemaining ?? 0);\n const wastageQtyError = !Number.isInteger(wastageQtyNum) || wastageQtyNum <= 0\n ? 'Enter a whole number greater than 0.'\n : wastageQtyNum > wastageMax\n ? `Only ${wastageMax} available to waste.`\n : '';\n const wastageValid = !wastageQtyError && !!wastageDialog.reason;\n\n async function doWaste() {\n if (!wastageDialog.batch || !wastageValid) return;\n setSubmittingWastage(true);\n try {\n await wasteBatch({\n id: uuidv4(),\n batchId: wastageDialog.batch.id,\n wastedQty: wastageQtyNum,\n reason: wastageDialog.reason,\n comments: wastageDialog.comments || null,\n });\n showToast({ message: \"Wastage recorded.\", type: \"success\" });\n setWastageDialog({ open: false, batch: null, qty: 1, reason: \"DAMAGED\", comments: \"\" });\n await load();\n } catch (err) {\n const message = err?.response?.data?.message ?? err?.message ?? \"Failed to record wastage\";\n showToast({ message, type: \"error\" });\n } finally {\n setSubmittingWastage(false);\n }\n }\n\n async function doQuarantine() {\n try {\n await quarantineBatch({\n id: uuidv4(),\n batchId: quarantineDialog.batch.id,\n reason: quarantineDialog.reason,\n notes: quarantineDialog.notes || null,\n });\n showToast({ message: \"Batch quarantined.\", type: \"success\" });\n setQuarantineDialog({\n open: false,\n batch: null,\n reason: \"OTHER\",\n notes: \"\",\n });\n await load();\n } catch (err) {\n const message =\n err?.response?.data?.message ?? err?.message ?? \"Failed to quarantine\";\n showToast({ message, type: \"error\" });\n }\n }\n\n async function doUnquarantine(batch) {\n try {\n await unquarantineBatch({ id: uuidv4(), batchId: batch.id });\n showToast({ message: \"Quarantine lifted.\", type: \"success\" });\n await load();\n } catch (err) {\n const message =\n err?.response?.data?.message ??\n err?.message ??\n \"Failed to unquarantine\";\n showToast({ message, type: \"error\" });\n }\n }\n\n return (\n <Box>\n <PmNav />\n <PmGuard perm={'view.manage.productManagement.inventory'}>\n <PmPageShell\n title=\"Inventory\"\n subtitle=\"Your stock batches. Click to see history or quarantine items if there's a problem.\"\n chips={[\"Stock\"]}\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"outlined\"\n startIcon={<RefreshRoundedIcon />}\n onClick={() => void load()}\n >\n Refresh\n </Button>\n </Stack>\n }\n >\n {error ? (\n <Alert severity=\"error\" sx={{ mb: 2 }}>\n {error}\n </Alert>\n ) : null}\n\n <PmTableCard\n eyebrow=\"Stock on hand\"\n title=\"Inventory batches\"\n subtitle={\n loading\n ? \"Loading…\"\n : hasFilter\n ? `${filteredItems.length} of ${items.length} ${items.length === 1 ? 'batch' : 'batches'} match`\n : `${items.length} ${items.length === 1 ? 'batch' : 'batches'} visible`\n }\n >\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ mb: 2 }} alignItems={{ md: 'center' }}>\n <TextField\n size=\"small\"\n fullWidth\n placeholder=\"Search product or lot…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n InputProps={{\n startAdornment: (\n <InputAdornment position=\"start\">\n <SearchRoundedIcon fontSize=\"small\" />\n </InputAdornment>\n ),\n }}\n sx={{ maxWidth: { md: 360 } }}\n />\n <Box sx={{ minWidth: 190 }}>\n <PmSelect\n label=\"Show\"\n value={view}\n onChange={setView}\n options={INVENTORY_VIEWS}\n />\n </Box>\n <Box sx={{ flexGrow: 1 }} />\n <Button variant=\"text\" onClick={clearFilters} disabled={!hasFilter}>Clear</Button>\n </Stack>\n\n <PmTable dense>\n <TableHead>\n <TableRow>\n <TableCell>Product</TableCell>\n <TableCell>Lot</TableCell>\n <TableCell>Owner</TableCell>\n <TableCell>Expiry</TableCell>\n <TableCell align=\"right\">Stock</TableCell>\n <TableCell>Storage</TableCell>\n <TableCell>Status</TableCell>\n <TableCell align=\"right\">Action</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {loading ? (\n Array.from({ length: 5 }).map((_, i) => (\n <TableRow key={i}>\n {Array.from({ length: 8 }).map((__, j) => (\n <TableCell key={j}>\n <Skeleton height={22} />\n </TableCell>\n ))}\n </TableRow>\n ))\n ) : filteredItems.length === 0 ? (\n <PmEmptyRow\n colSpan={8}\n icon={WidgetsRoundedIcon}\n title={hasFilter ? 'No batches match your filters' : 'No batches in stock'}\n hint={hasFilter\n ? 'Adjust your search or filters.'\n : 'Record a purchase to add stock.'}\n action={hasFilter ? (\n <Button variant=\"text\" size=\"small\" onClick={clearFilters}>Clear filters</Button>\n ) : null}\n />\n ) : (\n filteredItems.map((b) => {\n const isExpired = isBatchExpired(b);\n const remainingRatio =\n b.qtyPurchased > 0 ? b.qtyRemaining / b.qtyPurchased : 0;\n return (\n <TableRow key={b.id} hover>\n <TableCell>\n <Stack\n direction=\"row\"\n alignItems=\"baseline\"\n spacing={1}\n sx={{ minWidth: 0 }}\n >\n <Typography\n variant=\"body2\"\n sx={{ fontWeight: 700, lineHeight: 1.2 }}\n >\n {b.productName}\n </Typography>\n <PmRef id={b.serialId} />\n </Stack>\n </TableCell>\n <TableCell>\n {b.lotNumber ? (\n <Box\n component=\"span\"\n sx={{\n display: \"inline-flex\",\n px: 0.7,\n py: 0.1,\n borderRadius: 1,\n bgcolor: alpha(theme.palette.primary.main, 0.06),\n color: \"primary.dark\",\n fontFamily: \"monospace\",\n fontSize: \"0.72rem\",\n fontWeight: 700,\n }}\n >\n {b.lotNumber}\n </Box>\n ) : (\n <Typography variant=\"caption\" color=\"text.disabled\">\n —\n </Typography>\n )}\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>\n {b.ownerName || labelOf(INVENTORY_OWNER_TYPES, b.ownerType)}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {b.ownerType}\n </Typography>\n </TableCell>\n <TableCell>\n {b.expiryDate ? (\n <Tooltip\n title={isExpired ? \"Past printed expiry\" : \"\"}\n >\n <Typography\n variant=\"body2\"\n sx={{\n fontWeight: 700,\n fontVariantNumeric: \"tabular-nums\",\n color: isExpired\n ? \"error.dark\"\n : \"text.primary\",\n }}\n >\n {formatDate(b.expiryDate)}\n </Typography>\n </Tooltip>\n ) : (\n <Typography variant=\"caption\" color=\"text.disabled\">\n —\n </Typography>\n )}\n </TableCell>\n <TableCell align=\"right\" sx={{ minWidth: 150 }}>\n <Stack alignItems=\"flex-end\" spacing={0.5}>\n <Typography\n variant=\"body2\"\n sx={{\n fontWeight: 800,\n fontVariantNumeric: \"tabular-nums\",\n }}\n >\n {b.qtyRemaining}\n <Typography\n component=\"span\"\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{\n ml: 0.5,\n fontVariantNumeric: \"tabular-nums\",\n }}\n >\n / {b.qtyPurchased}\n </Typography>\n </Typography>\n <Box sx={{ width: 80 }}>\n <LinearProgress\n variant=\"determinate\"\n value={Math.min(\n 100,\n Math.max(0, remainingRatio * 100),\n )}\n sx={{ height: 4, borderRadius: 2 }}\n color={\n remainingRatio < 0.25 ? \"warning\" : \"primary\"\n }\n />\n </Box>\n {Number(b.wastedQty ?? 0) > 0 ? (\n <Typography variant=\"caption\" sx={{ color: \"warning.dark\", fontWeight: 700 }}>\n {b.wastedQty} wasted\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n <TableCell>\n {b.storageUnitName ? (\n <Typography\n variant=\"caption\"\n sx={{ fontWeight: 700 }}\n >\n {b.storageUnitName}\n </Typography>\n ) : (\n <Typography variant=\"caption\" color=\"text.disabled\">\n —\n </Typography>\n )}\n </TableCell>\n <TableCell>\n <Stack\n direction=\"row\"\n spacing={0.5}\n flexWrap=\"wrap\"\n useFlexGap\n >\n <Chip\n size=\"small\"\n color={statusColor(b.status)}\n label={labelOf(BATCH_STATUSES, b.status)}\n sx={{\n fontWeight: 700,\n borderRadius: 1.5,\n height: 22,\n }}\n />\n {b.isQuarantined ? (\n <Chip\n size=\"small\"\n color=\"error\"\n label={labelOf(QUARANTINE_REASONS, b.quarantineReason) || \"Quarantined\"}\n sx={{\n fontWeight: 700,\n borderRadius: 1.5,\n height: 22,\n }}\n />\n ) : null}\n {isExpired && b.status !== \"EXPIRED\" ? (\n <Chip\n size=\"small\"\n color=\"warning\"\n label=\"Expired\"\n sx={{\n fontWeight: 700,\n borderRadius: 1.5,\n height: 22,\n }}\n />\n ) : null}\n {b.sourceType ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={\n b.sourceType === 'PICKUP' ? 'info'\n : b.sourceType === 'TRANSFER_IN' ? 'primary'\n : 'default'\n }\n label={b.sourceType === 'TRANSFER_IN' ? 'Transfer' : b.sourceType === 'PICKUP' ? 'Pickup' : b.sourceType === 'PURCHASE' ? 'Purchase' : b.sourceType}\n sx={{\n fontWeight: 700,\n borderRadius: 1.5,\n height: 22,\n }}\n />\n ) : null}\n </Stack>\n </TableCell>\n <TableCell align=\"right\">\n {!canWrite ? null : b.isQuarantined ? (\n // An expired batch can't be returned to usable\n // stock, so we don't offer Unquarantine on it — the\n // backend rejects it too. Show a muted note instead.\n isExpired ? (\n <Typography variant=\"caption\" color=\"text.disabled\">\n Expired\n </Typography>\n ) : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"success\"\n startIcon={<LockOpenRoundedIcon />}\n onClick={() => void doUnquarantine(b)}\n >\n Unquarantine\n </Button>\n )\n ) : (\n <Stack direction=\"row\" spacing={1} justifyContent=\"flex-end\">\n {isExpired ||\n b.status === \"EXHAUSTED\" ||\n b.status === \"IN_USE\" ||\n b.canBeQuarantined === false ? null : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"error\"\n startIcon={<LockRoundedIcon />}\n onClick={() =>\n setQuarantineDialog({\n open: true,\n batch: b,\n reason: \"OTHER\",\n notes: \"\",\n })\n }\n >\n Quarantine\n </Button>\n )}\n {Number(b.qtyRemaining ?? 0) > 0 ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"warning\"\n startIcon={<ReportRoundedIcon />}\n onClick={() => openWastage(b)}\n >\n Waste\n </Button>\n ) : null}\n </Stack>\n )}\n </TableCell>\n </TableRow>\n );\n })\n )}\n </TableBody>\n </PmTable>\n </PmTableCard>\n </PmPageShell>\n\n <Dialog\n open={quarantineDialog.open}\n onClose={() =>\n setQuarantineDialog({ ...quarantineDialog, open: false })\n }\n fullWidth\n maxWidth=\"sm\"\n >\n <DialogTitle>Quarantine Batch</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <Typography variant=\"body2\">\n Product: <strong>{quarantineDialog.batch?.productName}</strong>\n <br />\n Lot: <strong>{quarantineDialog.batch?.lotNumber || \"—\"}</strong>\n <br />\n Remaining: <strong>{quarantineDialog.batch?.qtyRemaining}</strong>\n </Typography>\n <PmSelect\n label=\"Reason\"\n required\n value={quarantineDialog.reason}\n onChange={(v) =>\n setQuarantineDialog({ ...quarantineDialog, reason: v })\n }\n options={QUARANTINE_REASONS}\n error={!!validateRequired(quarantineDialog.reason, \"Reason\")}\n helperText={validateRequired(quarantineDialog.reason, \"Reason\") || \"\"}\n />\n <TextField\n label=\"Notes\"\n size=\"small\"\n multiline\n minRows={2}\n fullWidth\n value={quarantineDialog.notes}\n onChange={(e) =>\n setQuarantineDialog({\n ...quarantineDialog,\n notes: e.target.value,\n })\n }\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button\n onClick={() =>\n setQuarantineDialog({ ...quarantineDialog, open: false })\n }\n >\n Cancel\n </Button>\n <Button\n variant=\"contained\"\n color=\"error\"\n disabled={!!validateRequired(quarantineDialog.reason, \"Reason\")}\n onClick={() => void doQuarantine()}\n >\n Quarantine\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={wastageDialog.open}\n onClose={() => { if (!submittingWastage) setWastageDialog({ ...wastageDialog, open: false }); }}\n fullWidth\n maxWidth=\"sm\"\n >\n <DialogTitle>Mark Wastage</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <Typography variant=\"body2\">\n Product: <strong>{wastageDialog.batch?.productName}</strong>\n <br />\n Lot: <strong>{wastageDialog.batch?.lotNumber || \"—\"}</strong>\n <br />\n Available to waste: <strong>{wastageDialog.batch?.qtyRemaining ?? 0}</strong>\n </Typography>\n <TextField\n required\n label=\"Quantity to waste\"\n size=\"small\"\n type=\"number\"\n fullWidth\n inputProps={{ min: 1, max: wastageMax }}\n value={wastageDialog.qty}\n onChange={(e) => setWastageDialog({ ...wastageDialog, qty: e.target.value })}\n error={!!wastageQtyError}\n helperText={wastageQtyError || `Units to write off (max ${wastageMax}).`}\n />\n <PmSelect\n label=\"Reason\"\n required\n value={wastageDialog.reason}\n onChange={(v) => setWastageDialog({ ...wastageDialog, reason: v })}\n options={WASTAGE_REASONS}\n />\n <TextField\n label=\"Comments\"\n size=\"small\"\n multiline\n minRows={2}\n fullWidth\n value={wastageDialog.comments}\n onChange={(e) => setWastageDialog({ ...wastageDialog, comments: e.target.value })}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button\n onClick={() => setWastageDialog({ ...wastageDialog, open: false })}\n disabled={submittingWastage}\n >\n Cancel\n </Button>\n <Button\n variant=\"contained\"\n color=\"warning\"\n disabled={!wastageValid || submittingWastage}\n onClick={() => void doWaste()}\n >\n {submittingWastage ? \"Recording…\" : \"Mark Wastage\"}\n </Button>\n </DialogActions>\n </Dialog>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InventoryPage-C_AxSy7A.js","names":[],"sources":["../src/views/InventoryPage.jsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n Alert,\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n InputAdornment,\n LinearProgress,\n Skeleton,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Tooltip,\n Typography,\n} from \"@mui/material\";\nimport { alpha, useTheme } from \"@mui/material/styles\";\nimport WidgetsRoundedIcon from \"@mui/icons-material/WidgetsRounded\";\nimport { PmEmptyRow, PmRef, PmSelect, PmTable, PmTableCard } from \"@pm/components/PmTable\";\nimport RefreshRoundedIcon from \"@mui/icons-material/RefreshRounded\";\nimport LockRoundedIcon from \"@mui/icons-material/LockRounded\";\nimport LockOpenRoundedIcon from \"@mui/icons-material/LockOpenRounded\";\nimport ReportRoundedIcon from \"@mui/icons-material/ReportRounded\";\nimport SearchRoundedIcon from \"@mui/icons-material/SearchRounded\";\nimport { showToast } from '@pm/utils/pmToast';\nimport PmPageShell from \"@pm/components/PmPageShell\";\nimport PmNav from \"@pm/components/PmNav\";\nimport PmGuard from \"@pm/components/PmGuard\";\nimport { usePermissionedEffect, usePmPermissions } from \"@pm/utils/permissions\";\nimport {\n listBatches,\n quarantineBatch,\n unquarantineBatch,\n wasteBatch,\n} from \"@pm/services/pmApi\";\nimport { BATCH_STATUSES, INVENTORY_OWNER_TYPES, QUARANTINE_REASONS, WASTAGE_REASONS, labelOf } from \"@pm/utils/constants\";\nimport { formatDate, isPastExpiry, statusColor } from \"@pm/utils/format\";\nimport { validateRequired } from \"@pm/utils/validation\";\n\n// Shared \"is this batch past its printed expiry?\" check. Used for both the\n// per-row pill and the \"Not yet expired\" filter so they can never disagree.\n// A batch whose expiryDate equals today is still valid — only the day AFTER\n// the printed expiry counts as expired (matches the unit packaging rule).\nfunction isBatchExpired(b) {\n if (!b) return false;\n if (b.status === 'EXPIRED') return true;\n return isPastExpiry(b.expiryDate);\n}\n\n// Scenario-based \"Show\" filter. Each option isolates one situation an operator\n// looks for, replacing the old status-dropdown + overlapping chips.\nconst INVENTORY_VIEWS = [\n { value: 'ACTIVE', label: 'Active stock' },\n { value: 'IN_USE', label: 'In use' },\n { value: 'USED_UP', label: 'Used up' },\n { value: 'EXPIRED', label: 'Expired' },\n { value: 'QUARANTINED', label: 'Quarantined' },\n { value: 'WASTAGE', label: 'Has wastage' },\n { value: 'ALL', label: 'All batches' },\n];\n\nfunction matchesInventoryView(b, view) {\n const expired = isBatchExpired(b);\n const qty = Number(b.qtyRemaining ?? 0);\n switch (view) {\n case 'ACTIVE': return qty > 0 && !expired && b.isQuarantined !== true;\n case 'IN_USE': return b.status === 'IN_USE';\n case 'USED_UP': return b.status === 'EXHAUSTED';\n case 'EXPIRED': return expired;\n case 'QUARANTINED': return b.isQuarantined === true || b.status === 'QUARANTINED';\n case 'WASTAGE': return Number(b.wastedQty ?? 0) > 0;\n case 'ALL':\n default: return true;\n }\n}\n\nexport default function InventoryPage() {\n const perms = usePmPermissions();\n const canWrite = perms.has('update.manage.productManagement.inventory');\n const [items, setItems] = useState([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(\"\");\n const [quarantineDialog, setQuarantineDialog] = useState({\n open: false,\n batch: null,\n reason: \"OTHER\",\n notes: \"\",\n });\n // Batch-level wastage dialog — write off a quantity as wastage (any product).\n const [wastageDialog, setWastageDialog] = useState({\n open: false,\n batch: null,\n qty: 1,\n reason: \"DAMAGED\",\n comments: \"\",\n });\n const [submittingWastage, setSubmittingWastage] = useState(false);\n const theme = useTheme();\n const [search, setSearch] = useState('');\n // One scenario-based \"Show\" filter instead of a status dropdown + several\n // overlapping chips. Each option isolates one situation the operator cares\n // about; defaults to Active stock so the list is scannable out of the box.\n const [view, setView] = useState('ACTIVE');\n\n async function load() {\n setLoading(true);\n setError(\"\");\n try {\n setItems(await listBatches());\n } catch (err) {\n const message =\n err?.response?.data?.message ??\n err?.message ??\n \"Failed to load inventory\";\n setError(message);\n showToast({ message, type: \"error\" });\n } finally {\n setLoading(false);\n }\n }\n\n usePermissionedEffect('view.manage.productManagement.inventory', () => {\n void load();\n }, []);\n\n // Filters apply in-memory over whatever the list endpoint returned. Search\n // matches product name + lot (case-insensitive); the Show filter isolates one\n // scenario.\n const filteredItems = useMemo(() => {\n const q = search.trim().toLowerCase();\n return items.filter((b) => {\n if (q) {\n const hay = `${b.productName ?? ''} ${b.lotNumber ?? ''}`.toLowerCase();\n if (!hay.includes(q)) return false;\n }\n return matchesInventoryView(b, view);\n });\n }, [items, search, view]);\n\n const hasFilter = search.trim() !== '' || view !== 'ALL';\n\n function clearFilters() {\n setSearch('');\n setView('ALL');\n }\n\n function openWastage(batch) {\n setWastageDialog({\n open: true,\n batch,\n qty: batch.qtyRemaining ?? 1,\n reason: \"DAMAGED\",\n comments: \"\",\n });\n }\n\n const wastageQtyNum = Number(wastageDialog.qty);\n const wastageMax = Number(wastageDialog.batch?.qtyRemaining ?? 0);\n const wastageQtyError = !Number.isInteger(wastageQtyNum) || wastageQtyNum <= 0\n ? 'Enter a whole number greater than 0.'\n : wastageQtyNum > wastageMax\n ? `Only ${wastageMax} available to waste.`\n : '';\n const wastageValid = !wastageQtyError && !!wastageDialog.reason;\n\n async function doWaste() {\n if (!wastageDialog.batch || !wastageValid) return;\n setSubmittingWastage(true);\n try {\n await wasteBatch({\n id: uuidv4(),\n batchId: wastageDialog.batch.id,\n wastedQty: wastageQtyNum,\n reason: wastageDialog.reason,\n comments: wastageDialog.comments || null,\n });\n showToast({ message: \"Wastage recorded.\", type: \"success\" });\n setWastageDialog({ open: false, batch: null, qty: 1, reason: \"DAMAGED\", comments: \"\" });\n await load();\n } catch (err) {\n const message = err?.response?.data?.message ?? err?.message ?? \"Failed to record wastage\";\n showToast({ message, type: \"error\" });\n } finally {\n setSubmittingWastage(false);\n }\n }\n\n async function doQuarantine() {\n try {\n await quarantineBatch({\n id: uuidv4(),\n batchId: quarantineDialog.batch.id,\n reason: quarantineDialog.reason,\n notes: quarantineDialog.notes || null,\n });\n showToast({ message: \"Batch quarantined.\", type: \"success\" });\n setQuarantineDialog({\n open: false,\n batch: null,\n reason: \"OTHER\",\n notes: \"\",\n });\n await load();\n } catch (err) {\n const message =\n err?.response?.data?.message ?? err?.message ?? \"Failed to quarantine\";\n showToast({ message, type: \"error\" });\n }\n }\n\n async function doUnquarantine(batch) {\n try {\n await unquarantineBatch({ id: uuidv4(), batchId: batch.id });\n showToast({ message: \"Quarantine lifted.\", type: \"success\" });\n await load();\n } catch (err) {\n const message =\n err?.response?.data?.message ??\n err?.message ??\n \"Failed to unquarantine\";\n showToast({ message, type: \"error\" });\n }\n }\n\n return (\n <Box>\n <PmNav />\n <PmGuard perm={'view.manage.productManagement.inventory'}>\n <PmPageShell\n title=\"Inventory\"\n subtitle=\"Your stock batches. Click to see history or quarantine items if there's a problem.\"\n chips={[\"Stock\"]}\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"outlined\"\n startIcon={<RefreshRoundedIcon />}\n onClick={() => void load()}\n >\n Refresh\n </Button>\n </Stack>\n }\n >\n {error ? (\n <Alert severity=\"error\" sx={{ mb: 2 }}>\n {error}\n </Alert>\n ) : null}\n\n <PmTableCard\n eyebrow=\"Stock on hand\"\n title=\"Inventory batches\"\n subtitle={\n loading\n ? \"Loading…\"\n : hasFilter\n ? `${filteredItems.length} of ${items.length} ${items.length === 1 ? 'batch' : 'batches'} match`\n : `${items.length} ${items.length === 1 ? 'batch' : 'batches'} visible`\n }\n >\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ mb: 2 }} alignItems={{ md: 'center' }}>\n <TextField\n size=\"small\"\n fullWidth\n placeholder=\"Search product or lot…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n InputProps={{\n startAdornment: (\n <InputAdornment position=\"start\">\n <SearchRoundedIcon fontSize=\"small\" />\n </InputAdornment>\n ),\n }}\n sx={{ maxWidth: { md: 360 } }}\n />\n <Box sx={{ minWidth: 190 }}>\n <PmSelect\n label=\"Show\"\n value={view}\n onChange={setView}\n options={INVENTORY_VIEWS}\n />\n </Box>\n <Box sx={{ flexGrow: 1 }} />\n <Button variant=\"text\" onClick={clearFilters} disabled={!hasFilter}>Clear</Button>\n </Stack>\n\n <PmTable dense>\n <TableHead>\n <TableRow>\n <TableCell>Product</TableCell>\n <TableCell>Lot</TableCell>\n <TableCell>Owner</TableCell>\n <TableCell>Expiry</TableCell>\n <TableCell align=\"right\">Stock</TableCell>\n <TableCell>Storage</TableCell>\n <TableCell>Status</TableCell>\n <TableCell align=\"right\">Action</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {loading ? (\n Array.from({ length: 5 }).map((_, i) => (\n <TableRow key={i}>\n {Array.from({ length: 8 }).map((__, j) => (\n <TableCell key={j}>\n <Skeleton height={22} />\n </TableCell>\n ))}\n </TableRow>\n ))\n ) : filteredItems.length === 0 ? (\n <PmEmptyRow\n colSpan={8}\n icon={WidgetsRoundedIcon}\n title={hasFilter ? 'No batches match your filters' : 'No batches in stock'}\n hint={hasFilter\n ? 'Adjust your search or filters.'\n : 'Record a purchase to add stock.'}\n action={hasFilter ? (\n <Button variant=\"text\" size=\"small\" onClick={clearFilters}>Clear filters</Button>\n ) : null}\n />\n ) : (\n filteredItems.map((b) => {\n const isExpired = isBatchExpired(b);\n const remainingRatio =\n b.qtyPurchased > 0 ? b.qtyRemaining / b.qtyPurchased : 0;\n return (\n <TableRow key={b.id} hover>\n <TableCell>\n <Stack\n direction=\"row\"\n alignItems=\"baseline\"\n spacing={1}\n sx={{ minWidth: 0 }}\n >\n <Typography\n variant=\"body2\"\n sx={{ fontWeight: 700, lineHeight: 1.2 }}\n >\n {b.productName}\n </Typography>\n <PmRef id={b.serialId} />\n </Stack>\n </TableCell>\n <TableCell>\n {b.lotNumber ? (\n <Box\n component=\"span\"\n sx={{\n display: \"inline-flex\",\n px: 0.7,\n py: 0.1,\n borderRadius: 1,\n bgcolor: alpha(theme.palette.primary.main, 0.06),\n color: \"primary.dark\",\n fontFamily: \"monospace\",\n fontSize: \"0.72rem\",\n fontWeight: 700,\n }}\n >\n {b.lotNumber}\n </Box>\n ) : (\n <Typography variant=\"caption\" color=\"text.disabled\">\n —\n </Typography>\n )}\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>\n {b.ownerName || labelOf(INVENTORY_OWNER_TYPES, b.ownerType)}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {b.ownerType}\n </Typography>\n </TableCell>\n <TableCell>\n {b.expiryDate ? (\n <Tooltip\n title={isExpired ? \"Past printed expiry\" : \"\"}\n >\n <Typography\n variant=\"body2\"\n sx={{\n fontWeight: 700,\n fontVariantNumeric: \"tabular-nums\",\n color: isExpired\n ? \"error.dark\"\n : \"text.primary\",\n }}\n >\n {formatDate(b.expiryDate)}\n </Typography>\n </Tooltip>\n ) : (\n <Typography variant=\"caption\" color=\"text.disabled\">\n —\n </Typography>\n )}\n </TableCell>\n <TableCell align=\"right\" sx={{ minWidth: 150 }}>\n <Stack alignItems=\"flex-end\" spacing={0.5}>\n <Typography\n variant=\"body2\"\n sx={{\n fontWeight: 800,\n fontVariantNumeric: \"tabular-nums\",\n }}\n >\n {b.qtyRemaining}\n <Typography\n component=\"span\"\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{\n ml: 0.5,\n fontVariantNumeric: \"tabular-nums\",\n }}\n >\n / {b.qtyPurchased}\n </Typography>\n </Typography>\n <Box sx={{ width: 80 }}>\n <LinearProgress\n variant=\"determinate\"\n value={Math.min(\n 100,\n Math.max(0, remainingRatio * 100),\n )}\n sx={{ height: 4, borderRadius: 2 }}\n color={\n remainingRatio < 0.25 ? \"warning\" : \"primary\"\n }\n />\n </Box>\n {Number(b.wastedQty ?? 0) > 0 ? (\n <Typography variant=\"caption\" sx={{ color: \"warning.dark\", fontWeight: 700 }}>\n {b.wastedQty} wasted\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n <TableCell>\n {b.storageUnitName ? (\n <Typography\n variant=\"caption\"\n sx={{ fontWeight: 700 }}\n >\n {b.storageUnitName}\n </Typography>\n ) : (\n <Typography variant=\"caption\" color=\"text.disabled\">\n —\n </Typography>\n )}\n </TableCell>\n <TableCell>\n <Stack\n direction=\"row\"\n spacing={0.5}\n flexWrap=\"wrap\"\n useFlexGap\n >\n <Chip\n size=\"small\"\n color={statusColor(b.status)}\n label={labelOf(BATCH_STATUSES, b.status)}\n sx={{\n fontWeight: 700,\n borderRadius: 1.5,\n height: 22,\n }}\n />\n {b.isQuarantined ? (\n <Chip\n size=\"small\"\n color=\"error\"\n label={labelOf(QUARANTINE_REASONS, b.quarantineReason) || \"Quarantined\"}\n sx={{\n fontWeight: 700,\n borderRadius: 1.5,\n height: 22,\n }}\n />\n ) : null}\n {isExpired && b.status !== \"EXPIRED\" ? (\n <Chip\n size=\"small\"\n color=\"warning\"\n label=\"Expired\"\n sx={{\n fontWeight: 700,\n borderRadius: 1.5,\n height: 22,\n }}\n />\n ) : null}\n {b.sourceType ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={\n b.sourceType === 'PICKUP' ? 'info'\n : b.sourceType === 'TRANSFER_IN' ? 'primary'\n : 'default'\n }\n label={b.sourceType === 'TRANSFER_IN' ? 'Transfer' : b.sourceType === 'PICKUP' ? 'Pickup' : b.sourceType === 'PURCHASE' ? 'Purchase' : b.sourceType}\n sx={{\n fontWeight: 700,\n borderRadius: 1.5,\n height: 22,\n }}\n />\n ) : null}\n </Stack>\n </TableCell>\n <TableCell align=\"right\">\n {!canWrite ? null : b.isQuarantined ? (\n // An expired batch can't be returned to usable\n // stock, so we don't offer Unquarantine on it — the\n // backend rejects it too. Show a muted note instead.\n isExpired ? (\n <Typography variant=\"caption\" color=\"text.disabled\">\n Expired\n </Typography>\n ) : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"success\"\n startIcon={<LockOpenRoundedIcon />}\n onClick={() => void doUnquarantine(b)}\n >\n Unquarantine\n </Button>\n )\n ) : (\n <Stack direction=\"row\" spacing={1} justifyContent=\"flex-end\">\n {isExpired ||\n b.status === \"EXHAUSTED\" ||\n b.status === \"IN_USE\" ||\n b.canBeQuarantined === false ? null : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"error\"\n startIcon={<LockRoundedIcon />}\n onClick={() =>\n setQuarantineDialog({\n open: true,\n batch: b,\n reason: \"OTHER\",\n notes: \"\",\n })\n }\n >\n Quarantine\n </Button>\n )}\n {Number(b.qtyRemaining ?? 0) > 0 ? (\n <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"warning\"\n startIcon={<ReportRoundedIcon />}\n onClick={() => openWastage(b)}\n >\n Waste\n </Button>\n ) : null}\n </Stack>\n )}\n </TableCell>\n </TableRow>\n );\n })\n )}\n </TableBody>\n </PmTable>\n </PmTableCard>\n </PmPageShell>\n\n <Dialog\n open={quarantineDialog.open}\n onClose={() =>\n setQuarantineDialog({ ...quarantineDialog, open: false })\n }\n fullWidth\n maxWidth=\"sm\"\n >\n <DialogTitle>Quarantine Batch</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <Typography variant=\"body2\">\n Product: <strong>{quarantineDialog.batch?.productName}</strong>\n <br />\n Lot: <strong>{quarantineDialog.batch?.lotNumber || \"—\"}</strong>\n <br />\n Remaining: <strong>{quarantineDialog.batch?.qtyRemaining}</strong>\n </Typography>\n <PmSelect\n label=\"Reason\"\n required\n value={quarantineDialog.reason}\n onChange={(v) =>\n setQuarantineDialog({ ...quarantineDialog, reason: v })\n }\n options={QUARANTINE_REASONS}\n error={!!validateRequired(quarantineDialog.reason, \"Reason\")}\n helperText={validateRequired(quarantineDialog.reason, \"Reason\") || \"\"}\n />\n <TextField\n label=\"Notes\"\n size=\"small\"\n multiline\n minRows={2}\n fullWidth\n value={quarantineDialog.notes}\n onChange={(e) =>\n setQuarantineDialog({\n ...quarantineDialog,\n notes: e.target.value,\n })\n }\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button\n onClick={() =>\n setQuarantineDialog({ ...quarantineDialog, open: false })\n }\n >\n Cancel\n </Button>\n <Button\n variant=\"contained\"\n color=\"error\"\n disabled={!!validateRequired(quarantineDialog.reason, \"Reason\")}\n onClick={() => void doQuarantine()}\n >\n Quarantine\n </Button>\n </DialogActions>\n </Dialog>\n\n <Dialog\n open={wastageDialog.open}\n onClose={() => { if (!submittingWastage) setWastageDialog({ ...wastageDialog, open: false }); }}\n fullWidth\n maxWidth=\"sm\"\n >\n <DialogTitle>Mark Wastage</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <Typography variant=\"body2\">\n Product: <strong>{wastageDialog.batch?.productName}</strong>\n <br />\n Lot: <strong>{wastageDialog.batch?.lotNumber || \"—\"}</strong>\n <br />\n Available to waste: <strong>{wastageDialog.batch?.qtyRemaining ?? 0}</strong>\n </Typography>\n <TextField\n required\n label=\"Quantity to waste\"\n size=\"small\"\n type=\"number\"\n fullWidth\n inputProps={{ min: 1, max: wastageMax }}\n value={wastageDialog.qty}\n onChange={(e) => setWastageDialog({ ...wastageDialog, qty: e.target.value })}\n error={!!wastageQtyError}\n helperText={wastageQtyError || `Units to write off (max ${wastageMax}).`}\n />\n <PmSelect\n label=\"Reason\"\n required\n value={wastageDialog.reason}\n onChange={(v) => setWastageDialog({ ...wastageDialog, reason: v })}\n options={WASTAGE_REASONS}\n />\n <TextField\n label=\"Comments\"\n size=\"small\"\n multiline\n minRows={2}\n fullWidth\n value={wastageDialog.comments}\n onChange={(e) => setWastageDialog({ ...wastageDialog, comments: e.target.value })}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button\n onClick={() => setWastageDialog({ ...wastageDialog, open: false })}\n disabled={submittingWastage}\n >\n Cancel\n </Button>\n <Button\n variant=\"contained\"\n color=\"warning\"\n disabled={!wastageValid || submittingWastage}\n onClick={() => void doWaste()}\n >\n {submittingWastage ? \"Recording…\" : \"Mark Wastage\"}\n </Button>\n </DialogActions>\n </Dialog>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as e } from "./settingsStore-
|
|
1
|
+
import { S as e } from "./settingsStore-BifOsjlB.js";
|
|
2
2
|
import { r as t } from "./format-UOyOu75i.js";
|
|
3
|
-
import { i as n } from "./PmGuard-
|
|
3
|
+
import { i as n } from "./PmGuard-b10TGXEi.js";
|
|
4
4
|
import { useEffect as r, useState as i } from "react";
|
|
5
5
|
import { Fragment as a, jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
6
6
|
import { Avatar as c, Box as l, Chip as u, LinearProgress as d, Stack as f, Typography as p } from "@mui/material";
|
|
@@ -270,4 +270,4 @@ function C({ icon: e, label: t, value: n }) {
|
|
|
270
270
|
//#endregion
|
|
271
271
|
export { S as t };
|
|
272
272
|
|
|
273
|
-
//# sourceMappingURL=MemberProfileCard-
|
|
273
|
+
//# sourceMappingURL=MemberProfileCard-BdUNgHRB.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MemberProfileCard-DwWda-tS.js","names":[],"sources":["../src/components/MemberProfileCard.jsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Avatar, Box, Chip, LinearProgress, Stack, Typography } from '@mui/material';\nimport { alpha } from '@mui/material/styles';\nimport BadgeRoundedIcon from '@mui/icons-material/BadgeRounded';\nimport GroupsRoundedIcon from '@mui/icons-material/GroupsRounded';\nimport CakeRoundedIcon from '@mui/icons-material/CakeRounded';\nimport PaymentsRoundedIcon from '@mui/icons-material/PaymentsRounded';\nimport LockOutlinedIcon from '@mui/icons-material/LockOutlined';\nimport { getMemberProfile } from '@pm/services/pmApi';\nimport { formatNumber } from '@pm/utils/format';\nimport { usePmPermissions } from '@pm/utils/permissions';\n\nfunction initials(name) {\n if (!name) return '?';\n return name.split(/\\s+/).filter(Boolean).slice(0, 2).map((s) => s[0]).join('').toUpperCase();\n}\n\n// Whole-year age from DOB (string or Date). Returns null when DOB is unset.\nfunction computeAge(dob) {\n if (!dob) return null;\n const birth = dob instanceof Date ? dob : new Date(dob);\n if (Number.isNaN(birth.getTime())) return null;\n const today = new Date();\n let age = today.getFullYear() - birth.getFullYear();\n const m = today.getMonth() - birth.getMonth();\n if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age -= 1;\n return age >= 0 ? age : null;\n}\n\n// Profile card for Sales / Payments. Receives the memberId and fetches the\n// profile + computes age inline. Outstanding dues are passed in by the\n// caller because the dues query is separate and the caller often has it\n// loaded already.\nexport default function MemberProfileCard({\n memberId,\n previousDue = 0,\n currencySymbol = '',\n onLoad,\n}) {\n const perms = usePmPermissions();\n const canSeeMember = perms.has('view.manage.productManagement.member');\n const [profile, setProfile] = useState(null);\n const [loading, setLoading] = useState(false);\n const [missing, setMissing] = useState(false);\n\n useEffect(() => {\n if (!memberId) {\n setProfile(null);\n setMissing(false);\n setLoading(false);\n return undefined;\n }\n if (!canSeeMember) {\n // No access — don't fire the /members/{id} call, the filter would 403.\n // The card renders a friendly \"no access\" panel below.\n setProfile(null);\n setMissing(false);\n setLoading(false);\n return undefined;\n }\n let cancelled = false;\n setLoading(true);\n setMissing(false);\n (async () => {\n try {\n const data = await getMemberProfile(memberId);\n if (cancelled) return;\n if (!data) {\n setProfile(null);\n setMissing(true);\n } else {\n setProfile(data);\n setMissing(false);\n onLoad?.(data);\n }\n } catch {\n if (!cancelled) {\n setProfile(null);\n setMissing(true);\n }\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => { cancelled = true; };\n // onLoad is intentionally excluded; passing a fresh function each render\n // shouldn't re-fire the fetch. The caller should memoize if needed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [memberId, canSeeMember]);\n\n if (!canSeeMember && memberId) {\n return (\n <Box sx={(theme) => ({\n p: 2,\n borderRadius: 1.5,\n border: `1px solid ${alpha(theme.palette.warning.main, 0.4)}`,\n bgcolor: alpha(theme.palette.warning.main, 0.05),\n })}>\n <Stack direction=\"row\" alignItems=\"center\" spacing={1.25}>\n <LockOutlinedIcon fontSize=\"small\" sx={{ color: 'warning.dark' }} />\n <Box>\n <Typography variant=\"body2\" sx={{ fontWeight: 700, color: 'warning.dark' }}>\n Member details hidden\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n You don’t have permission to view member profiles. Ask an admin\n to grant access if you need to see this customer’s details.\n </Typography>\n </Box>\n </Stack>\n </Box>\n );\n }\n\n if (loading) {\n return (\n <Box sx={{ p: 2, borderRadius: 1.5, border: '1px solid', borderColor: 'divider' }}>\n <LinearProgress />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ mt: 1, display: 'block' }}>\n Loading member…\n </Typography>\n </Box>\n );\n }\n\n if (missing) {\n return (\n <Box sx={(theme) => ({\n p: 2,\n borderRadius: 1.5,\n border: `1px solid ${alpha(theme.palette.error.main, 0.4)}`,\n bgcolor: alpha(theme.palette.error.main, 0.05),\n })}>\n <Typography variant=\"body2\" sx={{ fontWeight: 700, color: 'error.dark' }}>\n Member not found\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n The id passed via the URL didn’t resolve in the configured member table, or the column mapping in Settings is incomplete.\n </Typography>\n </Box>\n );\n }\n\n if (!profile) return null;\n\n const age = computeAge(profile.dob);\n\n return (\n <Box\n sx={(theme) => ({\n p: 2.5,\n borderRadius: 1.5,\n border: '1px solid',\n borderColor: 'divider',\n background: `linear-gradient(135deg, ${alpha(theme.palette.primary.main, 0.04)} 0%, ${theme.palette.background.paper} 100%)`,\n })}\n >\n <Stack direction={{ xs: 'column', md: 'row' }} alignItems={{ md: 'center' }} spacing={2}>\n <Avatar sx={{ width: 56, height: 56, bgcolor: 'primary.main', fontWeight: 800, fontSize: 18 }}>\n {initials(profile.name)}\n </Avatar>\n <Box sx={{ flexGrow: 1, minWidth: 0 }}>\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.2 }}>\n {profile.name || <Typography component=\"span\" variant=\"caption\" color=\"text.disabled\">No name configured</Typography>}\n </Typography>\n <Stack direction=\"row\" flexWrap=\"wrap\" useFlexGap spacing={1.25} sx={{ mt: 1 }}>\n {profile.identifier ? (\n <DetailChip icon={<BadgeRoundedIcon fontSize=\"small\" />} label=\"Identifier\" value={profile.identifier} />\n ) : null}\n {profile.familyId ? (\n <DetailChip icon={<GroupsRoundedIcon fontSize=\"small\" />} label=\"Family ID\" value={profile.familyId} />\n ) : null}\n {age != null ? (\n <DetailChip icon={<CakeRoundedIcon fontSize=\"small\" />} label=\"Age\" value={`${age} ${age === 1 ? 'year' : 'years'}`} />\n ) : null}\n </Stack>\n </Box>\n <Box sx={{ minWidth: 160, textAlign: { xs: 'left', md: 'right' } }}>\n {previousDue > 0 ? (\n <>\n <Stack direction=\"row\" alignItems=\"center\" spacing={0.75} justifyContent={{ xs: 'flex-start', md: 'flex-end' }} sx={{ color: 'text.secondary' }}>\n <PaymentsRoundedIcon fontSize=\"small\" />\n <Typography variant=\"caption\" sx={{ fontWeight: 700, letterSpacing: 0.6, textTransform: 'uppercase' }}>\n Outstanding\n </Typography>\n </Stack>\n <Typography variant=\"h5\" sx={{ fontWeight: 800, color: 'warning.dark', fontVariantNumeric: 'tabular-nums' }}>\n {currencySymbol}{formatNumber(previousDue)}\n </Typography>\n </>\n ) : (\n <Chip size=\"small\" color=\"success\" label=\"No outstanding dues\" sx={{ fontWeight: 700 }} />\n )}\n </Box>\n </Stack>\n </Box>\n );\n}\n\nfunction DetailChip({ icon, label, value }) {\n return (\n <Box\n sx={(theme) => ({\n display: 'inline-flex',\n alignItems: 'center',\n gap: 0.75,\n px: 1,\n py: 0.4,\n borderRadius: 1.5,\n bgcolor: alpha(theme.palette.primary.main, 0.06),\n border: `1px solid ${alpha(theme.palette.primary.main, 0.16)}`,\n color: 'text.primary',\n })}\n >\n <Box sx={{ display: 'flex', color: 'text.secondary' }}>{icon}</Box>\n <Typography component=\"span\" variant=\"caption\" color=\"text.secondary\" sx={{ fontWeight: 700, letterSpacing: 0.4, textTransform: 'uppercase' }}>\n {label}\n </Typography>\n <Typography component=\"span\" variant=\"body2\" sx={{ fontWeight: 700 }}>\n {value}\n </Typography>\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MemberProfileCard-BdUNgHRB.js","names":[],"sources":["../src/components/MemberProfileCard.jsx"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { Avatar, Box, Chip, LinearProgress, Stack, Typography } from '@mui/material';\nimport { alpha } from '@mui/material/styles';\nimport BadgeRoundedIcon from '@mui/icons-material/BadgeRounded';\nimport GroupsRoundedIcon from '@mui/icons-material/GroupsRounded';\nimport CakeRoundedIcon from '@mui/icons-material/CakeRounded';\nimport PaymentsRoundedIcon from '@mui/icons-material/PaymentsRounded';\nimport LockOutlinedIcon from '@mui/icons-material/LockOutlined';\nimport { getMemberProfile } from '@pm/services/pmApi';\nimport { formatNumber } from '@pm/utils/format';\nimport { usePmPermissions } from '@pm/utils/permissions';\n\nfunction initials(name) {\n if (!name) return '?';\n return name.split(/\\s+/).filter(Boolean).slice(0, 2).map((s) => s[0]).join('').toUpperCase();\n}\n\n// Whole-year age from DOB (string or Date). Returns null when DOB is unset.\nfunction computeAge(dob) {\n if (!dob) return null;\n const birth = dob instanceof Date ? dob : new Date(dob);\n if (Number.isNaN(birth.getTime())) return null;\n const today = new Date();\n let age = today.getFullYear() - birth.getFullYear();\n const m = today.getMonth() - birth.getMonth();\n if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) age -= 1;\n return age >= 0 ? age : null;\n}\n\n// Profile card for Sales / Payments. Receives the memberId and fetches the\n// profile + computes age inline. Outstanding dues are passed in by the\n// caller because the dues query is separate and the caller often has it\n// loaded already.\nexport default function MemberProfileCard({\n memberId,\n previousDue = 0,\n currencySymbol = '',\n onLoad,\n}) {\n const perms = usePmPermissions();\n const canSeeMember = perms.has('view.manage.productManagement.member');\n const [profile, setProfile] = useState(null);\n const [loading, setLoading] = useState(false);\n const [missing, setMissing] = useState(false);\n\n useEffect(() => {\n if (!memberId) {\n setProfile(null);\n setMissing(false);\n setLoading(false);\n return undefined;\n }\n if (!canSeeMember) {\n // No access — don't fire the /members/{id} call, the filter would 403.\n // The card renders a friendly \"no access\" panel below.\n setProfile(null);\n setMissing(false);\n setLoading(false);\n return undefined;\n }\n let cancelled = false;\n setLoading(true);\n setMissing(false);\n (async () => {\n try {\n const data = await getMemberProfile(memberId);\n if (cancelled) return;\n if (!data) {\n setProfile(null);\n setMissing(true);\n } else {\n setProfile(data);\n setMissing(false);\n onLoad?.(data);\n }\n } catch {\n if (!cancelled) {\n setProfile(null);\n setMissing(true);\n }\n } finally {\n if (!cancelled) setLoading(false);\n }\n })();\n return () => { cancelled = true; };\n // onLoad is intentionally excluded; passing a fresh function each render\n // shouldn't re-fire the fetch. The caller should memoize if needed.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [memberId, canSeeMember]);\n\n if (!canSeeMember && memberId) {\n return (\n <Box sx={(theme) => ({\n p: 2,\n borderRadius: 1.5,\n border: `1px solid ${alpha(theme.palette.warning.main, 0.4)}`,\n bgcolor: alpha(theme.palette.warning.main, 0.05),\n })}>\n <Stack direction=\"row\" alignItems=\"center\" spacing={1.25}>\n <LockOutlinedIcon fontSize=\"small\" sx={{ color: 'warning.dark' }} />\n <Box>\n <Typography variant=\"body2\" sx={{ fontWeight: 700, color: 'warning.dark' }}>\n Member details hidden\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n You don’t have permission to view member profiles. Ask an admin\n to grant access if you need to see this customer’s details.\n </Typography>\n </Box>\n </Stack>\n </Box>\n );\n }\n\n if (loading) {\n return (\n <Box sx={{ p: 2, borderRadius: 1.5, border: '1px solid', borderColor: 'divider' }}>\n <LinearProgress />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ mt: 1, display: 'block' }}>\n Loading member…\n </Typography>\n </Box>\n );\n }\n\n if (missing) {\n return (\n <Box sx={(theme) => ({\n p: 2,\n borderRadius: 1.5,\n border: `1px solid ${alpha(theme.palette.error.main, 0.4)}`,\n bgcolor: alpha(theme.palette.error.main, 0.05),\n })}>\n <Typography variant=\"body2\" sx={{ fontWeight: 700, color: 'error.dark' }}>\n Member not found\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n The id passed via the URL didn’t resolve in the configured member table, or the column mapping in Settings is incomplete.\n </Typography>\n </Box>\n );\n }\n\n if (!profile) return null;\n\n const age = computeAge(profile.dob);\n\n return (\n <Box\n sx={(theme) => ({\n p: 2.5,\n borderRadius: 1.5,\n border: '1px solid',\n borderColor: 'divider',\n background: `linear-gradient(135deg, ${alpha(theme.palette.primary.main, 0.04)} 0%, ${theme.palette.background.paper} 100%)`,\n })}\n >\n <Stack direction={{ xs: 'column', md: 'row' }} alignItems={{ md: 'center' }} spacing={2}>\n <Avatar sx={{ width: 56, height: 56, bgcolor: 'primary.main', fontWeight: 800, fontSize: 18 }}>\n {initials(profile.name)}\n </Avatar>\n <Box sx={{ flexGrow: 1, minWidth: 0 }}>\n <Typography variant=\"h6\" sx={{ fontWeight: 800, lineHeight: 1.2 }}>\n {profile.name || <Typography component=\"span\" variant=\"caption\" color=\"text.disabled\">No name configured</Typography>}\n </Typography>\n <Stack direction=\"row\" flexWrap=\"wrap\" useFlexGap spacing={1.25} sx={{ mt: 1 }}>\n {profile.identifier ? (\n <DetailChip icon={<BadgeRoundedIcon fontSize=\"small\" />} label=\"Identifier\" value={profile.identifier} />\n ) : null}\n {profile.familyId ? (\n <DetailChip icon={<GroupsRoundedIcon fontSize=\"small\" />} label=\"Family ID\" value={profile.familyId} />\n ) : null}\n {age != null ? (\n <DetailChip icon={<CakeRoundedIcon fontSize=\"small\" />} label=\"Age\" value={`${age} ${age === 1 ? 'year' : 'years'}`} />\n ) : null}\n </Stack>\n </Box>\n <Box sx={{ minWidth: 160, textAlign: { xs: 'left', md: 'right' } }}>\n {previousDue > 0 ? (\n <>\n <Stack direction=\"row\" alignItems=\"center\" spacing={0.75} justifyContent={{ xs: 'flex-start', md: 'flex-end' }} sx={{ color: 'text.secondary' }}>\n <PaymentsRoundedIcon fontSize=\"small\" />\n <Typography variant=\"caption\" sx={{ fontWeight: 700, letterSpacing: 0.6, textTransform: 'uppercase' }}>\n Outstanding\n </Typography>\n </Stack>\n <Typography variant=\"h5\" sx={{ fontWeight: 800, color: 'warning.dark', fontVariantNumeric: 'tabular-nums' }}>\n {currencySymbol}{formatNumber(previousDue)}\n </Typography>\n </>\n ) : (\n <Chip size=\"small\" color=\"success\" label=\"No outstanding dues\" sx={{ fontWeight: 700 }} />\n )}\n </Box>\n </Stack>\n </Box>\n );\n}\n\nfunction DetailChip({ icon, label, value }) {\n return (\n <Box\n sx={(theme) => ({\n display: 'inline-flex',\n alignItems: 'center',\n gap: 0.75,\n px: 1,\n py: 0.4,\n borderRadius: 1.5,\n bgcolor: alpha(theme.palette.primary.main, 0.06),\n border: `1px solid ${alpha(theme.palette.primary.main, 0.16)}`,\n color: 'text.primary',\n })}\n >\n <Box sx={{ display: 'flex', color: 'text.secondary' }}>{icon}</Box>\n <Typography component=\"span\" variant=\"caption\" color=\"text.secondary\" sx={{ fontWeight: 700, letterSpacing: 0.4, textTransform: 'uppercase' }}>\n {label}\n </Typography>\n <Typography component=\"span\" variant=\"body2\" sx={{ fontWeight: 700 }}>\n {value}\n </Typography>\n </Box>\n );\n}\n"],"mappings":"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"}
|