@burdenoff/microfe-theglobalfuel 2026.705.2 → 2026.705.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/theglobalfuel/hooks/useCursorPager.js +6 -15
- package/dist/theglobalfuel/hooks/useCursorPager.js.map +1 -1
- package/dist/theglobalfuel/pages/assets/AssetsPage.js +99 -99
- package/dist/theglobalfuel/pages/assets/AssetsPage.js.map +1 -1
- package/dist/theglobalfuel/pages/compliance/CompliancePage.js +250 -250
- package/dist/theglobalfuel/pages/compliance/CompliancePage.js.map +1 -1
- package/dist/theglobalfuel/pages/contracts/ContractsPage.js +2 -2
- package/dist/theglobalfuel/pages/contracts/ContractsPage.js.map +1 -1
- package/dist/theglobalfuel/pages/emissions/EmissionsPage.js +174 -174
- package/dist/theglobalfuel/pages/emissions/EmissionsPage.js.map +1 -1
- package/dist/theglobalfuel/pages/inventory/InventoryPage.js +60 -60
- package/dist/theglobalfuel/pages/inventory/InventoryPage.js.map +1 -1
- package/dist/theglobalfuel/pages/production/ProductionPage.js +2 -2
- package/dist/theglobalfuel/pages/production/ProductionPage.js.map +1 -1
- package/dist/theglobalfuel/pages/reserves/ReservesPage.js +152 -152
- package/dist/theglobalfuel/pages/reserves/ReservesPage.js.map +1 -1
- package/dist/theglobalfuel/pages/shipments/ShipmentsPage.js +6 -6
- package/dist/theglobalfuel/pages/shipments/ShipmentsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReservesPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/reserves/ReservesPage.tsx"],"sourcesContent":["/**\n * Reserve Estimates — booked recoverable volumes per asset, by category.\n * @module theglobalfuel/pages/reserves\n *\n * Reads `theListReserveEstimates` (a cursor connection — `{ items, pageInfo }`);\n * writes `theCreateReserveEstimate`. Asset names are resolved from `theListAssets`\n * since the reserve payload carries only `assetId`. No mock data.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Layers, Database, Percent } 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 useTheAssets,\n useTheReserveEstimates,\n useTheCreateReserveEstimate,\n useCursorPager,\n useNotify,\n} from '../../hooks';\nimport {\n formatDate,\n formatVolume,\n formatPercent,\n formatUnit,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport { toBackendDate } from '../../utils';\nimport type { TheReserveEstimate, TheReserveCategory, TheUnit } from '../../types';\n\nconst CATEGORY_FILTERS: FilterOption<TheReserveCategory | 'ALL'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'PROVED', label: 'Proved' },\n { value: 'PROBABLE', label: 'Probable' },\n { value: 'POSSIBLE', label: 'Possible' },\n { value: 'CONTINGENT', label: 'Contingent' },\n { value: 'PROSPECTIVE', label: 'Prospective' },\n];\n\nconst RESERVE_CATEGORIES: TheReserveCategory[] = [\n 'PROVED',\n 'PROBABLE',\n 'POSSIBLE',\n 'CONTINGENT',\n 'PROSPECTIVE',\n];\n\nconst UNITS: TheUnit[] = ['BARRELS', 'CUBIC_METERS', 'TONNES', 'KILOGRAMS', 'MWH', 'GJ', 'MCF'];\n\nconst PAGE_SIZE = 25;\n\nconst today = (): string => new Date().toISOString().slice(0, 10);\n\ninterface CreateFormState {\n assetId: string;\n category: TheReserveCategory;\n asOfDate: string;\n recoverable: string;\n recoveryFactor: string;\n unit: TheUnit;\n methodology: string;\n notes: string;\n}\n\nconst EMPTY_FORM: CreateFormState = {\n assetId: '',\n category: 'PROVED',\n asOfDate: '',\n recoverable: '',\n recoveryFactor: '',\n unit: 'BARRELS',\n methodology: '',\n notes: '',\n};\n\nexport function ReservesPage() {\n const notify = useNotify();\n const [category, setCategory] = useState<TheReserveCategory | 'ALL'>('ALL');\n const [assetId, setAssetId] = useState<string>('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\n // Asset registry — feeds both the filter Select and the create dialog, and\n // resolves asset names for the reserve rows (payload carries only assetId).\n const assets = useTheAssets({ pagination: { limit: 100, offset: 0 } });\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.name;\n return map;\n }, [assets.data]);\n\n const assetScope = assetId === 'ALL' ? undefined : assetId;\n const categoryScope = category === 'ALL' ? undefined : category;\n\n const { data, isLoading, isError, refetch } = useTheReserveEstimates(assetScope, categoryScope, {\n limit: PAGE_SIZE,\n cursor: pager.cursor,\n });\n // Same-filter broad query for the header KPIs. The table is paginated to\n // PAGE_SIZE, so the metrics must read from a representative set (not one page)\n // or the counts/totals would silently reflect only the first 25 rows.\n const metricsQuery = useTheReserveEstimates(assetScope, categoryScope, { limit: 200 });\n const createEstimate = useTheCreateReserveEstimate();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n const metricsRows = useMemo(() => metricsQuery.data?.items ?? [], [metricsQuery.data]);\n\n // Header metrics — computed from the (filtered) portfolio, not just the current\n // page. We surface the total recoverable in the *dominant* unit so we never sum\n // apples & oranges.\n const metrics = useMemo(() => {\n const tally: Partial<Record<TheUnit, number>> = {};\n for (const r of metricsRows) tally[r.unit] = (tally[r.unit] ?? 0) + 1;\n const dominantUnit = (Object.entries(tally) as [TheUnit, number][]).sort(\n (a, b) => b[1] - a[1]\n )[0]?.[0];\n const totalRecoverable = dominantUnit\n ? metricsRows\n .filter((r) => r.unit === dominantUnit)\n .reduce((sum, r) => sum + r.recoverable, 0)\n : 0;\n const withFactor = metricsRows.filter((r) => r.recoveryFactor != null);\n const avgFactor = withFactor.length\n ? (withFactor.reduce((sum, r) => sum + (r.recoveryFactor ?? 0), 0) / withFactor.length) * 100\n : null;\n return { dominantUnit, totalRecoverable, avgFactor };\n }, [metricsRows]);\n\n const columns: Column<TheReserveEstimate>[] = [\n {\n key: 'asset',\n header: 'Asset',\n render: (r) => (\n <span className=\"font-semibold text-text-primary\">\n {assetMap[r.assetId] ?? <span className=\"text-text-tertiary\">Unknown asset</span>}\n </span>\n ),\n },\n {\n key: 'category',\n header: 'Category',\n render: (r) => <StatusBadge status={r.category} />,\n },\n {\n key: 'asOfDate',\n header: 'As of',\n hideOnMobile: true,\n sortValue: (r) => new Date(r.asOfDate).getTime(),\n render: (r) => <span className=\"text-text-secondary\">{formatDate(r.asOfDate)}</span>,\n },\n {\n key: 'recoverable',\n header: 'Recoverable',\n align: 'right',\n sortValue: (r) => r.recoverable,\n render: (r) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatVolume(r.recoverable, r.unit)}\n </span>\n ),\n },\n {\n key: 'recoveryFactor',\n header: 'Recovery factor',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.recoveryFactor ?? -1,\n render: (r) =>\n r.recoveryFactor != null ? (\n <span className=\"tabular-nums text-text-secondary\">\n {formatPercent(r.recoveryFactor * 100)}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'methodology',\n header: 'Methodology',\n hideOnMobile: true,\n render: (r) =>\n r.methodology ? (\n <span className=\"text-text-secondary\">{r.methodology}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n const openDialog = () => {\n setForm({ ...EMPTY_FORM, asOfDate: today() });\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(EMPTY_FORM);\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!form.assetId) {\n setFormError('Select the asset this estimate belongs to.');\n return;\n }\n // Anchor the date-only input at noon UTC so the calendar day survives every TZ.\n const asOfDate = toBackendDate(form.asOfDate);\n if (!asOfDate) {\n setFormError('An \"as of\" date is required.');\n return;\n }\n const recoverable = Number(form.recoverable);\n if (!form.recoverable || Number.isNaN(recoverable) || recoverable <= 0) {\n const message = 'Recoverable volume must be greater than zero.';\n setFormError(message);\n notify.error(message);\n return;\n }\n const recoveryFactor = form.recoveryFactor ? Number(form.recoveryFactor) : null;\n if (recoveryFactor != null && (recoveryFactor < 0 || recoveryFactor > 1)) {\n setFormError('Recovery factor must be between 0 and 1.');\n return;\n }\n setFormError(null);\n try {\n await createEstimate.mutateAsync({\n assetId: form.assetId,\n category: form.category,\n asOfDate,\n recoverable,\n recoveryFactor,\n unit: form.unit,\n methodology: form.methodology.trim() || null,\n notes: form.notes.trim() || null,\n });\n notify.success('Reserve estimate created');\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create reserve estimate.');\n notify.fromError(err);\n }\n };\n\n const noAssets = !assets.isLoading && (assets.data?.items.length ?? 0) === 0;\n\n return (\n <PageLayout\n title=\"Reserve Estimates\"\n description=\"Booked recoverable volumes per asset — proved, probable, possible, contingent & prospective.\"\n actions={\n <Button onClick={openDialog} disabled={noAssets}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add estimate\n </Button>\n }\n >\n {/* KPI strip */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Database className=\"h-5 w-5\" />}\n value={metricsRows.length}\n label=\"Reserve estimates\"\n featured\n />\n <MetricsCard\n icon={<Layers className=\"h-5 w-5\" />}\n value={\n metrics.dominantUnit\n ? formatVolume(metrics.totalRecoverable, metrics.dominantUnit)\n : '—'\n }\n label={\n metrics.dominantUnit\n ? `Total recoverable (${formatUnit(metrics.dominantUnit)})`\n : 'Total recoverable'\n }\n />\n <MetricsCard\n icon={<Percent className=\"h-5 w-5\" />}\n value={metrics.avgFactor != null ? formatPercent(metrics.avgFactor) : '—'}\n label=\"Avg recovery factor\"\n />\n </div>\n\n {/* Filters */}\n <div className=\"mt-6 mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={CATEGORY_FILTERS}\n value={category}\n onChange={(v) => {\n setCategory(v);\n pager.reset();\n }}\n />\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 {Object.entries(assetMap).map(([id, name]) => (\n <SelectItem key={id} value={id}>\n {name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading reserve estimates…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load reserve estimates.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Layers className=\"h-10 w-10\" />}\n title=\"No reserve estimates\"\n description={\n noAssets\n ? 'Register a resource asset first, then book its recoverable reserves here.'\n : category === 'ALL' && assetId === 'ALL'\n ? 'Book your first recoverable-reserves estimate against an asset.'\n : 'No estimates match these filters.'\n }\n action={\n !noAssets ? (\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add estimate\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'asOfDate', 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={(p) => pager.onPageChange(p, data?.pageInfo?.cursor)}\n />\n </>\n )}\n\n {/* Add reserve estimate dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => (open ? setDialogOpen(true) : closeDialog())}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add reserve estimate</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Book a recoverable-reserves figure for an asset as of a valuation date.\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-1.5\">\n <Label htmlFor=\"reAsset\">Asset *</Label>\n <Select value={form.assetId} onValueChange={(v) => setForm({ ...form, assetId: v })}>\n <SelectTrigger id=\"reAsset\">\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(assetMap).map(([id, name]) => (\n <SelectItem key={id} value={id}>\n {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=\"reCategory\">Category</Label>\n <Select\n value={form.category}\n onValueChange={(v) => setForm({ ...form, category: v as TheReserveCategory })}\n >\n <SelectTrigger id=\"reCategory\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {RESERVE_CATEGORIES.map((c) => (\n <SelectItem key={c} value={c}>\n {formatEnumLabel(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"reAsOf\">As of date *</Label>\n <Input\n id=\"reAsOf\"\n type=\"date\"\n value={form.asOfDate}\n onChange={(e) => setForm({ ...form, asOfDate: e.target.value })}\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=\"reRecoverable\">Recoverable *</Label>\n <Input\n id=\"reRecoverable\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.recoverable}\n onChange={(e) => setForm({ ...form, recoverable: e.target.value })}\n placeholder=\"1200000\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"reUnit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"reUnit\">\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=\"reFactor\">Recovery factor</Label>\n <Input\n id=\"reFactor\"\n type=\"number\"\n min=\"0\"\n max=\"1\"\n step=\"any\"\n value={form.recoveryFactor}\n onChange={(e) => setForm({ ...form, recoveryFactor: e.target.value })}\n placeholder=\"0.35\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"reMethodology\">Methodology</Label>\n <Input\n id=\"reMethodology\"\n value={form.methodology}\n onChange={(e) => setForm({ ...form, methodology: e.target.value })}\n placeholder=\"Volumetric / decline curve / PRMS deterministic\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"reNotes\">Notes</Label>\n <Textarea\n id=\"reNotes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this estimate.\"\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={createEstimate.isPending}>\n {createEstimate.isPending ? 'Saving…' : 'Add estimate'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReservesPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/reserves/ReservesPage.tsx"],"sourcesContent":["/**\n * Reserve Estimates — booked recoverable volumes per asset, by category.\n * @module theglobalfuel/pages/reserves\n *\n * Reads `theListReserveEstimates` (a cursor connection — `{ items, pageInfo }`);\n * writes `theCreateReserveEstimate`. Asset names are resolved from `theListAssets`\n * since the reserve payload carries only `assetId`. No mock data.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Layers, Database, Percent } 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 useTheAssets,\n useTheReserveEstimates,\n useTheCreateReserveEstimate,\n useCursorPager,\n useNotify,\n} from '../../hooks';\nimport {\n formatDate,\n formatVolume,\n formatPercent,\n formatUnit,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport { toBackendDate } from '../../utils';\nimport type { TheReserveEstimate, TheReserveCategory, TheUnit } from '../../types';\n\nconst CATEGORY_FILTERS: FilterOption<TheReserveCategory | 'ALL'>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'PROVED', label: 'Proved' },\n { value: 'PROBABLE', label: 'Probable' },\n { value: 'POSSIBLE', label: 'Possible' },\n { value: 'CONTINGENT', label: 'Contingent' },\n { value: 'PROSPECTIVE', label: 'Prospective' },\n];\n\nconst RESERVE_CATEGORIES: TheReserveCategory[] = [\n 'PROVED',\n 'PROBABLE',\n 'POSSIBLE',\n 'CONTINGENT',\n 'PROSPECTIVE',\n];\n\nconst UNITS: TheUnit[] = ['BARRELS', 'CUBIC_METERS', 'TONNES', 'KILOGRAMS', 'MWH', 'GJ', 'MCF'];\n\nconst PAGE_SIZE = 25;\n\nconst today = (): string => new Date().toISOString().slice(0, 10);\n\ninterface CreateFormState {\n assetId: string;\n category: TheReserveCategory;\n asOfDate: string;\n recoverable: string;\n recoveryFactor: string;\n unit: TheUnit;\n methodology: string;\n notes: string;\n}\n\nconst EMPTY_FORM: CreateFormState = {\n assetId: '',\n category: 'PROVED',\n asOfDate: '',\n recoverable: '',\n recoveryFactor: '',\n unit: 'BARRELS',\n methodology: '',\n notes: '',\n};\n\nexport function ReservesPage() {\n const notify = useNotify();\n const [category, setCategory] = useState<TheReserveCategory | 'ALL'>('ALL');\n const [assetId, setAssetId] = useState<string>('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\n // Asset registry — feeds both the filter Select and the create dialog, and\n // resolves asset names for the reserve rows (payload carries only assetId).\n const assets = useTheAssets({ pagination: { limit: 100, offset: 0 } });\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.name;\n return map;\n }, [assets.data]);\n\n const assetScope = assetId === 'ALL' ? undefined : assetId;\n const categoryScope = category === 'ALL' ? undefined : category;\n\n const { data, isLoading, isError, refetch } = useTheReserveEstimates(assetScope, categoryScope, {\n limit: PAGE_SIZE,\n offset: pager.page * PAGE_SIZE,\n });\n // Same-filter broad query for the header KPIs. The table is paginated to\n // PAGE_SIZE, so the metrics must read from a representative set (not one page)\n // or the counts/totals would silently reflect only the first 25 rows.\n const metricsQuery = useTheReserveEstimates(assetScope, categoryScope, { limit: 200 });\n const createEstimate = useTheCreateReserveEstimate();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n const metricsRows = useMemo(() => metricsQuery.data?.items ?? [], [metricsQuery.data]);\n\n // Header metrics — computed from the (filtered) portfolio, not just the current\n // page. We surface the total recoverable in the *dominant* unit so we never sum\n // apples & oranges.\n const metrics = useMemo(() => {\n const tally: Partial<Record<TheUnit, number>> = {};\n for (const r of metricsRows) tally[r.unit] = (tally[r.unit] ?? 0) + 1;\n const dominantUnit = (Object.entries(tally) as [TheUnit, number][]).sort(\n (a, b) => b[1] - a[1]\n )[0]?.[0];\n const totalRecoverable = dominantUnit\n ? metricsRows\n .filter((r) => r.unit === dominantUnit)\n .reduce((sum, r) => sum + r.recoverable, 0)\n : 0;\n const withFactor = metricsRows.filter((r) => r.recoveryFactor != null);\n const avgFactor = withFactor.length\n ? (withFactor.reduce((sum, r) => sum + (r.recoveryFactor ?? 0), 0) / withFactor.length) * 100\n : null;\n return { dominantUnit, totalRecoverable, avgFactor };\n }, [metricsRows]);\n\n const columns: Column<TheReserveEstimate>[] = [\n {\n key: 'asset',\n header: 'Asset',\n render: (r) => (\n <span className=\"font-semibold text-text-primary\">\n {assetMap[r.assetId] ?? <span className=\"text-text-tertiary\">Unknown asset</span>}\n </span>\n ),\n },\n {\n key: 'category',\n header: 'Category',\n render: (r) => <StatusBadge status={r.category} />,\n },\n {\n key: 'asOfDate',\n header: 'As of',\n hideOnMobile: true,\n sortValue: (r) => new Date(r.asOfDate).getTime(),\n render: (r) => <span className=\"text-text-secondary\">{formatDate(r.asOfDate)}</span>,\n },\n {\n key: 'recoverable',\n header: 'Recoverable',\n align: 'right',\n sortValue: (r) => r.recoverable,\n render: (r) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatVolume(r.recoverable, r.unit)}\n </span>\n ),\n },\n {\n key: 'recoveryFactor',\n header: 'Recovery factor',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.recoveryFactor ?? -1,\n render: (r) =>\n r.recoveryFactor != null ? (\n <span className=\"tabular-nums text-text-secondary\">\n {formatPercent(r.recoveryFactor * 100)}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'methodology',\n header: 'Methodology',\n hideOnMobile: true,\n render: (r) =>\n r.methodology ? (\n <span className=\"text-text-secondary\">{r.methodology}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n const openDialog = () => {\n setForm({ ...EMPTY_FORM, asOfDate: today() });\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(EMPTY_FORM);\n setFormError(null);\n };\n\n const handleCreate = async () => {\n if (!form.assetId) {\n setFormError('Select the asset this estimate belongs to.');\n return;\n }\n // Anchor the date-only input at noon UTC so the calendar day survives every TZ.\n const asOfDate = toBackendDate(form.asOfDate);\n if (!asOfDate) {\n setFormError('An \"as of\" date is required.');\n return;\n }\n const recoverable = Number(form.recoverable);\n if (!form.recoverable || Number.isNaN(recoverable) || recoverable <= 0) {\n const message = 'Recoverable volume must be greater than zero.';\n setFormError(message);\n notify.error(message);\n return;\n }\n const recoveryFactor = form.recoveryFactor ? Number(form.recoveryFactor) : null;\n if (recoveryFactor != null && (recoveryFactor < 0 || recoveryFactor > 1)) {\n setFormError('Recovery factor must be between 0 and 1.');\n return;\n }\n setFormError(null);\n try {\n await createEstimate.mutateAsync({\n assetId: form.assetId,\n category: form.category,\n asOfDate,\n recoverable,\n recoveryFactor,\n unit: form.unit,\n methodology: form.methodology.trim() || null,\n notes: form.notes.trim() || null,\n });\n notify.success('Reserve estimate created');\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to create reserve estimate.');\n notify.fromError(err);\n }\n };\n\n const noAssets = !assets.isLoading && (assets.data?.items.length ?? 0) === 0;\n\n return (\n <PageLayout\n title=\"Reserve Estimates\"\n description=\"Booked recoverable volumes per asset — proved, probable, possible, contingent & prospective.\"\n actions={\n <Button onClick={openDialog} disabled={noAssets}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add estimate\n </Button>\n }\n >\n {/* KPI strip */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Database className=\"h-5 w-5\" />}\n value={metricsRows.length}\n label=\"Reserve estimates\"\n featured\n />\n <MetricsCard\n icon={<Layers className=\"h-5 w-5\" />}\n value={\n metrics.dominantUnit\n ? formatVolume(metrics.totalRecoverable, metrics.dominantUnit)\n : '—'\n }\n label={\n metrics.dominantUnit\n ? `Total recoverable (${formatUnit(metrics.dominantUnit)})`\n : 'Total recoverable'\n }\n />\n <MetricsCard\n icon={<Percent className=\"h-5 w-5\" />}\n value={metrics.avgFactor != null ? formatPercent(metrics.avgFactor) : '—'}\n label=\"Avg recovery factor\"\n />\n </div>\n\n {/* Filters */}\n <div className=\"mt-6 mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={CATEGORY_FILTERS}\n value={category}\n onChange={(v) => {\n setCategory(v);\n pager.reset();\n }}\n />\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 {Object.entries(assetMap).map(([id, name]) => (\n <SelectItem key={id} value={id}>\n {name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading reserve estimates…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load reserve estimates.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Layers className=\"h-10 w-10\" />}\n title=\"No reserve estimates\"\n description={\n noAssets\n ? 'Register a resource asset first, then book its recoverable reserves here.'\n : category === 'ALL' && assetId === 'ALL'\n ? 'Book your first recoverable-reserves estimate against an asset.'\n : 'No estimates match these filters.'\n }\n action={\n !noAssets ? (\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Add estimate\n </Button>\n ) : undefined\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'asOfDate', 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 {/* Add reserve estimate dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => (open ? setDialogOpen(true) : closeDialog())}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Add reserve estimate</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Book a recoverable-reserves figure for an asset as of a valuation date.\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-1.5\">\n <Label htmlFor=\"reAsset\">Asset *</Label>\n <Select value={form.assetId} onValueChange={(v) => setForm({ ...form, assetId: v })}>\n <SelectTrigger id=\"reAsset\">\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(assetMap).map(([id, name]) => (\n <SelectItem key={id} value={id}>\n {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=\"reCategory\">Category</Label>\n <Select\n value={form.category}\n onValueChange={(v) => setForm({ ...form, category: v as TheReserveCategory })}\n >\n <SelectTrigger id=\"reCategory\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {RESERVE_CATEGORIES.map((c) => (\n <SelectItem key={c} value={c}>\n {formatEnumLabel(c)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"reAsOf\">As of date *</Label>\n <Input\n id=\"reAsOf\"\n type=\"date\"\n value={form.asOfDate}\n onChange={(e) => setForm({ ...form, asOfDate: e.target.value })}\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=\"reRecoverable\">Recoverable *</Label>\n <Input\n id=\"reRecoverable\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.recoverable}\n onChange={(e) => setForm({ ...form, recoverable: e.target.value })}\n placeholder=\"1200000\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"reUnit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"reUnit\">\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=\"reFactor\">Recovery factor</Label>\n <Input\n id=\"reFactor\"\n type=\"number\"\n min=\"0\"\n max=\"1\"\n step=\"any\"\n value={form.recoveryFactor}\n onChange={(e) => setForm({ ...form, recoveryFactor: e.target.value })}\n placeholder=\"0.35\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"reMethodology\">Methodology</Label>\n <Input\n id=\"reMethodology\"\n value={form.methodology}\n onChange={(e) => setForm({ ...form, methodology: e.target.value })}\n placeholder=\"Volumetric / decline curve / PRMS deterministic\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"reNotes\">Notes</Label>\n <Textarea\n id=\"reNotes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this estimate.\"\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={createEstimate.isPending}>\n {createEstimate.isPending ? 'Saving…' : 'Add estimate'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -10,8 +10,8 @@ import { useCursorPager as o } from "../../hooks/useCursorPager.js";
|
|
|
10
10
|
import { useNotify as ue } from "../../hooks/useNotify.js";
|
|
11
11
|
import "../../hooks/index.js";
|
|
12
12
|
import { SHIPMENT_DELIVERABLE_FROM as de, SHIPMENT_TRANSITIONS as fe, nextStatuses as pe } from "../../utils/transitions.js";
|
|
13
|
-
import { FilterTabs as
|
|
14
|
-
import { Pagination as
|
|
13
|
+
import { FilterTabs as s } from "../../components/FilterTabs.js";
|
|
14
|
+
import { Pagination as me } from "../../components/Pagination.js";
|
|
15
15
|
import { EvidenceUpload as he } from "../../components/EvidenceUpload.js";
|
|
16
16
|
import { FileLink as ge } from "../../components/FileLink.js";
|
|
17
17
|
import { useMemo as c, useState as l } from "react";
|
|
@@ -74,7 +74,7 @@ var T = [
|
|
|
74
74
|
function A() {
|
|
75
75
|
let [A, Ee] = l("ALL"), [j, De] = l("ALL"), M = o(), [Oe, N] = l(!1), [P, F] = l(k), [I, L] = l(null), [R, z] = l(null), [ke, B] = l(null), V = ue(), H = j === "ALL" ? void 0 : j, { data: U, isLoading: Ae, isError: je, refetch: Me } = a(H, A === "ALL" ? void 0 : A, {
|
|
76
76
|
limit: O,
|
|
77
|
-
|
|
77
|
+
offset: M.page * O
|
|
78
78
|
}), W = a(H, void 0, { limit: 200 }), G = oe({ pagination: { limit: 100 } }), K = c(() => G.data?.items ?? [], [G.data]), q = c(() => {
|
|
79
79
|
let e = /* @__PURE__ */ new Map();
|
|
80
80
|
for (let t of K) e.set(t.id, t);
|
|
@@ -310,7 +310,7 @@ function A() {
|
|
|
310
310
|
}),
|
|
311
311
|
/* @__PURE__ */ h("div", {
|
|
312
312
|
className: "mb-5 flex flex-wrap items-center gap-3",
|
|
313
|
-
children: [/* @__PURE__ */ m(
|
|
313
|
+
children: [/* @__PURE__ */ m(s, {
|
|
314
314
|
options: Re,
|
|
315
315
|
value: A,
|
|
316
316
|
onChange: (e) => {
|
|
@@ -358,12 +358,12 @@ function A() {
|
|
|
358
358
|
key: "scheduledAt",
|
|
359
359
|
dir: "desc"
|
|
360
360
|
}
|
|
361
|
-
}), /* @__PURE__ */ m(
|
|
361
|
+
}), /* @__PURE__ */ m(me, {
|
|
362
362
|
page: M.page,
|
|
363
363
|
pageSize: O,
|
|
364
364
|
itemCount: X.length,
|
|
365
365
|
hasMore: U?.pageInfo?.hasMore ?? !1,
|
|
366
|
-
onPageChange:
|
|
366
|
+
onPageChange: M.onPageChange
|
|
367
367
|
})] }),
|
|
368
368
|
/* @__PURE__ */ m(be, {
|
|
369
369
|
open: Oe,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShipmentsPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/shipments/ShipmentsPage.tsx"],"sourcesContent":["/**\n * Shipments — schedule and track cargo movements against offtake contracts.\n * @module theglobalfuel/pages/shipments\n *\n * Reads `theListShipments` (a cursor connection — `{ items, pageInfo }`, filtered\n * by contract + status) and `theListContracts` (to resolve contract references +\n * populate the picker); writes `theScheduleShipment`, `theDeliverShipment` and\n * lifecycle transitions via `theTransitionShipment`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Ship, CalendarClock, Truck, PackageCheck, ArrowRight } 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 { FileLink } from '../../components/FileLink';\nimport { EvidenceUpload } from '../../components/EvidenceUpload';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useNotify,\n useTheShipments,\n useTheContracts,\n useTheScheduleShipment,\n useTheDeliverShipment,\n useTheTransitionShipment,\n useCursorPager,\n type UploadedTheFile,\n} from '../../hooks';\nimport { formatEnumLabel, formatDate, formatVolume } from '../../utils/formatters';\nimport {\n toBackendDate,\n SHIPMENT_TRANSITIONS,\n SHIPMENT_DELIVERABLE_FROM,\n nextStatuses,\n} from '../../utils';\nimport type {\n TheShipment,\n TheShipmentStatus,\n TheTransportMode,\n TheUnit,\n TheOfftakeContract,\n} from '../../types';\n\nconst STATUS_FILTERS: Array<{ value: TheShipmentStatus | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'LOADING', label: 'Loading' },\n { value: 'IN_TRANSIT', label: 'In transit' },\n { value: 'DELIVERED', label: 'Delivered' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nconst TRANSPORT_MODES: TheTransportMode[] = [\n 'PIPELINE',\n 'TANKER',\n 'VESSEL_BULK',\n 'RAIL',\n 'TRUCK',\n 'GRID',\n];\n\nconst UNITS: TheUnit[] = ['BARRELS', 'CUBIC_METERS', 'TONNES', 'KILOGRAMS', 'MWH', 'GJ', 'MCF'];\n\nconst PAGE_SIZE = 25;\n\ninterface ScheduleFormState {\n contractId: string;\n reference: string;\n mode: TheTransportMode;\n carrier: string;\n origin: string;\n destination: string;\n volume: string;\n unit: TheUnit;\n scheduledAt: string;\n notes: string;\n}\n\nconst EMPTY_FORM: ScheduleFormState = {\n contractId: '',\n reference: '',\n mode: 'TANKER',\n carrier: '',\n origin: '',\n destination: '',\n volume: '',\n unit: 'BARRELS',\n scheduledAt: '',\n notes: '',\n};\n\nexport function ShipmentsPage() {\n const [status, setStatus] = useState<TheShipmentStatus | 'ALL'>('ALL');\n const [contractId, setContractId] = useState<string | 'ALL'>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<ScheduleFormState>(EMPTY_FORM);\n const [doc, setDoc] = useState<UploadedTheFile | null>(null);\n const [formError, setFormError] = useState<string | null>(null);\n // Track the shipment whose transition is in flight so only that row's Select\n // disables (transitionShipment.isPending is shared across all rows).\n const [transitioningId, setTransitioningId] = useState<string | null>(null);\n\n const notify = useNotify();\n\n const scopedContractId = contractId === 'ALL' ? undefined : contractId;\n\n // Table query — respects both the contract scope and the status filter, and is\n // cursor-paginated to PAGE_SIZE.\n const { data, isLoading, isError, refetch } = useTheShipments(\n scopedContractId,\n status === 'ALL' ? undefined : status,\n { limit: PAGE_SIZE, cursor: pager.cursor }\n );\n // Metrics query — same contract scope but ignores the status filter so the\n // header counts stay stable as the table is narrowed. A broad limit keeps the\n // KPI status mix representative (the table itself is paginated to PAGE_SIZE).\n const metricsQuery = useTheShipments(scopedContractId, undefined, { limit: 200 });\n\n // Contracts drive the picker + let us resolve a shipment's contract reference.\n const contractsQuery = useTheContracts({ pagination: { limit: 100 } });\n const contractItems = useMemo(\n () => contractsQuery.data?.items ?? [],\n [contractsQuery.data]\n );\n const contractMap = useMemo(() => {\n const map = new Map<string, TheOfftakeContract>();\n for (const c of contractItems) map.set(c.id, c);\n return map;\n }, [contractItems]);\n\n const scheduleShipment = useTheScheduleShipment();\n const deliverShipment = useTheDeliverShipment();\n const transitionShipment = useTheTransitionShipment();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n const metricsRows = useMemo(() => metricsQuery.data?.items ?? [], [metricsQuery.data]);\n\n // ── Header metrics (derived from the contract-scoped, status-agnostic list) ──\n const scheduledCount = metricsRows.filter((s) => s.status === 'SCHEDULED').length;\n const inTransitCount = metricsRows.filter((s) => s.status === 'IN_TRANSIT').length;\n const deliveredCount = metricsRows.filter((s) => s.status === 'DELIVERED').length;\n\n const statusCounts = useMemo(() => {\n const counts: Record<string, number> = {};\n for (const s of metricsRows) counts[s.status] = (counts[s.status] ?? 0) + 1;\n return counts;\n }, [metricsRows]);\n\n const filterOptions: FilterOption<TheShipmentStatus | 'ALL'>[] = STATUS_FILTERS.map((opt) => ({\n value: opt.value,\n label: opt.label,\n count: opt.value === 'ALL' ? metricsRows.length : (statusCounts[opt.value] ?? 0),\n }));\n\n const resetForm = () => {\n setForm(EMPTY_FORM);\n setDoc(null);\n setFormError(null);\n };\n\n const handleSchedule = async () => {\n if (!form.contractId) {\n setFormError('Select the contract this shipment fulfils.');\n return;\n }\n if (!form.reference.trim()) {\n setFormError('A shipment reference is required.');\n return;\n }\n const volume = Number(form.volume);\n if (!form.volume || Number.isNaN(volume) || volume <= 0) {\n setFormError('Enter a volume greater than zero.');\n notify.error('Enter a volume greater than zero.');\n return;\n }\n setFormError(null);\n try {\n await scheduleShipment.mutateAsync({\n contractId: form.contractId,\n reference: form.reference.trim(),\n mode: form.mode,\n carrier: form.carrier.trim() || null,\n origin: form.origin.trim() || null,\n destination: form.destination.trim() || null,\n volume,\n unit: form.unit,\n scheduledAt: toBackendDate(form.scheduledAt) ?? null,\n documentFileId: doc?.fileId ?? null,\n notes: form.notes.trim() || null,\n });\n notify.success('Shipment scheduled');\n setDialogOpen(false);\n resetForm();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to schedule shipment.');\n notify.fromError(err);\n }\n };\n\n const columns: Column<TheShipment>[] = [\n {\n key: 'reference',\n header: 'Shipment',\n render: (s) => (\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{s.reference}</p>\n <p className=\"truncate text-xs text-text-tertiary md:hidden\">{formatEnumLabel(s.mode)}</p>\n </div>\n ),\n },\n {\n key: 'contract',\n header: 'Contract',\n hideOnMobile: true,\n render: (s) => {\n const contract = contractMap.get(s.contractId);\n return contract ? (\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{contract.reference}</p>\n <p className=\"truncate text-xs text-text-tertiary\">{contract.counterparty}</p>\n </div>\n ) : (\n <span className=\"font-mono text-xs text-text-tertiary\">{s.contractId.slice(0, 8)}</span>\n );\n },\n },\n {\n key: 'mode',\n header: 'Mode',\n hideOnMobile: true,\n render: (s) => <span className=\"text-text-secondary\">{formatEnumLabel(s.mode)}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'route',\n header: 'Route',\n hideOnMobile: true,\n render: (s) => {\n const origin = s.origin?.trim();\n const destination = s.destination?.trim();\n if (!origin && !destination) return <span className=\"text-text-tertiary\">—</span>;\n return (\n <span className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <span className=\"truncate\">{origin ?? '—'}</span>\n <ArrowRight className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" />\n <span className=\"truncate\">{destination ?? '—'}</span>\n </span>\n );\n },\n },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n sortValue: (s) => s.volume,\n render: (s) => (\n <span className=\"tabular-nums text-text-primary\">{formatVolume(s.volume, s.unit)}</span>\n ),\n },\n {\n key: 'scheduledAt',\n header: 'Scheduled',\n align: 'right',\n hideOnMobile: true,\n sortValue: (s) => s.scheduledAt ?? '',\n render: (s) => <span className=\"text-text-tertiary\">{formatDate(s.scheduledAt)}</span>,\n },\n {\n key: 'document',\n header: 'Document',\n hideOnMobile: true,\n render: (s) =>\n s.document?.id ? (\n <FileLink fileId={s.document.id} />\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'action',\n header: '',\n align: 'right',\n render: (s) => {\n // Lifecycle transitions reachable via theTransitionShipment (SCHEDULED →\n // LOADING → IN_TRANSIT → CANCELLED). DELIVERED is NOT offered here — it is\n // reached through \"Mark delivered\", which additionally rolls volume into\n // the contract. Terminal states (DELIVERED / CANCELLED) expose neither.\n const nexts = nextStatuses(SHIPMENT_TRANSITIONS, s.status);\n const canDeliver = SHIPMENT_DELIVERABLE_FROM.includes(s.status);\n const rowBusy = transitioningId === s.id;\n if (nexts.length === 0 && !canDeliver) {\n return <span className=\"text-text-tertiary\">—</span>;\n }\n return (\n <div className=\"flex items-center justify-end gap-2\">\n {nexts.length > 0 && (\n <Select\n value={s.status}\n disabled={rowBusy}\n onValueChange={(v) => {\n if (v === s.status) return;\n setTransitioningId(s.id);\n transitionShipment.mutate(\n { id: s.id, status: v as TheShipmentStatus },\n {\n onSuccess: () => notify.success('Shipment updated'),\n onError: (e) => notify.fromError(e),\n onSettled: () => setTransitioningId(null),\n }\n );\n }}\n >\n <SelectTrigger\n className=\"h-8 w-36\"\n aria-label={`Change status for shipment ${s.reference}`}\n >\n <SelectValue placeholder={formatEnumLabel(s.status)} />\n </SelectTrigger>\n <SelectContent>\n {nexts.map((st) => (\n <SelectItem key={st} value={st}>\n {formatEnumLabel(st)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n {canDeliver && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={deliverShipment.isPending || rowBusy}\n onClick={() =>\n deliverShipment.mutate(s.id, {\n onSuccess: () => notify.success('Shipment delivered'),\n onError: (e) => notify.fromError(e),\n })\n }\n >\n <PackageCheck className=\"mr-1.5 h-4 w-4\" /> Mark delivered\n </Button>\n )}\n </div>\n );\n },\n },\n ];\n\n const isFiltered = status !== 'ALL' || contractId !== 'ALL';\n\n return (\n <PageLayout\n title=\"Shipments\"\n description=\"Cargo movements delivering offtake contracts — pipelines, tankers, vessels, rail & grid.\"\n actions={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Schedule shipment\n </Button>\n }\n >\n {/* Header metrics — status mix within the current contract scope. */}\n <div className=\"mb-6 grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n featured\n icon={<Ship className=\"h-5 w-5\" />}\n value={metricsRows.length}\n label=\"Total shipments\"\n />\n <MetricsCard\n icon={<CalendarClock className=\"h-5 w-5\" />}\n value={scheduledCount}\n label=\"Scheduled\"\n />\n <MetricsCard\n accent=\"warning\"\n icon={<Truck className=\"h-5 w-5\" />}\n value={inTransitCount}\n label=\"In transit\"\n />\n <MetricsCard\n accent=\"success\"\n icon={<PackageCheck className=\"h-5 w-5\" />}\n value={deliveredCount}\n label=\"Delivered\"\n />\n </div>\n\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={filterOptions}\n value={status}\n onChange={(v) => {\n setStatus(v);\n pager.reset();\n }}\n />\n <Select\n value={contractId}\n onValueChange={(v) => {\n setContractId(v as string | 'ALL');\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-56\" aria-label=\"Filter by contract\">\n <SelectValue placeholder=\"All contracts\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All contracts</SelectItem>\n {contractItems.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.reference} · {c.counterparty}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading shipments…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load shipments.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Ship className=\"h-10 w-10\" />}\n title={isFiltered ? 'No matching shipments' : 'No shipments yet'}\n description={\n isFiltered\n ? 'No shipments match these filters. Try a different status or contract.'\n : 'Schedule your first shipment to start tracking cargo against your offtake contracts.'\n }\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Schedule shipment\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n defaultSort={{ key: 'scheduledAt', 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={(p) => pager.onPageChange(p, data?.pageInfo?.cursor)}\n />\n </>\n )}\n\n {/* Schedule shipment dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open);\n if (!open) resetForm();\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Schedule shipment</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Book a cargo movement against an offtake contract and track it to delivery.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleSchedule();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipContract\">Contract *</Label>\n <Select\n value={form.contractId}\n onValueChange={(v) => {\n // Default the unit to the contract's unit for a sensible start.\n const contract = contractMap.get(v);\n setForm((prev) => ({\n ...prev,\n contractId: v,\n unit: contract?.unit ?? prev.unit,\n }));\n }}\n >\n <SelectTrigger id=\"shipContract\">\n <SelectValue placeholder=\"Select a contract\" />\n </SelectTrigger>\n <SelectContent>\n {contractItems.length === 0 ? (\n <SelectItem value=\"none\" disabled>\n No contracts available\n </SelectItem>\n ) : (\n contractItems.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.reference} · {c.counterparty}\n </SelectItem>\n ))\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=\"shipRef\">Reference *</Label>\n <Input\n id=\"shipRef\"\n value={form.reference}\n onChange={(e) => setForm({ ...form, reference: e.target.value })}\n placeholder=\"SHP-2026-0142\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipMode\">Mode</Label>\n <Select\n value={form.mode}\n onValueChange={(v) => setForm({ ...form, mode: v as TheTransportMode })}\n >\n <SelectTrigger id=\"shipMode\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {TRANSPORT_MODES.map((m) => (\n <SelectItem key={m} value={m}>\n {formatEnumLabel(m)}\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=\"shipOrigin\">Origin</Label>\n <Input\n id=\"shipOrigin\"\n value={form.origin}\n onChange={(e) => setForm({ ...form, origin: e.target.value })}\n placeholder=\"Ras Tanura Terminal\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipDest\">Destination</Label>\n <Input\n id=\"shipDest\"\n value={form.destination}\n onChange={(e) => setForm({ ...form, destination: e.target.value })}\n placeholder=\"Rotterdam\"\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=\"shipVolume\">Volume *</Label>\n <Input\n id=\"shipVolume\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.volume}\n onChange={(e) => setForm({ ...form, volume: e.target.value })}\n placeholder=\"500000\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipUnit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"shipUnit\">\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 className=\"grid gap-1.5\">\n <Label htmlFor=\"shipScheduled\">Scheduled date</Label>\n <Input\n id=\"shipScheduled\"\n type=\"date\"\n value={form.scheduledAt}\n onChange={(e) => setForm({ ...form, scheduledAt: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipCarrier\">Carrier</Label>\n <Input\n id=\"shipCarrier\"\n value={form.carrier}\n onChange={(e) => setForm({ ...form, carrier: e.target.value })}\n placeholder=\"Frontline Ltd.\"\n />\n </div>\n\n <EvidenceUpload\n id=\"shipDocument\"\n label=\"Bill of lading (optional)\"\n value={doc}\n onChange={setDoc}\n hint=\"Attach the bill of lading or shipping document (PDF or image).\"\n />\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipNotes\">Notes</Label>\n <Textarea\n id=\"shipNotes\"\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context — laycan window, inspector, terms.\"\n rows={2}\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={scheduleShipment.isPending}>\n {scheduleShipment.isPending ? 'Scheduling…' : 'Schedule shipment'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ShipmentsPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/shipments/ShipmentsPage.tsx"],"sourcesContent":["/**\n * Shipments — schedule and track cargo movements against offtake contracts.\n * @module theglobalfuel/pages/shipments\n *\n * Reads `theListShipments` (a cursor connection — `{ items, pageInfo }`, filtered\n * by contract + status) and `theListContracts` (to resolve contract references +\n * populate the picker); writes `theScheduleShipment`, `theDeliverShipment` and\n * lifecycle transitions via `theTransitionShipment`.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, Ship, CalendarClock, Truck, PackageCheck, ArrowRight } 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 { FileLink } from '../../components/FileLink';\nimport { EvidenceUpload } from '../../components/EvidenceUpload';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useNotify,\n useTheShipments,\n useTheContracts,\n useTheScheduleShipment,\n useTheDeliverShipment,\n useTheTransitionShipment,\n useCursorPager,\n type UploadedTheFile,\n} from '../../hooks';\nimport { formatEnumLabel, formatDate, formatVolume } from '../../utils/formatters';\nimport {\n toBackendDate,\n SHIPMENT_TRANSITIONS,\n SHIPMENT_DELIVERABLE_FROM,\n nextStatuses,\n} from '../../utils';\nimport type {\n TheShipment,\n TheShipmentStatus,\n TheTransportMode,\n TheUnit,\n TheOfftakeContract,\n} from '../../types';\n\nconst STATUS_FILTERS: Array<{ value: TheShipmentStatus | 'ALL'; label: string }> = [\n { value: 'ALL', label: 'All' },\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'LOADING', label: 'Loading' },\n { value: 'IN_TRANSIT', label: 'In transit' },\n { value: 'DELIVERED', label: 'Delivered' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nconst TRANSPORT_MODES: TheTransportMode[] = [\n 'PIPELINE',\n 'TANKER',\n 'VESSEL_BULK',\n 'RAIL',\n 'TRUCK',\n 'GRID',\n];\n\nconst UNITS: TheUnit[] = ['BARRELS', 'CUBIC_METERS', 'TONNES', 'KILOGRAMS', 'MWH', 'GJ', 'MCF'];\n\nconst PAGE_SIZE = 25;\n\ninterface ScheduleFormState {\n contractId: string;\n reference: string;\n mode: TheTransportMode;\n carrier: string;\n origin: string;\n destination: string;\n volume: string;\n unit: TheUnit;\n scheduledAt: string;\n notes: string;\n}\n\nconst EMPTY_FORM: ScheduleFormState = {\n contractId: '',\n reference: '',\n mode: 'TANKER',\n carrier: '',\n origin: '',\n destination: '',\n volume: '',\n unit: 'BARRELS',\n scheduledAt: '',\n notes: '',\n};\n\nexport function ShipmentsPage() {\n const [status, setStatus] = useState<TheShipmentStatus | 'ALL'>('ALL');\n const [contractId, setContractId] = useState<string | 'ALL'>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<ScheduleFormState>(EMPTY_FORM);\n const [doc, setDoc] = useState<UploadedTheFile | null>(null);\n const [formError, setFormError] = useState<string | null>(null);\n // Track the shipment whose transition is in flight so only that row's Select\n // disables (transitionShipment.isPending is shared across all rows).\n const [transitioningId, setTransitioningId] = useState<string | null>(null);\n\n const notify = useNotify();\n\n const scopedContractId = contractId === 'ALL' ? undefined : contractId;\n\n // Table query — respects both the contract scope and the status filter, and is\n // cursor-paginated to PAGE_SIZE.\n const { data, isLoading, isError, refetch } = useTheShipments(\n scopedContractId,\n status === 'ALL' ? undefined : status,\n { limit: PAGE_SIZE, offset: pager.page * PAGE_SIZE }\n );\n // Metrics query — same contract scope but ignores the status filter so the\n // header counts stay stable as the table is narrowed. A broad limit keeps the\n // KPI status mix representative (the table itself is paginated to PAGE_SIZE).\n const metricsQuery = useTheShipments(scopedContractId, undefined, { limit: 200 });\n\n // Contracts drive the picker + let us resolve a shipment's contract reference.\n const contractsQuery = useTheContracts({ pagination: { limit: 100 } });\n const contractItems = useMemo(\n () => contractsQuery.data?.items ?? [],\n [contractsQuery.data]\n );\n const contractMap = useMemo(() => {\n const map = new Map<string, TheOfftakeContract>();\n for (const c of contractItems) map.set(c.id, c);\n return map;\n }, [contractItems]);\n\n const scheduleShipment = useTheScheduleShipment();\n const deliverShipment = useTheDeliverShipment();\n const transitionShipment = useTheTransitionShipment();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n const metricsRows = useMemo(() => metricsQuery.data?.items ?? [], [metricsQuery.data]);\n\n // ── Header metrics (derived from the contract-scoped, status-agnostic list) ──\n const scheduledCount = metricsRows.filter((s) => s.status === 'SCHEDULED').length;\n const inTransitCount = metricsRows.filter((s) => s.status === 'IN_TRANSIT').length;\n const deliveredCount = metricsRows.filter((s) => s.status === 'DELIVERED').length;\n\n const statusCounts = useMemo(() => {\n const counts: Record<string, number> = {};\n for (const s of metricsRows) counts[s.status] = (counts[s.status] ?? 0) + 1;\n return counts;\n }, [metricsRows]);\n\n const filterOptions: FilterOption<TheShipmentStatus | 'ALL'>[] = STATUS_FILTERS.map((opt) => ({\n value: opt.value,\n label: opt.label,\n count: opt.value === 'ALL' ? metricsRows.length : (statusCounts[opt.value] ?? 0),\n }));\n\n const resetForm = () => {\n setForm(EMPTY_FORM);\n setDoc(null);\n setFormError(null);\n };\n\n const handleSchedule = async () => {\n if (!form.contractId) {\n setFormError('Select the contract this shipment fulfils.');\n return;\n }\n if (!form.reference.trim()) {\n setFormError('A shipment reference is required.');\n return;\n }\n const volume = Number(form.volume);\n if (!form.volume || Number.isNaN(volume) || volume <= 0) {\n setFormError('Enter a volume greater than zero.');\n notify.error('Enter a volume greater than zero.');\n return;\n }\n setFormError(null);\n try {\n await scheduleShipment.mutateAsync({\n contractId: form.contractId,\n reference: form.reference.trim(),\n mode: form.mode,\n carrier: form.carrier.trim() || null,\n origin: form.origin.trim() || null,\n destination: form.destination.trim() || null,\n volume,\n unit: form.unit,\n scheduledAt: toBackendDate(form.scheduledAt) ?? null,\n documentFileId: doc?.fileId ?? null,\n notes: form.notes.trim() || null,\n });\n notify.success('Shipment scheduled');\n setDialogOpen(false);\n resetForm();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to schedule shipment.');\n notify.fromError(err);\n }\n };\n\n const columns: Column<TheShipment>[] = [\n {\n key: 'reference',\n header: 'Shipment',\n render: (s) => (\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{s.reference}</p>\n <p className=\"truncate text-xs text-text-tertiary md:hidden\">{formatEnumLabel(s.mode)}</p>\n </div>\n ),\n },\n {\n key: 'contract',\n header: 'Contract',\n hideOnMobile: true,\n render: (s) => {\n const contract = contractMap.get(s.contractId);\n return contract ? (\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-text-primary\">{contract.reference}</p>\n <p className=\"truncate text-xs text-text-tertiary\">{contract.counterparty}</p>\n </div>\n ) : (\n <span className=\"font-mono text-xs text-text-tertiary\">{s.contractId.slice(0, 8)}</span>\n );\n },\n },\n {\n key: 'mode',\n header: 'Mode',\n hideOnMobile: true,\n render: (s) => <span className=\"text-text-secondary\">{formatEnumLabel(s.mode)}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n render: (s) => <StatusBadge status={s.status} />,\n },\n {\n key: 'route',\n header: 'Route',\n hideOnMobile: true,\n render: (s) => {\n const origin = s.origin?.trim();\n const destination = s.destination?.trim();\n if (!origin && !destination) return <span className=\"text-text-tertiary\">—</span>;\n return (\n <span className=\"inline-flex items-center gap-1.5 text-text-secondary\">\n <span className=\"truncate\">{origin ?? '—'}</span>\n <ArrowRight className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" />\n <span className=\"truncate\">{destination ?? '—'}</span>\n </span>\n );\n },\n },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n sortValue: (s) => s.volume,\n render: (s) => (\n <span className=\"tabular-nums text-text-primary\">{formatVolume(s.volume, s.unit)}</span>\n ),\n },\n {\n key: 'scheduledAt',\n header: 'Scheduled',\n align: 'right',\n hideOnMobile: true,\n sortValue: (s) => s.scheduledAt ?? '',\n render: (s) => <span className=\"text-text-tertiary\">{formatDate(s.scheduledAt)}</span>,\n },\n {\n key: 'document',\n header: 'Document',\n hideOnMobile: true,\n render: (s) =>\n s.document?.id ? (\n <FileLink fileId={s.document.id} />\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'action',\n header: '',\n align: 'right',\n render: (s) => {\n // Lifecycle transitions reachable via theTransitionShipment (SCHEDULED →\n // LOADING → IN_TRANSIT → CANCELLED). DELIVERED is NOT offered here — it is\n // reached through \"Mark delivered\", which additionally rolls volume into\n // the contract. Terminal states (DELIVERED / CANCELLED) expose neither.\n const nexts = nextStatuses(SHIPMENT_TRANSITIONS, s.status);\n const canDeliver = SHIPMENT_DELIVERABLE_FROM.includes(s.status);\n const rowBusy = transitioningId === s.id;\n if (nexts.length === 0 && !canDeliver) {\n return <span className=\"text-text-tertiary\">—</span>;\n }\n return (\n <div className=\"flex items-center justify-end gap-2\">\n {nexts.length > 0 && (\n <Select\n value={s.status}\n disabled={rowBusy}\n onValueChange={(v) => {\n if (v === s.status) return;\n setTransitioningId(s.id);\n transitionShipment.mutate(\n { id: s.id, status: v as TheShipmentStatus },\n {\n onSuccess: () => notify.success('Shipment updated'),\n onError: (e) => notify.fromError(e),\n onSettled: () => setTransitioningId(null),\n }\n );\n }}\n >\n <SelectTrigger\n className=\"h-8 w-36\"\n aria-label={`Change status for shipment ${s.reference}`}\n >\n <SelectValue placeholder={formatEnumLabel(s.status)} />\n </SelectTrigger>\n <SelectContent>\n {nexts.map((st) => (\n <SelectItem key={st} value={st}>\n {formatEnumLabel(st)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n {canDeliver && (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled={deliverShipment.isPending || rowBusy}\n onClick={() =>\n deliverShipment.mutate(s.id, {\n onSuccess: () => notify.success('Shipment delivered'),\n onError: (e) => notify.fromError(e),\n })\n }\n >\n <PackageCheck className=\"mr-1.5 h-4 w-4\" /> Mark delivered\n </Button>\n )}\n </div>\n );\n },\n },\n ];\n\n const isFiltered = status !== 'ALL' || contractId !== 'ALL';\n\n return (\n <PageLayout\n title=\"Shipments\"\n description=\"Cargo movements delivering offtake contracts — pipelines, tankers, vessels, rail & grid.\"\n actions={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Schedule shipment\n </Button>\n }\n >\n {/* Header metrics — status mix within the current contract scope. */}\n <div className=\"mb-6 grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n featured\n icon={<Ship className=\"h-5 w-5\" />}\n value={metricsRows.length}\n label=\"Total shipments\"\n />\n <MetricsCard\n icon={<CalendarClock className=\"h-5 w-5\" />}\n value={scheduledCount}\n label=\"Scheduled\"\n />\n <MetricsCard\n accent=\"warning\"\n icon={<Truck className=\"h-5 w-5\" />}\n value={inTransitCount}\n label=\"In transit\"\n />\n <MetricsCard\n accent=\"success\"\n icon={<PackageCheck className=\"h-5 w-5\" />}\n value={deliveredCount}\n label=\"Delivered\"\n />\n </div>\n\n <div className=\"mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={filterOptions}\n value={status}\n onChange={(v) => {\n setStatus(v);\n pager.reset();\n }}\n />\n <Select\n value={contractId}\n onValueChange={(v) => {\n setContractId(v as string | 'ALL');\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-56\" aria-label=\"Filter by contract\">\n <SelectValue placeholder=\"All contracts\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All contracts</SelectItem>\n {contractItems.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.reference} · {c.counterparty}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading shipments…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load shipments.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Ship className=\"h-10 w-10\" />}\n title={isFiltered ? 'No matching shipments' : 'No shipments yet'}\n description={\n isFiltered\n ? 'No shipments match these filters. Try a different status or contract.'\n : 'Schedule your first shipment to start tracking cargo against your offtake contracts.'\n }\n action={\n <Button onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Schedule shipment\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(s) => s.id}\n defaultSort={{ key: 'scheduledAt', 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 {/* Schedule shipment dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open);\n if (!open) resetForm();\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Schedule shipment</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Book a cargo movement against an offtake contract and track it to delivery.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleSchedule();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipContract\">Contract *</Label>\n <Select\n value={form.contractId}\n onValueChange={(v) => {\n // Default the unit to the contract's unit for a sensible start.\n const contract = contractMap.get(v);\n setForm((prev) => ({\n ...prev,\n contractId: v,\n unit: contract?.unit ?? prev.unit,\n }));\n }}\n >\n <SelectTrigger id=\"shipContract\">\n <SelectValue placeholder=\"Select a contract\" />\n </SelectTrigger>\n <SelectContent>\n {contractItems.length === 0 ? (\n <SelectItem value=\"none\" disabled>\n No contracts available\n </SelectItem>\n ) : (\n contractItems.map((c) => (\n <SelectItem key={c.id} value={c.id}>\n {c.reference} · {c.counterparty}\n </SelectItem>\n ))\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=\"shipRef\">Reference *</Label>\n <Input\n id=\"shipRef\"\n value={form.reference}\n onChange={(e) => setForm({ ...form, reference: e.target.value })}\n placeholder=\"SHP-2026-0142\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipMode\">Mode</Label>\n <Select\n value={form.mode}\n onValueChange={(v) => setForm({ ...form, mode: v as TheTransportMode })}\n >\n <SelectTrigger id=\"shipMode\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {TRANSPORT_MODES.map((m) => (\n <SelectItem key={m} value={m}>\n {formatEnumLabel(m)}\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=\"shipOrigin\">Origin</Label>\n <Input\n id=\"shipOrigin\"\n value={form.origin}\n onChange={(e) => setForm({ ...form, origin: e.target.value })}\n placeholder=\"Ras Tanura Terminal\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipDest\">Destination</Label>\n <Input\n id=\"shipDest\"\n value={form.destination}\n onChange={(e) => setForm({ ...form, destination: e.target.value })}\n placeholder=\"Rotterdam\"\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=\"shipVolume\">Volume *</Label>\n <Input\n id=\"shipVolume\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.volume}\n onChange={(e) => setForm({ ...form, volume: e.target.value })}\n placeholder=\"500000\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipUnit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"shipUnit\">\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 className=\"grid gap-1.5\">\n <Label htmlFor=\"shipScheduled\">Scheduled date</Label>\n <Input\n id=\"shipScheduled\"\n type=\"date\"\n value={form.scheduledAt}\n onChange={(e) => setForm({ ...form, scheduledAt: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipCarrier\">Carrier</Label>\n <Input\n id=\"shipCarrier\"\n value={form.carrier}\n onChange={(e) => setForm({ ...form, carrier: e.target.value })}\n placeholder=\"Frontline Ltd.\"\n />\n </div>\n\n <EvidenceUpload\n id=\"shipDocument\"\n label=\"Bill of lading (optional)\"\n value={doc}\n onChange={setDoc}\n hint=\"Attach the bill of lading or shipping document (PDF or image).\"\n />\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"shipNotes\">Notes</Label>\n <Textarea\n id=\"shipNotes\"\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context — laycan window, inspector, terms.\"\n rows={2}\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={scheduleShipment.isPending}>\n {scheduleShipment.isPending ? 'Scheduling…' : 'Schedule shipment'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAgEA,IAAM,IAA6E;CACjF;EAAE,OAAO;EAAO,OAAO;EAAO;CAC9B;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAc,OAAO;EAAc;CAC5C;EAAE,OAAO;EAAa,OAAO;EAAa;CAC1C;EAAE,OAAO;EAAa,OAAO;EAAa;CAC3C,EAEK,IAAsC;CAC1C;CACA;CACA;CACA;CACA;CACA;CACD,EAEK,IAAmB;CAAC;CAAW;CAAgB;CAAU;CAAa;CAAO;CAAM;CAAM,EAEzF,IAAY,IAeZ,IAAgC;CACpC,YAAY;CACZ,WAAW;CACX,MAAM;CACN,SAAS;CACT,QAAQ;CACR,aAAa;CACb,QAAQ;CACR,MAAM;CACN,aAAa;CACb,OAAO;CACR;AAED,SAAgB,IAAgB;CAC9B,IAAM,CAAC,GAAQ,MAAa,EAAoC,MAAM,EAChE,CAAC,GAAY,MAAiB,EAAyB,MAAM,EAC7D,IAAQ,GAAgB,EACxB,CAAC,IAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAM,KAAW,EAA4B,EAAW,EACzD,CAAC,GAAK,KAAU,EAAiC,KAAK,EACtD,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAGzD,CAAC,IAAiB,KAAsB,EAAwB,KAAK,EAErE,IAAS,IAAW,EAEpB,IAAmB,MAAe,QAAQ,KAAA,IAAY,GAItD,EAAE,SAAM,eAAW,aAAS,gBAAY,EAC5C,GACA,MAAW,QAAQ,KAAA,IAAY,GAC/B;EAAE,OAAO;EAAW,QAAQ,EAAM,OAAO;EAAW,CACrD,EAIK,IAAe,EAAgB,GAAkB,KAAA,GAAW,EAAE,OAAO,KAAK,CAAC,EAG3E,IAAiB,GAAgB,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE,CAAC,EAChE,IAAgB,QACd,EAAe,MAAM,SAAS,EAAE,EACtC,CAAC,EAAe,KAAK,CACtB,EACK,IAAc,QAAc;EAChC,IAAM,oBAAM,IAAI,KAAiC;AACjD,OAAK,IAAM,KAAK,EAAe,GAAI,IAAI,EAAE,IAAI,EAAE;AAC/C,SAAO;IACN,CAAC,EAAc,CAAC,EAEb,IAAmB,IAAwB,EAC3C,IAAkB,IAAuB,EACzC,KAAqB,IAA0B,EAE/C,IAAO,QAAc,GAAM,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EAC/C,IAAc,QAAc,EAAa,MAAM,SAAS,EAAE,EAAE,CAAC,EAAa,KAAK,CAAC,EAGhF,KAAiB,EAAY,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QACrE,KAAiB,EAAY,QAAQ,MAAM,EAAE,WAAW,aAAa,CAAC,QACtE,KAAiB,EAAY,QAAQ,MAAM,EAAE,WAAW,YAAY,CAAC,QAErE,KAAe,QAAc;EACjC,IAAM,IAAiC,EAAE;AACzC,OAAK,IAAM,KAAK,EAAa,GAAO,EAAE,WAAW,EAAO,EAAE,WAAW,KAAK;AAC1E,SAAO;IACN,CAAC,EAAY,CAAC,EAEX,KAA2D,EAAe,KAAK,OAAS;EAC5F,OAAO,EAAI;EACX,OAAO,EAAI;EACX,OAAO,EAAI,UAAU,QAAQ,EAAY,SAAU,GAAa,EAAI,UAAU;EAC/E,EAAE,EAEG,UAAkB;AAGtB,EAFA,EAAQ,EAAW,EACnB,EAAO,KAAK,EACZ,EAAa,KAAK;IAGd,KAAiB,YAAY;AACjC,MAAI,CAAC,EAAK,YAAY;AACpB,KAAa,6CAA6C;AAC1D;;AAEF,MAAI,CAAC,EAAK,UAAU,MAAM,EAAE;AAC1B,KAAa,oCAAoC;AACjD;;EAEF,IAAM,IAAS,OAAO,EAAK,OAAO;AAClC,MAAI,CAAC,EAAK,UAAU,OAAO,MAAM,EAAO,IAAI,KAAU,GAAG;AAEvD,GADA,EAAa,oCAAoC,EACjD,EAAO,MAAM,oCAAoC;AACjD;;AAEF,IAAa,KAAK;AAClB,MAAI;AAgBF,GAfA,MAAM,EAAiB,YAAY;IACjC,YAAY,EAAK;IACjB,WAAW,EAAK,UAAU,MAAM;IAChC,MAAM,EAAK;IACX,SAAS,EAAK,QAAQ,MAAM,IAAI;IAChC,QAAQ,EAAK,OAAO,MAAM,IAAI;IAC9B,aAAa,EAAK,YAAY,MAAM,IAAI;IACxC;IACA,MAAM,EAAK;IACX,aAAa,GAAc,EAAK,YAAY,IAAI;IAChD,gBAAgB,GAAK,UAAU;IAC/B,OAAO,EAAK,MAAM,MAAM,IAAI;IAC7B,CAAC,EACF,EAAO,QAAQ,qBAAqB,EACpC,EAAc,GAAM,EACpB,GAAW;WACJ,GAAK;AAEZ,GADA,EAAa,aAAe,QAAQ,EAAI,UAAU,+BAA+B,EACjF,EAAO,UAAU,EAAI;;IAInB,KAAiC;EACrC;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAA4C,EAAE;KAAc,CAAA,EACzE,kBAAC,KAAD;KAAG,WAAU;eAAiD,EAAgB,EAAE,KAAK;KAAK,CAAA,CACtF;;GAET;EACD;GACE,KAAK;GACL,QAAQ;GACR,cAAc;GACd,SAAS,MAAM;IACb,IAAM,IAAW,EAAY,IAAI,EAAE,WAAW;AAC9C,WAAO,IACL,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA0C,EAAS;MAAc,CAAA,EAC9E,kBAAC,KAAD;MAAG,WAAU;gBAAuC,EAAS;MAAiB,CAAA,CAC1E;SAEN,kBAAC,QAAD;KAAM,WAAU;eAAwC,EAAE,WAAW,MAAM,GAAG,EAAE;KAAQ,CAAA;;GAG7F;EACD;GACE,KAAK;GACL,QAAQ;GACR,cAAc;GACd,SAAS,MAAM,kBAAC,QAAD;IAAM,WAAU;cAAuB,EAAgB,EAAE,KAAK;IAAQ,CAAA;GACtF;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,IAAD,EAAa,QAAQ,EAAE,QAAU,CAAA;GACjD;EACD;GACE,KAAK;GACL,QAAQ;GACR,cAAc;GACd,SAAS,MAAM;IACb,IAAM,IAAS,EAAE,QAAQ,MAAM,EACzB,IAAc,EAAE,aAAa,MAAM;AAEzC,WADI,CAAC,KAAU,CAAC,IAAoB,kBAAC,QAAD;KAAM,WAAU;eAAqB;KAAQ,CAAA,GAE/E,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACE,kBAAC,QAAD;OAAM,WAAU;iBAAY,KAAU;OAAW,CAAA;MACjD,kBAAC,IAAD,EAAY,WAAU,2CAA4C,CAAA;MAClE,kBAAC,QAAD;OAAM,WAAU;iBAAY,KAAe;OAAW,CAAA;MACjD;;;GAGZ;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,YAAY,MAAM,EAAE;GACpB,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cAAkC,EAAa,EAAE,QAAQ,EAAE,KAAK;IAAQ,CAAA;GAE3F;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,cAAc;GACd,YAAY,MAAM,EAAE,eAAe;GACnC,SAAS,MAAM,kBAAC,QAAD;IAAM,WAAU;cAAsB,EAAW,EAAE,YAAY;IAAQ,CAAA;GACvF;EACD;GACE,KAAK;GACL,QAAQ;GACR,cAAc;GACd,SAAS,MACP,EAAE,UAAU,KACV,kBAAC,IAAD,EAAU,QAAQ,EAAE,SAAS,IAAM,CAAA,GAEnC,kBAAC,QAAD;IAAM,WAAU;cAAqB;IAAQ,CAAA;GAElD;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MAAM;IAKb,IAAM,IAAQ,GAAa,IAAsB,EAAE,OAAO,EACpD,IAAa,GAA0B,SAAS,EAAE,OAAO,EACzD,IAAU,OAAoB,EAAE;AAItC,WAHI,EAAM,WAAW,KAAK,CAAC,IAClB,kBAAC,QAAD;KAAM,WAAU;eAAqB;KAAQ,CAAA,GAGpD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACG,EAAM,SAAS,KACd,kBAAC,GAAD;MACE,OAAO,EAAE;MACT,UAAU;MACV,gBAAgB,MAAM;AAChB,aAAM,EAAE,WACZ,EAAmB,EAAE,GAAG,EACxB,GAAmB,OACjB;QAAE,IAAI,EAAE;QAAI,QAAQ;QAAwB,EAC5C;QACE,iBAAiB,EAAO,QAAQ,mBAAmB;QACnD,UAAU,MAAM,EAAO,UAAU,EAAE;QACnC,iBAAiB,EAAmB,KAAK;QAC1C,CACF;;gBAbL,CAgBE,kBAAC,GAAD;OACE,WAAU;OACV,cAAY,8BAA8B,EAAE;iBAE5C,kBAAC,GAAD,EAAa,aAAa,EAAgB,EAAE,OAAO,EAAI,CAAA;OACzC,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAM,KAAK,MACV,kBAAC,GAAD;OAAqB,OAAO;iBACzB,EAAgB,EAAG;OACT,EAFI,EAEJ,CACb,EACY,CAAA,CACT;SAEV,KACC,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,UAAU,EAAgB,aAAa;MACvC,eACE,EAAgB,OAAO,EAAE,IAAI;OAC3B,iBAAiB,EAAO,QAAQ,qBAAqB;OACrD,UAAU,MAAM,EAAO,UAAU,EAAE;OACpC,CAAC;gBARN,CAWE,kBAAC,GAAD,EAAc,WAAU,kBAAmB,CAAA,EAAA,kBACpC;QAEP;;;GAGX;EACF,EAEK,IAAa,MAAW,SAAS,MAAe;AAEtD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,eAAe,EAAc,GAAK;aAA1C,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,qBAC5B;;YANb;GAUE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,UAAA;MACA,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MAClC,OAAO,EAAY;MACnB,OAAM;MACN,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,IAAD,EAAe,WAAU,WAAY,CAAA;MAC3C,OAAO;MACP,OAAM;MACN,CAAA;KACF,kBAAC,GAAD;MACE,QAAO;MACP,MAAM,kBAAC,IAAD,EAAO,WAAU,WAAY,CAAA;MACnC,OAAO;MACP,OAAM;MACN,CAAA;KACF,kBAAC,GAAD;MACE,QAAO;MACP,MAAM,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;MAC1C,OAAO;MACP,OAAM;MACN,CAAA;KACE;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,SAAS;KACT,OAAO;KACP,WAAW,MAAM;AAEf,MADA,GAAU,EAAE,EACZ,EAAM,OAAO;;KAEf,CAAA,EACF,kBAAC,GAAD;KACE,OAAO;KACP,gBAAgB,MAAM;AAEpB,MADA,GAAc,EAAoB,EAClC,EAAM,OAAO;;eAJjB,CAOE,kBAAC,GAAD;MAAe,WAAU;MAAO,cAAW;gBACzC,kBAAC,GAAD,EAAa,aAAY,iBAAkB,CAAA;MAC7B,CAAA,EAChB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAY,OAAM;gBAAM;MAA0B,CAAA,EACjD,EAAc,KAAK,MAClB,kBAAC,GAAD;MAAuB,OAAO,EAAE;gBAAhC;OACG,EAAE;OAAU;OAAI,EAAE;OACR;QAFI,EAAE,GAEN,CACb,CACY,EAAA,CAAA,CACT;OACL;;GAEL,KACC,kBAAC,IAAD,EAAa,OAAM,sBAAuB,CAAA,GACxC,KACF,kBAAC,IAAD;IAAW,SAAQ;IAA2B,eAAe,IAAS;IAAI,CAAA,GACxE,EAAK,WAAW,IAClB,kBAAC,IAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAM,WAAU,aAAc,CAAA;IACpC,OAAO,IAAa,0BAA0B;IAC9C,aACE,IACI,0EACA;IAEN,QACE,kBAAC,GAAD;KAAQ,eAAe,EAAc,GAAK;eAA1C,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,qBAC5B;;IAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,IAAD;IACW;IACH;IACN,SAAS,MAAM,EAAE;IACjB,aAAa;KAAE,KAAK;KAAe,KAAK;KAAQ;IAChD,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,MAAS;AAEtB,KADA,EAAc,EAAK,EACd,KAAM,GAAW;;cAGxB,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAuB,qBAAyC,CAAA,EAChE,kBAAC,IAAD,EAAA,UAA6B,+EAEC,CAAA,CACP,EAAA,CAAA,EAEzB,kBAAC,QAAD;KACE,WAAW,MAAM;AAEV,MADL,EAAE,gBAAgB,EACb,IAAgB;;eAHzB,CAME,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACA,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAe;SAAkB,CAAA,EAChD,kBAAC,GAAD;SACE,OAAO,EAAK;SACZ,gBAAgB,MAAM;UAEpB,IAAM,IAAW,EAAY,IAAI,EAAE;AACnC,aAAS,OAAU;WACjB,GAAG;WACH,YAAY;WACZ,MAAM,GAAU,QAAQ,EAAK;WAC9B,EAAE;;mBATP,CAYE,kBAAC,GAAD;UAAe,IAAG;oBAChB,kBAAC,GAAD,EAAa,aAAY,qBAAsB,CAAA;UACjC,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAc,WAAW,IACxB,kBAAC,GAAD;UAAY,OAAM;UAAO,UAAA;oBAAS;UAErB,CAAA,GAEb,EAAc,KAAK,MACjB,kBAAC,GAAD;UAAuB,OAAO,EAAE;oBAAhC;WACG,EAAE;WAAU;WAAI,EAAE;WACR;YAFI,EAAE,GAEN,CACb,EAEU,CAAA,CACT;WACL;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAU;UAAmB,CAAA,EAC5C,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,WAAW,EAAE,OAAO;WAAO,CAAC;UAChE,aAAY;UACZ,CAAA,CACE;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAW;UAAY,CAAA,EACtC,kBAAC,GAAD;UACE,OAAO,EAAK;UACZ,gBAAgB,MAAM,EAAQ;WAAE,GAAG;WAAM,MAAM;WAAuB,CAAC;oBAFzE,CAIE,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,EAAe,CAAA;WACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAgB,KAAK,MACpB,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;oBAAa;UAAc,CAAA,EAC1C,kBAAC,GAAD;UACE,IAAG;UACH,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;oBAAW;UAAmB,CAAA,EAC7C,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,aAAa,EAAE,OAAO;WAAO,CAAC;UAClE,aAAY;UACZ,CAAA,CACE;WACF;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAO,SAAQ;qBAAa;WAAgB,CAAA,EAC5C,kBAAC,GAAD;WACE,IAAG;WACH,MAAK;WACL,KAAI;WACJ,MAAK;WACL,OAAO,EAAK;WACZ,WAAW,MAAM,EAAQ;YAAE,GAAG;YAAM,QAAQ,EAAE,OAAO;YAAO,CAAC;WAC7D,aAAY;WACZ,CAAA,CACE;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAO,SAAQ;qBAAW;WAAY,CAAA,EACtC,kBAAC,GAAD;WACE,OAAO,EAAK;WACZ,gBAAgB,MAAM,EAAQ;YAAE,GAAG;YAAM,MAAM;YAAc,CAAC;qBAFhE,CAIE,kBAAC,GAAD;YAAe,IAAG;sBAChB,kBAAC,GAAD,EAAe,CAAA;YACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAM,KAAK,MACV,kBAAC,GAAD;YAAoB,OAAO;sBACxB,EAAgB,EAAE;YACR,EAFI,EAEJ,CACb,EACY,CAAA,CACT;aACL;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAO,SAAQ;qBAAgB;WAAsB,CAAA,EACrD,kBAAC,GAAD;WACE,IAAG;WACH,MAAK;WACL,OAAO,EAAK;WACZ,WAAW,MAAM,EAAQ;YAAE,GAAG;YAAM,aAAa,EAAE,OAAO;YAAO,CAAC;WAClE,CAAA,CACE;;SACF;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAc;SAAe,CAAA,EAC5C,kBAAC,GAAD;SACE,IAAG;SACH,OAAO,EAAK;SACZ,WAAW,MAAM,EAAQ;UAAE,GAAG;UAAM,SAAS,EAAE,OAAO;UAAO,CAAC;SAC9D,aAAY;SACZ,CAAA,CACE;;OAEN,kBAAC,IAAD;QACE,IAAG;QACH,OAAM;QACN,OAAO;QACP,UAAU;QACV,MAAK;QACL,CAAA;OAEF,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAY;SAAa,CAAA,EACxC,kBAAC,GAAD;SACE,IAAG;SACH,OAAO,EAAK;SACZ,WAAW,MAAM,EAAQ;UAAE,GAAG;UAAM,OAAO,EAAE,OAAO;UAAO,CAAC;SAC5D,aAAY;SACZ,MAAM;SACN,CAAA,CACE;;OAEL,KAAa,kBAAC,KAAD;QAAG,WAAU;kBAAkC;QAAc,CAAA;OACvE;SAEN,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,eAAe;AAEb,OADA,EAAc,GAAM,EACpB,GAAW;;gBAEd;MAEQ,CAAA,EACT,kBAAC,GAAD;MAAQ,MAAK;MAAS,UAAU,EAAiB;gBAC9C,EAAiB,YAAY,gBAAgB;MACvC,CAAA,CACc,EAAA,CAAA,CAClB;OACiB,EAAA,CAAA;IACT,CAAA;GACR"}
|
package/package.json
CHANGED