@burdenoff/microfe-theglobalfuel 2026.705.3 → 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.
- package/dist/theglobalfuel/TheGlobalFuelRoot.js +54 -46
- package/dist/theglobalfuel/TheGlobalFuelRoot.js.map +1 -1
- package/dist/theglobalfuel/TheGlobalFuelRoutes.js +10 -6
- package/dist/theglobalfuel/TheGlobalFuelRoutes.js.map +1 -1
- package/dist/theglobalfuel/components/DataTable.js +126 -58
- package/dist/theglobalfuel/components/DataTable.js.map +1 -1
- package/dist/theglobalfuel/components/EmptyState.js +4 -4
- package/dist/theglobalfuel/components/EmptyState.js.map +1 -1
- package/dist/theglobalfuel/components/MetricsCard.js +2 -2
- package/dist/theglobalfuel/components/MetricsCard.js.map +1 -1
- package/dist/theglobalfuel/components/WorldMap.js +115 -0
- package/dist/theglobalfuel/components/WorldMap.js.map +1 -0
- package/dist/theglobalfuel/components/index.js +9 -0
- package/dist/theglobalfuel/components/viz/AllocationBar.js +68 -0
- package/dist/theglobalfuel/components/viz/AllocationBar.js.map +1 -0
- package/dist/theglobalfuel/components/viz/AreaChart.js +102 -0
- package/dist/theglobalfuel/components/viz/AreaChart.js.map +1 -0
- package/dist/theglobalfuel/components/viz/AvatarStack.js +2 -0
- package/dist/theglobalfuel/components/viz/BarChart.js +4 -0
- package/dist/theglobalfuel/components/viz/CertificateCard.js +4 -0
- package/dist/theglobalfuel/components/viz/DonutChart.js +109 -0
- package/dist/theglobalfuel/components/viz/DonutChart.js.map +1 -0
- package/dist/theglobalfuel/components/viz/HBarFunnel.js +3 -0
- package/dist/theglobalfuel/components/viz/Heatmap.js +3 -0
- package/dist/theglobalfuel/components/viz/KanbanBoard.js +2 -0
- package/dist/theglobalfuel/components/viz/LineChart.js +4 -0
- package/dist/theglobalfuel/components/viz/OrgTree.js +2 -0
- package/dist/theglobalfuel/components/viz/PathSteps.js +68 -0
- package/dist/theglobalfuel/components/viz/PathSteps.js.map +1 -0
- package/dist/theglobalfuel/components/viz/ProgressRing.js +6 -0
- package/dist/theglobalfuel/components/viz/ProgressRing.js.map +1 -0
- package/dist/theglobalfuel/components/viz/RatingStars.js +3 -0
- package/dist/theglobalfuel/components/viz/SegmentedTabs.js +3 -0
- package/dist/theglobalfuel/components/viz/SkillBar.js +3 -0
- package/dist/theglobalfuel/components/viz/Sparkline.js +74 -0
- package/dist/theglobalfuel/components/viz/Sparkline.js.map +1 -0
- package/dist/theglobalfuel/components/viz/StatCard.js +3 -0
- package/dist/theglobalfuel/components/viz/StepProgress.js +64 -0
- package/dist/theglobalfuel/components/viz/StepProgress.js.map +1 -0
- package/dist/theglobalfuel/components/viz/Timeline.js +66 -0
- package/dist/theglobalfuel/components/viz/Timeline.js.map +1 -0
- package/dist/theglobalfuel/components/viz/index.js +23 -0
- package/dist/theglobalfuel/components/viz/lib/Avatar.js +1 -0
- package/dist/theglobalfuel/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/theglobalfuel/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/theglobalfuel/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/format.js +17 -0
- package/dist/theglobalfuel/components/viz/lib/format.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/geometry.js +61 -0
- package/dist/theglobalfuel/components/viz/lib/geometry.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/hooks.js +39 -0
- package/dist/theglobalfuel/components/viz/lib/hooks.js.map +1 -0
- package/dist/theglobalfuel/components/viz/lib/palette.js +27 -0
- package/dist/theglobalfuel/components/viz/lib/palette.js.map +1 -0
- package/dist/theglobalfuel/pages/assets/AssetDetailPage.js +431 -316
- package/dist/theglobalfuel/pages/assets/AssetDetailPage.js.map +1 -1
- package/dist/theglobalfuel/pages/dashboard/DashboardPage.js +525 -227
- package/dist/theglobalfuel/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/theglobalfuel/pages/emissions/EmissionsPage.js +255 -206
- package/dist/theglobalfuel/pages/emissions/EmissionsPage.js.map +1 -1
- package/dist/theglobalfuel/pages/provenance/ProvenancePage.js +465 -0
- package/dist/theglobalfuel/pages/provenance/ProvenancePage.js.map +1 -0
- package/dist/theglobalfuel/pages/shipments/ShipmentsPage.js +356 -227
- package/dist/theglobalfuel/pages/shipments/ShipmentsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmissionsPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/emissions/EmissionsPage.tsx"],"sourcesContent":["/**\n * Emissions — the ESG greenhouse-gas ledger.\n * @module theglobalfuel/pages/emissions\n *\n * Reads `theListEmissionRecords` (optionally filtered by GHG-Protocol scope and\n * by asset); writes `theRecordEmission` and `theVerifyEmission`. Asset names are\n * resolved from `theListAssets`. The KPI strip summarises the CO₂e captured on\n * the current page, split by Scope 1 / 2 / 3. No mock data — every number comes\n * from the real `wspace-theglobalfuel-svc` contract.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { Plus, Factory, Flame, Zap, Truck, Cloud, CheckCircle2, Circle } 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 { MiniBar } from '../../components/MiniBar';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport {\n useTheAssets,\n useTheEmissionRecords,\n useTheRecordEmission,\n useTheVerifyEmission,\n useCursorPager,\n useNotify,\n} from '../../hooks';\nimport {\n formatDate,\n formatRelativeTime,\n formatPercent,\n formatTonnesCo2e,\n formatEnumLabel,\n toBackendDate,\n} from '../../utils/formatters';\nimport type { TheEmissionRecord, TheEmissionScope } from '../../types';\n\nconst SCOPES: TheEmissionScope[] = ['SCOPE_1', 'SCOPE_2', 'SCOPE_3'];\n\nconst SCOPE_FILTERS: FilterOption<TheEmissionScope | 'ALL'>[] = [\n { value: 'ALL', label: 'All scopes' },\n { value: 'SCOPE_1', label: 'Scope 1' },\n { value: 'SCOPE_2', label: 'Scope 2' },\n { value: 'SCOPE_3', label: 'Scope 3' },\n];\n\n/** MiniBar fill token per GHG scope — mirrors the StatusBadge scope palette. */\nconst SCOPE_BAR: Record<TheEmissionScope, string> = {\n SCOPE_1: 'bg-status-error-text',\n SCOPE_2: 'bg-status-warning-text',\n SCOPE_3: 'bg-accent',\n};\n\nconst PAGE_SIZE = 25;\n\nconst today = (): string => new Date().toISOString().slice(0, 10);\n\ninterface RecordFormState {\n assetId: string;\n scope: TheEmissionScope;\n co2eTonnes: string;\n periodStart: string;\n periodEnd: string;\n source: string;\n methodology: string;\n notes: string;\n}\n\nconst emptyForm = (): RecordFormState => ({\n assetId: '',\n scope: 'SCOPE_1',\n co2eTonnes: '',\n periodStart: today(),\n periodEnd: today(),\n source: '',\n methodology: '',\n notes: '',\n});\n\n/** KPI-card footer: this scope's share of the page total, tinted by scope. */\nconst renderScopeShare = (\n value: number,\n total: number,\n scope: TheEmissionScope\n): ReactNode => {\n if (total <= 0) return undefined;\n const pct = (value / total) * 100;\n return (\n <div className=\"space-y-2\">\n <MiniBar value={pct} barClassName={SCOPE_BAR[scope]} height={4} />\n <p className=\"text-xs tabular-nums text-text-tertiary\">{formatPercent(pct)} of total</p>\n </div>\n );\n};\n\nexport function EmissionsPage() {\n const notify = useNotify();\n const [scope, setScope] = useState<TheEmissionScope | 'ALL'>('ALL');\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 // Track the row whose verification is in flight, so only that row's Verify\n // button disables (verifyEmission.isPending is shared across all rows).\n const [verifyingId, setVerifyingId] = 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 const assetOptions = assetsQuery.data?.items ?? [];\n\n const { data, isLoading, isError, refetch } = useTheEmissionRecords({\n scope: scope === 'ALL' ? undefined : scope,\n assetId: assetId === 'ALL' ? undefined : assetId,\n pagination: { limit: PAGE_SIZE, offset: pager.page * PAGE_SIZE },\n });\n const recordEmission = useTheRecordEmission();\n const verifyEmission = useTheVerifyEmission();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n // KPI summary — page-scoped CO₂e, total plus per-scope breakdown.\n const { total, byScope } = useMemo(() => {\n const byScope: Record<TheEmissionScope, number> = {\n SCOPE_1: 0,\n SCOPE_2: 0,\n SCOPE_3: 0,\n };\n let total = 0;\n for (const r of rows) {\n total += r.co2eTonnes;\n byScope[r.scope] += r.co2eTonnes;\n }\n return { total, byScope };\n }, [rows]);\n\n const columns: Column<TheEmissionRecord>[] = [\n {\n key: 'scope',\n header: 'Scope',\n render: (r) => <StatusBadge status={r.scope} />,\n },\n {\n key: 'asset',\n header: 'Asset',\n render: (r) => (\n <span className=\"text-text-secondary\">\n {r.assetId ? (assetMap[r.assetId] ?? `${r.assetId.slice(0, 8)}…`) : '—'}\n </span>\n ),\n },\n {\n key: 'co2e',\n header: 'CO₂e',\n align: 'right',\n sortValue: (r) => r.co2eTonnes,\n render: (r) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatTonnesCo2e(r.co2eTonnes)}\n </span>\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: 'source',\n header: 'Source',\n hideOnMobile: true,\n render: (r) => <span className=\"text-text-secondary\">{r.source ?? '—'}</span>,\n },\n {\n key: 'verified',\n header: 'Verified',\n render: (r) =>\n r.verified ? (\n <span className=\"inline-flex items-center gap-1.5 text-status-success-text\">\n <CheckCircle2 className=\"h-4 w-4\" />\n <span className=\"text-xs font-medium\">Verified</span>\n {r.verifiedAt && (\n <span className=\"text-xs font-normal tabular-nums text-text-tertiary\">\n {formatDate(r.verifiedAt)}\n </span>\n )}\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-text-tertiary\">\n <Circle className=\"h-3.5 w-3.5\" />\n <span className=\"text-xs\">Unverified</span>\n </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 key: 'verify',\n header: '',\n align: 'right',\n render: (r) =>\n !r.verified ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n setVerifyingId(r.id);\n verifyEmission.mutate(r.id, {\n onSuccess: () => notify.success('Emission verified'),\n onError: (e) => notify.fromError(e),\n onSettled: () => setVerifyingId(null),\n });\n }}\n disabled={verifyingId === r.id}\n >\n {verifyingId === r.id ? 'Verifying…' : 'Verify'}\n </Button>\n ) : null,\n },\n ];\n\n const openDialog = () => {\n setForm(emptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const handleRecord = async () => {\n const co2e = Number(form.co2eTonnes);\n if (!form.co2eTonnes || Number.isNaN(co2e) || co2e <= 0) {\n const message = 'CO₂e (tonnes) must be greater than zero.';\n setFormError(message);\n notify.error(message);\n return;\n }\n if (!form.periodStart || !form.periodEnd) {\n setFormError('Period start and end are required.');\n return;\n }\n const start = toBackendDate(form.periodStart);\n const end = toBackendDate(form.periodEnd);\n if (!start || !end) {\n setFormError('Period start and end are required.');\n return;\n }\n if (new Date(end).getTime() < new Date(start).getTime()) {\n setFormError('Period end must be on or after the period start.');\n return;\n }\n setFormError(null);\n try {\n await recordEmission.mutateAsync({\n assetId: form.assetId || null,\n scope: form.scope,\n co2eTonnes: co2e,\n periodStart: start,\n periodEnd: end,\n source: form.source.trim() || null,\n methodology: form.methodology.trim() || null,\n notes: form.notes.trim() || null,\n });\n notify.success('Emission recorded');\n setDialogOpen(false);\n setForm(emptyForm());\n } catch (err) {\n notify.fromError(err);\n setFormError(err instanceof Error ? err.message : 'Failed to record emission.');\n }\n };\n\n return (\n <PageLayout\n title=\"Emissions\"\n description=\"ESG greenhouse-gas ledger across every asset — GHG Protocol Scope 1 / 2 / 3, with third-party verification.\"\n actions={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record emission\n </Button>\n }\n >\n {/* KPI strip — page-scoped CO₂e, split by scope */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <MetricsCard\n icon={<Factory className=\"h-5 w-5\" />}\n value={rows.length > 0 ? formatTonnesCo2e(total) : '—'}\n label=\"CO₂e on this page\"\n featured\n footer={\n <p className=\"text-xs tabular-nums text-text-tertiary\">\n {rows.length} record{rows.length === 1 ? '' : 's'} · Scope 1 · 2 · 3\n </p>\n }\n />\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={rows.length > 0 ? formatTonnesCo2e(byScope.SCOPE_1) : '—'}\n label=\"Scope 1 · Direct\"\n footer={renderScopeShare(byScope.SCOPE_1, total, 'SCOPE_1')}\n />\n <MetricsCard\n icon={<Zap className=\"h-5 w-5\" />}\n value={rows.length > 0 ? formatTonnesCo2e(byScope.SCOPE_2) : '—'}\n label=\"Scope 2 · Purchased energy\"\n footer={renderScopeShare(byScope.SCOPE_2, total, 'SCOPE_2')}\n />\n <MetricsCard\n icon={<Truck className=\"h-5 w-5\" />}\n value={rows.length > 0 ? formatTonnesCo2e(byScope.SCOPE_3) : '—'}\n label=\"Scope 3 · Value chain\"\n footer={renderScopeShare(byScope.SCOPE_3, total, 'SCOPE_3')}\n />\n </div>\n\n {/* Filters */}\n <div className=\"mt-6 mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={SCOPE_FILTERS}\n value={scope}\n onChange={(v) => {\n setScope(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 {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 emission records…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load emission records.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Cloud className=\"h-10 w-10\" />}\n title=\"No emissions recorded\"\n description={\n scope === 'ALL' && assetId === 'ALL'\n ? 'Record a CO₂e figure to start building your Scope 1–3 emissions ledger.'\n : 'No emission records match these filters.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record emission\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\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 emission dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open);\n if (!open) {\n setForm(emptyForm());\n setFormError(null);\n }\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record emission</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a CO₂e figure for a reporting period — optionally attributed to an asset.\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-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emScope\">Scope</Label>\n <Select\n value={form.scope}\n onValueChange={(v) => setForm({ ...form, scope: v as TheEmissionScope })}\n >\n <SelectTrigger id=\"emScope\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SCOPES.map((s) => (\n <SelectItem key={s} value={s}>\n {formatEnumLabel(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emCo2e\">CO₂e (tonnes) *</Label>\n <Input\n id=\"emCo2e\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.co2eTonnes}\n onChange={(e) => setForm({ ...form, co2eTonnes: e.target.value })}\n placeholder=\"1250\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emAsset\">Asset</Label>\n <Select\n value={form.assetId}\n onValueChange={(v) => setForm({ ...form, assetId: v })}\n >\n <SelectTrigger id=\"emAsset\">\n <SelectValue placeholder=\"Optional — org-wide if unset\" />\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=\"emStart\">Period start *</Label>\n <Input\n id=\"emStart\"\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=\"emEnd\">Period end *</Label>\n <Input\n id=\"emEnd\"\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=\"emSource\">Source</Label>\n <Input\n id=\"emSource\"\n value={form.source}\n onChange={(e) => setForm({ ...form, source: e.target.value })}\n placeholder=\"Flaring, grid electricity…\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emMethod\">Methodology</Label>\n <Input\n id=\"emMethod\"\n value={form.methodology}\n onChange={(e) => setForm({ ...form, methodology: e.target.value })}\n placeholder=\"GHG Protocol / IPCC / API\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emNotes\">Notes</Label>\n <Textarea\n id=\"emNotes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this figure.\"\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={() => setDialogOpen(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={recordEmission.isPending}>\n {recordEmission.isPending ? 'Recording…' : 'Record emission'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAyDA,IAAM,IAA6B;CAAC;CAAW;CAAW;CAAU,EAE9D,IAA0D;CAC9D;EAAE,OAAO;EAAO,OAAO;EAAc;CACrC;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAW,OAAO;EAAW;CACvC,EAGK,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,SAAS;CACV,EAEK,IAAY,IAEZ,2BAAsB,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG,EAa3D,WAAoC;CACxC,SAAS;CACT,OAAO;CACP,YAAY;CACZ,aAAa,GAAO;CACpB,WAAW,GAAO;CAClB,QAAQ;CACR,aAAa;CACb,OAAO;CACR,GAGK,KACJ,GACA,GACA,MACc;AACd,KAAI,KAAS,EAAG;CAChB,IAAM,IAAO,IAAQ,IAAS;AAC9B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAS,OAAO;GAAK,cAAc,EAAU;GAAQ,QAAQ;GAAK,CAAA,EAClE,kBAAC,KAAD;GAAG,WAAU;aAAb,CAAwD,EAAc,EAAI,EAAC,YAAa;KACpF;;;AAIV,SAAgB,IAAgB;CAC9B,IAAM,IAAS,IAAW,EACpB,CAAC,GAAO,KAAY,EAAmC,MAAM,EAC7D,CAAC,GAAS,KAAc,EAAiB,MAAM,EAC/C,IAAQ,IAAgB,EACxB,CAAC,IAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAM,KAAW,EAA0B,EAAU,EACtD,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAGzD,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAG7D,IAAc,GAAa,EAAE,YAAY,EAAE,OAAO,KAAK,EAAE,CAAC,EAC1D,KAAW,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,EAChB,IAAe,EAAY,MAAM,SAAS,EAAE,EAE5C,EAAE,SAAM,eAAW,aAAS,eAAY,GAAsB;EAClE,OAAO,MAAU,QAAQ,KAAA,IAAY;EACrC,SAAS,MAAY,QAAQ,KAAA,IAAY;EACzC,YAAY;GAAE,OAAO;GAAW,QAAQ,EAAM,OAAO;GAAW;EACjE,CAAC,EACI,IAAiB,IAAsB,EACvC,KAAiB,IAAsB,EAEvC,IAAO,QAAc,GAAM,SAAS,EAAE,EAAE,CAAC,EAAK,CAAC,EAG/C,EAAE,UAAO,eAAY,QAAc;EACvC,IAAM,IAA4C;GAChD,SAAS;GACT,SAAS;GACT,SAAS;GACV,EACG,IAAQ;AACZ,OAAK,IAAM,KAAK,EAEd,CADA,KAAS,EAAE,YACX,EAAQ,EAAE,UAAU,EAAE;AAExB,SAAO;GAAE;GAAO;GAAS;IACxB,CAAC,EAAK,CAAC,EAEJ,KAAuC;EAC3C;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MAAM,kBAAC,IAAD,EAAa,QAAQ,EAAE,OAAS,CAAA;GAChD;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cACb,EAAE,UAAW,GAAS,EAAE,YAAY,GAAG,EAAE,QAAQ,MAAM,GAAG,EAAE,CAAC,KAAM;IAC/D,CAAA;GAEV;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,YAAY,MAAM,EAAE;GACpB,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cACb,EAAiB,EAAE,WAAW;IAC1B,CAAA;GAEV;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,cAAc;GACd,SAAS,MAAM,kBAAC,QAAD;IAAM,WAAU;cAAuB,EAAE,UAAU;IAAW,CAAA;GAC9E;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,EAAE,WACA,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACE,kBAAC,IAAD,EAAc,WAAU,WAAY,CAAA;KACpC,kBAAC,QAAD;MAAM,WAAU;gBAAsB;MAAe,CAAA;KACpD,EAAE,cACD,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAW,EAAE,WAAW;MACpB,CAAA;KAEJ;QAEP,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACE,kBAAC,IAAD,EAAQ,WAAU,eAAgB,CAAA,EAClC,kBAAC,QAAD;KAAM,WAAU;eAAU;KAAiB,CAAA,CACtC;;GAEZ;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,cAAc;GACd,SAAS,MAAM,kBAAC,QAAD;IAAM,WAAU;cAAsB,GAAmB,EAAE,UAAU;IAAQ,CAAA;GAC7F;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,SAAS,MACN,EAAE,WAgBC,OAfF,kBAAC,GAAD;IACE,MAAK;IACL,SAAQ;IACR,eAAe;AAEb,KADA,EAAe,EAAE,GAAG,EACpB,GAAe,OAAO,EAAE,IAAI;MAC1B,iBAAiB,EAAO,QAAQ,oBAAoB;MACpD,UAAU,MAAM,EAAO,UAAU,EAAE;MACnC,iBAAiB,EAAe,KAAK;MACtC,CAAC;;IAEJ,UAAU,MAAgB,EAAE;cAE3B,MAAgB,EAAE,KAAK,eAAe;IAChC,CAAA;GAEd;EACF,EAEK,UAAmB;AAGvB,EAFA,EAAQ,GAAW,CAAC,EACpB,EAAa,KAAK,EAClB,EAAc,GAAK;IAGf,KAAe,YAAY;EAC/B,IAAM,IAAO,OAAO,EAAK,WAAW;AACpC,MAAI,CAAC,EAAK,cAAc,OAAO,MAAM,EAAK,IAAI,KAAQ,GAAG;GACvD,IAAM,IAAU;AAEhB,GADA,EAAa,EAAQ,EACrB,EAAO,MAAM,EAAQ;AACrB;;AAEF,MAAI,CAAC,EAAK,eAAe,CAAC,EAAK,WAAW;AACxC,KAAa,qCAAqC;AAClD;;EAEF,IAAM,IAAQ,EAAc,EAAK,YAAY,EACvC,IAAM,EAAc,EAAK,UAAU;AACzC,MAAI,CAAC,KAAS,CAAC,GAAK;AAClB,KAAa,qCAAqC;AAClD;;AAEF,MAAI,IAAI,KAAK,EAAI,CAAC,SAAS,GAAG,IAAI,KAAK,EAAM,CAAC,SAAS,EAAE;AACvD,KAAa,mDAAmD;AAChE;;AAEF,IAAa,KAAK;AAClB,MAAI;AAaF,GAZA,MAAM,EAAe,YAAY;IAC/B,SAAS,EAAK,WAAW;IACzB,OAAO,EAAK;IACZ,YAAY;IACZ,aAAa;IACb,WAAW;IACX,QAAQ,EAAK,OAAO,MAAM,IAAI;IAC9B,aAAa,EAAK,YAAY,MAAM,IAAI;IACxC,OAAO,EAAK,MAAM,MAAM,IAAI;IAC7B,CAAC,EACF,EAAO,QAAQ,oBAAoB,EACnC,EAAc,GAAM,EACpB,EAAQ,GAAW,CAAC;WACb,GAAK;AAEZ,GADA,EAAO,UAAU,EAAI,EACrB,EAAa,aAAe,QAAQ,EAAI,UAAU,6BAA6B;;;AAInF,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,SAAS;aAAjB,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,mBAC5B;;YANb;GAUE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;MACrC,OAAO,EAAK,SAAS,IAAI,EAAiB,EAAM,GAAG;MACnD,OAAM;MACN,UAAA;MACA,QACE,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACG,EAAK;QAAO;QAAQ,EAAK,WAAW,IAAI,KAAK;QAAI;QAChD;;MAEN,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,IAAD,EAAO,WAAU,WAAY,CAAA;MACnC,OAAO,EAAK,SAAS,IAAI,EAAiB,EAAQ,QAAQ,GAAG;MAC7D,OAAM;MACN,QAAQ,EAAiB,EAAQ,SAAS,GAAO,UAAU;MAC3D,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;MACjC,OAAO,EAAK,SAAS,IAAI,EAAiB,EAAQ,QAAQ,GAAG;MAC7D,OAAM;MACN,QAAQ,EAAiB,EAAQ,SAAS,GAAO,UAAU;MAC3D,CAAA;KACF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;MACnC,OAAO,EAAK,SAAS,IAAI,EAAiB,EAAQ,QAAQ,GAAG;MAC7D,OAAM;MACN,QAAQ,EAAiB,EAAQ,SAAS,GAAO,UAAU;MAC3D,CAAA;KACE;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,IAAD;KACE,SAAS;KACT,OAAO;KACP,WAAW,MAAM;AAEf,MADA,EAAS,EAAE,EACX,EAAM,OAAO;;KAEf,CAAA,EACF,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;OACL;;GAEL,KACC,kBAAC,GAAD,EAAa,OAAM,6BAA8B,CAAA,GAC/C,KACF,kBAAC,IAAD;IAAW,SAAQ;IAAkC,eAAe,GAAS;IAAI,CAAA,GAC/E,EAAK,WAAW,IAClB,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAO,WAAU,aAAc,CAAA;IACrC,OAAM;IACN,aACE,MAAU,SAAS,MAAY,QAC3B,4EACA;IAEN,QACE,kBAAC,GAAD;KAAQ,SAAS;eAAjB,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,mBAC5B;;IAEX,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,IAAD;IACW;IACH;IACN,SAAS,MAAM,EAAE;IACjB,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,MAAS;AAEtB,KADA,EAAc,EAAK,EACd,MACH,EAAQ,GAAW,CAAC,EACpB,EAAa,KAAK;;cAItB,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAuB,mBAAuC,CAAA,EAC9D,kBAAC,IAAD,EAAA,UAA6B,iFAEC,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,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAU;UAAa,CAAA,EACtC,kBAAC,GAAD;UACE,OAAO,EAAK;UACZ,gBAAgB,MAAM,EAAQ;WAAE,GAAG;WAAM,OAAO;WAAuB,CAAC;oBAF1E,CAIE,kBAAC,GAAD;WAAe,IAAG;qBAChB,kBAAC,GAAD,EAAe,CAAA;WACD,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAO,KAAK,MACX,kBAAC,GAAD;WAAoB,OAAO;qBACxB,EAAgB,EAAE;WACR,EAFI,EAEJ,CACb,EACY,CAAA,CACT;YACL;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAS;UAAuB,CAAA,EAC/C,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,KAAI;UACJ,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,EAAQ;WAAE,GAAG;WAAM,YAAY,EAAE,OAAO;WAAO,CAAC;UACjE,aAAY;UACZ,CAAA,CACE;WACF;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAU;SAAa,CAAA,EACtC,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,gCAAiC,CAAA;UAC5C,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;UAAc,CAAA,EACxC,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,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,eAAe,EAAc,GAAM;gBAAE;MAEpE,CAAA,EACT,kBAAC,GAAD;MAAQ,MAAK;MAAS,UAAU,EAAe;gBAC5C,EAAe,YAAY,eAAe;MACpC,CAAA,CACc,EAAA,CAAA,CACpB;OACiB,EAAA,CAAA;IACT,CAAA;GACR"}
|
|
1
|
+
{"version":3,"file":"EmissionsPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/emissions/EmissionsPage.tsx"],"sourcesContent":["/**\n * Emissions — the ESG greenhouse-gas ledger.\n * @module theglobalfuel/pages/emissions\n *\n * Reads `theListEmissionRecords` (optionally filtered by GHG-Protocol scope and\n * by asset); writes `theRecordEmission` and `theVerifyEmission`. Asset names are\n * resolved from `theListAssets`. The header pairs a scope-breakdown donut with a\n * KPI strip — both computed from a broad, asset-scoped pull of the ledger (not\n * just the current table page), split by Scope 1 / 2 / 3. No mock data — every\n * number comes from the real `wspace-theglobalfuel-svc` contract.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { Plus, Factory, Flame, Zap, Truck, Cloud, CheckCircle2, Circle } 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 { MiniBar } from '../../components/MiniBar';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { DonutChart, type Segment } from '../../components';\nimport {\n useTheAssets,\n useTheEmissionRecords,\n useTheRecordEmission,\n useTheVerifyEmission,\n useCursorPager,\n useNotify,\n} from '../../hooks';\nimport {\n formatDate,\n formatRelativeTime,\n formatPercent,\n formatTonnesCo2e,\n formatEnumLabel,\n toBackendDate,\n} from '../../utils/formatters';\nimport type { TheEmissionRecord, TheEmissionScope } from '../../types';\n\nconst SCOPES: TheEmissionScope[] = ['SCOPE_1', 'SCOPE_2', 'SCOPE_3'];\n\nconst SCOPE_FILTERS: FilterOption<TheEmissionScope | 'ALL'>[] = [\n { value: 'ALL', label: 'All scopes' },\n { value: 'SCOPE_1', label: 'Scope 1' },\n { value: 'SCOPE_2', label: 'Scope 2' },\n { value: 'SCOPE_3', label: 'Scope 3' },\n];\n\n/** MiniBar fill token per GHG scope — mirrors the StatusBadge scope palette. */\nconst SCOPE_BAR: Record<TheEmissionScope, string> = {\n SCOPE_1: 'bg-status-error-text',\n SCOPE_2: 'bg-status-warning-text',\n SCOPE_3: 'bg-accent',\n};\n\n/** Short legend label per scope for the breakdown donut. */\nconst SCOPE_LABEL: Record<TheEmissionScope, string> = {\n SCOPE_1: 'Scope 1',\n SCOPE_2: 'Scope 2',\n SCOPE_3: 'Scope 3',\n};\n\n/**\n * Tokenized donut-segment color per scope — aligned to the StatusBadge scope\n * palette (Scope 1 = danger, Scope 2 = warning, Scope 3 = info/blue). All are\n * semantic CSS variables, so the chart themes automatically.\n */\nconst SCOPE_DONUT_COLOR: Record<TheEmissionScope, string> = {\n SCOPE_1: 'var(--color-status-error-text)',\n SCOPE_2: 'var(--color-status-warning-text)',\n SCOPE_3: 'var(--color-accent-blue)',\n};\n\n/** Compact CO₂e for the donut center (e.g. 1.24M) — full figures live in the KPIs. */\nconst compactTonnes = (n: number): string =>\n new Intl.NumberFormat('en', { notation: 'compact', maximumFractionDigits: 1 }).format(n);\n\nconst PAGE_SIZE = 25;\n\nconst today = (): string => new Date().toISOString().slice(0, 10);\n\ninterface RecordFormState {\n assetId: string;\n scope: TheEmissionScope;\n co2eTonnes: string;\n periodStart: string;\n periodEnd: string;\n source: string;\n methodology: string;\n notes: string;\n}\n\nconst emptyForm = (): RecordFormState => ({\n assetId: '',\n scope: 'SCOPE_1',\n co2eTonnes: '',\n periodStart: today(),\n periodEnd: today(),\n source: '',\n methodology: '',\n notes: '',\n});\n\n/** KPI-card footer: this scope's share of the page total, tinted by scope. */\nconst renderScopeShare = (\n value: number,\n total: number,\n scope: TheEmissionScope\n): ReactNode => {\n if (total <= 0) return undefined;\n const pct = (value / total) * 100;\n return (\n <div className=\"space-y-2\">\n <MiniBar value={pct} barClassName={SCOPE_BAR[scope]} height={4} />\n <p className=\"text-xs tabular-nums text-text-tertiary\">{formatPercent(pct)} of total</p>\n </div>\n );\n};\n\nexport function EmissionsPage() {\n const notify = useNotify();\n const [scope, setScope] = useState<TheEmissionScope | 'ALL'>('ALL');\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 // Track the row whose verification is in flight, so only that row's Verify\n // button disables (verifyEmission.isPending is shared across all rows).\n const [verifyingId, setVerifyingId] = 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 const assetOptions = assetsQuery.data?.items ?? [];\n\n const { data, isLoading, isError, refetch } = useTheEmissionRecords({\n scope: scope === 'ALL' ? undefined : scope,\n assetId: assetId === 'ALL' ? undefined : assetId,\n pagination: { limit: PAGE_SIZE, offset: pager.page * PAGE_SIZE },\n });\n const recordEmission = useTheRecordEmission();\n const verifyEmission = useTheVerifyEmission();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n // Broad, scope-agnostic pull (respects the asset filter only) so the header\n // donut + KPI totals reflect the whole ledger — stable as the scope tabs and\n // pagination narrow the table below.\n const metricsQuery = useTheEmissionRecords({\n assetId: assetId === 'ALL' ? undefined : assetId,\n pagination: { limit: 200 },\n });\n const metricsRows = useMemo(() => metricsQuery.data?.items ?? [], [metricsQuery.data]);\n const metricsCount = metricsRows.length;\n\n // KPI + donut summary — CO₂e total plus per-scope breakdown across the ledger.\n const { total, byScope } = useMemo(() => {\n const byScope: Record<TheEmissionScope, number> = {\n SCOPE_1: 0,\n SCOPE_2: 0,\n SCOPE_3: 0,\n };\n let total = 0;\n for (const r of metricsRows) {\n total += r.co2eTonnes;\n byScope[r.scope] += r.co2eTonnes;\n }\n return { total, byScope };\n }, [metricsRows]);\n\n // Donut segments — one per scope that actually carries CO₂e.\n const scopeSegments = useMemo<Segment[]>(\n () =>\n SCOPES.filter((s) => byScope[s] > 0).map((s) => ({\n label: SCOPE_LABEL[s],\n value: byScope[s],\n color: SCOPE_DONUT_COLOR[s],\n })),\n [byScope]\n );\n\n const columns: Column<TheEmissionRecord>[] = [\n {\n key: 'scope',\n header: 'Scope',\n render: (r) => <StatusBadge status={r.scope} />,\n },\n {\n key: 'asset',\n header: 'Asset',\n render: (r) => (\n <span className=\"text-text-secondary\">\n {r.assetId ? (assetMap[r.assetId] ?? `${r.assetId.slice(0, 8)}…`) : '—'}\n </span>\n ),\n },\n {\n key: 'co2e',\n header: 'CO₂e',\n align: 'right',\n sortValue: (r) => r.co2eTonnes,\n render: (r) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatTonnesCo2e(r.co2eTonnes)}\n </span>\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: 'source',\n header: 'Source',\n hideOnMobile: true,\n render: (r) => <span className=\"text-text-secondary\">{r.source ?? '—'}</span>,\n },\n {\n key: 'verified',\n header: 'Verified',\n render: (r) =>\n r.verified ? (\n <span className=\"inline-flex items-center gap-1.5 text-status-success-text\">\n <CheckCircle2 className=\"h-4 w-4\" />\n <span className=\"text-xs font-medium\">Verified</span>\n {r.verifiedAt && (\n <span className=\"text-xs font-normal tabular-nums text-text-tertiary\">\n {formatDate(r.verifiedAt)}\n </span>\n )}\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1.5 text-text-tertiary\">\n <Circle className=\"h-3.5 w-3.5\" />\n <span className=\"text-xs\">Unverified</span>\n </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 key: 'verify',\n header: '',\n align: 'right',\n render: (r) =>\n !r.verified ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n setVerifyingId(r.id);\n verifyEmission.mutate(r.id, {\n onSuccess: () => notify.success('Emission verified'),\n onError: (e) => notify.fromError(e),\n onSettled: () => setVerifyingId(null),\n });\n }}\n disabled={verifyingId === r.id}\n >\n {verifyingId === r.id ? 'Verifying…' : 'Verify'}\n </Button>\n ) : null,\n },\n ];\n\n const openDialog = () => {\n setForm(emptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const handleRecord = async () => {\n const co2e = Number(form.co2eTonnes);\n if (!form.co2eTonnes || Number.isNaN(co2e) || co2e <= 0) {\n const message = 'CO₂e (tonnes) must be greater than zero.';\n setFormError(message);\n notify.error(message);\n return;\n }\n if (!form.periodStart || !form.periodEnd) {\n setFormError('Period start and end are required.');\n return;\n }\n const start = toBackendDate(form.periodStart);\n const end = toBackendDate(form.periodEnd);\n if (!start || !end) {\n setFormError('Period start and end are required.');\n return;\n }\n if (new Date(end).getTime() < new Date(start).getTime()) {\n setFormError('Period end must be on or after the period start.');\n return;\n }\n setFormError(null);\n try {\n await recordEmission.mutateAsync({\n assetId: form.assetId || null,\n scope: form.scope,\n co2eTonnes: co2e,\n periodStart: start,\n periodEnd: end,\n source: form.source.trim() || null,\n methodology: form.methodology.trim() || null,\n notes: form.notes.trim() || null,\n });\n notify.success('Emission recorded');\n setDialogOpen(false);\n setForm(emptyForm());\n } catch (err) {\n notify.fromError(err);\n setFormError(err instanceof Error ? err.message : 'Failed to record emission.');\n }\n };\n\n return (\n <PageLayout\n title=\"Emissions\"\n description=\"ESG greenhouse-gas ledger across every asset — GHG Protocol Scope 1 / 2 / 3, with third-party verification.\"\n actions={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record emission\n </Button>\n }\n >\n {/* Header — scope-breakdown donut beside the CO₂e KPI cards (both broad). */}\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-3\">\n <div className=\"flex flex-col rounded-[var(--radius-card)] border border-border-subtle bg-bg-surface p-5 shadow-elevation-1\">\n <div className=\"mb-1 flex items-center justify-between gap-2\">\n <h3 className=\"text-sm font-semibold text-text-secondary\">Emissions by scope</h3>\n <Cloud className=\"h-4 w-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n </div>\n {metricsQuery.isLoading ? (\n <p className=\"flex flex-1 items-center py-8 text-sm text-text-tertiary\">\n Loading breakdown…\n </p>\n ) : scopeSegments.length > 0 ? (\n <DonutChart\n className=\"mt-3\"\n segments={scopeSegments}\n size={148}\n thickness={0.32}\n centerLabel={compactTonnes(total)}\n centerCaption=\"t CO₂e\"\n valueFormatter={formatTonnesCo2e}\n />\n ) : (\n <p className=\"flex flex-1 items-center py-8 text-sm text-text-tertiary\">\n No emissions recorded yet.\n </p>\n )}\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:col-span-2\">\n <MetricsCard\n icon={<Factory className=\"h-5 w-5\" />}\n value={metricsCount > 0 ? formatTonnesCo2e(total) : '—'}\n label=\"Total CO₂e\"\n featured\n footer={\n <p className=\"text-xs tabular-nums text-text-tertiary\">\n {metricsCount} record{metricsCount === 1 ? '' : 's'} · Scope 1 · 2 · 3\n </p>\n }\n />\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={metricsCount > 0 ? formatTonnesCo2e(byScope.SCOPE_1) : '—'}\n label=\"Scope 1 · Direct\"\n footer={renderScopeShare(byScope.SCOPE_1, total, 'SCOPE_1')}\n />\n <MetricsCard\n icon={<Zap className=\"h-5 w-5\" />}\n value={metricsCount > 0 ? formatTonnesCo2e(byScope.SCOPE_2) : '—'}\n label=\"Scope 2 · Purchased energy\"\n footer={renderScopeShare(byScope.SCOPE_2, total, 'SCOPE_2')}\n />\n <MetricsCard\n icon={<Truck className=\"h-5 w-5\" />}\n value={metricsCount > 0 ? formatTonnesCo2e(byScope.SCOPE_3) : '—'}\n label=\"Scope 3 · Value chain\"\n footer={renderScopeShare(byScope.SCOPE_3, total, 'SCOPE_3')}\n />\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"mt-6 mb-5 flex flex-wrap items-center gap-3\">\n <FilterTabs\n options={SCOPE_FILTERS}\n value={scope}\n onChange={(v) => {\n setScope(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 {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 emission records…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load emission records.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Cloud className=\"h-10 w-10\" />}\n title=\"No emissions recorded\"\n description={\n scope === 'ALL' && assetId === 'ALL'\n ? 'Record a CO₂e figure to start building your Scope 1–3 emissions ledger.'\n : 'No emission records match these filters.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record emission\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\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 emission dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => {\n setDialogOpen(open);\n if (!open) {\n setForm(emptyForm());\n setFormError(null);\n }\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record emission</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a CO₂e figure for a reporting period — optionally attributed to an asset.\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-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emScope\">Scope</Label>\n <Select\n value={form.scope}\n onValueChange={(v) => setForm({ ...form, scope: v as TheEmissionScope })}\n >\n <SelectTrigger id=\"emScope\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {SCOPES.map((s) => (\n <SelectItem key={s} value={s}>\n {formatEnumLabel(s)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emCo2e\">CO₂e (tonnes) *</Label>\n <Input\n id=\"emCo2e\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.co2eTonnes}\n onChange={(e) => setForm({ ...form, co2eTonnes: e.target.value })}\n placeholder=\"1250\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emAsset\">Asset</Label>\n <Select\n value={form.assetId}\n onValueChange={(v) => setForm({ ...form, assetId: v })}\n >\n <SelectTrigger id=\"emAsset\">\n <SelectValue placeholder=\"Optional — org-wide if unset\" />\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=\"emStart\">Period start *</Label>\n <Input\n id=\"emStart\"\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=\"emEnd\">Period end *</Label>\n <Input\n id=\"emEnd\"\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=\"emSource\">Source</Label>\n <Input\n id=\"emSource\"\n value={form.source}\n onChange={(e) => setForm({ ...form, source: e.target.value })}\n placeholder=\"Flaring, grid electricity…\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emMethod\">Methodology</Label>\n <Input\n id=\"emMethod\"\n value={form.methodology}\n onChange={(e) => setForm({ ...form, methodology: e.target.value })}\n placeholder=\"GHG Protocol / IPCC / API\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"emNotes\">Notes</Label>\n <Textarea\n id=\"emNotes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this figure.\"\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={() => setDialogOpen(false)}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={recordEmission.isPending}>\n {recordEmission.isPending ? 'Recording…' : 'Record emission'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { useTheGlobalFuel as e } from "../../context/TheGlobalFuelContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { formatCoords as r, formatDate as i, formatEnumLabel as a, formatNumber as o, formatTonnesCo2e as ee, formatVolume as s } from "../../utils/formatters.js";
|
|
5
|
+
import { StatusBadge as c } from "../../components/StatusBadge.js";
|
|
6
|
+
import { ErrorView as l, LoadingView as u } from "../../components/StateViews.js";
|
|
7
|
+
import { EmptyState as te } from "../../components/EmptyState.js";
|
|
8
|
+
import { Timeline as ne } from "../../components/viz/Timeline.js";
|
|
9
|
+
import { StepProgress as re } from "../../components/viz/StepProgress.js";
|
|
10
|
+
import "../../context/index.js";
|
|
11
|
+
import { useTheAsset as ie, useTheAssets as ae, useTheComplianceCerts as oe, useTheContract as se, useTheContracts as ce, useTheEmissionRecords as le, useTheInventoryLots as ue } from "../../hooks/useTheGlobalFuelApi.js";
|
|
12
|
+
import "../../hooks/index.js";
|
|
13
|
+
import { FileLink as d } from "../../components/FileLink.js";
|
|
14
|
+
import { WorldMap as de } from "../../components/WorldMap.js";
|
|
15
|
+
import "../../components/index.js";
|
|
16
|
+
import { useMemo as f, useState as fe } from "react";
|
|
17
|
+
import { Boxes as p, CheckCircle2 as m, Factory as pe, Fuel as me, Gauge as h, Layers as he, Link2 as ge, MapPin as g, Route as _e, Shield as ve, ShieldCheck as _, Ship as ye } from "lucide-react";
|
|
18
|
+
import { Fragment as be, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
19
|
+
import { GlassCard as b, Select as xe, SelectContent as Se, SelectItem as Ce, SelectTrigger as we, SelectValue as Te } from "@burdenoff/fe-libs/ui";
|
|
20
|
+
//#region src/theglobalfuel/pages/provenance/ProvenancePage.tsx
|
|
21
|
+
var x = { limit: 200 };
|
|
22
|
+
function S(e) {
|
|
23
|
+
return (t, n) => {
|
|
24
|
+
let r = new Date(e(t) ?? t.createdAt).getTime();
|
|
25
|
+
return new Date(e(n) ?? n.createdAt).getTime() - r;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
var C = {
|
|
29
|
+
done: "bg-status-success-bg-subtle text-status-success-text",
|
|
30
|
+
active: "bg-[var(--color-accent-soft)] text-accent",
|
|
31
|
+
todo: "bg-bg-sunken text-text-muted",
|
|
32
|
+
locked: "bg-bg-sunken text-text-muted"
|
|
33
|
+
}, w = {
|
|
34
|
+
done: "bg-status-success-bg-subtle text-status-success-text",
|
|
35
|
+
active: "bg-[var(--color-accent-soft)] text-accent",
|
|
36
|
+
todo: "bg-bg-sunken text-text-tertiary",
|
|
37
|
+
locked: "bg-bg-sunken text-text-tertiary"
|
|
38
|
+
}, T = {
|
|
39
|
+
done: "bg-status-success-text",
|
|
40
|
+
active: "bg-[var(--color-accent-primary)] motion-safe:animate-vc-glow-pulse",
|
|
41
|
+
todo: "bg-border-strong",
|
|
42
|
+
locked: "bg-border-strong"
|
|
43
|
+
};
|
|
44
|
+
function Ee({ node: e }) {
|
|
45
|
+
return /* @__PURE__ */ y(b, {
|
|
46
|
+
treatment: "glass",
|
|
47
|
+
className: t("relative flex h-full flex-col gap-3 p-5 transition-shadow", e.state === "done" && "shadow-[inset_0_2px_0_0_var(--color-status-success-text)]", e.state === "active" && "shadow-[inset_0_2px_0_0_var(--color-accent-primary)]"),
|
|
48
|
+
children: [
|
|
49
|
+
/* @__PURE__ */ y("div", {
|
|
50
|
+
className: "flex items-center justify-between",
|
|
51
|
+
children: [/* @__PURE__ */ v("span", {
|
|
52
|
+
className: t("flex size-7 items-center justify-center rounded-full text-xs font-semibold tabular-nums", C[e.state]),
|
|
53
|
+
children: e.step
|
|
54
|
+
}), /* @__PURE__ */ v("span", {
|
|
55
|
+
"aria-hidden": "true",
|
|
56
|
+
className: t("size-2 rounded-full", T[e.state])
|
|
57
|
+
})]
|
|
58
|
+
}),
|
|
59
|
+
/* @__PURE__ */ v("span", {
|
|
60
|
+
className: t("flex size-11 items-center justify-center rounded-xl [&_svg]:size-6", w[e.state]),
|
|
61
|
+
children: e.icon
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ y("div", {
|
|
64
|
+
className: "min-w-0",
|
|
65
|
+
children: [
|
|
66
|
+
/* @__PURE__ */ v("p", {
|
|
67
|
+
className: "text-xs font-medium uppercase tracking-wide text-text-tertiary",
|
|
68
|
+
children: e.label
|
|
69
|
+
}),
|
|
70
|
+
/* @__PURE__ */ v("p", {
|
|
71
|
+
className: t("mt-1 truncate text-base font-semibold", e.present ? "text-text-primary" : "text-text-muted"),
|
|
72
|
+
title: typeof e.primary == "string" ? e.primary : void 0,
|
|
73
|
+
children: e.present ? e.primary : "Pending"
|
|
74
|
+
}),
|
|
75
|
+
e.present && e.secondary && /* @__PURE__ */ v("p", {
|
|
76
|
+
className: "mt-0.5 line-clamp-2 text-xs text-text-secondary",
|
|
77
|
+
children: e.secondary
|
|
78
|
+
})
|
|
79
|
+
]
|
|
80
|
+
}),
|
|
81
|
+
(e.badge || e.present && e.fileId) && /* @__PURE__ */ y("div", {
|
|
82
|
+
className: "mt-auto flex flex-wrap items-center gap-2 pt-1",
|
|
83
|
+
children: [e.badge, e.present && e.fileId && /* @__PURE__ */ v(d, { fileId: e.fileId })]
|
|
84
|
+
})
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function E({ icon: e, label: t, value: n, caption: r }) {
|
|
89
|
+
return /* @__PURE__ */ y("div", {
|
|
90
|
+
className: "flex items-start gap-3",
|
|
91
|
+
children: [/* @__PURE__ */ v("span", {
|
|
92
|
+
className: "mt-0.5 flex size-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-accent [&_svg]:size-4",
|
|
93
|
+
children: e
|
|
94
|
+
}), /* @__PURE__ */ y("div", {
|
|
95
|
+
className: "min-w-0",
|
|
96
|
+
children: [
|
|
97
|
+
/* @__PURE__ */ v("p", {
|
|
98
|
+
className: "text-xs font-medium uppercase tracking-wide text-text-tertiary",
|
|
99
|
+
children: t
|
|
100
|
+
}),
|
|
101
|
+
/* @__PURE__ */ v("p", {
|
|
102
|
+
className: "mt-0.5 truncate text-sm font-semibold text-text-primary",
|
|
103
|
+
children: n
|
|
104
|
+
}),
|
|
105
|
+
r && /* @__PURE__ */ v("p", {
|
|
106
|
+
className: "truncate text-xs text-text-tertiary",
|
|
107
|
+
children: r
|
|
108
|
+
})
|
|
109
|
+
]
|
|
110
|
+
})]
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function D() {
|
|
114
|
+
let { navigateTo: d } = e(), C = ae({ pagination: x }), w = f(() => C.data?.items ?? [], [C.data]), [T, D] = fe(""), O = T || w[0]?.id || "", k = ie(O || void 0), A = ue({ pagination: x }), j = le({
|
|
115
|
+
assetId: O || void 0,
|
|
116
|
+
pagination: x
|
|
117
|
+
}), M = oe(O || void 0, void 0, void 0, x), N = ce({
|
|
118
|
+
commodity: k.data?.commodity,
|
|
119
|
+
pagination: x
|
|
120
|
+
}), P = f(() => {
|
|
121
|
+
let e = [...N.data?.items ?? []].sort(S((e) => e.createdAt));
|
|
122
|
+
return (e.find((e) => e.deliveredVolume > 0 && e.side === "SELL") ?? e.find((e) => e.deliveredVolume > 0) ?? e.find((e) => e.side === "SELL") ?? e[0])?.id;
|
|
123
|
+
}, [N.data]), F = se(P), I = f(() => [...k.data?.productionRecords ?? []].sort(S((e) => e.periodEnd)), [k.data]), L = I[0], R = f(() => (A.data?.items ?? []).filter((e) => e.assetId === O).sort(S((e) => e.producedAt)), [A.data, O]), z = R[0], B = f(() => F.data?.shipments ?? [], [F.data]), V = f(() => [...B].filter((e) => e.status === "DELIVERED").sort(S((e) => e.deliveredAt))[0], [B]), H = f(() => V ?? [...B].sort(S((e) => e.departedAt ?? e.scheduledAt))[0], [V, B]), De = f(() => N.data?.items.find((e) => e.id === P), [N.data, P]), U = f(() => M.data?.items ?? [], [M.data]), W = f(() => U.find((e) => e.category === "CHAIN_OF_CUSTODY") ?? U.find((e) => e.status === "APPROVED") ?? U[0], [U]), G = W?.status === "APPROVED", Oe = f(() => j.data?.items ?? [], [j.data]), ke = Oe.reduce((e, t) => e + (t.co2eTonnes ?? 0), 0), Ae = Oe.filter((e) => e.verified).length, K = k.data, q = [
|
|
124
|
+
!!K,
|
|
125
|
+
I.length > 0,
|
|
126
|
+
R.length > 0,
|
|
127
|
+
!!H,
|
|
128
|
+
!!V,
|
|
129
|
+
G
|
|
130
|
+
], je = q.filter(Boolean).length, Me = q.findIndex((e) => !e), J = Me === -1 ? q.length : Me, Y = G && !!V, X = (e) => q[e] ? "done" : e === J ? "active" : "todo", Ne = [
|
|
131
|
+
{
|
|
132
|
+
label: "Asset",
|
|
133
|
+
description: "Origin"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: "Production",
|
|
137
|
+
description: "Extracted"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: "Inventory",
|
|
141
|
+
description: "Lotted"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
label: "Shipment",
|
|
145
|
+
description: "In transit"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
label: "Delivered",
|
|
149
|
+
description: "Received"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
label: "Certificate",
|
|
153
|
+
description: "Verified"
|
|
154
|
+
}
|
|
155
|
+
], Z = H && (H.origin || H.destination) ? [H.origin, H.destination].filter(Boolean).join(" → ") : void 0, Pe = K ? [
|
|
156
|
+
{
|
|
157
|
+
step: 1,
|
|
158
|
+
icon: /* @__PURE__ */ v(g, {}),
|
|
159
|
+
label: "Asset",
|
|
160
|
+
primary: K.name,
|
|
161
|
+
secondary: K.location ?? r(K.latitude, K.longitude),
|
|
162
|
+
badge: /* @__PURE__ */ v(c, { status: K.status }),
|
|
163
|
+
state: X(0),
|
|
164
|
+
present: !0
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
step: 2,
|
|
168
|
+
icon: /* @__PURE__ */ v(h, {}),
|
|
169
|
+
label: "Production",
|
|
170
|
+
primary: L ? s(L.volume, L.unit) : "Not yet recorded",
|
|
171
|
+
secondary: L ? `${i(L.periodStart)} – ${i(L.periodEnd)}` : void 0,
|
|
172
|
+
state: X(1),
|
|
173
|
+
present: !!L
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
step: 3,
|
|
177
|
+
icon: /* @__PURE__ */ v(p, {}),
|
|
178
|
+
label: "Inventory lot",
|
|
179
|
+
primary: z ? z.lotCode : "No lot yet",
|
|
180
|
+
secondary: z ? s(z.quantity, z.unit) : void 0,
|
|
181
|
+
badge: z ? /* @__PURE__ */ v(c, { status: z.status }) : void 0,
|
|
182
|
+
state: X(2),
|
|
183
|
+
present: !!z
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
step: 4,
|
|
187
|
+
icon: /* @__PURE__ */ v(ye, {}),
|
|
188
|
+
label: "Shipment",
|
|
189
|
+
primary: H ? H.reference : "Not shipped",
|
|
190
|
+
secondary: H ? [a(H.mode), Z].filter(Boolean).join(" · ") : void 0,
|
|
191
|
+
badge: H ? /* @__PURE__ */ v(c, { status: H.status }) : void 0,
|
|
192
|
+
fileId: H?.document?.id,
|
|
193
|
+
state: X(3),
|
|
194
|
+
present: !!H
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
step: 5,
|
|
198
|
+
icon: /* @__PURE__ */ v(m, {}),
|
|
199
|
+
label: "Delivered",
|
|
200
|
+
primary: V ? s(V.volume, V.unit) : "Awaiting delivery",
|
|
201
|
+
secondary: V && [V.deliveredAt ? i(V.deliveredAt) : null, De ? `Contract ${De.reference}` : null].filter(Boolean).join(" · ") || void 0,
|
|
202
|
+
state: X(4),
|
|
203
|
+
present: !!V
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
step: 6,
|
|
207
|
+
icon: /* @__PURE__ */ v(_, {}),
|
|
208
|
+
label: "Certificate",
|
|
209
|
+
primary: W ? W.certNumber ?? W.title : "No certificate",
|
|
210
|
+
secondary: W ? a(W.category) : void 0,
|
|
211
|
+
badge: W ? /* @__PURE__ */ v(c, { status: W.status }) : void 0,
|
|
212
|
+
fileId: W?.evidence?.id,
|
|
213
|
+
state: X(5),
|
|
214
|
+
present: !!W
|
|
215
|
+
}
|
|
216
|
+
] : [], Fe = f(() => {
|
|
217
|
+
if (!K) return [];
|
|
218
|
+
let e = [], t = (t, n) => {
|
|
219
|
+
if (!t) return;
|
|
220
|
+
let r = new Date(t).getTime();
|
|
221
|
+
Number.isNaN(r) || e.push({
|
|
222
|
+
date: t,
|
|
223
|
+
item: n
|
|
224
|
+
});
|
|
225
|
+
};
|
|
226
|
+
return t(K.commissionedAt ?? K.createdAt, {
|
|
227
|
+
title: "Asset registered",
|
|
228
|
+
meta: K.name,
|
|
229
|
+
tone: "accent",
|
|
230
|
+
icon: /* @__PURE__ */ v(g, { className: "h-3 w-3" })
|
|
231
|
+
}), L && t(L.periodEnd ?? L.createdAt, {
|
|
232
|
+
title: "Production recorded",
|
|
233
|
+
meta: s(L.volume, L.unit),
|
|
234
|
+
tone: "info",
|
|
235
|
+
icon: /* @__PURE__ */ v(h, { className: "h-3 w-3" })
|
|
236
|
+
}), z && t(z.producedAt ?? z.createdAt, {
|
|
237
|
+
title: "Inventory lot created",
|
|
238
|
+
meta: `${z.lotCode} · ${s(z.quantity, z.unit)}`,
|
|
239
|
+
tone: "info",
|
|
240
|
+
icon: /* @__PURE__ */ v(p, { className: "h-3 w-3" })
|
|
241
|
+
}), H && t(H.departedAt ?? H.scheduledAt ?? H.createdAt, {
|
|
242
|
+
title: H.departedAt ? "Shipment departed" : "Shipment scheduled",
|
|
243
|
+
meta: [H.reference, Z].filter(Boolean).join(" · "),
|
|
244
|
+
tone: "accent",
|
|
245
|
+
icon: /* @__PURE__ */ v(ye, { className: "h-3 w-3" })
|
|
246
|
+
}), V?.deliveredAt && t(V.deliveredAt, {
|
|
247
|
+
title: "Delivered",
|
|
248
|
+
meta: s(V.volume, V.unit),
|
|
249
|
+
tone: "success",
|
|
250
|
+
icon: /* @__PURE__ */ v(m, { className: "h-3 w-3" })
|
|
251
|
+
}), W && t(W.issuedAt ?? W.createdAt, {
|
|
252
|
+
title: G ? "Certificate approved" : "Certificate registered",
|
|
253
|
+
meta: W.certNumber ?? W.title,
|
|
254
|
+
tone: G ? "success" : "warning",
|
|
255
|
+
icon: /* @__PURE__ */ v(_, { className: "h-3 w-3" })
|
|
256
|
+
}), e.sort((e, t) => new Date(e.date).getTime() - new Date(t.date).getTime()).map(({ date: e, item: t }) => ({
|
|
257
|
+
...t,
|
|
258
|
+
time: i(e)
|
|
259
|
+
}));
|
|
260
|
+
}, [
|
|
261
|
+
K,
|
|
262
|
+
L,
|
|
263
|
+
z,
|
|
264
|
+
H,
|
|
265
|
+
V,
|
|
266
|
+
W,
|
|
267
|
+
G,
|
|
268
|
+
Z
|
|
269
|
+
]), Q = K && typeof K.latitude == "number" && typeof K.longitude == "number" ? {
|
|
270
|
+
lat: K.latitude,
|
|
271
|
+
lng: K.longitude,
|
|
272
|
+
label: K.name,
|
|
273
|
+
primary: !0
|
|
274
|
+
} : null, $ = "Pit to port to certificate — end-to-end traceability from origin to verified certificate.";
|
|
275
|
+
return C.isLoading && w.length === 0 ? /* @__PURE__ */ v(n, {
|
|
276
|
+
title: "Provenance",
|
|
277
|
+
description: $,
|
|
278
|
+
children: /* @__PURE__ */ v(u, { label: "Loading assets…" })
|
|
279
|
+
}) : C.isError ? /* @__PURE__ */ v(n, {
|
|
280
|
+
title: "Provenance",
|
|
281
|
+
description: $,
|
|
282
|
+
children: /* @__PURE__ */ v(l, {
|
|
283
|
+
message: "Couldn't load assets to trace.",
|
|
284
|
+
onRetry: () => C.refetch()
|
|
285
|
+
})
|
|
286
|
+
}) : w.length === 0 ? /* @__PURE__ */ v(n, {
|
|
287
|
+
title: "Provenance",
|
|
288
|
+
description: $,
|
|
289
|
+
children: /* @__PURE__ */ v(te, {
|
|
290
|
+
illustration: "empty-data",
|
|
291
|
+
icon: /* @__PURE__ */ v(ge, { className: "h-10 w-10" }),
|
|
292
|
+
title: "No assets to trace",
|
|
293
|
+
description: "Register a resource asset to build its end-to-end chain of custody.",
|
|
294
|
+
action: /* @__PURE__ */ v("button", {
|
|
295
|
+
type: "button",
|
|
296
|
+
onClick: () => d("assets"),
|
|
297
|
+
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90",
|
|
298
|
+
children: "Go to assets"
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
}) : /* @__PURE__ */ y(n, {
|
|
302
|
+
title: "Provenance",
|
|
303
|
+
description: $,
|
|
304
|
+
children: [/* @__PURE__ */ y(b, {
|
|
305
|
+
treatment: "glass",
|
|
306
|
+
className: "flex flex-col gap-4 p-5 sm:flex-row sm:items-center sm:justify-between",
|
|
307
|
+
children: [/* @__PURE__ */ y("div", {
|
|
308
|
+
className: "flex min-w-0 flex-1 flex-col gap-1.5",
|
|
309
|
+
children: [/* @__PURE__ */ v("label", {
|
|
310
|
+
htmlFor: "provenance-asset",
|
|
311
|
+
className: "text-xs font-medium uppercase tracking-wide text-text-tertiary",
|
|
312
|
+
children: "Trace asset"
|
|
313
|
+
}), /* @__PURE__ */ y(xe, {
|
|
314
|
+
value: O,
|
|
315
|
+
onValueChange: D,
|
|
316
|
+
children: [/* @__PURE__ */ v(we, {
|
|
317
|
+
id: "provenance-asset",
|
|
318
|
+
className: "h-10 w-full sm:w-96",
|
|
319
|
+
"aria-label": "Select an asset to trace",
|
|
320
|
+
children: /* @__PURE__ */ v(Te, { placeholder: "Select an asset to trace" })
|
|
321
|
+
}), /* @__PURE__ */ v(Se, { children: w.map((e) => /* @__PURE__ */ y(Ce, {
|
|
322
|
+
value: e.id,
|
|
323
|
+
children: [
|
|
324
|
+
e.name,
|
|
325
|
+
" · ",
|
|
326
|
+
a(e.commodity)
|
|
327
|
+
]
|
|
328
|
+
}, e.id)) })]
|
|
329
|
+
})]
|
|
330
|
+
}), /* @__PURE__ */ y("div", {
|
|
331
|
+
className: t("flex items-center gap-2 self-start rounded-full px-3.5 py-1.5 text-sm font-medium", Y ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-secondary"),
|
|
332
|
+
children: [v(Y ? _ : ve, { className: "h-4 w-4" }), Y ? "Chain of custody verified" : `Traceability in progress · ${je}/6`]
|
|
333
|
+
})]
|
|
334
|
+
}), k.isLoading ? /* @__PURE__ */ v("div", {
|
|
335
|
+
className: "mt-6",
|
|
336
|
+
children: /* @__PURE__ */ v(u, { label: "Assembling chain of custody…" })
|
|
337
|
+
}) : k.isError || !K ? /* @__PURE__ */ v("div", {
|
|
338
|
+
className: "mt-6",
|
|
339
|
+
children: /* @__PURE__ */ v(l, {
|
|
340
|
+
message: "Couldn't load this asset's provenance.",
|
|
341
|
+
onRetry: () => k.refetch()
|
|
342
|
+
})
|
|
343
|
+
}) : /* @__PURE__ */ y(be, { children: [
|
|
344
|
+
/* @__PURE__ */ v(b, {
|
|
345
|
+
treatment: "glass",
|
|
346
|
+
className: "mt-6 p-5",
|
|
347
|
+
children: /* @__PURE__ */ v(re, {
|
|
348
|
+
steps: Ne,
|
|
349
|
+
current: J
|
|
350
|
+
})
|
|
351
|
+
}),
|
|
352
|
+
/* @__PURE__ */ v("div", {
|
|
353
|
+
className: "mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6",
|
|
354
|
+
children: Pe.map((e) => /* @__PURE__ */ v(Ee, { node: e }, e.step))
|
|
355
|
+
}),
|
|
356
|
+
/* @__PURE__ */ y("div", {
|
|
357
|
+
className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
358
|
+
children: [/* @__PURE__ */ y(b, {
|
|
359
|
+
treatment: "glass",
|
|
360
|
+
className: "p-5 lg:col-span-2",
|
|
361
|
+
children: [/* @__PURE__ */ y("div", {
|
|
362
|
+
className: "mb-4 flex items-center gap-2.5",
|
|
363
|
+
children: [/* @__PURE__ */ v("span", {
|
|
364
|
+
className: "flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-accent",
|
|
365
|
+
children: /* @__PURE__ */ v(_e, { className: "h-4 w-4" })
|
|
366
|
+
}), /* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h2", {
|
|
367
|
+
className: "text-base font-semibold text-text-primary",
|
|
368
|
+
children: "Traceability timeline"
|
|
369
|
+
}), /* @__PURE__ */ v("p", {
|
|
370
|
+
className: "text-xs text-text-tertiary",
|
|
371
|
+
children: "Recorded custody events, oldest to newest."
|
|
372
|
+
})] })]
|
|
373
|
+
}), Fe.length > 0 ? /* @__PURE__ */ v(ne, { items: Fe }) : /* @__PURE__ */ v("div", {
|
|
374
|
+
className: "rounded-xl border border-dashed border-border-seam py-8 text-center text-sm text-text-secondary",
|
|
375
|
+
children: "No dated custody events yet."
|
|
376
|
+
})]
|
|
377
|
+
}), /* @__PURE__ */ y(b, {
|
|
378
|
+
treatment: "glass",
|
|
379
|
+
className: "flex flex-col gap-4 p-5",
|
|
380
|
+
children: [/* @__PURE__ */ y("div", {
|
|
381
|
+
className: "flex items-center gap-2.5",
|
|
382
|
+
children: [/* @__PURE__ */ v("span", {
|
|
383
|
+
className: "flex size-8 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-accent",
|
|
384
|
+
children: /* @__PURE__ */ v(g, { className: "h-4 w-4" })
|
|
385
|
+
}), /* @__PURE__ */ y("div", {
|
|
386
|
+
className: "min-w-0",
|
|
387
|
+
children: [/* @__PURE__ */ v("h2", {
|
|
388
|
+
className: "text-base font-semibold text-text-primary",
|
|
389
|
+
children: "Origin"
|
|
390
|
+
}), /* @__PURE__ */ v("p", {
|
|
391
|
+
className: "truncate text-xs text-text-tertiary",
|
|
392
|
+
children: K.location ?? a(K.kind)
|
|
393
|
+
})]
|
|
394
|
+
})]
|
|
395
|
+
}), Q ? /* @__PURE__ */ v(de, { points: [Q] }) : /* @__PURE__ */ v("div", {
|
|
396
|
+
className: "rounded-xl border border-dashed border-border-seam py-8 text-center text-sm text-text-secondary",
|
|
397
|
+
children: "No coordinates recorded for this asset."
|
|
398
|
+
})]
|
|
399
|
+
})]
|
|
400
|
+
}),
|
|
401
|
+
/* @__PURE__ */ y(b, {
|
|
402
|
+
treatment: "glass",
|
|
403
|
+
className: "mt-6 p-5",
|
|
404
|
+
children: [/* @__PURE__ */ v("h2", {
|
|
405
|
+
className: "mb-4 text-base font-semibold text-text-primary",
|
|
406
|
+
children: "Provenance summary"
|
|
407
|
+
}), /* @__PURE__ */ y("div", {
|
|
408
|
+
className: "grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6",
|
|
409
|
+
children: [
|
|
410
|
+
/* @__PURE__ */ v(E, {
|
|
411
|
+
icon: /* @__PURE__ */ v(me, {}),
|
|
412
|
+
label: "Commodity",
|
|
413
|
+
value: a(K.commodity),
|
|
414
|
+
caption: a(K.kind)
|
|
415
|
+
}),
|
|
416
|
+
/* @__PURE__ */ v(E, {
|
|
417
|
+
icon: /* @__PURE__ */ v(g, {}),
|
|
418
|
+
label: "Origin",
|
|
419
|
+
value: K.location ?? K.name,
|
|
420
|
+
caption: r(K.latitude, K.longitude)
|
|
421
|
+
}),
|
|
422
|
+
/* @__PURE__ */ v(E, {
|
|
423
|
+
icon: /* @__PURE__ */ v(h, {}),
|
|
424
|
+
label: "Production",
|
|
425
|
+
value: `${o(I.length)} records`,
|
|
426
|
+
caption: L ? `Latest ${s(L.volume, L.unit)}` : "None yet"
|
|
427
|
+
}),
|
|
428
|
+
/* @__PURE__ */ v(E, {
|
|
429
|
+
icon: /* @__PURE__ */ v(p, {}),
|
|
430
|
+
label: "Inventory",
|
|
431
|
+
value: `${o(R.length)} lots`,
|
|
432
|
+
caption: z ? z.lotCode : "None yet"
|
|
433
|
+
}),
|
|
434
|
+
/* @__PURE__ */ v(E, {
|
|
435
|
+
icon: /* @__PURE__ */ v(pe, {}),
|
|
436
|
+
label: "Emissions",
|
|
437
|
+
value: j.isError ? "—" : ee(ke),
|
|
438
|
+
caption: `${Ae} verified`
|
|
439
|
+
}),
|
|
440
|
+
/* @__PURE__ */ v(E, {
|
|
441
|
+
icon: /* @__PURE__ */ v(_, {}),
|
|
442
|
+
label: "Certificates",
|
|
443
|
+
value: `${o(U.length)} tracked`,
|
|
444
|
+
caption: G ? "Custody approved" : W ? "Under review" : "None yet"
|
|
445
|
+
})
|
|
446
|
+
]
|
|
447
|
+
})]
|
|
448
|
+
}),
|
|
449
|
+
(K.reserveEstimates?.length ?? 0) > 0 && /* @__PURE__ */ y("p", {
|
|
450
|
+
className: "mt-4 flex items-center gap-1.5 text-xs text-text-tertiary",
|
|
451
|
+
children: [
|
|
452
|
+
/* @__PURE__ */ v(he, { className: "h-3.5 w-3.5" }),
|
|
453
|
+
o(K.reserveEstimates?.length ?? 0),
|
|
454
|
+
" reserve estimate",
|
|
455
|
+
(K.reserveEstimates?.length ?? 0) === 1 ? "" : "s",
|
|
456
|
+
" on record for this asset."
|
|
457
|
+
]
|
|
458
|
+
})
|
|
459
|
+
] })]
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
//#endregion
|
|
463
|
+
export { D as ProvenancePage };
|
|
464
|
+
|
|
465
|
+
//# sourceMappingURL=ProvenancePage.js.map
|