@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,23 +1,27 @@
1
1
  import { PageLayout as e } from "../../components/PageLayout.js";
2
- import { formatEnumLabel as t, formatNumber as n, formatPercent as r, formatVolume as i } from "../../utils/formatters.js";
3
- import { ErrorView as a, LoadingView as o } from "../../components/StateViews.js";
4
- import { EmptyState as ee } from "../../components/EmptyState.js";
5
- import { AreaChart as te } from "../../components/viz/AreaChart.js";
6
- import { BarChart as s } from "../../components/viz/BarChart.js";
7
- import { DonutChart as c } from "../../components/viz/DonutChart.js";
8
- import { StatCard as l } from "../../components/viz/StatCard.js";
9
- import { useTheAssets as ne, useTheContracts as re, useTheInventoryLots as ie, useTheProductionRecords as u, useTheShipments as ae } from "../../hooks/useTheGlobalFuelApi.js";
10
- import { useThePageView as d } from "../../hooks/useThePageView.js";
11
- import { useTheTranslation as oe } from "../../hooks/useTheTranslation.js";
2
+ import { formatEnumLabel as t, formatMoney as n, formatNumber as r, formatPercent as i, formatTonnesCo2e as a, formatUnit as o, formatVolume as ee } from "../../utils/formatters.js";
3
+ import { MiniBar as te } from "../../components/MiniBar.js";
4
+ import { ErrorView as ne, LoadingView as re } from "../../components/StateViews.js";
5
+ import { EmptyState as ie } from "../../components/EmptyState.js";
6
+ import { AreaChart as ae } from "../../components/viz/AreaChart.js";
7
+ import { BarChart as oe } from "../../components/viz/BarChart.js";
8
+ import { DonutChart as s } from "../../components/viz/DonutChart.js";
9
+ import { StatCard as c } from "../../components/viz/StatCard.js";
10
+ import { useTheAssets as se, useTheContracts as ce, useTheEmissionRecords as le, useTheInventoryLots as ue, useTheProductionRecords as de, useTheShipments as fe } from "../../hooks/useTheGlobalFuelApi.js";
11
+ import { useNotify as pe } from "../../hooks/useNotify.js";
12
+ import { useThePageView as me } from "../../hooks/useThePageView.js";
13
+ import { useTheTranslation as he } from "../../hooks/useTheTranslation.js";
12
14
  import "../../hooks/index.js";
13
- import { FilterTabs as se } from "../../components/FilterTabs.js";
15
+ import { toCsv as ge } from "../../utils/csv.js";
16
+ import { downloadTextFile as _e } from "../../utils/provenance.js";
17
+ import { FilterTabs as ve } from "../../components/FilterTabs.js";
14
18
  import "../../components/index.js";
15
- import { useMemo as f, useState as p } from "react";
16
- import { Activity as m, Gauge as h, Package as g, Ship as _, Timer as v, TrendingUp as y } from "lucide-react";
17
- import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
18
- import { GlassCard as C } from "@burdenoff/fe-libs/ui";
19
+ import { useMemo as l, useState as u } from "react";
20
+ import { Activity as ye, Coins as be, Download as xe, Gauge as Se, Leaf as Ce, Package as we, Ship as Te, Timer as Ee, TrendingUp as De } from "lucide-react";
21
+ import { Fragment as Oe, jsx as d, jsxs as f } from "react/jsx-runtime";
22
+ import { Button as ke, GlassCard as p } from "@burdenoff/fe-libs/ui";
19
23
  //#region src/theglobalfuel/pages/analytics/AnalyticsPage.tsx
20
- var w = { limit: 200 }, ce = [
24
+ var m = { limit: 200 }, Ae = [
21
25
  {
22
26
  value: "30",
23
27
  label: "Last 30 days"
@@ -30,12 +34,24 @@ var w = { limit: 200 }, ce = [
30
34
  value: "365",
31
35
  label: "Last 12 months"
32
36
  }
33
- ], T = 1440 * 60 * 1e3;
34
- function E(e) {
37
+ ], h = 1440 * 60 * 1e3, je = {
38
+ SCOPE_1: "var(--color-status-error-text)",
39
+ SCOPE_2: "var(--color-status-warning-text)",
40
+ SCOPE_3: "var(--color-accent-blue)"
41
+ }, Me = [
42
+ "SCOPE_1",
43
+ "SCOPE_2",
44
+ "SCOPE_3"
45
+ ], Ne = new Set([
46
+ "AVAILABLE",
47
+ "RESERVED",
48
+ "IN_TRANSIT"
49
+ ]), g = "vs the preceding window", Pe = new Set(["ACTIVE", "SIGNED"]);
50
+ function Fe(e) {
35
51
  let t = new Date(e);
36
52
  return Number.isNaN(t.getTime()) ? "" : `${t.getUTCFullYear()}-${String(t.getUTCMonth() + 1).padStart(2, "0")}`;
37
53
  }
38
- function le(e) {
54
+ function Ie(e) {
39
55
  let [t, n] = e.split("-"), r = new Date(Date.UTC(Number(t), Number(n) - 1, 1));
40
56
  return Number.isNaN(r.getTime()) ? e : r.toLocaleString(void 0, {
41
57
  month: "short",
@@ -43,27 +59,44 @@ function le(e) {
43
59
  timeZone: "UTC"
44
60
  });
45
61
  }
46
- function ue(e) {
62
+ function Le(e) {
47
63
  let t = /* @__PURE__ */ new Map();
48
64
  for (let n of e) t.set(n.unit, (t.get(n.unit) ?? 0) + (n.volume ?? 0));
49
65
  return t;
50
66
  }
51
- function de(e) {
67
+ function Re(e) {
52
68
  let t = null, n = -1;
53
69
  for (let [r, i] of e) i > n && (t = r, n = i);
54
70
  return t;
55
71
  }
56
- function D() {
57
- d("analytics");
58
- let D = oe(), [O, fe] = p("90"), k = Number(O), [A] = p(() => Date.now()), j = f(() => A - k * T, [A, k]), M = ne({ pagination: w }), N = u({ pagination: w }), P = ae(void 0, void 0, w), F = re({ pagination: w }), I = ie({ pagination: w }), L = M.isLoading || N.isLoading || P.isLoading || F.isLoading || I.isLoading, R = M.isError || N.isError || P.isError || F.isError || I.isError, z = f(() => M.data?.items ?? [], [M.data]), B = f(() => F.data?.items ?? [], [F.data]), V = f(() => I.data?.items ?? [], [I.data]), H = f(() => (N.data?.items ?? []).filter((e) => {
72
+ function _() {
73
+ me("analytics");
74
+ let _ = he(), v = pe(), [y, ze] = u("90"), b = Number(y), [x] = u(() => Date.now()), S = l(() => x - b * h, [x, b]), C = l(() => x - 2 * b * h, [x, b]), w = se({ pagination: m }), T = de({ pagination: m }), E = fe(void 0, void 0, m), D = ce({ pagination: m }), O = ue({ pagination: m }), k = le({ pagination: m }), Be = w.isLoading || T.isLoading || E.isLoading || D.isLoading || O.isLoading || k.isLoading, Ve = w.isError || T.isError || E.isError || D.isError || O.isError || k.isError, A = l(() => w.data?.items ?? [], [w.data]), j = l(() => D.data?.items ?? [], [D.data]), M = l(() => O.data?.items ?? [], [O.data]), N = l(() => (T.data?.items ?? []).filter((e) => {
59
75
  let t = new Date(e.periodEnd ?? e.createdAt).getTime();
60
- return !Number.isNaN(t) && t >= j;
61
- }), [N.data, j]), U = f(() => (P.data?.items ?? []).filter((e) => {
76
+ return !Number.isNaN(t) && t >= S;
77
+ }), [T.data, S]), P = l(() => (E.data?.items ?? []).filter((e) => {
62
78
  let t = new Date(e.deliveredAt ?? e.departedAt ?? e.scheduledAt ?? e.createdAt).getTime();
63
- return !Number.isNaN(t) && t >= j;
64
- }), [P.data, j]), W = f(() => {
79
+ return !Number.isNaN(t) && t >= S;
80
+ }), [E.data, S]), F = l(() => (k.data?.items ?? []).filter((e) => {
81
+ let t = new Date(e.periodEnd ?? e.createdAt).getTime();
82
+ return !Number.isNaN(t) && t >= S;
83
+ }), [k.data, S]), I = T.data?.pageInfo?.hasMore === !1, He = E.data?.pageInfo?.hasMore === !1, Ue = l(() => (T.data?.items ?? []).filter((e) => {
84
+ let t = new Date(e.periodEnd ?? e.createdAt).getTime();
85
+ return !Number.isNaN(t) && t >= C && t < S;
86
+ }), [
87
+ T.data,
88
+ C,
89
+ S
90
+ ]), We = l(() => (E.data?.items ?? []).filter((e) => {
91
+ let t = new Date(e.deliveredAt ?? e.departedAt ?? e.scheduledAt ?? e.createdAt).getTime();
92
+ return !Number.isNaN(t) && t >= C && t < S;
93
+ }), [
94
+ E.data,
95
+ C,
96
+ S
97
+ ]), L = l(() => {
65
98
  let e = /* @__PURE__ */ new Map();
66
- for (let t of H) {
99
+ for (let t of N) {
67
100
  let n = e.get(t.assetId);
68
101
  n && n.unit !== t.unit || e.set(t.assetId, {
69
102
  produced: (n?.produced ?? 0) + (t.volume ?? 0),
@@ -71,10 +104,10 @@ function D() {
71
104
  });
72
105
  }
73
106
  let t = [], n = 0, r = 0;
74
- for (let i of z) {
107
+ for (let i of A) {
75
108
  let a = e.get(i.id);
76
109
  if (!a || i.capacityPerDay <= 0 || i.capacityUnit !== a.unit) continue;
77
- let o = i.capacityPerDay * k;
110
+ let o = i.capacityPerDay * b;
78
111
  o <= 0 || (n += a.produced, r += o, t.push({
79
112
  name: i.name,
80
113
  pct: a.produced / o * 100
@@ -86,25 +119,90 @@ function D() {
86
119
  top: t.sort((e, t) => t.pct - e.pct).slice(0, 8)
87
120
  };
88
121
  }, [
89
- H,
90
- z,
91
- k
92
- ]), pe = f(() => H.reduce((e, t) => e + (t.downtimeHours ?? 0), 0), [H]), G = f(() => ue(H), [H]), K = f(() => de(G), [G]), q = f(() => {
93
- if (!K) return [];
122
+ N,
123
+ A,
124
+ b
125
+ ]), R = l(() => N.reduce((e, t) => e + (t.downtimeHours ?? 0), 0), [N]), z = l(() => {
126
+ if (!I) return null;
127
+ let e = Ue.reduce((e, t) => e + (t.downtimeHours ?? 0), 0);
128
+ return e <= 0 ? null : (R - e) / e * 100;
129
+ }, [
130
+ I,
131
+ Ue,
132
+ R
133
+ ]), B = l(() => Le(N), [N]), V = l(() => Re(B), [B]), Ge = l(() => {
134
+ if (!V) return [];
94
135
  let e = /* @__PURE__ */ new Map();
95
- for (let t of H) {
96
- if (t.unit !== K) continue;
97
- let n = E(t.periodEnd ?? t.createdAt);
136
+ for (let t of N) {
137
+ if (t.unit !== V) continue;
138
+ let n = Fe(t.periodEnd ?? t.createdAt);
98
139
  n && e.set(n, (e.get(n) ?? 0) + (t.volume ?? 0));
99
140
  }
100
141
  return [...e.entries()].sort(([e], [t]) => e.localeCompare(t)).map(([e, t]) => ({
101
- label: le(e),
142
+ label: Ie(e),
102
143
  value: t
103
144
  }));
104
- }, [H, K]), J = f(() => {
105
- let e = B.filter((e) => e.contractedVolume > 0 && (e.status === "ACTIVE" || e.status === "SIGNED")), t = e.reduce((e, t) => e + t.contractedVolume, 0), n = e.reduce((e, t) => e + t.deliveredVolume, 0), r = e.filter((e) => {
145
+ }, [N, V]), H = l(() => {
146
+ if (!V) return null;
147
+ let e = /* @__PURE__ */ new Set(), t = 0;
148
+ for (let n of N) n.unit === V && (e.add(n.assetId), t += n.volume ?? 0);
149
+ if (t <= 0 || e.size === 0) return null;
150
+ let n = 0, r = 0, i = 0;
151
+ for (let t of F) {
152
+ if (!t.assetId) {
153
+ i += 1;
154
+ continue;
155
+ }
156
+ e.has(t.assetId) && (n += t.co2eTonnes ?? 0, r += 1);
157
+ }
158
+ return r === 0 ? null : {
159
+ perUnit: n / t,
160
+ tonnes: n,
161
+ volume: t,
162
+ unit: V,
163
+ assets: e.size,
164
+ attributed: r,
165
+ unattributed: i
166
+ };
167
+ }, [
168
+ N,
169
+ F,
170
+ V
171
+ ]), U = l(() => {
172
+ if (F.length === 0) return [];
173
+ let e = {
174
+ SCOPE_1: 0,
175
+ SCOPE_2: 0,
176
+ SCOPE_3: 0
177
+ };
178
+ for (let t of F) e[t.scope] += t.co2eTonnes ?? 0;
179
+ return Me.filter((t) => e[t] > 0).map((n) => ({
180
+ label: t(n),
181
+ value: e[n],
182
+ color: je[n]
183
+ }));
184
+ }, [F]), Ke = l(() => F.reduce((e, t) => e + (t.co2eTonnes ?? 0), 0), [F]), qe = l(() => F.filter((e) => e.verified).length, [F]), W = l(() => {
185
+ let e = /* @__PURE__ */ new Map(), t = 0, n = 0;
186
+ for (let r of M) {
187
+ if (!Ne.has(r.status)) continue;
188
+ if (!(r.unitValue > 0) || !(r.quantity > 0)) {
189
+ n += 1;
190
+ continue;
191
+ }
192
+ let i = r.currency || "USD";
193
+ e.set(i, (e.get(i) ?? 0) + r.quantity * r.unitValue), t += 1;
194
+ }
195
+ let r = [...e.entries()].sort((e, t) => t[1] - e[1]);
196
+ return {
197
+ lead: r[0] ?? null,
198
+ others: r.slice(1),
199
+ valuedLots: t,
200
+ unvaluedLots: n
201
+ };
202
+ }, [M]), G = l(() => {
203
+ let e = j.filter((e) => e.contractedVolume > 0 && (e.status === "ACTIVE" || e.status === "SIGNED")), t = e.reduce((e, t) => e + t.contractedVolume, 0), n = e.reduce((e, t) => e + t.deliveredVolume, 0), r = e.filter((e) => {
106
204
  if (!e.endDate) return !1;
107
- let t = (new Date(e.endDate).getTime() - A) / T, n = e.deliveredVolume / e.contractedVolume;
205
+ let t = (new Date(e.endDate).getTime() - x) / h, n = e.deliveredVolume / e.contractedVolume;
108
206
  return t > 0 && t <= 60 && n < .75;
109
207
  }).length;
110
208
  return {
@@ -116,144 +214,263 @@ function D() {
116
214
  value: Math.min(100, e.deliveredVolume / e.contractedVolume * 100)
117
215
  })).sort((e, t) => e.value - t.value).slice(0, 8)
118
216
  };
119
- }, [B, A]), Y = f(() => {
217
+ }, [j, x]), K = l(() => {
218
+ let e = /* @__PURE__ */ new Map();
219
+ for (let t of j) {
220
+ if (!Pe.has(t.status) || t.contractedVolume <= 0) continue;
221
+ let n = t.counterparty.trim() || "Unnamed counterparty", r = Math.min(100, t.deliveredVolume / t.contractedVolume * 100), i = e.get(n) ?? {
222
+ total: 0,
223
+ count: 0
224
+ };
225
+ e.set(n, {
226
+ total: i.total + r,
227
+ count: i.count + 1
228
+ });
229
+ }
230
+ return [...e.entries()].map(([e, t]) => ({
231
+ name: e,
232
+ pct: t.total / t.count,
233
+ contracts: t.count
234
+ })).sort((e, t) => t.pct - e.pct).slice(0, 8);
235
+ }, [j]), q = l(() => {
120
236
  let e = /* @__PURE__ */ new Map();
121
- for (let t of U) e.set(t.mode, (e.get(t.mode) ?? 0) + 1);
237
+ for (let t of P) e.set(t.mode, (e.get(t.mode) ?? 0) + 1);
122
238
  return [...e.entries()].sort((e, t) => t[1] - e[1]).map(([e, n]) => ({
123
239
  label: t(e),
124
240
  value: n
125
241
  }));
126
- }, [U]), X = f(() => {
127
- let e = U.filter((e) => e.status !== "CANCELLED");
242
+ }, [P]), J = l(() => {
243
+ let e = P.filter((e) => e.status !== "CANCELLED");
128
244
  return e.length === 0 ? null : e.filter((e) => e.status === "DELIVERED").length / e.length * 100;
129
- }, [U]), me = f(() => U.filter((e) => e.status === "IN_TRANSIT" || e.status === "LOADING").length, [U]), Z = f(() => {
245
+ }, [P]), Y = l(() => {
246
+ if (!He || J == null) return null;
247
+ let e = We.filter((e) => e.status !== "CANCELLED");
248
+ return e.length === 0 ? null : J - e.filter((e) => e.status === "DELIVERED").length / e.length * 100;
249
+ }, [
250
+ He,
251
+ We,
252
+ J
253
+ ]), X = l(() => P.filter((e) => e.status === "IN_TRANSIT" || e.status === "LOADING").length, [P]), Je = l(() => {
130
254
  let e = /* @__PURE__ */ new Map();
131
- for (let t of V) e.set(t.status, (e.get(t.status) ?? 0) + 1);
255
+ for (let t of M) e.set(t.status, (e.get(t.status) ?? 0) + 1);
132
256
  return [...e.entries()].sort((e, t) => t[1] - e[1]).map(([e, n]) => ({
133
257
  label: t(e),
134
258
  value: n
135
259
  }));
136
- }, [V]), Q = D("theglobalfuel.pages.analytics.description", "Utilization, fulfilment and movement performance across the portfolio."), $ = D("theglobalfuel.pages.analytics.title", "Operational Analytics");
137
- if (L) return /* @__PURE__ */ x(e, {
138
- title: $,
139
- description: Q,
260
+ }, [M]), Ye = () => {
261
+ let e = ge([
262
+ {
263
+ header: "Metric",
264
+ value: (e) => e.metric
265
+ },
266
+ {
267
+ header: "Value",
268
+ value: (e) => e.value
269
+ },
270
+ {
271
+ header: "Detail",
272
+ value: (e) => e.detail
273
+ }
274
+ ], [
275
+ {
276
+ metric: "Window",
277
+ value: Ae.find((e) => e.value === y)?.label ?? `${b} days`,
278
+ detail: `Evaluated ${new Date(x).toISOString()}`
279
+ },
280
+ {
281
+ metric: "Capacity utilization",
282
+ value: L.overallPct == null ? "" : i(L.overallPct),
283
+ detail: `${L.measuredAssets} assets with capacity on record`
284
+ },
285
+ {
286
+ metric: "Contract fulfilment",
287
+ value: G.pct == null ? "" : i(G.pct),
288
+ detail: `${G.liveCount} live, ${G.atRisk} at risk`
289
+ },
290
+ {
291
+ metric: "Delivery rate",
292
+ value: J == null ? "" : i(J),
293
+ detail: Y == null ? `${X} currently moving` : `${Math.round(Y)} pp ${g}; ${X} currently moving`
294
+ },
295
+ {
296
+ metric: "Recorded downtime (hours)",
297
+ value: String(R),
298
+ detail: z == null ? `${N.length} production records` : `${Math.round(z)}% ${g}; ${N.length} production records`
299
+ },
300
+ {
301
+ metric: H ? `Carbon intensity (t CO2e per ${o(H.unit)})` : "Carbon intensity",
302
+ value: H ? String(H.perUnit) : "",
303
+ detail: H ? `${H.tonnes} t CO2e over ${H.volume} ${o(H.unit)} across ${H.assets} assets` : "Not computable for this window"
304
+ },
305
+ {
306
+ metric: W.lead ? `Inventory on hand (${W.lead[0]})` : "Inventory on hand",
307
+ value: W.lead ? String(W.lead[1]) : "",
308
+ detail: `${W.valuedLots} valued lots, ${W.unvaluedLots} without a unit value`
309
+ },
310
+ ...W.others.map(([e, t]) => ({
311
+ metric: `Inventory on hand (${e})`,
312
+ value: String(t),
313
+ detail: ""
314
+ })),
315
+ ...U.map((e) => ({
316
+ metric: `Emissions ${e.label} (t CO2e)`,
317
+ value: String(e.value),
318
+ detail: ""
319
+ })),
320
+ ...K.map((e) => ({
321
+ metric: `Fulfilment: ${e.name}`,
322
+ value: i(e.pct),
323
+ detail: `${e.contracts} live ${e.contracts === 1 ? "contract" : "contracts"}`
324
+ }))
325
+ ]);
326
+ _e(`theglobalfuel-analytics-${b}d.csv`, "text/csv", e) ? v.success("Analytics exported") : v.error("This browser can't download the export.");
327
+ }, Z = _("theglobalfuel.pages.analytics.description", "Utilization, fulfilment and movement performance across the portfolio."), Q = _("theglobalfuel.pages.analytics.title", "Operational Analytics");
328
+ if (Be) return /* @__PURE__ */ d(e, {
329
+ title: Q,
330
+ description: Z,
140
331
  dataTour: "theglobalfuel-analytics-page",
141
- children: /* @__PURE__ */ x(o, { label: "Aggregating operational data…" })
332
+ children: /* @__PURE__ */ d(re, { label: "Aggregating operational data…" })
142
333
  });
143
- if (R) return /* @__PURE__ */ x(e, {
144
- title: $,
145
- description: Q,
334
+ if (Ve) return /* @__PURE__ */ d(e, {
335
+ title: Q,
336
+ description: Z,
146
337
  dataTour: "theglobalfuel-analytics-page",
147
- children: /* @__PURE__ */ x(a, {
338
+ children: /* @__PURE__ */ d(ne, {
148
339
  message: "Couldn't load the operational data for analytics.",
149
340
  onRetry: () => {
150
- M.refetch(), N.refetch(), P.refetch(), F.refetch(), I.refetch();
341
+ w.refetch(), T.refetch(), E.refetch(), D.refetch(), O.refetch(), k.refetch();
151
342
  }
152
343
  })
153
344
  });
154
- let he = z.length > 0 || H.length > 0 || B.length > 0 || U.length > 0;
155
- return /* @__PURE__ */ x(e, {
156
- title: $,
157
- description: Q,
345
+ let $ = A.length > 0 || N.length > 0 || j.length > 0 || P.length > 0 || F.length > 0 || M.length > 0;
346
+ return /* @__PURE__ */ d(e, {
347
+ title: Q,
348
+ description: Z,
158
349
  dataTour: "theglobalfuel-analytics-page",
159
- actions: /* @__PURE__ */ x(se, {
160
- options: ce,
161
- value: O,
162
- onChange: fe
350
+ actions: /* @__PURE__ */ f("div", {
351
+ className: "flex flex-wrap items-center gap-2",
352
+ children: [/* @__PURE__ */ d(ve, {
353
+ options: Ae,
354
+ value: y,
355
+ onChange: ze
356
+ }), /* @__PURE__ */ f(ke, {
357
+ variant: "outline",
358
+ onClick: Ye,
359
+ disabled: !$,
360
+ children: [/* @__PURE__ */ d(xe, { className: "mr-1.5 h-4 w-4" }), " Export"]
361
+ })]
163
362
  }),
164
- children: he ? /* @__PURE__ */ S(b, { children: [
165
- /* @__PURE__ */ S("div", {
166
- className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4",
363
+ children: $ ? /* @__PURE__ */ f(Oe, { children: [
364
+ /* @__PURE__ */ f("div", {
365
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3",
167
366
  children: [
168
- /* @__PURE__ */ x(l, {
367
+ /* @__PURE__ */ d(c, {
169
368
  label: "Capacity utilization",
170
- value: W.overallPct == null ? "—" : r(W.overallPct),
171
- icon: /* @__PURE__ */ x(h, { className: "h-4 w-4" }),
172
- deltaLabel: W.overallPct == null ? "No asset has both capacity and matching-unit production" : `${n(W.measuredAssets)} assets with capacity on record`
369
+ value: L.overallPct == null ? "—" : i(L.overallPct),
370
+ icon: /* @__PURE__ */ d(Se, { className: "h-4 w-4" }),
371
+ deltaLabel: L.overallPct == null ? "No asset has both capacity and matching-unit production" : `${r(L.measuredAssets)} assets with capacity on record`
173
372
  }),
174
- /* @__PURE__ */ x(l, {
373
+ /* @__PURE__ */ d(c, {
175
374
  label: "Contract fulfilment",
176
- value: J.pct == null ? "—" : r(J.pct),
177
- icon: /* @__PURE__ */ x(y, { className: "h-4 w-4" }),
178
- deltaLabel: `${n(J.liveCount)} live · ${n(J.atRisk)} at risk`
375
+ value: G.pct == null ? "—" : i(G.pct),
376
+ icon: /* @__PURE__ */ d(De, { className: "h-4 w-4" }),
377
+ deltaLabel: `${r(G.liveCount)} live · ${r(G.atRisk)} at risk`
179
378
  }),
180
- /* @__PURE__ */ x(l, {
379
+ /* @__PURE__ */ d(c, {
181
380
  label: "Delivery rate",
182
- value: X == null ? "—" : r(X),
183
- icon: /* @__PURE__ */ x(_, { className: "h-4 w-4" }),
184
- deltaLabel: `${n(me)} currently moving`
381
+ value: J == null ? "—" : i(J),
382
+ icon: /* @__PURE__ */ d(Te, { className: "h-4 w-4" }),
383
+ ...Y == null ? {} : {
384
+ delta: Math.round(Y),
385
+ deltaSuffix: " pp"
386
+ },
387
+ deltaLabel: Y == null ? `${r(X)} currently moving` : `${g} · ${r(X)} currently moving`
185
388
  }),
186
- /* @__PURE__ */ x(l, {
389
+ /* @__PURE__ */ d(c, {
187
390
  label: "Recorded downtime",
188
- value: `${n(pe)} h`,
189
- icon: /* @__PURE__ */ x(v, { className: "h-4 w-4" }),
391
+ value: `${r(R)} h`,
392
+ icon: /* @__PURE__ */ d(Ee, { className: "h-4 w-4" }),
190
393
  invertDelta: !0,
191
- deltaLabel: `Across ${n(H.length)} production records`
394
+ ...z == null ? {} : { delta: Math.round(z) },
395
+ deltaLabel: z == null ? `Across ${r(N.length)} production records` : `${g} · ${r(N.length)} records`
396
+ }),
397
+ /* @__PURE__ */ d(c, {
398
+ label: "Carbon intensity",
399
+ value: H ? `${r(H.perUnit, { maximumFractionDigits: 4 })} t CO₂e/${o(H.unit)}` : "—",
400
+ icon: /* @__PURE__ */ d(Ce, { className: "h-4 w-4" }),
401
+ invertDelta: !0,
402
+ deltaLabel: H ? [`${a(H.tonnes)} against ${ee(H.volume, H.unit)} from ${r(H.assets)} assets`, H.unattributed > 0 ? `${r(H.unattributed)} unattributed ${H.unattributed === 1 ? "record" : "records"} excluded` : null].filter(Boolean).join(" · ") : "Needs emissions logged against an asset that also reports production"
403
+ }),
404
+ /* @__PURE__ */ d(c, {
405
+ label: "Inventory on hand",
406
+ value: W.lead ? n(W.lead[1], W.lead[0]) : "—",
407
+ icon: /* @__PURE__ */ d(be, { className: "h-4 w-4" }),
408
+ deltaLabel: W.lead ? [`${r(W.valuedLots)} valued ${W.valuedLots === 1 ? "lot" : "lots"}`, ...W.others.map(([e, t]) => `${n(t, e)}`)].join(" · ") : "No lot on hand carries a unit value"
192
409
  })
193
410
  ]
194
411
  }),
195
- /* @__PURE__ */ S("div", {
412
+ /* @__PURE__ */ f("div", {
196
413
  className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3",
197
- children: [/* @__PURE__ */ S(C, {
414
+ children: [/* @__PURE__ */ f(p, {
198
415
  treatment: "glass",
199
416
  className: "flex flex-col p-5 lg:col-span-2",
200
- children: [/* @__PURE__ */ S("div", {
417
+ children: [/* @__PURE__ */ f("div", {
201
418
  className: "mb-4",
202
- children: [/* @__PURE__ */ x("h2", {
419
+ children: [/* @__PURE__ */ d("h2", {
203
420
  className: "text-base font-semibold text-text-primary",
204
421
  children: "Production trend"
205
- }), /* @__PURE__ */ x("p", {
422
+ }), /* @__PURE__ */ d("p", {
206
423
  className: "text-xs text-text-tertiary",
207
- children: K ? `Monthly volume in ${t(K)} — the unit carrying most of the portfolio. Other units are charted on their own assets.` : "No production recorded in this window."
424
+ children: V ? `Monthly volume in ${t(V)} — the unit carrying most of the portfolio. Other units are charted on their own assets.` : "No production recorded in this window."
208
425
  })]
209
- }), q.length > 0 ? /* @__PURE__ */ x(te, {
210
- series: q,
426
+ }), Ge.length > 0 ? /* @__PURE__ */ d(ae, {
427
+ series: Ge,
211
428
  height: 220,
212
429
  title: "Monthly production volume"
213
- }) : /* @__PURE__ */ x("div", {
430
+ }) : /* @__PURE__ */ d("div", {
214
431
  className: "rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary",
215
432
  children: "No production records in this window."
216
433
  })]
217
- }), /* @__PURE__ */ S(C, {
434
+ }), /* @__PURE__ */ f(p, {
218
435
  treatment: "glass",
219
436
  className: "flex flex-col p-5",
220
- children: [/* @__PURE__ */ S("div", {
437
+ children: [/* @__PURE__ */ f("div", {
221
438
  className: "mb-4",
222
- children: [/* @__PURE__ */ x("h2", {
439
+ children: [/* @__PURE__ */ d("h2", {
223
440
  className: "text-base font-semibold text-text-primary",
224
441
  children: "Movement mix"
225
- }), /* @__PURE__ */ x("p", {
442
+ }), /* @__PURE__ */ d("p", {
226
443
  className: "text-xs text-text-tertiary",
227
444
  children: "Shipments by transport mode."
228
445
  })]
229
- }), Y.length > 0 ? /* @__PURE__ */ x(c, {
230
- segments: Y,
231
- centerLabel: n(U.length),
446
+ }), q.length > 0 ? /* @__PURE__ */ d(s, {
447
+ segments: q,
448
+ centerLabel: r(P.length),
232
449
  centerCaption: "shipments",
233
450
  showLegend: !0,
234
451
  title: "Shipments by transport mode"
235
- }) : /* @__PURE__ */ x("div", {
452
+ }) : /* @__PURE__ */ d("div", {
236
453
  className: "rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary",
237
454
  children: "No shipments in this window."
238
455
  })]
239
456
  })]
240
457
  }),
241
- /* @__PURE__ */ S("div", {
458
+ /* @__PURE__ */ f("div", {
242
459
  className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2",
243
- children: [/* @__PURE__ */ S(C, {
460
+ children: [/* @__PURE__ */ f(p, {
244
461
  treatment: "glass",
245
462
  className: "flex flex-col p-5",
246
- children: [/* @__PURE__ */ S("div", {
463
+ children: [/* @__PURE__ */ f("div", {
247
464
  className: "mb-4",
248
- children: [/* @__PURE__ */ x("h2", {
465
+ children: [/* @__PURE__ */ d("h2", {
249
466
  className: "text-base font-semibold text-text-primary",
250
467
  children: "Utilization by asset"
251
- }), /* @__PURE__ */ x("p", {
468
+ }), /* @__PURE__ */ d("p", {
252
469
  className: "text-xs text-text-tertiary",
253
470
  children: "Produced volume against nameplate capacity for the window. Assets without a capacity figure, or whose production is recorded in a different unit, are omitted."
254
471
  })]
255
- }), W.top.length > 0 ? /* @__PURE__ */ x(s, {
256
- series: W.top.map((e) => ({
472
+ }), L.top.length > 0 ? /* @__PURE__ */ d(oe, {
473
+ series: L.top.map((e) => ({
257
474
  label: e.name,
258
475
  value: Math.round(e.pct)
259
476
  })),
@@ -261,24 +478,24 @@ function D() {
261
478
  colorful: !0,
262
479
  valueFormatter: (e) => `${e}%`,
263
480
  title: "Capacity utilization by asset"
264
- }) : /* @__PURE__ */ x("div", {
481
+ }) : /* @__PURE__ */ d("div", {
265
482
  className: "rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary",
266
483
  children: "No asset has both a capacity figure and matching-unit production in this window."
267
484
  })]
268
- }), /* @__PURE__ */ S(C, {
485
+ }), /* @__PURE__ */ f(p, {
269
486
  treatment: "glass",
270
487
  className: "flex flex-col p-5",
271
- children: [/* @__PURE__ */ S("div", {
488
+ children: [/* @__PURE__ */ f("div", {
272
489
  className: "mb-4",
273
- children: [/* @__PURE__ */ x("h2", {
490
+ children: [/* @__PURE__ */ d("h2", {
274
491
  className: "text-base font-semibold text-text-primary",
275
492
  children: "Least-fulfilled contracts"
276
- }), /* @__PURE__ */ x("p", {
493
+ }), /* @__PURE__ */ d("p", {
277
494
  className: "text-xs text-text-tertiary",
278
495
  children: "Delivered against contracted volume for live (signed/active) contracts."
279
496
  })]
280
- }), J.top.length > 0 ? /* @__PURE__ */ x(s, {
281
- series: J.top.map((e) => ({
497
+ }), G.top.length > 0 ? /* @__PURE__ */ d(oe, {
498
+ series: G.top.map((e) => ({
282
499
  label: e.label,
283
500
  value: Math.round(e.value)
284
501
  })),
@@ -286,73 +503,144 @@ function D() {
286
503
  colorful: !0,
287
504
  valueFormatter: (e) => `${e}%`,
288
505
  title: "Contract fulfilment"
289
- }) : /* @__PURE__ */ x("div", {
506
+ }) : /* @__PURE__ */ d("div", {
507
+ className: "rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary",
508
+ children: "No live contracts with a contracted volume."
509
+ })]
510
+ })]
511
+ }),
512
+ /* @__PURE__ */ f("div", {
513
+ className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2",
514
+ children: [/* @__PURE__ */ f(p, {
515
+ treatment: "glass",
516
+ className: "flex flex-col p-5",
517
+ children: [/* @__PURE__ */ f("div", {
518
+ className: "mb-4",
519
+ children: [/* @__PURE__ */ d("h2", {
520
+ className: "text-base font-semibold text-text-primary",
521
+ children: "Emissions by scope"
522
+ }), /* @__PURE__ */ f("p", {
523
+ className: "text-xs text-text-tertiary",
524
+ children: ["Tonnes CO₂e logged in this window, split by GHG protocol scope.", F.length > 0 ? ` ${r(qe)} of ${r(F.length)} records verified.` : ""]
525
+ })]
526
+ }), U.length > 0 ? /* @__PURE__ */ d(s, {
527
+ segments: U,
528
+ centerLabel: r(Ke, { maximumFractionDigits: 0 }),
529
+ centerCaption: "t CO₂e",
530
+ showLegend: !0,
531
+ valueFormatter: (e) => r(e, { maximumFractionDigits: 1 }),
532
+ title: "Emissions by GHG scope"
533
+ }) : /* @__PURE__ */ d("div", {
534
+ className: "rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary",
535
+ children: "No emissions recorded in this window."
536
+ })]
537
+ }), /* @__PURE__ */ f(p, {
538
+ treatment: "glass",
539
+ className: "flex flex-col p-5",
540
+ children: [/* @__PURE__ */ f("div", {
541
+ className: "mb-4",
542
+ children: [/* @__PURE__ */ d("h2", {
543
+ className: "text-base font-semibold text-text-primary",
544
+ children: "Fulfilment by counterparty"
545
+ }), /* @__PURE__ */ d("p", {
546
+ className: "text-xs text-text-tertiary",
547
+ children: "Mean delivered-against-contracted across each counterparty's live (signed/active) contracts. Ratios are unitless, so no volumes are summed across units of measure."
548
+ })]
549
+ }), K.length > 0 ? /* @__PURE__ */ d("ul", {
550
+ className: "space-y-3.5",
551
+ children: K.map((e) => /* @__PURE__ */ f("li", { children: [
552
+ /* @__PURE__ */ f("div", {
553
+ className: "flex items-baseline justify-between gap-3",
554
+ children: [/* @__PURE__ */ d("span", {
555
+ className: "truncate text-sm text-text-secondary",
556
+ children: e.name
557
+ }), /* @__PURE__ */ d("span", {
558
+ className: "shrink-0 text-sm font-medium tabular-nums text-text-primary",
559
+ children: i(e.pct)
560
+ })]
561
+ }),
562
+ /* @__PURE__ */ d(te, {
563
+ value: e.pct,
564
+ className: "mt-1.5",
565
+ barClassName: "bg-status-success-text"
566
+ }),
567
+ /* @__PURE__ */ f("p", {
568
+ className: "mt-1 text-[11px] text-text-tertiary",
569
+ children: [
570
+ r(e.contracts),
571
+ " live",
572
+ " ",
573
+ e.contracts === 1 ? "contract" : "contracts"
574
+ ]
575
+ })
576
+ ] }, e.name))
577
+ }) : /* @__PURE__ */ d("div", {
290
578
  className: "rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary",
291
579
  children: "No live contracts with a contracted volume."
292
580
  })]
293
581
  })]
294
582
  }),
295
- /* @__PURE__ */ S("div", {
583
+ /* @__PURE__ */ f("div", {
296
584
  className: "mt-6 grid grid-cols-1 gap-6 lg:grid-cols-3",
297
- children: [/* @__PURE__ */ S(C, {
585
+ children: [/* @__PURE__ */ f(p, {
298
586
  treatment: "glass",
299
587
  className: "flex flex-col p-5",
300
- children: [/* @__PURE__ */ S("div", {
588
+ children: [/* @__PURE__ */ f("div", {
301
589
  className: "mb-4 flex items-center gap-2",
302
- children: [/* @__PURE__ */ x(g, { className: "h-4 w-4 text-text-secondary" }), /* @__PURE__ */ x("h2", {
590
+ children: [/* @__PURE__ */ d(we, { className: "h-4 w-4 text-text-secondary" }), /* @__PURE__ */ d("h2", {
303
591
  className: "text-base font-semibold text-text-primary",
304
592
  children: "Inventory position"
305
593
  })]
306
- }), Z.length > 0 ? /* @__PURE__ */ x(c, {
307
- segments: Z,
308
- centerLabel: n(V.length),
594
+ }), Je.length > 0 ? /* @__PURE__ */ d(s, {
595
+ segments: Je,
596
+ centerLabel: r(M.length),
309
597
  centerCaption: "lots",
310
598
  showLegend: !0,
311
599
  title: "Inventory lots by status"
312
- }) : /* @__PURE__ */ x("div", {
600
+ }) : /* @__PURE__ */ d("div", {
313
601
  className: "rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary",
314
602
  children: "No inventory lots recorded."
315
603
  })]
316
- }), /* @__PURE__ */ S(C, {
604
+ }), /* @__PURE__ */ f(p, {
317
605
  treatment: "glass",
318
606
  className: "p-5 lg:col-span-2",
319
607
  children: [
320
- /* @__PURE__ */ x("h2", {
608
+ /* @__PURE__ */ d("h2", {
321
609
  className: "mb-4 text-base font-semibold text-text-primary",
322
610
  children: "Production volume by unit"
323
611
  }),
324
- /* @__PURE__ */ x("p", {
612
+ /* @__PURE__ */ d("p", {
325
613
  className: "mb-4 text-xs text-text-tertiary",
326
614
  children: "Volumes are never summed across units of measure. Each row is an independent total."
327
615
  }),
328
- G.size > 0 ? /* @__PURE__ */ x("ul", {
616
+ B.size > 0 ? /* @__PURE__ */ d("ul", {
329
617
  className: "divide-y divide-border-subtle",
330
- children: [...G.entries()].sort((e, t) => t[1] - e[1]).map(([e, n]) => /* @__PURE__ */ S("li", {
618
+ children: [...B.entries()].sort((e, t) => t[1] - e[1]).map(([e, n]) => /* @__PURE__ */ f("li", {
331
619
  className: "flex items-center justify-between py-2.5",
332
- children: [/* @__PURE__ */ x("span", {
620
+ children: [/* @__PURE__ */ d("span", {
333
621
  className: "text-sm text-text-secondary",
334
622
  children: t(e)
335
- }), /* @__PURE__ */ x("span", {
623
+ }), /* @__PURE__ */ d("span", {
336
624
  className: "text-sm font-medium tabular-nums text-text-primary",
337
- children: i(n, e)
625
+ children: ee(n, e)
338
626
  })]
339
627
  }, e))
340
- }) : /* @__PURE__ */ x("div", {
628
+ }) : /* @__PURE__ */ d("div", {
341
629
  className: "rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary",
342
630
  children: "No production records in this window."
343
631
  })
344
632
  ]
345
633
  })]
346
634
  })
347
- ] }) : /* @__PURE__ */ x(ee, {
635
+ ] }) : /* @__PURE__ */ d(ie, {
348
636
  illustration: "empty-data",
349
- icon: /* @__PURE__ */ x(m, { className: "h-10 w-10" }),
637
+ icon: /* @__PURE__ */ d(ye, { className: "h-10 w-10" }),
350
638
  title: "Nothing to analyse yet",
351
639
  description: "Register assets and record production, contracts and shipments — analytics is computed from those records."
352
640
  })
353
641
  });
354
642
  }
355
643
  //#endregion
356
- export { D as AnalyticsPage };
644
+ export { _ as AnalyticsPage };
357
645
 
358
646
  //# sourceMappingURL=AnalyticsPage.js.map