@burdenoff/microfe-ecoimpacthub 2026.827.1 → 2026.1014.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.
@@ -9,4 +9,5 @@ export * from './useEcoComments';
9
9
  export * from './useEcoTags';
10
10
  export * from './useDashboardLayout';
11
11
  export * from './useActiveEcoEntity';
12
+ export * from './useTr';
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/hooks/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAC7B,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC"}
@@ -5,3 +5,4 @@ import "./useEcoFiles.js";
5
5
  import "./useEcoComments.js";
6
6
  import "./useEcoTags.js";
7
7
  import "./useActiveEcoEntity.js";
8
+ import "./useTr.js";
@@ -0,0 +1,4 @@
1
+ type TranslationParams = Record<string, string | number>;
2
+ export declare function useTr(): (key: string, fallback: string, params?: TranslationParams) => string;
3
+ export {};
4
+ //# sourceMappingURL=useTr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/hooks/useTr.ts"],"names":[],"mappings":"AAkBA,KAAK,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAgBzD,wBAAgB,KAAK,IAAI,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,iBAAiB,KACvB,MAAM,CAYV"}
@@ -0,0 +1,20 @@
1
+ import { useCallback as e } from "react";
2
+ import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
3
+ //#region src/ecoimpacthub/hooks/useTr.ts
4
+ function n(e, t) {
5
+ return t ? e.replace(/\{\{\s*(\w+)\s*\}\}/g, (e, n) => {
6
+ let r = t[n];
7
+ return r === void 0 ? `{{${n}}}` : String(r);
8
+ }) : e;
9
+ }
10
+ function r() {
11
+ let { t: r } = t();
12
+ return e((e, t, i) => {
13
+ let a = r(e, i), o = a === e ? t : a;
14
+ return i ? n(o, i) : o;
15
+ }, [r]);
16
+ }
17
+ //#endregion
18
+ export { r as useTr };
19
+
20
+ //# sourceMappingURL=useTr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.js","names":[],"sources":["../../../src/ecoimpacthub/hooks/useTr.ts"],"sourcesContent":["/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('ecoDashboard.title', 'EcoImpactHub')}</h1>;\n * return <p>{tr('ecoDashboard.kpis.monitoredSites', 'Monitored sites')}</p>;\n */\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype TranslationParams = Record<string, string | number>;\n\nfunction interpolateFallback(\n fallback: string,\n params?: TranslationParams,\n): string {\n if (!params) {\n return fallback;\n }\n\n return fallback.replace(/\\{\\{\\s*(\\w+)\\s*\\}\\}/g, (_, key: string) => {\n const value = params[key];\n return value === undefined ? `{{${key}}}` : String(value);\n });\n}\n\nexport function useTr(): (\n key: string,\n fallback: string,\n params?: TranslationParams,\n) => string {\n const { t } = useI18n();\n\n return useCallback(\n (key: string, fallback: string, params?: TranslationParams): string => {\n const translated = t(key, params);\n const raw = translated === key ? fallback : translated;\n if (!params) return raw;\n return interpolateFallback(raw, params);\n },\n [t],\n );\n}\n"],"mappings":";;;AAoBA,SAAS,EACP,GACA,GACQ;CAKR,OAJK,IAIE,EAAS,QAAQ,yBAAyB,GAAG,MAAgB;EAClE,IAAM,IAAQ,EAAO;EACrB,OAAO,MAAU,KAAA,IAAY,KAAK,EAAI,MAAM,OAAO,CAAK;CAC1D,CAAC,IANQ;AAOX;AAEA,SAAgB,IAIJ;CACV,IAAM,EAAE,SAAM,EAAQ;CAEtB,OAAO,GACJ,GAAa,GAAkB,MAAuC;EACrE,IAAM,IAAa,EAAE,GAAK,CAAM,GAC1B,IAAM,MAAe,IAAM,IAAW;EAE5C,OADK,IACE,EAAoB,GAAK,CAAM,IADlB;CAEtB,GACA,CAAC,CAAC,CACJ;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/pages/DashboardPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAuEH,wBAAgB,aAAa,4CAyhB5B"}
1
+ {"version":3,"file":"DashboardPage.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/pages/DashboardPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAwEH,wBAAgB,aAAa,4CAykB5B"}
@@ -4,33 +4,34 @@ import { ResponsiveContainer as n } from "../../node_modules/recharts/es6/compon
4
4
  import { Cell as r } from "../../node_modules/recharts/es6/component/Cell.js";
5
5
  import { LabelList as i } from "../../node_modules/recharts/es6/component/LabelList.js";
6
6
  import { Bar as ee } from "../../node_modules/recharts/es6/cartesian/Bar.js";
7
- import { CartesianGrid as te } from "../../node_modules/recharts/es6/cartesian/CartesianGrid.js";
8
- import { XAxis as a } from "../../node_modules/recharts/es6/cartesian/XAxis.js";
7
+ import { CartesianGrid as a } from "../../node_modules/recharts/es6/cartesian/CartesianGrid.js";
8
+ import { XAxis as te } from "../../node_modules/recharts/es6/cartesian/XAxis.js";
9
9
  import { YAxis as ne } from "../../node_modules/recharts/es6/cartesian/YAxis.js";
10
10
  import { BarChart as re } from "../../node_modules/recharts/es6/chart/BarChart.js";
11
11
  import { PageLayout as o } from "../components/PageLayout.js";
12
12
  import { MetricsCard as s } from "../components/MetricsCard.js";
13
- import { formatCo2e as c, formatEnumLabel as ie, formatNumber as l, formatPercent as ae, formatRelativeTime as u } from "../utils/formatters.js";
14
- import { StatusBadge as d } from "../components/StatusBadge.js";
15
- import { MiniBar as f } from "../components/MiniBar.js";
16
- import { LoadingView as p, Section as m } from "../components/StateViews.js";
13
+ import { formatCo2e as c, formatEnumLabel as l, formatNumber as u, formatPercent as ie, formatRelativeTime as d } from "../utils/formatters.js";
14
+ import { StatusBadge as f } from "../components/StatusBadge.js";
15
+ import { MiniBar as p } from "../components/MiniBar.js";
16
+ import { LoadingView as ae, Section as m } from "../components/StateViews.js";
17
17
  import { EmptyState as h } from "../components/EmptyState.js";
18
18
  import { useDashboardLayout as oe } from "../hooks/useDashboardLayout.js";
19
19
  import { DashboardCustomizerDialog as se } from "../components/DashboardCustomizerDialog.js";
20
20
  import { Meter as ce, SCOPE_HUE as g, ScopeLegend as le, ScopeSplitBar as ue } from "./carbonVisuals.js";
21
21
  import "../context/index.js";
22
22
  import { useEcoCarbonTotals as de, useEcoHabitatProjects as fe, useEcoMeasurements as pe, useEcoObservations as me, useEcoSensors as he, useEcoSites as ge, useEcoSpecies as _e } from "../hooks/useEcoImpactHubApi.js";
23
+ import { useTr as ve } from "../hooks/useTr.js";
23
24
  import "../hooks/index.js";
24
- import { useMemo as ve, useState as ye } from "react";
25
- import { ArrowRight as _, Binoculars as v, Factory as y, LayoutGrid as be, MapPin as xe, PawPrint as Se, Radio as Ce, ShieldCheck as we, StickyNote as Te, Trees as Ee, TriangleAlert as De } from "lucide-react";
26
- import { Fragment as Oe, jsx as b, jsxs as x } from "react/jsx-runtime";
25
+ import { useMemo as ye, useState as be } from "react";
26
+ import { ArrowRight as _, Binoculars as v, Factory as y, LayoutGrid as xe, MapPin as Se, PawPrint as Ce, Radio as we, ShieldCheck as Te, StickyNote as Ee, Trees as De, TriangleAlert as Oe } from "lucide-react";
27
+ import { Fragment as ke, jsx as b, jsxs as x } from "react/jsx-runtime";
27
28
  import { Button as S, GlassCard as C } from "@burdenoff/fe-libs/ui";
28
29
  //#region src/ecoimpacthub/pages/DashboardPage.tsx
29
30
  var w = new Set([
30
31
  "PLANNED",
31
32
  "IN_PROGRESS",
32
33
  "MONITORING"
33
- ]), T = {
34
+ ]), Ae = {
34
35
  LEAST_CONCERN: "var(--color-status-success-text)",
35
36
  NEAR_THREATENED: "var(--color-accent-teal)",
36
37
  VULNERABLE: "var(--color-status-warning-text)",
@@ -40,32 +41,32 @@ var w = new Set([
40
41
  DATA_DEFICIENT: "var(--color-text-tertiary)",
41
42
  NOT_EVALUATED: "var(--color-text-tertiary)"
42
43
  };
43
- function E() {
44
- let { navigateTo: E, currentUser: D, workspaceId: ke } = e(), [Ae, O] = ye(!1), k = oe(ke, D?.id), A = ge({ pagination: {
44
+ function T() {
45
+ let T = ve(), { navigateTo: E, currentUser: D, workspaceId: O } = e(), [je, k] = be(!1), A = oe(O, D?.id), j = ge({ pagination: {
45
46
  limit: 100,
46
47
  offset: 0
47
- } }), j = _e({ pagination: {
48
+ } }), M = _e({ pagination: {
48
49
  limit: 100,
49
50
  offset: 0
50
- } }), M = fe(), N = de(), P = pe({
51
+ } }), N = fe(), P = de(), F = pe({
51
52
  breachesOnly: !0,
52
53
  pagination: {
53
54
  limit: 6,
54
55
  offset: 0
55
56
  }
56
- }), je = me({ pagination: {
57
+ }), Me = me({ pagination: {
57
58
  limit: 6,
58
59
  offset: 0
59
- } }), Me = he(), F = A.data?.items ?? [], I = j.data?.items ?? [], L = M.data?.items ?? [], R = Me.data?.items ?? [], z = F.filter((e) => e.status === "ACTIVE").length, Ne = I.filter((e) => e.isPriority).length, B = I.filter((e) => [
60
+ } }), Ne = he(), I = j.data?.items ?? [], L = M.data?.items ?? [], R = N.data?.items ?? [], z = Ne.data?.items ?? [], B = I.filter((e) => e.status === "ACTIVE").length, Pe = L.filter((e) => e.isPriority).length, V = L.filter((e) => [
60
61
  "VULNERABLE",
61
62
  "ENDANGERED",
62
63
  "CRITICALLY_ENDANGERED"
63
- ].includes(e.conservationStatus)).length, V = L.filter((e) => w.has(e.status)).length, H = R.filter((e) => e.status === "ONLINE").length, U = R.filter((e) => [
64
+ ].includes(e.conservationStatus)).length, H = R.filter((e) => w.has(e.status)).length, U = z.filter((e) => e.status === "ONLINE").length, W = z.filter((e) => [
64
65
  "OFFLINE",
65
66
  "DEGRADED",
66
67
  "MAINTENANCE"
67
- ].includes(e.status)).length, W = ve(() => {
68
- let e = N.data;
68
+ ].includes(e.status)).length, G = ye(() => {
69
+ let e = P.data;
69
70
  return e ? [
70
71
  {
71
72
  scope: "SCOPE_1",
@@ -83,51 +84,55 @@ function E() {
83
84
  kg: e.scope3
84
85
  }
85
86
  ] : [];
86
- }, [N.data]), Pe = A.isLoading && j.isLoading && M.isLoading && N.isLoading, G = /* @__PURE__ */ x(S, {
87
+ }, [P.data]), Fe = j.isLoading && M.isLoading && N.isLoading && P.isLoading, K = /* @__PURE__ */ x(S, {
87
88
  type: "button",
88
89
  variant: "outline",
89
90
  size: "sm",
90
- onClick: () => O(!0),
91
- children: [/* @__PURE__ */ b(be, { className: "mr-1.5 h-3.5 w-3.5" }), "Customize dashboard"]
91
+ onClick: () => k(!0),
92
+ children: [/* @__PURE__ */ b(xe, { className: "mr-1.5 h-3.5 w-3.5" }), T("ecoDashboard.customize", "Customize dashboard")]
92
93
  });
93
- if (Pe) return /* @__PURE__ */ b(o, {
94
- title: "EcoImpactHub",
94
+ if (Fe) return /* @__PURE__ */ b(o, {
95
+ title: T("ecoDashboard.title", "EcoImpactHub"),
95
96
  titleGradient: !0,
96
97
  aurora: !0,
97
- actions: G,
98
- children: /* @__PURE__ */ b(p, { label: "Loading your impact overview…" })
98
+ actions: K,
99
+ children: /* @__PURE__ */ b(ae, { label: T("ecoDashboard.loading", "Loading your impact overview…") })
99
100
  });
100
- let Fe = D?.firstName || D?.name || "there", K = P.data?.items ?? [], q = je.data?.items ?? [], J = /* @__PURE__ */ x("div", {
101
+ let Ie = D?.firstName || D?.name || "there", q = F.data?.items ?? [], J = Me.data?.items ?? [], Le = /* @__PURE__ */ x("div", {
101
102
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-6",
102
103
  children: [
103
104
  /* @__PURE__ */ b(s, {
104
- icon: /* @__PURE__ */ b(xe, { className: "h-5 w-5" }),
105
- value: A.data?.pageInfo?.hasMore ? `${F.length}+` : l(F.length),
106
- label: "Monitored sites",
105
+ icon: /* @__PURE__ */ b(Se, { className: "h-5 w-5" }),
106
+ value: j.data?.pageInfo?.hasMore ? `${I.length}+` : u(I.length),
107
+ label: T("ecoDashboard.kpis.monitoredSites", "Monitored sites"),
107
108
  footer: /* @__PURE__ */ x("div", {
108
109
  className: "space-y-2",
109
- children: [/* @__PURE__ */ b(f, {
110
- value: F.length ? z / F.length * 100 : 0,
110
+ children: [/* @__PURE__ */ b(p, {
111
+ value: I.length ? B / I.length * 100 : 0,
111
112
  height: 4,
112
113
  barClassName: "bg-action-primary-bg"
113
114
  }), /* @__PURE__ */ x("p", {
114
115
  className: "text-xs text-text-tertiary",
115
- children: [/* @__PURE__ */ b("span", {
116
- className: "tabular-nums text-text-secondary",
117
- children: z
118
- }), " active"]
116
+ children: [
117
+ /* @__PURE__ */ b("span", {
118
+ className: "tabular-nums text-text-secondary",
119
+ children: B
120
+ }),
121
+ " ",
122
+ T("ecoDashboard.kpis.active", "active")
123
+ ]
119
124
  })]
120
125
  })
121
126
  }),
122
127
  /* @__PURE__ */ b(s, {
123
- icon: /* @__PURE__ */ b(Se, { className: "h-5 w-5" }),
124
- value: j.data?.pageInfo?.hasMore ? `${I.length}+` : l(I.length),
125
- label: "Species tracked",
126
- accent: B > 0 ? "warning" : void 0,
128
+ icon: /* @__PURE__ */ b(Ce, { className: "h-5 w-5" }),
129
+ value: M.data?.pageInfo?.hasMore ? `${L.length}+` : u(L.length),
130
+ label: T("ecoDashboard.kpis.speciesTracked", "Species tracked"),
131
+ accent: V > 0 ? "warning" : void 0,
127
132
  footer: /* @__PURE__ */ x("div", {
128
133
  className: "space-y-2",
129
- children: [/* @__PURE__ */ b(f, {
130
- value: I.length ? B / I.length * 100 : 0,
134
+ children: [/* @__PURE__ */ b(p, {
135
+ value: L.length ? V / L.length * 100 : 0,
131
136
  height: 4,
132
137
  barClassName: "bg-status-warning-text"
133
138
  }), /* @__PURE__ */ x("p", {
@@ -135,27 +140,30 @@ function E() {
135
140
  children: [
136
141
  /* @__PURE__ */ b("span", {
137
142
  className: "tabular-nums text-text-secondary",
138
- children: Ne
143
+ children: Pe
139
144
  }),
140
- " priority ·",
145
+ " ",
146
+ T("ecoDashboard.kpis.priority", "priority"),
147
+ " ·",
141
148
  " ",
142
149
  /* @__PURE__ */ b("span", {
143
150
  className: "tabular-nums text-text-secondary",
144
- children: B
151
+ children: V
145
152
  }),
146
- " threatened"
153
+ " ",
154
+ T("ecoDashboard.kpis.threatened", "threatened")
147
155
  ]
148
156
  })]
149
157
  })
150
158
  }),
151
159
  /* @__PURE__ */ b(s, {
152
- icon: /* @__PURE__ */ b(Ee, { className: "h-5 w-5" }),
153
- value: l(V),
154
- label: "Active habitat projects",
160
+ icon: /* @__PURE__ */ b(De, { className: "h-5 w-5" }),
161
+ value: u(H),
162
+ label: T("ecoDashboard.kpis.activeHabitatProjects", "Active habitat projects"),
155
163
  footer: /* @__PURE__ */ x("div", {
156
164
  className: "space-y-2",
157
- children: [/* @__PURE__ */ b(f, {
158
- value: L.length ? V / L.length * 100 : 0,
165
+ children: [/* @__PURE__ */ b(p, {
166
+ value: R.length ? H / R.length * 100 : 0,
159
167
  height: 4,
160
168
  barClassName: "bg-action-primary-bg"
161
169
  }), /* @__PURE__ */ x("p", {
@@ -163,60 +171,64 @@ function E() {
163
171
  children: [
164
172
  /* @__PURE__ */ b("span", {
165
173
  className: "tabular-nums text-text-secondary",
166
- children: l(L.length)
174
+ children: u(R.length)
167
175
  }),
168
176
  " ",
169
- "total"
177
+ T("ecoDashboard.kpis.total", "total")
170
178
  ]
171
179
  })]
172
180
  })
173
181
  }),
174
182
  /* @__PURE__ */ b(s, {
175
183
  icon: /* @__PURE__ */ b(y, { className: "h-5 w-5" }),
176
- value: N.data ? c(N.data.total) : "—",
177
- label: "Total footprint",
184
+ value: P.data ? c(P.data.total) : "—",
185
+ label: T("ecoDashboard.kpis.totalFootprint", "Total footprint"),
178
186
  featured: !0,
179
- footer: N.data && N.data.total > 0 ? /* @__PURE__ */ x("div", {
187
+ footer: P.data && P.data.total > 0 ? /* @__PURE__ */ x("div", {
180
188
  className: "space-y-2",
181
189
  children: [/* @__PURE__ */ b(ue, {
182
- scope1: N.data.scope1,
183
- scope2: N.data.scope2,
184
- scope3: N.data.scope3,
190
+ scope1: P.data.scope1,
191
+ scope2: P.data.scope2,
192
+ scope3: P.data.scope3,
185
193
  height: 6
186
194
  }), /* @__PURE__ */ b("p", {
187
195
  className: "text-xs text-text-tertiary",
188
- children: "Scope 1 · 2 · 3 split"
196
+ children: T("ecoDashboard.kpis.scopeSplit", "Scope 1 · 2 · 3 split")
189
197
  })]
190
198
  }) : void 0
191
199
  }),
192
200
  /* @__PURE__ */ b(s, {
193
- icon: /* @__PURE__ */ b(Ce, { className: "h-5 w-5" }),
194
- value: l(H),
195
- label: "Sensors online",
196
- accent: U > 0 ? "warning" : "success",
201
+ icon: /* @__PURE__ */ b(we, { className: "h-5 w-5" }),
202
+ value: u(U),
203
+ label: T("ecoDashboard.kpis.sensorsOnline", "Sensors online"),
204
+ accent: W > 0 ? "warning" : "success",
197
205
  footer: /* @__PURE__ */ x("div", {
198
206
  className: "space-y-2",
199
- children: [/* @__PURE__ */ b(f, {
200
- value: R.length ? H / R.length * 100 : 0,
207
+ children: [/* @__PURE__ */ b(p, {
208
+ value: z.length ? U / z.length * 100 : 0,
201
209
  height: 4,
202
210
  barClassName: "bg-status-success-text"
203
211
  }), /* @__PURE__ */ x("p", {
204
212
  className: "text-xs text-text-tertiary",
205
- children: [/* @__PURE__ */ b("span", {
206
- className: "tabular-nums text-text-secondary",
207
- children: U
208
- }), " need attention"]
213
+ children: [
214
+ /* @__PURE__ */ b("span", {
215
+ className: "tabular-nums text-text-secondary",
216
+ children: W
217
+ }),
218
+ " ",
219
+ T("ecoDashboard.kpis.needAttention", "need attention")
220
+ ]
209
221
  })]
210
222
  })
211
223
  }),
212
224
  /* @__PURE__ */ b(s, {
213
- icon: /* @__PURE__ */ b(De, { className: "h-5 w-5" }),
214
- value: P.data?.pageInfo?.hasMore ? `${K.length}+` : l(K.length),
215
- label: "Open breaches",
216
- accent: K.length > 0 ? "error" : "success",
225
+ icon: /* @__PURE__ */ b(Oe, { className: "h-5 w-5" }),
226
+ value: F.data?.pageInfo?.hasMore ? `${q.length}+` : u(q.length),
227
+ label: T("ecoDashboard.kpis.openBreaches", "Open breaches"),
228
+ accent: q.length > 0 ? "error" : "success",
217
229
  footer: /* @__PURE__ */ b("p", {
218
- className: K.length > 0 ? "text-xs font-medium text-status-error-text" : "text-xs text-text-tertiary",
219
- children: K.length > 0 ? "Needs attention" : "All parameters in range"
230
+ className: q.length > 0 ? "text-xs font-medium text-status-error-text" : "text-xs text-text-tertiary",
231
+ children: q.length > 0 ? T("ecoDashboard.kpis.needsAttention", "Needs attention") : T("ecoDashboard.kpis.allParametersInRange", "All parameters in range")
220
232
  })
221
233
  })
222
234
  ]
@@ -227,40 +239,44 @@ function E() {
227
239
  className: "mb-4 flex flex-wrap items-start justify-between gap-3",
228
240
  children: [/* @__PURE__ */ x("div", { children: [/* @__PURE__ */ b("h2", {
229
241
  className: "text-base font-semibold text-text-primary",
230
- children: "Carbon footprint by scope"
242
+ children: T("ecoDashboard.carbonByScope.title", "Carbon footprint by scope")
231
243
  }), /* @__PURE__ */ b("p", {
232
244
  className: "mt-0.5 text-sm text-text-secondary",
233
- children: "GHG Protocol Scope 1 / 2 / 3 emissions (CO₂e)."
245
+ children: T("ecoDashboard.carbonByScope.description", "GHG Protocol Scope 1 / 2 / 3 emissions (CO₂e).")
234
246
  })] }), /* @__PURE__ */ x("div", {
235
247
  className: "flex items-center gap-4",
236
- children: [N.data && N.data.total > 0 && /* @__PURE__ */ x("div", {
248
+ children: [P.data && P.data.total > 0 && /* @__PURE__ */ x("div", {
237
249
  className: "text-right",
238
250
  children: [/* @__PURE__ */ b("p", {
239
251
  className: "text-lg font-semibold tabular-nums text-text-primary",
240
- children: c(N.data.total)
252
+ children: c(P.data.total)
241
253
  }), /* @__PURE__ */ b("p", {
242
254
  className: "text-xs text-text-tertiary",
243
- children: "total"
255
+ children: T("ecoDashboard.carbonByScope.total", "total")
244
256
  })]
245
257
  }), /* @__PURE__ */ x("button", {
246
258
  type: "button",
247
259
  onClick: () => E("carbon"),
248
260
  className: "inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline",
249
- children: ["Ledger ", /* @__PURE__ */ b(_, { className: "h-4 w-4" })]
261
+ children: [
262
+ T("ecoDashboard.carbonByScope.ledger", "Ledger"),
263
+ " ",
264
+ /* @__PURE__ */ b(_, { className: "h-4 w-4" })
265
+ ]
250
266
  })]
251
267
  })]
252
- }), W.every((e) => e.kg === 0) ? /* @__PURE__ */ b(h, {
268
+ }), G.every((e) => e.kg === 0) ? /* @__PURE__ */ b(h, {
253
269
  illustration: "empty-data",
254
270
  icon: /* @__PURE__ */ b(y, { className: "h-10 w-10" }),
255
- title: "No carbon entries yet",
256
- description: "Record activity in the carbon ledger to see your Scope 1–3 footprint here."
257
- }) : /* @__PURE__ */ x(Oe, { children: [/* @__PURE__ */ b("div", {
271
+ title: T("ecoDashboard.carbonByScope.noDataTitle", "No carbon entries yet"),
272
+ description: T("ecoDashboard.carbonByScope.noDataDescription", "Record activity in the carbon ledger to see your Scope 1–3 footprint here.")
273
+ }) : /* @__PURE__ */ x(ke, { children: [/* @__PURE__ */ b("div", {
258
274
  className: "h-56",
259
275
  children: /* @__PURE__ */ b(n, {
260
276
  width: "100%",
261
277
  height: "100%",
262
278
  children: /* @__PURE__ */ x(re, {
263
- data: W,
279
+ data: G,
264
280
  margin: {
265
281
  top: 20,
266
282
  right: 8,
@@ -268,7 +284,7 @@ function E() {
268
284
  bottom: 0
269
285
  },
270
286
  children: [
271
- /* @__PURE__ */ b("defs", { children: W.map((e) => /* @__PURE__ */ x("linearGradient", {
287
+ /* @__PURE__ */ b("defs", { children: G.map((e) => /* @__PURE__ */ x("linearGradient", {
272
288
  id: `ecoScopeGrad-${e.scope}`,
273
289
  x1: "0",
274
290
  y1: "0",
@@ -288,13 +304,13 @@ function E() {
288
304
  }
289
305
  })]
290
306
  }, e.scope)) }),
291
- /* @__PURE__ */ b(te, {
307
+ /* @__PURE__ */ b(a, {
292
308
  vertical: !1,
293
309
  stroke: "var(--color-border-seam)",
294
310
  strokeOpacity: .6,
295
311
  strokeDasharray: "3 4"
296
312
  }),
297
- /* @__PURE__ */ b(a, {
313
+ /* @__PURE__ */ b(te, {
298
314
  dataKey: "label",
299
315
  tick: {
300
316
  fill: "var(--color-text-secondary)",
@@ -325,7 +341,7 @@ function E() {
325
341
  borderRadius: 12,
326
342
  color: "var(--color-text-primary)"
327
343
  },
328
- formatter: (e) => [c(e), "Emissions"]
344
+ formatter: (e) => [c(e), T("ecoDashboard.carbonByScope.emissions", "Emissions")]
329
345
  }),
330
346
  /* @__PURE__ */ x(ee, {
331
347
  dataKey: "kg",
@@ -336,7 +352,7 @@ function E() {
336
352
  2
337
353
  ],
338
354
  maxBarSize: 72,
339
- children: [W.map((e) => /* @__PURE__ */ b(r, { fill: `url(#ecoScopeGrad-${e.scope})` }, e.scope)), /* @__PURE__ */ b(i, {
355
+ children: [G.map((e) => /* @__PURE__ */ b(r, { fill: `url(#ecoScopeGrad-${e.scope})` }, e.scope)), /* @__PURE__ */ b(i, {
340
356
  dataKey: "kg",
341
357
  position: "top",
342
358
  formatter: (e) => e > 0 ? c(e) : "",
@@ -351,7 +367,7 @@ function E() {
351
367
  })
352
368
  })
353
369
  }), /* @__PURE__ */ b(le, {
354
- data: W,
370
+ data: G,
355
371
  className: "mt-4 border-t border-border-seam pt-4"
356
372
  })] })]
357
373
  }), X = /* @__PURE__ */ x(C, {
@@ -363,36 +379,40 @@ function E() {
363
379
  className: "flex items-center gap-2",
364
380
  children: [/* @__PURE__ */ b("h2", {
365
381
  className: "text-base font-semibold text-text-primary",
366
- children: "Recent breaches"
367
- }), K.length > 0 && /* @__PURE__ */ b("span", {
382
+ children: T("ecoDashboard.recentBreaches.title", "Recent breaches")
383
+ }), q.length > 0 && /* @__PURE__ */ b("span", {
368
384
  className: "rounded-full bg-status-error-bg-subtle px-2 py-0.5 text-xs font-medium tabular-nums text-status-error-text",
369
- children: P.data?.pageInfo?.hasMore ? `${K.length}+` : K.length
385
+ children: F.data?.pageInfo?.hasMore ? `${q.length}+` : q.length
370
386
  })]
371
387
  }), /* @__PURE__ */ x("button", {
372
388
  type: "button",
373
389
  onClick: () => E("monitoring"),
374
390
  className: "inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline",
375
- children: ["Monitoring ", /* @__PURE__ */ b(_, { className: "h-4 w-4" })]
391
+ children: [
392
+ T("ecoDashboard.recentBreaches.monitoring", "Monitoring"),
393
+ " ",
394
+ /* @__PURE__ */ b(_, { className: "h-4 w-4" })
395
+ ]
376
396
  })]
377
- }), K.length === 0 ? /* @__PURE__ */ x("div", {
397
+ }), q.length === 0 ? /* @__PURE__ */ x("div", {
378
398
  className: "flex flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-10 text-center",
379
399
  children: [
380
400
  /* @__PURE__ */ b("span", {
381
401
  className: "flex size-12 items-center justify-center rounded-full bg-status-success-bg-subtle",
382
- children: /* @__PURE__ */ b(we, { className: "h-6 w-6 text-status-success-text" })
402
+ children: /* @__PURE__ */ b(Te, { className: "h-6 w-6 text-status-success-text" })
383
403
  }),
384
404
  /* @__PURE__ */ b("p", {
385
405
  className: "mt-3 text-sm font-medium text-text-primary",
386
- children: "All parameters in range"
406
+ children: T("ecoDashboard.recentBreaches.noBreachesTitle", "All parameters in range")
387
407
  }),
388
408
  /* @__PURE__ */ b("p", {
389
409
  className: "mt-1 text-xs text-text-tertiary",
390
- children: "No open threshold breaches."
410
+ children: T("ecoDashboard.recentBreaches.noBreachesDescription", "No open threshold breaches.")
391
411
  })
392
412
  ]
393
413
  }) : /* @__PURE__ */ b("ul", {
394
414
  className: "space-y-2.5",
395
- children: K.map((e) => /* @__PURE__ */ x("li", {
415
+ children: q.map((e) => /* @__PURE__ */ x("li", {
396
416
  className: "flex items-start justify-between gap-3 rounded-xl bg-bg-sunken/50 p-3",
397
417
  children: [/* @__PURE__ */ x("div", {
398
418
  className: "flex min-w-0 items-start gap-2.5",
@@ -410,41 +430,45 @@ function E() {
410
430
  /* @__PURE__ */ x("span", {
411
431
  className: "tabular-nums",
412
432
  children: [
413
- l(e.value),
433
+ u(e.value),
414
434
  " ",
415
435
  e.unit
416
436
  ]
417
437
  }),
418
438
  " ",
419
439
  "· ",
420
- u(e.recordedAt)
440
+ d(e.recordedAt)
421
441
  ]
422
442
  })]
423
443
  })]
424
- }), /* @__PURE__ */ b(d, { status: "BREACH" })]
444
+ }), /* @__PURE__ */ b(f, { status: "BREACH" })]
425
445
  }, e.id))
426
446
  })]
427
447
  }), Z = {
428
- kpis: J,
448
+ kpis: Le,
429
449
  carbonByScope: Y,
430
450
  recentBreaches: X,
431
451
  recentObservations: /* @__PURE__ */ b(m, {
432
- title: "Recent field observations",
433
- description: "Latest wildlife & biodiversity sightings recorded across your sites.",
452
+ title: T("ecoDashboard.recentObservations.title", "Recent field observations"),
453
+ description: T("ecoDashboard.recentObservations.description", "Latest wildlife & biodiversity sightings recorded across your sites."),
434
454
  action: /* @__PURE__ */ x("button", {
435
455
  type: "button",
436
456
  onClick: () => E("observations"),
437
457
  className: "inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline",
438
- children: ["All observations ", /* @__PURE__ */ b(_, { className: "h-4 w-4" })]
458
+ children: [
459
+ T("ecoDashboard.recentObservations.all", "All observations"),
460
+ " ",
461
+ /* @__PURE__ */ b(_, { className: "h-4 w-4" })
462
+ ]
439
463
  }),
440
- children: q.length === 0 ? /* @__PURE__ */ b(h, {
464
+ children: J.length === 0 ? /* @__PURE__ */ b(h, {
441
465
  illustration: "empty-data",
442
466
  icon: /* @__PURE__ */ b(v, { className: "h-10 w-10" }),
443
- title: "No observations yet",
444
- description: "Record your first field observation to build your biodiversity dataset."
467
+ title: T("ecoDashboard.recentObservations.noDataTitle", "No observations yet"),
468
+ description: T("ecoDashboard.recentObservations.noDataDescription", "Record your first field observation to build your biodiversity dataset.")
445
469
  }) : /* @__PURE__ */ b("div", {
446
470
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
447
- children: q.map((e) => /* @__PURE__ */ x(C, {
471
+ children: J.map((e) => /* @__PURE__ */ x(C, {
448
472
  treatment: "plain",
449
473
  className: "p-4 transition-shadow hover:shadow-float",
450
474
  children: [/* @__PURE__ */ x("div", {
@@ -458,7 +482,7 @@ function E() {
458
482
  className: "min-w-0",
459
483
  children: [/* @__PURE__ */ b("p", {
460
484
  className: "truncate text-sm font-semibold text-text-primary",
461
- children: e.species?.commonName ?? "Unknown species"
485
+ children: e.species?.commonName ?? T("ecoDashboard.recentObservations.unknownSpecies", "Unknown species")
462
486
  }), e.species?.scientificName && /* @__PURE__ */ b("p", {
463
487
  className: "truncate text-xs italic text-text-tertiary",
464
488
  children: e.species.scientificName
@@ -466,24 +490,24 @@ function E() {
466
490
  })]
467
491
  }), /* @__PURE__ */ x("span", {
468
492
  className: "shrink-0 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium tabular-nums text-text-primary",
469
- children: ["×", l(e.count)]
493
+ children: ["×", u(e.count)]
470
494
  })]
471
495
  }), /* @__PURE__ */ x("div", {
472
496
  className: "mt-3 flex items-center justify-between border-t border-border-seam/70 pt-2.5",
473
497
  children: [/* @__PURE__ */ b("span", {
474
498
  className: "rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
475
- children: ie(e.method)
499
+ children: l(e.method)
476
500
  }), /* @__PURE__ */ b("span", {
477
501
  className: "text-xs text-text-tertiary",
478
- children: u(e.observedAt)
502
+ children: d(e.observedAt)
479
503
  })]
480
504
  })]
481
505
  }, e.id))
482
506
  })
483
507
  }),
484
- conservationStatus: I.length > 0 ? /* @__PURE__ */ b(m, {
485
- title: "Conservation status",
486
- description: "Distribution of tracked species by IUCN-style status.",
508
+ conservationStatus: L.length > 0 ? /* @__PURE__ */ b(m, {
509
+ title: T("ecoDashboard.conservationStatus.title", "Conservation status"),
510
+ description: T("ecoDashboard.conservationStatus.description", "Distribution of tracked species by IUCN-style status."),
487
511
  children: /* @__PURE__ */ b(C, {
488
512
  treatment: "plain",
489
513
  className: "p-6",
@@ -491,16 +515,16 @@ function E() {
491
515
  className: "space-y-3.5",
492
516
  children: (() => {
493
517
  let e = /* @__PURE__ */ new Map();
494
- for (let t of I) e.set(t.conservationStatus, (e.get(t.conservationStatus) ?? 0) + 1);
495
- let t = I.length;
518
+ for (let t of L) e.set(t.conservationStatus, (e.get(t.conservationStatus) ?? 0) + 1);
519
+ let t = L.length;
496
520
  return [...e.entries()].sort((e, t) => t[1] - e[1]).map(([e, n]) => {
497
- let r = n / t * 100, i = T[e] ?? "var(--color-text-tertiary)";
521
+ let r = n / t * 100, i = Ae[e] ?? "var(--color-text-tertiary)";
498
522
  return /* @__PURE__ */ x("div", {
499
523
  className: "flex items-center gap-3 sm:gap-4",
500
524
  children: [
501
525
  /* @__PURE__ */ b("div", {
502
526
  className: "w-36 shrink-0 sm:w-48",
503
- children: /* @__PURE__ */ b(d, { status: e })
527
+ children: /* @__PURE__ */ b(f, { status: e })
504
528
  }),
505
529
  /* @__PURE__ */ b(ce, {
506
530
  value: r,
@@ -513,7 +537,7 @@ function E() {
513
537
  }),
514
538
  /* @__PURE__ */ b("span", {
515
539
  className: "hidden w-12 shrink-0 text-right text-xs tabular-nums text-text-tertiary sm:inline",
516
- children: ae(r)
540
+ children: ie(r)
517
541
  })
518
542
  ]
519
543
  }, e);
@@ -522,7 +546,7 @@ function E() {
522
546
  })
523
547
  })
524
548
  }) : null
525
- }, Q = k.layout.widgetOrder, $ = [];
549
+ }, Q = A.layout.widgetOrder, $ = [];
526
550
  for (let e = 0; e < Q.length; e++) {
527
551
  let t = Q[e];
528
552
  if (t === "kpis") {
@@ -544,24 +568,24 @@ function E() {
544
568
  n && $.push(/* @__PURE__ */ b("div", { children: n }, t));
545
569
  }
546
570
  return /* @__PURE__ */ x(o, {
547
- title: "EcoImpactHub",
571
+ title: T("ecoDashboard.title", "EcoImpactHub"),
548
572
  titleGradient: !0,
549
573
  aurora: !0,
550
- description: `Welcome back, ${Fe} — your environment, conservation & sustainability control room.`,
551
- actions: G,
574
+ description: T("ecoDashboard.description", "Welcome back, {{name}} — your environment, conservation & sustainability control room.", { name: Ie }),
575
+ actions: K,
552
576
  children: [/* @__PURE__ */ x("div", {
553
577
  className: "space-y-6",
554
- children: [$, k.layout.notes.length > 0 && /* @__PURE__ */ b(m, {
555
- title: "Custom notes",
556
- description: "Your personal notes, visible only to you.",
578
+ children: [$, A.layout.notes.length > 0 && /* @__PURE__ */ b(m, {
579
+ title: T("ecoDashboard.customNotes.title", "Custom notes"),
580
+ description: T("ecoDashboard.customNotes.description", "Your personal notes, visible only to you."),
557
581
  children: /* @__PURE__ */ b("div", {
558
582
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
559
- children: k.layout.notes.map((e) => /* @__PURE__ */ b(C, {
583
+ children: A.layout.notes.map((e) => /* @__PURE__ */ b(C, {
560
584
  treatment: "plain",
561
585
  className: "p-4",
562
586
  children: /* @__PURE__ */ x("div", {
563
587
  className: "flex items-start gap-2.5",
564
- children: [/* @__PURE__ */ b(Te, { className: "mt-0.5 h-4 w-4 shrink-0 text-accent" }), /* @__PURE__ */ x("div", {
588
+ children: [/* @__PURE__ */ b(Ee, { className: "mt-0.5 h-4 w-4 shrink-0 text-accent" }), /* @__PURE__ */ x("div", {
565
589
  className: "min-w-0",
566
590
  children: [e.title && /* @__PURE__ */ b("p", {
567
591
  className: "truncate text-sm font-semibold text-text-primary",
@@ -576,13 +600,13 @@ function E() {
576
600
  })
577
601
  })]
578
602
  }), /* @__PURE__ */ b(se, {
579
- open: Ae,
580
- onOpenChange: O,
581
- layout: k
603
+ open: je,
604
+ onOpenChange: k,
605
+ layout: A
582
606
  })]
583
607
  });
584
608
  }
585
609
  //#endregion
586
- export { E as DashboardPage };
610
+ export { T as DashboardPage };
587
611
 
588
612
  //# sourceMappingURL=DashboardPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/ecoimpacthub/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — EcoImpactHub environment, conservation & sustainability overview.\n * @module ecoimpacthub/pages\n *\n * KPIs are aggregated client-side from the real `eco*` list endpoints plus the\n * `ecoCarbonTotalKg` aggregate (by scope). Widgets show recent field\n * observations and open threshold breaches. No mock data.\n *\n * Customizable (BOFF-3943): the \"Customize dashboard\" CTA opens a widget\n * palette (show/hide/reorder + freeform notes), persisted per user/workspace\n * via {@link useDashboardLayout}. Uncustomized users see the exact same\n * layout as before this change — the widget order defaults to this file's\n * original hardcoded order, and the carbon-by-scope / recent-breaches pair\n * keeps its original side-by-side layout whenever they remain adjacent.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport {\n MapPin,\n PawPrint,\n Trees,\n Factory,\n TriangleAlert,\n Binoculars,\n ShieldCheck,\n ArrowRight,\n Radio,\n LayoutGrid,\n StickyNote,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n BarChart,\n Bar,\n XAxis,\n YAxis,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip,\n Cell,\n LabelList,\n} from 'recharts';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { MiniBar } from '../components/MiniBar';\nimport { LoadingView, Section } from '../components/StateViews';\nimport { EmptyState } from '../components/EmptyState';\nimport { DashboardCustomizerDialog } from '../components/DashboardCustomizerDialog';\nimport { ScopeSplitBar, ScopeLegend, Meter, SCOPE_HUE } from './carbonVisuals';\nimport { useEcoImpactHub } from '../context';\nimport {\n useEcoSites,\n useEcoSpecies,\n useEcoHabitatProjects,\n useEcoCarbonTotals,\n useEcoMeasurements,\n useEcoObservations,\n useEcoSensors,\n useDashboardLayout,\n type DashboardWidgetId,\n} from '../hooks';\nimport {\n formatNumber,\n formatRelativeTime,\n formatCo2e,\n formatEnumLabel,\n formatPercent,\n} from '../utils/formatters';\nimport type { EcoConservationStatus, EcoEmissionScope } from '../types';\n\nconst ACTIVE_PROJECT_STATUSES = new Set(['PLANNED', 'IN_PROGRESS', 'MONITORING']);\n\n/** Conservation-status → meter tint (semantic, IUCN-severity ordered). */\nconst CONSERVATION_TONE: Record<EcoConservationStatus, string> = {\n LEAST_CONCERN: 'var(--color-status-success-text)',\n NEAR_THREATENED: 'var(--color-accent-teal)',\n VULNERABLE: 'var(--color-status-warning-text)',\n ENDANGERED: 'var(--color-status-error-text)',\n CRITICALLY_ENDANGERED: 'var(--color-status-error-text)',\n EXTINCT_IN_WILD: 'var(--color-text-tertiary)',\n DATA_DEFICIENT: 'var(--color-text-tertiary)',\n NOT_EVALUATED: 'var(--color-text-tertiary)',\n};\n\nexport function DashboardPage() {\n const { navigateTo, currentUser, workspaceId } = useEcoImpactHub();\n const [customizerOpen, setCustomizerOpen] = useState(false);\n const dashboardLayout = useDashboardLayout(workspaceId, currentUser?.id);\n\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n const species = useEcoSpecies({ pagination: { limit: 100, offset: 0 } });\n const projects = useEcoHabitatProjects();\n const carbon = useEcoCarbonTotals();\n const breaches = useEcoMeasurements({ breachesOnly: true, pagination: { limit: 6, offset: 0 } });\n const observations = useEcoObservations({ pagination: { limit: 6, offset: 0 } });\n const sensors = useEcoSensors();\n\n const siteRows = sites.data?.items ?? [];\n const speciesRows = species.data?.items ?? [];\n const projectRows = projects.data?.items ?? [];\n const sensorRows = sensors.data?.items ?? [];\n\n const activeSites = siteRows.filter((s) => s.status === 'ACTIVE').length;\n const priorityCount = speciesRows.filter((s) => s.isPriority).length;\n const threatened = speciesRows.filter((s) =>\n ['VULNERABLE', 'ENDANGERED', 'CRITICALLY_ENDANGERED'].includes(s.conservationStatus)\n ).length;\n const activeProjects = projectRows.filter((p) => ACTIVE_PROJECT_STATUSES.has(p.status)).length;\n const onlineSensors = sensorRows.filter((s) => s.status === 'ONLINE').length;\n const offlineSensors = sensorRows.filter((s) =>\n ['OFFLINE', 'DEGRADED', 'MAINTENANCE'].includes(s.status)\n ).length;\n\n const carbonData = useMemo<{ scope: EcoEmissionScope; label: string; kg: number }[]>(() => {\n const c = carbon.data;\n if (!c) return [];\n return [\n { scope: 'SCOPE_1', label: 'Scope 1', kg: c.scope1 },\n { scope: 'SCOPE_2', label: 'Scope 2', kg: c.scope2 },\n { scope: 'SCOPE_3', label: 'Scope 3', kg: c.scope3 },\n ];\n }, [carbon.data]);\n\n const isInitialLoading =\n sites.isLoading && species.isLoading && projects.isLoading && carbon.isLoading;\n\n const headerActions = (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setCustomizerOpen(true)}>\n <LayoutGrid className=\"mr-1.5 h-3.5 w-3.5\" />\n Customize dashboard\n </Button>\n );\n\n if (isInitialLoading) {\n return (\n <PageLayout title=\"EcoImpactHub\" titleGradient aurora actions={headerActions}>\n <LoadingView label=\"Loading your impact overview…\" />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n const breachRows = breaches.data?.items ?? [];\n const observationRows = observations.data?.items ?? [];\n\n const kpisWidget = (\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={<MapPin className=\"h-5 w-5\" />}\n value={\n sites.data?.pageInfo?.hasMore ? `${siteRows.length}+` : formatNumber(siteRows.length)\n }\n label=\"Monitored sites\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={siteRows.length ? (activeSites / siteRows.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\">{activeSites}</span> active\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<PawPrint className=\"h-5 w-5\" />}\n value={\n species.data?.pageInfo?.hasMore\n ? `${speciesRows.length}+`\n : formatNumber(speciesRows.length)\n }\n label=\"Species tracked\"\n accent={threatened > 0 ? 'warning' : undefined}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={speciesRows.length ? (threatened / speciesRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-warning-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{priorityCount}</span> priority ·{' '}\n <span className=\"tabular-nums text-text-secondary\">{threatened}</span> threatened\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<Trees className=\"h-5 w-5\" />}\n value={formatNumber(activeProjects)}\n label=\"Active habitat projects\"\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={projectRows.length ? (activeProjects / projectRows.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 {formatNumber(projectRows.length)}\n </span>{' '}\n total\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<Factory className=\"h-5 w-5\" />}\n value={carbon.data ? formatCo2e(carbon.data.total) : '—'}\n label=\"Total footprint\"\n featured\n footer={\n carbon.data && carbon.data.total > 0 ? (\n <div className=\"space-y-2\">\n <ScopeSplitBar\n scope1={carbon.data.scope1}\n scope2={carbon.data.scope2}\n scope3={carbon.data.scope3}\n height={6}\n />\n <p className=\"text-xs text-text-tertiary\">Scope 1 · 2 · 3 split</p>\n </div>\n ) : undefined\n }\n />\n <MetricsCard\n icon={<Radio className=\"h-5 w-5\" />}\n value={formatNumber(onlineSensors)}\n label=\"Sensors online\"\n accent={offlineSensors > 0 ? 'warning' : 'success'}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={sensorRows.length ? (onlineSensors / sensorRows.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\">{offlineSensors}</span> need\n attention\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<TriangleAlert className=\"h-5 w-5\" />}\n value={\n breaches.data?.pageInfo?.hasMore\n ? `${breachRows.length}+`\n : formatNumber(breachRows.length)\n }\n label=\"Open breaches\"\n accent={breachRows.length > 0 ? 'error' : 'success'}\n footer={\n <p\n className={\n breachRows.length > 0\n ? 'text-xs font-medium text-status-error-text'\n : 'text-xs text-text-tertiary'\n }\n >\n {breachRows.length > 0 ? 'Needs attention' : 'All parameters in range'}\n </p>\n }\n />\n </div>\n );\n\n const carbonByScopeWidget = (\n <GlassCard treatment=\"glass\" className=\"p-6 h-full\">\n <div className=\"mb-4 flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">Carbon footprint by scope</h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n GHG Protocol Scope 1 / 2 / 3 emissions (CO₂e).\n </p>\n </div>\n <div className=\"flex items-center gap-4\">\n {carbon.data && carbon.data.total > 0 && (\n <div className=\"text-right\">\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatCo2e(carbon.data.total)}\n </p>\n <p className=\"text-xs text-text-tertiary\">total</p>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo('carbon')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Ledger <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {carbonData.every((d) => d.kg === 0) ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Factory className=\"h-10 w-10\" />}\n title=\"No carbon entries yet\"\n description=\"Record activity in the carbon ledger to see your Scope 1–3 footprint here.\"\n />\n ) : (\n <>\n <div className=\"h-56\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart data={carbonData} margin={{ top: 20, right: 8, left: 8, bottom: 0 }}>\n <defs>\n {carbonData.map((d) => (\n <linearGradient\n key={d.scope}\n id={`ecoScopeGrad-${d.scope}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop offset=\"0%\" style={{ stopColor: SCOPE_HUE[d.scope], stopOpacity: 1 }} />\n <stop\n offset=\"100%\"\n style={{ stopColor: SCOPE_HUE[d.scope], stopOpacity: 0.55 }}\n />\n </linearGradient>\n ))}\n </defs>\n <CartesianGrid\n vertical={false}\n stroke=\"var(--color-border-seam)\"\n strokeOpacity={0.6}\n strokeDasharray=\"3 4\"\n />\n <XAxis\n dataKey=\"label\"\n tick={{ fill: 'var(--color-text-secondary)', fontSize: 12 }}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n dy={4}\n />\n <YAxis\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 11 }}\n axisLine={false}\n tickLine={false}\n width={48}\n tickFormatter={(v: number) => (v >= 1000 ? `${Math.round(v / 1000)}t` : `${v}`)}\n />\n <Tooltip\n cursor={{ fill: 'var(--color-bg-sunken)', opacity: 0.4 }}\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n }}\n formatter={(value: number) => [formatCo2e(value), 'Emissions']}\n />\n <Bar dataKey=\"kg\" radius={[8, 8, 2, 2]} maxBarSize={72}>\n {carbonData.map((d) => (\n <Cell key={d.scope} fill={`url(#ecoScopeGrad-${d.scope})`} />\n ))}\n <LabelList\n dataKey=\"kg\"\n position=\"top\"\n formatter={(v: number) => (v > 0 ? formatCo2e(v) : '')}\n style={{ fill: 'var(--color-text-secondary)', fontSize: 11, fontWeight: 600 }}\n />\n </Bar>\n </BarChart>\n </ResponsiveContainer>\n </div>\n <ScopeLegend data={carbonData} className=\"mt-4 border-t border-border-seam pt-4\" />\n </>\n )}\n </GlassCard>\n );\n\n const recentBreachesWidget = (\n <GlassCard treatment=\"glass\" className=\"p-6 h-full\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-text-primary\">Recent breaches</h2>\n {breachRows.length > 0 && (\n <span className=\"rounded-full bg-status-error-bg-subtle px-2 py-0.5 text-xs font-medium tabular-nums text-status-error-text\">\n {breaches.data?.pageInfo?.hasMore ? `${breachRows.length}+` : breachRows.length}\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('monitoring')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n Monitoring <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {breachRows.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-10 text-center\">\n <span className=\"flex size-12 items-center justify-center rounded-full bg-status-success-bg-subtle\">\n <ShieldCheck className=\"h-6 w-6 text-status-success-text\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">All parameters in range</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">No open threshold breaches.</p>\n </div>\n ) : (\n <ul className=\"space-y-2.5\">\n {breachRows.map((m) => (\n <li\n key={m.id}\n className=\"flex items-start justify-between gap-3 rounded-xl bg-bg-sunken/50 p-3\"\n >\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"mt-1.5 size-2 shrink-0 rounded-full bg-status-error-text motion-safe:animate-vc-glow-pulse\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{m.parameter}</p>\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums\">\n {formatNumber(m.value)} {m.unit}\n </span>{' '}\n · {formatRelativeTime(m.recordedAt)}\n </p>\n </div>\n </div>\n <StatusBadge status=\"BREACH\" />\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n );\n\n const recentObservationsWidget = (\n <Section\n title=\"Recent field observations\"\n description=\"Latest wildlife & biodiversity sightings recorded across your sites.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('observations')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n All observations <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {observationRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Binoculars className=\"h-10 w-10\" />}\n title=\"No observations yet\"\n description=\"Record your first field observation to build your biodiversity dataset.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {observationRows.map((o) => (\n <GlassCard\n key={o.id}\n treatment=\"plain\"\n className=\"p-4 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-start justify-between gap-2\">\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-accent\">\n <Binoculars className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {o.species?.commonName ?? 'Unknown species'}\n </p>\n {o.species?.scientificName && (\n <p className=\"truncate text-xs italic text-text-tertiary\">\n {o.species.scientificName}\n </p>\n )}\n </div>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium tabular-nums text-text-primary\">\n ×{formatNumber(o.count)}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between border-t border-border-seam/70 pt-2.5\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {formatEnumLabel(o.method)}\n </span>\n <span className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(o.observedAt)}\n </span>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n );\n\n const conservationStatusWidget =\n speciesRows.length > 0 ? (\n <Section\n title=\"Conservation status\"\n description=\"Distribution of tracked species by IUCN-style status.\"\n >\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"space-y-3.5\">\n {(() => {\n const counts = new Map<string, number>();\n for (const s of speciesRows) {\n counts.set(s.conservationStatus, (counts.get(s.conservationStatus) ?? 0) + 1);\n }\n const total = speciesRows.length;\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([status, count]) => {\n const pct = (count / total) * 100;\n const tone =\n CONSERVATION_TONE[status as EcoConservationStatus] ??\n 'var(--color-text-tertiary)';\n return (\n <div key={status} className=\"flex items-center gap-3 sm:gap-4\">\n <div className=\"w-36 shrink-0 sm:w-48\">\n <StatusBadge status={status} />\n </div>\n <Meter value={pct} color={tone} className=\"flex-1\" />\n <span className=\"w-9 shrink-0 text-right text-sm font-medium tabular-nums text-text-primary\">\n {count}\n </span>\n <span className=\"hidden w-12 shrink-0 text-right text-xs tabular-nums text-text-tertiary sm:inline\">\n {formatPercent(pct)}\n </span>\n </div>\n );\n });\n })()}\n </div>\n </GlassCard>\n </Section>\n ) : null;\n\n const WIDGET_RENDER: Record<DashboardWidgetId, ReactNode> = {\n kpis: kpisWidget,\n carbonByScope: carbonByScopeWidget,\n recentBreaches: recentBreachesWidget,\n recentObservations: recentObservationsWidget,\n conservationStatus: conservationStatusWidget,\n };\n\n const order = dashboardLayout.layout.widgetOrder;\n const blocks: ReactNode[] = [];\n for (let i = 0; i < order.length; i++) {\n const id = order[i];\n if (id === 'kpis') {\n blocks.push(<div key={id}>{WIDGET_RENDER[id]}</div>);\n continue;\n }\n // Preserve the original side-by-side carbon-by-scope / recent-breaches pairing\n // whenever they remain adjacent in that order (the default, uncustomized case).\n if (id === 'carbonByScope' && order[i + 1] === 'recentBreaches') {\n blocks.push(\n <div key={`${id}-pair`} className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">{carbonByScopeWidget}</div>\n <div>{recentBreachesWidget}</div>\n </div>\n );\n i += 1;\n continue;\n }\n if (id === 'recentBreaches' && order[i - 1] === 'carbonByScope') {\n // Already rendered as part of the pair above.\n continue;\n }\n const content = WIDGET_RENDER[id];\n if (!content) continue;\n blocks.push(<div key={id}>{content}</div>);\n }\n\n return (\n <PageLayout\n title=\"EcoImpactHub\"\n titleGradient\n aurora\n description={`Welcome back, ${greetingName} — your environment, conservation & sustainability control room.`}\n actions={headerActions}\n >\n <div className=\"space-y-6\">\n {blocks}\n\n {dashboardLayout.layout.notes.length > 0 && (\n <Section title=\"Custom notes\" description=\"Your personal notes, visible only to you.\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {dashboardLayout.layout.notes.map((note) => (\n <GlassCard key={note.id} treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start gap-2.5\">\n <StickyNote className=\"mt-0.5 h-4 w-4 shrink-0 text-accent\" />\n <div className=\"min-w-0\">\n {note.title && (\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {note.title}\n </p>\n )}\n {note.body && (\n <p className=\"mt-0.5 text-sm text-text-secondary\">{note.body}</p>\n )}\n </div>\n </div>\n </GlassCard>\n ))}\n </div>\n </Section>\n )}\n </div>\n\n <DashboardCustomizerDialog\n open={customizerOpen}\n onOpenChange={setCustomizerOpen}\n layout={dashboardLayout}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/ecoimpacthub/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard — EcoImpactHub environment, conservation & sustainability overview.\n * @module ecoimpacthub/pages\n *\n * KPIs are aggregated client-side from the real `eco*` list endpoints plus the\n * `ecoCarbonTotalKg` aggregate (by scope). Widgets show recent field\n * observations and open threshold breaches. No mock data.\n *\n * Customizable (BOFF-3943): the \"Customize dashboard\" CTA opens a widget\n * palette (show/hide/reorder + freeform notes), persisted per user/workspace\n * via {@link useDashboardLayout}. Uncustomized users see the exact same\n * layout as before this change — the widget order defaults to this file's\n * original hardcoded order, and the carbon-by-scope / recent-breaches pair\n * keeps its original side-by-side layout whenever they remain adjacent.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport {\n MapPin,\n PawPrint,\n Trees,\n Factory,\n TriangleAlert,\n Binoculars,\n ShieldCheck,\n ArrowRight,\n Radio,\n LayoutGrid,\n StickyNote,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n BarChart,\n Bar,\n XAxis,\n YAxis,\n CartesianGrid,\n ResponsiveContainer,\n Tooltip,\n Cell,\n LabelList,\n} from 'recharts';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { StatusBadge } from '../components/StatusBadge';\nimport { MiniBar } from '../components/MiniBar';\nimport { LoadingView, Section } from '../components/StateViews';\nimport { EmptyState } from '../components/EmptyState';\nimport { DashboardCustomizerDialog } from '../components/DashboardCustomizerDialog';\nimport { ScopeSplitBar, ScopeLegend, Meter, SCOPE_HUE } from './carbonVisuals';\nimport { useEcoImpactHub } from '../context';\nimport {\n useEcoSites,\n useEcoSpecies,\n useEcoHabitatProjects,\n useEcoCarbonTotals,\n useEcoMeasurements,\n useEcoObservations,\n useEcoSensors,\n useDashboardLayout,\n useTr,\n type DashboardWidgetId,\n} from '../hooks';\nimport {\n formatNumber,\n formatRelativeTime,\n formatCo2e,\n formatEnumLabel,\n formatPercent,\n} from '../utils/formatters';\nimport type { EcoConservationStatus, EcoEmissionScope } from '../types';\n\nconst ACTIVE_PROJECT_STATUSES = new Set(['PLANNED', 'IN_PROGRESS', 'MONITORING']);\n\n/** Conservation-status → meter tint (semantic, IUCN-severity ordered). */\nconst CONSERVATION_TONE: Record<EcoConservationStatus, string> = {\n LEAST_CONCERN: 'var(--color-status-success-text)',\n NEAR_THREATENED: 'var(--color-accent-teal)',\n VULNERABLE: 'var(--color-status-warning-text)',\n ENDANGERED: 'var(--color-status-error-text)',\n CRITICALLY_ENDANGERED: 'var(--color-status-error-text)',\n EXTINCT_IN_WILD: 'var(--color-text-tertiary)',\n DATA_DEFICIENT: 'var(--color-text-tertiary)',\n NOT_EVALUATED: 'var(--color-text-tertiary)',\n};\n\nexport function DashboardPage() {\n const tr = useTr();\n const { navigateTo, currentUser, workspaceId } = useEcoImpactHub();\n const [customizerOpen, setCustomizerOpen] = useState(false);\n const dashboardLayout = useDashboardLayout(workspaceId, currentUser?.id);\n\n const sites = useEcoSites({ pagination: { limit: 100, offset: 0 } });\n const species = useEcoSpecies({ pagination: { limit: 100, offset: 0 } });\n const projects = useEcoHabitatProjects();\n const carbon = useEcoCarbonTotals();\n const breaches = useEcoMeasurements({ breachesOnly: true, pagination: { limit: 6, offset: 0 } });\n const observations = useEcoObservations({ pagination: { limit: 6, offset: 0 } });\n const sensors = useEcoSensors();\n\n const siteRows = sites.data?.items ?? [];\n const speciesRows = species.data?.items ?? [];\n const projectRows = projects.data?.items ?? [];\n const sensorRows = sensors.data?.items ?? [];\n\n const activeSites = siteRows.filter((s) => s.status === 'ACTIVE').length;\n const priorityCount = speciesRows.filter((s) => s.isPriority).length;\n const threatened = speciesRows.filter((s) =>\n ['VULNERABLE', 'ENDANGERED', 'CRITICALLY_ENDANGERED'].includes(s.conservationStatus)\n ).length;\n const activeProjects = projectRows.filter((p) => ACTIVE_PROJECT_STATUSES.has(p.status)).length;\n const onlineSensors = sensorRows.filter((s) => s.status === 'ONLINE').length;\n const offlineSensors = sensorRows.filter((s) =>\n ['OFFLINE', 'DEGRADED', 'MAINTENANCE'].includes(s.status)\n ).length;\n\n const carbonData = useMemo<{ scope: EcoEmissionScope; label: string; kg: number }[]>(() => {\n const c = carbon.data;\n if (!c) return [];\n return [\n { scope: 'SCOPE_1', label: 'Scope 1', kg: c.scope1 },\n { scope: 'SCOPE_2', label: 'Scope 2', kg: c.scope2 },\n { scope: 'SCOPE_3', label: 'Scope 3', kg: c.scope3 },\n ];\n }, [carbon.data]);\n\n const isInitialLoading =\n sites.isLoading && species.isLoading && projects.isLoading && carbon.isLoading;\n\n const headerActions = (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setCustomizerOpen(true)}>\n <LayoutGrid className=\"mr-1.5 h-3.5 w-3.5\" />\n {tr('ecoDashboard.customize', 'Customize dashboard')}\n </Button>\n );\n\n if (isInitialLoading) {\n return (\n <PageLayout title={tr('ecoDashboard.title', 'EcoImpactHub')} titleGradient aurora actions={headerActions}>\n <LoadingView label={tr('ecoDashboard.loading', 'Loading your impact overview…')} />\n </PageLayout>\n );\n }\n\n const greetingName = currentUser?.firstName || currentUser?.name || 'there';\n const breachRows = breaches.data?.items ?? [];\n const observationRows = observations.data?.items ?? [];\n\n const kpisWidget = (\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={<MapPin className=\"h-5 w-5\" />}\n value={\n sites.data?.pageInfo?.hasMore ? `${siteRows.length}+` : formatNumber(siteRows.length)\n }\n label={tr('ecoDashboard.kpis.monitoredSites', 'Monitored sites')}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={siteRows.length ? (activeSites / siteRows.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\">{activeSites}</span>{' '}\n {tr('ecoDashboard.kpis.active', 'active')}\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<PawPrint className=\"h-5 w-5\" />}\n value={\n species.data?.pageInfo?.hasMore\n ? `${speciesRows.length}+`\n : formatNumber(speciesRows.length)\n }\n label={tr('ecoDashboard.kpis.speciesTracked', 'Species tracked')}\n accent={threatened > 0 ? 'warning' : undefined}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={speciesRows.length ? (threatened / speciesRows.length) * 100 : 0}\n height={4}\n barClassName=\"bg-status-warning-text\"\n />\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{priorityCount}</span>{' '}\n {tr('ecoDashboard.kpis.priority', 'priority')} ·{' '}\n <span className=\"tabular-nums text-text-secondary\">{threatened}</span>{' '}\n {tr('ecoDashboard.kpis.threatened', 'threatened')}\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<Trees className=\"h-5 w-5\" />}\n value={formatNumber(activeProjects)}\n label={tr('ecoDashboard.kpis.activeHabitatProjects', 'Active habitat projects')}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={projectRows.length ? (activeProjects / projectRows.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 {formatNumber(projectRows.length)}\n </span>{' '}\n {tr('ecoDashboard.kpis.total', 'total')}\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<Factory className=\"h-5 w-5\" />}\n value={carbon.data ? formatCo2e(carbon.data.total) : '—'}\n label={tr('ecoDashboard.kpis.totalFootprint', 'Total footprint')}\n featured\n footer={\n carbon.data && carbon.data.total > 0 ? (\n <div className=\"space-y-2\">\n <ScopeSplitBar\n scope1={carbon.data.scope1}\n scope2={carbon.data.scope2}\n scope3={carbon.data.scope3}\n height={6}\n />\n <p className=\"text-xs text-text-tertiary\">\n {tr('ecoDashboard.kpis.scopeSplit', 'Scope 1 · 2 · 3 split')}\n </p>\n </div>\n ) : undefined\n }\n />\n <MetricsCard\n icon={<Radio className=\"h-5 w-5\" />}\n value={formatNumber(onlineSensors)}\n label={tr('ecoDashboard.kpis.sensorsOnline', 'Sensors online')}\n accent={offlineSensors > 0 ? 'warning' : 'success'}\n footer={\n <div className=\"space-y-2\">\n <MiniBar\n value={sensorRows.length ? (onlineSensors / sensorRows.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\">{offlineSensors}</span>{' '}\n {tr('ecoDashboard.kpis.needAttention', 'need attention')}\n </p>\n </div>\n }\n />\n <MetricsCard\n icon={<TriangleAlert className=\"h-5 w-5\" />}\n value={\n breaches.data?.pageInfo?.hasMore\n ? `${breachRows.length}+`\n : formatNumber(breachRows.length)\n }\n label={tr('ecoDashboard.kpis.openBreaches', 'Open breaches')}\n accent={breachRows.length > 0 ? 'error' : 'success'}\n footer={\n <p\n className={\n breachRows.length > 0\n ? 'text-xs font-medium text-status-error-text'\n : 'text-xs text-text-tertiary'\n }\n >\n {breachRows.length > 0\n ? tr('ecoDashboard.kpis.needsAttention', 'Needs attention')\n : tr('ecoDashboard.kpis.allParametersInRange', 'All parameters in range')}\n </p>\n }\n />\n </div>\n );\n\n const carbonByScopeWidget = (\n <GlassCard treatment=\"glass\" className=\"p-6 h-full\">\n <div className=\"mb-4 flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('ecoDashboard.carbonByScope.title', 'Carbon footprint by scope')}\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n {tr(\n 'ecoDashboard.carbonByScope.description',\n 'GHG Protocol Scope 1 / 2 / 3 emissions (CO₂e).',\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-4\">\n {carbon.data && carbon.data.total > 0 && (\n <div className=\"text-right\">\n <p className=\"text-lg font-semibold tabular-nums text-text-primary\">\n {formatCo2e(carbon.data.total)}\n </p>\n <p className=\"text-xs text-text-tertiary\">\n {tr('ecoDashboard.carbonByScope.total', 'total')}\n </p>\n </div>\n )}\n <button\n type=\"button\"\n onClick={() => navigateTo('carbon')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n {tr('ecoDashboard.carbonByScope.ledger', 'Ledger')} <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n {carbonData.every((d) => d.kg === 0) ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Factory className=\"h-10 w-10\" />}\n title={tr('ecoDashboard.carbonByScope.noDataTitle', 'No carbon entries yet')}\n description={tr(\n 'ecoDashboard.carbonByScope.noDataDescription',\n 'Record activity in the carbon ledger to see your Scope 1–3 footprint here.',\n )}\n />\n ) : (\n <>\n <div className=\"h-56\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <BarChart data={carbonData} margin={{ top: 20, right: 8, left: 8, bottom: 0 }}>\n <defs>\n {carbonData.map((d) => (\n <linearGradient\n key={d.scope}\n id={`ecoScopeGrad-${d.scope}`}\n x1=\"0\"\n y1=\"0\"\n x2=\"0\"\n y2=\"1\"\n >\n <stop offset=\"0%\" style={{ stopColor: SCOPE_HUE[d.scope], stopOpacity: 1 }} />\n <stop\n offset=\"100%\"\n style={{ stopColor: SCOPE_HUE[d.scope], stopOpacity: 0.55 }}\n />\n </linearGradient>\n ))}\n </defs>\n <CartesianGrid\n vertical={false}\n stroke=\"var(--color-border-seam)\"\n strokeOpacity={0.6}\n strokeDasharray=\"3 4\"\n />\n <XAxis\n dataKey=\"label\"\n tick={{ fill: 'var(--color-text-secondary)', fontSize: 12 }}\n axisLine={{ stroke: 'var(--color-border-seam)' }}\n tickLine={false}\n dy={4}\n />\n <YAxis\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 11 }}\n axisLine={false}\n tickLine={false}\n width={48}\n tickFormatter={(v: number) => (v >= 1000 ? `${Math.round(v / 1000)}t` : `${v}`)}\n />\n <Tooltip\n cursor={{ fill: 'var(--color-bg-sunken)', opacity: 0.4 }}\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n }}\n formatter={(value: number) => [\n formatCo2e(value),\n tr('ecoDashboard.carbonByScope.emissions', 'Emissions'),\n ]}\n />\n <Bar dataKey=\"kg\" radius={[8, 8, 2, 2]} maxBarSize={72}>\n {carbonData.map((d) => (\n <Cell key={d.scope} fill={`url(#ecoScopeGrad-${d.scope})`} />\n ))}\n <LabelList\n dataKey=\"kg\"\n position=\"top\"\n formatter={(v: number) => (v > 0 ? formatCo2e(v) : '')}\n style={{ fill: 'var(--color-text-secondary)', fontSize: 11, fontWeight: 600 }}\n />\n </Bar>\n </BarChart>\n </ResponsiveContainer>\n </div>\n <ScopeLegend data={carbonData} className=\"mt-4 border-t border-border-seam pt-4\" />\n </>\n )}\n </GlassCard>\n );\n\n const recentBreachesWidget = (\n <GlassCard treatment=\"glass\" className=\"p-6 h-full\">\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('ecoDashboard.recentBreaches.title', 'Recent breaches')}\n </h2>\n {breachRows.length > 0 && (\n <span className=\"rounded-full bg-status-error-bg-subtle px-2 py-0.5 text-xs font-medium tabular-nums text-status-error-text\">\n {breaches.data?.pageInfo?.hasMore ? `${breachRows.length}+` : breachRows.length}\n </span>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => navigateTo('monitoring')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n {tr('ecoDashboard.recentBreaches.monitoring', 'Monitoring')}{' '}\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n </div>\n {breachRows.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center rounded-xl border border-dashed border-border-seam py-10 text-center\">\n <span className=\"flex size-12 items-center justify-center rounded-full bg-status-success-bg-subtle\">\n <ShieldCheck className=\"h-6 w-6 text-status-success-text\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {tr('ecoDashboard.recentBreaches.noBreachesTitle', 'All parameters in range')}\n </p>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {tr(\n 'ecoDashboard.recentBreaches.noBreachesDescription',\n 'No open threshold breaches.',\n )}\n </p>\n </div>\n ) : (\n <ul className=\"space-y-2.5\">\n {breachRows.map((m) => (\n <li\n key={m.id}\n className=\"flex items-start justify-between gap-3 rounded-xl bg-bg-sunken/50 p-3\"\n >\n <div className=\"flex min-w-0 items-start gap-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"mt-1.5 size-2 shrink-0 rounded-full bg-status-error-text motion-safe:animate-vc-glow-pulse\"\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{m.parameter}</p>\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums\">\n {formatNumber(m.value)} {m.unit}\n </span>{' '}\n · {formatRelativeTime(m.recordedAt)}\n </p>\n </div>\n </div>\n <StatusBadge status=\"BREACH\" />\n </li>\n ))}\n </ul>\n )}\n </GlassCard>\n );\n\n const recentObservationsWidget = (\n <Section\n title={tr('ecoDashboard.recentObservations.title', 'Recent field observations')}\n description={tr(\n 'ecoDashboard.recentObservations.description',\n 'Latest wildlife & biodiversity sightings recorded across your sites.',\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('observations')}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-accent hover:underline\"\n >\n {tr('ecoDashboard.recentObservations.all', 'All observations')}{' '}\n <ArrowRight className=\"h-4 w-4\" />\n </button>\n }\n >\n {observationRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Binoculars className=\"h-10 w-10\" />}\n title={tr('ecoDashboard.recentObservations.noDataTitle', 'No observations yet')}\n description={tr(\n 'ecoDashboard.recentObservations.noDataDescription',\n 'Record your first field observation to build your biodiversity dataset.',\n )}\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {observationRows.map((o) => (\n <GlassCard\n key={o.id}\n treatment=\"plain\"\n className=\"p-4 transition-shadow hover:shadow-float\"\n >\n <div className=\"flex items-start justify-between gap-2\">\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-accent\">\n <Binoculars className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {o.species?.commonName ??\n tr('ecoDashboard.recentObservations.unknownSpecies', 'Unknown species')}\n </p>\n {o.species?.scientificName && (\n <p className=\"truncate text-xs italic text-text-tertiary\">\n {o.species.scientificName}\n </p>\n )}\n </div>\n </div>\n <span className=\"shrink-0 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium tabular-nums text-text-primary\">\n ×{formatNumber(o.count)}\n </span>\n </div>\n <div className=\"mt-3 flex items-center justify-between border-t border-border-seam/70 pt-2.5\">\n <span className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {formatEnumLabel(o.method)}\n </span>\n <span className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(o.observedAt)}\n </span>\n </div>\n </GlassCard>\n ))}\n </div>\n )}\n </Section>\n );\n\n const conservationStatusWidget =\n speciesRows.length > 0 ? (\n <Section\n title={tr('ecoDashboard.conservationStatus.title', 'Conservation status')}\n description={tr(\n 'ecoDashboard.conservationStatus.description',\n 'Distribution of tracked species by IUCN-style status.',\n )}\n >\n <GlassCard treatment=\"plain\" className=\"p-6\">\n <div className=\"space-y-3.5\">\n {(() => {\n const counts = new Map<string, number>();\n for (const s of speciesRows) {\n counts.set(s.conservationStatus, (counts.get(s.conservationStatus) ?? 0) + 1);\n }\n const total = speciesRows.length;\n return [...counts.entries()]\n .sort((a, b) => b[1] - a[1])\n .map(([status, count]) => {\n const pct = (count / total) * 100;\n const tone =\n CONSERVATION_TONE[status as EcoConservationStatus] ??\n 'var(--color-text-tertiary)';\n return (\n <div key={status} className=\"flex items-center gap-3 sm:gap-4\">\n <div className=\"w-36 shrink-0 sm:w-48\">\n <StatusBadge status={status} />\n </div>\n <Meter value={pct} color={tone} className=\"flex-1\" />\n <span className=\"w-9 shrink-0 text-right text-sm font-medium tabular-nums text-text-primary\">\n {count}\n </span>\n <span className=\"hidden w-12 shrink-0 text-right text-xs tabular-nums text-text-tertiary sm:inline\">\n {formatPercent(pct)}\n </span>\n </div>\n );\n });\n })()}\n </div>\n </GlassCard>\n </Section>\n ) : null;\n\n const WIDGET_RENDER: Record<DashboardWidgetId, ReactNode> = {\n kpis: kpisWidget,\n carbonByScope: carbonByScopeWidget,\n recentBreaches: recentBreachesWidget,\n recentObservations: recentObservationsWidget,\n conservationStatus: conservationStatusWidget,\n };\n\n const order = dashboardLayout.layout.widgetOrder;\n const blocks: ReactNode[] = [];\n for (let i = 0; i < order.length; i++) {\n const id = order[i];\n if (id === 'kpis') {\n blocks.push(<div key={id}>{WIDGET_RENDER[id]}</div>);\n continue;\n }\n // Preserve the original side-by-side carbon-by-scope / recent-breaches pairing\n // whenever they remain adjacent in that order (the default, uncustomized case).\n if (id === 'carbonByScope' && order[i + 1] === 'recentBreaches') {\n blocks.push(\n <div key={`${id}-pair`} className=\"grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <div className=\"lg:col-span-2\">{carbonByScopeWidget}</div>\n <div>{recentBreachesWidget}</div>\n </div>\n );\n i += 1;\n continue;\n }\n if (id === 'recentBreaches' && order[i - 1] === 'carbonByScope') {\n // Already rendered as part of the pair above.\n continue;\n }\n const content = WIDGET_RENDER[id];\n if (!content) continue;\n blocks.push(<div key={id}>{content}</div>);\n }\n\n return (\n <PageLayout\n title={tr('ecoDashboard.title', 'EcoImpactHub')}\n titleGradient\n aurora\n description={tr('ecoDashboard.description', 'Welcome back, {{name}} — your environment, conservation & sustainability control room.', { name: greetingName })}\n actions={headerActions}\n >\n <div className=\"space-y-6\">\n {blocks}\n\n {dashboardLayout.layout.notes.length > 0 && (\n <Section\n title={tr('ecoDashboard.customNotes.title', 'Custom notes')}\n description={tr(\n 'ecoDashboard.customNotes.description',\n 'Your personal notes, visible only to you.',\n )}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {dashboardLayout.layout.notes.map((note) => (\n <GlassCard key={note.id} treatment=\"plain\" className=\"p-4\">\n <div className=\"flex items-start gap-2.5\">\n <StickyNote className=\"mt-0.5 h-4 w-4 shrink-0 text-accent\" />\n <div className=\"min-w-0\">\n {note.title && (\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {note.title}\n </p>\n )}\n {note.body && (\n <p className=\"mt-0.5 text-sm text-text-secondary\">{note.body}</p>\n )}\n </div>\n </div>\n </GlassCard>\n ))}\n </div>\n </Section>\n )}\n </div>\n\n <DashboardCustomizerDialog\n open={customizerOpen}\n onOpenChange={setCustomizerOpen}\n layout={dashboardLayout}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-ecoimpacthub",
3
- "version": "2026.827.1",
3
+ "version": "2026.1014.1",
4
4
  "description": "Burdenoff EcoImpactHub Microfrontend",
5
5
  "type": "module",
6
6
  "files": [