@burdenoff/microfe-theglobalfuel 2026.705.2 → 2026.705.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/theglobalfuel/TheGlobalFuelRoot.js +54 -46
  2. package/dist/theglobalfuel/TheGlobalFuelRoot.js.map +1 -1
  3. package/dist/theglobalfuel/TheGlobalFuelRoutes.js +10 -6
  4. package/dist/theglobalfuel/TheGlobalFuelRoutes.js.map +1 -1
  5. package/dist/theglobalfuel/components/DataTable.js +126 -58
  6. package/dist/theglobalfuel/components/DataTable.js.map +1 -1
  7. package/dist/theglobalfuel/components/EmptyState.js +4 -4
  8. package/dist/theglobalfuel/components/EmptyState.js.map +1 -1
  9. package/dist/theglobalfuel/components/MetricsCard.js +2 -2
  10. package/dist/theglobalfuel/components/MetricsCard.js.map +1 -1
  11. package/dist/theglobalfuel/components/WorldMap.js +115 -0
  12. package/dist/theglobalfuel/components/WorldMap.js.map +1 -0
  13. package/dist/theglobalfuel/components/index.js +9 -0
  14. package/dist/theglobalfuel/components/viz/AllocationBar.js +68 -0
  15. package/dist/theglobalfuel/components/viz/AllocationBar.js.map +1 -0
  16. package/dist/theglobalfuel/components/viz/AreaChart.js +102 -0
  17. package/dist/theglobalfuel/components/viz/AreaChart.js.map +1 -0
  18. package/dist/theglobalfuel/components/viz/AvatarStack.js +2 -0
  19. package/dist/theglobalfuel/components/viz/BarChart.js +4 -0
  20. package/dist/theglobalfuel/components/viz/CertificateCard.js +4 -0
  21. package/dist/theglobalfuel/components/viz/DonutChart.js +109 -0
  22. package/dist/theglobalfuel/components/viz/DonutChart.js.map +1 -0
  23. package/dist/theglobalfuel/components/viz/HBarFunnel.js +3 -0
  24. package/dist/theglobalfuel/components/viz/Heatmap.js +3 -0
  25. package/dist/theglobalfuel/components/viz/KanbanBoard.js +2 -0
  26. package/dist/theglobalfuel/components/viz/LineChart.js +4 -0
  27. package/dist/theglobalfuel/components/viz/OrgTree.js +2 -0
  28. package/dist/theglobalfuel/components/viz/PathSteps.js +68 -0
  29. package/dist/theglobalfuel/components/viz/PathSteps.js.map +1 -0
  30. package/dist/theglobalfuel/components/viz/ProgressRing.js +6 -0
  31. package/dist/theglobalfuel/components/viz/ProgressRing.js.map +1 -0
  32. package/dist/theglobalfuel/components/viz/RatingStars.js +3 -0
  33. package/dist/theglobalfuel/components/viz/SegmentedTabs.js +3 -0
  34. package/dist/theglobalfuel/components/viz/SkillBar.js +3 -0
  35. package/dist/theglobalfuel/components/viz/Sparkline.js +74 -0
  36. package/dist/theglobalfuel/components/viz/Sparkline.js.map +1 -0
  37. package/dist/theglobalfuel/components/viz/StatCard.js +3 -0
  38. package/dist/theglobalfuel/components/viz/StepProgress.js +64 -0
  39. package/dist/theglobalfuel/components/viz/StepProgress.js.map +1 -0
  40. package/dist/theglobalfuel/components/viz/Timeline.js +66 -0
  41. package/dist/theglobalfuel/components/viz/Timeline.js.map +1 -0
  42. package/dist/theglobalfuel/components/viz/index.js +23 -0
  43. package/dist/theglobalfuel/components/viz/lib/Avatar.js +1 -0
  44. package/dist/theglobalfuel/components/viz/lib/CartesianFrame.js +119 -0
  45. package/dist/theglobalfuel/components/viz/lib/CartesianFrame.js.map +1 -0
  46. package/dist/theglobalfuel/components/viz/lib/ResponsiveSvg.js +35 -0
  47. package/dist/theglobalfuel/components/viz/lib/ResponsiveSvg.js.map +1 -0
  48. package/dist/theglobalfuel/components/viz/lib/format.js +17 -0
  49. package/dist/theglobalfuel/components/viz/lib/format.js.map +1 -0
  50. package/dist/theglobalfuel/components/viz/lib/geometry.js +61 -0
  51. package/dist/theglobalfuel/components/viz/lib/geometry.js.map +1 -0
  52. package/dist/theglobalfuel/components/viz/lib/hooks.js +39 -0
  53. package/dist/theglobalfuel/components/viz/lib/hooks.js.map +1 -0
  54. package/dist/theglobalfuel/components/viz/lib/palette.js +27 -0
  55. package/dist/theglobalfuel/components/viz/lib/palette.js.map +1 -0
  56. package/dist/theglobalfuel/hooks/useCursorPager.js +6 -15
  57. package/dist/theglobalfuel/hooks/useCursorPager.js.map +1 -1
  58. package/dist/theglobalfuel/pages/assets/AssetDetailPage.js +431 -316
  59. package/dist/theglobalfuel/pages/assets/AssetDetailPage.js.map +1 -1
  60. package/dist/theglobalfuel/pages/assets/AssetsPage.js +99 -99
  61. package/dist/theglobalfuel/pages/assets/AssetsPage.js.map +1 -1
  62. package/dist/theglobalfuel/pages/compliance/CompliancePage.js +250 -250
  63. package/dist/theglobalfuel/pages/compliance/CompliancePage.js.map +1 -1
  64. package/dist/theglobalfuel/pages/contracts/ContractsPage.js +2 -2
  65. package/dist/theglobalfuel/pages/contracts/ContractsPage.js.map +1 -1
  66. package/dist/theglobalfuel/pages/dashboard/DashboardPage.js +525 -227
  67. package/dist/theglobalfuel/pages/dashboard/DashboardPage.js.map +1 -1
  68. package/dist/theglobalfuel/pages/emissions/EmissionsPage.js +255 -206
  69. package/dist/theglobalfuel/pages/emissions/EmissionsPage.js.map +1 -1
  70. package/dist/theglobalfuel/pages/inventory/InventoryPage.js +60 -60
  71. package/dist/theglobalfuel/pages/inventory/InventoryPage.js.map +1 -1
  72. package/dist/theglobalfuel/pages/production/ProductionPage.js +2 -2
  73. package/dist/theglobalfuel/pages/production/ProductionPage.js.map +1 -1
  74. package/dist/theglobalfuel/pages/provenance/ProvenancePage.js +465 -0
  75. package/dist/theglobalfuel/pages/provenance/ProvenancePage.js.map +1 -0
  76. package/dist/theglobalfuel/pages/reserves/ReservesPage.js +152 -152
  77. package/dist/theglobalfuel/pages/reserves/ReservesPage.js.map +1 -1
  78. package/dist/theglobalfuel/pages/shipments/ShipmentsPage.js +356 -227
  79. package/dist/theglobalfuel/pages/shipments/ShipmentsPage.js.map +1 -1
  80. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"InventoryPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/inventory/InventoryPage.tsx"],"sourcesContent":["/**\n * Commodity inventory — list, filter, create lots & drive lot status.\n * @module theglobalfuel/pages/inventory\n *\n * Reads `theListInventoryLots`; writes `theCreateInventoryLot` and\n * `theSetInventoryLotStatus`. A KPI strip (lots on hand, available quantity &\n * inventory value) is aggregated client-side from a broad lot query. No mock\n * data — every number is derived from live `the*` responses.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { Plus, Boxes, Warehouse, Layers, Coins } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useNotify,\n useTheInventoryLots,\n useTheCreateInventoryLot,\n useTheSetInventoryLotStatus,\n useTheAssets,\n useCursorPager,\n} from '../../hooks';\nimport {\n formatDate,\n formatEnumLabel,\n formatMoney,\n formatNumber,\n formatUnit,\n formatVolume,\n} from '../../utils/formatters';\nimport { INVENTORY_TRANSITIONS, nextStatuses, toBackendDate } from '../../utils';\nimport type { TheInventoryLot, TheInventoryStatus, TheCommodityType, TheUnit } from '../../types';\n\nconst STATUS_FILTERS: FilterOption<TheInventoryStatus | 'ALL'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'AVAILABLE', label: 'Available' },\n { value: 'RESERVED', label: 'Reserved' },\n { value: 'IN_TRANSIT', label: 'In transit' },\n { value: 'CONSUMED', label: 'Consumed' },\n { value: 'WRITTEN_OFF', label: 'Written off' },\n];\n\n/** Sentinel Select value for \"no producing asset\" — Radix forbids an empty value. */\nconst NONE_ASSET_VALUE = '__none__';\n\nconst COMMODITIES: TheCommodityType[] = [\n 'CRUDE_OIL',\n 'NATURAL_GAS',\n 'LNG',\n 'COAL',\n 'ELECTRICITY',\n 'HYDROGEN',\n 'BIOFUEL',\n 'IRON_ORE',\n 'COPPER',\n 'BAUXITE',\n 'GOLD',\n 'LITHIUM',\n 'RARE_EARTH',\n];\n\nconst UNITS: TheUnit[] = ['BARRELS', 'CUBIC_METERS', 'TONNES', 'KILOGRAMS', 'MWH', 'GJ', 'MCF'];\n\nconst PAGE_SIZE = 25;\n\n/** Book value of a lot in its own currency. */\nconst lotValue = (l: TheInventoryLot): number => l.quantity * l.unitValue;\n\ninterface CreateFormState {\n lotCode: string;\n commodity: TheCommodityType;\n assetId: string;\n quantity: string;\n unit: TheUnit;\n grade: string;\n location: string;\n unitValue: string;\n currency: string;\n producedAt: string;\n notes: string;\n}\n\nconst EMPTY_FORM: CreateFormState = {\n lotCode: '',\n commodity: 'CRUDE_OIL',\n assetId: '',\n quantity: '',\n unit: 'BARRELS',\n grade: '',\n location: '',\n unitValue: '',\n currency: 'USD',\n producedAt: '',\n notes: '',\n};\n\nexport function InventoryPage() {\n const [status, setStatus] = useState<TheInventoryStatus | 'ALL'>('ALL');\n const [commodity, setCommodity] = useState<TheCommodityType | 'ALL'>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<CreateFormState>(EMPTY_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n // Track the lot whose status mutation is in flight so we disable ONLY its own\n // Select (a shared isPending flag would freeze every row's control at once).\n const [pendingLotId, setPendingLotId] = useState<string | null>(null);\n\n const notify = useNotify();\n\n const { data, isLoading, isError, refetch } = useTheInventoryLots({\n status: status === 'ALL' ? undefined : status,\n commodity: commodity === 'ALL' ? undefined : commodity,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n\n // Broad, unfiltered query feeding the headline KPI strip (aggregated client-\n // side) and the \"produced by\" asset picker in the create dialog.\n const metricsQuery = useTheInventoryLots({ pagination: { limit: 200 } });\n const assets = useTheAssets({ pagination: { limit: 200 } });\n\n const createLot = useTheCreateInventoryLot();\n const setLotStatus = useTheSetInventoryLotStatus();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n const assetMap = useMemo<Record<string, string>>(() => {\n const map: Record<string, string> = {};\n for (const a of assets.data?.items ?? []) map[a.id] = a.code ? `${a.name} · ${a.code}` : a.name;\n return map;\n }, [assets.data]);\n\n // ── Headline metrics (available inventory only) ─────────────────────────────\n // On-hand quantity is grouped BY UNIT — barrels, tonnes and MWh are physically\n // incommensurable, so a single cross-unit sum would be meaningless. Value is\n // likewise grouped BY CURRENCY. We surface the dominant bucket in the KPI value\n // and the honest full per-bucket breakdown in the footer.\n const metrics = useMemo(() => {\n const lots = metricsQuery.data?.items ?? [];\n const available = lots.filter((l) => l.status === 'AVAILABLE');\n\n const qtyByUnit = new Map<TheUnit, number>();\n for (const l of available) {\n qtyByUnit.set(l.unit, (qtyByUnit.get(l.unit) ?? 0) + (l.quantity ?? 0));\n }\n let topUnit: TheUnit | null = null;\n let topUnitQty = 0;\n for (const [unit, qty] of qtyByUnit) {\n if (qty > topUnitQty) {\n topUnitQty = qty;\n topUnit = unit;\n }\n }\n\n const valueByCurrency = new Map<string, number>();\n for (const l of available) {\n const cur = l.currency || 'USD';\n valueByCurrency.set(cur, (valueByCurrency.get(cur) ?? 0) + lotValue(l));\n }\n let topCurrency = 'USD';\n let topValue = 0;\n for (const [cur, val] of valueByCurrency) {\n if (val > topValue) {\n topValue = val;\n topCurrency = cur;\n }\n }\n\n return {\n totalLots: lots.length,\n availableCount: available.length,\n qtyByUnit,\n topUnit,\n topUnitQty,\n topCurrency,\n topValue,\n otherCurrencies: Math.max(0, valueByCurrency.size - 1),\n };\n }, [metricsQuery.data]);\n\n const columns: Column<TheInventoryLot>[] = [\n {\n key: 'lotCode',\n header: 'Lot',\n render: (l) => (\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-accent\">\n <Boxes className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{l.lotCode}</p>\n {l.assetId && assetMap[l.assetId] && (\n <p className=\"truncate text-xs text-text-tertiary\">{assetMap[l.assetId]}</p>\n )}\n </div>\n </div>\n ),\n },\n {\n key: 'commodity',\n header: 'Commodity',\n hideOnMobile: true,\n render: (l) => <span className=\"text-text-secondary\">{formatEnumLabel(l.commodity)}</span>,\n },\n {\n key: 'quantity',\n header: 'Quantity',\n align: 'right',\n sortValue: (l) => l.quantity,\n render: (l) => (\n <span className=\"tabular-nums text-text-primary\">{formatVolume(l.quantity, l.unit)}</span>\n ),\n },\n {\n key: 'grade',\n header: 'Grade',\n align: 'right',\n hideOnMobile: true,\n sortValue: (l) => l.grade ?? -1,\n render: (l) =>\n l.grade != null ? (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(l.grade)}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (l) => <StatusBadge status={l.status} />,\n },\n {\n key: 'location',\n header: 'Location',\n hideOnMobile: true,\n render: (l) =>\n l.location ? (\n <span className=\"text-text-secondary\">{l.location}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n hideOnMobile: true,\n sortValue: (l) => lotValue(l),\n render: (l) => (\n <span className=\"tabular-nums text-text-secondary\">\n {formatMoney(lotValue(l), l.currency)}\n </span>\n ),\n },\n {\n key: 'producedAt',\n header: 'Produced',\n align: 'right',\n hideOnMobile: true,\n sortValue: (l) => (l.producedAt ? new Date(l.producedAt).getTime() : 0),\n render: (l) => <span className=\"text-text-tertiary\">{formatDate(l.producedAt)}</span>,\n },\n {\n key: 'setStatus',\n header: '',\n align: 'right',\n render: (l) => {\n const next = nextStatuses(INVENTORY_TRANSITIONS, l.status);\n const isTerminal = next.length === 0;\n const isRowPending = pendingLotId === l.id;\n return (\n <Select\n value={l.status}\n disabled={isTerminal || isRowPending}\n onValueChange={(v) => {\n setPendingLotId(l.id);\n setLotStatus.mutate(\n { id: l.id, status: v as TheInventoryStatus },\n {\n onSuccess: () => notify.success('Lot status updated'),\n onError: (e) => notify.fromError(e),\n onSettled: () => setPendingLotId(null),\n }\n );\n }}\n >\n <SelectTrigger className=\"h-8 w-36\" aria-label={`Change status for lot ${l.lotCode}`}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {/* Current status is a disabled anchor so the trigger renders it;\n only LEGAL next states are selectable. Terminal lots\n (CONSUMED / WRITTEN_OFF) have no next state → Select disabled. */}\n <SelectItem value={l.status} disabled>\n {formatEnumLabel(l.status)}\n </SelectItem>\n {next.map((s) => (\n <SelectItem key={s} value={s}>\n {formatEnumLabel(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n },\n },\n ];\n\n const resetForm = () => {\n setForm(EMPTY_FORM);\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!form.lotCode.trim()) {\n setFormError('Lot code is required.');\n return;\n }\n const quantity = Number(form.quantity);\n if (!form.quantity || Number.isNaN(quantity) || quantity <= 0) {\n setFormError('Enter a quantity greater than zero.');\n notify.error('Enter a quantity greater than zero.');\n return;\n }\n setFormError(null);\n try {\n await createLot.mutateAsync({\n lotCode: form.lotCode.trim(),\n commodity: form.commodity,\n assetId: form.assetId || null,\n quantity,\n unit: form.unit,\n grade: form.grade ? Number(form.grade) : null,\n location: form.location.trim() || null,\n unitValue: form.unitValue ? Number(form.unitValue) : null,\n currency: form.currency.trim() || 'USD',\n producedAt: toBackendDate(form.producedAt) ?? null,\n notes: form.notes.trim() || null,\n });\n notify.success('Inventory lot created');\n setDialogOpen(false);\n resetForm();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create inventory lot.');\n notify.fromError(err);\n }\n };\n\n // When multiple units are on hand, show the honest per-unit breakdown (never a\n // single cross-unit number); with a single unit, fall back to the lot count.\n const onHandFooter: ReactNode = (() => {\n if (!metricsQuery.data) return undefined;\n if (metrics.qtyByUnit.size > 1) {\n return (\n <p className=\"text-xs text-text-tertiary\">\n {[...metrics.qtyByUnit.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([unit, qty]) => formatVolume(qty, unit))\n .join(' · ')}\n </p>\n );\n }\n if (metrics.availableCount > 0) {\n return (\n <p className=\"text-xs text-text-tertiary\">\n across {metrics.availableCount} available {metrics.availableCount === 1 ? 'lot' : 'lots'}\n </p>\n );\n }\n return undefined;\n })();\n\n const valueFooter: ReactNode =\n metricsQuery.data && metrics.otherCurrencies > 0 ? (\n <p className=\"text-xs text-text-tertiary\">\n in {metrics.topCurrency} · +{metrics.otherCurrencies} more{' '}\n {metrics.otherCurrencies === 1 ? 'currency' : 'currencies'}\n </p>\n ) : undefined;\n\n return (\n <PageLayout\n title=\"Inventory\"\n description=\"Commodity inventory lots — on-hand quantity, grade, location & book value across the portfolio.\"\n actions={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New lot\n </Button>\n }\n >\n {/* KPI strip */}\n <div className=\"mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <MetricsCard\n icon={<Warehouse className=\"h-5 w-5\" />}\n value={metricsQuery.data ? formatNumber(metrics.totalLots) : '—'}\n label=\"Inventory lots\"\n featured\n footer={\n metricsQuery.data ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatNumber(metrics.availableCount)} available\n </p>\n ) : undefined\n }\n />\n <MetricsCard\n icon={<Layers className=\"h-5 w-5\" />}\n value={metricsQuery.data ? formatVolume(metrics.topUnitQty, metrics.topUnit) : '—'}\n label=\"On-hand quantity\"\n accent=\"success\"\n footer={onHandFooter}\n />\n <MetricsCard\n icon={<Coins className=\"h-5 w-5\" />}\n value={metricsQuery.data ? formatMoney(metrics.topValue, metrics.topCurrency) : '—'}\n label=\"Inventory value\"\n footer={valueFooter}\n />\n </div>\n\n {/* Filters */}\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n pager.reset();\n }}\n />\n <Select\n value={commodity}\n onValueChange={(v) => {\n setCommodity(v as TheCommodityType | 'ALL');\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-48\" aria-label=\"Filter by commodity\">\n <SelectValue placeholder=\"All commodities\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All commodities</SelectItem>\n {COMMODITIES.map((c) => (\n <SelectItem key={c} value={c}>\n {formatEnumLabel(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading inventory…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load inventory lots.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Boxes className=\"h-10 w-10\" />}\n title=\"No inventory lots yet\"\n description={\n status === 'ALL' && commodity === 'ALL'\n ? 'Create your first inventory lot to track on-hand commodity quantity, grade and value.'\n : 'No lots match these filters.'\n }\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New lot\n </Button>\n }\n />\n ) : (\n <>\n <DataTable columns={columns} rows={rows} rowKey={(l) => l.id} />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo?.cursor)}\n />\n </>\n )}\n\n {/* New lot dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open);\n if (!open) resetForm();\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New inventory lot</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register an on-hand quantity of a commodity — optionally attributed to a producing\n asset.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleCreate();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"lotCode\">Lot code *</Label>\n <Input\n id=\"lotCode\"\n value={form.lotCode}\n onChange={(e) => setForm({ ...form, lotCode: e.target.value })}\n placeholder=\"LOT-2026-0042\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"commodity\">Commodity *</Label>\n <Select\n value={form.commodity}\n onValueChange={(v) => setForm({ ...form, commodity: v as TheCommodityType })}\n >\n <SelectTrigger id=\"commodity\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {COMMODITIES.map((c) => (\n <SelectItem key={c} value={c}>\n {formatEnumLabel(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"assetId\">Produced by asset</Label>\n <Select\n value={form.assetId || NONE_ASSET_VALUE}\n onValueChange={(v) =>\n setForm({ ...form, assetId: v === NONE_ASSET_VALUE ? '' : v })\n }\n >\n <SelectTrigger id=\"assetId\">\n <SelectValue placeholder=\"Optional — link to a producing asset\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NONE_ASSET_VALUE}>None</SelectItem>\n {Object.entries(assetMap).map(([id, label]) => (\n <SelectItem key={id} value={id}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-3\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"quantity\">Quantity *</Label>\n <Input\n id=\"quantity\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.quantity}\n onChange={(e) => setForm({ ...form, quantity: e.target.value })}\n placeholder=\"120000\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"unit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"unit\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {UNITS.map((u) => (\n <SelectItem key={u} value={u}>\n {formatUnit(u)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"grade\">Grade</Label>\n <Input\n id=\"grade\"\n type=\"number\"\n step=\"any\"\n value={form.grade}\n onChange={(e) => setForm({ ...form, grade: e.target.value })}\n placeholder=\"0.32\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-3\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"unitValue\">Unit value</Label>\n <Input\n id=\"unitValue\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.unitValue}\n onChange={(e) => setForm({ ...form, unitValue: e.target.value })}\n placeholder=\"82.50\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"currency\">Currency</Label>\n <Input\n id=\"currency\"\n value={form.currency}\n onChange={(e) => setForm({ ...form, currency: e.target.value })}\n placeholder=\"USD\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"producedAt\">Produced at</Label>\n <Input\n id=\"producedAt\"\n type=\"date\"\n value={form.producedAt}\n onChange={(e) => setForm({ ...form, producedAt: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"location\">Location</Label>\n <Input\n id=\"location\"\n value={form.location}\n onChange={(e) => setForm({ ...form, location: e.target.value })}\n placeholder=\"Rotterdam Tank Farm 3\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"notes\">Notes</Label>\n <Textarea\n id=\"notes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this lot.\"\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n setDialogOpen(false);\n resetForm();\n }}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={createLot.isPending}>\n {createLot.isPending ? 'Creating…' : 'Create lot'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"InventoryPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/inventory/InventoryPage.tsx"],"sourcesContent":["/**\n * Commodity inventory — list, filter, create lots & drive lot status.\n * @module theglobalfuel/pages/inventory\n *\n * Reads `theListInventoryLots`; writes `theCreateInventoryLot` and\n * `theSetInventoryLotStatus`. A KPI strip (lots on hand, available quantity &\n * inventory value) is aggregated client-side from a broad lot query. No mock\n * data — every number is derived from live `the*` responses.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { Plus, Boxes, Warehouse, Layers, Coins } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useNotify,\n useTheInventoryLots,\n useTheCreateInventoryLot,\n useTheSetInventoryLotStatus,\n useTheAssets,\n useCursorPager,\n} from '../../hooks';\nimport {\n formatDate,\n formatEnumLabel,\n formatMoney,\n formatNumber,\n formatUnit,\n formatVolume,\n} from '../../utils/formatters';\nimport { INVENTORY_TRANSITIONS, nextStatuses, toBackendDate } from '../../utils';\nimport type { TheInventoryLot, TheInventoryStatus, TheCommodityType, TheUnit } from '../../types';\n\nconst STATUS_FILTERS: FilterOption<TheInventoryStatus | 'ALL'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'AVAILABLE', label: 'Available' },\n { value: 'RESERVED', label: 'Reserved' },\n { value: 'IN_TRANSIT', label: 'In transit' },\n { value: 'CONSUMED', label: 'Consumed' },\n { value: 'WRITTEN_OFF', label: 'Written off' },\n];\n\n/** Sentinel Select value for \"no producing asset\" — Radix forbids an empty value. */\nconst NONE_ASSET_VALUE = '__none__';\n\nconst COMMODITIES: TheCommodityType[] = [\n 'CRUDE_OIL',\n 'NATURAL_GAS',\n 'LNG',\n 'COAL',\n 'ELECTRICITY',\n 'HYDROGEN',\n 'BIOFUEL',\n 'IRON_ORE',\n 'COPPER',\n 'BAUXITE',\n 'GOLD',\n 'LITHIUM',\n 'RARE_EARTH',\n];\n\nconst UNITS: TheUnit[] = ['BARRELS', 'CUBIC_METERS', 'TONNES', 'KILOGRAMS', 'MWH', 'GJ', 'MCF'];\n\nconst PAGE_SIZE = 25;\n\n/** Book value of a lot in its own currency. */\nconst lotValue = (l: TheInventoryLot): number => l.quantity * l.unitValue;\n\ninterface CreateFormState {\n lotCode: string;\n commodity: TheCommodityType;\n assetId: string;\n quantity: string;\n unit: TheUnit;\n grade: string;\n location: string;\n unitValue: string;\n currency: string;\n producedAt: string;\n notes: string;\n}\n\nconst EMPTY_FORM: CreateFormState = {\n lotCode: '',\n commodity: 'CRUDE_OIL',\n assetId: '',\n quantity: '',\n unit: 'BARRELS',\n grade: '',\n location: '',\n unitValue: '',\n currency: 'USD',\n producedAt: '',\n notes: '',\n};\n\nexport function InventoryPage() {\n const [status, setStatus] = useState<TheInventoryStatus | 'ALL'>('ALL');\n const [commodity, setCommodity] = useState<TheCommodityType | 'ALL'>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<CreateFormState>(EMPTY_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n // Track the lot whose status mutation is in flight so we disable ONLY its own\n // Select (a shared isPending flag would freeze every row's control at once).\n const [pendingLotId, setPendingLotId] = useState<string | null>(null);\n\n const notify = useNotify();\n\n const { data, isLoading, isError, refetch } = useTheInventoryLots({\n status: status === 'ALL' ? undefined : status,\n commodity: commodity === 'ALL' ? undefined : commodity,\n pagination: { limit: PAGE_SIZE, offset: pager.page * PAGE_SIZE },\n });\n\n // Broad, unfiltered query feeding the headline KPI strip (aggregated client-\n // side) and the \"produced by\" asset picker in the create dialog.\n const metricsQuery = useTheInventoryLots({ pagination: { limit: 200 } });\n const assets = useTheAssets({ pagination: { limit: 200 } });\n\n const createLot = useTheCreateInventoryLot();\n const setLotStatus = useTheSetInventoryLotStatus();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n const assetMap = useMemo<Record<string, string>>(() => {\n const map: Record<string, string> = {};\n for (const a of assets.data?.items ?? []) map[a.id] = a.code ? `${a.name} · ${a.code}` : a.name;\n return map;\n }, [assets.data]);\n\n // ── Headline metrics (available inventory only) ─────────────────────────────\n // On-hand quantity is grouped BY UNIT — barrels, tonnes and MWh are physically\n // incommensurable, so a single cross-unit sum would be meaningless. Value is\n // likewise grouped BY CURRENCY. We surface the dominant bucket in the KPI value\n // and the honest full per-bucket breakdown in the footer.\n const metrics = useMemo(() => {\n const lots = metricsQuery.data?.items ?? [];\n const available = lots.filter((l) => l.status === 'AVAILABLE');\n\n const qtyByUnit = new Map<TheUnit, number>();\n for (const l of available) {\n qtyByUnit.set(l.unit, (qtyByUnit.get(l.unit) ?? 0) + (l.quantity ?? 0));\n }\n let topUnit: TheUnit | null = null;\n let topUnitQty = 0;\n for (const [unit, qty] of qtyByUnit) {\n if (qty > topUnitQty) {\n topUnitQty = qty;\n topUnit = unit;\n }\n }\n\n const valueByCurrency = new Map<string, number>();\n for (const l of available) {\n const cur = l.currency || 'USD';\n valueByCurrency.set(cur, (valueByCurrency.get(cur) ?? 0) + lotValue(l));\n }\n let topCurrency = 'USD';\n let topValue = 0;\n for (const [cur, val] of valueByCurrency) {\n if (val > topValue) {\n topValue = val;\n topCurrency = cur;\n }\n }\n\n return {\n totalLots: lots.length,\n availableCount: available.length,\n qtyByUnit,\n topUnit,\n topUnitQty,\n topCurrency,\n topValue,\n otherCurrencies: Math.max(0, valueByCurrency.size - 1),\n };\n }, [metricsQuery.data]);\n\n const columns: Column<TheInventoryLot>[] = [\n {\n key: 'lotCode',\n header: 'Lot',\n render: (l) => (\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-accent\">\n <Boxes className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{l.lotCode}</p>\n {l.assetId && assetMap[l.assetId] && (\n <p className=\"truncate text-xs text-text-tertiary\">{assetMap[l.assetId]}</p>\n )}\n </div>\n </div>\n ),\n },\n {\n key: 'commodity',\n header: 'Commodity',\n hideOnMobile: true,\n render: (l) => <span className=\"text-text-secondary\">{formatEnumLabel(l.commodity)}</span>,\n },\n {\n key: 'quantity',\n header: 'Quantity',\n align: 'right',\n sortValue: (l) => l.quantity,\n render: (l) => (\n <span className=\"tabular-nums text-text-primary\">{formatVolume(l.quantity, l.unit)}</span>\n ),\n },\n {\n key: 'grade',\n header: 'Grade',\n align: 'right',\n hideOnMobile: true,\n sortValue: (l) => l.grade ?? -1,\n render: (l) =>\n l.grade != null ? (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(l.grade)}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n render: (l) => <StatusBadge status={l.status} />,\n },\n {\n key: 'location',\n header: 'Location',\n hideOnMobile: true,\n render: (l) =>\n l.location ? (\n <span className=\"text-text-secondary\">{l.location}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n hideOnMobile: true,\n sortValue: (l) => lotValue(l),\n render: (l) => (\n <span className=\"tabular-nums text-text-secondary\">\n {formatMoney(lotValue(l), l.currency)}\n </span>\n ),\n },\n {\n key: 'producedAt',\n header: 'Produced',\n align: 'right',\n hideOnMobile: true,\n sortValue: (l) => (l.producedAt ? new Date(l.producedAt).getTime() : 0),\n render: (l) => <span className=\"text-text-tertiary\">{formatDate(l.producedAt)}</span>,\n },\n {\n key: 'setStatus',\n header: '',\n align: 'right',\n render: (l) => {\n const next = nextStatuses(INVENTORY_TRANSITIONS, l.status);\n const isTerminal = next.length === 0;\n const isRowPending = pendingLotId === l.id;\n return (\n <Select\n value={l.status}\n disabled={isTerminal || isRowPending}\n onValueChange={(v) => {\n setPendingLotId(l.id);\n setLotStatus.mutate(\n { id: l.id, status: v as TheInventoryStatus },\n {\n onSuccess: () => notify.success('Lot status updated'),\n onError: (e) => notify.fromError(e),\n onSettled: () => setPendingLotId(null),\n }\n );\n }}\n >\n <SelectTrigger className=\"h-8 w-36\" aria-label={`Change status for lot ${l.lotCode}`}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {/* Current status is a disabled anchor so the trigger renders it;\n only LEGAL next states are selectable. Terminal lots\n (CONSUMED / WRITTEN_OFF) have no next state → Select disabled. */}\n <SelectItem value={l.status} disabled>\n {formatEnumLabel(l.status)}\n </SelectItem>\n {next.map((s) => (\n <SelectItem key={s} value={s}>\n {formatEnumLabel(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n },\n },\n ];\n\n const resetForm = () => {\n setForm(EMPTY_FORM);\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!form.lotCode.trim()) {\n setFormError('Lot code is required.');\n return;\n }\n const quantity = Number(form.quantity);\n if (!form.quantity || Number.isNaN(quantity) || quantity <= 0) {\n setFormError('Enter a quantity greater than zero.');\n notify.error('Enter a quantity greater than zero.');\n return;\n }\n setFormError(null);\n try {\n await createLot.mutateAsync({\n lotCode: form.lotCode.trim(),\n commodity: form.commodity,\n assetId: form.assetId || null,\n quantity,\n unit: form.unit,\n grade: form.grade ? Number(form.grade) : null,\n location: form.location.trim() || null,\n unitValue: form.unitValue ? Number(form.unitValue) : null,\n currency: form.currency.trim() || 'USD',\n producedAt: toBackendDate(form.producedAt) ?? null,\n notes: form.notes.trim() || null,\n });\n notify.success('Inventory lot created');\n setDialogOpen(false);\n resetForm();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create inventory lot.');\n notify.fromError(err);\n }\n };\n\n // When multiple units are on hand, show the honest per-unit breakdown (never a\n // single cross-unit number); with a single unit, fall back to the lot count.\n const onHandFooter: ReactNode = (() => {\n if (!metricsQuery.data) return undefined;\n if (metrics.qtyByUnit.size > 1) {\n return (\n <p className=\"text-xs text-text-tertiary\">\n {[...metrics.qtyByUnit.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([unit, qty]) => formatVolume(qty, unit))\n .join(' · ')}\n </p>\n );\n }\n if (metrics.availableCount > 0) {\n return (\n <p className=\"text-xs text-text-tertiary\">\n across {metrics.availableCount} available {metrics.availableCount === 1 ? 'lot' : 'lots'}\n </p>\n );\n }\n return undefined;\n })();\n\n const valueFooter: ReactNode =\n metricsQuery.data && metrics.otherCurrencies > 0 ? (\n <p className=\"text-xs text-text-tertiary\">\n in {metrics.topCurrency} · +{metrics.otherCurrencies} more{' '}\n {metrics.otherCurrencies === 1 ? 'currency' : 'currencies'}\n </p>\n ) : undefined;\n\n return (\n <PageLayout\n title=\"Inventory\"\n description=\"Commodity inventory lots — on-hand quantity, grade, location & book value across the portfolio.\"\n actions={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New lot\n </Button>\n }\n >\n {/* KPI strip */}\n <div className=\"mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <MetricsCard\n icon={<Warehouse className=\"h-5 w-5\" />}\n value={metricsQuery.data ? formatNumber(metrics.totalLots) : '—'}\n label=\"Inventory lots\"\n featured\n footer={\n metricsQuery.data ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatNumber(metrics.availableCount)} available\n </p>\n ) : undefined\n }\n />\n <MetricsCard\n icon={<Layers className=\"h-5 w-5\" />}\n value={metricsQuery.data ? formatVolume(metrics.topUnitQty, metrics.topUnit) : '—'}\n label=\"On-hand quantity\"\n accent=\"success\"\n footer={onHandFooter}\n />\n <MetricsCard\n icon={<Coins className=\"h-5 w-5\" />}\n value={metricsQuery.data ? formatMoney(metrics.topValue, metrics.topCurrency) : '—'}\n label=\"Inventory value\"\n footer={valueFooter}\n />\n </div>\n\n {/* Filters */}\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={STATUS_FILTERS}\n value={status}\n onChange={(v) => {\n setStatus(v);\n pager.reset();\n }}\n />\n <Select\n value={commodity}\n onValueChange={(v) => {\n setCommodity(v as TheCommodityType | 'ALL');\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-48\" aria-label=\"Filter by commodity\">\n <SelectValue placeholder=\"All commodities\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All commodities</SelectItem>\n {COMMODITIES.map((c) => (\n <SelectItem key={c} value={c}>\n {formatEnumLabel(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading inventory…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load inventory lots.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Boxes className=\"h-10 w-10\" />}\n title=\"No inventory lots yet\"\n description={\n status === 'ALL' && commodity === 'ALL'\n ? 'Create your first inventory lot to track on-hand commodity quantity, grade and value.'\n : 'No lots match these filters.'\n }\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> New lot\n </Button>\n }\n />\n ) : (\n <>\n <DataTable columns={columns} rows={rows} rowKey={(l) => l.id} />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={pager.onPageChange}\n />\n </>\n )}\n\n {/* New lot dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open);\n if (!open) resetForm();\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New inventory lot</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Register an on-hand quantity of a commodity — optionally attributed to a producing\n asset.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleCreate();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"lotCode\">Lot code *</Label>\n <Input\n id=\"lotCode\"\n value={form.lotCode}\n onChange={(e) => setForm({ ...form, lotCode: e.target.value })}\n placeholder=\"LOT-2026-0042\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"commodity\">Commodity *</Label>\n <Select\n value={form.commodity}\n onValueChange={(v) => setForm({ ...form, commodity: v as TheCommodityType })}\n >\n <SelectTrigger id=\"commodity\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {COMMODITIES.map((c) => (\n <SelectItem key={c} value={c}>\n {formatEnumLabel(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"assetId\">Produced by asset</Label>\n <Select\n value={form.assetId || NONE_ASSET_VALUE}\n onValueChange={(v) =>\n setForm({ ...form, assetId: v === NONE_ASSET_VALUE ? '' : v })\n }\n >\n <SelectTrigger id=\"assetId\">\n <SelectValue placeholder=\"Optional — link to a producing asset\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NONE_ASSET_VALUE}>None</SelectItem>\n {Object.entries(assetMap).map(([id, label]) => (\n <SelectItem key={id} value={id}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-3\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"quantity\">Quantity *</Label>\n <Input\n id=\"quantity\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.quantity}\n onChange={(e) => setForm({ ...form, quantity: e.target.value })}\n placeholder=\"120000\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"unit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"unit\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {UNITS.map((u) => (\n <SelectItem key={u} value={u}>\n {formatUnit(u)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"grade\">Grade</Label>\n <Input\n id=\"grade\"\n type=\"number\"\n step=\"any\"\n value={form.grade}\n onChange={(e) => setForm({ ...form, grade: e.target.value })}\n placeholder=\"0.32\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-3\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"unitValue\">Unit value</Label>\n <Input\n id=\"unitValue\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.unitValue}\n onChange={(e) => setForm({ ...form, unitValue: e.target.value })}\n placeholder=\"82.50\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"currency\">Currency</Label>\n <Input\n id=\"currency\"\n value={form.currency}\n onChange={(e) => setForm({ ...form, currency: e.target.value })}\n placeholder=\"USD\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"producedAt\">Produced at</Label>\n <Input\n id=\"producedAt\"\n type=\"date\"\n value={form.producedAt}\n onChange={(e) => setForm({ ...form, producedAt: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"location\">Location</Label>\n <Input\n id=\"location\"\n value={form.location}\n onChange={(e) => setForm({ ...form, location: e.target.value })}\n placeholder=\"Rotterdam Tank Farm 3\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"notes\">Notes</Label>\n <Textarea\n id=\"notes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this lot.\"\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n setDialogOpen(false);\n resetForm();\n }}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={createLot.isPending}>\n {createLot.isPending ? 'Creating…' : 'Create lot'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -43,7 +43,7 @@ function P() {
43
43
  assetId: F === "ALL" ? void 0 : F,
44
44
  pagination: {
45
45
  limit: j,
46
- cursor: L.cursor
46
+ offset: L.page * j
47
47
  }
48
48
  }), q = ie(), J = d(() => K?.items ?? [], [K]), { volumeValue: _e, volumeFooter: Y, downtimeTotal: X } = d(() => {
49
49
  let e = /* @__PURE__ */ new Map(), t = 0;
@@ -246,7 +246,7 @@ function P() {
246
246
  pageSize: j,
247
247
  itemCount: J.length,
248
248
  hasMore: K?.pageInfo?.hasMore ?? !1,
249
- onPageChange: (e) => L.onPageChange(e, K?.pageInfo?.cursor)
249
+ onPageChange: L.onPageChange
250
250
  })] }),
251
251
  /* @__PURE__ */ y(ce, {
252
252
  open: R,
@@ -1 +1 @@
1
- {"version":3,"file":"ProductionPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/production/ProductionPage.tsx"],"sourcesContent":["/**\n * Production — every production record across all assets.\n * @module theglobalfuel/pages/production\n *\n * Reads `theListProductionRecords` (optionally filtered by asset); writes\n * `theRecordProduction`. Asset names are resolved from `theListAssets`. The KPI\n * strip summarises the volume + downtime captured on the current page. No mock\n * data — every number comes from the real `wspace-theglobalfuel-svc` contract.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { Plus, BarChart3, Droplets, Clock, Gauge } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { useTheGlobalFuel } from '../../context';\nimport {\n useTheAssets,\n useTheProductionRecords,\n useTheRecordProduction,\n useCursorPager,\n useNotify,\n} from '../../hooks';\nimport {\n formatDate,\n formatNumber,\n formatRelativeTime,\n formatVolume,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport { toBackendDate } from '../../utils';\nimport type { TheProductionRecord, TheUnit } from '../../types';\n\nconst UNITS: TheUnit[] = [\n 'BARRELS',\n 'CUBIC_METERS',\n 'TONNES',\n 'KILOGRAMS',\n 'MWH',\n 'GJ',\n 'MCF',\n];\n\nconst PAGE_SIZE = 25;\n\nconst today = (): string => new Date().toISOString().slice(0, 10);\n\ninterface RecordFormState {\n assetId: string;\n periodStart: string;\n periodEnd: string;\n volume: string;\n unit: TheUnit;\n grade: string;\n downtimeHours: string;\n notes: string;\n}\n\nconst emptyForm = (): RecordFormState => ({\n assetId: '',\n periodStart: today(),\n periodEnd: today(),\n volume: '',\n unit: 'BARRELS',\n grade: '',\n downtimeHours: '',\n notes: '',\n});\n\nexport function ProductionPage() {\n const { navigateTo } = useTheGlobalFuel();\n const notify = useNotify();\n const [assetId, setAssetId] = useState<string>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<RecordFormState>(emptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n // Assets feed the filter Select, the create picker, and the id→name map.\n const assetsQuery = useTheAssets({ pagination: { limit: 100 } });\n const assetMap = useMemo<Record<string, string>>(() => {\n const map: Record<string, string> = {};\n for (const a of assetsQuery.data?.items ?? []) map[a.id] = a.name;\n return map;\n }, [assetsQuery.data]);\n\n const { data, isLoading, isError, refetch } = useTheProductionRecords({\n assetId: assetId === 'ALL' ? undefined : assetId,\n pagination: { limit: PAGE_SIZE, cursor: pager.cursor },\n });\n const recordProduction = useTheRecordProduction();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n // KPI summary — page-scoped totals (volume grouped by unit, downtime summed).\n const { volumeValue, volumeFooter, downtimeTotal } = useMemo(() => {\n const byUnit = new Map<TheUnit, number>();\n let downtime = 0;\n for (const r of rows) {\n byUnit.set(r.unit, (byUnit.get(r.unit) ?? 0) + r.volume);\n downtime += r.downtimeHours;\n }\n const units = [...byUnit.entries()];\n let value: string;\n let footer: ReactNode | undefined;\n if (units.length === 0) {\n value = '—';\n } else if (units.length === 1) {\n value = formatVolume(units[0][1], units[0][0]);\n } else {\n value = 'Mixed units';\n footer = (\n <p className=\"text-xs tabular-nums text-text-tertiary\">\n {units.map(([u, v]) => formatVolume(v, u)).join(' · ')}\n </p>\n );\n }\n return { volumeValue: value, volumeFooter: footer, downtimeTotal: downtime };\n }, [rows]);\n\n const columns: Column<TheProductionRecord>[] = [\n {\n key: 'asset',\n header: 'Asset',\n render: (r) => (\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">\n {assetMap[r.assetId] ?? `${r.assetId.slice(0, 8)}…`}\n </p>\n </div>\n ),\n },\n {\n key: 'period',\n header: 'Period',\n hideOnMobile: true,\n sortValue: (r) => new Date(r.periodStart).getTime(),\n render: (r) => (\n <span className=\"text-text-secondary\">\n {formatDate(r.periodStart)} – {formatDate(r.periodEnd)}\n </span>\n ),\n },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n sortValue: (r) => r.volume,\n render: (r) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatVolume(r.volume, r.unit)}\n </span>\n ),\n },\n {\n key: 'grade',\n header: 'Grade',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.grade ?? 0,\n render: (r) =>\n r.grade != null ? (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(r.grade)}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'downtime',\n header: 'Downtime',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.downtimeHours,\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(r.downtimeHours)}h</span>\n ),\n },\n {\n key: 'created',\n header: 'Logged',\n align: 'right',\n hideOnMobile: true,\n render: (r) => <span className=\"text-text-tertiary\">{formatRelativeTime(r.createdAt)}</span>,\n },\n ];\n\n const openDialog = () => {\n setForm(emptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(emptyForm());\n setFormError(null);\n };\n\n const handleRecord = async () => {\n if (!form.assetId) {\n setFormError('Select an asset.');\n return;\n }\n // Anchor date-only inputs at noon UTC so the calendar day survives every TZ.\n const periodStart = toBackendDate(form.periodStart);\n const periodEnd = toBackendDate(form.periodEnd);\n if (!periodStart || !periodEnd) {\n setFormError('Period start and end are required.');\n return;\n }\n if (new Date(periodEnd).getTime() < new Date(periodStart).getTime()) {\n setFormError('Period end must be on or after the period start.');\n return;\n }\n const volume = Number(form.volume);\n if (!form.volume || Number.isNaN(volume) || volume <= 0) {\n const message = 'Volume must be greater than zero.';\n setFormError(message);\n notify.error(message);\n return;\n }\n setFormError(null);\n try {\n await recordProduction.mutateAsync({\n assetId: form.assetId,\n periodStart,\n periodEnd,\n volume,\n unit: form.unit,\n grade: form.grade ? Number(form.grade) : null,\n downtimeHours: form.downtimeHours ? Number(form.downtimeHours) : null,\n notes: form.notes.trim() || null,\n });\n notify.success('Production recorded');\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to record production.');\n notify.fromError(err);\n }\n };\n\n const assetOptions = assetsQuery.data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Production\"\n description=\"Captured production volumes across every field, well, mine & plant — with downtime and grade.\"\n actions={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record production\n </Button>\n }\n >\n {/* KPI strip — page-scoped totals */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Droplets className=\"h-5 w-5\" />}\n value={volumeValue}\n label=\"Volume on this page\"\n featured\n footer={volumeFooter}\n />\n <MetricsCard\n icon={<Clock className=\"h-5 w-5\" />}\n value={`${formatNumber(downtimeTotal)}h`}\n label=\"Downtime on this page\"\n accent={downtimeTotal > 0 ? 'warning' : undefined}\n />\n <MetricsCard\n icon={<BarChart3 className=\"h-5 w-5\" />}\n value={rows.length}\n label=\"Records on this page\"\n />\n </div>\n\n {/* Filter */}\n <div className=\"mt-6 mb-5 flex flex-wrap items-center gap-3\">\n <Select\n value={assetId}\n onValueChange={(v) => {\n setAssetId(v);\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-56\" aria-label=\"Filter by asset\">\n <SelectValue placeholder=\"All assets\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All assets</SelectItem>\n {assetOptions.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading production records…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load production records.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Gauge className=\"h-10 w-10\" />}\n title=\"No production recorded\"\n description={\n assetId === 'ALL'\n ? 'Record a production period to start tracking output, grade and downtime.'\n : 'No production has been recorded for this asset yet.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record production\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\n onRowClick={(r) => navigateTo(`assets/${r.assetId}`)}\n rowLabel={(r) => `Open ${assetMap[r.assetId] ?? 'asset'}`}\n defaultSort={{ key: 'period', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={(target) => pager.onPageChange(target, data?.pageInfo?.cursor)}\n />\n </>\n )}\n\n {/* Record production dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => (open ? setDialogOpen(true) : closeDialog())}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record production</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log the volume produced by an asset over a reporting period.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleRecord();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prAsset\">Asset *</Label>\n <Select\n value={form.assetId}\n onValueChange={(v) => setForm({ ...form, assetId: v })}\n >\n <SelectTrigger id=\"prAsset\">\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n {assetOptions.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prStart\">Period start *</Label>\n <Input\n id=\"prStart\"\n type=\"date\"\n value={form.periodStart}\n onChange={(e) => setForm({ ...form, periodStart: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prEnd\">Period end *</Label>\n <Input\n id=\"prEnd\"\n type=\"date\"\n value={form.periodEnd}\n onChange={(e) => setForm({ ...form, periodEnd: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prVolume\">Volume *</Label>\n <Input\n id=\"prVolume\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.volume}\n onChange={(e) => setForm({ ...form, volume: e.target.value })}\n placeholder=\"12500\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prUnit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"prUnit\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {UNITS.map((u) => (\n <SelectItem key={u} value={u}>\n {formatEnumLabel(u)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prGrade\">Grade</Label>\n <Input\n id=\"prGrade\"\n type=\"number\"\n step=\"any\"\n value={form.grade}\n onChange={(e) => setForm({ ...form, grade: e.target.value })}\n placeholder=\"Quality / concentration\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prDowntime\">Downtime (hours)</Label>\n <Input\n id=\"prDowntime\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.downtimeHours}\n onChange={(e) => setForm({ ...form, downtimeHours: e.target.value })}\n placeholder=\"0\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prNotes\">Notes</Label>\n <Textarea\n id=\"prNotes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this period.\"\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={recordProduction.isPending}>\n {recordProduction.isPending ? 'Recording…' : 'Record production'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProductionPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/production/ProductionPage.tsx"],"sourcesContent":["/**\n * Production — every production record across all assets.\n * @module theglobalfuel/pages/production\n *\n * Reads `theListProductionRecords` (optionally filtered by asset); writes\n * `theRecordProduction`. Asset names are resolved from `theListAssets`. The KPI\n * strip summarises the volume + downtime captured on the current page. No mock\n * data — every number comes from the real `wspace-theglobalfuel-svc` contract.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { Plus, BarChart3, Droplets, Clock, Gauge } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { useTheGlobalFuel } from '../../context';\nimport {\n useTheAssets,\n useTheProductionRecords,\n useTheRecordProduction,\n useCursorPager,\n useNotify,\n} from '../../hooks';\nimport {\n formatDate,\n formatNumber,\n formatRelativeTime,\n formatVolume,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport { toBackendDate } from '../../utils';\nimport type { TheProductionRecord, TheUnit } from '../../types';\n\nconst UNITS: TheUnit[] = [\n 'BARRELS',\n 'CUBIC_METERS',\n 'TONNES',\n 'KILOGRAMS',\n 'MWH',\n 'GJ',\n 'MCF',\n];\n\nconst PAGE_SIZE = 25;\n\nconst today = (): string => new Date().toISOString().slice(0, 10);\n\ninterface RecordFormState {\n assetId: string;\n periodStart: string;\n periodEnd: string;\n volume: string;\n unit: TheUnit;\n grade: string;\n downtimeHours: string;\n notes: string;\n}\n\nconst emptyForm = (): RecordFormState => ({\n assetId: '',\n periodStart: today(),\n periodEnd: today(),\n volume: '',\n unit: 'BARRELS',\n grade: '',\n downtimeHours: '',\n notes: '',\n});\n\nexport function ProductionPage() {\n const { navigateTo } = useTheGlobalFuel();\n const notify = useNotify();\n const [assetId, setAssetId] = useState<string>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<RecordFormState>(emptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n // Assets feed the filter Select, the create picker, and the id→name map.\n const assetsQuery = useTheAssets({ pagination: { limit: 100 } });\n const assetMap = useMemo<Record<string, string>>(() => {\n const map: Record<string, string> = {};\n for (const a of assetsQuery.data?.items ?? []) map[a.id] = a.name;\n return map;\n }, [assetsQuery.data]);\n\n const { data, isLoading, isError, refetch } = useTheProductionRecords({\n assetId: assetId === 'ALL' ? undefined : assetId,\n pagination: { limit: PAGE_SIZE, offset: pager.page * PAGE_SIZE },\n });\n const recordProduction = useTheRecordProduction();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n // KPI summary — page-scoped totals (volume grouped by unit, downtime summed).\n const { volumeValue, volumeFooter, downtimeTotal } = useMemo(() => {\n const byUnit = new Map<TheUnit, number>();\n let downtime = 0;\n for (const r of rows) {\n byUnit.set(r.unit, (byUnit.get(r.unit) ?? 0) + r.volume);\n downtime += r.downtimeHours;\n }\n const units = [...byUnit.entries()];\n let value: string;\n let footer: ReactNode | undefined;\n if (units.length === 0) {\n value = '—';\n } else if (units.length === 1) {\n value = formatVolume(units[0][1], units[0][0]);\n } else {\n value = 'Mixed units';\n footer = (\n <p className=\"text-xs tabular-nums text-text-tertiary\">\n {units.map(([u, v]) => formatVolume(v, u)).join(' · ')}\n </p>\n );\n }\n return { volumeValue: value, volumeFooter: footer, downtimeTotal: downtime };\n }, [rows]);\n\n const columns: Column<TheProductionRecord>[] = [\n {\n key: 'asset',\n header: 'Asset',\n render: (r) => (\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">\n {assetMap[r.assetId] ?? `${r.assetId.slice(0, 8)}…`}\n </p>\n </div>\n ),\n },\n {\n key: 'period',\n header: 'Period',\n hideOnMobile: true,\n sortValue: (r) => new Date(r.periodStart).getTime(),\n render: (r) => (\n <span className=\"text-text-secondary\">\n {formatDate(r.periodStart)} – {formatDate(r.periodEnd)}\n </span>\n ),\n },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n sortValue: (r) => r.volume,\n render: (r) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatVolume(r.volume, r.unit)}\n </span>\n ),\n },\n {\n key: 'grade',\n header: 'Grade',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.grade ?? 0,\n render: (r) =>\n r.grade != null ? (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(r.grade)}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'downtime',\n header: 'Downtime',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.downtimeHours,\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(r.downtimeHours)}h</span>\n ),\n },\n {\n key: 'created',\n header: 'Logged',\n align: 'right',\n hideOnMobile: true,\n render: (r) => <span className=\"text-text-tertiary\">{formatRelativeTime(r.createdAt)}</span>,\n },\n ];\n\n const openDialog = () => {\n setForm(emptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(emptyForm());\n setFormError(null);\n };\n\n const handleRecord = async () => {\n if (!form.assetId) {\n setFormError('Select an asset.');\n return;\n }\n // Anchor date-only inputs at noon UTC so the calendar day survives every TZ.\n const periodStart = toBackendDate(form.periodStart);\n const periodEnd = toBackendDate(form.periodEnd);\n if (!periodStart || !periodEnd) {\n setFormError('Period start and end are required.');\n return;\n }\n if (new Date(periodEnd).getTime() < new Date(periodStart).getTime()) {\n setFormError('Period end must be on or after the period start.');\n return;\n }\n const volume = Number(form.volume);\n if (!form.volume || Number.isNaN(volume) || volume <= 0) {\n const message = 'Volume must be greater than zero.';\n setFormError(message);\n notify.error(message);\n return;\n }\n setFormError(null);\n try {\n await recordProduction.mutateAsync({\n assetId: form.assetId,\n periodStart,\n periodEnd,\n volume,\n unit: form.unit,\n grade: form.grade ? Number(form.grade) : null,\n downtimeHours: form.downtimeHours ? Number(form.downtimeHours) : null,\n notes: form.notes.trim() || null,\n });\n notify.success('Production recorded');\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to record production.');\n notify.fromError(err);\n }\n };\n\n const assetOptions = assetsQuery.data?.items ?? [];\n\n return (\n <PageLayout\n title=\"Production\"\n description=\"Captured production volumes across every field, well, mine & plant — with downtime and grade.\"\n actions={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record production\n </Button>\n }\n >\n {/* KPI strip — page-scoped totals */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Droplets className=\"h-5 w-5\" />}\n value={volumeValue}\n label=\"Volume on this page\"\n featured\n footer={volumeFooter}\n />\n <MetricsCard\n icon={<Clock className=\"h-5 w-5\" />}\n value={`${formatNumber(downtimeTotal)}h`}\n label=\"Downtime on this page\"\n accent={downtimeTotal > 0 ? 'warning' : undefined}\n />\n <MetricsCard\n icon={<BarChart3 className=\"h-5 w-5\" />}\n value={rows.length}\n label=\"Records on this page\"\n />\n </div>\n\n {/* Filter */}\n <div className=\"mt-6 mb-5 flex flex-wrap items-center gap-3\">\n <Select\n value={assetId}\n onValueChange={(v) => {\n setAssetId(v);\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-56\" aria-label=\"Filter by asset\">\n <SelectValue placeholder=\"All assets\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All assets</SelectItem>\n {assetOptions.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading production records…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load production records.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Gauge className=\"h-10 w-10\" />}\n title=\"No production recorded\"\n description={\n assetId === 'ALL'\n ? 'Record a production period to start tracking output, grade and downtime.'\n : 'No production has been recorded for this asset yet.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record production\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\n onRowClick={(r) => navigateTo(`assets/${r.assetId}`)}\n rowLabel={(r) => `Open ${assetMap[r.assetId] ?? 'asset'}`}\n defaultSort={{ key: 'period', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={pager.onPageChange}\n />\n </>\n )}\n\n {/* Record production dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => (open ? setDialogOpen(true) : closeDialog())}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record production</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log the volume produced by an asset over a reporting period.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleRecord();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prAsset\">Asset *</Label>\n <Select\n value={form.assetId}\n onValueChange={(v) => setForm({ ...form, assetId: v })}\n >\n <SelectTrigger id=\"prAsset\">\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n {assetOptions.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prStart\">Period start *</Label>\n <Input\n id=\"prStart\"\n type=\"date\"\n value={form.periodStart}\n onChange={(e) => setForm({ ...form, periodStart: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prEnd\">Period end *</Label>\n <Input\n id=\"prEnd\"\n type=\"date\"\n value={form.periodEnd}\n onChange={(e) => setForm({ ...form, periodEnd: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prVolume\">Volume *</Label>\n <Input\n id=\"prVolume\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.volume}\n onChange={(e) => setForm({ ...form, volume: e.target.value })}\n placeholder=\"12500\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prUnit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"prUnit\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {UNITS.map((u) => (\n <SelectItem key={u} value={u}>\n {formatEnumLabel(u)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prGrade\">Grade</Label>\n <Input\n id=\"prGrade\"\n type=\"number\"\n step=\"any\"\n value={form.grade}\n onChange={(e) => setForm({ ...form, grade: e.target.value })}\n placeholder=\"Quality / concentration\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prDowntime\">Downtime (hours)</Label>\n <Input\n id=\"prDowntime\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.downtimeHours}\n onChange={(e) => setForm({ ...form, downtimeHours: e.target.value })}\n placeholder=\"0\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prNotes\">Notes</Label>\n <Textarea\n id=\"prNotes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this period.\"\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={recordProduction.isPending}>\n {recordProduction.isPending ? 'Recording…' : 'Record production'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAqDA,IAAM,IAAmB;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAY,IAEZ,2BAAsB,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG,EAa3D,WAAoC;CACxC,SAAS;CACT,aAAa,GAAO;CACpB,WAAW,GAAO;CAClB,QAAQ;CACR,MAAM;CACN,OAAO;CACP,eAAe;CACf,OAAO;CACR;AAED,SAAgB,IAAiB;CAC/B,IAAM,EAAE,kBAAe,GAAkB,EACnC,IAAS,IAAW,EACpB,CAAC,GAAS,KAAc,EAAiB,MAAM,EAC/C,IAAQ,IAAgB,EACxB,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAM,KAAW,EAA0B,EAAU,EACtD,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAGzD,IAAc,GAAa,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE,CAAC,EAC1D,IAAW,QAAsC;EACrD,IAAM,IAA8B,EAAE;AACtC,OAAK,IAAM,KAAK,EAAY,MAAM,SAAS,EAAE,CAAE,GAAI,EAAE,MAAM,EAAE;AAC7D,SAAO;IACN,CAAC,EAAY,KAAK,CAAC,EAEhB,EAAE,SAAM,eAAW,aAAS,gBAAY,GAAwB;EACpE,SAAS,MAAY,QAAQ,KAAA,IAAY;EACzC,YAAY;GAAE,OAAO;GAAW,QAAQ,EAAM,OAAO;GAAW;EACjE,CAAC,EACI,IAAmB,IAAwB,EAE3C,IAAO,QAAc,GAAM,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EAG/C,EAAE,iBAAa,iBAAc,qBAAkB,QAAc;EACjE,IAAM,oBAAS,IAAI,KAAsB,EACrC,IAAW;AACf,OAAK,IAAM,KAAK,EAEd,CADA,EAAO,IAAI,EAAE,OAAO,EAAO,IAAI,EAAE,KAAK,IAAI,KAAK,EAAE,OAAO,EACxD,KAAY,EAAE;EAEhB,IAAM,IAAQ,CAAC,GAAG,EAAO,SAAS,CAAC,EAC/B,GACA;AAaJ,SAZI,EAAM,WAAW,IACnB,IAAQ,MACC,EAAM,WAAW,IAC1B,IAAQ,EAAa,EAAM,GAAG,IAAI,EAAM,GAAG,GAAG,IAE9C,IAAQ,eACR,IACE,kBAAC,KAAD;GAAG,WAAU;aACV,EAAM,KAAK,CAAC,GAAG,OAAO,EAAa,GAAG,EAAE,CAAC,CAAC,KAAK,MAAM;GACpD,CAAA,GAGD;GAAE,aAAa;GAAO,cAAc;GAAQ,eAAe;GAAU;IAC3E,CAAC,EAAK,CAAC,EAEJ,KAAyC;EAC7C;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eACV,EAAS,EAAE,YAAY,GAAG,EAAE,QAAQ,MAAM,GAAG,EAAE,CAAC;KAC/C,CAAA;IACA,CAAA;GAET;EACD;GACE,KAAK;GACL,QAAQ;GACR,cAAc;GACd,YAAY,MAAM,IAAI,KAAK,EAAE,YAAY,CAAC,SAAS;GACnD,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACG,EAAW,EAAE,YAAY;KAAC;KAAI,EAAW,EAAE,UAAU;KACjD;;GAEV;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,YAAY,MAAM,EAAE;GACpB,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cACb,EAAa,EAAE,QAAQ,EAAE,KAAK;IAC1B,CAAA;GAEV;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,cAAc;GACd,YAAY,MAAM,EAAE,SAAS;GAC7B,SAAS,MACP,EAAE,SAAS,OAGT,kBAAC,QAAD;IAAM,WAAU;cAAqB;IAAQ,CAAA,GAF7C,kBAAC,QAAD;IAAM,WAAU;cAAoC,EAAa,EAAE,MAAM;IAAQ,CAAA;GAItF;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,cAAc;GACd,YAAY,MAAM,EAAE;GACpB,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cAAhB,CAAoD,EAAa,EAAE,cAAc,EAAC,IAAQ;;GAE7F;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,cAAc;GACd,SAAS,MAAM,kBAAC,QAAD;IAAM,WAAU;cAAsB,GAAmB,EAAE,UAAU;IAAQ,CAAA;GAC7F;EACF,EAEK,UAAmB;AAGvB,EAFA,EAAQ,GAAW,CAAC,EACpB,EAAa,KAAK,EAClB,EAAc,GAAK;IAGf,UAAoB;AAGxB,EAFA,EAAc,GAAM,EACpB,EAAQ,GAAW,CAAC,EACpB,EAAa,KAAK;IAGd,KAAe,YAAY;AAC/B,MAAI,CAAC,EAAK,SAAS;AACjB,KAAa,mBAAmB;AAChC;;EAGF,IAAM,IAAc,EAAc,EAAK,YAAY,EAC7C,IAAY,EAAc,EAAK,UAAU;AAC/C,MAAI,CAAC,KAAe,CAAC,GAAW;AAC9B,KAAa,qCAAqC;AAClD;;AAEF,MAAI,IAAI,KAAK,EAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAY,CAAC,SAAS,EAAE;AACnE,KAAa,mDAAmD;AAChE;;EAEF,IAAM,IAAS,OAAO,EAAK,OAAO;AAClC,MAAI,CAAC,EAAK,UAAU,OAAO,MAAM,EAAO,IAAI,KAAU,GAAG;GACvD,IAAM,IAAU;AAEhB,GADA,EAAa,EAAQ,EACrB,EAAO,MAAM,EAAQ;AACrB;;AAEF,IAAa,KAAK;AAClB,MAAI;AAYF,GAXA,MAAM,EAAiB,YAAY;IACjC,SAAS,EAAK;IACd;IACA;IACA;IACA,MAAM,EAAK;IACX,OAAO,EAAK,QAAQ,OAAO,EAAK,MAAM,GAAG;IACzC,eAAe,EAAK,gBAAgB,OAAO,EAAK,cAAc,GAAG;IACjE,OAAO,EAAK,MAAM,MAAM,IAAI;IAC7B,CAAC,EACF,EAAO,QAAQ,sBAAsB,EACrC,GAAa;WACN,GAAK;AAEZ,GADA,EAAa,aAAe,QAAQ,EAAI,UAAU,+BAA+B,EACjF,EAAO,UAAU,EAAI;;IAInB,IAAe,EAAY,MAAM,SAAS,EAAE;AAElD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,SAAS;aAAjB,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,qBAC5B;;YANb;GAUE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;MACtC,OAAO;MACP,OAAM;MACN,UAAA;MACA,QAAQ;MACR,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,OAAO,GAAG,EAAa,EAAc,CAAC;MACtC,OAAM;MACN,QAAQ,IAAgB,IAAI,YAAY,KAAA;MACxC,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;MACvC,OAAO,EAAK;MACZ,OAAM;MACN,CAAA;KACE;;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,OAAO;KACP,gBAAgB,MAAM;AAEpB,MADA,EAAW,EAAE,EACb,EAAM,OAAO;;eAJjB,CAOE,kBAAC,GAAD;MAAe,WAAU;MAAO,cAAW;gBACzC,kBAAC,GAAD,EAAa,aAAY,cAAe,CAAA;MAC1B,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAY,OAAM;gBAAM;MAAuB,CAAA,EAC9C,EAAa,KAAK,MACjB,kBAAC,GAAD;MAAuB,OAAO,EAAE;gBAC7B,EAAE;MACQ,EAFI,EAAE,GAEN,CACb,CACY,EAAA,CAAA,CACT;;IACL,CAAA;GAEL,KACC,kBAAC,GAAD,EAAa,OAAM,+BAAgC,CAAA,GACjD,KACF,kBAAC,GAAD;IAAW,SAAQ;IAAoC,eAAe,IAAS;IAAI,CAAA,GACjF,EAAK,WAAW,IAClB,kBAAC,IAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;IACrC,OAAM;IACN,aACE,MAAY,QACR,6EACA;IAEN,QACE,kBAAC,GAAD;KAAQ,SAAS;eAAjB,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,qBAC5B;;IAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;IACW;IACH;IACN,SAAS,MAAM,EAAE;IACjB,aAAa,MAAM,EAAW,UAAU,EAAE,UAAU;IACpD,WAAW,MAAM,QAAQ,EAAS,EAAE,YAAY;IAChD,aAAa;KAAE,KAAK;KAAU,KAAK;KAAQ;IAC3C,CAAA,EACF,kBAAC,IAAD;IACE,MAAM,EAAM;IACZ,UAAU;IACV,WAAW,EAAK;IAChB,SAAS,GAAM,UAAU,WAAW;IACpC,cAAc,EAAM;IACpB,CAAA,CACD,EAAA,CAAA;GAIL,kBAAC,IAAD;IACE,MAAM;IACN,eAAe,MAAU,IAAO,EAAc,GAAK,GAAG,GAAa;cAEnE,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAuB,qBAAyC,CAAA,EAChE,kBAAC,IAAD,EAAA,UAA6B,gEAEC,CAAA,CACP,EAAA,CAAA,EAEzB,kBAAC,QAAD;KACE,WAAW,MAAM;AAEV,MADL,EAAE,gBAAgB,EACb,IAAc;;eAHvB,CAME,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACA,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAU;SAAe,CAAA,EACxC,kBAAC,GAAD;SACE,OAAO,EAAK;SACZ,gBAAgB,MAAM,EAAQ;UAAE,GAAG;UAAM,SAAS;UAAG,CAAC;mBAFxD,CAIE,kBAAC,GAAD;UAAe,IAAG;oBAChB,kBAAC,GAAD,EAAa,aAAY,mBAAoB,CAAA;UAC/B,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAa,KAAK,MACjB,kBAAC,GAAD;UAAuB,OAAO,EAAE;oBAC7B,EAAE;UACQ,EAFI,EAAE,GAEN,CACb,EACY,CAAA,CACT;WACL;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAU;UAAsB,CAAA,EAC/C,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,aAAa,EAAE,OAAO;WAAO,CAAC;UAClE,CAAA,CACE;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAQ;UAAoB,CAAA,EAC3C,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,WAAW,EAAE,OAAO;WAAO,CAAC;UAChE,CAAA,CACE;WACF;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAW;UAAgB,CAAA,EAC1C,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,KAAI;UACJ,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,QAAQ,EAAE,OAAO;WAAO,CAAC;UAC7D,aAAY;UACZ,CAAA,CACE;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAS;UAAY,CAAA,EACpC,kBAAC,GAAD;UACE,OAAO,EAAK;UACZ,gBAAgB,MAAM,EAAQ;WAAE,GAAG;WAAM,MAAM;WAAc,CAAC;oBAFhE,CAIE,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,EAAe,CAAA;WACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAM,KAAK,MACV,kBAAC,GAAD;WAAoB,OAAO;qBACxB,EAAgB,EAAE;WACR,EAFI,EAEJ,CACb,EACY,CAAA,CACT;YACL;WACF;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAU;UAAa,CAAA,EACtC,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,OAAO,EAAE,OAAO;WAAO,CAAC;UAC5D,aAAY;UACZ,CAAA,CACE;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAa;UAAwB,CAAA,EACpD,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,KAAI;UACJ,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,eAAe,EAAE,OAAO;WAAO,CAAC;UACpE,aAAY;UACZ,CAAA,CACE;WACF;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAU;SAAa,CAAA,EACtC,kBAAC,GAAD;SACE,IAAG;SACH,MAAM;SACN,OAAO,EAAK;SACZ,WAAW,MAAM,EAAQ;UAAE,GAAG;UAAM,OAAO,EAAE,OAAO;UAAO,CAAC;SAC5D,aAAY;SACZ,CAAA,CACE;;OAEL,KAAa,kBAAC,KAAD;QAAG,WAAU;kBAAkC;QAAc,CAAA;OACrE;SAEN,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,MAAK;MAAS,SAAQ;MAAU,SAAS;gBAAa;MAErD,CAAA,EACT,kBAAC,GAAD;MAAQ,MAAK;MAAS,UAAU,EAAiB;gBAC9C,EAAiB,YAAY,eAAe;MACtC,CAAA,CACc,EAAA,CAAA,CACpB;OACiB,EAAA,CAAA;IACT,CAAA;GACR"}