@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.
- package/dist/ecoimpacthub/hooks/index.d.ts +1 -0
- package/dist/ecoimpacthub/hooks/index.d.ts.map +1 -1
- package/dist/ecoimpacthub/hooks/index.js +1 -0
- package/dist/ecoimpacthub/hooks/useTr.d.ts +4 -0
- package/dist/ecoimpacthub/hooks/useTr.d.ts.map +1 -0
- package/dist/ecoimpacthub/hooks/useTr.js +20 -0
- package/dist/ecoimpacthub/hooks/useTr.js.map +1 -0
- package/dist/ecoimpacthub/pages/DashboardPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/DashboardPage.js +162 -138
- package/dist/ecoimpacthub/pages/DashboardPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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"}
|
|
@@ -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;
|
|
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
|
|
8
|
-
import { XAxis as
|
|
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
|
|
14
|
-
import { StatusBadge as
|
|
15
|
-
import { MiniBar as
|
|
16
|
-
import { LoadingView as
|
|
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
|
|
25
|
-
import { ArrowRight as _, Binoculars as v, Factory as y, LayoutGrid as
|
|
26
|
-
import { Fragment as
|
|
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
|
-
]),
|
|
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
|
|
44
|
-
let { navigateTo: E, currentUser: D, workspaceId:
|
|
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
|
-
} }),
|
|
48
|
+
} }), M = _e({ pagination: {
|
|
48
49
|
limit: 100,
|
|
49
50
|
offset: 0
|
|
50
|
-
} }),
|
|
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
|
-
}),
|
|
57
|
+
}), Me = me({ pagination: {
|
|
57
58
|
limit: 6,
|
|
58
59
|
offset: 0
|
|
59
|
-
} }),
|
|
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,
|
|
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,
|
|
68
|
-
let e =
|
|
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
|
-
}, [
|
|
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: () =>
|
|
91
|
-
children: [/* @__PURE__ */ b(
|
|
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 (
|
|
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:
|
|
98
|
-
children: /* @__PURE__ */ b(
|
|
98
|
+
actions: K,
|
|
99
|
+
children: /* @__PURE__ */ b(ae, { label: T("ecoDashboard.loading", "Loading your impact overview…") })
|
|
99
100
|
});
|
|
100
|
-
let
|
|
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(
|
|
105
|
-
value:
|
|
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(
|
|
110
|
-
value:
|
|
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: [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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(
|
|
124
|
-
value:
|
|
125
|
-
label: "Species tracked",
|
|
126
|
-
accent:
|
|
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(
|
|
130
|
-
value:
|
|
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:
|
|
143
|
+
children: Pe
|
|
139
144
|
}),
|
|
140
|
-
"
|
|
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:
|
|
151
|
+
children: V
|
|
145
152
|
}),
|
|
146
|
-
"
|
|
153
|
+
" ",
|
|
154
|
+
T("ecoDashboard.kpis.threatened", "threatened")
|
|
147
155
|
]
|
|
148
156
|
})]
|
|
149
157
|
})
|
|
150
158
|
}),
|
|
151
159
|
/* @__PURE__ */ b(s, {
|
|
152
|
-
icon: /* @__PURE__ */ b(
|
|
153
|
-
value:
|
|
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(
|
|
158
|
-
value:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
183
|
-
scope2:
|
|
184
|
-
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(
|
|
194
|
-
value:
|
|
195
|
-
label: "Sensors online",
|
|
196
|
-
accent:
|
|
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(
|
|
200
|
-
value:
|
|
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: [
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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(
|
|
214
|
-
value:
|
|
215
|
-
label: "Open breaches",
|
|
216
|
-
accent:
|
|
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:
|
|
219
|
-
children:
|
|
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: [
|
|
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(
|
|
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: [
|
|
261
|
+
children: [
|
|
262
|
+
T("ecoDashboard.carbonByScope.ledger", "Ledger"),
|
|
263
|
+
" ",
|
|
264
|
+
/* @__PURE__ */ b(_, { className: "h-4 w-4" })
|
|
265
|
+
]
|
|
250
266
|
})]
|
|
251
267
|
})]
|
|
252
|
-
}),
|
|
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(
|
|
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:
|
|
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:
|
|
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(
|
|
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(
|
|
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: [
|
|
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:
|
|
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
|
-
}),
|
|
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:
|
|
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: [
|
|
391
|
+
children: [
|
|
392
|
+
T("ecoDashboard.recentBreaches.monitoring", "Monitoring"),
|
|
393
|
+
" ",
|
|
394
|
+
/* @__PURE__ */ b(_, { className: "h-4 w-4" })
|
|
395
|
+
]
|
|
376
396
|
})]
|
|
377
|
-
}),
|
|
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(
|
|
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:
|
|
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
|
-
|
|
433
|
+
u(e.value),
|
|
414
434
|
" ",
|
|
415
435
|
e.unit
|
|
416
436
|
]
|
|
417
437
|
}),
|
|
418
438
|
" ",
|
|
419
439
|
"· ",
|
|
420
|
-
|
|
440
|
+
d(e.recordedAt)
|
|
421
441
|
]
|
|
422
442
|
})]
|
|
423
443
|
})]
|
|
424
|
-
}), /* @__PURE__ */ b(
|
|
444
|
+
}), /* @__PURE__ */ b(f, { status: "BREACH" })]
|
|
425
445
|
}, e.id))
|
|
426
446
|
})]
|
|
427
447
|
}), Z = {
|
|
428
|
-
kpis:
|
|
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: [
|
|
458
|
+
children: [
|
|
459
|
+
T("ecoDashboard.recentObservations.all", "All observations"),
|
|
460
|
+
" ",
|
|
461
|
+
/* @__PURE__ */ b(_, { className: "h-4 w-4" })
|
|
462
|
+
]
|
|
439
463
|
}),
|
|
440
|
-
children:
|
|
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:
|
|
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: ["×",
|
|
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:
|
|
499
|
+
children: l(e.method)
|
|
476
500
|
}), /* @__PURE__ */ b("span", {
|
|
477
501
|
className: "text-xs text-text-tertiary",
|
|
478
|
-
children:
|
|
502
|
+
children: d(e.observedAt)
|
|
479
503
|
})]
|
|
480
504
|
})]
|
|
481
505
|
}, e.id))
|
|
482
506
|
})
|
|
483
507
|
}),
|
|
484
|
-
conservationStatus:
|
|
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
|
|
495
|
-
let t =
|
|
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 =
|
|
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(
|
|
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:
|
|
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 =
|
|
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:
|
|
551
|
-
actions:
|
|
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: [$,
|
|
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:
|
|
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(
|
|
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:
|
|
580
|
-
onOpenChange:
|
|
581
|
-
layout:
|
|
603
|
+
open: je,
|
|
604
|
+
onOpenChange: k,
|
|
605
|
+
layout: A
|
|
582
606
|
})]
|
|
583
607
|
});
|
|
584
608
|
}
|
|
585
609
|
//#endregion
|
|
586
|
-
export {
|
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,IAAM,IAA0B,IAAI,IAAI;CAAC;CAAW;CAAe;AAAY,CAAC,GAG1E,KAA2D;CAC/D,eAAe;CACf,iBAAiB;CACjB,YAAY;CACZ,YAAY;CACZ,uBAAuB;CACvB,iBAAiB;CACjB,gBAAgB;CAChB,eAAe;AACjB;AAEA,SAAgB,IAAgB;CAC9B,IAAM,IAAK,GAAM,GACX,EAAE,eAAY,gBAAa,mBAAgB,EAAgB,GAC3D,CAAC,IAAgB,KAAqB,GAAS,EAAK,GACpD,IAAkB,GAAmB,GAAa,GAAa,EAAE,GAEjE,IAAQ,GAAY,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;CAAE,EAAE,CAAC,GAC7D,IAAU,GAAc,EAAE,YAAY;EAAE,OAAO;EAAK,QAAQ;CAAE,EAAE,CAAC,GACjE,IAAW,GAAsB,GACjC,IAAS,GAAmB,GAC5B,IAAW,GAAmB;EAAE,cAAc;EAAM,YAAY;GAAE,OAAO;GAAG,QAAQ;EAAE;CAAE,CAAC,GACzF,KAAe,GAAmB,EAAE,YAAY;EAAE,OAAO;EAAG,QAAQ;CAAE,EAAE,CAAC,GACzE,KAAU,GAAc,GAExB,IAAW,EAAM,MAAM,SAAS,CAAC,GACjC,IAAc,EAAQ,MAAM,SAAS,CAAC,GACtC,IAAc,EAAS,MAAM,SAAS,CAAC,GACvC,IAAa,GAAQ,MAAM,SAAS,CAAC,GAErC,IAAc,EAAS,QAAQ,MAAM,EAAE,WAAW,QAAQ,EAAE,QAC5D,KAAgB,EAAY,QAAQ,MAAM,EAAE,UAAU,EAAE,QACxD,IAAa,EAAY,QAAQ,MACrC;EAAC;EAAc;EAAc;CAAuB,EAAE,SAAS,EAAE,kBAAkB,CACrF,EAAE,QACI,IAAiB,EAAY,QAAQ,MAAM,EAAwB,IAAI,EAAE,MAAM,CAAC,EAAE,QAClF,IAAgB,EAAW,QAAQ,MAAM,EAAE,WAAW,QAAQ,EAAE,QAChE,IAAiB,EAAW,QAAQ,MACxC;EAAC;EAAW;EAAY;CAAa,EAAE,SAAS,EAAE,MAAM,CAC1D,EAAE,QAEI,IAAa,SAAwE;EACzF,IAAM,IAAI,EAAO;EAEjB,OADK,IACE;GACL;IAAE,OAAO;IAAW,OAAO;IAAW,IAAI,EAAE;GAAO;GACnD;IAAE,OAAO;IAAW,OAAO;IAAW,IAAI,EAAE;GAAO;GACnD;IAAE,OAAO;IAAW,OAAO;IAAW,IAAI,EAAE;GAAO;EACrD,IALe,CAAC;CAMlB,GAAG,CAAC,EAAO,IAAI,CAAC,GAEV,KACJ,EAAM,aAAa,EAAQ,aAAa,EAAS,aAAa,EAAO,WAEjE,IACJ,kBAAC,GAAD;EAAQ,MAAK;EAAS,SAAQ;EAAU,MAAK;EAAK,eAAe,EAAkB,EAAI;YAAvF,CACE,kBAAC,IAAD,EAAY,WAAU,qBAAsB,CAAA,GAC3C,EAAG,0BAA0B,qBAAqB,CAC7C;;CAGV,IAAI,IACF,OACE,kBAAC,GAAD;EAAY,OAAO,EAAG,sBAAsB,cAAc;EAAG,eAAA;EAAc,QAAA;EAAO,SAAS;YACzF,kBAAC,IAAD,EAAa,OAAO,EAAG,wBAAwB,+BAA+B,EAAI,CAAA;CACxE,CAAA;CAIhB,IAAM,KAAe,GAAa,aAAa,GAAa,QAAQ,SAC9D,IAAa,EAAS,MAAM,SAAS,CAAC,GACtC,IAAkB,GAAa,MAAM,SAAS,CAAC,GAE/C,KACJ,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAQ,WAAU,UAAW,CAAA;IACnC,OACE,EAAM,MAAM,UAAU,UAAU,GAAG,EAAS,OAAO,KAAK,EAAa,EAAS,MAAM;IAEtF,OAAO,EAAG,oCAAoC,iBAAiB;IAC/D,QACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAO,EAAS,SAAU,IAAc,EAAS,SAAU,MAAM;MACjE,QAAQ;MACR,cAAa;KACd,CAAA,GACD,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAoC;OAAkB,CAAA;OAAE;OACvE,EAAG,4BAA4B,QAAQ;MACvC;OACA;;GAER,CAAA;GACD,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAU,WAAU,UAAW,CAAA;IACrC,OACE,EAAQ,MAAM,UAAU,UACpB,GAAG,EAAY,OAAO,KACtB,EAAa,EAAY,MAAM;IAErC,OAAO,EAAG,oCAAoC,iBAAiB;IAC/D,QAAQ,IAAa,IAAI,YAAY,KAAA;IACrC,QACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAO,EAAY,SAAU,IAAa,EAAY,SAAU,MAAM;MACtE,QAAQ;MACR,cAAa;KACd,CAAA,GACD,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAoC;OAAoB,CAAA;OAAE;OACzE,EAAG,8BAA8B,UAAU;OAAE;OAAG;OACjD,kBAAC,QAAD;QAAM,WAAU;kBAAoC;OAAiB,CAAA;OAAE;OACtE,EAAG,gCAAgC,YAAY;MAC/C;OACA;;GAER,CAAA;GACD,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAO,WAAU,UAAW,CAAA;IAClC,OAAO,EAAa,CAAc;IAClC,OAAO,EAAG,2CAA2C,yBAAyB;IAC9E,QACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAO,EAAY,SAAU,IAAiB,EAAY,SAAU,MAAM;MAC1E,QAAQ;MACR,cAAa;KACd,CAAA,GACD,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACE,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAa,EAAY,MAAM;OAC5B,CAAA;OAAE;OACP,EAAG,2BAA2B,OAAO;MACrC;OACA;;GAER,CAAA;GACD,kBAAC,GAAD;IACE,MAAM,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA;IACpC,OAAO,EAAO,OAAO,EAAW,EAAO,KAAK,KAAK,IAAI;IACrD,OAAO,EAAG,oCAAoC,iBAAiB;IAC/D,UAAA;IACA,QACE,EAAO,QAAQ,EAAO,KAAK,QAAQ,IACjC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,IAAD;MACE,QAAQ,EAAO,KAAK;MACpB,QAAQ,EAAO,KAAK;MACpB,QAAQ,EAAO,KAAK;MACpB,QAAQ;KACT,CAAA,GACD,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,gCAAgC,uBAAuB;KAC1D,CAAA,CACA;SACH,KAAA;GAEP,CAAA;GACD,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAO,WAAU,UAAW,CAAA;IAClC,OAAO,EAAa,CAAa;IACjC,OAAO,EAAG,mCAAmC,gBAAgB;IAC7D,QAAQ,IAAiB,IAAI,YAAY;IACzC,QACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,OAAO,EAAW,SAAU,IAAgB,EAAW,SAAU,MAAM;MACvE,QAAQ;MACR,cAAa;KACd,CAAA,GACD,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAoC;OAAqB,CAAA;OAAE;OAC1E,EAAG,mCAAmC,gBAAgB;MACtD;OACA;;GAER,CAAA;GACD,kBAAC,GAAD;IACE,MAAM,kBAAC,IAAD,EAAe,WAAU,UAAW,CAAA;IAC1C,OACE,EAAS,MAAM,UAAU,UACrB,GAAG,EAAW,OAAO,KACrB,EAAa,EAAW,MAAM;IAEpC,OAAO,EAAG,kCAAkC,eAAe;IAC3D,QAAQ,EAAW,SAAS,IAAI,UAAU;IAC1C,QACE,kBAAC,KAAD;KACE,WACE,EAAW,SAAS,IAChB,+CACA;eAGL,EAAW,SAAS,IACjB,EAAG,oCAAoC,iBAAiB,IACxD,EAAG,0CAA0C,yBAAyB;IACzE,CAAA;GAEN,CAAA;EACE;KAGD,IACJ,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAG,oCAAoC,2BAA2B;GACjE,CAAA,GACJ,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,0CACA,gDACF;GACC,CAAA,CACA,EAAA,CAAA,GACL,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAO,QAAQ,EAAO,KAAK,QAAQ,KAClC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAW,EAAO,KAAK,KAAK;KAC5B,CAAA,GACH,kBAAC,KAAD;MAAG,WAAU;gBACV,EAAG,oCAAoC,OAAO;KAC9C,CAAA,CACA;QAEP,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,QAAQ;KAClC,WAAU;eAHZ;MAKG,EAAG,qCAAqC,QAAQ;MAAE;MAAC,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;KAC/E;MACL;KACF;MACJ,EAAW,OAAO,MAAM,EAAE,OAAO,CAAC,IACjC,kBAAC,GAAD;GACE,cAAa;GACb,MAAM,kBAAC,GAAD,EAAS,WAAU,YAAa,CAAA;GACtC,OAAO,EAAG,0CAA0C,uBAAuB;GAC3E,aAAa,EACX,gDACA,4EACF;EACD,CAAA,IAED,kBAAA,IAAA,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IAAqB,OAAM;IAAO,QAAO;cACvC,kBAAC,IAAD;KAAU,MAAM;KAAY,QAAQ;MAAE,KAAK;MAAI,OAAO;MAAG,MAAM;MAAG,QAAQ;KAAE;eAA5E;MACE,kBAAC,QAAD,EAAA,UACG,EAAW,KAAK,MACf,kBAAC,kBAAD;OAEE,IAAI,gBAAgB,EAAE;OACtB,IAAG;OACH,IAAG;OACH,IAAG;OACH,IAAG;iBANL,CAQE,kBAAC,QAAD;QAAM,QAAO;QAAK,OAAO;SAAE,WAAW,EAAU,EAAE;SAAQ,aAAa;QAAE;OAAI,CAAA,GAC7E,kBAAC,QAAD;QACE,QAAO;QACP,OAAO;SAAE,WAAW,EAAU,EAAE;SAAQ,aAAa;QAAK;OAC3D,CAAA,CACa;SAZT,EAAE,KAYO,CACjB,EACG,CAAA;MACN,kBAAC,GAAD;OACE,UAAU;OACV,QAAO;OACP,eAAe;OACf,iBAAgB;MACjB,CAAA;MACD,kBAAC,IAAD;OACE,SAAQ;OACR,MAAM;QAAE,MAAM;QAA+B,UAAU;OAAG;OAC1D,UAAU,EAAE,QAAQ,2BAA2B;OAC/C,UAAU;OACV,IAAI;MACL,CAAA;MACD,kBAAC,IAAD;OACE,MAAM;QAAE,MAAM;QAA8B,UAAU;OAAG;OACzD,UAAU;OACV,UAAU;OACV,OAAO;OACP,gBAAgB,MAAe,KAAK,MAAO,GAAG,KAAK,MAAM,IAAI,GAAI,EAAE,KAAK,GAAG;MAC5E,CAAA;MACD,kBAAC,GAAD;OACE,QAAQ;QAAE,MAAM;QAA0B,SAAS;OAAI;OACvD,cAAc;QACZ,YAAY;QACZ,QAAQ;QACR,cAAc;QACd,OAAO;OACT;OACA,YAAY,MAAkB,CAC5B,EAAW,CAAK,GAChB,EAAG,wCAAwC,WAAW,CACxD;MACD,CAAA;MACD,kBAAC,IAAD;OAAK,SAAQ;OAAK,QAAQ;QAAC;QAAG;QAAG;QAAG;OAAC;OAAG,YAAY;iBAApD,CACG,EAAW,KAAK,MACf,kBAAC,GAAD,EAAoB,MAAM,qBAAqB,EAAE,MAAM,GAAK,GAAjD,EAAE,KAA+C,CAC7D,GACD,kBAAC,GAAD;QACE,SAAQ;QACR,UAAS;QACT,YAAY,MAAe,IAAI,IAAI,EAAW,CAAC,IAAI;QACnD,OAAO;SAAE,MAAM;SAA+B,UAAU;SAAI,YAAY;QAAI;OAC7E,CAAA,CACE;;KACG;;GACS,CAAA;EAClB,CAAA,GACL,kBAAC,IAAD;GAAa,MAAM;GAAY,WAAU;EAAyC,CAAA,CAClF,EAAA,CAAA,CAEK;KAGP,IACJ,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,qCAAqC,iBAAiB;IACxD,CAAA,GACH,EAAW,SAAS,KACnB,kBAAC,QAAD;KAAM,WAAU;eACb,EAAS,MAAM,UAAU,UAAU,GAAG,EAAW,OAAO,KAAK,EAAW;IACrE,CAAA,CAEL;OACL,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,YAAY;IACtC,WAAU;cAHZ;KAKG,EAAG,0CAA0C,YAAY;KAAG;KAC7D,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;IAC3B;KACL;MACJ,EAAW,WAAW,IACrB,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,QAAD;KAAM,WAAU;eACd,kBAAC,IAAD,EAAa,WAAU,mCAAoC,CAAA;IACvD,CAAA;IACN,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,+CAA+C,yBAAyB;IAC3E,CAAA;IACH,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,qDACA,6BACF;IACC,CAAA;GACA;OAEL,kBAAC,MAAD;GAAI,WAAU;aACX,EAAW,KAAK,MACf,kBAAC,MAAD;IAEE,WAAU;cAFZ,CAIE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAU;KACX,CAAA,GACD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAkD,EAAE;MAAa,CAAA,GAC9E,kBAAC,KAAD;OAAG,WAAU;iBAAb;QACE,kBAAC,QAAD;SAAM,WAAU;mBAAhB;UACG,EAAa,EAAE,KAAK;UAAE;UAAE,EAAE;SACvB;;QAAE;QAAI;QACT,EAAmB,EAAE,UAAU;OACjC;QACA;OACF;QACL,kBAAC,GAAD,EAAa,QAAO,SAAU,CAAA,CAC5B;MAnBG,EAAE,EAmBL,CACL;EACC,CAAA,CAEG;KAwHP,IAAsD;EAC1D,MAAM;EACN,eAAe;EACf,gBAAgB;EAChB,oBAxHA,kBAAC,GAAD;GACE,OAAO,EAAG,yCAAyC,2BAA2B;GAC9E,aAAa,EACX,+CACA,sEACF;GACA,QACE,kBAAC,UAAD;IACE,MAAK;IACL,eAAe,EAAW,cAAc;IACxC,WAAU;cAHZ;KAKG,EAAG,uCAAuC,kBAAkB;KAAG;KAChE,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;IAC3B;;aAGT,EAAgB,WAAW,IAC1B,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAY,WAAU,YAAa,CAAA;IACzC,OAAO,EAAG,+CAA+C,qBAAqB;IAC9E,aAAa,EACX,qDACA,yEACF;GACD,CAAA,IAED,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAgB,KAAK,MACpB,kBAAC,GAAD;KAEE,WAAU;KACV,WAAU;eAHZ,CAKE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBACd,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;OAC7B,CAAA,GACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAE,SAAS,cACV,EAAG,kDAAkD,iBAAiB;QACvE,CAAA,GACF,EAAE,SAAS,kBACV,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAE,QAAQ;QACV,CAAA,CAEF;SACF;UACL,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CAAqI,KACjI,EAAa,EAAE,KAAK,CAClB;QACH;SACL,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAgB,EAAE,MAAM;MACrB,CAAA,GACN,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAmB,EAAE,UAAU;MAC5B,CAAA,CACH;OACI;OAjCJ,EAAE,EAiCE,CACZ;GACE,CAAA;EAEA,CAoDW;EACpB,oBAjDA,EAAY,SAAS,IACnB,kBAAC,GAAD;GACE,OAAO,EAAG,yCAAyC,qBAAqB;GACxE,aAAa,EACX,+CACA,uDACF;aAEA,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cACrC,kBAAC,OAAD;KAAK,WAAU;sBACL;MACN,IAAM,oBAAS,IAAI,IAAoB;MACvC,KAAK,IAAM,KAAK,GACd,EAAO,IAAI,EAAE,qBAAqB,EAAO,IAAI,EAAE,kBAAkB,KAAK,KAAK,CAAC;MAE9E,IAAM,IAAQ,EAAY;MAC1B,OAAO,CAAC,GAAG,EAAO,QAAQ,CAAC,EACxB,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,EAC1B,KAAK,CAAC,GAAQ,OAAW;OACxB,IAAM,IAAO,IAAQ,IAAS,KACxB,IACJ,GAAkB,MAClB;OACF,OACE,kBAAC,OAAD;QAAkB,WAAU;kBAA5B;SACE,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,GAAD,EAAqB,UAAS,CAAA;SAC3B,CAAA;SACL,kBAAC,IAAD;UAAO,OAAO;UAAK,OAAO;UAAM,WAAU;SAAU,CAAA;SACpD,kBAAC,QAAD;UAAM,WAAU;oBACb;SACG,CAAA;SACN,kBAAC,QAAD;UAAM,WAAU;oBACb,GAAc,CAAG;SACd,CAAA;QACH;UAXK,CAWL;MAET,CAAC;KACL,GAAG;IACA,CAAA;GACI,CAAA;EACJ,CAAA,IACP;CAQN,GAEM,IAAQ,EAAgB,OAAO,aAC/B,IAAsB,CAAC;CAC7B,KAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAAK;EACrC,IAAM,IAAK,EAAM;EACjB,IAAI,MAAO,QAAQ;GACjB,EAAO,KAAK,kBAAC,OAAD,EAAA,UAAe,EAAc,GAAS,GAA5B,CAA4B,CAAC;GACnD;EACF;EAGA,IAAI,MAAO,mBAAmB,EAAM,IAAI,OAAO,kBAAkB;GAO/D,AANA,EAAO,KACL,kBAAC,OAAD;IAAwB,WAAU;cAAlC,CACE,kBAAC,OAAD;KAAK,WAAU;eAAiB;IAAyB,CAAA,GACzD,kBAAC,OAAD,EAAA,UAAM,EAA0B,CAAA,CAC7B;MAHK,GAAG,EAAG,MAGX,CACP,GACA,KAAK;GACL;EACF;EACA,IAAI,MAAO,oBAAoB,EAAM,IAAI,OAAO,iBAE9C;EAEF,IAAM,IAAU,EAAc;EACzB,KACL,EAAO,KAAK,kBAAC,OAAD,EAAA,UAAe,EAAa,GAAlB,CAAkB,CAAC;CAC3C;CAEA,OACE,kBAAC,GAAD;EACE,OAAO,EAAG,sBAAsB,cAAc;EAC9C,eAAA;EACA,QAAA;EACA,aAAa,EAAG,4BAA4B,0FAA0F,EAAE,MAAM,GAAa,CAAC;EAC5J,SAAS;YALX,CAOE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,GAEA,EAAgB,OAAO,MAAM,SAAS,KACrC,kBAAC,GAAD;IACE,OAAO,EAAG,kCAAkC,cAAc;IAC1D,aAAa,EACX,wCACA,2CACF;cAEA,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAgB,OAAO,MAAM,KAAK,MACjC,kBAAC,GAAD;MAAyB,WAAU;MAAQ,WAAU;gBACnD,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,IAAD,EAAY,WAAU,sCAAuC,CAAA,GAC7D,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,EAAK,SACJ,kBAAC,KAAD;SAAG,WAAU;mBACV,EAAK;QACL,CAAA,GAEJ,EAAK,QACJ,kBAAC,KAAD;SAAG,WAAU;mBAAsC,EAAK;QAAQ,CAAA,CAE/D;SACF;;KACI,GAdK,EAAK,EAcV,CACZ;IACE,CAAA;GACE,CAAA,CAER;MAEL,kBAAC,IAAD;GACE,MAAM;GACN,cAAc;GACd,QAAQ;EACT,CAAA,CACS;;AAEhB"}
|