@burdenoff/microfe-theglobalfuel 2026.804.2 → 2026.827.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/dashboard/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — TheGlobalFuel resources operating-system overview.\n * @module theglobalfuel/pages/dashboard\n *\n * The hero landing (eager-loaded). Every figure is aggregated client-side from\n * the real `the*` list endpoints — no mock data. The page opens with the domain\n * KPI grid (registry / commerce / sustainability), then a rich analytics band:\n * - a PRODUCTION TREND area chart (metered output bucketed by month),\n * - a COMMODITY MIX donut (share of assets by commodity), and\n * - an EMISSIONS-BY-SCOPE donut (tonnes CO₂e per GHG scope);\n * a COMMODITY PORTFOLIO grid (one glass card per commodity present, with real\n * asset counts, summed capacity and a production sparkline); an ASSETS-BY-\n * LIFECYCLE distribution; and recent-activity tables. All charts derive from\n * the fetched arrays and degrade to pre-launch empty states.\n */\n\nimport { useMemo, type ReactNode } from 'react';\nimport {\n Layers,\n ScrollText,\n Ship,\n Factory,\n Boxes,\n ShieldCheck,\n ArrowRight,\n ArrowUpRight,\n Fuel,\n GitBranch,\n Gauge,\n Activity,\n ClipboardList,\n} from 'lucide-react';\nimport { GlassCard, Spinner } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { MiniBar } from '../../components/MiniBar';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport {\n AreaChart,\n DonutChart,\n Sparkline,\n chartPalette,\n type LabeledValue,\n type Segment,\n} from '../../components/viz';\nimport { cn } from '../../utils/cn';\nimport { useTheGlobalFuel } from '../../context';\nimport {\n useTheAssets,\n useTheContracts,\n useTheShipments,\n useTheEmissionRecords,\n useTheInventoryLots,\n useTheComplianceCerts,\n useTheProductionRecords,\n useThePageView,\n useTheTranslation,\n} from '../../hooks';\nimport {\n formatNumber,\n formatPercent,\n formatDate,\n formatRelativeTime,\n formatEnumLabel,\n formatVolume,\n formatTonnesCo2e,\n isExpired,\n} from '../../utils/formatters';\nimport type {\n TheAssetStatus,\n TheCommodityType,\n TheEmissionScope,\n TheOfftakeContract,\n TheProductionRecord,\n} from '../../types';\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst EXPIRY_WINDOW_DAYS = 30;\n\n/** True when a cert expires within the next 30 days (but hasn't yet). */\nconst isExpiringSoon = (date: string | null | undefined): boolean => {\n if (!date) return false;\n const t = new Date(date).getTime();\n if (Number.isNaN(t)) return false;\n const diff = t - Date.now();\n return diff >= 0 && diff <= EXPIRY_WINDOW_DAYS * DAY_MS;\n};\n\n/** Asset lifecycle → MiniBar fill token (semantic, maturity-ordered). */\nconst ASSET_STATUS_BAR: Record<TheAssetStatus, string> = {\n EXPLORATION: 'bg-[var(--color-accent-indigo)]',\n APPRAISAL: 'bg-[var(--color-accent-blue)]',\n DEVELOPMENT: 'bg-[var(--color-accent-teal)]',\n PRODUCING: 'bg-status-success-text',\n SUSPENDED: 'bg-status-warning-text',\n DECOMMISSIONING: 'bg-status-error-text',\n RETIRED: 'bg-[var(--color-text-tertiary)]',\n};\n\n/** GHG scope → donut segment color token (Scope 1 direct · 2 energy · 3 chain). */\nconst SCOPE_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\nconst ALL_SCOPES: TheEmissionScope[] = ['SCOPE_1', 'SCOPE_2', 'SCOPE_3'];\n\n// ── Month bucketing (UTC-stable, gap-free) ───────────────────────────────────\n\n/** UTC year-month key (e.g. \"2026-07\"). */\nconst monthKey = (d: Date): string =>\n `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`;\n\nconst MONTH_FMT = new Intl.DateTimeFormat(undefined, {\n month: 'short',\n year: '2-digit',\n timeZone: 'UTC',\n});\n\nconst monthLabel = (year: number, month1: number): string =>\n MONTH_FMT.format(new Date(Date.UTC(year, month1 - 1, 1)));\n\n/** Sum `valueOf` into UTC-month buckets keyed by the `dateOf` field. */\nfunction bucketByMonth<T>(\n rows: readonly T[],\n dateOf: (row: T) => string | null | undefined,\n valueOf: (row: T) => number\n): Map<string, number> {\n const buckets = new Map<string, number>();\n for (const row of rows) {\n const raw = dateOf(row);\n if (!raw) continue;\n const d = new Date(raw);\n if (Number.isNaN(d.getTime())) continue;\n const key = monthKey(d);\n buckets.set(key, (buckets.get(key) ?? 0) + valueOf(row));\n }\n return buckets;\n}\n\n/**\n * Expand month buckets into a gap-free ordered series from the earliest to the\n * latest populated month (so a trend line stays continuous), then keep only the\n * most recent `maxMonths` so a stray old record can't blow out the axis.\n */\nfunction monthlySeries(buckets: Map<string, number>, maxMonths = 18): LabeledValue[] {\n const keys = [...buckets.keys()].sort();\n if (keys.length === 0) return [];\n const parse = (k: string): [number, number] => {\n const [y, m] = k.split('-');\n return [Number(y), Number(m)];\n };\n const [maxY, maxM] = parse(keys[keys.length - 1]);\n let [y, m] = parse(keys[0]);\n const series: LabeledValue[] = [];\n let guard = 0;\n while ((y < maxY || (y === maxY && m <= maxM)) && guard < 240) {\n series.push({\n label: monthLabel(y, m),\n value: buckets.get(`${y}-${String(m).padStart(2, '0')}`) ?? 0,\n });\n m += 1;\n if (m > 12) {\n m = 1;\n y += 1;\n }\n guard += 1;\n }\n return series.length > maxMonths ? series.slice(series.length - maxMonths) : series;\n}\n\n/** Group rows by a discriminator, sorted by descending count, with % of total. */\nfunction distribution<T>(\n rows: T[],\n keyOf: (row: T) => string\n): { key: string; count: number; pct: number }[] {\n const counts = new Map<string, number>();\n for (const row of rows) {\n const k = keyOf(row);\n counts.set(k, (counts.get(k) ?? 0) + 1);\n }\n const total = rows.length;\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([key, count]) => ({ key, count, pct: total ? (count / total) * 100 : 0 }));\n}\n\n// ── Distribution card (MiniBar rows) ─────────────────────────────────────────\n\ninterface DistBar {\n key: string;\n label: ReactNode;\n /** Pre-formatted primary figure shown right of the bar. */\n value: string;\n /** 0–100 — drives the bar width + the muted percentage. */\n pct: number;\n barClassName: string;\n}\n\ninterface DistributionCardProps {\n title: string;\n description: string;\n icon: ReactNode;\n linkLabel: string;\n onLink: () => void;\n isLoading: boolean;\n isError: boolean;\n onRetry: () => void;\n emptyLabel: string;\n bars: DistBar[];\n footer?: ReactNode;\n className?: string;\n}\n\n/** A titled glass card that renders a compact set of MiniBar distribution rows. */\nfunction DistributionCard({\n title,\n description,\n icon,\n linkLabel,\n onLink,\n isLoading,\n isError,\n onRetry,\n emptyLabel,\n bars,\n footer,\n className,\n}: DistributionCardProps) {\n return (\n <GlassCard treatment=\"glass\" className={cn('flex flex-col p-5', className)}>\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-action-primary-bg\">\n {icon}\n </span>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">{description}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onLink}\n className=\"inline-flex shrink-0 items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n {linkLabel} <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n\n {isLoading ? (\n <div className=\"flex items-center gap-2 py-8 text-sm text-text-secondary\">\n <Spinner size=\"sm\" /> Loading…\n </div>\n ) : isError ? (\n <div className=\"flex flex-col items-start gap-2 py-6\">\n <p className=\"text-sm text-status-error-text\">\n Couldn&apos;t load {title.toLowerCase()}.\n </p>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg border border-border-seam px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Retry\n </button>\n </div>\n ) : bars.length === 0 ? (\n <div className=\"flex flex-1 flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-8 text-center\">\n <p className=\"text-sm text-text-secondary\">{emptyLabel}</p>\n </div>\n ) : (\n <div className=\"flex flex-1 flex-col justify-between gap-4\">\n <div className=\"space-y-3\">\n {bars.map((bar) => (\n <div key={bar.key} className=\"flex items-center gap-3\">\n <div className=\"w-24 shrink-0 truncate text-sm text-text-secondary sm:w-28\">\n {bar.label}\n </div>\n <MiniBar\n value={bar.pct}\n barClassName={bar.barClassName}\n className=\"flex-1\"\n height={8}\n />\n <span className=\"w-16 shrink-0 whitespace-nowrap text-right text-sm font-medium tabular-nums text-text-primary\">\n {bar.value}\n </span>\n <span className=\"hidden w-11 shrink-0 text-right text-xs tabular-nums text-text-tertiary sm:inline\">\n {formatPercent(bar.pct)}\n </span>\n </div>\n ))}\n </div>\n {footer && (\n <p className=\"border-t border-border-seam/70 pt-3 text-xs text-text-tertiary\">\n {footer}\n </p>\n )}\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ── Chart card (titled glass panel for a chart body) ─────────────────────────\n\ninterface ChartCardProps {\n title: string;\n caption: string;\n icon: ReactNode;\n link?: { label: string; onClick: () => void };\n isLoading: boolean;\n isError: boolean;\n onRetry: () => void;\n isEmpty: boolean;\n emptyLabel: string;\n footer?: ReactNode;\n className?: string;\n children: ReactNode;\n}\n\n/** A titled glass card wrapping a viz-kit chart, with loading/error/empty states. */\nfunction ChartCard({\n title,\n caption,\n icon,\n link,\n isLoading,\n isError,\n onRetry,\n isEmpty,\n emptyLabel,\n footer,\n className,\n children,\n}: ChartCardProps) {\n return (\n <GlassCard treatment=\"glass\" className={cn('flex flex-col p-5', className)}>\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-action-primary-bg\">\n {icon}\n </span>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">{caption}</p>\n </div>\n </div>\n {link ? (\n <button\n type=\"button\"\n onClick={link.onClick}\n className=\"inline-flex shrink-0 items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n {link.label} <ArrowRight className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n\n {isLoading ? (\n <div className=\"flex flex-1 items-center gap-2 py-8 text-sm text-text-secondary\">\n <Spinner size=\"sm\" /> Loading…\n </div>\n ) : isError ? (\n <div className=\"flex flex-1 flex-col items-start gap-2 py-6\">\n <p className=\"text-sm text-status-error-text\">\n Couldn&apos;t load {title.toLowerCase()}.\n </p>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg border border-border-seam px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Retry\n </button>\n </div>\n ) : isEmpty ? (\n <div className=\"flex flex-1 flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-10 text-center\">\n <p className=\"text-sm text-text-secondary\">{emptyLabel}</p>\n </div>\n ) : (\n <div className=\"flex flex-1 flex-col justify-between gap-4\">\n <div className=\"flex-1\">{children}</div>\n {footer ? (\n <p className=\"border-t border-border-seam/70 pt-3 text-xs text-text-tertiary\">\n {footer}\n </p>\n ) : null}\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ── Commodity portfolio card ─────────────────────────────────────────────────\n\ninterface CommodityPortfolioEntry {\n key: TheCommodityType;\n name: string;\n /** Tokenized commodity color, shared with the commodity-mix donut. */\n color: string;\n assetCount: number;\n producing: number;\n capacity: number;\n /** Dominant `TheUnit` token across the commodity's assets. */\n unit: string;\n /** Monthly production volumes (oldest first) — empty when none logged. */\n spark: number[];\n}\n\nfunction CommodityCard({ card, onOpen }: { card: CommodityPortfolioEntry; onOpen: () => void }) {\n return (\n <GlassCard treatment=\"metric\" className=\"p-0\">\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${card.name} assets`}\n className=\"flex w-full flex-col gap-4 rounded-[var(--radius-card)] p-5 text-left outline-none transition-transform duration-200 hover:-translate-y-0.5 focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"size-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: card.color }}\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{card.name}</p>\n <p className=\"text-xs text-text-tertiary\">\n {card.assetCount} {card.assetCount === 1 ? 'asset' : 'assets'}\n </p>\n </div>\n </div>\n <ArrowUpRight className=\"h-4 w-4 shrink-0 text-text-tertiary\" />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-[11px] uppercase tracking-wide text-text-tertiary\">Capacity/day</p>\n <p className=\"mt-0.5 truncate text-sm font-semibold tabular-nums text-text-primary\">\n {card.capacity > 0 ? formatVolume(card.capacity, card.unit) : '—'}\n </p>\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] uppercase tracking-wide text-text-tertiary\">Producing</p>\n <p className=\"mt-0.5 text-sm font-semibold tabular-nums text-text-primary\">\n {card.producing}{' '}\n <span className=\"text-xs font-normal text-text-tertiary\">of {card.assetCount}</span>\n </p>\n </div>\n </div>\n\n {card.spark.length >= 2 ? (\n <div>\n <Sparkline\n data={card.spark}\n variant=\"area\"\n height={40}\n label={`${card.name} monthly production`}\n />\n <p className=\"mt-1 text-[11px] text-text-tertiary\">Monthly output</p>\n </div>\n ) : (\n <p className=\"text-xs text-text-tertiary\">No production logged yet.</p>\n )}\n </button>\n </GlassCard>\n );\n}\n\nexport function DashboardPage() {\n useThePageView('dashboard');\n const { navigateTo, currentUser } = useTheGlobalFuel();\n\n // Real backend queries. Aggregate domains fetch a generous page (200) so the\n // charts + portfolio reflect the whole book; commerce lists stay at 50.\n const assets = useTheAssets({ pagination: { limit: 200 } });\n const contracts = useTheContracts({ pagination: { limit: 50 } });\n const shipments = useTheShipments(undefined, 'IN_TRANSIT');\n const emissions = useTheEmissionRecords({ pagination: { limit: 200 } });\n const inventory = useTheInventoryLots({ pagination: { limit: 50 } });\n const compliance = useTheComplianceCerts();\n const production = useTheProductionRecords({ pagination: { limit: 200 } });\n\n const assetRows = useMemo(() => assets.data?.items ?? [], [assets.data]);\n const contractRows = useMemo(() => contracts.data?.items ?? [], [contracts.data]);\n const inTransitRows = useMemo(() => shipments.data?.items ?? [], [shipments.data]);\n const emissionRows = useMemo(() => emissions.data?.items ?? [], [emissions.data]);\n const inventoryRows = useMemo(() => inventory.data?.items ?? [], [inventory.data]);\n const certRows = useMemo(() => compliance.data?.items ?? [], [compliance.data]);\n const productionRows = useMemo(() => production.data?.items ?? [], [production.data]);\n\n // ── KPI aggregates ─────────────────────────────────────────────────────────\n const producingCount = assetRows.filter((a) => a.status === 'PRODUCING').length;\n const activeContracts = contractRows.filter((c) => c.status === 'ACTIVE').length;\n const totalTonnes = emissionRows.reduce((sum, e) => sum + (e.co2eTonnes ?? 0), 0);\n const verifiedEmissions = emissionRows.filter((e) => e.verified).length;\n const availableLots = inventoryRows.filter((l) => l.status === 'AVAILABLE').length;\n const totalProductionVolume = productionRows.reduce((sum, p) => sum + (p.volume ?? 0), 0);\n\n const pendingCerts = certRows.filter((c) => c.status === 'PENDING').length;\n const expiringCerts = certRows.filter(\n (c) => c.expiresAt != null && (isExpired(c.expiresAt) || isExpiringSoon(c.expiresAt))\n ).length;\n const attentionCerts = certRows.filter(\n (c) =>\n c.status === 'PENDING' ||\n (c.expiresAt != null && (isExpired(c.expiresAt) || isExpiringSoon(c.expiresAt)))\n ).length;\n\n const withPlus = (rows: unknown[], hasMore: boolean | undefined): string =>\n hasMore ? `${rows.length}+` : formatNumber(rows.length);\n\n // ── Production trend (metered output by month) ───────────────────────────────\n const productionSeries = useMemo<LabeledValue[]>(\n () =>\n monthlySeries(\n bucketByMonth(\n productionRows,\n (p) => p.periodEnd,\n (p) => p.volume ?? 0\n )\n ),\n [productionRows]\n );\n\n // ── Commodity distribution + shared per-commodity colors ─────────────────────\n const commodityDist = useMemo(() => distribution(assetRows, (a) => a.commodity), [assetRows]);\n\n const commodityColor = useMemo(() => {\n const palette = chartPalette(commodityDist.length);\n const map = new Map<string, string>();\n commodityDist.forEach((d, i) => map.set(d.key, palette[i]));\n return map;\n }, [commodityDist]);\n\n const commodityMix = useMemo<Segment[]>(\n () =>\n commodityDist.map((d) => ({\n label: formatEnumLabel(d.key),\n value: d.count,\n color: commodityColor.get(d.key),\n })),\n [commodityDist, commodityColor]\n );\n\n // ── Emissions by scope (tonnes CO₂e) ─────────────────────────────────────────\n const scopeMix = useMemo<Segment[]>(() => {\n if (emissionRows.length === 0) return [];\n const totals: Record<TheEmissionScope, number> = { SCOPE_1: 0, SCOPE_2: 0, SCOPE_3: 0 };\n for (const e of emissionRows) totals[e.scope] += e.co2eTonnes ?? 0;\n return ALL_SCOPES.filter((s) => totals[s] > 0).map((s) => ({\n label: formatEnumLabel(s),\n value: totals[s],\n color: SCOPE_COLOR[s],\n }));\n }, [emissionRows]);\n\n // ── Assets by lifecycle (MiniBar rows) ───────────────────────────────────────\n const lifecycleBars = useMemo<DistBar[]>(\n () =>\n distribution(assetRows, (a) => a.status).map((d) => ({\n key: d.key,\n label: <StatusBadge status={d.key} />,\n value: formatNumber(d.count),\n pct: d.pct,\n barClassName: ASSET_STATUS_BAR[d.key as TheAssetStatus] ?? 'bg-action-primary-bg',\n })),\n [assetRows]\n );\n\n // ── Commodity portfolio (one entry per commodity with assets) ────────────────\n const assetCommodity = useMemo(() => {\n const m = new Map<string, TheCommodityType>();\n for (const a of assetRows) m.set(a.id, a.commodity);\n return m;\n }, [assetRows]);\n\n const commodityCards = useMemo<CommodityPortfolioEntry[]>(() => {\n if (commodityDist.length === 0) return [];\n\n // Per-commodity asset aggregates.\n interface Agg {\n count: number;\n producing: number;\n capacity: number;\n units: Map<string, number>;\n }\n const agg = new Map<string, Agg>();\n for (const a of assetRows) {\n const cur = agg.get(a.commodity) ?? {\n count: 0,\n producing: 0,\n capacity: 0,\n units: new Map<string, number>(),\n };\n cur.count += 1;\n if (a.status === 'PRODUCING') cur.producing += 1;\n cur.capacity += a.capacityPerDay ?? 0;\n if (a.capacityUnit) cur.units.set(a.capacityUnit, (cur.units.get(a.capacityUnit) ?? 0) + 1);\n agg.set(a.commodity, cur);\n }\n\n // Per-commodity monthly production buckets → sparkline series.\n const prodBuckets = new Map<string, Map<string, number>>();\n for (const p of productionRows) {\n const commodity = assetCommodity.get(p.assetId);\n if (!commodity) continue;\n const d = new Date(p.periodEnd);\n if (Number.isNaN(d.getTime())) continue;\n const mk = monthKey(d);\n let bucket = prodBuckets.get(commodity);\n if (!bucket) {\n bucket = new Map<string, number>();\n prodBuckets.set(commodity, bucket);\n }\n bucket.set(mk, (bucket.get(mk) ?? 0) + (p.volume ?? 0));\n }\n\n const dominantUnit = (units: Map<string, number>): string => {\n let best = '';\n let bestN = -1;\n for (const [unit, n] of units) {\n if (n > bestN) {\n best = unit;\n bestN = n;\n }\n }\n return best;\n };\n\n return commodityDist.map((d) => {\n const a = agg.get(d.key) ?? {\n count: 0,\n producing: 0,\n capacity: 0,\n units: new Map<string, number>(),\n };\n const bucket = prodBuckets.get(d.key);\n const spark = bucket ? monthlySeries(bucket, 12).map((s) => s.value) : [];\n return {\n key: d.key as TheCommodityType,\n name: formatEnumLabel(d.key),\n color: commodityColor.get(d.key) ?? 'var(--color-accent-primary)',\n assetCount: a.count,\n producing: a.producing,\n capacity: a.capacity,\n unit: dominantUnit(a.units),\n spark,\n };\n });\n }, [assetRows, productionRows, assetCommodity, commodityDist, commodityColor]);\n\n // ── Recent activity ────────────────────────────────────────────────────────\n const assetName = useMemo(() => {\n const m = new Map<string, string>();\n for (const a of assetRows) m.set(a.id, a.name);\n return m;\n }, [assetRows]);\n\n const recentContracts = useMemo(\n () =>\n [...contractRows]\n .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())\n .slice(0, 6),\n [contractRows]\n );\n\n const recentProduction = useMemo(\n () =>\n [...productionRows]\n .sort((a, b) => new Date(b.periodEnd).getTime() - new Date(a.periodEnd).getTime())\n .slice(0, 6),\n [productionRows]\n );\n\n const contractColumns: Column<TheOfftakeContract>[] = [\n {\n key: 'reference',\n header: 'Reference',\n render: (c) => <span className=\"font-medium text-text-primary\">{c.reference}</span>,\n },\n {\n key: 'counterparty',\n header: 'Counterparty',\n hideOnMobile: true,\n render: (c) => <span className=\"text-text-secondary\">{c.counterparty}</span>,\n },\n {\n key: 'commodity',\n header: 'Commodity',\n hideOnMobile: true,\n render: (c) => <span className=\"text-text-secondary\">{formatEnumLabel(c.commodity)}</span>,\n },\n { key: 'side', header: 'Side', render: (c) => <StatusBadge status={c.side} /> },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n hideOnMobile: true,\n render: (c) => (\n <span className=\"tabular-nums text-text-secondary\">\n {formatVolume(c.contractedVolume, c.unit)}\n </span>\n ),\n },\n { key: 'status', header: 'Status', render: (c) => <StatusBadge status={c.status} /> },\n {\n key: 'created',\n header: 'Created',\n align: 'right',\n hideOnMobile: true,\n render: (c) => <span className=\"text-text-tertiary\">{formatRelativeTime(c.createdAt)}</span>,\n },\n ];\n\n const productionColumns: Column<TheProductionRecord>[] = [\n {\n key: 'asset',\n header: 'Asset',\n render: (p) => (\n <span className=\"font-medium text-text-primary\">\n {assetName.get(p.assetId) ?? 'Unknown asset'}\n </span>\n ),\n },\n {\n key: 'period',\n header: 'Period',\n hideOnMobile: true,\n render: (p) => (\n <span className=\"text-text-secondary\">\n {formatDate(p.periodStart)} – {formatDate(p.periodEnd)}\n </span>\n ),\n },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n render: (p) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatVolume(p.volume, p.unit)}\n </span>\n ),\n },\n {\n key: 'downtime',\n header: 'Downtime',\n align: 'right',\n hideOnMobile: true,\n render: (p) => (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(p.downtimeHours)} h</span>\n ),\n },\n {\n key: 'logged',\n header: 'Logged',\n align: 'right',\n hideOnMobile: true,\n render: (p) => <span className=\"text-text-tertiary\">{formatRelativeTime(p.createdAt)}</span>,\n },\n ];\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n const t = useTheTranslation();\n const description = t(\n 'theglobalfuel.pages.dashboard.subtitle',\n 'Welcome back, {name} — your energy, mineral & commodity resource control room.',\n { name: greetingName }\n );\n\n // Full-page loader only while every primary domain is still loading its first page.\n const initialLoading =\n assets.isLoading &&\n contracts.isLoading &&\n emissions.isLoading &&\n inventory.isLoading &&\n compliance.isLoading;\n\n const pageTitle = t('theglobalfuel.pages.dashboard.title', 'TheGlobalFuel');\n\n if (initialLoading) {\n return (\n <PageLayout\n title={pageTitle}\n titleGradient\n aurora\n description={description}\n dataTour=\"theglobalfuel-dashboard-page\"\n >\n <LoadingView label=\"Loading your resource portfolio…\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={pageTitle}\n titleGradient\n aurora\n description={description}\n dataTour=\"theglobalfuel-dashboard-page\"\n >\n {/* KPI grid */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6\">\n <MetricsCard\n icon={<Layers className=\"h-5 w-5\" />}\n value={withPlus(assetRows, assets.data?.pageInfo?.hasMore)}\n label=\"Resource assets\"\n featured\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={assetRows.length ? (producingCount / assetRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-success-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{producingCount}</span> producing\n </p>\n </div>\n }\n />\n\n <MetricsCard\n icon={<ScrollText className=\"h-5 w-5\" />}\n value={withPlus(contractRows, contracts.data?.pageInfo?.hasMore)}\n label=\"Offtake contracts\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={contractRows.length ? (activeContracts / contractRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-action-primary-bg\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{activeContracts}</span> active\n </p>\n </div>\n }\n />\n\n <MetricsCard\n icon={<Ship className=\"h-5 w-5\" />}\n value={shipments.isError ? '—' : formatNumber(inTransitRows.length)}\n label=\"Shipments in transit\"\n footer={\n <p className=\"text-xs text-text-tertiary\">\n {inTransitRows.length > 0 ? 'In motion across carriers' : 'No active movements'}\n </p>\n }\n />\n\n <MetricsCard\n icon={<Factory className=\"h-5 w-5\" />}\n value={emissions.isError ? '—' : formatTonnesCo2e(totalTonnes)}\n label=\"Emissions logged\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={emissionRows.length ? (verifiedEmissions / emissionRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-success-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{verifiedEmissions}</span>{' '}\n verified\n </p>\n </div>\n }\n />\n\n <MetricsCard\n icon={<Boxes className=\"h-5 w-5\" />}\n value={inventory.isError ? '—' : formatNumber(availableLots)}\n label=\"Inventory available\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={inventoryRows.length ? (availableLots / inventoryRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-action-primary-bg\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">\n {withPlus(inventoryRows, inventory.data?.pageInfo?.hasMore)}\n </span>{' '}\n lots total\n </p>\n </div>\n }\n />\n\n <MetricsCard\n icon={<ShieldCheck className=\"h-5 w-5\" />}\n value={compliance.isError ? '—' : formatNumber(attentionCerts)}\n label=\"Certs need attention\"\n accent={attentionCerts > 0 ? 'warning' : certRows.length > 0 ? 'success' : undefined}\n footer={\n <p\n className={\n attentionCerts > 0\n ? 'text-xs font-medium text-status-warning-text'\n : 'text-xs text-text-tertiary'\n }\n >\n {certRows.length === 0\n ? 'No certifications tracked yet'\n : attentionCerts > 0\n ? `${pendingCerts} pending · ${expiringCerts} expiring`\n : 'All certifications current'}\n </p>\n }\n />\n </div>\n\n {/* Analytics — production trend + commodity mix (real aggregates) */}\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <ChartCard\n className=\"lg:col-span-2\"\n title=\"Production trend\"\n caption=\"Metered output aggregated by month across your producing assets.\"\n icon={<Activity className=\"h-4 w-4\" />}\n link={{ label: 'Production', onClick: () => navigateTo('production') }}\n isLoading={production.isLoading}\n isError={production.isError}\n onRetry={() => production.refetch()}\n isEmpty={productionSeries.length === 0}\n emptyLabel=\"No production logged yet — record output to build the trend.\"\n footer={\n productionSeries.length > 0\n ? `${formatNumber(totalProductionVolume, { maximumFractionDigits: 0 })} total volume across ${withPlus(productionRows, production.data?.pageInfo?.hasMore)} records`\n : undefined\n }\n >\n <AreaChart series={productionSeries} height={220} title=\"Monthly production volume\" />\n </ChartCard>\n\n <ChartCard\n title=\"Commodity mix\"\n caption=\"Share of assets across your commodity streams.\"\n icon={<Fuel className=\"h-4 w-4\" />}\n link={{ label: 'Assets', onClick: () => navigateTo('assets') }}\n isLoading={assets.isLoading}\n isError={assets.isError}\n onRetry={() => assets.refetch()}\n isEmpty={commodityMix.length === 0}\n emptyLabel=\"No assets registered yet.\"\n footer={\n commodityMix.length > 0\n ? `${withPlus(assetRows, assets.data?.pageInfo?.hasMore)} assets · ${commodityMix.length} ${commodityMix.length === 1 ? 'commodity' : 'commodities'}`\n : undefined\n }\n >\n <div className=\"flex items-center justify-center py-2\">\n <DonutChart\n segments={commodityMix}\n size={168}\n centerLabel={formatNumber(assetRows.length)}\n centerCaption=\"assets\"\n valueFormatter={(v) => formatNumber(v)}\n title=\"Assets by commodity\"\n />\n </div>\n </ChartCard>\n </div>\n\n {/* Commodity portfolio — one model, every commodity */}\n <Section\n title=\"Commodity portfolio\"\n description=\"One model, every commodity — assets, capacity and output across your book.\"\n >\n {assets.isLoading ? (\n <LoadingView label=\"Loading commodities…\" />\n ) : assets.isError ? (\n <ErrorView\n message=\"Couldn't load your commodity portfolio.\"\n onRetry={() => assets.refetch()}\n />\n ) : commodityCards.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Layers className=\"h-10 w-10\" />}\n title=\"No commodities yet\"\n description=\"Register your first resource asset to build out your commodity portfolio.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n {commodityCards.map((card) => (\n <CommodityCard key={card.key} card={card} onOpen={() => navigateTo('assets')} />\n ))}\n </div>\n )}\n </Section>\n\n {/* Lifecycle distribution + emissions by scope */}\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <DistributionCard\n title=\"Assets by lifecycle\"\n description=\"Where the portfolio sits from exploration to retirement.\"\n icon={<GitBranch className=\"h-4 w-4\" />}\n linkLabel=\"Assets\"\n onLink={() => navigateTo('assets')}\n isLoading={assets.isLoading}\n isError={assets.isError}\n onRetry={() => assets.refetch()}\n emptyLabel=\"No assets to profile yet.\"\n bars={lifecycleBars}\n footer={\n assetRows.length > 0\n ? `${producingCount} of ${withPlus(assetRows, assets.data?.pageInfo?.hasMore)} currently producing`\n : undefined\n }\n />\n\n <ChartCard\n title=\"Emissions by scope\"\n caption=\"GHG Protocol Scope 1 / 2 / 3 — tonnes CO₂e logged.\"\n icon={<Gauge className=\"h-4 w-4\" />}\n link={{ label: 'Emissions', onClick: () => navigateTo('emissions') }}\n isLoading={emissions.isLoading}\n isError={emissions.isError}\n onRetry={() => emissions.refetch()}\n isEmpty={scopeMix.length === 0}\n emptyLabel=\"No emission records logged yet.\"\n footer={\n emissionRows.length > 0\n ? `${formatTonnesCo2e(totalTonnes)} across ${withPlus(emissionRows, emissions.data?.pageInfo?.hasMore)} records`\n : undefined\n }\n >\n <div className=\"flex items-center justify-center py-2\">\n <DonutChart\n segments={scopeMix}\n size={168}\n centerLabel={formatNumber(totalTonnes, {\n notation: 'compact',\n maximumFractionDigits: 1,\n })}\n centerCaption=\"t CO₂e\"\n valueFormatter={(v) =>\n `${formatNumber(v, { maximumFractionDigits: v >= 100 ? 0 : 1 })} t`\n }\n title=\"Emissions by scope\"\n />\n </div>\n </ChartCard>\n </div>\n\n {/* Recent offtake contracts */}\n <Section\n title=\"Recent offtake contracts\"\n description=\"Latest buy/sell agreements across your commodity book.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('contracts')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n All contracts <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {contracts.isLoading ? (\n <LoadingView label=\"Loading contracts…\" />\n ) : contracts.isError ? (\n <ErrorView message=\"Couldn't load contracts.\" onRetry={() => contracts.refetch()} />\n ) : recentContracts.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ScrollText className=\"h-10 w-10\" />}\n title=\"No contracts yet\"\n description=\"Draft your first offtake contract to start tracking commodity trade.\"\n />\n ) : (\n <DataTable\n columns={contractColumns}\n rows={recentContracts}\n rowKey={(c) => c.id}\n onRowClick={(c) => navigateTo(`contracts/${c.id}`)}\n rowLabel={(c) => `Open contract ${c.reference}`}\n />\n )}\n </Section>\n\n {/* Recent production */}\n <Section\n title=\"Recent production\"\n description=\"Latest metered output captured against your producing assets.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('production')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n All production <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {production.isLoading ? (\n <LoadingView label=\"Loading production…\" />\n ) : production.isError ? (\n <ErrorView\n message=\"Couldn't load production records.\"\n onRetry={() => production.refetch()}\n />\n ) : recentProduction.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ClipboardList className=\"h-10 w-10\" />}\n title=\"No production logged yet\"\n description=\"Record production against an asset to build its output history.\"\n />\n ) : (\n <DataTable\n columns={productionColumns}\n rows={recentProduction}\n rowKey={(p) => p.id}\n onRowClick={(p) => navigateTo(`assets/${p.assetId}`)}\n rowLabel={(p) => `Open ${assetName.get(p.assetId) ?? 'asset'}`}\n />\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/dashboard/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — TheGlobalFuel resources operating-system overview.\n * @module theglobalfuel/pages/dashboard\n *\n * The hero landing (eager-loaded). Every figure is aggregated client-side from\n * the real `the*` list endpoints — no mock data. The page opens with the domain\n * KPI grid (registry / commerce / sustainability), then a rich analytics band:\n * - a PRODUCTION TREND area chart (metered output bucketed by month),\n * - a COMMODITY MIX donut (share of assets by commodity), and\n * - an EMISSIONS-BY-SCOPE donut (tonnes CO₂e per GHG scope);\n * a COMMODITY PORTFOLIO grid (one glass card per commodity present, with real\n * asset counts, summed capacity, booked PROVED reserves and a production\n * sparkline); an ASSETS-BY-LIFECYCLE distribution; and recent-activity tables.\n * All charts derive from the fetched arrays and degrade to pre-launch empty\n * states.\n */\n\nimport { useMemo, type ReactNode } from 'react';\nimport {\n Layers,\n ScrollText,\n Ship,\n Factory,\n Boxes,\n ShieldCheck,\n ArrowRight,\n ArrowUpRight,\n Fuel,\n GitBranch,\n Gauge,\n Activity,\n ClipboardList,\n} from 'lucide-react';\nimport { GlassCard, Spinner } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { MiniBar } from '../../components/MiniBar';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView, Section } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport {\n AreaChart,\n DonutChart,\n Sparkline,\n chartPalette,\n type LabeledValue,\n type Segment,\n} from '../../components/viz';\nimport { cn } from '../../utils/cn';\nimport { useTheGlobalFuel } from '../../context';\nimport {\n useTheAssets,\n useTheContracts,\n useTheShipments,\n useTheEmissionRecords,\n useTheInventoryLots,\n useTheComplianceCerts,\n useTheProductionRecords,\n useTheReserveEstimates,\n useThePageView,\n useTheTranslation,\n} from '../../hooks';\nimport {\n formatNumber,\n formatPercent,\n formatDate,\n formatRelativeTime,\n formatEnumLabel,\n formatVolume,\n formatTonnesCo2e,\n isExpired,\n} from '../../utils/formatters';\nimport type {\n TheAssetStatus,\n TheCommodityType,\n TheEmissionScope,\n TheOfftakeContract,\n TheProductionRecord,\n} from '../../types';\n\nconst DAY_MS = 24 * 60 * 60 * 1000;\nconst EXPIRY_WINDOW_DAYS = 30;\n\n/** True when a cert expires within the next 30 days (but hasn't yet). */\nconst isExpiringSoon = (date: string | null | undefined): boolean => {\n if (!date) return false;\n const t = new Date(date).getTime();\n if (Number.isNaN(t)) return false;\n const diff = t - Date.now();\n return diff >= 0 && diff <= EXPIRY_WINDOW_DAYS * DAY_MS;\n};\n\n/** Asset lifecycle → MiniBar fill token (semantic, maturity-ordered). */\nconst ASSET_STATUS_BAR: Record<TheAssetStatus, string> = {\n EXPLORATION: 'bg-[var(--color-accent-indigo)]',\n APPRAISAL: 'bg-[var(--color-accent-blue)]',\n DEVELOPMENT: 'bg-[var(--color-accent-teal)]',\n PRODUCING: 'bg-status-success-text',\n SUSPENDED: 'bg-status-warning-text',\n DECOMMISSIONING: 'bg-status-error-text',\n RETIRED: 'bg-[var(--color-text-tertiary)]',\n};\n\n/** GHG scope → donut segment color token (Scope 1 direct · 2 energy · 3 chain). */\nconst SCOPE_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\nconst ALL_SCOPES: TheEmissionScope[] = ['SCOPE_1', 'SCOPE_2', 'SCOPE_3'];\n\n// ── Month bucketing (UTC-stable, gap-free) ───────────────────────────────────\n\n/** UTC year-month key (e.g. \"2026-07\"). */\nconst monthKey = (d: Date): string =>\n `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`;\n\nconst MONTH_FMT = new Intl.DateTimeFormat(undefined, {\n month: 'short',\n year: '2-digit',\n timeZone: 'UTC',\n});\n\nconst monthLabel = (year: number, month1: number): string =>\n MONTH_FMT.format(new Date(Date.UTC(year, month1 - 1, 1)));\n\n/** Sum `valueOf` into UTC-month buckets keyed by the `dateOf` field. */\nfunction bucketByMonth<T>(\n rows: readonly T[],\n dateOf: (row: T) => string | null | undefined,\n valueOf: (row: T) => number\n): Map<string, number> {\n const buckets = new Map<string, number>();\n for (const row of rows) {\n const raw = dateOf(row);\n if (!raw) continue;\n const d = new Date(raw);\n if (Number.isNaN(d.getTime())) continue;\n const key = monthKey(d);\n buckets.set(key, (buckets.get(key) ?? 0) + valueOf(row));\n }\n return buckets;\n}\n\n/**\n * Expand month buckets into a gap-free ordered series from the earliest to the\n * latest populated month (so a trend line stays continuous), then keep only the\n * most recent `maxMonths` so a stray old record can't blow out the axis.\n */\nfunction monthlySeries(buckets: Map<string, number>, maxMonths = 18): LabeledValue[] {\n const keys = [...buckets.keys()].sort();\n if (keys.length === 0) return [];\n const parse = (k: string): [number, number] => {\n const [y, m] = k.split('-');\n return [Number(y), Number(m)];\n };\n const [maxY, maxM] = parse(keys[keys.length - 1]);\n let [y, m] = parse(keys[0]);\n const series: LabeledValue[] = [];\n let guard = 0;\n while ((y < maxY || (y === maxY && m <= maxM)) && guard < 240) {\n series.push({\n label: monthLabel(y, m),\n value: buckets.get(`${y}-${String(m).padStart(2, '0')}`) ?? 0,\n });\n m += 1;\n if (m > 12) {\n m = 1;\n y += 1;\n }\n guard += 1;\n }\n return series.length > maxMonths ? series.slice(series.length - maxMonths) : series;\n}\n\n/** Group rows by a discriminator, sorted by descending count, with % of total. */\nfunction distribution<T>(\n rows: T[],\n keyOf: (row: T) => string\n): { key: string; count: number; pct: number }[] {\n const counts = new Map<string, number>();\n for (const row of rows) {\n const k = keyOf(row);\n counts.set(k, (counts.get(k) ?? 0) + 1);\n }\n const total = rows.length;\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([key, count]) => ({ key, count, pct: total ? (count / total) * 100 : 0 }));\n}\n\n// ── Distribution card (MiniBar rows) ─────────────────────────────────────────\n\ninterface DistBar {\n key: string;\n label: ReactNode;\n /** Pre-formatted primary figure shown right of the bar. */\n value: string;\n /** 0–100 — drives the bar width + the muted percentage. */\n pct: number;\n barClassName: string;\n}\n\ninterface DistributionCardProps {\n title: string;\n description: string;\n icon: ReactNode;\n linkLabel: string;\n onLink: () => void;\n isLoading: boolean;\n isError: boolean;\n onRetry: () => void;\n emptyLabel: string;\n bars: DistBar[];\n footer?: ReactNode;\n className?: string;\n}\n\n/** A titled glass card that renders a compact set of MiniBar distribution rows. */\nfunction DistributionCard({\n title,\n description,\n icon,\n linkLabel,\n onLink,\n isLoading,\n isError,\n onRetry,\n emptyLabel,\n bars,\n footer,\n className,\n}: DistributionCardProps) {\n return (\n <GlassCard treatment=\"glass\" className={cn('flex flex-col p-5', className)}>\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-action-primary-bg\">\n {icon}\n </span>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">{description}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onLink}\n className=\"inline-flex shrink-0 items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n {linkLabel} <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n\n {isLoading ? (\n <div className=\"flex items-center gap-2 py-8 text-sm text-text-secondary\">\n <Spinner size=\"sm\" /> Loading…\n </div>\n ) : isError ? (\n <div className=\"flex flex-col items-start gap-2 py-6\">\n <p className=\"text-sm text-status-error-text\">\n Couldn&apos;t load {title.toLowerCase()}.\n </p>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg border border-border-seam px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Retry\n </button>\n </div>\n ) : bars.length === 0 ? (\n <div className=\"flex flex-1 flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-8 text-center\">\n <p className=\"text-sm text-text-secondary\">{emptyLabel}</p>\n </div>\n ) : (\n <div className=\"flex flex-1 flex-col justify-between gap-4\">\n <div className=\"space-y-3\">\n {bars.map((bar) => (\n <div key={bar.key} className=\"flex items-center gap-3\">\n <div className=\"w-24 shrink-0 truncate text-sm text-text-secondary sm:w-28\">\n {bar.label}\n </div>\n <MiniBar\n value={bar.pct}\n barClassName={bar.barClassName}\n className=\"flex-1\"\n height={8}\n />\n <span className=\"w-16 shrink-0 whitespace-nowrap text-right text-sm font-medium tabular-nums text-text-primary\">\n {bar.value}\n </span>\n <span className=\"hidden w-11 shrink-0 text-right text-xs tabular-nums text-text-tertiary sm:inline\">\n {formatPercent(bar.pct)}\n </span>\n </div>\n ))}\n </div>\n {footer && (\n <p className=\"border-t border-border-seam/70 pt-3 text-xs text-text-tertiary\">\n {footer}\n </p>\n )}\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ── Chart card (titled glass panel for a chart body) ─────────────────────────\n\ninterface ChartCardProps {\n title: string;\n caption: string;\n icon: ReactNode;\n link?: { label: string; onClick: () => void };\n isLoading: boolean;\n isError: boolean;\n onRetry: () => void;\n isEmpty: boolean;\n emptyLabel: string;\n footer?: ReactNode;\n className?: string;\n children: ReactNode;\n}\n\n/** A titled glass card wrapping a viz-kit chart, with loading/error/empty states. */\nfunction ChartCard({\n title,\n caption,\n icon,\n link,\n isLoading,\n isError,\n onRetry,\n isEmpty,\n emptyLabel,\n footer,\n className,\n children,\n}: ChartCardProps) {\n return (\n <GlassCard treatment=\"glass\" className={cn('flex flex-col p-5', className)}>\n <div className=\"mb-4 flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span className=\"mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-action-primary-bg\">\n {icon}\n </span>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title}</h2>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">{caption}</p>\n </div>\n </div>\n {link ? (\n <button\n type=\"button\"\n onClick={link.onClick}\n className=\"inline-flex shrink-0 items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n {link.label} <ArrowRight className=\"h-4 w-4\" />\n </button>\n ) : null}\n </div>\n\n {isLoading ? (\n <div className=\"flex flex-1 items-center gap-2 py-8 text-sm text-text-secondary\">\n <Spinner size=\"sm\" /> Loading…\n </div>\n ) : isError ? (\n <div className=\"flex flex-1 flex-col items-start gap-2 py-6\">\n <p className=\"text-sm text-status-error-text\">\n Couldn&apos;t load {title.toLowerCase()}.\n </p>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-lg border border-border-seam px-3 py-1.5 text-xs font-medium text-text-primary hover:bg-bg-sunken\"\n >\n Retry\n </button>\n </div>\n ) : isEmpty ? (\n <div className=\"flex flex-1 flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-10 text-center\">\n <p className=\"text-sm text-text-secondary\">{emptyLabel}</p>\n </div>\n ) : (\n <div className=\"flex flex-1 flex-col justify-between gap-4\">\n <div className=\"flex-1\">{children}</div>\n {footer ? (\n <p className=\"border-t border-border-seam/70 pt-3 text-xs text-text-tertiary\">\n {footer}\n </p>\n ) : null}\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ── Commodity portfolio card ─────────────────────────────────────────────────\n\ninterface CommodityPortfolioEntry {\n key: TheCommodityType;\n name: string;\n /** Tokenized commodity color, shared with the commodity-mix donut. */\n color: string;\n assetCount: number;\n producing: number;\n capacity: number;\n /** Dominant `TheUnit` token across the commodity's assets. */\n unit: string;\n /**\n * Booked PROVED recoverable volume — the latest estimate per asset, summed\n * only across assets booked in `reserveUnit`. Null when nothing is booked.\n */\n reserves: number | null;\n /** Unit the `reserves` figure is expressed in. */\n reserveUnit: string;\n /** Assets in this commodity that carry a proved estimate in `reserveUnit`. */\n reserveAssets: number;\n /** Monthly production volumes (oldest first) — empty when none logged. */\n spark: number[];\n}\n\nfunction CommodityCard({ card, onOpen }: { card: CommodityPortfolioEntry; onOpen: () => void }) {\n return (\n <GlassCard treatment=\"metric\" className=\"p-0\">\n <button\n type=\"button\"\n onClick={onOpen}\n aria-label={`Open ${card.name} assets`}\n className=\"flex w-full flex-col gap-4 rounded-[var(--radius-card)] p-5 text-left outline-none transition-transform duration-200 hover:-translate-y-0.5 focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"size-2.5 shrink-0 rounded-full\"\n style={{ backgroundColor: card.color }}\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{card.name}</p>\n <p className=\"text-xs text-text-tertiary\">\n {card.assetCount} {card.assetCount === 1 ? 'asset' : 'assets'}\n </p>\n </div>\n </div>\n <ArrowUpRight className=\"h-4 w-4 shrink-0 text-text-tertiary\" />\n </div>\n\n <div className=\"grid grid-cols-3 gap-3\">\n <div className=\"min-w-0\">\n <p className=\"text-[11px] uppercase tracking-wide text-text-tertiary\">Capacity/day</p>\n <p className=\"mt-0.5 truncate text-sm font-semibold tabular-nums text-text-primary\">\n {card.capacity > 0 ? formatVolume(card.capacity, card.unit) : '—'}\n </p>\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] uppercase tracking-wide text-text-tertiary\">Proved</p>\n <p\n className=\"mt-0.5 truncate text-sm font-semibold tabular-nums text-text-primary\"\n title={\n card.reserves != null\n ? `Latest proved estimate across ${card.reserveAssets} ${card.reserveAssets === 1 ? 'asset' : 'assets'}`\n : 'No proved reserves booked'\n }\n >\n {card.reserves != null ? formatVolume(card.reserves, card.reserveUnit) : '—'}\n </p>\n </div>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] uppercase tracking-wide text-text-tertiary\">Producing</p>\n <p className=\"mt-0.5 text-sm font-semibold tabular-nums text-text-primary\">\n {card.producing}{' '}\n <span className=\"text-xs font-normal text-text-tertiary\">of {card.assetCount}</span>\n </p>\n </div>\n </div>\n\n {card.spark.length >= 2 ? (\n <div>\n <Sparkline\n data={card.spark}\n variant=\"area\"\n height={40}\n label={`${card.name} monthly production`}\n />\n <p className=\"mt-1 text-[11px] text-text-tertiary\">Monthly output</p>\n </div>\n ) : (\n <p className=\"text-xs text-text-tertiary\">No production logged yet.</p>\n )}\n </button>\n </GlassCard>\n );\n}\n\nexport function DashboardPage() {\n useThePageView('dashboard');\n const { navigateTo, currentUser } = useTheGlobalFuel();\n\n // Real backend queries. Aggregate domains fetch a generous page (200) so the\n // charts + portfolio reflect the whole book; commerce lists stay at 50.\n const assets = useTheAssets({ pagination: { limit: 200 } });\n const contracts = useTheContracts({ pagination: { limit: 50 } });\n const shipments = useTheShipments(undefined, 'IN_TRANSIT');\n const emissions = useTheEmissionRecords({ pagination: { limit: 200 } });\n const inventory = useTheInventoryLots({ pagination: { limit: 50 } });\n const compliance = useTheComplianceCerts();\n const production = useTheProductionRecords({ pagination: { limit: 200 } });\n // PROVED only. Summing across categories would double-count the same barrels\n // (a 2P figure contains the 1P one), so the portfolio quotes the booked\n // proved position and says so.\n const reserves = useTheReserveEstimates(undefined, 'PROVED', { limit: 200 });\n\n const assetRows = useMemo(() => assets.data?.items ?? [], [assets.data]);\n const contractRows = useMemo(() => contracts.data?.items ?? [], [contracts.data]);\n const inTransitRows = useMemo(() => shipments.data?.items ?? [], [shipments.data]);\n const emissionRows = useMemo(() => emissions.data?.items ?? [], [emissions.data]);\n const inventoryRows = useMemo(() => inventory.data?.items ?? [], [inventory.data]);\n const certRows = useMemo(() => compliance.data?.items ?? [], [compliance.data]);\n const productionRows = useMemo(() => production.data?.items ?? [], [production.data]);\n const reserveRows = useMemo(() => reserves.data?.items ?? [], [reserves.data]);\n\n // ── KPI aggregates ─────────────────────────────────────────────────────────\n const producingCount = assetRows.filter((a) => a.status === 'PRODUCING').length;\n const activeContracts = contractRows.filter((c) => c.status === 'ACTIVE').length;\n const totalTonnes = emissionRows.reduce((sum, e) => sum + (e.co2eTonnes ?? 0), 0);\n const verifiedEmissions = emissionRows.filter((e) => e.verified).length;\n const availableLots = inventoryRows.filter((l) => l.status === 'AVAILABLE').length;\n const totalProductionVolume = productionRows.reduce((sum, p) => sum + (p.volume ?? 0), 0);\n\n const pendingCerts = certRows.filter((c) => c.status === 'PENDING').length;\n const expiringCerts = certRows.filter(\n (c) => c.expiresAt != null && (isExpired(c.expiresAt) || isExpiringSoon(c.expiresAt))\n ).length;\n const attentionCerts = certRows.filter(\n (c) =>\n c.status === 'PENDING' ||\n (c.expiresAt != null && (isExpired(c.expiresAt) || isExpiringSoon(c.expiresAt)))\n ).length;\n\n const withPlus = (rows: unknown[], hasMore: boolean | undefined): string =>\n hasMore ? `${rows.length}+` : formatNumber(rows.length);\n\n // ── Production trend (metered output by month) ───────────────────────────────\n const productionSeries = useMemo<LabeledValue[]>(\n () =>\n monthlySeries(\n bucketByMonth(\n productionRows,\n (p) => p.periodEnd,\n (p) => p.volume ?? 0\n )\n ),\n [productionRows]\n );\n\n // ── Commodity distribution + shared per-commodity colors ─────────────────────\n const commodityDist = useMemo(() => distribution(assetRows, (a) => a.commodity), [assetRows]);\n\n const commodityColor = useMemo(() => {\n const palette = chartPalette(commodityDist.length);\n const map = new Map<string, string>();\n commodityDist.forEach((d, i) => map.set(d.key, palette[i]));\n return map;\n }, [commodityDist]);\n\n const commodityMix = useMemo<Segment[]>(\n () =>\n commodityDist.map((d) => ({\n label: formatEnumLabel(d.key),\n value: d.count,\n color: commodityColor.get(d.key),\n })),\n [commodityDist, commodityColor]\n );\n\n // ── Emissions by scope (tonnes CO₂e) ─────────────────────────────────────────\n const scopeMix = useMemo<Segment[]>(() => {\n if (emissionRows.length === 0) return [];\n const totals: Record<TheEmissionScope, number> = { SCOPE_1: 0, SCOPE_2: 0, SCOPE_3: 0 };\n for (const e of emissionRows) totals[e.scope] += e.co2eTonnes ?? 0;\n return ALL_SCOPES.filter((s) => totals[s] > 0).map((s) => ({\n label: formatEnumLabel(s),\n value: totals[s],\n color: SCOPE_COLOR[s],\n }));\n }, [emissionRows]);\n\n // ── Assets by lifecycle (MiniBar rows) ───────────────────────────────────────\n const lifecycleBars = useMemo<DistBar[]>(\n () =>\n distribution(assetRows, (a) => a.status).map((d) => ({\n key: d.key,\n label: <StatusBadge status={d.key} />,\n value: formatNumber(d.count),\n pct: d.pct,\n barClassName: ASSET_STATUS_BAR[d.key as TheAssetStatus] ?? 'bg-action-primary-bg',\n })),\n [assetRows]\n );\n\n // ── Commodity portfolio (one entry per commodity with assets) ────────────────\n const assetCommodity = useMemo(() => {\n const m = new Map<string, TheCommodityType>();\n for (const a of assetRows) m.set(a.id, a.commodity);\n return m;\n }, [assetRows]);\n\n /**\n * Booked PROVED reserves per commodity.\n *\n * Two traps this avoids: an asset re-estimated every year would be counted\n * once per estimate (so only the LATEST `asOfDate` per asset is kept), and a\n * commodity booked in more than one unit of measure would produce a\n * meaningless sum (so only the unit carrying the most assets is totalled, and\n * the card reports how many assets that figure covers).\n */\n const commodityReserves = useMemo(() => {\n const latestPerAsset = new Map<string, { recoverable: number; unit: string; at: number }>();\n for (const r of reserveRows) {\n const at = new Date(r.asOfDate).getTime();\n if (Number.isNaN(at)) continue;\n const prev = latestPerAsset.get(r.assetId);\n if (prev && prev.at >= at) continue;\n latestPerAsset.set(r.assetId, { recoverable: r.recoverable ?? 0, unit: r.unit, at });\n }\n\n const byCommodity = new Map<string, Map<string, { total: number; assets: number }>>();\n for (const [assetId, estimate] of latestPerAsset) {\n const commodity = assetCommodity.get(assetId);\n if (!commodity) continue;\n let units = byCommodity.get(commodity);\n if (!units) {\n units = new Map<string, { total: number; assets: number }>();\n byCommodity.set(commodity, units);\n }\n const cur = units.get(estimate.unit) ?? { total: 0, assets: 0 };\n cur.total += estimate.recoverable;\n cur.assets += 1;\n units.set(estimate.unit, cur);\n }\n\n const out = new Map<string, { total: number; unit: string; assets: number }>();\n for (const [commodity, units] of byCommodity) {\n let best: { total: number; unit: string; assets: number } | null = null;\n for (const [unit, agg] of units) {\n if (\n !best ||\n agg.assets > best.assets ||\n (agg.assets === best.assets && agg.total > best.total)\n ) {\n best = { total: agg.total, unit, assets: agg.assets };\n }\n }\n if (best) out.set(commodity, best);\n }\n return out;\n }, [reserveRows, assetCommodity]);\n\n const commodityCards = useMemo<CommodityPortfolioEntry[]>(() => {\n if (commodityDist.length === 0) return [];\n\n // Per-commodity asset aggregates.\n interface Agg {\n count: number;\n producing: number;\n capacity: number;\n units: Map<string, number>;\n }\n const agg = new Map<string, Agg>();\n for (const a of assetRows) {\n const cur = agg.get(a.commodity) ?? {\n count: 0,\n producing: 0,\n capacity: 0,\n units: new Map<string, number>(),\n };\n cur.count += 1;\n if (a.status === 'PRODUCING') cur.producing += 1;\n cur.capacity += a.capacityPerDay ?? 0;\n if (a.capacityUnit) cur.units.set(a.capacityUnit, (cur.units.get(a.capacityUnit) ?? 0) + 1);\n agg.set(a.commodity, cur);\n }\n\n // Per-commodity monthly production buckets → sparkline series.\n const prodBuckets = new Map<string, Map<string, number>>();\n for (const p of productionRows) {\n const commodity = assetCommodity.get(p.assetId);\n if (!commodity) continue;\n const d = new Date(p.periodEnd);\n if (Number.isNaN(d.getTime())) continue;\n const mk = monthKey(d);\n let bucket = prodBuckets.get(commodity);\n if (!bucket) {\n bucket = new Map<string, number>();\n prodBuckets.set(commodity, bucket);\n }\n bucket.set(mk, (bucket.get(mk) ?? 0) + (p.volume ?? 0));\n }\n\n const dominantUnit = (units: Map<string, number>): string => {\n let best = '';\n let bestN = -1;\n for (const [unit, n] of units) {\n if (n > bestN) {\n best = unit;\n bestN = n;\n }\n }\n return best;\n };\n\n return commodityDist.map((d) => {\n const a = agg.get(d.key) ?? {\n count: 0,\n producing: 0,\n capacity: 0,\n units: new Map<string, number>(),\n };\n const bucket = prodBuckets.get(d.key);\n const spark = bucket ? monthlySeries(bucket, 12).map((s) => s.value) : [];\n const booked = commodityReserves.get(d.key);\n return {\n key: d.key as TheCommodityType,\n name: formatEnumLabel(d.key),\n color: commodityColor.get(d.key) ?? 'var(--color-accent-primary)',\n assetCount: a.count,\n producing: a.producing,\n capacity: a.capacity,\n unit: dominantUnit(a.units),\n reserves: booked ? booked.total : null,\n reserveUnit: booked?.unit ?? '',\n reserveAssets: booked?.assets ?? 0,\n spark,\n };\n });\n }, [assetRows, productionRows, assetCommodity, commodityDist, commodityColor, commodityReserves]);\n\n // ── Recent activity ────────────────────────────────────────────────────────\n const assetName = useMemo(() => {\n const m = new Map<string, string>();\n for (const a of assetRows) m.set(a.id, a.name);\n return m;\n }, [assetRows]);\n\n const recentContracts = useMemo(\n () =>\n [...contractRows]\n .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())\n .slice(0, 6),\n [contractRows]\n );\n\n const recentProduction = useMemo(\n () =>\n [...productionRows]\n .sort((a, b) => new Date(b.periodEnd).getTime() - new Date(a.periodEnd).getTime())\n .slice(0, 6),\n [productionRows]\n );\n\n const contractColumns: Column<TheOfftakeContract>[] = [\n {\n key: 'reference',\n header: 'Reference',\n render: (c) => <span className=\"font-medium text-text-primary\">{c.reference}</span>,\n },\n {\n key: 'counterparty',\n header: 'Counterparty',\n hideOnMobile: true,\n render: (c) => <span className=\"text-text-secondary\">{c.counterparty}</span>,\n },\n {\n key: 'commodity',\n header: 'Commodity',\n hideOnMobile: true,\n render: (c) => <span className=\"text-text-secondary\">{formatEnumLabel(c.commodity)}</span>,\n },\n { key: 'side', header: 'Side', render: (c) => <StatusBadge status={c.side} /> },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n hideOnMobile: true,\n render: (c) => (\n <span className=\"tabular-nums text-text-secondary\">\n {formatVolume(c.contractedVolume, c.unit)}\n </span>\n ),\n },\n { key: 'status', header: 'Status', render: (c) => <StatusBadge status={c.status} /> },\n {\n key: 'created',\n header: 'Created',\n align: 'right',\n hideOnMobile: true,\n render: (c) => <span className=\"text-text-tertiary\">{formatRelativeTime(c.createdAt)}</span>,\n },\n ];\n\n const productionColumns: Column<TheProductionRecord>[] = [\n {\n key: 'asset',\n header: 'Asset',\n render: (p) => (\n <span className=\"font-medium text-text-primary\">\n {assetName.get(p.assetId) ?? 'Unknown asset'}\n </span>\n ),\n },\n {\n key: 'period',\n header: 'Period',\n hideOnMobile: true,\n render: (p) => (\n <span className=\"text-text-secondary\">\n {formatDate(p.periodStart)} – {formatDate(p.periodEnd)}\n </span>\n ),\n },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n render: (p) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatVolume(p.volume, p.unit)}\n </span>\n ),\n },\n {\n key: 'downtime',\n header: 'Downtime',\n align: 'right',\n hideOnMobile: true,\n render: (p) => (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(p.downtimeHours)} h</span>\n ),\n },\n {\n key: 'logged',\n header: 'Logged',\n align: 'right',\n hideOnMobile: true,\n render: (p) => <span className=\"text-text-tertiary\">{formatRelativeTime(p.createdAt)}</span>,\n },\n ];\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n const t = useTheTranslation();\n const description = t(\n 'theglobalfuel.pages.dashboard.subtitle',\n 'Welcome back, {name} — your energy, mineral & commodity resource control room.',\n { name: greetingName }\n );\n\n // Full-page loader only while every primary domain is still loading its first page.\n const initialLoading =\n assets.isLoading &&\n contracts.isLoading &&\n emissions.isLoading &&\n inventory.isLoading &&\n compliance.isLoading;\n\n const pageTitle = t('theglobalfuel.pages.dashboard.title', 'TheGlobalFuel');\n\n if (initialLoading) {\n return (\n <PageLayout\n title={pageTitle}\n titleGradient\n aurora\n description={description}\n dataTour=\"theglobalfuel-dashboard-page\"\n >\n <LoadingView label=\"Loading your resource portfolio…\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={pageTitle}\n titleGradient\n aurora\n description={description}\n dataTour=\"theglobalfuel-dashboard-page\"\n >\n {/* KPI grid */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6\">\n <MetricsCard\n icon={<Layers className=\"h-5 w-5\" />}\n value={withPlus(assetRows, assets.data?.pageInfo?.hasMore)}\n label=\"Resource assets\"\n featured\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={assetRows.length ? (producingCount / assetRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-success-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{producingCount}</span> producing\n </p>\n </div>\n }\n />\n\n <MetricsCard\n icon={<ScrollText className=\"h-5 w-5\" />}\n value={withPlus(contractRows, contracts.data?.pageInfo?.hasMore)}\n label=\"Offtake contracts\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={contractRows.length ? (activeContracts / contractRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-action-primary-bg\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{activeContracts}</span> active\n </p>\n </div>\n }\n />\n\n <MetricsCard\n icon={<Ship className=\"h-5 w-5\" />}\n value={shipments.isError ? '—' : formatNumber(inTransitRows.length)}\n label=\"Shipments in transit\"\n footer={\n <p className=\"text-xs text-text-tertiary\">\n {inTransitRows.length > 0 ? 'In motion across carriers' : 'No active movements'}\n </p>\n }\n />\n\n <MetricsCard\n icon={<Factory className=\"h-5 w-5\" />}\n value={emissions.isError ? '—' : formatTonnesCo2e(totalTonnes)}\n label=\"Emissions logged\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={emissionRows.length ? (verifiedEmissions / emissionRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-success-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{verifiedEmissions}</span>{' '}\n verified\n </p>\n </div>\n }\n />\n\n <MetricsCard\n icon={<Boxes className=\"h-5 w-5\" />}\n value={inventory.isError ? '—' : formatNumber(availableLots)}\n label=\"Inventory available\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={inventoryRows.length ? (availableLots / inventoryRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-action-primary-bg\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">\n {withPlus(inventoryRows, inventory.data?.pageInfo?.hasMore)}\n </span>{' '}\n lots total\n </p>\n </div>\n }\n />\n\n <MetricsCard\n icon={<ShieldCheck className=\"h-5 w-5\" />}\n value={compliance.isError ? '—' : formatNumber(attentionCerts)}\n label=\"Certs need attention\"\n accent={attentionCerts > 0 ? 'warning' : certRows.length > 0 ? 'success' : undefined}\n footer={\n <p\n className={\n attentionCerts > 0\n ? 'text-xs font-medium text-status-warning-text'\n : 'text-xs text-text-tertiary'\n }\n >\n {certRows.length === 0\n ? 'No certifications tracked yet'\n : attentionCerts > 0\n ? `${pendingCerts} pending · ${expiringCerts} expiring`\n : 'All certifications current'}\n </p>\n }\n />\n </div>\n\n {/* Analytics — production trend + commodity mix (real aggregates) */}\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <ChartCard\n className=\"lg:col-span-2\"\n title=\"Production trend\"\n caption=\"Metered output aggregated by month across your producing assets.\"\n icon={<Activity className=\"h-4 w-4\" />}\n link={{ label: 'Production', onClick: () => navigateTo('production') }}\n isLoading={production.isLoading}\n isError={production.isError}\n onRetry={() => production.refetch()}\n isEmpty={productionSeries.length === 0}\n emptyLabel=\"No production logged yet — record output to build the trend.\"\n footer={\n productionSeries.length > 0\n ? `${formatNumber(totalProductionVolume, { maximumFractionDigits: 0 })} total volume across ${withPlus(productionRows, production.data?.pageInfo?.hasMore)} records`\n : undefined\n }\n >\n <AreaChart series={productionSeries} height={220} title=\"Monthly production volume\" />\n </ChartCard>\n\n <ChartCard\n title=\"Commodity mix\"\n caption=\"Share of assets across your commodity streams.\"\n icon={<Fuel className=\"h-4 w-4\" />}\n link={{ label: 'Assets', onClick: () => navigateTo('assets') }}\n isLoading={assets.isLoading}\n isError={assets.isError}\n onRetry={() => assets.refetch()}\n isEmpty={commodityMix.length === 0}\n emptyLabel=\"No assets registered yet.\"\n footer={\n commodityMix.length > 0\n ? `${withPlus(assetRows, assets.data?.pageInfo?.hasMore)} assets · ${commodityMix.length} ${commodityMix.length === 1 ? 'commodity' : 'commodities'}`\n : undefined\n }\n >\n <div className=\"flex items-center justify-center py-2\">\n <DonutChart\n segments={commodityMix}\n size={168}\n centerLabel={formatNumber(assetRows.length)}\n centerCaption=\"assets\"\n valueFormatter={(v) => formatNumber(v)}\n title=\"Assets by commodity\"\n />\n </div>\n </ChartCard>\n </div>\n\n {/* Commodity portfolio — one model, every commodity */}\n <Section\n title=\"Commodity portfolio\"\n description=\"One model, every commodity — assets, capacity, booked proved reserves and output across your book.\"\n >\n {assets.isLoading ? (\n <LoadingView label=\"Loading commodities…\" />\n ) : assets.isError ? (\n <ErrorView\n message=\"Couldn't load your commodity portfolio.\"\n onRetry={() => assets.refetch()}\n />\n ) : commodityCards.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Layers className=\"h-10 w-10\" />}\n title=\"No commodities yet\"\n description=\"Register your first resource asset to build out your commodity portfolio.\"\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n {commodityCards.map((card) => (\n <CommodityCard key={card.key} card={card} onOpen={() => navigateTo('assets')} />\n ))}\n </div>\n <p className=\"mt-3 text-xs text-text-tertiary\">\n {reserves.isError\n ? 'Proved reserves could not be loaded, so the proved column is blank — it is not a zero position.'\n : reserves.isLoading\n ? 'Loading booked proved reserves…'\n : 'Proved reserves use the latest estimate booked per asset, totalled only within a single unit of measure.'}\n </p>\n </>\n )}\n </Section>\n\n {/* Lifecycle distribution + emissions by scope */}\n <div className=\"mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <DistributionCard\n title=\"Assets by lifecycle\"\n description=\"Where the portfolio sits from exploration to retirement.\"\n icon={<GitBranch className=\"h-4 w-4\" />}\n linkLabel=\"Assets\"\n onLink={() => navigateTo('assets')}\n isLoading={assets.isLoading}\n isError={assets.isError}\n onRetry={() => assets.refetch()}\n emptyLabel=\"No assets to profile yet.\"\n bars={lifecycleBars}\n footer={\n assetRows.length > 0\n ? `${producingCount} of ${withPlus(assetRows, assets.data?.pageInfo?.hasMore)} currently producing`\n : undefined\n }\n />\n\n <ChartCard\n title=\"Emissions by scope\"\n caption=\"GHG Protocol Scope 1 / 2 / 3 — tonnes CO₂e logged.\"\n icon={<Gauge className=\"h-4 w-4\" />}\n link={{ label: 'Emissions', onClick: () => navigateTo('emissions') }}\n isLoading={emissions.isLoading}\n isError={emissions.isError}\n onRetry={() => emissions.refetch()}\n isEmpty={scopeMix.length === 0}\n emptyLabel=\"No emission records logged yet.\"\n footer={\n emissionRows.length > 0\n ? `${formatTonnesCo2e(totalTonnes)} across ${withPlus(emissionRows, emissions.data?.pageInfo?.hasMore)} records`\n : undefined\n }\n >\n <div className=\"flex items-center justify-center py-2\">\n <DonutChart\n segments={scopeMix}\n size={168}\n centerLabel={formatNumber(totalTonnes, {\n notation: 'compact',\n maximumFractionDigits: 1,\n })}\n centerCaption=\"t CO₂e\"\n valueFormatter={(v) =>\n `${formatNumber(v, { maximumFractionDigits: v >= 100 ? 0 : 1 })} t`\n }\n title=\"Emissions by scope\"\n />\n </div>\n </ChartCard>\n </div>\n\n {/* Recent offtake contracts */}\n <Section\n title=\"Recent offtake contracts\"\n description=\"Latest buy/sell agreements across your commodity book.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('contracts')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n All contracts <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {contracts.isLoading ? (\n <LoadingView label=\"Loading contracts…\" />\n ) : contracts.isError ? (\n <ErrorView message=\"Couldn't load contracts.\" onRetry={() => contracts.refetch()} />\n ) : recentContracts.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ScrollText className=\"h-10 w-10\" />}\n title=\"No contracts yet\"\n description=\"Draft your first offtake contract to start tracking commodity trade.\"\n />\n ) : (\n <DataTable\n columns={contractColumns}\n rows={recentContracts}\n rowKey={(c) => c.id}\n onRowClick={(c) => navigateTo(`contracts/${c.id}`)}\n rowLabel={(c) => `Open contract ${c.reference}`}\n />\n )}\n </Section>\n\n {/* Recent production */}\n <Section\n title=\"Recent production\"\n description=\"Latest metered output captured against your producing assets.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('production')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-action-primary-bg hover:underline\"\n >\n All production <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {production.isLoading ? (\n <LoadingView label=\"Loading production…\" />\n ) : production.isError ? (\n <ErrorView\n message=\"Couldn't load production records.\"\n onRetry={() => production.refetch()}\n />\n ) : recentProduction.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<ClipboardList className=\"h-10 w-10\" />}\n title=\"No production logged yet\"\n description=\"Record production against an asset to build its output history.\"\n />\n ) : (\n <DataTable\n columns={productionColumns}\n rows={recentProduction}\n rowKey={(p) => p.id}\n onRowClick={(p) => navigateTo(`assets/${p.assetId}`)}\n rowLabel={(p) => `Open ${assetName.get(p.assetId) ?? 'asset'}`}\n />\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ProductionPage.d.ts","sourceRoot":"","sources":["../../../../src/theglobalfuel/pages/production/ProductionPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2EH,wBAAgB,cAAc,gCAqa7B"}
1
+ {"version":3,"file":"ProductionPage.d.ts","sourceRoot":"","sources":["../../../../src/theglobalfuel/pages/production/ProductionPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6EH,wBAAgB,cAAc,gCA+b7B"}
@@ -34,6 +34,7 @@ var j = [
34
34
  unit: "BARRELS",
35
35
  grade: "",
36
36
  downtimeHours: "",
37
+ source: "",
37
38
  notes: ""
38
39
  });
39
40
  function F() {
@@ -121,6 +122,18 @@ function F() {
121
122
  children: [a(e.downtimeHours), "h"]
122
123
  })
123
124
  },
125
+ {
126
+ key: "source",
127
+ header: "Source",
128
+ hideOnMobile: !0,
129
+ render: (e) => e.source ? /* @__PURE__ */ b("span", {
130
+ className: "text-text-secondary",
131
+ children: e.source
132
+ }) : /* @__PURE__ */ b("span", {
133
+ className: "text-text-tertiary",
134
+ children: "—"
135
+ })
136
+ },
124
137
  {
125
138
  key: "created",
126
139
  header: "Logged",
@@ -165,6 +178,7 @@ function F() {
165
178
  unit: V.unit,
166
179
  grade: V.grade ? Number(V.grade) : null,
167
180
  downtimeHours: V.downtimeHours ? Number(V.downtimeHours) : null,
181
+ source: V.source.trim() || null,
168
182
  notes: V.notes.trim() || null
169
183
  }), I.success("Production recorded"), Q();
170
184
  } catch (e) {
@@ -391,6 +405,21 @@ function F() {
391
405
  })]
392
406
  })]
393
407
  }),
408
+ /* @__PURE__ */ x("div", {
409
+ className: "grid gap-1.5",
410
+ children: [/* @__PURE__ */ b(w, {
411
+ htmlFor: "prSource",
412
+ children: "Source"
413
+ }), /* @__PURE__ */ b(C, {
414
+ id: "prSource",
415
+ value: V.source,
416
+ onChange: (e) => H({
417
+ ...V,
418
+ source: e.target.value
419
+ }),
420
+ placeholder: "SCADA, Manual, Meter…"
421
+ })]
422
+ }),
394
423
  /* @__PURE__ */ x("div", {
395
424
  className: "grid gap-1.5",
396
425
  children: [/* @__PURE__ */ b(w, {
@@ -1 +1 @@
1
- {"version":3,"file":"ProductionPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/production/ProductionPage.tsx"],"sourcesContent":["/**\n * Production — every production record across all assets.\n * @module theglobalfuel/pages/production\n *\n * Reads `theListProductionRecords` (optionally filtered by asset); writes\n * `theRecordProduction`. Asset names are resolved from `theListAssets`. The KPI\n * strip summarises the volume + downtime captured on the current page. No mock\n * data — every number comes from the real `wspace-theglobalfuel-svc` contract.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { Plus, BarChart3, Droplets, Clock, Gauge } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { useTheGlobalFuel } from '../../context';\nimport {\n useTheAssets,\n useTheProductionRecords,\n useTheRecordProduction,\n useCursorPager,\n useNotify,\n useThePageView,\n useTheTranslation,\n} from '../../hooks';\nimport {\n formatDate,\n formatNumber,\n formatRelativeTime,\n formatVolume,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport { toBackendDate } from '../../utils';\nimport type { TheProductionRecord, TheUnit } from '../../types';\n\nconst UNITS: TheUnit[] = ['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 RecordFormState {\n assetId: string;\n periodStart: string;\n periodEnd: string;\n volume: string;\n unit: TheUnit;\n grade: string;\n downtimeHours: string;\n notes: string;\n}\n\nconst emptyForm = (): RecordFormState => ({\n assetId: '',\n periodStart: today(),\n periodEnd: today(),\n volume: '',\n unit: 'BARRELS',\n grade: '',\n downtimeHours: '',\n notes: '',\n});\n\nexport function ProductionPage() {\n useThePageView('production');\n const t = useTheTranslation();\n const { navigateTo } = useTheGlobalFuel();\n const notify = useNotify();\n const [assetId, setAssetId] = useState<string>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<RecordFormState>(emptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n // Assets feed the filter Select, the create picker, and the id→name map.\n const assetsQuery = useTheAssets({ pagination: { limit: 100 } });\n const assetMap = useMemo<Record<string, string>>(() => {\n const map: Record<string, string> = {};\n for (const a of assetsQuery.data?.items ?? []) map[a.id] = a.name;\n return map;\n }, [assetsQuery.data]);\n\n const { data, isLoading, isError, refetch } = useTheProductionRecords({\n assetId: assetId === 'ALL' ? undefined : assetId,\n pagination: { limit: PAGE_SIZE, offset: pager.page * PAGE_SIZE },\n });\n const recordProduction = useTheRecordProduction();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n // KPI summary — page-scoped totals (volume grouped by unit, downtime summed).\n const { volumeValue, volumeFooter, downtimeTotal } = useMemo(() => {\n const byUnit = new Map<TheUnit, number>();\n let downtime = 0;\n for (const r of rows) {\n byUnit.set(r.unit, (byUnit.get(r.unit) ?? 0) + r.volume);\n downtime += r.downtimeHours;\n }\n const units = [...byUnit.entries()];\n let value: string;\n let footer: ReactNode | undefined;\n if (units.length === 0) {\n value = '—';\n } else if (units.length === 1) {\n value = formatVolume(units[0][1], units[0][0]);\n } else {\n value = 'Mixed units';\n footer = (\n <p className=\"text-xs tabular-nums text-text-tertiary\">\n {units.map(([u, v]) => formatVolume(v, u)).join(' · ')}\n </p>\n );\n }\n return { volumeValue: value, volumeFooter: footer, downtimeTotal: downtime };\n }, [rows]);\n\n const columns: Column<TheProductionRecord>[] = [\n {\n key: 'asset',\n header: 'Asset',\n render: (r) => (\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">\n {assetMap[r.assetId] ?? `${r.assetId.slice(0, 8)}…`}\n </p>\n </div>\n ),\n },\n {\n key: 'period',\n header: 'Period',\n hideOnMobile: true,\n sortValue: (r) => new Date(r.periodStart).getTime(),\n render: (r) => (\n <span className=\"text-text-secondary\">\n {formatDate(r.periodStart)} – {formatDate(r.periodEnd)}\n </span>\n ),\n },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n sortValue: (r) => r.volume,\n render: (r) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatVolume(r.volume, r.unit)}\n </span>\n ),\n },\n {\n key: 'grade',\n header: 'Grade',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.grade ?? 0,\n render: (r) =>\n r.grade != null ? (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(r.grade)}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'downtime',\n header: 'Downtime',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.downtimeHours,\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(r.downtimeHours)}h</span>\n ),\n },\n {\n key: 'created',\n header: 'Logged',\n align: 'right',\n hideOnMobile: true,\n render: (r) => <span className=\"text-text-tertiary\">{formatRelativeTime(r.createdAt)}</span>,\n },\n ];\n\n const openDialog = () => {\n setForm(emptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(emptyForm());\n setFormError(null);\n };\n\n const handleRecord = async () => {\n if (!form.assetId) {\n setFormError('Select an asset.');\n return;\n }\n // Anchor date-only inputs at noon UTC so the calendar day survives every TZ.\n const periodStart = toBackendDate(form.periodStart);\n const periodEnd = toBackendDate(form.periodEnd);\n if (!periodStart || !periodEnd) {\n setFormError('Period start and end are required.');\n return;\n }\n if (new Date(periodEnd).getTime() < new Date(periodStart).getTime()) {\n setFormError('Period end must be on or after the period start.');\n return;\n }\n const volume = Number(form.volume);\n if (!form.volume || Number.isNaN(volume) || volume <= 0) {\n const message = 'Volume must be greater than zero.';\n setFormError(message);\n notify.error(message);\n return;\n }\n setFormError(null);\n try {\n await recordProduction.mutateAsync({\n assetId: form.assetId,\n periodStart,\n periodEnd,\n volume,\n unit: form.unit,\n grade: form.grade ? Number(form.grade) : null,\n downtimeHours: form.downtimeHours ? Number(form.downtimeHours) : null,\n notes: form.notes.trim() || null,\n });\n notify.success('Production recorded');\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to record production.');\n notify.fromError(err);\n }\n };\n\n const assetOptions = assetsQuery.data?.items ?? [];\n\n return (\n <PageLayout\n title={t('theglobalfuel.pages.production.title', 'Production')}\n description={t(\n 'theglobalfuel.pages.production.description',\n 'Captured production volumes across every field, well, mine & plant — with downtime and grade.'\n )}\n dataTour=\"theglobalfuel-production-page\"\n actions={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record production\n </Button>\n }\n >\n {/* KPI strip — page-scoped totals */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Droplets className=\"h-5 w-5\" />}\n value={volumeValue}\n label=\"Volume on this page\"\n featured\n footer={volumeFooter}\n />\n <MetricsCard\n icon={<Clock className=\"h-5 w-5\" />}\n value={`${formatNumber(downtimeTotal)}h`}\n label=\"Downtime on this page\"\n accent={downtimeTotal > 0 ? 'warning' : undefined}\n />\n <MetricsCard\n icon={<BarChart3 className=\"h-5 w-5\" />}\n value={rows.length}\n label=\"Records on this page\"\n />\n </div>\n\n {/* Filter */}\n <div className=\"mt-6 mb-5 flex flex-wrap items-center gap-3\">\n <Select\n value={assetId}\n onValueChange={(v) => {\n setAssetId(v);\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-56\" aria-label=\"Filter by asset\">\n <SelectValue placeholder=\"All assets\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All assets</SelectItem>\n {assetOptions.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading production records…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load production records.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Gauge className=\"h-10 w-10\" />}\n title=\"No production recorded\"\n description={\n assetId === 'ALL'\n ? 'Record a production period to start tracking output, grade and downtime.'\n : 'No production has been recorded for this asset yet.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record production\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\n onRowClick={(r) => navigateTo(`assets/${r.assetId}`)}\n rowLabel={(r) => `Open ${assetMap[r.assetId] ?? 'asset'}`}\n defaultSort={{ key: 'period', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={pager.onPageChange}\n />\n </>\n )}\n\n {/* Record production dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => (open ? setDialogOpen(true) : closeDialog())}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record production</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log the volume produced by an asset over a reporting period.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleRecord();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prAsset\">Asset *</Label>\n <Select\n value={form.assetId}\n onValueChange={(v) => setForm({ ...form, assetId: v })}\n >\n <SelectTrigger id=\"prAsset\">\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n {assetOptions.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prStart\">Period start *</Label>\n <Input\n id=\"prStart\"\n type=\"date\"\n value={form.periodStart}\n onChange={(e) => setForm({ ...form, periodStart: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prEnd\">Period end *</Label>\n <Input\n id=\"prEnd\"\n type=\"date\"\n value={form.periodEnd}\n onChange={(e) => setForm({ ...form, periodEnd: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prVolume\">Volume *</Label>\n <Input\n id=\"prVolume\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.volume}\n onChange={(e) => setForm({ ...form, volume: e.target.value })}\n placeholder=\"12500\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prUnit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"prUnit\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {UNITS.map((u) => (\n <SelectItem key={u} value={u}>\n {formatEnumLabel(u)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prGrade\">Grade</Label>\n <Input\n id=\"prGrade\"\n type=\"number\"\n step=\"any\"\n value={form.grade}\n onChange={(e) => setForm({ ...form, grade: e.target.value })}\n placeholder=\"Quality / concentration\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prDowntime\">Downtime (hours)</Label>\n <Input\n id=\"prDowntime\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.downtimeHours}\n onChange={(e) => setForm({ ...form, downtimeHours: e.target.value })}\n placeholder=\"0\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prNotes\">Notes</Label>\n <Textarea\n id=\"prNotes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this period.\"\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={recordProduction.isPending}>\n {recordProduction.isPending ? 'Recording…' : 'Record production'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProductionPage.js","names":[],"sources":["../../../../src/theglobalfuel/pages/production/ProductionPage.tsx"],"sourcesContent":["/**\n * Production — every production record across all assets.\n * @module theglobalfuel/pages/production\n *\n * Reads `theListProductionRecords` (optionally filtered by asset); writes\n * `theRecordProduction`. Asset names are resolved from `theListAssets`. The KPI\n * strip summarises the volume + downtime captured on the current page. No mock\n * data — every number comes from the real `wspace-theglobalfuel-svc` contract.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { Plus, BarChart3, Droplets, Clock, Gauge } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { DataTable, type Column } from '../../components/DataTable';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport { EmptyState } from '../../components/EmptyState';\nimport { Pagination } from '../../components/Pagination';\nimport { useTheGlobalFuel } from '../../context';\nimport {\n useTheAssets,\n useTheProductionRecords,\n useTheRecordProduction,\n useCursorPager,\n useNotify,\n useThePageView,\n useTheTranslation,\n} from '../../hooks';\nimport {\n formatDate,\n formatNumber,\n formatRelativeTime,\n formatVolume,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport { toBackendDate } from '../../utils';\nimport type { TheProductionRecord, TheUnit } from '../../types';\n\nconst UNITS: TheUnit[] = ['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 RecordFormState {\n assetId: string;\n periodStart: string;\n periodEnd: string;\n volume: string;\n unit: TheUnit;\n grade: string;\n downtimeHours: string;\n source: string;\n notes: string;\n}\n\nconst emptyForm = (): RecordFormState => ({\n assetId: '',\n periodStart: today(),\n periodEnd: today(),\n volume: '',\n unit: 'BARRELS',\n grade: '',\n downtimeHours: '',\n source: '',\n notes: '',\n});\n\nexport function ProductionPage() {\n useThePageView('production');\n const t = useTheTranslation();\n const { navigateTo } = useTheGlobalFuel();\n const notify = useNotify();\n const [assetId, setAssetId] = useState<string>('ALL');\n const pager = useCursorPager();\n const [dialogOpen, setDialogOpen] = useState(false);\n const [form, setForm] = useState<RecordFormState>(emptyForm);\n const [formError, setFormError] = useState<string | null>(null);\n\n // Assets feed the filter Select, the create picker, and the id→name map.\n const assetsQuery = useTheAssets({ pagination: { limit: 100 } });\n const assetMap = useMemo<Record<string, string>>(() => {\n const map: Record<string, string> = {};\n for (const a of assetsQuery.data?.items ?? []) map[a.id] = a.name;\n return map;\n }, [assetsQuery.data]);\n\n const { data, isLoading, isError, refetch } = useTheProductionRecords({\n assetId: assetId === 'ALL' ? undefined : assetId,\n pagination: { limit: PAGE_SIZE, offset: pager.page * PAGE_SIZE },\n });\n const recordProduction = useTheRecordProduction();\n\n const rows = useMemo(() => data?.items ?? [], [data]);\n\n // KPI summary — page-scoped totals (volume grouped by unit, downtime summed).\n const { volumeValue, volumeFooter, downtimeTotal } = useMemo(() => {\n const byUnit = new Map<TheUnit, number>();\n let downtime = 0;\n for (const r of rows) {\n byUnit.set(r.unit, (byUnit.get(r.unit) ?? 0) + r.volume);\n downtime += r.downtimeHours;\n }\n const units = [...byUnit.entries()];\n let value: string;\n let footer: ReactNode | undefined;\n if (units.length === 0) {\n value = '—';\n } else if (units.length === 1) {\n value = formatVolume(units[0][1], units[0][0]);\n } else {\n value = 'Mixed units';\n footer = (\n <p className=\"text-xs tabular-nums text-text-tertiary\">\n {units.map(([u, v]) => formatVolume(v, u)).join(' · ')}\n </p>\n );\n }\n return { volumeValue: value, volumeFooter: footer, downtimeTotal: downtime };\n }, [rows]);\n\n const columns: Column<TheProductionRecord>[] = [\n {\n key: 'asset',\n header: 'Asset',\n render: (r) => (\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">\n {assetMap[r.assetId] ?? `${r.assetId.slice(0, 8)}…`}\n </p>\n </div>\n ),\n },\n {\n key: 'period',\n header: 'Period',\n hideOnMobile: true,\n sortValue: (r) => new Date(r.periodStart).getTime(),\n render: (r) => (\n <span className=\"text-text-secondary\">\n {formatDate(r.periodStart)} – {formatDate(r.periodEnd)}\n </span>\n ),\n },\n {\n key: 'volume',\n header: 'Volume',\n align: 'right',\n sortValue: (r) => r.volume,\n render: (r) => (\n <span className=\"tabular-nums font-medium text-text-primary\">\n {formatVolume(r.volume, r.unit)}\n </span>\n ),\n },\n {\n key: 'grade',\n header: 'Grade',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.grade ?? 0,\n render: (r) =>\n r.grade != null ? (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(r.grade)}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'downtime',\n header: 'Downtime',\n align: 'right',\n hideOnMobile: true,\n sortValue: (r) => r.downtimeHours,\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatNumber(r.downtimeHours)}h</span>\n ),\n },\n {\n // Provenance of the reading: which system reported it (SCADA / meter /\n // manual entry / an agent writing over the API). Already returned by\n // `theListProductionRecords` — an operator auditing a figure needs to\n // know where it came from before trusting it.\n key: 'source',\n header: 'Source',\n hideOnMobile: true,\n render: (r) =>\n r.source ? (\n <span className=\"text-text-secondary\">{r.source}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'created',\n header: 'Logged',\n align: 'right',\n hideOnMobile: true,\n render: (r) => <span className=\"text-text-tertiary\">{formatRelativeTime(r.createdAt)}</span>,\n },\n ];\n\n const openDialog = () => {\n setForm(emptyForm());\n setFormError(null);\n setDialogOpen(true);\n };\n\n const closeDialog = () => {\n setDialogOpen(false);\n setForm(emptyForm());\n setFormError(null);\n };\n\n const handleRecord = async () => {\n if (!form.assetId) {\n setFormError('Select an asset.');\n return;\n }\n // Anchor date-only inputs at noon UTC so the calendar day survives every TZ.\n const periodStart = toBackendDate(form.periodStart);\n const periodEnd = toBackendDate(form.periodEnd);\n if (!periodStart || !periodEnd) {\n setFormError('Period start and end are required.');\n return;\n }\n if (new Date(periodEnd).getTime() < new Date(periodStart).getTime()) {\n setFormError('Period end must be on or after the period start.');\n return;\n }\n const volume = Number(form.volume);\n if (!form.volume || Number.isNaN(volume) || volume <= 0) {\n const message = 'Volume must be greater than zero.';\n setFormError(message);\n notify.error(message);\n return;\n }\n setFormError(null);\n try {\n await recordProduction.mutateAsync({\n assetId: form.assetId,\n periodStart,\n periodEnd,\n volume,\n unit: form.unit,\n grade: form.grade ? Number(form.grade) : null,\n downtimeHours: form.downtimeHours ? Number(form.downtimeHours) : null,\n source: form.source.trim() || null,\n notes: form.notes.trim() || null,\n });\n notify.success('Production recorded');\n closeDialog();\n } catch (err) {\n setFormError(err instanceof Error ? err.message : 'Failed to record production.');\n notify.fromError(err);\n }\n };\n\n const assetOptions = assetsQuery.data?.items ?? [];\n\n return (\n <PageLayout\n title={t('theglobalfuel.pages.production.title', 'Production')}\n description={t(\n 'theglobalfuel.pages.production.description',\n 'Captured production volumes across every field, well, mine & plant — with downtime and grade.'\n )}\n dataTour=\"theglobalfuel-production-page\"\n actions={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record production\n </Button>\n }\n >\n {/* KPI strip — page-scoped totals */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Droplets className=\"h-5 w-5\" />}\n value={volumeValue}\n label=\"Volume on this page\"\n featured\n footer={volumeFooter}\n />\n <MetricsCard\n icon={<Clock className=\"h-5 w-5\" />}\n value={`${formatNumber(downtimeTotal)}h`}\n label=\"Downtime on this page\"\n accent={downtimeTotal > 0 ? 'warning' : undefined}\n />\n <MetricsCard\n icon={<BarChart3 className=\"h-5 w-5\" />}\n value={rows.length}\n label=\"Records on this page\"\n />\n </div>\n\n {/* Filter */}\n <div className=\"mt-6 mb-5 flex flex-wrap items-center gap-3\">\n <Select\n value={assetId}\n onValueChange={(v) => {\n setAssetId(v);\n pager.reset();\n }}\n >\n <SelectTrigger className=\"w-56\" aria-label=\"Filter by asset\">\n <SelectValue placeholder=\"All assets\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All assets</SelectItem>\n {assetOptions.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isLoading ? (\n <LoadingView label=\"Loading production records…\" />\n ) : isError ? (\n <ErrorView message=\"Couldn't load production records.\" onRetry={() => refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Gauge className=\"h-10 w-10\" />}\n title=\"No production recorded\"\n description={\n assetId === 'ALL'\n ? 'Record a production period to start tracking output, grade and downtime.'\n : 'No production has been recorded for this asset yet.'\n }\n action={\n <Button onClick={openDialog}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record production\n </Button>\n }\n />\n ) : (\n <>\n <DataTable\n columns={columns}\n rows={rows}\n rowKey={(r) => r.id}\n onRowClick={(r) => navigateTo(`assets/${r.assetId}`)}\n rowLabel={(r) => `Open ${assetMap[r.assetId] ?? 'asset'}`}\n defaultSort={{ key: 'period', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={data?.pageInfo?.hasMore ?? false}\n onPageChange={pager.onPageChange}\n />\n </>\n )}\n\n {/* Record production dialog */}\n <ResponsiveDialog\n open={dialogOpen}\n onOpenChange={(open) => (open ? setDialogOpen(true) : closeDialog())}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record production</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log the volume produced by an asset over a reporting period.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <form\n onSubmit={(e) => {\n e.preventDefault();\n void handleRecord();\n }}\n >\n <div className=\"grid gap-4 py-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prAsset\">Asset *</Label>\n <Select\n value={form.assetId}\n onValueChange={(v) => setForm({ ...form, assetId: v })}\n >\n <SelectTrigger id=\"prAsset\">\n <SelectValue placeholder=\"Select an asset\" />\n </SelectTrigger>\n <SelectContent>\n {assetOptions.map((a) => (\n <SelectItem key={a.id} value={a.id}>\n {a.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prStart\">Period start *</Label>\n <Input\n id=\"prStart\"\n type=\"date\"\n value={form.periodStart}\n onChange={(e) => setForm({ ...form, periodStart: e.target.value })}\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prEnd\">Period end *</Label>\n <Input\n id=\"prEnd\"\n type=\"date\"\n value={form.periodEnd}\n onChange={(e) => setForm({ ...form, periodEnd: e.target.value })}\n />\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prVolume\">Volume *</Label>\n <Input\n id=\"prVolume\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.volume}\n onChange={(e) => setForm({ ...form, volume: e.target.value })}\n placeholder=\"12500\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prUnit\">Unit</Label>\n <Select\n value={form.unit}\n onValueChange={(v) => setForm({ ...form, unit: v as TheUnit })}\n >\n <SelectTrigger id=\"prUnit\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {UNITS.map((u) => (\n <SelectItem key={u} value={u}>\n {formatEnumLabel(u)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prGrade\">Grade</Label>\n <Input\n id=\"prGrade\"\n type=\"number\"\n step=\"any\"\n value={form.grade}\n onChange={(e) => setForm({ ...form, grade: e.target.value })}\n placeholder=\"Quality / concentration\"\n />\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prDowntime\">Downtime (hours)</Label>\n <Input\n id=\"prDowntime\"\n type=\"number\"\n min=\"0\"\n step=\"any\"\n value={form.downtimeHours}\n onChange={(e) => setForm({ ...form, downtimeHours: e.target.value })}\n placeholder=\"0\"\n />\n </div>\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prSource\">Source</Label>\n <Input\n id=\"prSource\"\n value={form.source}\n onChange={(e) => setForm({ ...form, source: e.target.value })}\n placeholder=\"SCADA, Manual, Meter…\"\n />\n </div>\n\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"prNotes\">Notes</Label>\n <Textarea\n id=\"prNotes\"\n rows={2}\n value={form.notes}\n onChange={(e) => setForm({ ...form, notes: e.target.value })}\n placeholder=\"Optional context about this period.\"\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={closeDialog}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={recordProduction.isPending}>\n {recordProduction.isPending ? 'Recording…' : 'Record production'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../../src/theglobalfuel/utils/pageContext.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAG1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IAGpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAoB/C;AAMD,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,wBAAwB,CAgH1B"}
1
+ {"version":3,"file":"pageContext.d.ts","sourceRoot":"","sources":["../../../src/theglobalfuel/utils/pageContext.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,UAAU,wBAAwB;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,WAAW;IAG1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IAGpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAoB/C;AAMD,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,wBAAwB,CAuH1B"}
@@ -75,6 +75,8 @@ function n(e, n) {
75
75
  [/^(\/theglobalfuel)?\/reports\/?$/, "THEGLOBALFUEL_REPORTS"],
76
76
  [/^(\/theglobalfuel)?\/alerts\/?$/, "THEGLOBALFUEL_ALERTS"],
77
77
  [/^(\/theglobalfuel)?\/esg-disclosures\/?$/, "THEGLOBALFUEL_ESG_DISCLOSURE"],
78
+ [/^(\/theglobalfuel)?\/safety\/incidents\/?$/, "THEGLOBALFUEL_SAFETY_INCIDENT"],
79
+ [/^(\/theglobalfuel)?\/safety\/?$/, "THEGLOBALFUEL_SAFETY"],
78
80
  [/^\/theglobalfuel\/?$/, "THEGLOBALFUEL_DASHBOARD"]
79
81
  ]) if (t(n, e)) return {
80
82
  entityType: r,
@@ -1 +1 @@
1
- {"version":3,"file":"pageContext.js","names":[],"sources":["../../../src/theglobalfuel/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering — captures the user's current browser context\n * to be sent alongside every assistant prompt for better AI awareness.\n * @module theglobalfuel/utils\n */\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface PageContext {\n // `currentUrl` (NOT `activeUrl`) — must match the agent's allowed-context-key\n // contract, otherwise the agent filters it out.\n currentUrl: string;\n currentPage: string;\n // The entity the user is currently looking at, resolved from the route. Lets\n // the assistant act on \"this / current / here\" without asking for an id.\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveTheGlobalFuelConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'theglobalfuel',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.705.2',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\nexport function resolveTheGlobalFuelConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n\n // TheGlobalFuel routes are mounted under `/theglobalfuel/*` in the host shell.\n // Detail pages carry the entity id as the second-to-last segment.\n const directMatches: Array<[RegExp, string]> = [\n [/^\\/theglobalfuel\\/assets\\/([^/]+)$/, 'THEGLOBALFUEL_ASSET'],\n [/^\\/theglobalfuel\\/contracts\\/([^/]+)$/, 'THEGLOBALFUEL_CONTRACT'],\n [/^\\/theglobalfuel\\/shipments\\/([^/]+)$/, 'THEGLOBALFUEL_SHIPMENT'],\n [/^\\/theglobalfuel\\/inventory\\/([^/]+)$/, 'THEGLOBALFUEL_INVENTORY_LOT'],\n [/^\\/theglobalfuel\\/emissions\\/([^/]+)$/, 'THEGLOBALFUEL_EMISSION_RECORD'],\n [/^\\/theglobalfuel\\/compliance\\/([^/]+)$/, 'THEGLOBALFUEL_COMPLIANCE_CERT'],\n [/^\\/theglobalfuel\\/reserves\\/([^/]+)$/, 'THEGLOBALFUEL_RESERVE_ESTIMATE'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: {\n routePattern: pattern.source,\n },\n };\n }\n }\n\n // Standalone dev mode routes (no /theglobalfuel prefix)\n const standaloneMatches: Array<[RegExp, string]> = [\n [/^\\/assets\\/([^/]+)$/, 'THEGLOBALFUEL_ASSET'],\n [/^\\/contracts\\/([^/]+)$/, 'THEGLOBALFUEL_CONTRACT'],\n [/^\\/shipments\\/([^/]+)$/, 'THEGLOBALFUEL_SHIPMENT'],\n [/^\\/inventory\\/([^/]+)$/, 'THEGLOBALFUEL_INVENTORY_LOT'],\n [/^\\/emissions\\/([^/]+)$/, 'THEGLOBALFUEL_EMISSION_RECORD'],\n [/^\\/compliance\\/([^/]+)$/, 'THEGLOBALFUEL_COMPLIANCE_CERT'],\n [/^\\/reserves\\/([^/]+)$/, 'THEGLOBALFUEL_RESERVE_ESTIMATE'],\n ];\n\n for (const [pattern, entityType] of standaloneMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: {\n routePattern: pattern.source,\n },\n };\n }\n }\n\n // The Provenance page has no `:id` detail route — the asset under trace is\n // carried in the query string (`?assetId=…`, the same parameter the Share\n // action produces). Resolve it so \"explain this chain of custody\" works from\n // a shared link without the user restating which asset they mean.\n if (/^(\\/theglobalfuel)?\\/provenance\\/?$/.test(pathname)) {\n const assetId = params.get('assetId');\n if (assetId) {\n return {\n entityType: 'THEGLOBALFUEL_ASSET',\n entityId: assetId,\n metadata: { surface: 'provenance', routePattern: '/provenance?assetId' },\n };\n }\n }\n\n // Section (list / overview) routes carry no entity id, but the assistant\n // still needs to know which product surface the user is looking at so it can\n // scope \"show me…\", \"why is this down…\" style questions. Previously only\n // detail routes resolved anything, which left /production, /provenance and\n // the dashboard index with no context at all.\n const sectionMatches: Array<[RegExp, string]> = [\n [/^(\\/theglobalfuel)?\\/assets\\/?$/, 'THEGLOBALFUEL_ASSET'],\n [/^(\\/theglobalfuel)?\\/production\\/?$/, 'THEGLOBALFUEL_PRODUCTION_RECORD'],\n [/^(\\/theglobalfuel)?\\/reserves\\/?$/, 'THEGLOBALFUEL_RESERVE_ESTIMATE'],\n [/^(\\/theglobalfuel)?\\/contracts\\/?$/, 'THEGLOBALFUEL_CONTRACT'],\n [/^(\\/theglobalfuel)?\\/shipments\\/?$/, 'THEGLOBALFUEL_SHIPMENT'],\n [/^(\\/theglobalfuel)?\\/inventory\\/?$/, 'THEGLOBALFUEL_INVENTORY_LOT'],\n [/^(\\/theglobalfuel)?\\/emissions\\/?$/, 'THEGLOBALFUEL_EMISSION_RECORD'],\n [/^(\\/theglobalfuel)?\\/compliance\\/?$/, 'THEGLOBALFUEL_COMPLIANCE_CERT'],\n [/^(\\/theglobalfuel)?\\/provenance\\/?$/, 'THEGLOBALFUEL_PROVENANCE'],\n [/^(\\/theglobalfuel)?\\/analytics\\/?$/, 'THEGLOBALFUEL_ANALYTICS'],\n [/^(\\/theglobalfuel)?\\/reports\\/?$/, 'THEGLOBALFUEL_REPORTS'],\n [/^(\\/theglobalfuel)?\\/alerts\\/?$/, 'THEGLOBALFUEL_ALERTS'],\n [/^(\\/theglobalfuel)?\\/esg-disclosures\\/?$/, 'THEGLOBALFUEL_ESG_DISCLOSURE'],\n // Product root = the portfolio dashboard (the index route).\n [/^\\/theglobalfuel\\/?$/, 'THEGLOBALFUEL_DASHBOARD'],\n ];\n\n for (const [pattern, entityType] of sectionMatches) {\n if (matchPath(pattern, pathname)) {\n return {\n entityType,\n metadata: { surface: 'section', routePattern: pattern.source },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n },\n };\n}\n"],"mappings":";AA6BA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QACpD,IAAS,EACb,OAAO,SAAS,UAChB,OAAO,SAAS,OACjB;AAED,QAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,UAAU;EACV,SAA6C;EAC7C,cAAc,GAAG,OAAO,WAAW,GAAG,OAAO;EAC7C,WAAW,UAAU;EACrB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC;;AAGH,SAAS,EAAU,GAAiB,GAA2C;AAC7E,QAAO,EAAS,MAAM,EAAQ;;AAGhC,SAAgB,EACd,GACA,GAC0B;CAC1B,IAAM,IAAS,IAAI,gBAAgB,EAAO;AAc1C,MAAK,IAAM,CAAC,GAAS,MAV0B;EAC7C,CAAC,sCAAsC,sBAAsB;EAC7D,CAAC,yCAAyC,yBAAyB;EACnE,CAAC,yCAAyC,yBAAyB;EACnE,CAAC,yCAAyC,8BAA8B;EACxE,CAAC,yCAAyC,gCAAgC;EAC1E,CAAC,0CAA0C,gCAAgC;EAC3E,CAAC,wCAAwC,iCAAiC;EAC3E,EAEkD;EACjD,IAAM,IAAQ,EAAU,GAAS,EAAS;AAC1C,MAAI,EACF,QAAO;GACL;GACA,UAAU,EAAM;GAChB,UAAU,EACR,cAAc,EAAQ,QACvB;GACF;;AAeL,MAAK,IAAM,CAAC,GAAS,MAV8B;EACjD,CAAC,uBAAuB,sBAAsB;EAC9C,CAAC,0BAA0B,yBAAyB;EACpD,CAAC,0BAA0B,yBAAyB;EACpD,CAAC,0BAA0B,8BAA8B;EACzD,CAAC,0BAA0B,gCAAgC;EAC3D,CAAC,2BAA2B,gCAAgC;EAC5D,CAAC,yBAAyB,iCAAiC;EAC5D,EAEsD;EACrD,IAAM,IAAQ,EAAU,GAAS,EAAS;AAC1C,MAAI,EACF,QAAO;GACL;GACA,UAAU,EAAM;GAChB,UAAU,EACR,cAAc,EAAQ,QACvB;GACF;;AAQL,KAAI,sCAAsC,KAAK,EAAS,EAAE;EACxD,IAAM,IAAU,EAAO,IAAI,UAAU;AACrC,MAAI,EACF,QAAO;GACL,YAAY;GACZ,UAAU;GACV,UAAU;IAAE,SAAS;IAAc,cAAc;IAAuB;GACzE;;AA2BL,MAAK,IAAM,CAAC,GAAS,MAlB2B;EAC9C,CAAC,mCAAmC,sBAAsB;EAC1D,CAAC,uCAAuC,kCAAkC;EAC1E,CAAC,qCAAqC,iCAAiC;EACvE,CAAC,sCAAsC,yBAAyB;EAChE,CAAC,sCAAsC,yBAAyB;EAChE,CAAC,sCAAsC,8BAA8B;EACrE,CAAC,sCAAsC,gCAAgC;EACvE,CAAC,uCAAuC,gCAAgC;EACxE,CAAC,uCAAuC,2BAA2B;EACnE,CAAC,sCAAsC,0BAA0B;EACjE,CAAC,oCAAoC,wBAAwB;EAC7D,CAAC,mCAAmC,uBAAuB;EAC3D,CAAC,4CAA4C,+BAA+B;EAE5E,CAAC,wBAAwB,0BAA0B;EACpD,CAGC,KAAI,EAAU,GAAS,EAAS,CAC9B,QAAO;EACL;EACA,UAAU;GAAE,SAAS;GAAW,cAAc,EAAQ;GAAQ;EAC/D;CAIL,IAAM,IACJ,EAAO,IAAI,MAAM,WACV;EACL,IAAM,IAAW,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;AACpD,SAAO,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;KAC3D;AAEN,QAAO;EACL,WAAW,KAAa,KAAA;EACxB,UAAU,EACR,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE,EACnC;EACF"}
1
+ {"version":3,"file":"pageContext.js","names":[],"sources":["../../../src/theglobalfuel/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering — captures the user's current browser context\n * to be sent alongside every assistant prompt for better AI awareness.\n * @module theglobalfuel/utils\n */\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface PageContext {\n // `currentUrl` (NOT `activeUrl`) — must match the agent's allowed-context-key\n // contract, otherwise the agent filters it out.\n currentUrl: string;\n currentPage: string;\n // The entity the user is currently looking at, resolved from the route. Lets\n // the assistant act on \"this / current / here\" without asking for an id.\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveTheGlobalFuelConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'theglobalfuel',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.705.2',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\nexport function resolveTheGlobalFuelConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n\n // TheGlobalFuel routes are mounted under `/theglobalfuel/*` in the host shell.\n // Detail pages carry the entity id as the second-to-last segment.\n const directMatches: Array<[RegExp, string]> = [\n [/^\\/theglobalfuel\\/assets\\/([^/]+)$/, 'THEGLOBALFUEL_ASSET'],\n [/^\\/theglobalfuel\\/contracts\\/([^/]+)$/, 'THEGLOBALFUEL_CONTRACT'],\n [/^\\/theglobalfuel\\/shipments\\/([^/]+)$/, 'THEGLOBALFUEL_SHIPMENT'],\n [/^\\/theglobalfuel\\/inventory\\/([^/]+)$/, 'THEGLOBALFUEL_INVENTORY_LOT'],\n [/^\\/theglobalfuel\\/emissions\\/([^/]+)$/, 'THEGLOBALFUEL_EMISSION_RECORD'],\n [/^\\/theglobalfuel\\/compliance\\/([^/]+)$/, 'THEGLOBALFUEL_COMPLIANCE_CERT'],\n [/^\\/theglobalfuel\\/reserves\\/([^/]+)$/, 'THEGLOBALFUEL_RESERVE_ESTIMATE'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: {\n routePattern: pattern.source,\n },\n };\n }\n }\n\n // Standalone dev mode routes (no /theglobalfuel prefix)\n const standaloneMatches: Array<[RegExp, string]> = [\n [/^\\/assets\\/([^/]+)$/, 'THEGLOBALFUEL_ASSET'],\n [/^\\/contracts\\/([^/]+)$/, 'THEGLOBALFUEL_CONTRACT'],\n [/^\\/shipments\\/([^/]+)$/, 'THEGLOBALFUEL_SHIPMENT'],\n [/^\\/inventory\\/([^/]+)$/, 'THEGLOBALFUEL_INVENTORY_LOT'],\n [/^\\/emissions\\/([^/]+)$/, 'THEGLOBALFUEL_EMISSION_RECORD'],\n [/^\\/compliance\\/([^/]+)$/, 'THEGLOBALFUEL_COMPLIANCE_CERT'],\n [/^\\/reserves\\/([^/]+)$/, 'THEGLOBALFUEL_RESERVE_ESTIMATE'],\n ];\n\n for (const [pattern, entityType] of standaloneMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: {\n routePattern: pattern.source,\n },\n };\n }\n }\n\n // The Provenance page has no `:id` detail route — the asset under trace is\n // carried in the query string (`?assetId=…`, the same parameter the Share\n // action produces). Resolve it so \"explain this chain of custody\" works from\n // a shared link without the user restating which asset they mean.\n if (/^(\\/theglobalfuel)?\\/provenance\\/?$/.test(pathname)) {\n const assetId = params.get('assetId');\n if (assetId) {\n return {\n entityType: 'THEGLOBALFUEL_ASSET',\n entityId: assetId,\n metadata: { surface: 'provenance', routePattern: '/provenance?assetId' },\n };\n }\n }\n\n // Section (list / overview) routes carry no entity id, but the assistant\n // still needs to know which product surface the user is looking at so it can\n // scope \"show me…\", \"why is this down…\" style questions. Previously only\n // detail routes resolved anything, which left /production, /provenance and\n // the dashboard index with no context at all.\n const sectionMatches: Array<[RegExp, string]> = [\n [/^(\\/theglobalfuel)?\\/assets\\/?$/, 'THEGLOBALFUEL_ASSET'],\n [/^(\\/theglobalfuel)?\\/production\\/?$/, 'THEGLOBALFUEL_PRODUCTION_RECORD'],\n [/^(\\/theglobalfuel)?\\/reserves\\/?$/, 'THEGLOBALFUEL_RESERVE_ESTIMATE'],\n [/^(\\/theglobalfuel)?\\/contracts\\/?$/, 'THEGLOBALFUEL_CONTRACT'],\n [/^(\\/theglobalfuel)?\\/shipments\\/?$/, 'THEGLOBALFUEL_SHIPMENT'],\n [/^(\\/theglobalfuel)?\\/inventory\\/?$/, 'THEGLOBALFUEL_INVENTORY_LOT'],\n [/^(\\/theglobalfuel)?\\/emissions\\/?$/, 'THEGLOBALFUEL_EMISSION_RECORD'],\n [/^(\\/theglobalfuel)?\\/compliance\\/?$/, 'THEGLOBALFUEL_COMPLIANCE_CERT'],\n [/^(\\/theglobalfuel)?\\/provenance\\/?$/, 'THEGLOBALFUEL_PROVENANCE'],\n [/^(\\/theglobalfuel)?\\/analytics\\/?$/, 'THEGLOBALFUEL_ANALYTICS'],\n [/^(\\/theglobalfuel)?\\/reports\\/?$/, 'THEGLOBALFUEL_REPORTS'],\n [/^(\\/theglobalfuel)?\\/alerts\\/?$/, 'THEGLOBALFUEL_ALERTS'],\n [/^(\\/theglobalfuel)?\\/esg-disclosures\\/?$/, 'THEGLOBALFUEL_ESG_DISCLOSURE'],\n // Safety is a two-route surface (`/safety` dashboard, `/safety/incidents`\n // register). The more specific pattern must be tested first, otherwise the\n // dashboard pattern would never match the register. Without these, both\n // routes fell through to the trailing `activeTab` branch and reached the\n // assistant with NO entity context at all.\n [/^(\\/theglobalfuel)?\\/safety\\/incidents\\/?$/, 'THEGLOBALFUEL_SAFETY_INCIDENT'],\n [/^(\\/theglobalfuel)?\\/safety\\/?$/, 'THEGLOBALFUEL_SAFETY'],\n // Product root = the portfolio dashboard (the index route).\n [/^\\/theglobalfuel\\/?$/, 'THEGLOBALFUEL_DASHBOARD'],\n ];\n\n for (const [pattern, entityType] of sectionMatches) {\n if (matchPath(pattern, pathname)) {\n return {\n entityType,\n metadata: { surface: 'section', routePattern: pattern.source },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-theglobalfuel",
3
- "version": "2026.804.2",
3
+ "version": "2026.827.1",
4
4
  "description": "Burdenoff TheGlobalFuel Microfrontend — Energy, Mineral & Commodity Resource operating system",
5
5
  "type": "module",
6
6
  "files": [
@@ -101,7 +101,7 @@
101
101
  "zustand": "^5.0.0"
102
102
  },
103
103
  "publishConfig": {
104
- "access": "restricted"
104
+ "access": "public"
105
105
  },
106
106
  "overrides": {
107
107
  "lodash": ">=4.17.21",