@argusoft/medplat-product-management 1.0.8 → 1.0.10
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-DmRW5bhm.js → BrandsPage-CdcAvaml.js} +7 -7
- package/dist/{BrandsPage-DmRW5bhm.js.map → BrandsPage-CdcAvaml.js.map} +1 -1
- package/dist/CategoriesPage-BprQEYJw.js +398 -0
- package/dist/CategoriesPage-BprQEYJw.js.map +1 -0
- package/dist/{DashboardPage-C3bKc5na.js → DashboardPage-CTiGSx71.js} +14 -14
- package/dist/{DashboardPage-C3bKc5na.js.map → DashboardPage-CTiGSx71.js.map} +1 -1
- package/dist/FacilityPicker-BXa2VF7i.js +112 -0
- package/dist/FacilityPicker-BXa2VF7i.js.map +1 -0
- package/dist/InventoryPage-JadYMCzU.js +1005 -0
- package/dist/InventoryPage-JadYMCzU.js.map +1 -0
- package/dist/{MemberProfileCard-BdUNgHRB.js → MemberProfileCard-01d_jz3w.js} +3 -3
- package/dist/{MemberProfileCard-BdUNgHRB.js.map → MemberProfileCard-01d_jz3w.js.map} +1 -1
- package/dist/{PaymentCreatePage-CIVe0P0p.js → PaymentCreatePage-D6NZBUC3.js} +12 -12
- package/dist/{PaymentCreatePage-CIVe0P0p.js.map → PaymentCreatePage-D6NZBUC3.js.map} +1 -1
- package/dist/{PaymentsPage-BvPe5Q9F.js → PaymentsPage-CHieGNrn.js} +4 -4
- package/dist/{PaymentsPage-BvPe5Q9F.js.map → PaymentsPage-CHieGNrn.js.map} +1 -1
- package/dist/{PickupBatchLinesEditor-DHmcAG23.js → PickupBatchLinesEditor-QPaqFtUO.js} +2 -2
- package/dist/{PickupBatchLinesEditor-DHmcAG23.js.map → PickupBatchLinesEditor-QPaqFtUO.js.map} +1 -1
- package/dist/{PickupCreatePage-CB0Sl0Hb.js → PickupCreatePage-C7_67u_u.js} +207 -187
- package/dist/PickupCreatePage-C7_67u_u.js.map +1 -0
- package/dist/{PickupsPage-Ctpghavs.js → PickupsPage-BWblMlic.js} +18 -18
- package/dist/PickupsPage-BWblMlic.js.map +1 -0
- package/dist/{PmGuard-b10TGXEi.js → PmGuard-BvUUbU80.js} +214 -172
- package/dist/PmGuard-BvUUbU80.js.map +1 -0
- package/dist/{PmTable-_GbScaa4.js → PmTable-COZEuDAr.js} +6 -4
- package/dist/{PmTable-_GbScaa4.js.map → PmTable-COZEuDAr.js.map} +1 -1
- package/dist/ProductEditPage-Dbq4vUAA.js +1514 -0
- package/dist/ProductEditPage-Dbq4vUAA.js.map +1 -0
- package/dist/ProductsPage-BI22nPb4.js +253 -0
- package/dist/ProductsPage-BI22nPb4.js.map +1 -0
- package/dist/PurchaseCreatePage-C0EK9z4w.js +723 -0
- package/dist/PurchaseCreatePage-C0EK9z4w.js.map +1 -0
- package/dist/{PurchaseRequestDetailPage-Dg_MRY_N.js → PurchaseRequestDetailPage-DhBuOGHZ.js} +233 -223
- package/dist/PurchaseRequestDetailPage-DhBuOGHZ.js.map +1 -0
- package/dist/{PurchaseRequestsPage-DPAPzpmm.js → PurchaseRequestsPage-Bj9HUMxb.js} +12 -12
- package/dist/{PurchaseRequestsPage-DPAPzpmm.js.map → PurchaseRequestsPage-Bj9HUMxb.js.map} +1 -1
- package/dist/{PurchasesPage-It1GHiXl.js → PurchasesPage-Djl1AqD_.js} +4 -4
- package/dist/{PurchasesPage-It1GHiXl.js.map → PurchasesPage-Djl1AqD_.js.map} +1 -1
- package/dist/{SaleCreatePage-C9kFmTvm.js → SaleCreatePage-DHBLmlBr.js} +15 -15
- package/dist/{SaleCreatePage-C9kFmTvm.js.map → SaleCreatePage-DHBLmlBr.js.map} +1 -1
- package/dist/{SalesPage-D18DCseD.js → SalesPage-eF9Yr42z.js} +4 -4
- package/dist/{SalesPage-D18DCseD.js.map → SalesPage-eF9Yr42z.js.map} +1 -1
- package/dist/SettingsPage-Cre-UE3r.js +739 -0
- package/dist/SettingsPage-Cre-UE3r.js.map +1 -0
- package/dist/StorageUnitsPage-CTEVuemZ.js +745 -0
- package/dist/StorageUnitsPage-CTEVuemZ.js.map +1 -0
- package/dist/{TransactionsPage-DrH53pVS.js → TransactionsPage-Zuu7sjID.js} +21 -6
- package/dist/TransactionsPage-Zuu7sjID.js.map +1 -0
- package/dist/TransferCreatePage-DlT0nCDj.js +635 -0
- package/dist/TransferCreatePage-DlT0nCDj.js.map +1 -0
- package/dist/TransfersPage-DqaSvHda.js +593 -0
- package/dist/TransfersPage-DqaSvHda.js.map +1 -0
- package/dist/VialsPage-LoDaUqVv.js +454 -0
- package/dist/VialsPage-LoDaUqVv.js.map +1 -0
- package/dist/{constants-ClZJY2x1.js → constants-BTpJFQe_.js} +127 -32
- package/dist/constants-BTpJFQe_.js.map +1 -0
- package/dist/index.js +128 -105
- package/dist/index.js.map +1 -1
- package/dist/listValueStore-5d4vNGQM.js +43 -0
- package/dist/listValueStore-5d4vNGQM.js.map +1 -0
- package/dist/{settingsStore-BifOsjlB.js → settingsStore-Df_wGo55.js} +84 -57
- package/dist/settingsStore-Df_wGo55.js.map +1 -0
- package/package.json +1 -1
- package/dist/CategoriesPage-BsS5IQh3.js +0 -391
- package/dist/CategoriesPage-BsS5IQh3.js.map +0 -1
- package/dist/FacilityPicker-BiZeupmP.js +0 -79
- package/dist/FacilityPicker-BiZeupmP.js.map +0 -1
- package/dist/InventoryPage-C_AxSy7A.js +0 -655
- package/dist/InventoryPage-C_AxSy7A.js.map +0 -1
- package/dist/PickupCreatePage-CB0Sl0Hb.js.map +0 -1
- package/dist/PickupsPage-Ctpghavs.js.map +0 -1
- package/dist/PmGuard-b10TGXEi.js.map +0 -1
- package/dist/ProductEditPage-C1thoKHu.js +0 -1294
- package/dist/ProductEditPage-C1thoKHu.js.map +0 -1
- package/dist/ProductsPage-CupVmOD4.js +0 -252
- package/dist/ProductsPage-CupVmOD4.js.map +0 -1
- package/dist/PurchaseCreatePage-YD3NmhPQ.js +0 -648
- package/dist/PurchaseCreatePage-YD3NmhPQ.js.map +0 -1
- package/dist/PurchaseRequestDetailPage-Dg_MRY_N.js.map +0 -1
- package/dist/SettingsPage-VsxI-ano.js +0 -746
- package/dist/SettingsPage-VsxI-ano.js.map +0 -1
- package/dist/StorageUnitsPage-7EqwuMOI.js +0 -718
- package/dist/StorageUnitsPage-7EqwuMOI.js.map +0 -1
- package/dist/TransactionsPage-DrH53pVS.js.map +0 -1
- package/dist/TransferCreatePage-CuvoMrYH.js +0 -315
- package/dist/TransferCreatePage-CuvoMrYH.js.map +0 -1
- package/dist/TransfersPage-Cf701t8s.js +0 -470
- package/dist/TransfersPage-Cf701t8s.js.map +0 -1
- package/dist/VialsPage-8oAc1yFv.js +0 -320
- package/dist/VialsPage-8oAc1yFv.js.map +0 -1
- package/dist/constants-ClZJY2x1.js.map +0 -1
- package/dist/settingsStore-BifOsjlB.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as e, F as t, G as n, K as r, V as i, i as a, k as o, r as s, v as c, y as l } from "./settingsStore-Df_wGo55.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-BvUUbU80.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";
|
|
@@ -187,7 +187,7 @@ R.propTypes = {
|
|
|
187
187
|
onItemClick: x.func.isRequired
|
|
188
188
|
};
|
|
189
189
|
function z() {
|
|
190
|
-
let x = P(), L = N(), z =
|
|
190
|
+
let x = P(), L = N(), z = s(), B = p(), V = a(z?.currencyCode), H = h(B, z), U = B.has("view.manage.productManagement.ledger"), [W, G] = v(!0), [K, q] = v({
|
|
191
191
|
categories: 0,
|
|
192
192
|
brands: 0,
|
|
193
193
|
products: 0,
|
|
@@ -200,18 +200,18 @@ function z() {
|
|
|
200
200
|
async function J() {
|
|
201
201
|
G(!0);
|
|
202
202
|
try {
|
|
203
|
-
let
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
203
|
+
let a = (e, t) => B.has(e) ? t().catch(() => []) : Promise.resolve([]), [s, u, d, f, p, m, h, g] = await Promise.all([
|
|
204
|
+
a("view.manage.productManagement.category", () => l()),
|
|
205
|
+
a("view.manage.productManagement.brand", () => c()),
|
|
206
|
+
a("view.manage.productManagement.product", () => o()),
|
|
207
|
+
a("view.manage.productManagement.inventory", () => t()),
|
|
208
|
+
a("view.manage.productManagement.vial", () => e()),
|
|
209
|
+
a("view.manage.productManagement.sale", () => n(0, 100)),
|
|
210
|
+
a("view.manage.productManagement.payment", () => i(0, 100)),
|
|
211
|
+
a("view.manage.productManagement.ledger", () => r(0, 8))
|
|
212
212
|
]);
|
|
213
213
|
q({
|
|
214
|
-
categories:
|
|
214
|
+
categories: s.length,
|
|
215
215
|
brands: u.length,
|
|
216
216
|
products: d.length,
|
|
217
217
|
batches: f.length,
|
|
@@ -433,4 +433,4 @@ function z() {
|
|
|
433
433
|
//#endregion
|
|
434
434
|
export { z as default };
|
|
435
435
|
|
|
436
|
-
//# sourceMappingURL=DashboardPage-
|
|
436
|
+
//# sourceMappingURL=DashboardPage-CTiGSx71.js.map
|
|
@@ -1 +1 @@
|
|
|
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":";;;;;;;;;;;AAsCA,IAAM,IAAY;CAChB,SAAS;CACT,MAAM;CACN,SAAS;CACT,aAAa;CACb,gBAAgB;CAChB,SAAS;CACT,QAAQ;CACR,YAAY;CACZ,QAAQ;CACR,cAAc;CACd,cAAc;CACd,aAAa;AACf;AAEA,SAAS,EAAW,EAAE,UAAO,UAAO,cAAW;CAC7C,OACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;EAAY,SAAQ;EAAU,OAAM;EAAiB,IAAI;GAAE,YAAY;GAAK,eAAe;EAAI;YAC5F,EAAM,YAAY;CACT,CAAA,GACX,IACC,kBAAC,GAAD;EAAU,OAAO;EAAI,QAAQ;CAAK,CAAA,IAElC,kBAAC,GAAD;EAAY,SAAQ;EAAK,IAAI;GAAE,YAAY;GAAK,oBAAoB;GAAgB,YAAY;GAAK,IAAI;EAAK;YAC3G;CACS,CAAA,CAEX,EAAA,CAAA;AAET;AACA,EAAW,YAAY;CACrB,OAAO,EAAU;CACjB,OAAO,EAAU;CACjB,SAAS,EAAU;AACrB;AAEA,SAAS,EAAc,EAAE,cAAW,YAAS,YAAS,kBAAe;CACnE,IAAM,IAAQ,EAAS,GACjB,IAAO,EAAU;CAEvB,OACE,kBAAC,GAAD;EACE,IAAI;GACF,cAAc;GACd,QAAQ;GACR,UAAU;GACV,UAAU;GACV,QAAQ,aAAa,EAAM,EAAU,QAAQ,GAAI;GACjD,YAAY,2BAA2B,EAAM,EAAU,QAAQ,GAAI,EAAE,OAAO,EAAM,EAAM,QAAQ,WAAW,OAAO,CAAC,EAAE;GACrH,WAAW,eAAe,EAAM,EAAU,QAAQ,GAAI;GACtD,YAAY;GACZ,WAAW;IACT,WAAW;IACX,WAAW,eAAe,EAAM,EAAU,QAAQ,GAAI;GACxD;GAEA,aAAa;IACX,SAAS;IACT,UAAU;IACV,MAAM;IACN,OAAO;IACP,KAAK;IACL,QAAQ;IACR,YAAY,0BAA0B,EAAU,OAAO,OAAO,EAAU,WAAW;GACrF;EACF;YAEA,kBAAC,GAAD;GAAa,IAAI;IAAE,IAAI;IAAG,SAAS;IAAQ,eAAe;IAAU,QAAQ;GAAO;aAAnF;IACE,kBAAC,GAAD;KAAO,WAAU;KAAM,YAAW;KAAS,SAAS;eAApD,CACE,kBAAC,GAAD;MACE,IAAI;OACF,OAAO;OACP,QAAQ;OACR,cAAc;OACd,SAAS;OACT,YAAY;OACZ,OAAO;OACP,YAAY,2BAA2B,EAAU,OAAO,OAAO,EAAU,WAAW;OACpF,WAAW,cAAc,EAAM,EAAU,QAAQ,EAAG;MACtD;gBAEA,kBAAC,GAAD,EAAM,IAAI,EAAE,UAAU,GAAG,EAAI,CAAA;KAC1B,CAAA,GACL,kBAAC,GAAD;MAAK,IAAI;OAAE,UAAU;OAAG,UAAU;MAAE;gBAApC,CACE,kBAAC,GAAD;OAAY,SAAQ;OAAW,IAAI;QAAE,OAAO,EAAU;QAAY,YAAY;QAAK,eAAe;QAAK,YAAY;OAAE;iBAAG;MAE5G,CAAA,GACZ,kBAAC,GAAD;OAAY,SAAQ;OAAK,IAAI;QAAE,YAAY;QAAK,YAAY;OAAI;iBAC7D,EAAU;MACD,CAAA,CACT;OACA;;IAEP,kBAAC,GAAD;KAAY,SAAQ;KAAQ,OAAM;KAAiB,IAAI;MAAE,IAAI;MAAK,IAAI;KAAE;eACrE,EAAU;IACD,CAAA;IAGX,GAAS,SACR,kBAAC,GAAD;KAAO,WAAU;KAAM,SAAS;KAAG,IAAI,EAAE,IAAI,IAAI;eAC9C,EAAQ,KAAK,MACZ,kBAAC,GAAD;MAA0B,OAAO,EAAE;MAAO,OAAO,EAAE;MAAgB;KAAU,GAA5D,EAAE,KAA0D,CAC9E;IACI,CAAA,IACL;IAGJ,kBAAC,GAAD;KAAK,IAAI,EAAE,IAAI,OAAO;eACpB,kBAAC,GAAD;MAAO,WAAU;MAAM,SAAS;MAAG,UAAS;MAAO,YAAA;gBAChD,EAAU,MAAM,KAAK,MAAO;OAC3B,IAAM,IAAW,EAAG;OACpB,OACE,kBAAC,GAAD;QAEE,MAAM,IAAW,kBAAC,GAAD,EAAU,IAAI;SAAE,UAAU;SAAmB,OAAO,GAAG,EAAU,WAAW;QAAa,EAAI,CAAA,IAAI,KAAA;QAClH,OAAO,EAAG;QACV,eAAe,EAAY,CAAE;QAC7B,IAAI;SACF,cAAc;SACd,YAAY;SACZ,iBAAiB,EAAM,EAAU,QAAQ,GAAI;SAC7C,OAAO,EAAU;SACjB,QAAQ,aAAa,EAAM,EAAU,QAAQ,GAAI;SACjD,YAAY;SACZ,WAAW;UACT,iBAAiB,EAAM,EAAU,QAAQ,GAAI;UAC7C,WAAW;SACb;QACF;OACD,GAhBM,EAAG,EAgBT;MAEL,CAAC;KACI,CAAA;IACJ,CAAA;GACM;;CACT,CAAA;AAEV;AACA,EAAc,YAAY;CACxB,WAAW,EAAU,OAAO;CAC5B,SAAS,EAAU;CACnB,SAAS,EAAU;CACnB,aAAa,EAAU,KAAK;AAC9B;AAEA,SAAwB,IAAgB;CACtC,IAAM,IAAW,EAAY,GACvB,IAAQ,EAAS,GACjB,IAAW,EAAc,GACzB,IAAQ,EAAiB,GACzB,IAAM,EAAe,GAAU,YAAY,GAG3C,IAAoB,EAAqB,GAAO,CAAQ,GACxD,IAAqB,EAAM,IAAI,sCAAsC,GACrE,CAAC,GAAS,KAAc,EAAS,EAAI,GACrC,CAAC,GAAM,KAAW,EAAS;EAC/B,YAAY;EAAG,QAAQ;EAAG,UAAU;EACpC,SAAS;EAAG,WAAW;EAAG,OAAO;EAAG,UAAU;EAC9C,MAAM,CAAC;CACT,CAAC;CAED,eAAe,IAAO;EACpB,EAAW,EAAI;EACf,IAAI;GAMF,IAAM,KAAQ,GAAM,MAAO,EAAM,IAAI,CAAI,IAAI,EAAG,EAAE,YAAY,CAAC,CAAC,IAAI,QAAQ,QAAQ,CAAC,CAAC,GAChF,CAAC,GAAM,GAAQ,GAAU,GAAS,GAAO,GAAO,GAAU,KAAQ,MAAM,QAAQ,IAAI;IACxF,EAAK,gDAAkD,EAAc,CAAC;IACtE,EAAK,6CAAkD,EAAU,CAAC;IAClE,EAAK,+CAAkD,EAAY,CAAC;IACpE,EAAK,iDAAkD,EAAY,CAAC;IACpE,EAAK,4CAAkD,EAAc,CAAC;IACtE,EAAK,4CAAkD,EAAU,GAAG,GAAG,CAAC;IACxE,EAAK,+CAAkD,EAAa,GAAG,GAAG,CAAC;IAC3E,EAAK,8CAAkD,EAAiB,GAAG,CAAC,CAAC;GAC/E,CAAC;GACD,EAAQ;IACN,YAAY,EAAK;IACjB,QAAQ,EAAO;IACf,UAAU,EAAS;IACnB,SAAS,EAAQ;IACjB,WAAW,EAAM;IACjB,OAAO,EAAM;IACb,UAAU,EAAS;IACnB;GACF,CAAC;EACH,UAAU;GACR,EAAW,EAAK;EAClB;CACF;CAIA,EAAsB,uCAAuC;EAC3D,EAAU;CAEZ,GAAG,CAAC,CAAC;CAEL,IAAM,IAAqB;EACzB,SAAY;GACV;IAAE,OAAO;IAAc,OAAO,EAAK;GAAW;GAC9C;IAAE,OAAO;IAAc,OAAO,EAAK;GAAO;GAC1C;IAAE,OAAO;IAAc,OAAO,EAAK;GAAS;EAC9C;EACA,YAAY;GACV;IAAE,OAAO;IAAc,OAAO,EAAK;GAAQ;GAC3C;IAAE,OAAO;IAAc,OAAO,EAAK;GAAU;GAC7C;IAAE,OAAO;IAAc,OAAO,EAAK;GAAM;EAC3C;EACA,QAAY,CACV;GAAE,OAAO;GAAc,OAAO,EAAK,KAAK;EAAO,CACjD;CACF;CAEA,OACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,CAAQ,CAAA,GACT,kBAAC,GAAD;EAAS,MAAM;YACd,kBAAC,GAAD;GACE,OAAM;GACN,UAAS;GACT,OAAO;IAAC;IAAW;IAAiB;GAAe;aAHrD;IAKG,IAAU,kBAAC,GAAD,EAAgB,IAAI;KAAE,IAAI;KAAG,cAAc;IAAE,EAAI,CAAA,IAAI;IAEhE,kBAAC,GAAD;KAAM,WAAA;KAAU,SAAS;eACtB,EAAkB,KAAK,GAAI,MAC1B,kBAAC,GAAD;MAAM,MAAA;MAAK,IAAI;MAAI,IAAI;gBACrB,kBAAC,GAAD;OAAM,IAAA;OAAG,SAAS;OAAK,OAAO,EAAE,iBAAiB,GAAG,IAAM,GAAG,IAAI;iBAC/D,kBAAC,GAAD;QAAK,IAAI,EAAE,QAAQ,OAAO;kBACxB,kBAAC,GAAD;SACE,WAAW;SACX,SAAS,EAAmB,EAAG;SACtB;SACT,cAAc,MAAO,EAAS,EAAG,IAAI;QACtC,CAAA;OACE,CAAA;MACD,CAAA;KACF,GAXyB,EAAG,EAW5B,CACP;IACG,CAAA;IAKL,IACD,kBAAC,GAAD;KAAM,IAAI;MAAE,cAAc;MAAK,IAAI;MAAG,WAAW,eAAe,EAAM,EAAM,QAAQ,OAAO,OAAO,GAAI;KAAI;eACxG,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAO,WAAU;MAAM,gBAAe;MAAgB,YAAW;MAAS,IAAI,EAAE,IAAI,IAAI;gBAAxF,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;OAAY,SAAQ;OAAW,IAAI;QAAE,OAAO;QAAkB,YAAY;QAAK,eAAe;OAAE;iBAAG;MAEvF,CAAA,GACZ,kBAAC,GAAD;OAAY,SAAQ;OAAK,IAAI;QAAE,YAAY;QAAK,YAAY;OAAI;iBAAG;MAEvD,CAAA,CACT,EAAA,CAAA,GACL,kBAAC,GAAD;OACE,WAAA;OACA,MAAM,kBAAC,GAAD,EAAyB,IAAI,EAAE,UAAU,kBAAkB,EAAI,CAAA;OACrE,OAAM;OACN,eAAe,EAAS,oCAAoC;OAC5D,IAAI;QACF,cAAc;QAAG,YAAY;QAC7B,SAAS,EAAM,EAAM,QAAQ,QAAQ,MAAM,GAAI;QAC/C,OAAO;QACP,WAAW,EAAE,SAAS,EAAM,EAAM,QAAQ,QAAQ,MAAM,GAAI,EAAE;OAChE;MACD,CAAA,CACI;SAEN,EAAK,KAAK,WAAW,KAAK,CAAC,IAC1B,kBAAC,GAAD;MAAY,SAAQ;MAAQ,OAAM;gBAAiB;KAA8E,CAAA,IAEjI,kBAAC,GAAD;MAAO,SAAS,kBAAC,GAAD,EAAK,IAAI,EAAE,WAAW,cAAc,EAAM,QAAQ,UAAU,EAAI,CAAA;MAAG,SAAS;iBACxF,IAAU,MAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAK,MAAM,KAAK,GAAG,MACzD,kBAAC,GAAD;OAAsB,IAAI;QACxB,SAAS;QAAQ,gBAAgB;QAAiB,YAAY;QAC9D,IAAI;QAAM,KAAK;OACjB;iBAHA,CAIE,kBAAC,GAAD;QAAO,WAAU;QAAM,YAAW;QAAS,SAAS;QAAK,IAAI,EAAE,UAAU,EAAE;kBAA3E,CACG,IACC,kBAAC,GAAD;SAAU,SAAQ;SAAU,OAAO;SAAI,QAAQ;QAAK,CAAA,IAEpD,kBAAC,GAAD;SACE,MAAK;SACL,OAAO,EAAE;SACT,OAAO,EAAU,EAAE,oBAAoB;SACvC,IAAI;UAAE,YAAY;UAAK,cAAc;UAAK,QAAQ;SAAG;QACtD,CAAA,GAEH,kBAAC,GAAD;SAAK,IAAI,EAAE,UAAU,EAAE;mBAAvB,CACG,IACC,kBAAC,GAAD;UAAU,OAAO;UAAK,QAAQ;SAAK,CAAA,IAEnC,kBAAC,GAAD;UAAY,SAAQ;UAAQ,IAAI;WAAE,YAAY;WAAK,YAAY;UAAI;UAAG,QAAA;oBAAtE,CACG,EAAE,eAAe,KACjB,EAAE,YACD,kBAAC,GAAD;WAAY,WAAU;WAAO,SAAQ;WAAQ,OAAM;WAAiB,IAAI;YAAE,YAAY;YAAa,IAAI;WAAE;qBAAzG,CAA4G,QACrG,EAAE,SACG;eACV,IACM;aAEb,IACC,kBAAC,GAAD;UAAU,OAAO;UAAK,QAAQ;SAAK,CAAA,IAEnC,kBAAC,GAAD;UAAY,SAAQ;UAAU,OAAM;oBACjC,EAAe,EAAE,eAAe;SACvB,CAAA,CAEX;UACA;WAEP,kBAAC,GAAD;QAAK,IAAI;SAAE,WAAW;SAAS,YAAY;QAAS;kBACjD,IACC,kBAAC,GAAD;SAAU,OAAO;SAAI,QAAQ;QAAK,CAAA,IAElC,kBAAC,GAAD;SAAY,SAAQ;SAAQ,IAAI;UAAE,YAAY;UAAK,oBAAoB;SAAe;mBAAtF;UACG,EAAE,YAAY,OAA6B,KAAtB,OAAO,EAAE;UAC9B,EAAE,YAAY,OAA4E,KAArE,GAAG,EAAE,YAAY,OAAe,KAAR,QAAa,EAAa,EAAE,QAAQ,EAAE;UACnF,EAAE,kBAAkB,OAA6G,KAAtG,GAAI,EAAE,YAAY,QAAQ,EAAE,YAAY,OAAQ,QAAQ,KAAK,IAAM,EAAa,EAAE,cAAc;SAClH;;OAEX,CAAA,CACF;SAjDK,GAAG,MAAM,CAiDd,CACN;KACI,CAAA,CAEE,EAAA,CAAA;IACT,CAAA,IACF;GACO;;CACL,CAAA,CACL,EAAA,CAAA;AAET"}
|
|
1
|
+
{"version":3,"file":"DashboardPage-CTiGSx71.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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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { L as e, at as t, b as n } from "./settingsStore-Df_wGo55.js";
|
|
2
|
+
import { useEffect as r, useMemo as i, useState as a } from "react";
|
|
3
|
+
import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
4
|
+
import { Autocomplete as l, CircularProgress as u, TextField as d } from "@mui/material";
|
|
5
|
+
//#region src/components/FacilityPicker.jsx
|
|
6
|
+
function f({ value: f, onChange: p, label: m = "Facility", scope: h = "assigned", required: g = !1, disabled: _ = !1, error: v = !1, helperText: y = "", onBlur: b, size: x = "small", fullWidth: S = !0 }) {
|
|
7
|
+
let C = h === "all", [w, T] = a(!1), [E, D] = a([]), [O, k] = a(!1), [A, j] = a(!1), [M, N] = a(null), [P, F] = a("");
|
|
8
|
+
r(() => {
|
|
9
|
+
if (!f) {
|
|
10
|
+
N(null);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (M?.id === f) return;
|
|
14
|
+
let e = E.find((e) => e.id === f);
|
|
15
|
+
if (e) {
|
|
16
|
+
N(e);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
let t = !1;
|
|
20
|
+
return (async () => {
|
|
21
|
+
try {
|
|
22
|
+
let e = await n([f]);
|
|
23
|
+
!t && e[0] && N(e[0]);
|
|
24
|
+
} catch {}
|
|
25
|
+
})(), () => {
|
|
26
|
+
t = !0;
|
|
27
|
+
};
|
|
28
|
+
}, [f]), r(() => {
|
|
29
|
+
if (C || !w || A) return;
|
|
30
|
+
let t = !1;
|
|
31
|
+
return k(!0), (async () => {
|
|
32
|
+
try {
|
|
33
|
+
let n = await e();
|
|
34
|
+
t || D(n);
|
|
35
|
+
} catch {
|
|
36
|
+
t || D([]);
|
|
37
|
+
} finally {
|
|
38
|
+
t || (k(!1), j(!0));
|
|
39
|
+
}
|
|
40
|
+
})(), () => {
|
|
41
|
+
t = !0;
|
|
42
|
+
};
|
|
43
|
+
}, [
|
|
44
|
+
C,
|
|
45
|
+
w,
|
|
46
|
+
A
|
|
47
|
+
]), r(() => {
|
|
48
|
+
if (!C || !w) return;
|
|
49
|
+
let e = !1;
|
|
50
|
+
k(!0);
|
|
51
|
+
let n = setTimeout(() => {
|
|
52
|
+
(async () => {
|
|
53
|
+
try {
|
|
54
|
+
let n = await t(P);
|
|
55
|
+
e || D(n);
|
|
56
|
+
} catch {
|
|
57
|
+
e || D([]);
|
|
58
|
+
} finally {
|
|
59
|
+
e || (k(!1), j(!0));
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
}, 300);
|
|
63
|
+
return () => {
|
|
64
|
+
e = !0, clearTimeout(n);
|
|
65
|
+
};
|
|
66
|
+
}, [
|
|
67
|
+
C,
|
|
68
|
+
w,
|
|
69
|
+
P
|
|
70
|
+
]);
|
|
71
|
+
let I = i(() => !M || E.some((e) => e.id === M.id) ? E : [M, ...E], [E, M]), L = O ? "Loading…" : A && E.length === 0 ? C ? "No active facilities match" : "No facilities assigned to your account" : "No matches", R = C ? "Any active facility can be chosen. Type to search." : "Only facilities assigned to your account are shown. Type to filter.";
|
|
72
|
+
return /* @__PURE__ */ s(l, {
|
|
73
|
+
size: x,
|
|
74
|
+
fullWidth: S,
|
|
75
|
+
disabled: _,
|
|
76
|
+
open: w,
|
|
77
|
+
onOpen: () => T(!0),
|
|
78
|
+
onClose: () => T(!1),
|
|
79
|
+
value: M,
|
|
80
|
+
isOptionEqualToValue: (e, t) => e?.id === t?.id,
|
|
81
|
+
getOptionLabel: (e) => e?.name ?? "",
|
|
82
|
+
options: I,
|
|
83
|
+
loading: O,
|
|
84
|
+
filterOptions: C ? (e) => e : void 0,
|
|
85
|
+
onInputChange: C ? (e, t, n) => {
|
|
86
|
+
n === "input" && F(t);
|
|
87
|
+
} : void 0,
|
|
88
|
+
onChange: (e, t) => {
|
|
89
|
+
N(t ?? null), p?.(t?.id ?? "");
|
|
90
|
+
},
|
|
91
|
+
noOptionsText: L,
|
|
92
|
+
renderInput: (e) => /* @__PURE__ */ s(d, {
|
|
93
|
+
...e,
|
|
94
|
+
required: g,
|
|
95
|
+
label: m,
|
|
96
|
+
error: v,
|
|
97
|
+
helperText: y || R,
|
|
98
|
+
onBlur: b,
|
|
99
|
+
InputProps: {
|
|
100
|
+
...e.InputProps,
|
|
101
|
+
endAdornment: /* @__PURE__ */ c(o, { children: [O ? /* @__PURE__ */ s(u, {
|
|
102
|
+
color: "inherit",
|
|
103
|
+
size: 16
|
|
104
|
+
}) : null, e.InputProps.endAdornment] })
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { f as t };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=FacilityPicker-BXa2VF7i.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FacilityPicker-BXa2VF7i.js","names":[],"sources":["../src/components/FacilityPicker.jsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Autocomplete, CircularProgress, TextField } from '@mui/material';\nimport { getFacilitiesByIds, listFacilities, searchFacilities } from '@pm/services/pmApi';\n\n// Facility picker, in two modes.\n//\n// `scope=\"assigned\"` (the default) fetches /pm/facilities once — the backend\n// scopes it to the current operator's assigned set (joined off\n// user_health_infrastructure), which is small, typically a handful — and lets\n// MUI's default client-side filter handle search.\n//\n// `scope=\"all\"` is for choosing a facility the operator is NOT assigned to: a\n// transfer destination outside the pilot. That set is every active facility in\n// the deployment, which is thousands, so it searches server-side on each\n// keystroke instead of fetching a list. Client-side filtering is turned off in\n// that mode, since the server has already filtered.\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 scope = 'assigned',\n required = false,\n disabled = false,\n error = false,\n helperText = '',\n onBlur,\n size = 'small',\n fullWidth = true,\n}) {\n const searchMode = scope === 'all';\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 const [term, setTerm] = useState('');\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 (searchMode || !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 }, [searchMode, open, loaded]);\n\n // Search mode: ask the server on each keystroke, debounced. An empty term\n // still queries, so opening the dropdown shows the first page of facilities\n // rather than an empty box the operator has to guess at.\n useEffect(() => {\n if (!searchMode || !open) return undefined;\n let cancelled = false;\n setLoading(true);\n const handle = setTimeout(() => {\n (async () => {\n try {\n const rows = await searchFacilities(term);\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 }, 300);\n return () => { cancelled = true; clearTimeout(handle); };\n }, [searchMode, open, term]);\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 ? (searchMode ? 'No active facilities match' : 'No facilities assigned to your account')\n : 'No matches';\n\n const defaultHelper = searchMode\n ? 'Any active facility can be chosen. Type to search.'\n : '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 // Server-side search already filtered; MUI's own filter would then\n // re-filter the results against the same term and hide rows the server\n // matched on something other than a plain substring.\n filterOptions={searchMode ? (opts) => opts : undefined}\n onInputChange={searchMode\n ? (_, next, reason) => { if (reason === 'input') setTerm(next); }\n : undefined}\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":"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"}
|