@argusoft/medplat-product-management 1.0.2 → 1.0.3
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-CSA3meke.js → BrandsPage-NSvaMXWg.js} +76 -75
- package/dist/BrandsPage-NSvaMXWg.js.map +1 -0
- package/dist/{CategoriesPage-BrspzkJL.js → CategoriesPage-aZBZrDOi.js} +8 -8
- package/dist/{CategoriesPage-BrspzkJL.js.map → CategoriesPage-aZBZrDOi.js.map} +1 -1
- package/dist/{DashboardPage-CqaquOs2.js → DashboardPage-DjwlOhrd.js} +22 -22
- package/dist/DashboardPage-DjwlOhrd.js.map +1 -0
- package/dist/{FacilityPicker-Dv_H95WW.js → FacilityPicker-Chh-ZaR0.js} +2 -2
- package/dist/{FacilityPicker-Dv_H95WW.js.map → FacilityPicker-Chh-ZaR0.js.map} +1 -1
- package/dist/{InventoryPage-CPbzfDMR.js → InventoryPage-cy76UsMp.js} +109 -119
- package/dist/InventoryPage-cy76UsMp.js.map +1 -0
- package/dist/{MemberProfileCard-0vjO3Gzd.js → MemberProfileCard-CinPt09F.js} +6 -6
- package/dist/MemberProfileCard-CinPt09F.js.map +1 -0
- package/dist/{PaymentCreatePage-BYmDZAIB.js → PaymentCreatePage-BJe3LyFF.js} +148 -153
- package/dist/PaymentCreatePage-BJe3LyFF.js.map +1 -0
- package/dist/{PaymentsPage-BXPvKon-.js → PaymentsPage-DxHoKksN.js} +11 -11
- package/dist/{PaymentsPage-BXPvKon-.js.map → PaymentsPage-DxHoKksN.js.map} +1 -1
- package/dist/PickupCreatePage-Cq8zJ62Y.js +705 -0
- package/dist/PickupCreatePage-Cq8zJ62Y.js.map +1 -0
- package/dist/{PickupsPage-D66_Wta4.js → PickupsPage-BOygBQAb.js} +66 -74
- package/dist/PickupsPage-BOygBQAb.js.map +1 -0
- package/dist/{PmTable-B9970kPY.js → PmTable-CCpaJEA_.js} +4 -4
- package/dist/PmTable-CCpaJEA_.js.map +1 -0
- package/dist/{ProductEditPage-B8qFTCW9.js → ProductEditPage-DmdHO-fS.js} +109 -109
- package/dist/ProductEditPage-DmdHO-fS.js.map +1 -0
- package/dist/{ProductsPage-C7_r9cKn.js → ProductsPage-BglMe8EM.js} +6 -6
- package/dist/{ProductsPage-C7_r9cKn.js.map → ProductsPage-BglMe8EM.js.map} +1 -1
- package/dist/PurchaseCreatePage-1tYk41RS.js +590 -0
- package/dist/PurchaseCreatePage-1tYk41RS.js.map +1 -0
- package/dist/{PurchaseRequestDetailPage-C7aAwEqw.js → PurchaseRequestDetailPage-BYLx3aBI.js} +14 -14
- package/dist/PurchaseRequestDetailPage-BYLx3aBI.js.map +1 -0
- package/dist/{PurchaseRequestsPage-BwCkbpaX.js → PurchaseRequestsPage-D2yorc4s.js} +4 -4
- package/dist/{PurchaseRequestsPage-BwCkbpaX.js.map → PurchaseRequestsPage-D2yorc4s.js.map} +1 -1
- package/dist/{PurchasesPage-CvaZouVy.js → PurchasesPage-CanQTjlw.js} +3 -3
- package/dist/{PurchasesPage-CvaZouVy.js.map → PurchasesPage-CanQTjlw.js.map} +1 -1
- package/dist/{SaleCreatePage-DP1Qmltu.js → SaleCreatePage-DxFvPvju.js} +145 -170
- package/dist/SaleCreatePage-DxFvPvju.js.map +1 -0
- package/dist/{SalesPage-CO3cdYX5.js → SalesPage-CPMrdr5-.js} +12 -12
- package/dist/SalesPage-CPMrdr5-.js.map +1 -0
- package/dist/SettingsPage-CEy922rW.js +662 -0
- package/dist/SettingsPage-CEy922rW.js.map +1 -0
- package/dist/{StorageUnitsPage-B9Hr_RkW.js → StorageUnitsPage-DfLT6xC_.js} +13 -13
- package/dist/{StorageUnitsPage-B9Hr_RkW.js.map → StorageUnitsPage-DfLT6xC_.js.map} +1 -1
- package/dist/{TransactionsPage-HKIGwEZN.js → TransactionsPage-KFUJ4O5g.js} +4 -4
- package/dist/TransactionsPage-KFUJ4O5g.js.map +1 -0
- package/dist/TransferCreatePage-k1tsRGB3.js +315 -0
- package/dist/TransferCreatePage-k1tsRGB3.js.map +1 -0
- package/dist/TransfersPage-CDafSVO9.js +470 -0
- package/dist/TransfersPage-CDafSVO9.js.map +1 -0
- package/dist/VialsPage-DPsYFgaQ.js +897 -0
- package/dist/VialsPage-DPsYFgaQ.js.map +1 -0
- package/dist/{currency-ClRLIEg2.js → currency-bVQ7aEk2.js} +141 -506
- package/dist/currency-bVQ7aEk2.js.map +1 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/BrandsPage-CSA3meke.js.map +0 -1
- package/dist/DashboardPage-CqaquOs2.js.map +0 -1
- package/dist/InventoryPage-CPbzfDMR.js.map +0 -1
- package/dist/MemberProfileCard-0vjO3Gzd.js.map +0 -1
- package/dist/PaymentCreatePage-BYmDZAIB.js.map +0 -1
- package/dist/PickupCreatePage-CMGwoAXj.js +0 -426
- package/dist/PickupCreatePage-CMGwoAXj.js.map +0 -1
- package/dist/PickupsPage-D66_Wta4.js.map +0 -1
- package/dist/PmTable-B9970kPY.js.map +0 -1
- package/dist/ProductEditPage-B8qFTCW9.js.map +0 -1
- package/dist/PurchaseCreatePage-CDfNDcKG.js +0 -627
- package/dist/PurchaseCreatePage-CDfNDcKG.js.map +0 -1
- package/dist/PurchaseRequestDetailPage-C7aAwEqw.js.map +0 -1
- package/dist/SaleCreatePage-DP1Qmltu.js.map +0 -1
- package/dist/SalesPage-CO3cdYX5.js.map +0 -1
- package/dist/SettingsPage-DvVRwSFw.js +0 -711
- package/dist/SettingsPage-DvVRwSFw.js.map +0 -1
- package/dist/TransactionsPage-HKIGwEZN.js.map +0 -1
- package/dist/TransferCreatePage-DMuZqQ3c.js +0 -363
- package/dist/TransferCreatePage-DMuZqQ3c.js.map +0 -1
- package/dist/TransfersPage-COIyouL4.js +0 -480
- package/dist/TransfersPage-COIyouL4.js.map +0 -1
- package/dist/VialsPage-BNPUwGCU.js +0 -922
- package/dist/VialsPage-BNPUwGCU.js.map +0 -1
- package/dist/currency-ClRLIEg2.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PurchaseCreatePage-1tYk41RS.js","names":[],"sources":["../src/views/PurchaseCreatePage.jsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useNavigate } 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 Grid,\n IconButton,\n Stack,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport DeleteOutlineRoundedIcon from '@mui/icons-material/DeleteOutlineRounded';\nimport AddRoundedIcon from '@mui/icons-material/AddRounded';\nimport SaveRoundedIcon from '@mui/icons-material/SaveRounded';\nimport Inventory2RoundedIcon from '@mui/icons-material/Inventory2Rounded';\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 {\n createPurchase,\n getProducts,\n getStorageUnits,\n listBatches,\n submitPurchaseRequest,\n} from '@pm/services/pmApi';\nimport FacilityPicker from '@pm/components/FacilityPicker';\nimport { BATCH_STATUSES, PAYMENT_MODES, labelOf } from '@pm/utils/constants';\nimport {\n isClean,\n validateFutureDate,\n validatePastOrTodayDate,\n validatePrice,\n validateQuantity,\n validateRequired,\n} from '@pm/utils/validation';\nimport { formatDate, formatNumber, statusColor } from '@pm/utils/format';\nimport { PmFormSection, PmSelect } from '@pm/components/PmTable';\nimport { useFieldErrors } from '@pm/utils/useFieldErrors';\nimport { usePermissionedEffect } from '@pm/utils/permissions';\nimport { usePmSettings } from '@pm/utils/settingsStore';\n\n// Purchase type → backend owner_type mapping. \"Personal Use\" puts the stock\n// against the acting user; \"Facility Use\" puts it against a facility the user\n// has access to. The Personal option is filtered out when the deployment-level\n// allowPersonalPurchase setting is off (see PURCHASE_TYPES filter below).\nconst ALL_PURCHASE_TYPES = [\n { value: 'USER', label: 'Personal Use' },\n { value: 'FACILITY', label: 'Facility Use' },\n];\n\nfunction emptyLine() {\n return {\n key: uuidv4(),\n batchId: uuidv4(),\n productId: '',\n quantity: 1,\n buyPrice: '',\n expiryDate: '',\n lotNumber: '',\n manufactureDate: '',\n storageUnitId: '',\n };\n}\n\nexport default function PurchaseCreatePage() {\n const navigate = useNavigate();\n const settings = usePmSettings();\n const manageAmounts = settings?.managePurchaseAmounts !== false;\n const allowPersonalPurchase = settings?.allowPersonalPurchase !== false;\n // When confirmation is on, the form submits to the request endpoint and\n // the user lands on the request detail page instead of the purchases list.\n const requireConfirmation = settings?.requirePurchaseConfirmation === true;\n // Personal Use is hidden entirely when the deployment doesn't allow it; the\n // dropdown then has a single option and the form defaults to FACILITY.\n const PURCHASE_TYPES = useMemo(\n () => (allowPersonalPurchase ? ALL_PURCHASE_TYPES : ALL_PURCHASE_TYPES.filter((t) => t.value !== 'USER')),\n [allowPersonalPurchase],\n );\n const [products, setProducts] = useState([]);\n const [storageUnits, setStorageUnits] = useState([]);\n const [batches, setBatches] = useState([]);\n const [error, setError] = useState('');\n const fieldErrors = useFieldErrors();\n const today = new Date().toISOString().substring(0, 10);\n const [form, setForm] = useState({\n id: uuidv4(),\n ownerType: allowPersonalPurchase ? 'USER' : 'FACILITY',\n ownerId: '',\n purchaseDate: today,\n paymentMode: 'CASH',\n notes: '',\n items: [emptyLine()],\n });\n\n // When the deployment setting flips from allowing personal purchases to not\n // allowing them (or vice-versa) after the form has rendered, snap the\n // ownerType back to a valid choice so the user can't end up submitting USER\n // against a deployment that doesn't accept it.\n useEffect(() => {\n if (!allowPersonalPurchase && form.ownerType === 'USER') {\n setForm((f) => ({ ...f, ownerType: 'FACILITY', ownerId: '' }));\n }\n }, [allowPersonalPurchase, form.ownerType]);\n\n usePermissionedEffect('update.manage.productManagement.purchase', () => {\n async function load() {\n try {\n const [p, su, b] = await Promise.all([\n getProducts(),\n getStorageUnits({ activeOnly: true }),\n // Pulled here so the existing-inventory panel renders alongside the\n // purchase form — the operator can see what they / the facility\n // already hold before deciding what to buy.\n listBatches().catch(() => []),\n ]);\n setProducts(p);\n setStorageUnits(su);\n setBatches(b);\n } catch (err) {\n setError(err?.response?.data?.message ?? err?.message ?? 'Failed to load reference data');\n }\n }\n void load();\n }, []);\n\n // Existing-stock view for the currently-selected owner. Personal purchases\n // show the operator's USER-owned batches; facility purchases show batches\n // owned by the picked facility. Exhausted/expired rows are skipped to keep\n // the panel decision-relevant.\n const existingInventory = useMemo(() => {\n if (form.ownerType === 'USER') {\n return batches.filter((b) => b.ownerType === 'USER' && Number(b.qtyRemaining ?? 0) > 0\n && b.status !== 'EXPIRED' && b.status !== 'EXHAUSTED');\n }\n if (form.ownerType === 'FACILITY' && form.ownerId) {\n return batches.filter((b) => b.ownerType === 'FACILITY' && b.ownerId === form.ownerId\n && Number(b.qtyRemaining ?? 0) > 0\n && b.status !== 'EXPIRED' && b.status !== 'EXHAUSTED');\n }\n return [];\n }, [batches, form.ownerType, form.ownerId]);\n\n // Storage units filtered by the selected facility (when applicable).\n // Personal purchases don't reference a storage unit at all.\n const eligibleStorageUnits = useMemo(() => {\n if (form.ownerType !== 'FACILITY' || !form.ownerId) return [];\n return storageUnits.filter((u) => u.ownerId === form.ownerId);\n }, [storageUnits, form.ownerType, form.ownerId]);\n\n function patchLine(idx, k, v) {\n setForm((f) => {\n const items = f.items.slice();\n items[idx] = { ...items[idx], [k]: v };\n return { ...f, items };\n });\n }\n\n function removeLine(idx) {\n setForm((f) => ({ ...f, items: f.items.filter((_, i) => i !== idx) }));\n }\n\n function addLine() {\n setForm((f) => ({ ...f, items: [...f.items, emptyLine()] }));\n }\n\n // Sum of (qty * buy price) for lines that have a price. Used to validate\n // the optional Total Amount header field.\n const computedLineTotal = useMemo(() => {\n let priced = false;\n const sum = form.items.reduce((acc, line) => {\n if (line.buyPrice === '' || line.quantity === '' || line.quantity == null) return acc;\n const qty = Number(line.quantity);\n const price = Number(line.buyPrice);\n if (Number.isNaN(qty) || Number.isNaN(price)) return acc;\n priced = true;\n return acc + qty * price;\n }, 0);\n return { sum, priced };\n }, [form.items]);\n\n // Header + per-line errors. The total is auto-derived from the line items so it\n // never needs its own validation message.\n const headerErrors = {\n ownerType: validateRequired(form.ownerType, 'Purchase type'),\n ownerId: form.ownerType === 'FACILITY' ? validateRequired(form.ownerId, 'Facility') : '',\n purchaseDate: validateRequired(form.purchaseDate, 'Purchase date'),\n };\n const lineErrors = form.items.map((line) => {\n const product = products.find((p) => p.id === line.productId);\n const expiryRequired = product?.hasExpiry === true;\n // Storage units only apply to facility-owned stock — personal purchases keep\n // the field disabled, so the requirement is only enforced for facility flows.\n const storageRequired = product?.needsStorageUnit === true && form.ownerType === 'FACILITY';\n let storageErr = storageRequired ? validateRequired(line.storageUnitId, 'Storage unit') : '';\n // Cold-chain check: if the product has a vaccine config with a required\n // storage range, the selected storage unit's operating range must fit\n // inside it. Mirror the backend STORAGE_UNIT_TEMP_MISMATCH rule.\n if (!storageErr && line.storageUnitId && product?.vaccineConfig) {\n const cfg = product.vaccineConfig;\n const unit = storageUnits.find((u) => u.id === line.storageUnitId);\n if (cfg.storageTempMinCelsius != null && cfg.storageTempMaxCelsius != null && unit) {\n const uMin = unit.operatingTempMinCelsius;\n const uMax = unit.operatingTempMaxCelsius;\n // Unit must be able to reach every temp the vaccine tolerates: its\n // operating range must contain the vaccine's required range.\n if (uMin == null || uMax == null\n || Number(uMin) > Number(cfg.storageTempMinCelsius)\n || Number(uMax) < Number(cfg.storageTempMaxCelsius)) {\n storageErr = `Unit operates ${uMin ?? '?'}°C → ${uMax ?? '?'}°C, which cannot cover this product's required ${cfg.storageTempMinCelsius}°C → ${cfg.storageTempMaxCelsius}°C.`;\n }\n }\n }\n return {\n productId: validateRequired(line.productId, 'Product'),\n quantity: validateQuantity(line.quantity),\n // When purchase amounts are managed, a positive buy price is mandatory;\n // otherwise it stays optional. Mirrors the backend enforcement.\n buyPrice: manageAmounts\n ? (validateRequired(line.buyPrice, 'Buy price')\n || validatePrice(line.buyPrice)\n || (Number(line.buyPrice) > 0 ? '' : 'Buy price must be greater than 0.'))\n : validatePrice(line.buyPrice),\n // In request mode the approver fills expiry at confirmation, so we\n // don't require it from the requester even when the product carries\n // hasExpiry. The approver gets to enter the actual expiry off the\n // packaging on PurchaseRequestDetailPage.\n expiryDate: requireConfirmation\n ? ''\n : expiryRequired\n ? (validateRequired(line.expiryDate, 'Expiry date')\n || validateFutureDate(line.expiryDate, { label: 'Expiry' }))\n : validateFutureDate(line.expiryDate, { label: 'Expiry' }),\n manufactureDate: validatePastOrTodayDate(line.manufactureDate, { label: 'Manufacture date' }),\n storageUnitId: storageErr,\n };\n });\n const formIsValid = isClean(headerErrors) && lineErrors.every(isClean) && form.items.length > 0;\n\n async function save() {\n if (!formIsValid) {\n fieldErrors.markAllTouched();\n setError('Please fix the highlighted fields and try again.');\n return;\n }\n // Payload shape is the same for direct purchase and request submission —\n // the difference is the endpoint and what we navigate to afterwards.\n const payload = {\n id: form.id,\n ownerType: form.ownerType,\n ownerId: form.ownerType === 'FACILITY' ? form.ownerId : null,\n purchaseDate: form.purchaseDate,\n paymentMode: manageAmounts ? (form.paymentMode || null) : null,\n // Amount tracking is a per-deployment toggle. When it's off, scrub money\n // values to null so the backend's validatePurchaseTotal stays a no-op\n // and inventory rows don't carry stale prices that aren't surfaced anywhere.\n totalAmount: manageAmounts && computedLineTotal.priced ? Number(computedLineTotal.sum.toFixed(2)) : null,\n notes: form.notes || null,\n items: form.items.map((line) => ({\n batchId: line.batchId,\n productId: line.productId,\n quantity: Number(line.quantity),\n buyPrice: manageAmounts && line.buyPrice !== '' ? Number(line.buyPrice) : null,\n expiryDate: line.expiryDate || null,\n lotNumber: line.lotNumber || null,\n manufactureDate: line.manufactureDate || null,\n storageUnitId: line.storageUnitId || null,\n })),\n };\n try {\n if (requireConfirmation) {\n const submitted = await submitPurchaseRequest(payload);\n showToast({ message: 'Purchase request submitted for approval.', type: 'success' });\n navigate(`/ui/medplat/manage/pm/purchase-requests/${submitted?.id ?? form.id}`);\n } else {\n await createPurchase(payload);\n showToast({ message: 'Purchase recorded.', type: 'success' });\n navigate('/ui/medplat/manage/pm/purchases');\n }\n } catch (err) {\n setError(err?.response?.data?.message\n ?? err?.message\n ?? (requireConfirmation ? 'Failed to submit purchase request' : 'Failed to record purchase'));\n }\n }\n\n return (\n <Box>\n <PmNav />\n <PmGuard\n perm={'update.manage.productManagement.purchase'}\n title={requireConfirmation\n ? \"You don’t have permission to submit purchase requests\"\n : \"You don’t have permission to record purchases\"}>\n <PmPageShell\n title={requireConfirmation ? 'Submit Purchase Request' : 'Record Purchase'}\n subtitle={requireConfirmation\n ? 'Purchases need approval before stock is added. Submit the request and wait for a manager to confirm.'\n : 'Each item creates a batch that you can track and a record in your ledger.'}\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"outlined\"\n onClick={() => navigate(requireConfirmation\n ? '/ui/medplat/manage/pm/purchase-requests'\n : '/ui/medplat/manage/pm/purchases')}>Cancel</Button>\n <Button variant=\"contained\" startIcon={<SaveRoundedIcon />}\n disabled={!formIsValid}\n onClick={() => void save()}>{requireConfirmation ? 'Submit Request' : 'Save'}</Button>\n </Stack>\n }\n >\n {error ? <Alert severity=\"error\" sx={{ mb: 2 }}>{error}</Alert> : null}\n\n {/* ---------- Ownership ---------- */}\n <Card sx={{ borderRadius: 1.5, mb: 2, border: '1px solid', borderColor: 'divider', boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)' }}>\n <CardContent sx={{ p: 2.5 }}>\n <PmFormSection\n eyebrow=\"Step 1\"\n title=\"Purchase for\"\n hint=\"Choose who will own this stock: yourself or a facility.\"\n sx={{ mb: 2 }}\n />\n <Grid container spacing={2}>\n <Grid item xs={12} md={4}>\n <PmSelect\n label=\"Purchase Type\"\n required\n value={form.ownerType}\n onChange={(v) => setForm({ ...form, ownerType: v, ownerId: '' })}\n onBlur={fieldErrors.onBlur('ownerType')}\n error={fieldErrors.show('ownerType') && !!headerErrors.ownerType}\n options={PURCHASE_TYPES}\n helperText={fieldErrors.show('ownerType') && headerErrors.ownerType\n ? headerErrors.ownerType\n : 'Personal stock vs. facility stock'}\n />\n </Grid>\n {form.ownerType === 'FACILITY' ? (\n <Grid item xs={12} md={4}>\n <FacilityPicker\n required\n value={form.ownerId}\n onChange={(id) => setForm({ ...form, ownerId: id })}\n onBlur={fieldErrors.onBlur('ownerId')}\n error={fieldErrors.show('ownerId') && !!headerErrors.ownerId}\n helperText={\n fieldErrors.show('ownerId') && headerErrors.ownerId\n ? headerErrors.ownerId\n : 'Search for the facility.'\n }\n />\n </Grid>\n ) : null}\n </Grid>\n </CardContent>\n </Card>\n\n {/* ---------- Existing inventory for the picked owner ---------- */}\n {(form.ownerType === 'USER' || (form.ownerType === 'FACILITY' && form.ownerId)) ? (\n <Card sx={{ borderRadius: 1.5, mb: 2, border: '1px solid', borderColor: 'divider', boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)' }}>\n <CardContent sx={{ p: 2.5 }}>\n <PmFormSection\n eyebrow=\"Context\"\n title=\"Existing inventory\"\n hint={form.ownerType === 'USER'\n ? 'Your current stock.'\n : 'What this facility already has, including where it is stored.'}\n sx={{ mb: 1.5 }}\n />\n {existingInventory.length === 0 ? (\n <Stack direction=\"row\" spacing={1.25} alignItems=\"center\" sx={{ py: 1.5, color: 'text.secondary' }}>\n <Inventory2RoundedIcon fontSize=\"small\" />\n <Typography variant=\"body2\">No active stock yet for this owner.</Typography>\n </Stack>\n ) : (\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Product</TableCell>\n <TableCell>Lot</TableCell>\n <TableCell align=\"right\">Remaining</TableCell>\n <TableCell>Expiry</TableCell>\n {form.ownerType === 'FACILITY' ? <TableCell>Storage</TableCell> : null}\n <TableCell>Status</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {existingInventory.map((b) => (\n <TableRow key={b.id}>\n <TableCell sx={{ fontWeight: 700 }}>{b.productName}</TableCell>\n <TableCell sx={{ fontFamily: 'monospace', fontSize: '0.78rem' }}>{b.lotNumber || '—'}</TableCell>\n <TableCell align=\"right\" sx={{ fontVariantNumeric: 'tabular-nums', fontWeight: 700 }}>\n {b.qtyRemaining}\n </TableCell>\n <TableCell>{b.expiryDate ? formatDate(b.expiryDate) : '—'}</TableCell>\n {form.ownerType === 'FACILITY' ? (\n <TableCell>{b.storageUnitName || '—'}</TableCell>\n ) : null}\n <TableCell>\n <Chip size=\"small\" label={labelOf(BATCH_STATUSES, b.status)} color={statusColor(b.status)} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n )}\n </CardContent>\n </Card>\n ) : null}\n\n {/* ---------- Lines ---------- */}\n <Card sx={{ borderRadius: 1.5, mb: 2, border: '1px solid', borderColor: 'divider', boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)' }}>\n <CardContent sx={{ p: 2.5 }}>\n <Stack direction=\"row\" justifyContent=\"space-between\" alignItems=\"flex-start\" sx={{ mb: 2 }}>\n <PmFormSection\n eyebrow=\"Step 2\"\n title=\"Line items\"\n hint=\"Each item creates one batch in your inventory and a receipt record.\"\n />\n <Button variant=\"outlined\" startIcon={<AddRoundedIcon />} onClick={addLine}>Add Line</Button>\n </Stack>\n <Divider sx={{ mb: 2 }} />\n <Stack spacing={2}>\n {form.items.map((line, idx) => {\n const lerr = lineErrors[idx] || {};\n const product = products.find((p) => p.id === line.productId);\n const expiryRequired = product?.hasExpiry === true;\n const storageRequired = product?.needsStorageUnit === true && form.ownerType === 'FACILITY';\n return (\n <Card key={line.key} variant=\"outlined\" sx={{ p: 2, borderRadius: 1.5 }}>\n <Grid container spacing={2}>\n <Grid item xs={12} md={3}>\n <PmSelect\n label=\"Product\"\n required\n value={line.productId}\n onChange={(v) => patchLine(idx, 'productId', v)}\n onBlur={fieldErrors.onBlur(`items.${idx}.productId`)}\n error={fieldErrors.show(`items.${idx}.productId`) && !!lerr.productId}\n helperText={fieldErrors.show(`items.${idx}.productId`) ? lerr.productId : ''}\n options={products.map((p) => ({ value: p.id, label: p.name }))}\n />\n </Grid>\n <Grid item xs={6} md={1}>\n <TextField required fullWidth size=\"small\" type=\"number\" label=\"Qty\"\n inputProps={{ min: 1 }}\n value={line.quantity}\n onChange={(e) => patchLine(idx, 'quantity', e.target.value)}\n onBlur={fieldErrors.onBlur(`items.${idx}.quantity`)}\n error={fieldErrors.show(`items.${idx}.quantity`) && !!lerr.quantity}\n helperText={fieldErrors.show(`items.${idx}.quantity`) ? lerr.quantity : ''} />\n </Grid>\n {manageAmounts ? (\n <Grid item xs={6} md={2}>\n <TextField required fullWidth size=\"small\" type=\"number\" inputProps={{ step: '0.01', min: 0 }}\n label=\"Buy Price\"\n value={line.buyPrice}\n onChange={(e) => patchLine(idx, 'buyPrice', e.target.value)}\n onBlur={fieldErrors.onBlur(`items.${idx}.buyPrice`)}\n error={fieldErrors.show(`items.${idx}.buyPrice`) && !!lerr.buyPrice}\n helperText={fieldErrors.show(`items.${idx}.buyPrice`) ? lerr.buyPrice : ''} />\n </Grid>\n ) : null}\n {/* Expiry is captured at confirmation time alongside Lot —\n the requester is asking for stock they don't yet have,\n so they can't read the expiry off the packaging. */}\n {requireConfirmation ? null : (\n <Grid item xs={6} md={2}>\n <TextField required={expiryRequired} fullWidth size=\"small\" type=\"date\" label=\"Expiry\"\n InputLabelProps={{ shrink: true }}\n inputProps={{ min: today }}\n value={line.expiryDate}\n onChange={(e) => patchLine(idx, 'expiryDate', e.target.value)}\n onBlur={fieldErrors.onBlur(`items.${idx}.expiryDate`)}\n error={fieldErrors.show(`items.${idx}.expiryDate`) && !!lerr.expiryDate}\n helperText={\n fieldErrors.show(`items.${idx}.expiryDate`) && lerr.expiryDate\n ? lerr.expiryDate\n : (expiryRequired ? 'Required for this product' : '')\n } />\n </Grid>\n )}\n {/* Lot # is captured at confirmation time by the approver,\n who physically receives the goods. Hidden when this\n form is in request-mode so the requester doesn't waste\n time guessing a lot they don't have yet. */}\n {requireConfirmation ? null : (\n <Grid item xs={6} md={2}>\n <TextField fullWidth size=\"small\" label=\"Lot #\"\n value={line.lotNumber}\n onChange={(e) => patchLine(idx, 'lotNumber', e.target.value)} />\n </Grid>\n )}\n <Grid item xs={10} md={1}>\n <PmSelect\n label=\"Storage\"\n required={storageRequired}\n disabled={form.ownerType !== 'FACILITY' || !form.ownerId}\n value={line.storageUnitId || ''}\n onChange={(v) => patchLine(idx, 'storageUnitId', v)}\n onBlur={fieldErrors.onBlur(`items.${idx}.storageUnitId`)}\n error={fieldErrors.show(`items.${idx}.storageUnitId`) && !!lerr.storageUnitId}\n options={eligibleStorageUnits.map((u) => ({ value: u.id, label: u.unitName }))}\n helperText={fieldErrors.show(`items.${idx}.storageUnitId`) && lerr.storageUnitId\n ? lerr.storageUnitId\n : storageRequired ? 'Required for this product' : ''}\n />\n </Grid>\n <Grid item xs={2} md={1} sx={{ textAlign: 'right' }}>\n <IconButton color=\"error\" onClick={() => removeLine(idx)} disabled={form.items.length === 1}>\n <DeleteOutlineRoundedIcon />\n </IconButton>\n </Grid>\n </Grid>\n </Card>\n );\n })}\n </Stack>\n </CardContent>\n </Card>\n\n {/* ---------- Summary footer (like a bill) ---------- */}\n <Card sx={{\n borderRadius: 1.5,\n bgcolor: 'grey.50',\n border: '1px solid',\n borderColor: 'divider',\n boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)',\n }}>\n <CardContent sx={{ p: 2.5 }}>\n <PmFormSection\n eyebrow=\"Step 3\"\n title={manageAmounts ? 'Bill summary' : 'Receipt details'}\n hint={manageAmounts\n ? 'The total is calculated from items. Add payment mode and notes. The date is set automatically.'\n : 'Add notes about this receipt. The date is set automatically.'}\n sx={{ mb: 2 }}\n />\n <Grid container spacing={2}>\n {manageAmounts ? (\n <Grid item xs={12} md={6}>\n <PmSelect\n label=\"Payment Mode\"\n value={form.paymentMode || ''}\n onChange={(v) => setForm({ ...form, paymentMode: v })}\n options={PAYMENT_MODES}\n />\n </Grid>\n ) : null}\n <Grid item xs={12} md={manageAmounts ? 6 : 12}>\n <TextField fullWidth size=\"small\" label=\"Notes\" value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })} />\n </Grid>\n </Grid>\n\n {manageAmounts ? (\n <>\n <Divider sx={{ my: 2 }} />\n\n <Stack direction=\"row\" justifyContent=\"flex-end\" alignItems=\"baseline\" spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Total ({form.items.length} {form.items.length === 1 ? 'line' : 'lines'})\n </Typography>\n <Typography variant=\"h4\" sx={{ fontWeight: 800, fontVariantNumeric: 'tabular-nums' }}>\n {computedLineTotal.priced ? formatNumber(computedLineTotal.sum) : '—'}\n </Typography>\n </Stack>\n {!computedLineTotal.priced ? (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ display: 'block', textAlign: 'right' }}>\n Add a Buy Price on at least one line to compute the total.\n </Typography>\n ) : null}\n </>\n ) : null}\n </CardContent>\n </Card>\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|
package/dist/{PurchaseRequestDetailPage-C7aAwEqw.js → PurchaseRequestDetailPage-BYLx3aBI.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as re, i as o, r as ie } from "./PmTable-
|
|
1
|
+
import { a as e, d as t, i as n, k as r, l as i, n as a, o as ee, r as te, v as ne } from "./currency-bVQ7aEk2.js";
|
|
2
|
+
import { a as re, i as o, r as ie } from "./PmTable-CCpaJEA_.js";
|
|
3
3
|
import { n as s, t as c } from "./format-UOyOu75i.js";
|
|
4
4
|
import { useMemo as l, useState as u } from "react";
|
|
5
5
|
import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
@@ -27,16 +27,16 @@ function E(e) {
|
|
|
27
27
|
return e ? e.toLowerCase().split("_").map((e) => e.charAt(0).toUpperCase() + e.slice(1)).join(" ") : "";
|
|
28
28
|
}
|
|
29
29
|
function D() {
|
|
30
|
-
let D = ue(), { requestId: k } = de(), A =
|
|
30
|
+
let D = ue(), { requestId: k } = de(), A = e().has("approve.manage.productManagement.purchase"), j = i()?.managePurchaseAmounts !== !1, [M, N] = u(null), [ve, P] = u(!0), [F, I] = u(!1), [L, R] = u(""), [z, B] = u({}), [V, H] = u({}), [U, W] = u({}), [G, K] = u("");
|
|
31
31
|
async function q() {
|
|
32
32
|
P(!0), R("");
|
|
33
33
|
try {
|
|
34
|
-
let
|
|
35
|
-
N(
|
|
36
|
-
let
|
|
37
|
-
(
|
|
38
|
-
|
|
39
|
-
}), B(
|
|
34
|
+
let e = await r(k);
|
|
35
|
+
N(e);
|
|
36
|
+
let t = {}, n = {}, i = {};
|
|
37
|
+
(e?.items ?? []).forEach((e) => {
|
|
38
|
+
t[e.id] = String(e.approvedQuantity == null ? e.requestedQuantity : e.approvedQuantity), n[e.id] = e.lotNumber ?? "", i[e.id] = e.expiryDate ? new Date(e.expiryDate).toISOString().substring(0, 10) : "";
|
|
39
|
+
}), B(t), H(n), W(i), K(e?.decisionNotes ?? "");
|
|
40
40
|
} catch (e) {
|
|
41
41
|
R(e?.response?.data?.message ?? e?.message ?? "Failed to load purchase request");
|
|
42
42
|
} finally {
|
|
@@ -158,7 +158,7 @@ function D() {
|
|
|
158
158
|
if (M) {
|
|
159
159
|
I(!0), R("");
|
|
160
160
|
try {
|
|
161
|
-
N(await
|
|
161
|
+
N(await t(M.id)), T({
|
|
162
162
|
message: "Request cancelled.",
|
|
163
163
|
type: "success"
|
|
164
164
|
});
|
|
@@ -197,7 +197,7 @@ function D() {
|
|
|
197
197
|
children: L
|
|
198
198
|
}) : null, !ve && M ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(_, {
|
|
199
199
|
sx: {
|
|
200
|
-
borderRadius:
|
|
200
|
+
borderRadius: 1.5,
|
|
201
201
|
mb: 2,
|
|
202
202
|
border: "1px solid",
|
|
203
203
|
borderColor: "divider",
|
|
@@ -318,7 +318,7 @@ function D() {
|
|
|
318
318
|
})
|
|
319
319
|
}), /* @__PURE__ */ f(_, {
|
|
320
320
|
sx: {
|
|
321
|
-
borderRadius:
|
|
321
|
+
borderRadius: 1.5,
|
|
322
322
|
mb: 2,
|
|
323
323
|
border: "1px solid",
|
|
324
324
|
borderColor: "divider",
|
|
@@ -481,7 +481,7 @@ function D() {
|
|
|
481
481
|
sx: (e) => ({
|
|
482
482
|
minWidth: 220,
|
|
483
483
|
p: 1.5,
|
|
484
|
-
borderRadius:
|
|
484
|
+
borderRadius: 1.5,
|
|
485
485
|
border: "1px solid",
|
|
486
486
|
borderColor: "divider",
|
|
487
487
|
bgcolor: fe(e.palette.primary.main, .04),
|
|
@@ -583,4 +583,4 @@ function O({ label: e, children: t }) {
|
|
|
583
583
|
//#endregion
|
|
584
584
|
export { D as default };
|
|
585
585
|
|
|
586
|
-
//# sourceMappingURL=PurchaseRequestDetailPage-
|
|
586
|
+
//# sourceMappingURL=PurchaseRequestDetailPage-BYLx3aBI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PurchaseRequestDetailPage-BYLx3aBI.js","names":[],"sources":["../src/views/PurchaseRequestDetailPage.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport {\n Alert,\n Box,\n Button,\n Card,\n CardContent,\n Chip,\n Divider,\n Stack,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport { alpha } from '@mui/material/styles';\nimport RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';\nimport ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';\nimport CheckCircleRoundedIcon from '@mui/icons-material/CheckCircleRounded';\nimport CancelRoundedIcon from '@mui/icons-material/CancelRounded';\nimport DoNotDisturbRoundedIcon from '@mui/icons-material/DoNotDisturbRounded';\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 { PmFormSection, PmMoney, PmRef } from '@pm/components/PmTable';\nimport { usePermissionedEffect, usePmPermissions } from '@pm/utils/permissions';\nimport {\n cancelPurchaseRequest,\n decidePurchaseRequest,\n getPurchaseRequest,\n} from '@pm/services/pmApi';\nimport { formatDate, formatDateTime } from '@pm/utils/format';\nimport { usePmSettings } from '@pm/utils/settingsStore';\n\nfunction statusChipColor(status) {\n switch (status) {\n case 'PENDING': return 'info';\n case 'APPROVED': return 'success';\n case 'PARTIALLY_APPROVED': return 'warning';\n case 'REJECTED': return 'error';\n case 'CANCELLED': return 'default';\n default: return 'default';\n }\n}\n\nfunction prettyStatus(status) {\n if (!status) return '';\n return status\n .toLowerCase()\n .split('_')\n .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n .join(' ');\n}\n\nexport default function PurchaseRequestDetailPage() {\n const navigate = useNavigate();\n const { requestId } = useParams();\n const perms = usePmPermissions();\n const canDecide = perms.has('approve.manage.productManagement.purchase');\n const settings = usePmSettings();\n const manageAmounts = settings?.managePurchaseAmounts !== false;\n\n const [request, setRequest] = useState(null);\n const [loading, setLoading] = useState(true);\n const [submitting, setSubmitting] = useState(false);\n const [error, setError] = useState('');\n // Per-line approved quantities the approver is currently editing. Keyed by\n // item.id so the controls stay decoupled from order in the source list.\n const [approvedDraft, setApprovedDraft] = useState({});\n // Per-line lot numbers the approver fills at decision time — the requester\n // doesn't have the goods yet, so they don't know what lot will be shipped.\n // The approver enters this when confirming receipt.\n const [lotDraft, setLotDraft] = useState({});\n // Same logic for expiry — the requester can't read the expiry off a vial\n // they don't have. Captured by the approver.\n const [expiryDraft, setExpiryDraft] = useState({});\n const [decisionNotes, setDecisionNotes] = useState('');\n\n async function load() {\n setLoading(true);\n setError('');\n try {\n const data = await getPurchaseRequest(requestId);\n setRequest(data);\n // Seed the draft with full approval — the typical case is \"approve everything\".\n const draft = {};\n const lots = {};\n const expiries = {};\n (data?.items ?? []).forEach((it) => {\n draft[it.id] = String(it.approvedQuantity != null ? it.approvedQuantity : it.requestedQuantity);\n lots[it.id] = it.lotNumber ?? '';\n // Expiry from the existing item, normalized to yyyy-MM-dd for the date input.\n expiries[it.id] = it.expiryDate ? new Date(it.expiryDate).toISOString().substring(0, 10) : '';\n });\n setApprovedDraft(draft);\n setLotDraft(lots);\n setExpiryDraft(expiries);\n setDecisionNotes(data?.decisionNotes ?? '');\n } catch (err) {\n setError(err?.response?.data?.message ?? err?.message ?? 'Failed to load purchase request');\n } finally {\n setLoading(false);\n }\n }\n\n usePermissionedEffect(\n ['view.manage.productManagement.purchase', 'approve.manage.productManagement.purchase'],\n () => { void load(); },\n [requestId],\n );\n\n const isPending = request?.status === 'PENDING';\n\n // Sum the *approved* dollars for the in-flight decision so the approver\n // can see the cost of what they're about to confirm.\n const approvedTotal = useMemo(() => {\n if (!request || !manageAmounts) return null;\n let total = 0;\n let priced = false;\n (request.items ?? []).forEach((it) => {\n const qty = Number(approvedDraft[it.id] ?? 0);\n if (!Number.isFinite(qty) || qty <= 0) return;\n if (it.buyPrice == null) return;\n total += Number(it.buyPrice) * qty;\n priced = true;\n });\n return priced ? total : null;\n }, [request, approvedDraft, manageAmounts]);\n\n function patchDraft(itemId, value) {\n setApprovedDraft((d) => ({ ...d, [itemId]: value }));\n }\n\n function patchLot(itemId, value) {\n setLotDraft((d) => ({ ...d, [itemId]: value }));\n }\n\n function patchExpiry(itemId, value) {\n setExpiryDraft((d) => ({ ...d, [itemId]: value }));\n }\n\n function setAllToRequested() {\n if (!request) return;\n const draft = {};\n request.items.forEach((it) => { draft[it.id] = String(it.requestedQuantity); });\n setApprovedDraft(draft);\n }\n\n function setAllToZero() {\n if (!request) return;\n const draft = {};\n request.items.forEach((it) => { draft[it.id] = '0'; });\n setApprovedDraft(draft);\n }\n\n // Validates each line's approved qty is an integer in [0, requestedQty].\n // Returns a per-id error map; empty means \"ready to submit\".\n const draftErrors = useMemo(() => {\n if (!request) return {};\n const errs = {};\n (request.items ?? []).forEach((it) => {\n const raw = approvedDraft[it.id];\n if (raw == null || raw === '') {\n errs[it.id] = 'Required';\n return;\n }\n const n = Number(raw);\n if (!Number.isInteger(n) || n < 0) errs[it.id] = 'Must be 0 or a positive whole number';\n else if (n > it.requestedQuantity) errs[it.id] = `At most ${it.requestedQuantity}`;\n });\n return errs;\n }, [request, approvedDraft]);\n // Lot is required on every line the approver is approving (>0). Rejected\n // lines don't need a lot since no stock lands.\n const lotErrors = useMemo(() => {\n if (!request) return {};\n const errs = {};\n (request.items ?? []).forEach((it) => {\n const qty = Number(approvedDraft[it.id] ?? 0);\n if (!Number.isFinite(qty) || qty <= 0) return;\n const lot = (lotDraft[it.id] ?? '').trim();\n if (!lot) errs[it.id] = 'Lot is required when approving this line';\n });\n return errs;\n }, [request, approvedDraft, lotDraft]);\n // Expiry mirrors lot — required for any approved line, must be in the future.\n const expiryErrors = useMemo(() => {\n if (!request) return {};\n const errs = {};\n const todayStr = new Date().toISOString().substring(0, 10);\n (request.items ?? []).forEach((it) => {\n const qty = Number(approvedDraft[it.id] ?? 0);\n if (!Number.isFinite(qty) || qty <= 0) return;\n const exp = (expiryDraft[it.id] ?? '').trim();\n if (!exp) {\n errs[it.id] = 'Expiry is required when approving this line';\n } else if (exp < todayStr) {\n errs[it.id] = 'Expiry must be today or later';\n }\n });\n return errs;\n }, [request, approvedDraft, expiryDraft]);\n const draftIsClean = Object.keys(draftErrors).length === 0\n && Object.keys(lotErrors).length === 0\n && Object.keys(expiryErrors).length === 0;\n\n async function submit() {\n if (!draftIsClean || !request) return;\n setSubmitting(true);\n setError('');\n try {\n const updated = await decidePurchaseRequest(request.id, {\n decisionNotes: decisionNotes || null,\n items: request.items.map((it) => {\n const lot = (lotDraft[it.id] ?? '').trim();\n const exp = (expiryDraft[it.id] ?? '').trim();\n return {\n itemId: it.id,\n approvedQuantity: Number(approvedDraft[it.id]),\n lotNumber: lot ? lot : null,\n expiryDate: exp ? exp : null,\n };\n }),\n });\n setRequest(updated);\n showToast({ message: `Request ${prettyStatus(updated.status).toLowerCase()}.`, type: 'success' });\n } catch (err) {\n setError(err?.response?.data?.message ?? err?.message ?? 'Failed to submit decision');\n } finally {\n setSubmitting(false);\n }\n }\n\n async function cancel() {\n if (!request) return;\n setSubmitting(true);\n setError('');\n try {\n const updated = await cancelPurchaseRequest(request.id);\n setRequest(updated);\n showToast({ message: 'Request cancelled.', type: 'success' });\n } catch (err) {\n setError(err?.response?.data?.message ?? err?.message ?? 'Failed to cancel request');\n } finally {\n setSubmitting(false);\n }\n }\n\n // Coarse-grained UI states for the approver: clean PENDING + decide perm →\n // show the editable decision form. Otherwise the same table is shown\n // read-only with the historical approved quantities.\n const showDecisionForm = isPending && canDecide;\n\n return (\n <Box>\n <PmNav />\n <PmGuard anyOf={['view.manage.productManagement.purchase', 'approve.manage.productManagement.purchase']}>\n <PmPageShell\n title={request?.serialId ? `Purchase Request #${request.serialId}` : 'Purchase Request'}\n subtitle=\"Approve or reject each item. Your decision closes the entire request.\"\n chips={request?.status ? [prettyStatus(request.status)] : []}\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"outlined\" startIcon={<ArrowBackRoundedIcon />} onClick={() => navigate('/ui/medplat/manage/pm/purchase-requests')}>Back</Button>\n <Button variant=\"outlined\" startIcon={<RefreshRoundedIcon />} onClick={() => void load()}>Refresh</Button>\n </Stack>\n }\n >\n {error ? <Alert severity=\"error\" sx={{ mb: 2 }}>{error}</Alert> : null}\n\n {!loading && request ? (\n <>\n {/* ---------- Summary ---------- */}\n <Card sx={{ borderRadius: 1.5, mb: 2, border: '1px solid', borderColor: 'divider', boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)' }}>\n <CardContent sx={{ p: 2.5 }}>\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2} alignItems={{ md: 'flex-start' }}>\n <Box sx={{ flexGrow: 1, minWidth: 0 }}>\n <Stack direction=\"row\" alignItems=\"center\" spacing={1.5} sx={{ mb: 0.5 }}>\n <PmRef id={request.serialId} />\n <Chip size=\"small\" label={prettyStatus(request.status)} color={statusChipColor(request.status)} />\n <Typography variant=\"caption\" color=\"text.secondary\">{request.ownerType}</Typography>\n </Stack>\n <Typography variant=\"h6\" sx={{ fontWeight: 800 }}>\n {request.ownerName || (request.ownerType === 'USER' ? 'Personal' : 'Facility')}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ display: 'block' }}>\n For purchase date {formatDate(request.purchaseDate)}\n </Typography>\n </Box>\n <Stack spacing={0.25} sx={{ minWidth: 260 }}>\n <Row label=\"Requested by\">{request.requestedByName || `#${request.requestedBy}`}</Row>\n <Row label=\"Requested on\">{formatDateTime(request.requestedOn)}</Row>\n {request.decidedBy ? (\n <>\n <Row label=\"Decided by\">{request.decidedByName || `#${request.decidedBy}`}</Row>\n <Row label=\"Decided on\">{formatDateTime(request.decidedOn)}</Row>\n </>\n ) : null}\n {request.paymentMode ? <Row label=\"Payment\">{request.paymentMode}</Row> : null}\n </Stack>\n </Stack>\n {request.notes ? (\n <>\n <Divider sx={{ my: 1.5 }} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ display: 'block', mb: 0.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6 }}>\n Requester notes\n </Typography>\n <Typography variant=\"body2\">{request.notes}</Typography>\n </>\n ) : null}\n {request.decisionNotes ? (\n <>\n <Divider sx={{ my: 1.5 }} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ display: 'block', mb: 0.5, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.6 }}>\n Decision notes\n </Typography>\n <Typography variant=\"body2\">{request.decisionNotes}</Typography>\n </>\n ) : null}\n </CardContent>\n </Card>\n\n {/* ---------- Line items ---------- */}\n <Card sx={{ borderRadius: 1.5, mb: 2, border: '1px solid', borderColor: 'divider', boxShadow: '0 12px 32px rgba(15, 23, 42, 0.045)' }}>\n <CardContent sx={{ p: 2.5 }}>\n <Stack direction={{ xs: 'column', md: 'row' }} alignItems={{ md: 'flex-end' }} justifyContent=\"space-between\" sx={{ mb: 1.5 }}>\n <PmFormSection\n eyebrow=\"Lines\"\n title={showDecisionForm ? 'Decide per-line quantities' : 'Requested items'}\n hint={showDecisionForm\n ? 'Enter 0 to reject an item, or less than requested to partially approve.'\n : null}\n />\n {showDecisionForm ? (\n <Stack direction=\"row\" spacing={1}>\n <Button size=\"small\" variant=\"outlined\" onClick={setAllToRequested}>Approve all</Button>\n <Button size=\"small\" variant=\"outlined\" color=\"error\" onClick={setAllToZero}>Reject all</Button>\n </Stack>\n ) : null}\n </Stack>\n\n <Table size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Product</TableCell>\n <TableCell>Lot</TableCell>\n <TableCell>Expiry</TableCell>\n {manageAmounts ? <TableCell align=\"right\">Buy Price</TableCell> : null}\n <TableCell align=\"right\">Requested</TableCell>\n <TableCell align=\"right\">{showDecisionForm ? 'Approve' : 'Approved'}</TableCell>\n {request.ownerType === 'FACILITY' ? <TableCell>Storage</TableCell> : null}\n </TableRow>\n </TableHead>\n <TableBody>\n {request.items.map((it) => {\n const lineErr = draftErrors[it.id];\n const lotErr = lotErrors[it.id];\n const expiryErr = expiryErrors[it.id];\n const approveQty = Number(approvedDraft[it.id] ?? 0);\n const lotInputActive = showDecisionForm && Number.isFinite(approveQty) && approveQty > 0;\n return (\n <TableRow key={it.id}>\n <TableCell sx={{ fontWeight: 700 }}>{it.productName}</TableCell>\n <TableCell sx={{ fontFamily: 'monospace', fontSize: '0.78rem' }}>\n {showDecisionForm ? (\n <TextField\n size=\"small\"\n placeholder=\"Enter lot\"\n value={lotDraft[it.id] ?? ''}\n onChange={(e) => patchLot(it.id, e.target.value)}\n disabled={!lotInputActive}\n error={!!lotErr}\n helperText={lotErr || (lotInputActive ? '' : 'Approve qty > 0 to enter lot')}\n inputProps={{ style: { fontFamily: 'monospace', fontSize: '0.78rem' } }}\n sx={{ minWidth: 140 }}\n />\n ) : (it.lotNumber || '—')}\n </TableCell>\n <TableCell>\n {showDecisionForm ? (\n <TextField\n size=\"small\"\n type=\"date\"\n InputLabelProps={{ shrink: true }}\n value={expiryDraft[it.id] ?? ''}\n onChange={(e) => patchExpiry(it.id, e.target.value)}\n disabled={!lotInputActive}\n error={!!expiryErr}\n helperText={expiryErr || (lotInputActive ? '' : '')}\n sx={{ minWidth: 150 }}\n />\n ) : (it.expiryDate ? formatDate(it.expiryDate) : '—')}\n </TableCell>\n {manageAmounts ? (\n <TableCell align=\"right\">\n {it.buyPrice != null ? <PmMoney value={it.buyPrice} bold={false} /> : <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>}\n </TableCell>\n ) : null}\n <TableCell align=\"right\" sx={{ fontVariantNumeric: 'tabular-nums', fontWeight: 700 }}>\n {it.requestedQuantity}\n </TableCell>\n <TableCell align=\"right\">\n {showDecisionForm ? (\n <TextField\n size=\"small\"\n type=\"number\"\n inputProps={{ min: 0, max: it.requestedQuantity, style: { textAlign: 'right' } }}\n value={approvedDraft[it.id] ?? ''}\n onChange={(e) => patchDraft(it.id, e.target.value)}\n error={!!lineErr}\n helperText={lineErr || ''}\n sx={{ maxWidth: 110 }}\n />\n ) : (\n <Typography sx={{ fontVariantNumeric: 'tabular-nums', fontWeight: 700 }}>\n {it.approvedQuantity == null ? '—' : it.approvedQuantity}\n </Typography>\n )}\n </TableCell>\n {request.ownerType === 'FACILITY' ? (\n <TableCell>{it.storageUnitName || '—'}</TableCell>\n ) : null}\n </TableRow>\n );\n })}\n </TableBody>\n </Table>\n\n {showDecisionForm ? (\n <>\n <Divider sx={{ my: 2 }} />\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={2} alignItems={{ md: 'flex-start' }}>\n <TextField\n fullWidth\n size=\"small\"\n label=\"Decision notes (optional)\"\n placeholder=\"Rationale shown to the requester\"\n value={decisionNotes}\n onChange={(e) => setDecisionNotes(e.target.value)}\n multiline\n minRows={2}\n />\n {manageAmounts && approvedTotal != null ? (\n <Box sx={(t) => ({\n minWidth: 220,\n p: 1.5,\n borderRadius: 1.5,\n border: '1px solid',\n borderColor: 'divider',\n bgcolor: alpha(t.palette.primary.main, 0.04),\n textAlign: 'right',\n })}>\n <Typography variant=\"caption\" color=\"text.secondary\"\n sx={{ display: 'block', fontWeight: 800, textTransform: 'uppercase', letterSpacing: 0.6 }}>\n Total if approved\n </Typography>\n <PmMoney value={Number(approvedTotal.toFixed(2))} size=\"big\" />\n </Box>\n ) : null}\n </Stack>\n <Stack direction=\"row\" spacing={1} justifyContent=\"flex-end\" sx={{ mt: 2 }}>\n <Button\n variant=\"outlined\" color=\"error\" startIcon={<CancelRoundedIcon />}\n disabled={submitting}\n onClick={() => { setAllToZero(); }}\n >\n Set all to 0\n </Button>\n <Button\n variant=\"contained\" startIcon={<CheckCircleRoundedIcon />}\n disabled={submitting || !draftIsClean}\n onClick={() => void submit()}\n >\n {submitting ? 'Submitting…' : 'Submit Decision'}\n </Button>\n </Stack>\n </>\n ) : null}\n\n {/* Requester withdrawal — only on PENDING + only when the\n current user is the original requester. The backend\n enforces this too; the UI just hides the button when it's\n not applicable. */}\n {isPending && !canDecide ? (\n <Stack direction=\"row\" spacing={1} justifyContent=\"flex-end\" sx={{ mt: 2 }}>\n <Button\n variant=\"outlined\" color=\"error\" startIcon={<DoNotDisturbRoundedIcon />}\n disabled={submitting} onClick={() => void cancel()}\n >\n {submitting ? 'Cancelling…' : 'Cancel Request'}\n </Button>\n </Stack>\n ) : null}\n\n {request.approvedPurchaseId ? (\n <Alert severity=\"success\" sx={{ mt: 2 }}>\n Stock was added to inventory under purchase id <strong>{request.approvedPurchaseId}</strong>.\n </Alert>\n ) : null}\n </CardContent>\n </Card>\n </>\n ) : null}\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n\nfunction Row({ label, children }) {\n return (\n <Stack direction=\"row\" spacing={1} sx={{ minWidth: 0 }}>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ minWidth: 100, fontWeight: 700, textTransform: 'uppercase', letterSpacing: 0.5 }}>\n {label}\n </Typography>\n <Typography variant=\"caption\" sx={{ minWidth: 0, fontWeight: 700 }}>\n {children}\n </Typography>\n </Stack>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a, c as o, s, t as c } from "./PmTable-
|
|
1
|
+
import { I as e, a as t, i as n, n as r, o as i, r as ee, z as te } from "./currency-bVQ7aEk2.js";
|
|
2
|
+
import { a, c as o, s, t as c } from "./PmTable-CCpaJEA_.js";
|
|
3
3
|
import { t as l } from "./format-UOyOu75i.js";
|
|
4
4
|
import { useMemo as u, useState as d } from "react";
|
|
5
5
|
import { Fragment as ne, jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
@@ -32,7 +32,7 @@ function F() {
|
|
|
32
32
|
q(!0), Y("");
|
|
33
33
|
try {
|
|
34
34
|
let t = [];
|
|
35
|
-
B ? t.push(
|
|
35
|
+
B ? t.push(e()) : t.push(Promise.resolve([])), z ? t.push(te()) : t.push(Promise.resolve([]));
|
|
36
36
|
let n = await Promise.all(t);
|
|
37
37
|
W(n[0] ?? []), ce(n[1] ?? []);
|
|
38
38
|
} catch (e) {
|
|
@@ -262,4 +262,4 @@ function F() {
|
|
|
262
262
|
//#endregion
|
|
263
263
|
export { F as default };
|
|
264
264
|
|
|
265
|
-
//# sourceMappingURL=PurchaseRequestsPage-
|
|
265
|
+
//# sourceMappingURL=PurchaseRequestsPage-D2yorc4s.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PurchaseRequestsPage-BwCkbpaX.js","names":[],"sources":["../src/views/PurchaseRequestsPage.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Alert,\n Box,\n Button,\n Chip,\n InputAdornment,\n Skeleton,\n Stack,\n Tab,\n Tabs,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport AddRoundedIcon from '@mui/icons-material/AddRounded';\nimport RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';\nimport SearchRoundedIcon from '@mui/icons-material/SearchRounded';\nimport FactCheckRoundedIcon from '@mui/icons-material/FactCheckRounded';\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 PmEmptyRow,\n PmRef,\n PmTable,\n PmTableCard,\n} from '@pm/components/PmTable';\nimport {\n listMyPurchaseRequests,\n listPendingPurchaseRequests,\n} from '@pm/services/pmApi';\nimport { formatDate } from '@pm/utils/format';\n\n// Compact one-liner of items + qty for the table. Up to MAX_ITEMS_INLINE\n// items render as inline pills; anything beyond gets a \"+N more\" pill so\n// the row height stays stable.\nconst MAX_ITEMS_INLINE = 3;\n\n// Visual mapping for the lifecycle states. Pending sits in info-blue while a\n// decision is awaited; approved/partial use success/warning to signal the\n// terminal verdict; rejected/cancelled drop to muted defaults.\nfunction statusChipColor(status) {\n switch (status) {\n case 'PENDING': return 'info';\n case 'APPROVED': return 'success';\n case 'PARTIALLY_APPROVED': return 'warning';\n case 'REJECTED': return 'error';\n case 'CANCELLED': return 'default';\n default: return 'default';\n }\n}\n\nfunction prettyStatus(status) {\n if (!status) return '';\n return status\n .toLowerCase()\n .split('_')\n .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n .join(' ');\n}\n\nexport default function PurchaseRequestsPage() {\n const navigate = useNavigate();\n const theme = useTheme();\n const perms = usePmPermissions();\n const canSubmit = perms.has('update.manage.productManagement.purchase');\n const canApprove = perms.has('approve.manage.productManagement.purchase');\n const canView = perms.has('view.manage.productManagement.purchase');\n // Default tab follows the user's role:\n // - Approver-only (no view perm) lands directly on Pending — they don't\n // have a \"My Requests\" tab to see anyway.\n // - Anyone else (view perm, or both) lands on My Requests.\n const [tab, setTab] = useState(canApprove && !canView ? 'pending' : 'mine');\n const [mine, setMine] = useState([]);\n const [pending, setPending] = useState([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState('');\n const [search, setSearch] = useState('');\n\n async function load() {\n setLoading(true);\n setError('');\n try {\n // Mine fetched when the user has view perm; Pending when they have\n // approve perm. Each side is independent — an approver-only role\n // never fetches /mine, a viewer-only role never fetches /pending.\n const calls = [];\n if (canView) calls.push(listMyPurchaseRequests());\n else calls.push(Promise.resolve([]));\n if (canApprove) calls.push(listPendingPurchaseRequests());\n else calls.push(Promise.resolve([]));\n const results = await Promise.all(calls);\n setMine(results[0] ?? []);\n setPending(results[1] ?? []);\n } catch (err) {\n const message = err?.response?.data?.message ?? err?.message ?? 'Failed to load purchase requests';\n setError(message);\n showToast({ message, type: 'error' });\n } finally {\n setLoading(false);\n }\n }\n\n usePermissionedEffect(\n ['view.manage.productManagement.purchase', 'approve.manage.productManagement.purchase'],\n () => { void load(); },\n [],\n );\n\n const rows = tab === 'pending' ? pending : mine;\n const filteredRows = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return rows;\n return rows.filter((r) => {\n const itemNames = (r.items ?? []).map((i) => i.productName ?? '').join(' ');\n const hay = `${r.notes ?? ''} ${r.ownerName ?? ''} ${r.requestedByName ?? ''} ${itemNames}`.toLowerCase();\n return hay.includes(q);\n });\n }, [rows, search]);\n\n return (\n <Box>\n <PmNav />\n <PmGuard anyOf={['view.manage.productManagement.purchase', 'approve.manage.productManagement.purchase']}>\n <PmPageShell\n title=\"Purchase Requests\"\n subtitle=\"Purchase requests waiting for approval. A manager must confirm them before stock is added.\"\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"outlined\" startIcon={<RefreshRoundedIcon />} onClick={() => void load()}>Refresh</Button>\n {canSubmit ? (\n <Button variant=\"contained\" startIcon={<AddRoundedIcon />}\n onClick={() => navigate('/ui/medplat/manage/pm/purchases/new')}>\n New Request\n </Button>\n ) : null}\n </Stack>\n }\n >\n {error ? <Alert severity=\"error\" sx={{ mb: 2 }}>{error}</Alert> : null}\n\n <PmTableCard\n eyebrow=\"Approvals\"\n title={tab === 'pending' ? 'Pending approvals' : 'My requests'}\n subtitle={loading\n ? 'Loading…'\n : `${filteredRows.length} ${filteredRows.length === 1 ? 'request' : 'requests'}`}\n >\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ mb: 2 }} alignItems={{ md: 'center' }}>\n <Tabs\n value={tab}\n onChange={(_, v) => setTab(v)}\n slotProps={{ indicator: { sx: { backgroundColor: theme.palette.primary.main } } }}\n sx={{ minHeight: 40 }}\n >\n {/* Tabs follow perms:\n - My Requests: only when the user has the view perm.\n - Pending: only when the user has the approve perm.\n An approver-only role sees just the Pending tab. */}\n {canView ? (\n <Tab value=\"mine\" label={`My requests${mine.length ? ` (${mine.length})` : ''}`} sx={{ minHeight: 40, textTransform: 'none', fontWeight: 700 }} />\n ) : null}\n {canApprove ? (\n <Tab value=\"pending\" label={`Pending${pending.length ? ` (${pending.length})` : ''}`} sx={{ minHeight: 40, textTransform: 'none', fontWeight: 700 }} />\n ) : null}\n </Tabs>\n <Box sx={{ flexGrow: 1 }} />\n <TextField\n size=\"small\"\n fullWidth\n placeholder=\"Search by owner, requester, item, notes…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n sx={{ maxWidth: { md: 360 } }}\n InputProps={{\n startAdornment: (\n <InputAdornment position=\"start\">\n <SearchRoundedIcon fontSize=\"small\" />\n </InputAdornment>\n ),\n }}\n />\n </Stack>\n\n <PmTable dense>\n <TableHead>\n <TableRow>\n <TableCell>Ref</TableCell>\n <TableCell>Date</TableCell>\n <TableCell>Owner</TableCell>\n <TableCell>Items</TableCell>\n <TableCell>Requester</TableCell>\n <TableCell>Decision</TableCell>\n <TableCell>Status</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {loading ? (\n Array.from({ length: 4 }).map((_, i) => (\n <TableRow key={i}>\n {Array.from({ length: 7 }).map((__, j) => (\n <TableCell key={j}><Skeleton height={22} /></TableCell>\n ))}\n </TableRow>\n ))\n ) : filteredRows.length === 0 ? (\n <PmEmptyRow\n colSpan={7}\n icon={FactCheckRoundedIcon}\n title={search.trim() !== ''\n ? 'No requests match your search'\n : tab === 'pending'\n ? 'No pending approvals'\n : 'No requests yet'}\n hint={tab === 'pending'\n ? 'Purchases needing your approval will appear here.'\n : 'Your submitted requests will appear here.'}\n />\n ) : filteredRows.map((r) => {\n const items = r.items ?? [];\n const visibleItems = items.slice(0, MAX_ITEMS_INLINE);\n const overflow = items.length - visibleItems.length;\n return (\n <TableRow\n key={r.id}\n hover\n sx={{ cursor: 'pointer' }}\n onClick={() => navigate(`/ui/medplat/manage/pm/purchase-requests/${r.id}`)}\n >\n <TableCell><PmRef id={r.serialId} /></TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>{formatDate(r.purchaseDate)}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n Submitted {formatDate(r.requestedOn)}\n </Typography>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>\n {r.ownerName || (r.ownerType === 'USER' ? 'Personal' : 'Facility')}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {r.ownerType}\n </Typography>\n </TableCell>\n <TableCell sx={{ maxWidth: 360 }}>\n {items.length === 0 ? (\n <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>\n ) : (\n <Stack direction=\"row\" spacing={0.5} flexWrap=\"wrap\" useFlexGap>\n {visibleItems.map((it) => (\n <Chip\n key={it.id}\n size=\"small\"\n label={\n <Box component=\"span\" sx={{ display: 'inline-flex', alignItems: 'baseline', gap: 0.5 }}>\n <Box component=\"span\" sx={{ fontWeight: 700 }}>{it.productName ?? '—'}</Box>\n <Box component=\"span\" sx={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums', color: 'text.secondary' }}>\n ×{it.approvedQuantity != null ? it.approvedQuantity : it.requestedQuantity}\n </Box>\n </Box>\n }\n sx={{\n height: 22,\n borderRadius: 1.5,\n bgcolor: alpha(theme.palette.primary.main, 0.06),\n color: 'text.primary',\n border: `1px solid ${alpha(theme.palette.primary.main, 0.16)}`,\n '& .MuiChip-label': { px: 0.85 },\n }}\n />\n ))}\n {overflow > 0 ? (\n <Chip\n size=\"small\"\n label={`+${overflow} more`}\n sx={{\n height: 22,\n borderRadius: 1.5,\n bgcolor: 'transparent',\n color: 'text.secondary',\n border: `1px dashed ${theme.palette.divider}`,\n '& .MuiChip-label': { px: 0.85, fontWeight: 700 },\n }}\n />\n ) : null}\n </Stack>\n )}\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">{r.requestedByName || `#${r.requestedBy}`}</Typography>\n </TableCell>\n <TableCell>\n {r.decidedOn ? (\n <>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>\n {r.decidedByName || `#${r.decidedBy}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {formatDate(r.decidedOn)}\n </Typography>\n </>\n ) : (\n <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>\n )}\n </TableCell>\n <TableCell>\n <Chip size=\"small\" label={prettyStatus(r.status)} color={statusChipColor(r.status)} />\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </PmTable>\n </PmTableCard>\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PurchaseRequestsPage-D2yorc4s.js","names":[],"sources":["../src/views/PurchaseRequestsPage.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Alert,\n Box,\n Button,\n Chip,\n InputAdornment,\n Skeleton,\n Stack,\n Tab,\n Tabs,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport AddRoundedIcon from '@mui/icons-material/AddRounded';\nimport RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';\nimport SearchRoundedIcon from '@mui/icons-material/SearchRounded';\nimport FactCheckRoundedIcon from '@mui/icons-material/FactCheckRounded';\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 PmEmptyRow,\n PmRef,\n PmTable,\n PmTableCard,\n} from '@pm/components/PmTable';\nimport {\n listMyPurchaseRequests,\n listPendingPurchaseRequests,\n} from '@pm/services/pmApi';\nimport { formatDate } from '@pm/utils/format';\n\n// Compact one-liner of items + qty for the table. Up to MAX_ITEMS_INLINE\n// items render as inline pills; anything beyond gets a \"+N more\" pill so\n// the row height stays stable.\nconst MAX_ITEMS_INLINE = 3;\n\n// Visual mapping for the lifecycle states. Pending sits in info-blue while a\n// decision is awaited; approved/partial use success/warning to signal the\n// terminal verdict; rejected/cancelled drop to muted defaults.\nfunction statusChipColor(status) {\n switch (status) {\n case 'PENDING': return 'info';\n case 'APPROVED': return 'success';\n case 'PARTIALLY_APPROVED': return 'warning';\n case 'REJECTED': return 'error';\n case 'CANCELLED': return 'default';\n default: return 'default';\n }\n}\n\nfunction prettyStatus(status) {\n if (!status) return '';\n return status\n .toLowerCase()\n .split('_')\n .map((p) => p.charAt(0).toUpperCase() + p.slice(1))\n .join(' ');\n}\n\nexport default function PurchaseRequestsPage() {\n const navigate = useNavigate();\n const theme = useTheme();\n const perms = usePmPermissions();\n const canSubmit = perms.has('update.manage.productManagement.purchase');\n const canApprove = perms.has('approve.manage.productManagement.purchase');\n const canView = perms.has('view.manage.productManagement.purchase');\n // Default tab follows the user's role:\n // - Approver-only (no view perm) lands directly on Pending — they don't\n // have a \"My Requests\" tab to see anyway.\n // - Anyone else (view perm, or both) lands on My Requests.\n const [tab, setTab] = useState(canApprove && !canView ? 'pending' : 'mine');\n const [mine, setMine] = useState([]);\n const [pending, setPending] = useState([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState('');\n const [search, setSearch] = useState('');\n\n async function load() {\n setLoading(true);\n setError('');\n try {\n // Mine fetched when the user has view perm; Pending when they have\n // approve perm. Each side is independent — an approver-only role\n // never fetches /mine, a viewer-only role never fetches /pending.\n const calls = [];\n if (canView) calls.push(listMyPurchaseRequests());\n else calls.push(Promise.resolve([]));\n if (canApprove) calls.push(listPendingPurchaseRequests());\n else calls.push(Promise.resolve([]));\n const results = await Promise.all(calls);\n setMine(results[0] ?? []);\n setPending(results[1] ?? []);\n } catch (err) {\n const message = err?.response?.data?.message ?? err?.message ?? 'Failed to load purchase requests';\n setError(message);\n showToast({ message, type: 'error' });\n } finally {\n setLoading(false);\n }\n }\n\n usePermissionedEffect(\n ['view.manage.productManagement.purchase', 'approve.manage.productManagement.purchase'],\n () => { void load(); },\n [],\n );\n\n const rows = tab === 'pending' ? pending : mine;\n const filteredRows = useMemo(() => {\n const q = search.trim().toLowerCase();\n if (!q) return rows;\n return rows.filter((r) => {\n const itemNames = (r.items ?? []).map((i) => i.productName ?? '').join(' ');\n const hay = `${r.notes ?? ''} ${r.ownerName ?? ''} ${r.requestedByName ?? ''} ${itemNames}`.toLowerCase();\n return hay.includes(q);\n });\n }, [rows, search]);\n\n return (\n <Box>\n <PmNav />\n <PmGuard anyOf={['view.manage.productManagement.purchase', 'approve.manage.productManagement.purchase']}>\n <PmPageShell\n title=\"Purchase Requests\"\n subtitle=\"Purchase requests waiting for approval. A manager must confirm them before stock is added.\"\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"outlined\" startIcon={<RefreshRoundedIcon />} onClick={() => void load()}>Refresh</Button>\n {canSubmit ? (\n <Button variant=\"contained\" startIcon={<AddRoundedIcon />}\n onClick={() => navigate('/ui/medplat/manage/pm/purchases/new')}>\n New Request\n </Button>\n ) : null}\n </Stack>\n }\n >\n {error ? <Alert severity=\"error\" sx={{ mb: 2 }}>{error}</Alert> : null}\n\n <PmTableCard\n eyebrow=\"Approvals\"\n title={tab === 'pending' ? 'Pending approvals' : 'My requests'}\n subtitle={loading\n ? 'Loading…'\n : `${filteredRows.length} ${filteredRows.length === 1 ? 'request' : 'requests'}`}\n >\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ mb: 2 }} alignItems={{ md: 'center' }}>\n <Tabs\n value={tab}\n onChange={(_, v) => setTab(v)}\n slotProps={{ indicator: { sx: { backgroundColor: theme.palette.primary.main } } }}\n sx={{ minHeight: 40 }}\n >\n {/* Tabs follow perms:\n - My Requests: only when the user has the view perm.\n - Pending: only when the user has the approve perm.\n An approver-only role sees just the Pending tab. */}\n {canView ? (\n <Tab value=\"mine\" label={`My requests${mine.length ? ` (${mine.length})` : ''}`} sx={{ minHeight: 40, textTransform: 'none', fontWeight: 700 }} />\n ) : null}\n {canApprove ? (\n <Tab value=\"pending\" label={`Pending${pending.length ? ` (${pending.length})` : ''}`} sx={{ minHeight: 40, textTransform: 'none', fontWeight: 700 }} />\n ) : null}\n </Tabs>\n <Box sx={{ flexGrow: 1 }} />\n <TextField\n size=\"small\"\n fullWidth\n placeholder=\"Search by owner, requester, item, notes…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n sx={{ maxWidth: { md: 360 } }}\n InputProps={{\n startAdornment: (\n <InputAdornment position=\"start\">\n <SearchRoundedIcon fontSize=\"small\" />\n </InputAdornment>\n ),\n }}\n />\n </Stack>\n\n <PmTable dense>\n <TableHead>\n <TableRow>\n <TableCell>Ref</TableCell>\n <TableCell>Date</TableCell>\n <TableCell>Owner</TableCell>\n <TableCell>Items</TableCell>\n <TableCell>Requester</TableCell>\n <TableCell>Decision</TableCell>\n <TableCell>Status</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {loading ? (\n Array.from({ length: 4 }).map((_, i) => (\n <TableRow key={i}>\n {Array.from({ length: 7 }).map((__, j) => (\n <TableCell key={j}><Skeleton height={22} /></TableCell>\n ))}\n </TableRow>\n ))\n ) : filteredRows.length === 0 ? (\n <PmEmptyRow\n colSpan={7}\n icon={FactCheckRoundedIcon}\n title={search.trim() !== ''\n ? 'No requests match your search'\n : tab === 'pending'\n ? 'No pending approvals'\n : 'No requests yet'}\n hint={tab === 'pending'\n ? 'Purchases needing your approval will appear here.'\n : 'Your submitted requests will appear here.'}\n />\n ) : filteredRows.map((r) => {\n const items = r.items ?? [];\n const visibleItems = items.slice(0, MAX_ITEMS_INLINE);\n const overflow = items.length - visibleItems.length;\n return (\n <TableRow\n key={r.id}\n hover\n sx={{ cursor: 'pointer' }}\n onClick={() => navigate(`/ui/medplat/manage/pm/purchase-requests/${r.id}`)}\n >\n <TableCell><PmRef id={r.serialId} /></TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>{formatDate(r.purchaseDate)}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n Submitted {formatDate(r.requestedOn)}\n </Typography>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>\n {r.ownerName || (r.ownerType === 'USER' ? 'Personal' : 'Facility')}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {r.ownerType}\n </Typography>\n </TableCell>\n <TableCell sx={{ maxWidth: 360 }}>\n {items.length === 0 ? (\n <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>\n ) : (\n <Stack direction=\"row\" spacing={0.5} flexWrap=\"wrap\" useFlexGap>\n {visibleItems.map((it) => (\n <Chip\n key={it.id}\n size=\"small\"\n label={\n <Box component=\"span\" sx={{ display: 'inline-flex', alignItems: 'baseline', gap: 0.5 }}>\n <Box component=\"span\" sx={{ fontWeight: 700 }}>{it.productName ?? '—'}</Box>\n <Box component=\"span\" sx={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums', color: 'text.secondary' }}>\n ×{it.approvedQuantity != null ? it.approvedQuantity : it.requestedQuantity}\n </Box>\n </Box>\n }\n sx={{\n height: 22,\n borderRadius: 1.5,\n bgcolor: alpha(theme.palette.primary.main, 0.06),\n color: 'text.primary',\n border: `1px solid ${alpha(theme.palette.primary.main, 0.16)}`,\n '& .MuiChip-label': { px: 0.85 },\n }}\n />\n ))}\n {overflow > 0 ? (\n <Chip\n size=\"small\"\n label={`+${overflow} more`}\n sx={{\n height: 22,\n borderRadius: 1.5,\n bgcolor: 'transparent',\n color: 'text.secondary',\n border: `1px dashed ${theme.palette.divider}`,\n '& .MuiChip-label': { px: 0.85, fontWeight: 700 },\n }}\n />\n ) : null}\n </Stack>\n )}\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\">{r.requestedByName || `#${r.requestedBy}`}</Typography>\n </TableCell>\n <TableCell>\n {r.decidedOn ? (\n <>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>\n {r.decidedByName || `#${r.decidedBy}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {formatDate(r.decidedOn)}\n </Typography>\n </>\n ) : (\n <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>\n )}\n </TableCell>\n <TableCell>\n <Chip size=\"small\" label={prettyStatus(r.status)} color={statusChipColor(r.status)} />\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </PmTable>\n </PmTableCard>\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as o, c as s, i as c, s as l, t as u } from "./PmTable-
|
|
1
|
+
import { H as e, a as t, i as n, l as r, n as i, o as a, r as ee, t as te } from "./currency-bVQ7aEk2.js";
|
|
2
|
+
import { a as o, c as s, i as c, s as l, t as u } from "./PmTable-CCpaJEA_.js";
|
|
3
3
|
import { t as d } from "./format-UOyOu75i.js";
|
|
4
4
|
import { useMemo as f, useState as p } from "react";
|
|
5
5
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
@@ -291,4 +291,4 @@ function j() {
|
|
|
291
291
|
//#endregion
|
|
292
292
|
export { j as default };
|
|
293
293
|
|
|
294
|
-
//# sourceMappingURL=PurchasesPage-
|
|
294
|
+
//# sourceMappingURL=PurchasesPage-CanQTjlw.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PurchasesPage-CvaZouVy.js","names":[],"sources":["../src/views/PurchasesPage.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Alert,\n Box,\n Button,\n Chip,\n InputAdornment,\n Skeleton,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport AddRoundedIcon from '@mui/icons-material/AddRounded';\nimport RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';\nimport SearchRoundedIcon from '@mui/icons-material/SearchRounded';\nimport ShoppingCartRoundedIcon from '@mui/icons-material/ShoppingCartRounded';\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 PmEmptyRow,\n PmMoney,\n PmRef,\n PmTable,\n PmTableCard,\n} from '@pm/components/PmTable';\nimport { listPurchases } from '@pm/services/pmApi';\nimport { formatDate } from '@pm/utils/format';\nimport { currencySymbol } from '@pm/utils/currency';\nimport { usePmSettings } from '@pm/utils/settingsStore';\n\n// Compact one-liner of items+qty for the table. We render up to MAX_ITEMS_INLINE\n// pills inline; anything beyond gets a \"+N more\" pill so the row height stays\n// stable regardless of how many lines a purchase has.\nconst MAX_ITEMS_INLINE = 3;\n\nexport default function PurchasesPage() {\n const perms = usePmPermissions();\n const canCreate = perms.has('update.manage.productManagement.purchase');\n const settings = usePmSettings();\n const cur = currencySymbol(settings?.currencyCode);\n const manageAmounts = settings?.managePurchaseAmounts !== false;\n const navigate = useNavigate();\n const theme = useTheme();\n const [items, setItems] = useState([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState('');\n const [search, setSearch] = useState('');\n const [fromDate, setFromDate] = useState('');\n const [toDate, setToDate] = useState('');\n\n async function load() {\n setLoading(true);\n setError('');\n try {\n setItems(await listPurchases(0, 50));\n } catch (err) {\n const message = err?.response?.data?.message ?? err?.message ?? 'Failed to load purchases';\n setError(message);\n showToast({ message, type: 'error' });\n } finally {\n setLoading(false);\n }\n }\n\n usePermissionedEffect('view.manage.productManagement.purchase', () => {\n void load();\n }, []);\n\n // Search hits notes + every batch's productName + lotNumber. Date filters\n // bracket purchaseDate inclusively on either side; either bound is optional.\n const filteredItems = useMemo(() => {\n const q = search.trim().toLowerCase();\n const from = fromDate ? new Date(fromDate) : null;\n const to = toDate ? new Date(toDate) : null;\n return items.filter((p) => {\n if (q) {\n const hayItems = (p.batches ?? [])\n .map((b) => `${b.productName ?? ''} ${b.lotNumber ?? ''}`)\n .join(' ');\n const hay = `${p.notes ?? ''} ${hayItems}`.toLowerCase();\n if (!hay.includes(q)) return false;\n }\n if (from || to) {\n if (!p.purchaseDate) return false;\n const d = new Date(p.purchaseDate);\n if (from && d < from) return false;\n if (to) {\n // Date inputs give midnight; include the entire `to` day.\n const toEnd = new Date(to);\n toEnd.setHours(23, 59, 59, 999);\n if (d > toEnd) return false;\n }\n }\n return true;\n });\n }, [items, search, fromDate, toDate]);\n\n const totalSpend = useMemo(\n () => filteredItems.reduce((s, x) => s + Number(x.totalAmount || 0), 0),\n [filteredItems],\n );\n\n const hasFilter = search.trim() !== '' || fromDate !== '' || toDate !== '';\n\n function clearFilters() {\n setSearch('');\n setFromDate('');\n setToDate('');\n }\n\n // Match the table column count when manageAmounts swaps Payment + Total in\n // and out. Owner + Items columns are always present.\n const columnCount = manageAmounts ? 7 : 5;\n\n return (\n <Box>\n <PmNav />\n <PmGuard perm={'view.manage.productManagement.purchase'}>\n <PmPageShell\n title=\"Completed Purchases\"\n subtitle=\"Completed purchases and approved requests. Each shows the items and cost.\"\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"outlined\" startIcon={<RefreshRoundedIcon />} onClick={() => void load()}>Refresh</Button>\n {canCreate ? (\n <Button variant=\"contained\" startIcon={<AddRoundedIcon />} onClick={() => navigate('/ui/medplat/manage/pm/purchases/new')}>\n New Purchase\n </Button>\n ) : null}\n </Stack>\n }\n >\n {error ? <Alert severity=\"error\" sx={{ mb: 2 }}>{error}</Alert> : null}\n\n <PmTableCard\n eyebrow=\"Procurement\"\n title=\"Recent purchases\"\n subtitle={loading\n ? 'Loading…'\n : manageAmounts\n ? `${filteredItems.length} ${filteredItems.length === 1 ? 'purchase' : 'purchases'} · total spend ${cur}${totalSpend.toFixed(2)}`\n : `${filteredItems.length} ${filteredItems.length === 1 ? 'purchase' : 'purchases'}`}\n >\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ mb: 2 }}>\n <TextField\n size=\"small\"\n fullWidth\n placeholder=\"Search notes or items…\"\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 />\n <TextField\n size=\"small\"\n type=\"date\"\n label=\"From\"\n InputLabelProps={{ shrink: true }}\n value={fromDate}\n onChange={(e) => setFromDate(e.target.value)}\n sx={{ minWidth: 160 }}\n />\n <TextField\n size=\"small\"\n type=\"date\"\n label=\"To\"\n InputLabelProps={{ shrink: true }}\n value={toDate}\n onChange={(e) => setToDate(e.target.value)}\n sx={{ minWidth: 160 }}\n />\n <Button\n variant=\"text\"\n onClick={clearFilters}\n disabled={!hasFilter}\n sx={{ alignSelf: { md: 'center' } }}\n >\n Clear\n </Button>\n </Stack>\n\n <PmTable dense>\n <TableHead>\n <TableRow>\n <TableCell>Ref</TableCell>\n <TableCell>Date</TableCell>\n <TableCell>Owner</TableCell>\n <TableCell>Items</TableCell>\n {manageAmounts ? <TableCell>Payment</TableCell> : null}\n {manageAmounts ? <TableCell align=\"right\">Total</TableCell> : null}\n <TableCell>Notes</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {loading ? (\n Array.from({ length: 5 }).map((_, i) => (\n <TableRow key={i}>\n {Array.from({ length: columnCount }).map((__, j) => (\n <TableCell key={j}><Skeleton height={22} /></TableCell>\n ))}\n </TableRow>\n ))\n ) : filteredItems.length === 0 ? (\n <PmEmptyRow\n colSpan={columnCount}\n icon={ShoppingCartRoundedIcon}\n title={hasFilter ? 'No purchases match your filters' : 'No purchases yet'}\n hint={hasFilter\n ? 'Adjust your search or filters.'\n : 'Record a purchase to add items to your stock.'}\n action={hasFilter\n ? <Button variant=\"text\" size=\"small\" onClick={clearFilters}>Clear filters</Button>\n : !canCreate ? null : (\n <Button variant=\"contained\" size=\"small\" startIcon={<AddRoundedIcon />} onClick={() => navigate('/ui/medplat/manage/pm/purchases/new')}>\n Record first purchase\n </Button>\n )}\n />\n ) : filteredItems.map((p) => {\n const batches = p.batches ?? [];\n const visibleBatches = batches.slice(0, MAX_ITEMS_INLINE);\n const overflow = batches.length - visibleBatches.length;\n return (\n <TableRow key={p.id} hover>\n <TableCell><PmRef id={p.serialId} /></TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>{formatDate(p.purchaseDate)}</Typography>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>\n {p.ownerName || (p.ownerType === 'USER' ? 'Personal' : 'Facility')}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {p.ownerType}\n </Typography>\n </TableCell>\n <TableCell sx={{ maxWidth: 360 }}>\n {batches.length === 0 ? (\n <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>\n ) : (\n <Stack direction=\"row\" spacing={0.5} flexWrap=\"wrap\" useFlexGap>\n {visibleBatches.map((b) => (\n <Chip\n key={b.id}\n size=\"small\"\n label={\n <Box component=\"span\" sx={{ display: 'inline-flex', alignItems: 'baseline', gap: 0.5 }}>\n <Box component=\"span\" sx={{ fontWeight: 700 }}>{b.productName ?? '—'}</Box>\n <Box component=\"span\" sx={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums', color: 'text.secondary' }}>\n ×{b.qtyPurchased ?? 0}\n </Box>\n </Box>\n }\n sx={{\n height: 22,\n borderRadius: 1.5,\n bgcolor: alpha(theme.palette.primary.main, 0.06),\n color: 'text.primary',\n border: `1px solid ${alpha(theme.palette.primary.main, 0.16)}`,\n '& .MuiChip-label': { px: 0.85 },\n }}\n />\n ))}\n {overflow > 0 ? (\n <Chip\n size=\"small\"\n label={`+${overflow} more`}\n sx={{\n height: 22,\n borderRadius: 1.5,\n bgcolor: 'transparent',\n color: 'text.secondary',\n border: `1px dashed ${theme.palette.divider}`,\n '& .MuiChip-label': { px: 0.85, fontWeight: 700 },\n }}\n />\n ) : null}\n </Stack>\n )}\n </TableCell>\n {manageAmounts ? (\n <TableCell>\n {p.paymentMode ? (\n <Chip\n size=\"small\"\n label={p.paymentMode}\n sx={{\n fontWeight: 700, borderRadius: 1.5, height: 22,\n bgcolor: alpha(theme.palette.primary.main, 0.08),\n color: 'primary.dark',\n border: `1px solid ${alpha(theme.palette.primary.main, 0.18)}`,\n }}\n />\n ) : <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>}\n </TableCell>\n ) : null}\n {manageAmounts ? (\n <TableCell align=\"right\"><PmMoney value={p.totalAmount} /></TableCell>\n ) : null}\n <TableCell sx={{ maxWidth: 320 }}>\n {p.notes ? (\n <Typography variant=\"caption\" color=\"text.secondary\"\n sx={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>\n {p.notes}\n </Typography>\n ) : <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>}\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </PmTable>\n </PmTableCard>\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PurchasesPage-CanQTjlw.js","names":[],"sources":["../src/views/PurchasesPage.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport {\n Alert,\n Box,\n Button,\n Chip,\n InputAdornment,\n Skeleton,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material';\nimport { alpha, useTheme } from '@mui/material/styles';\nimport AddRoundedIcon from '@mui/icons-material/AddRounded';\nimport RefreshRoundedIcon from '@mui/icons-material/RefreshRounded';\nimport SearchRoundedIcon from '@mui/icons-material/SearchRounded';\nimport ShoppingCartRoundedIcon from '@mui/icons-material/ShoppingCartRounded';\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 PmEmptyRow,\n PmMoney,\n PmRef,\n PmTable,\n PmTableCard,\n} from '@pm/components/PmTable';\nimport { listPurchases } from '@pm/services/pmApi';\nimport { formatDate } from '@pm/utils/format';\nimport { currencySymbol } from '@pm/utils/currency';\nimport { usePmSettings } from '@pm/utils/settingsStore';\n\n// Compact one-liner of items+qty for the table. We render up to MAX_ITEMS_INLINE\n// pills inline; anything beyond gets a \"+N more\" pill so the row height stays\n// stable regardless of how many lines a purchase has.\nconst MAX_ITEMS_INLINE = 3;\n\nexport default function PurchasesPage() {\n const perms = usePmPermissions();\n const canCreate = perms.has('update.manage.productManagement.purchase');\n const settings = usePmSettings();\n const cur = currencySymbol(settings?.currencyCode);\n const manageAmounts = settings?.managePurchaseAmounts !== false;\n const navigate = useNavigate();\n const theme = useTheme();\n const [items, setItems] = useState([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState('');\n const [search, setSearch] = useState('');\n const [fromDate, setFromDate] = useState('');\n const [toDate, setToDate] = useState('');\n\n async function load() {\n setLoading(true);\n setError('');\n try {\n setItems(await listPurchases(0, 50));\n } catch (err) {\n const message = err?.response?.data?.message ?? err?.message ?? 'Failed to load purchases';\n setError(message);\n showToast({ message, type: 'error' });\n } finally {\n setLoading(false);\n }\n }\n\n usePermissionedEffect('view.manage.productManagement.purchase', () => {\n void load();\n }, []);\n\n // Search hits notes + every batch's productName + lotNumber. Date filters\n // bracket purchaseDate inclusively on either side; either bound is optional.\n const filteredItems = useMemo(() => {\n const q = search.trim().toLowerCase();\n const from = fromDate ? new Date(fromDate) : null;\n const to = toDate ? new Date(toDate) : null;\n return items.filter((p) => {\n if (q) {\n const hayItems = (p.batches ?? [])\n .map((b) => `${b.productName ?? ''} ${b.lotNumber ?? ''}`)\n .join(' ');\n const hay = `${p.notes ?? ''} ${hayItems}`.toLowerCase();\n if (!hay.includes(q)) return false;\n }\n if (from || to) {\n if (!p.purchaseDate) return false;\n const d = new Date(p.purchaseDate);\n if (from && d < from) return false;\n if (to) {\n // Date inputs give midnight; include the entire `to` day.\n const toEnd = new Date(to);\n toEnd.setHours(23, 59, 59, 999);\n if (d > toEnd) return false;\n }\n }\n return true;\n });\n }, [items, search, fromDate, toDate]);\n\n const totalSpend = useMemo(\n () => filteredItems.reduce((s, x) => s + Number(x.totalAmount || 0), 0),\n [filteredItems],\n );\n\n const hasFilter = search.trim() !== '' || fromDate !== '' || toDate !== '';\n\n function clearFilters() {\n setSearch('');\n setFromDate('');\n setToDate('');\n }\n\n // Match the table column count when manageAmounts swaps Payment + Total in\n // and out. Owner + Items columns are always present.\n const columnCount = manageAmounts ? 7 : 5;\n\n return (\n <Box>\n <PmNav />\n <PmGuard perm={'view.manage.productManagement.purchase'}>\n <PmPageShell\n title=\"Completed Purchases\"\n subtitle=\"Completed purchases and approved requests. Each shows the items and cost.\"\n actions={\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"outlined\" startIcon={<RefreshRoundedIcon />} onClick={() => void load()}>Refresh</Button>\n {canCreate ? (\n <Button variant=\"contained\" startIcon={<AddRoundedIcon />} onClick={() => navigate('/ui/medplat/manage/pm/purchases/new')}>\n New Purchase\n </Button>\n ) : null}\n </Stack>\n }\n >\n {error ? <Alert severity=\"error\" sx={{ mb: 2 }}>{error}</Alert> : null}\n\n <PmTableCard\n eyebrow=\"Procurement\"\n title=\"Recent purchases\"\n subtitle={loading\n ? 'Loading…'\n : manageAmounts\n ? `${filteredItems.length} ${filteredItems.length === 1 ? 'purchase' : 'purchases'} · total spend ${cur}${totalSpend.toFixed(2)}`\n : `${filteredItems.length} ${filteredItems.length === 1 ? 'purchase' : 'purchases'}`}\n >\n <Stack direction={{ xs: 'column', md: 'row' }} spacing={1.5} sx={{ mb: 2 }}>\n <TextField\n size=\"small\"\n fullWidth\n placeholder=\"Search notes or items…\"\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 />\n <TextField\n size=\"small\"\n type=\"date\"\n label=\"From\"\n InputLabelProps={{ shrink: true }}\n value={fromDate}\n onChange={(e) => setFromDate(e.target.value)}\n sx={{ minWidth: 160 }}\n />\n <TextField\n size=\"small\"\n type=\"date\"\n label=\"To\"\n InputLabelProps={{ shrink: true }}\n value={toDate}\n onChange={(e) => setToDate(e.target.value)}\n sx={{ minWidth: 160 }}\n />\n <Button\n variant=\"text\"\n onClick={clearFilters}\n disabled={!hasFilter}\n sx={{ alignSelf: { md: 'center' } }}\n >\n Clear\n </Button>\n </Stack>\n\n <PmTable dense>\n <TableHead>\n <TableRow>\n <TableCell>Ref</TableCell>\n <TableCell>Date</TableCell>\n <TableCell>Owner</TableCell>\n <TableCell>Items</TableCell>\n {manageAmounts ? <TableCell>Payment</TableCell> : null}\n {manageAmounts ? <TableCell align=\"right\">Total</TableCell> : null}\n <TableCell>Notes</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {loading ? (\n Array.from({ length: 5 }).map((_, i) => (\n <TableRow key={i}>\n {Array.from({ length: columnCount }).map((__, j) => (\n <TableCell key={j}><Skeleton height={22} /></TableCell>\n ))}\n </TableRow>\n ))\n ) : filteredItems.length === 0 ? (\n <PmEmptyRow\n colSpan={columnCount}\n icon={ShoppingCartRoundedIcon}\n title={hasFilter ? 'No purchases match your filters' : 'No purchases yet'}\n hint={hasFilter\n ? 'Adjust your search or filters.'\n : 'Record a purchase to add items to your stock.'}\n action={hasFilter\n ? <Button variant=\"text\" size=\"small\" onClick={clearFilters}>Clear filters</Button>\n : !canCreate ? null : (\n <Button variant=\"contained\" size=\"small\" startIcon={<AddRoundedIcon />} onClick={() => navigate('/ui/medplat/manage/pm/purchases/new')}>\n Record first purchase\n </Button>\n )}\n />\n ) : filteredItems.map((p) => {\n const batches = p.batches ?? [];\n const visibleBatches = batches.slice(0, MAX_ITEMS_INLINE);\n const overflow = batches.length - visibleBatches.length;\n return (\n <TableRow key={p.id} hover>\n <TableCell><PmRef id={p.serialId} /></TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>{formatDate(p.purchaseDate)}</Typography>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" sx={{ fontWeight: 700 }}>\n {p.ownerName || (p.ownerType === 'USER' ? 'Personal' : 'Facility')}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {p.ownerType}\n </Typography>\n </TableCell>\n <TableCell sx={{ maxWidth: 360 }}>\n {batches.length === 0 ? (\n <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>\n ) : (\n <Stack direction=\"row\" spacing={0.5} flexWrap=\"wrap\" useFlexGap>\n {visibleBatches.map((b) => (\n <Chip\n key={b.id}\n size=\"small\"\n label={\n <Box component=\"span\" sx={{ display: 'inline-flex', alignItems: 'baseline', gap: 0.5 }}>\n <Box component=\"span\" sx={{ fontWeight: 700 }}>{b.productName ?? '—'}</Box>\n <Box component=\"span\" sx={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums', color: 'text.secondary' }}>\n ×{b.qtyPurchased ?? 0}\n </Box>\n </Box>\n }\n sx={{\n height: 22,\n borderRadius: 1.5,\n bgcolor: alpha(theme.palette.primary.main, 0.06),\n color: 'text.primary',\n border: `1px solid ${alpha(theme.palette.primary.main, 0.16)}`,\n '& .MuiChip-label': { px: 0.85 },\n }}\n />\n ))}\n {overflow > 0 ? (\n <Chip\n size=\"small\"\n label={`+${overflow} more`}\n sx={{\n height: 22,\n borderRadius: 1.5,\n bgcolor: 'transparent',\n color: 'text.secondary',\n border: `1px dashed ${theme.palette.divider}`,\n '& .MuiChip-label': { px: 0.85, fontWeight: 700 },\n }}\n />\n ) : null}\n </Stack>\n )}\n </TableCell>\n {manageAmounts ? (\n <TableCell>\n {p.paymentMode ? (\n <Chip\n size=\"small\"\n label={p.paymentMode}\n sx={{\n fontWeight: 700, borderRadius: 1.5, height: 22,\n bgcolor: alpha(theme.palette.primary.main, 0.08),\n color: 'primary.dark',\n border: `1px solid ${alpha(theme.palette.primary.main, 0.18)}`,\n }}\n />\n ) : <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>}\n </TableCell>\n ) : null}\n {manageAmounts ? (\n <TableCell align=\"right\"><PmMoney value={p.totalAmount} /></TableCell>\n ) : null}\n <TableCell sx={{ maxWidth: 320 }}>\n {p.notes ? (\n <Typography variant=\"caption\" color=\"text.secondary\"\n sx={{ display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>\n {p.notes}\n </Typography>\n ) : <Typography variant=\"caption\" color=\"text.disabled\">—</Typography>}\n </TableCell>\n </TableRow>\n );\n })}\n </TableBody>\n </PmTable>\n </PmTableCard>\n </PmPageShell>\n </PmGuard>\n </Box>\n );\n}\n"],"mappings":"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"}
|