@argusoft/medplat-product-management 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/{BrandsPage-CSA3meke.js → BrandsPage-BE3kn-8b.js} +77 -75
  2. package/dist/BrandsPage-BE3kn-8b.js.map +1 -0
  3. package/dist/{CategoriesPage-BrspzkJL.js → CategoriesPage-COqi3pMi.js} +71 -70
  4. package/dist/{CategoriesPage-BrspzkJL.js.map → CategoriesPage-COqi3pMi.js.map} +1 -1
  5. package/dist/{DashboardPage-CqaquOs2.js → DashboardPage-C_H1Jrey.js} +90 -89
  6. package/dist/DashboardPage-C_H1Jrey.js.map +1 -0
  7. package/dist/{FacilityPicker-Dv_H95WW.js → FacilityPicker-Diq2hnYg.js} +2 -2
  8. package/dist/{FacilityPicker-Dv_H95WW.js.map → FacilityPicker-Diq2hnYg.js.map} +1 -1
  9. package/dist/{InventoryPage-CPbzfDMR.js → InventoryPage-Bjpw6vTz.js} +106 -115
  10. package/dist/InventoryPage-Bjpw6vTz.js.map +1 -0
  11. package/dist/{MemberProfileCard-0vjO3Gzd.js → MemberProfileCard-BLUVRILO.js} +10 -9
  12. package/dist/MemberProfileCard-BLUVRILO.js.map +1 -0
  13. package/dist/{PaymentCreatePage-BYmDZAIB.js → PaymentCreatePage-B5VOgTx9.js} +173 -177
  14. package/dist/PaymentCreatePage-B5VOgTx9.js.map +1 -0
  15. package/dist/{PaymentsPage-BXPvKon-.js → PaymentsPage-DY6BclkN.js} +24 -23
  16. package/dist/{PaymentsPage-BXPvKon-.js.map → PaymentsPage-DY6BclkN.js.map} +1 -1
  17. package/dist/PickupCreatePage-CCSiyFQF.js +706 -0
  18. package/dist/PickupCreatePage-CCSiyFQF.js.map +1 -0
  19. package/dist/{PickupsPage-D66_Wta4.js → PickupsPage-DGi6T5Of.js} +83 -90
  20. package/dist/PickupsPage-DGi6T5Of.js.map +1 -0
  21. package/dist/PmGuard-C90yhpsN.js +592 -0
  22. package/dist/PmGuard-C90yhpsN.js.map +1 -0
  23. package/dist/{PmTable-B9970kPY.js → PmTable-BkK65bJQ.js} +98 -98
  24. package/dist/PmTable-BkK65bJQ.js.map +1 -0
  25. package/dist/{ProductEditPage-B8qFTCW9.js → ProductEditPage-CQj9egzW.js} +138 -137
  26. package/dist/ProductEditPage-CQj9egzW.js.map +1 -0
  27. package/dist/{ProductsPage-C7_r9cKn.js → ProductsPage-i8QVw_Jm.js} +65 -64
  28. package/dist/{ProductsPage-C7_r9cKn.js.map → ProductsPage-i8QVw_Jm.js.map} +1 -1
  29. package/dist/PurchaseCreatePage-Bp6bl9n8.js +591 -0
  30. package/dist/PurchaseCreatePage-Bp6bl9n8.js.map +1 -0
  31. package/dist/{PurchaseRequestDetailPage-C7aAwEqw.js → PurchaseRequestDetailPage-CNAqIFef.js} +216 -215
  32. package/dist/PurchaseRequestDetailPage-CNAqIFef.js.map +1 -0
  33. package/dist/{PurchaseRequestsPage-BwCkbpaX.js → PurchaseRequestsPage-BJqDlNrc.js} +57 -56
  34. package/dist/{PurchaseRequestsPage-BwCkbpaX.js.map → PurchaseRequestsPage-BJqDlNrc.js.map} +1 -1
  35. package/dist/{PurchasesPage-CvaZouVy.js → PurchasesPage-BQEToymQ.js} +76 -75
  36. package/dist/{PurchasesPage-CvaZouVy.js.map → PurchasesPage-BQEToymQ.js.map} +1 -1
  37. package/dist/{SaleCreatePage-DP1Qmltu.js → SaleCreatePage-DbfBK4PH.js} +149 -173
  38. package/dist/SaleCreatePage-DbfBK4PH.js.map +1 -0
  39. package/dist/{SalesPage-CO3cdYX5.js → SalesPage-CTBJMxZe.js} +50 -49
  40. package/dist/SalesPage-CTBJMxZe.js.map +1 -0
  41. package/dist/SettingsPage-CiwVmzM3.js +663 -0
  42. package/dist/SettingsPage-CiwVmzM3.js.map +1 -0
  43. package/dist/{StorageUnitsPage-B9Hr_RkW.js → StorageUnitsPage-BV7nK_iE.js} +57 -56
  44. package/dist/{StorageUnitsPage-B9Hr_RkW.js.map → StorageUnitsPage-BV7nK_iE.js.map} +1 -1
  45. package/dist/{TransactionsPage-HKIGwEZN.js → TransactionsPage-Cocn4901.js} +55 -54
  46. package/dist/TransactionsPage-Cocn4901.js.map +1 -0
  47. package/dist/TransferCreatePage-DbjcjuOg.js +316 -0
  48. package/dist/TransferCreatePage-DbjcjuOg.js.map +1 -0
  49. package/dist/TransfersPage-BFVF7Xvc.js +471 -0
  50. package/dist/TransfersPage-BFVF7Xvc.js.map +1 -0
  51. package/dist/VialsPage-BNzYTLVd.js +321 -0
  52. package/dist/VialsPage-BNzYTLVd.js.map +1 -0
  53. package/dist/index.js +652 -34
  54. package/dist/index.js.map +1 -1
  55. package/dist/settingsStore-KW217unV.js +417 -0
  56. package/dist/settingsStore-KW217unV.js.map +1 -0
  57. package/package.json +1 -1
  58. package/dist/BrandsPage-CSA3meke.js.map +0 -1
  59. package/dist/DashboardPage-CqaquOs2.js.map +0 -1
  60. package/dist/InventoryPage-CPbzfDMR.js.map +0 -1
  61. package/dist/MemberProfileCard-0vjO3Gzd.js.map +0 -1
  62. package/dist/PaymentCreatePage-BYmDZAIB.js.map +0 -1
  63. package/dist/PickupCreatePage-CMGwoAXj.js +0 -426
  64. package/dist/PickupCreatePage-CMGwoAXj.js.map +0 -1
  65. package/dist/PickupsPage-D66_Wta4.js.map +0 -1
  66. package/dist/PmTable-B9970kPY.js.map +0 -1
  67. package/dist/ProductEditPage-B8qFTCW9.js.map +0 -1
  68. package/dist/PurchaseCreatePage-CDfNDcKG.js +0 -627
  69. package/dist/PurchaseCreatePage-CDfNDcKG.js.map +0 -1
  70. package/dist/PurchaseRequestDetailPage-C7aAwEqw.js.map +0 -1
  71. package/dist/SaleCreatePage-DP1Qmltu.js.map +0 -1
  72. package/dist/SalesPage-CO3cdYX5.js.map +0 -1
  73. package/dist/SettingsPage-DvVRwSFw.js +0 -711
  74. package/dist/SettingsPage-DvVRwSFw.js.map +0 -1
  75. package/dist/TransactionsPage-HKIGwEZN.js.map +0 -1
  76. package/dist/TransferCreatePage-DMuZqQ3c.js +0 -363
  77. package/dist/TransferCreatePage-DMuZqQ3c.js.map +0 -1
  78. package/dist/TransfersPage-COIyouL4.js +0 -480
  79. package/dist/TransfersPage-COIyouL4.js.map +0 -1
  80. package/dist/VialsPage-BNPUwGCU.js +0 -922
  81. package/dist/VialsPage-BNPUwGCU.js.map +0 -1
  82. package/dist/currency-ClRLIEg2.js +0 -1372
  83. package/dist/currency-ClRLIEg2.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"BrandsPage-CSA3meke.js","names":[],"sources":["../src/views/BrandsPage.jsx"],"sourcesContent":["import { useState } from 'react';\nimport { v4 as uuidv4 } from 'uuid';\nimport {\n Alert,\n Box,\n Button,\n Checkbox,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n InputAdornment,\n Skeleton,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport AddRoundedIcon from '@mui/icons-material/AddRounded';\nimport RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';\nimport SellRoundedIcon from '@mui/icons-material/SellRounded';\nimport { showToast } from '@/common/toaster/toaster';\nimport CustomSwitch from '@/views/custom-components/CustomSwitch';\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 { PmEmptyRow, PmFieldHelp, PmTable, PmTableCard } from '@pm/components/PmTable';\nimport { getBrands, upsertBrand } from '@pm/services/pmApi';\nimport { isClean, validateEmail, validateRequired } from '@pm/utils/validation';\nimport { findCountry, validatePhoneForCountry } from '@pm/utils/countries';\nimport { usePmSettings } from '@pm/utils/settingsStore';\nimport { useFieldErrors } from '@pm/utils/useFieldErrors';\n\n// Backend stores contact_info as a JSON-encoded string. We keep the discrete\n// fields here so the UI is form-friendly, and only ever round-trip the JSON.\nfunction parseContactInfo(raw) {\n if (!raw) return { phone: '', email: '' };\n try {\n const obj = JSON.parse(raw);\n return {\n phone: typeof obj?.phone === 'string' ? obj.phone : '',\n email: typeof obj?.email === 'string' ? obj.email : '',\n };\n } catch {\n return { phone: '', email: '' };\n }\n}\n\nfunction serializeContactInfo({ phone, email }) {\n const out = {};\n if (phone?.trim()) out.phone = phone.trim();\n if (email?.trim()) out.email = email.trim();\n return Object.keys(out).length ? JSON.stringify(out) : null;\n}\n\nexport default function BrandsPage() {\n const perms = usePmPermissions();\n const canWrite = perms.has('update.manage.productManagement.brand');\n const settings = usePmSettings();\n const country = findCountry(settings?.countryCode);\n const [items, setItems] = useState([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState('');\n const [openDialog, setOpenDialog] = useState(false);\n const [editing, setEditing] = useState(null);\n // Default to active-only; the toggle above the table reveals inactive rows.\n const [showInactive, setShowInactive] = useState(false);\n const [form, setForm] = useState(emptyForm());\n const fieldErrors = useFieldErrors();\n\n function emptyForm() {\n return {\n id: '',\n name: '',\n manufacturerCode: '',\n contactPhone: '',\n contactEmail: '',\n isActive: true,\n };\n }\n\n async function load() {\n setLoading(true);\n setError('');\n try {\n const data = await getBrands(undefined, true);\n setItems(data);\n } catch (err) {\n const message = err?.response?.data?.message ?? err?.message ?? 'Failed to load brands';\n setError(message);\n showToast({ message, type: 'error' });\n } finally {\n setLoading(false);\n }\n }\n\n usePermissionedEffect('view.manage.productManagement.brand', () => {\n void load();\n }, []);\n\n function openCreate() {\n setEditing(null);\n setForm({ ...emptyForm(), id: uuidv4() });\n fieldErrors.reset();\n setError('');\n setOpenDialog(true);\n }\n\n function openEdit(item) {\n const { phone, email } = parseContactInfo(item.contactInfo);\n setEditing(item);\n fieldErrors.reset();\n setError('');\n setForm({\n id: item.id,\n name: item.name ?? '',\n manufacturerCode: item.manufacturerCode ?? '',\n contactPhone: phone,\n contactEmail: email,\n isActive: item.isActive !== false,\n });\n setOpenDialog(true);\n }\n\n const mfrCodeBase = validateRequired(form.manufacturerCode, 'Manufacturer code');\n const mfrNorm = form.manufacturerCode.trim().toUpperCase();\n const mfrClash = !mfrCodeBase\n && mfrNorm\n && items.some((b) => b.id !== form.id && (b.manufacturerCode || '').toUpperCase() === mfrNorm);\n const formErrors = {\n name: validateRequired(form.name, 'Name'),\n manufacturerCode: mfrCodeBase || (mfrClash ? 'Code is already in use — edit it manually to make it unique.' : ''),\n contactPhone: validatePhoneForCountry(form.contactPhone, country),\n contactEmail: validateEmail(form.contactEmail),\n };\n const formIsValid = isClean(formErrors);\n\n async function save() {\n if (!formIsValid) {\n fieldErrors.markAllTouched();\n setError('Please fix the highlighted fields.');\n return;\n }\n try {\n await upsertBrand({\n id: form.id,\n name: form.name.trim(),\n manufacturerCode: form.manufacturerCode.trim().toUpperCase(),\n contactInfo: serializeContactInfo({\n phone: form.contactPhone,\n email: form.contactEmail,\n }),\n isActive: form.isActive,\n });\n showToast({ message: 'Brand saved.', type: 'success' });\n setOpenDialog(false);\n await load();\n } catch (err) {\n const message = err?.response?.data?.message ?? err?.message ?? 'Failed to save brand';\n setError(message);\n showToast({ message, type: 'error' });\n }\n }\n\n const visible = showInactive ? items : items.filter((b) => b.isActive !== false);\n\n return (\n <Box>\n <PmNav />\n <PmGuard perm={'view.manage.productManagement.brand'}>\n <PmPageShell\n title=\"Brands\"\n subtitle=\"The manufacturer or supplier of each product.\"\n chips={['Catalog']}\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"outlined\" startIcon={<RefreshRoundedIcon />} onClick={() => void load()}>Refresh</Button>\n {canWrite ? (\n <Button variant=\"contained\" startIcon={<AddRoundedIcon />} onClick={openCreate}>New Brand</Button>\n ) : null}\n </Stack>\n }\n >\n {error ? <Alert severity=\"error\" sx={{ mb: 2 }}>{error}</Alert> : null}\n\n <PmTableCard\n eyebrow=\"Master data\"\n title=\"Brands\"\n subtitle={loading ? 'Loading…' : `${visible.length} ${visible.length === 1 ? 'brand' : 'brands'}`}\n actions={\n <FormControlLabel\n control={<CustomSwitch checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)} />}\n label={<Typography variant=\"body2\" sx={{ fontWeight: 600 }}>Show inactive</Typography>}\n labelPlacement=\"start\"\n sx={{ mr: 0.5, ml: 0 }}\n />\n }\n >\n <PmTable>\n <TableHead>\n <TableRow>\n <TableCell>Name</TableCell>\n <TableCell>Manufacturer Code</TableCell>\n <TableCell>Contact</TableCell>\n <TableCell>Status</TableCell>\n <TableCell align=\"right\">Action</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {loading ? (\n Array.from({ length: 4 }).map((_, i) => (\n <TableRow key={i}>\n {Array.from({ length: 5 }).map((__, j) => (\n <TableCell key={j}><Skeleton height={22} /></TableCell>\n ))}\n </TableRow>\n ))\n ) : visible.length === 0 ? (\n <PmEmptyRow\n colSpan={5}\n icon={SellRoundedIcon}\n title={items.length === 0 ? 'No brands yet' : 'No active brands'}\n hint={items.length === 0\n ? 'Add manufacturers so you can link them to your products.'\n : 'Turn on “Show inactive” to see deactivated brands.'}\n action={items.length === 0 && canWrite ? (\n <Button variant=\"contained\" size=\"small\" startIcon={<AddRoundedIcon />} onClick={openCreate}>\n Create first brand\n </Button>\n ) : null}\n />\n ) : visible.map((b) => {\n const { phone, email } = parseContactInfo(b.contactInfo);\n return (\n <TableRow key={b.id} hover>\n <TableCell sx={{ maxWidth: 240 }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 700, wordBreak: 'break-word' }}>{b.name}</Typography>\n </TableCell>\n <TableCell sx={{ maxWidth: 160 }}>\n <Box component=\"span\" sx={{\n display: 'inline-block', px: 0.85, py: 0.25, borderRadius: 1,\n bgcolor: 'grey.100', fontFamily: 'monospace', fontSize: '0.72rem', fontWeight: 800,\n maxWidth: '100%', wordBreak: 'break-all',\n }}>\n {b.manufacturerCode}\n </Box>\n </TableCell>\n <TableCell>\n {phone || email ? (\n <Stack spacing={0.25}>\n {phone ? <Typography variant=\"caption\" sx={{ fontWeight: 600 }}>{phone}</Typography> : null}\n {email ? <Typography variant=\"caption\" color=\"text.secondary\">{email}</Typography> : null}\n </Stack>\n ) : <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>}\n </TableCell>\n <TableCell>\n <Chip\n size=\"small\"\n label={b.isActive ? 'Active' : 'Inactive'}\n color={b.isActive ? 'success' : 'default'}\n variant={b.isActive ? 'filled' : 'outlined'}\n sx={{ fontWeight: 700, borderRadius: 1.5, height: 22 }}\n />\n </TableCell>\n <TableCell align=\"right\">\n {canWrite ? (\n <Button size=\"small\" variant=\"outlined\" onClick={() => openEdit(b)}>Edit</Button>\n ) : null}\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </PmTable>\n </PmTableCard>\n </PmPageShell>\n\n <Dialog open={openDialog} onClose={() => setOpenDialog(false)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{editing ? 'Edit Brand' : 'New Brand'}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n required\n label=\"Name\" size=\"small\" fullWidth\n value={form.name}\n inputProps={{ maxLength: 255 }}\n onChange={(e) => {\n const nextName = e.target.value;\n // Auto-derive manufacturer code from initials of every word — but\n // only on create (when editing an existing brand we never overwrite\n // a code the user has already curated).\n const initialsFromName = nextName\n .split(/\\s+/)\n .filter(Boolean)\n .map((w) => w[0])\n .join('')\n .toUpperCase();\n setForm((f) => ({\n ...f,\n name: nextName,\n manufacturerCode: editing ? f.manufacturerCode : initialsFromName,\n }));\n }}\n onBlur={fieldErrors.onBlur('name')}\n error={fieldErrors.show('name') && !!formErrors.name}\n FormHelperTextProps={{ component: 'div' }}\n helperText={<PmFieldHelp message={fieldErrors.show('name') ? formErrors.name : ''} value={form.name} max={255} />}\n />\n <TextField\n required\n label=\"Manufacturer code\" size=\"small\" fullWidth\n value={form.manufacturerCode}\n inputProps={{ maxLength: 100 }}\n onChange={(e) => setForm({ ...form, manufacturerCode: e.target.value.toUpperCase() })}\n onBlur={fieldErrors.onBlur('manufacturerCode')}\n error={fieldErrors.show('manufacturerCode') && !!formErrors.manufacturerCode}\n FormHelperTextProps={{ component: 'div' }}\n helperText={\n <PmFieldHelp\n message={fieldErrors.show('manufacturerCode') && formErrors.manufacturerCode\n ? formErrors.manufacturerCode\n : 'Auto-generated from the name as initials. Edit if needed to make it unique.'}\n value={form.manufacturerCode}\n max={100}\n />\n }\n />\n <TextField\n label=\"Contact Number\"\n size=\"small\"\n fullWidth\n type=\"tel\"\n placeholder={country\n ? (country.phoneMin === country.phoneMax\n ? `${country.phoneMin}-digit local number`\n : `${country.phoneMin}–${country.phoneMax} digit local number`)\n : 'Local number'}\n value={form.contactPhone}\n // Keep only digits and spaces — the dial code lives in the\n // adornment, not in the value.\n onChange={(e) => setForm({ ...form, contactPhone: e.target.value.replace(/[^\\d\\s]/g, '') })}\n onBlur={fieldErrors.onBlur('contactPhone')}\n error={fieldErrors.show('contactPhone') && !!formErrors.contactPhone}\n helperText={\n fieldErrors.show('contactPhone') && formErrors.contactPhone\n ? formErrors.contactPhone\n : country\n ? `Format for ${country.name}: ${country.phoneCode} followed by ${country.phoneMin}${country.phoneMin === country.phoneMax ? '' : `–${country.phoneMax}`} digits.`\n : 'Local number, digits only.'\n }\n InputProps={country ? {\n startAdornment: (\n <InputAdornment position=\"start\">\n <Box component=\"span\" sx={{ fontWeight: 700, color: 'text.secondary' }}>{country.phoneCode}</Box>\n </InputAdornment>\n ),\n } : undefined}\n />\n <TextField\n label=\"Email\"\n size=\"small\"\n fullWidth\n type=\"email\"\n placeholder=\"e.g. contact@manufacturer.example\"\n value={form.contactEmail}\n onChange={(e) => setForm({ ...form, contactEmail: e.target.value })}\n onBlur={fieldErrors.onBlur('contactEmail')}\n error={fieldErrors.show('contactEmail') && !!formErrors.contactEmail}\n helperText={fieldErrors.show('contactEmail') ? formErrors.contactEmail : ''}\n />\n <FormControlLabel\n control={<Checkbox checked={form.isActive}\n onChange={(e) => setForm({ ...form, isActive: e.target.checked })} />}\n label=\"Active\" />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setOpenDialog(false)}>Cancel</Button>\n <Button variant=\"contained\" disabled={!formIsValid} onClick={() => void save()}>Save</Button>\n </DialogActions>\n </Dialog>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"DashboardPage-CqaquOs2.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: 4,\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: 3,\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: 4, 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 +0,0 @@
1
- {"version":3,"file":"InventoryPage-CPbzfDMR.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 FormControl,\n FormHelperText,\n InputAdornment,\n InputLabel,\n LinearProgress,\n MenuItem,\n Select,\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, 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 SearchRoundedIcon from \"@mui/icons-material/SearchRounded\";\nimport { showToast } from \"@/common/toaster/toaster\";\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} from \"@pm/services/pmApi\";\nimport { BATCH_STATUSES, QUARANTINE_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\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 const theme = useTheme();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState('ALL');\n // Filter chips. notYetExpired excludes anything past its printed expiry\n // (regardless of status). inStock keeps rows with qtyRemaining > 0.\n const [notYetExpired, setNotYetExpired] = useState(false);\n const [inStock, setInStock] = useState(false);\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); chips + status narrow the\n // remaining set with AND semantics.\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 if (statusFilter !== 'ALL' && b.status !== statusFilter) return false;\n if (notYetExpired && isBatchExpired(b)) return false;\n if (inStock && !(Number(b.qtyRemaining ?? 0) > 0)) return false;\n return true;\n });\n }, [items, search, statusFilter, notYetExpired, inStock]);\n\n const hasFilter = search.trim() !== '' || statusFilter !== 'ALL' || notYetExpired || inStock;\n\n function clearFilters() {\n setSearch('');\n setStatusFilter('ALL');\n setNotYetExpired(false);\n setInStock(false);\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 <FormControl size=\"small\" sx={{ minWidth: 160 }}>\n <InputLabel>Status</InputLabel>\n <Select\n label=\"Status\"\n value={statusFilter}\n onChange={(e) => {\n const next = e.target.value;\n setStatusFilter(next);\n // Picking a terminal status from the dropdown auto-clears the\n // opposing chip, so the filter pair can never resolve to an\n // empty set by construction.\n if (next === 'EXPIRED') setNotYetExpired(false);\n if (next === 'EXHAUSTED') setInStock(false);\n }}\n >\n <MenuItem value=\"ALL\">All statuses</MenuItem>\n {BATCH_STATUSES.map((s) => {\n const blocked = (s.value === 'EXPIRED' && notYetExpired) || (s.value === 'EXHAUSTED' && inStock);\n return (\n <MenuItem key={s.value} value={s.value} disabled={blocked}>\n {s.label}\n {blocked ? (\n <Typography component=\"span\" variant=\"caption\" color=\"text.disabled\" sx={{ ml: 1 }}>\n (clear chip first)\n </Typography>\n ) : null}\n </MenuItem>\n );\n })}\n </Select>\n </FormControl>\n <Stack direction=\"row\" spacing={1} useFlexGap flexWrap=\"wrap\">\n <Chip\n label=\"Not yet expired\"\n size=\"small\"\n disabled={statusFilter === 'EXPIRED'}\n onClick={() => {\n // Auto-reset the conflicting dropdown value so the two\n // controls never produce an impossible combination.\n setNotYetExpired((prev) => {\n const next = !prev;\n if (next && statusFilter === 'EXPIRED') setStatusFilter('ALL');\n return next;\n });\n }}\n color={notYetExpired ? 'primary' : 'default'}\n variant={notYetExpired ? 'filled' : 'outlined'}\n sx={{ fontWeight: 700, borderRadius: 1.5 }}\n />\n <Chip\n label=\"Stock still left\"\n size=\"small\"\n disabled={statusFilter === 'EXHAUSTED'}\n onClick={() => {\n setInStock((prev) => {\n const next = !prev;\n if (next && statusFilter === 'EXHAUSTED') setStatusFilter('ALL');\n return next;\n });\n }}\n color={inStock ? 'primary' : 'default'}\n variant={inStock ? 'filled' : 'outlined'}\n sx={{ fontWeight: 700, borderRadius: 1.5 }}\n />\n </Stack>\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 || (b.ownerType === 'USER' ? 'Personal' : 'Facility')}\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 </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 <Button\n size=\"small\"\n variant=\"outlined\"\n color=\"success\"\n startIcon={<LockOpenRoundedIcon />}\n onClick={() => void doUnquarantine(b)}\n >\n Unquarantine\n </Button>\n ) : isExpired ||\n b.status === \"EXHAUSTED\" ||\n b.status === \"IN_USE\" ? 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 </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 <FormControl\n required\n fullWidth\n size=\"small\"\n error={!!validateRequired(quarantineDialog.reason, \"Reason\")}\n >\n <InputLabel>Reason</InputLabel>\n <Select\n label=\"Reason\"\n value={quarantineDialog.reason}\n onChange={(e) =>\n setQuarantineDialog({\n ...quarantineDialog,\n reason: e.target.value,\n })\n }\n >\n {QUARANTINE_REASONS.map((r) => (\n <MenuItem key={r.value} value={r.value}>\n {r.label}\n </MenuItem>\n ))}\n </Select>\n {validateRequired(quarantineDialog.reason, \"Reason\") ? (\n <FormHelperText>\n {validateRequired(quarantineDialog.reason, \"Reason\")}\n </FormHelperText>\n ) : null}\n </FormControl>\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 </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MemberProfileCard-0vjO3Gzd.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: 3,\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: 3, 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: 3,\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: 3,\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 +0,0 @@
1
- {"version":3,"file":"PaymentCreatePage-BYmDZAIB.js","names":[],"sources":["../src/views/PaymentCreatePage.jsx"],"sourcesContent":["import { useMemo, useState } from \"react\";\nimport { useNavigate, useParams } from \"react-router-dom\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n Alert,\n Box,\n Button,\n Card,\n CardContent,\n Chip,\n Divider,\n FormControl,\n InputAdornment,\n InputLabel,\n LinearProgress,\n MenuItem,\n Select,\n Stack,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from \"@mui/material\";\nimport SaveRoundedIcon from \"@mui/icons-material/SaveRounded\";\nimport { showToast } from \"@/common/toaster/toaster\";\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 createPayment,\n getMemberDuesDetails,\n getMemberOutstandingDues,\n} from \"@pm/services/pmApi\";\nimport MemberProfileCard from \"@pm/components/MemberProfileCard\";\nimport { PAYMENT_MODES } from \"@pm/utils/constants\";\nimport { currencySymbol } from \"@pm/utils/currency\";\nimport { usePmSettings } from \"@pm/utils/settingsStore\";\nimport { formatDate, formatNumber } from \"@pm/utils/format\";\nimport {\n isClean,\n validatePastOrTodayDate,\n validatePositiveNumber,\n} from \"@pm/utils/validation\";\nimport { useFieldErrors } from \"@pm/utils/useFieldErrors\";\n\nfunction daysSince(value) {\n if (!value) return null;\n const ms = new Date(value).getTime();\n if (Number.isNaN(ms)) return null;\n const diff = Date.now() - ms;\n return Math.max(0, Math.floor(diff / (24 * 3600 * 1000)));\n}\n\nexport default function PaymentCreatePage() {\n const { memberId } = useParams();\n const navigate = useNavigate();\n const settings = usePmSettings();\n const cur = currencySymbol(settings?.currencyCode);\n const perms = usePmPermissions();\n // Dues breakdown drives both the panel and the collect form's totalDue.\n // Without the dues permission we can't fetch it, so the page surfaces a\n // hard-stop alert and disables Confirm Collection.\n const canSeeDues = perms.has(\"view.manage.productManagement.dues\");\n const [error, setError] = useState(\"\");\n const [memberName, setMemberName] = useState(\"\");\n const [previousDue, setPreviousDue] = useState(0);\n const [duesDetails, setDuesDetails] = useState(null);\n const [detailsLoading, setDetailsLoading] = useState(false);\n const fieldErrors = useFieldErrors();\n const today = new Date().toISOString().substring(0, 10);\n const [form, setForm] = useState({\n id: uuidv4(),\n paymentDate: today,\n paymentMode: \"CASH\",\n collectingNow: \"\",\n notes: \"\",\n });\n\n async function loadMemberDues() {\n if (!memberId) {\n setDuesDetails(null);\n setDetailsLoading(false);\n setPreviousDue(0);\n return;\n }\n if (!canSeeDues) {\n // No dues access — leave panels empty. The hard-stop alert below\n // tells the operator to ask for the perm.\n setDuesDetails(null);\n setPreviousDue(0);\n setDetailsLoading(false);\n return;\n }\n setDetailsLoading(true);\n try {\n const [details, dues] = await Promise.all([\n getMemberDuesDetails(memberId).catch(() => null),\n getMemberOutstandingDues().catch(() => []),\n ]);\n setDuesDetails(details);\n const match = (dues ?? []).find((d) => d.memberId === memberId);\n setPreviousDue(\n match ? Number(match.totalDue) : Number(details?.totalDue ?? 0),\n );\n } catch (err) {\n showToast({\n message:\n err?.response?.data?.message ??\n err?.message ??\n \"Failed to load member dues\",\n type: \"error\",\n });\n } finally {\n setDetailsLoading(false);\n }\n }\n\n usePermissionedEffect(\n \"update.manage.productManagement.payment\",\n () => {\n void loadMemberDues();\n setForm((f) => ({ ...f, collectingNow: \"\" }));\n // eslint-disable-next-line react-hooks/exhaustive-deps\n },\n [memberId],\n );\n\n // -------- Money math --------\n const totalDue = Number(duesDetails?.totalDue ?? 0);\n const collectedRaw = Number(form.collectingNow || 0);\n const collected = Math.min(Math.max(collectedRaw, 0), totalDue);\n const remainingAfter = Math.max(totalDue - collected, 0);\n\n const oldestDueDays = daysSince(duesDetails?.oldestDueDate);\n const lastSaleDays = daysSince(duesDetails?.lastSaleDate);\n\n // Collection only makes sense when there's an outstanding balance — once\n // it's zero the collect form is hidden entirely (a \"Nothing to collect\"\n // alert is shown instead) and the Save button stays disabled.\n const canCollect = !!memberId && totalDue > 0 && canSeeDues;\n\n // -------- Validation --------\n const formErrors = useMemo(() => {\n const errs = {\n member: memberId ? \"\" : \"No member selected\",\n paymentDate: validatePastOrTodayDate(form.paymentDate, {\n required: true,\n label: \"Payment date\",\n }),\n collectingNow: validatePositiveNumber(form.collectingNow, \"Amount\", {\n required: true,\n }),\n };\n if (\n !errs.collectingNow &&\n totalDue > 0 &&\n Number(form.collectingNow) > totalDue\n ) {\n errs.collectingNow = `Cannot collect more than the outstanding due (${formatNumber(totalDue)}).`;\n }\n return errs;\n }, [memberId, form.paymentDate, form.collectingNow, totalDue]);\n const formIsValid = canCollect && isClean(formErrors);\n\n async function save() {\n if (!formIsValid) {\n fieldErrors.markAllTouched();\n setError(\"Please fix the highlighted fields.\");\n return;\n }\n try {\n await createPayment({\n id: form.id,\n memberId,\n amount: Number(collected.toFixed(2)),\n paymentDate: form.paymentDate,\n paymentMode: form.paymentMode || null,\n notes: form.notes || null,\n allocations: null,\n });\n showToast({\n message: memberName\n ? `Payment recorded for ${memberName}. Applied to their oldest sales first.`\n : \"Payment recorded. Applied to the oldest sales first.\",\n type: \"success\",\n });\n navigate(\"/ui/medplat/manage/pm/payments\");\n } catch (err) {\n const message =\n err?.response?.data?.message ??\n err?.message ??\n \"Failed to record payment\";\n setError(message);\n showToast({ message, type: \"error\" });\n }\n }\n\n return (\n <Box>\n <PmNav />\n <PmGuard\n perm={\"update.manage.productManagement.payment\"}\n title=\"You don’t have permission to collect payments\"\n >\n <PmPageShell\n title=\"Collect Payment\"\n subtitle=\"Collect payment for what a customer owes. Payments are matched to sales in order received.\"\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button\n variant=\"outlined\"\n onClick={() => navigate(\"/ui/medplat/manage/pm/payments\")}\n >\n Cancel\n </Button>\n <Button\n variant=\"contained\"\n startIcon={<SaveRoundedIcon />}\n disabled={!formIsValid}\n onClick={() => void save()}\n >\n Confirm Collection\n </Button>\n </Stack>\n }\n >\n {error ? (\n <Alert severity=\"error\" sx={{ mb: 2 }}>\n {error}\n </Alert>\n ) : null}\n\n {!canSeeDues ? (\n <Alert severity=\"warning\" sx={{ mb: 2 }}>\n You don’t have access to view outstanding dues, so this collection\n flow can’t determine how much is owed. Ask an admin to grant the\n dues permission to collect payments here.\n </Alert>\n ) : null}\n\n {/* ---------- Member profile (from URL) ---------- */}\n <Card sx={{ borderRadius: 4, mb: 2 }}>\n <CardContent>\n <Typography variant=\"h6\" sx={{ fontWeight: 800, mb: 2 }}>\n Customer\n </Typography>\n {memberId ? (\n <MemberProfileCard\n memberId={memberId}\n previousDue={previousDue}\n currencySymbol={cur}\n onLoad={(p) => setMemberName(p?.name ?? \"\")}\n />\n ) : (\n <Alert severity=\"warning\">No member selected</Alert>\n )}\n </CardContent>\n </Card>\n\n {/* ---------- Dues + collect (only when a member is selected) ---------- */}\n {memberId ? (\n <>\n <Card sx={{ borderRadius: 4, mb: 2 }}>\n <CardContent>\n {detailsLoading ? <LinearProgress sx={{ mb: 2 }} /> : null}\n <Grid3MetricStrip\n total={totalDue}\n oldestDueDays={oldestDueDays}\n oldestDueDate={duesDetails?.oldestDueDate}\n lastSaleDays={lastSaleDays}\n lastSaleDate={duesDetails?.lastSaleDate}\n currency={cur}\n />\n </CardContent>\n </Card>\n\n <Card sx={{ borderRadius: 4, mb: 2 }}>\n <CardContent>\n <Stack\n direction=\"row\"\n justifyContent=\"space-between\"\n alignItems=\"center\"\n sx={{ mb: 2 }}\n >\n <Typography variant=\"h6\" sx={{ fontWeight: 800 }}>\n Due Breakdown\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {(duesDetails?.openSales ?? []).length}{\" \"}\n {(duesDetails?.openSales ?? []).length === 1\n ? \"open sale\"\n : \"open sales\"}{\" \"}\n · oldest first\n </Typography>\n </Stack>\n <Divider sx={{ mb: 1 }} />\n {totalDue <= 0 ? (\n <Alert severity=\"success\" sx={{ mt: 1 }}>\n No outstanding dues for this member — nothing to collect.\n </Alert>\n ) : (\n <TableContainer>\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell sx={{ fontWeight: 800 }}>\n Sale Date\n </TableCell>\n <TableCell sx={{ fontWeight: 800 }}>\n Items\n </TableCell>\n <TableCell sx={{ fontWeight: 800 }} align=\"right\">\n Sale\n </TableCell>\n <TableCell sx={{ fontWeight: 800 }} align=\"right\">\n Paid\n </TableCell>\n <TableCell sx={{ fontWeight: 800 }} align=\"right\">\n Due\n </TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {(duesDetails?.openSales ?? []).map((sale) => (\n <TableRow key={sale.id} hover>\n <TableCell>\n <Stack\n direction=\"row\"\n spacing={1}\n alignItems=\"baseline\"\n >\n <Typography variant=\"body2\">\n {formatDate(sale.saleDate)}\n </Typography>\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n ({daysSince(sale.saleDate)}d ago)\n </Typography>\n </Stack>\n </TableCell>\n <TableCell>\n <Stack\n direction=\"row\"\n spacing={0.5}\n flexWrap=\"wrap\"\n >\n {(sale.items ?? []).map((it) => (\n <Chip\n key={it.id}\n size=\"small\"\n variant=\"outlined\"\n label={`${it.productName} × ${it.quantity}`}\n sx={{ mr: 0.5, mb: 0.5 }}\n />\n ))}\n </Stack>\n </TableCell>\n <TableCell\n align=\"right\"\n sx={{ fontVariantNumeric: \"tabular-nums\" }}\n >\n {cur}\n {formatNumber(sale.totalAmount)}\n </TableCell>\n <TableCell\n align=\"right\"\n sx={{\n fontVariantNumeric: \"tabular-nums\",\n color: \"text.secondary\",\n }}\n >\n {cur}\n {formatNumber(sale.paidAmount)}\n </TableCell>\n <TableCell\n align=\"right\"\n sx={{\n fontVariantNumeric: \"tabular-nums\",\n fontWeight: 700,\n color: \"warning.dark\",\n }}\n >\n {cur}\n {formatNumber(sale.dueAmount)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </TableContainer>\n )}\n </CardContent>\n </Card>\n\n {/* ---------- Collection footer — only when there's something to collect ---------- */}\n {canCollect ? (\n <Card sx={{ borderRadius: 4, bgcolor: \"grey.50\", mb: 3 }}>\n <CardContent>\n <Stack direction={{ xs: \"column\", md: \"row\" }} spacing={2}>\n <TextField\n required\n size=\"small\"\n type=\"date\"\n label=\"Payment Date\"\n sx={{ flex: 1 }}\n InputLabelProps={{ shrink: true }}\n value={form.paymentDate}\n onChange={(e) =>\n setForm({ ...form, paymentDate: e.target.value })\n }\n onBlur={fieldErrors.onBlur(\"paymentDate\")}\n error={\n fieldErrors.show(\"paymentDate\") &&\n !!formErrors.paymentDate\n }\n helperText={\n fieldErrors.show(\"paymentDate\")\n ? formErrors.paymentDate\n : \"\"\n }\n />\n <FormControl size=\"small\" sx={{ flex: 1 }}>\n <InputLabel>Payment Mode</InputLabel>\n <Select\n label=\"Payment Mode\"\n value={form.paymentMode}\n onChange={(e) =>\n setForm({ ...form, paymentMode: e.target.value })\n }\n >\n {PAYMENT_MODES.map((m) => (\n <MenuItem key={m.value} value={m.value}>\n {m.label}\n </MenuItem>\n ))}\n </Select>\n </FormControl>\n <TextField\n size=\"small\"\n label=\"Notes\"\n sx={{ flex: 2 }}\n value={form.notes}\n onChange={(e) =>\n setForm({ ...form, notes: e.target.value })\n }\n />\n </Stack>\n\n <Divider sx={{ my: 2 }} />\n\n <Stack spacing={1.25}>\n <Stack\n direction=\"row\"\n justifyContent=\"space-between\"\n alignItems=\"baseline\"\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n Total Due\n </Typography>\n <Typography\n variant=\"h5\"\n sx={{\n fontWeight: 800,\n fontVariantNumeric: \"tabular-nums\",\n color:\n totalDue > 0 ? \"warning.dark\" : \"text.primary\",\n }}\n >\n {cur}\n {formatNumber(totalDue)}\n </Typography>\n </Stack>\n\n <Stack\n direction=\"row\"\n justifyContent=\"space-between\"\n alignItems=\"center\"\n spacing={2}\n >\n <Typography\n variant=\"body2\"\n color=\"text.secondary\"\n sx={{ flexShrink: 0 }}\n >\n Amount Collecting Now\n </Typography>\n <TextField\n required\n size=\"small\"\n type=\"number\"\n inputProps={{\n step: \"0.01\",\n min: 0,\n max: totalDue,\n style: { textAlign: \"right\", fontWeight: 700 },\n }}\n InputProps={{\n startAdornment: (\n <InputAdornment position=\"start\">\n {cur}\n </InputAdornment>\n ),\n }}\n sx={{ width: 220 }}\n placeholder=\"0.00\"\n value={form.collectingNow}\n onChange={(e) =>\n setForm({ ...form, collectingNow: e.target.value })\n }\n onBlur={fieldErrors.onBlur(\"collectingNow\")}\n error={\n fieldErrors.show(\"collectingNow\") &&\n !!formErrors.collectingNow\n }\n helperText={\n fieldErrors.show(\"collectingNow\")\n ? formErrors.collectingNow\n : \"Partial amounts accepted\"\n }\n />\n </Stack>\n\n <Divider sx={{ my: 0.5 }} />\n\n <Stack\n direction=\"row\"\n justifyContent=\"space-between\"\n alignItems=\"baseline\"\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n Remaining Due After\n </Typography>\n <Typography\n variant=\"h5\"\n sx={{\n fontWeight: 800,\n fontVariantNumeric: \"tabular-nums\",\n color:\n remainingAfter > 0\n ? \"warning.dark\"\n : \"success.dark\",\n }}\n >\n {cur}\n {formatNumber(remainingAfter)}\n </Typography>\n </Stack>\n </Stack>\n </CardContent>\n </Card>\n ) : null}\n </>\n ) : null}\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n\n// Compact three-metric strip used inside the member dues panel.\nfunction Grid3MetricStrip({\n total,\n oldestDueDays,\n oldestDueDate,\n lastSaleDays,\n lastSaleDate,\n currency = \"₹\",\n}) {\n const cell = (label, value, sub, color) => (\n <Box\n sx={{\n flex: 1,\n p: 1.5,\n borderRadius: 2,\n bgcolor: \"background.paper\",\n border: 1,\n borderColor: \"divider\",\n textAlign: \"left\",\n }}\n >\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n <Typography\n variant=\"h6\"\n sx={{ fontWeight: 800, color, fontVariantNumeric: \"tabular-nums\" }}\n >\n {value}\n </Typography>\n {sub ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {sub}\n </Typography>\n ) : null}\n </Box>\n );\n return (\n <Stack direction={{ xs: \"column\", md: \"row\" }} spacing={1.5}>\n {cell(\n \"Total Due\",\n `${currency}${formatNumber(total)}`,\n null,\n total > 0 ? \"warning.dark\" : \"text.primary\",\n )}\n {cell(\n \"Last Sale\",\n lastSaleDays == null\n ? \"—\"\n : `${lastSaleDays} day${lastSaleDays === 1 ? \"\" : \"s\"} ago`,\n lastSaleDate ? formatDate(lastSaleDate) : \"No prior sale\",\n \"text.primary\",\n )}\n {cell(\n \"Due Since\",\n oldestDueDays == null\n ? \"—\"\n : `${oldestDueDays} day${oldestDueDays === 1 ? \"\" : \"s\"}`,\n oldestDueDate ? formatDate(oldestDueDate) : \"No open sales\",\n oldestDueDays != null && oldestDueDays >= 30\n ? \"error.dark\"\n : \"text.primary\",\n )}\n </Stack>\n );\n}\n"],"mappings":"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"}