@burdenoff/microfe-ecoimpacthub 2026.830.1 → 2026.1030.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/components/DataTable.d.ts +3 -1
- package/dist/ecoimpacthub/components/DataTable.d.ts.map +1 -1
- package/dist/ecoimpacthub/components/DataTable.js +15 -15
- package/dist/ecoimpacthub/components/DataTable.js.map +1 -1
- package/dist/ecoimpacthub/components/StatusBadge.d.ts.map +1 -1
- package/dist/ecoimpacthub/components/StatusBadge.js +6 -0
- package/dist/ecoimpacthub/components/StatusBadge.js.map +1 -1
- package/dist/ecoimpacthub/components/ai/AiAnomalyPanel.d.ts +12 -0
- package/dist/ecoimpacthub/components/ai/AiAnomalyPanel.d.ts.map +1 -0
- package/dist/ecoimpacthub/components/ai/AiAnomalyPanel.js +213 -0
- package/dist/ecoimpacthub/components/ai/AiAnomalyPanel.js.map +1 -0
- package/dist/ecoimpacthub/components/ai/AiShared.d.ts +22 -0
- package/dist/ecoimpacthub/components/ai/AiShared.d.ts.map +1 -0
- package/dist/ecoimpacthub/components/ai/AiShared.js +70 -0
- package/dist/ecoimpacthub/components/ai/AiShared.js.map +1 -0
- package/dist/ecoimpacthub/components/ai/AiThresholdSuggest.d.ts +11 -0
- package/dist/ecoimpacthub/components/ai/AiThresholdSuggest.d.ts.map +1 -0
- package/dist/ecoimpacthub/components/ai/AiThresholdSuggest.js +93 -0
- package/dist/ecoimpacthub/components/ai/AiThresholdSuggest.js.map +1 -0
- package/dist/ecoimpacthub/components/ai/AiTriageDialog.d.ts +13 -0
- package/dist/ecoimpacthub/components/ai/AiTriageDialog.d.ts.map +1 -0
- package/dist/ecoimpacthub/components/ai/AiTriageDialog.js +237 -0
- package/dist/ecoimpacthub/components/ai/AiTriageDialog.js.map +1 -0
- package/dist/ecoimpacthub/components/ai/index.d.ts +9 -0
- package/dist/ecoimpacthub/components/ai/index.d.ts.map +1 -0
- package/dist/ecoimpacthub/components/ai/index.js +4 -0
- package/dist/ecoimpacthub/hooks/ecoTransport.d.ts +18 -0
- package/dist/ecoimpacthub/hooks/ecoTransport.d.ts.map +1 -0
- package/dist/ecoimpacthub/hooks/ecoTransport.js +29 -0
- package/dist/ecoimpacthub/hooks/ecoTransport.js.map +1 -0
- 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/useEcoAiMonitoring.d.ts +20 -0
- package/dist/ecoimpacthub/hooks/useEcoAiMonitoring.d.ts.map +1 -0
- package/dist/ecoimpacthub/hooks/useEcoAiMonitoring.js +55 -0
- package/dist/ecoimpacthub/hooks/useEcoAiMonitoring.js.map +1 -0
- package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.d.ts +16 -1
- package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.d.ts.map +1 -1
- package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.js +368 -339
- package/dist/ecoimpacthub/hooks/useEcoImpactHubApi.js.map +1 -1
- package/dist/ecoimpacthub/pages/carbon/AiQuotaBanner.d.ts +9 -0
- package/dist/ecoimpacthub/pages/carbon/AiQuotaBanner.d.ts.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/AiQuotaBanner.js +31 -0
- package/dist/ecoimpacthub/pages/carbon/AiQuotaBanner.js.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/CarbonPage.d.ts +3 -0
- package/dist/ecoimpacthub/pages/carbon/CarbonPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/carbon/CarbonPage.js +503 -441
- package/dist/ecoimpacthub/pages/carbon/CarbonPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/carbon/DraftReviewPanel.d.ts +20 -0
- package/dist/ecoimpacthub/pages/carbon/DraftReviewPanel.d.ts.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/DraftReviewPanel.js +260 -0
- package/dist/ecoimpacthub/pages/carbon/DraftReviewPanel.js.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/DraftWithAiDialog.d.ts +21 -0
- package/dist/ecoimpacthub/pages/carbon/DraftWithAiDialog.d.ts.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/DraftWithAiDialog.js +338 -0
- package/dist/ecoimpacthub/pages/carbon/DraftWithAiDialog.js.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/SuggestFactorPanel.d.ts +17 -0
- package/dist/ecoimpacthub/pages/carbon/SuggestFactorPanel.d.ts.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/SuggestFactorPanel.js +162 -0
- package/dist/ecoimpacthub/pages/carbon/SuggestFactorPanel.js.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/aiDrafts.d.ts +93 -0
- package/dist/ecoimpacthub/pages/carbon/aiDrafts.d.ts.map +1 -0
- package/dist/ecoimpacthub/pages/carbon/aiDrafts.js +171 -0
- package/dist/ecoimpacthub/pages/carbon/aiDrafts.js.map +1 -0
- package/dist/ecoimpacthub/pages/monitoring/MonitoringPage.d.ts +5 -0
- package/dist/ecoimpacthub/pages/monitoring/MonitoringPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/monitoring/MonitoringPage.js +407 -374
- package/dist/ecoimpacthub/pages/monitoring/MonitoringPage.js.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.d.ts +5 -0
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.d.ts.map +1 -1
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js +381 -331
- package/dist/ecoimpacthub/pages/sites/SiteDetailPage.js.map +1 -1
- package/dist/ecoimpacthub/types/index.d.ts +180 -0
- package/dist/ecoimpacthub/types/index.d.ts.map +1 -1
- package/dist/ecoimpacthub/utils/aiMonitoring.d.ts +55 -0
- package/dist/ecoimpacthub/utils/aiMonitoring.d.ts.map +1 -0
- package/dist/ecoimpacthub/utils/aiMonitoring.js +100 -0
- package/dist/ecoimpacthub/utils/aiMonitoring.js.map +1 -0
- package/dist/ecoimpacthub/utils/graphqlErrors.d.ts +44 -0
- package/dist/ecoimpacthub/utils/graphqlErrors.d.ts.map +1 -0
- package/dist/ecoimpacthub/utils/graphqlErrors.js +30 -0
- package/dist/ecoimpacthub/utils/graphqlErrors.js.map +1 -0
- package/dist/generated/global-types.d.ts +9573 -578
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +109261 -27364
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -16,6 +16,8 @@ interface DataTableProps<T> {
|
|
|
16
16
|
rows: T[];
|
|
17
17
|
rowKey: (row: T) => string;
|
|
18
18
|
onRowClick?: (row: T) => void;
|
|
19
|
+
/** Extra classes for a row (e.g. to highlight readings an AI verdict cites). */
|
|
20
|
+
rowClassName?: (row: T) => string | false | null | undefined;
|
|
19
21
|
className?: string;
|
|
20
22
|
/** Default sort: a column key + direction. */
|
|
21
23
|
defaultSort?: {
|
|
@@ -23,6 +25,6 @@ interface DataTableProps<T> {
|
|
|
23
25
|
dir: 'asc' | 'desc';
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
|
-
export declare function DataTable<T>({ columns, rows, rowKey, onRowClick, className, defaultSort, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function DataTable<T>({ columns, rows, rowKey, onRowClick, rowClassName, className, defaultSort, }: DataTableProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
27
29
|
export {};
|
|
28
30
|
//# sourceMappingURL=DataTable.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/components/DataTable.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAK1D,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;CACzC;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAC;CACpD;AAQD,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAC3B,OAAO,EACP,IAAI,EACJ,MAAM,EACN,UAAU,EACV,SAAS,EACT,WAAW,GACZ,EAAE,cAAc,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/components/DataTable.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAK1D,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,+EAA+E;IAC/E,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,CAAC;CACzC;AAED,UAAU,cAAc,CAAC,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IAC9B,gFAAgF;IAChF,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,SAAS,CAAC;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,CAAC;CACpD;AAQD,wBAAgB,SAAS,CAAC,CAAC,EAAE,EAC3B,OAAO,EACP,IAAI,EACJ,MAAM,EACN,UAAU,EACV,YAAY,EACZ,SAAS,EACT,WAAW,GACZ,EAAE,cAAc,CAAC,CAAC,CAAC,2CAsGnB"}
|
|
@@ -9,22 +9,22 @@ var l = {
|
|
|
9
9
|
right: "text-right",
|
|
10
10
|
center: "text-center"
|
|
11
11
|
};
|
|
12
|
-
function u({ columns: u, rows: d, rowKey: f, onRowClick: p,
|
|
13
|
-
let [
|
|
14
|
-
if (!
|
|
15
|
-
let e = u.find((e) => e.key ===
|
|
12
|
+
function u({ columns: u, rows: d, rowKey: f, onRowClick: p, rowClassName: m, className: h, defaultSort: g }) {
|
|
13
|
+
let [_, v] = n(g ?? null), y = t(() => {
|
|
14
|
+
if (!_) return d;
|
|
15
|
+
let e = u.find((e) => e.key === _.key);
|
|
16
16
|
if (!e?.sortValue) return d;
|
|
17
|
-
let t =
|
|
17
|
+
let t = _.dir === "asc" ? 1 : -1;
|
|
18
18
|
return [...d].sort((n, r) => {
|
|
19
19
|
let i = e.sortValue(n), a = e.sortValue(r);
|
|
20
20
|
return i < a ? -1 * t : i > a ? 1 * t : 0;
|
|
21
21
|
});
|
|
22
22
|
}, [
|
|
23
23
|
d,
|
|
24
|
-
|
|
24
|
+
_,
|
|
25
25
|
u
|
|
26
|
-
]),
|
|
27
|
-
|
|
26
|
+
]), b = (e) => {
|
|
27
|
+
v((t) => t?.key === e ? {
|
|
28
28
|
key: e,
|
|
29
29
|
dir: t.dir === "asc" ? "desc" : "asc"
|
|
30
30
|
} : {
|
|
@@ -34,7 +34,7 @@ function u({ columns: u, rows: d, rowKey: f, onRowClick: p, className: m, defaul
|
|
|
34
34
|
};
|
|
35
35
|
return /* @__PURE__ */ o(c, {
|
|
36
36
|
treatment: "plain",
|
|
37
|
-
className: e("overflow-hidden",
|
|
37
|
+
className: e("overflow-hidden", h),
|
|
38
38
|
children: /* @__PURE__ */ o("div", {
|
|
39
39
|
className: "overflow-x-auto",
|
|
40
40
|
children: /* @__PURE__ */ s("table", {
|
|
@@ -42,20 +42,20 @@ function u({ columns: u, rows: d, rowKey: f, onRowClick: p, className: m, defaul
|
|
|
42
42
|
children: [/* @__PURE__ */ o("thead", { children: /* @__PURE__ */ o("tr", {
|
|
43
43
|
className: "border-b border-border-seam",
|
|
44
44
|
children: u.map((t) => {
|
|
45
|
-
let n = !!t.sortValue, c =
|
|
45
|
+
let n = !!t.sortValue, c = _?.key === t.key;
|
|
46
46
|
return /* @__PURE__ */ o("th", {
|
|
47
|
-
onClick: n ? () =>
|
|
48
|
-
"aria-sort": c ?
|
|
47
|
+
onClick: n ? () => b(t.key) : void 0,
|
|
48
|
+
"aria-sort": c ? _?.dir === "asc" ? "ascending" : "descending" : void 0,
|
|
49
49
|
className: e("px-4 py-3 font-medium text-text-secondary whitespace-nowrap", l[t.align ?? "left"], t.hideOnMobile && "hidden md:table-cell", n && "cursor-pointer select-none hover:text-text-primary"),
|
|
50
50
|
children: /* @__PURE__ */ s("span", {
|
|
51
51
|
className: e("inline-flex items-center gap-1", t.align === "right" && "flex-row-reverse"),
|
|
52
|
-
children: [t.header, n && (c ?
|
|
52
|
+
children: [t.header, n && (c ? _?.dir === "asc" ? /* @__PURE__ */ o(i, { className: "h-3.5 w-3.5 text-accent" }) : /* @__PURE__ */ o(r, { className: "h-3.5 w-3.5 text-accent" }) : /* @__PURE__ */ o(a, { className: "h-3.5 w-3.5 opacity-40" }))]
|
|
53
53
|
})
|
|
54
54
|
}, t.key);
|
|
55
55
|
})
|
|
56
|
-
}) }), /* @__PURE__ */ o("tbody", { children:
|
|
56
|
+
}) }), /* @__PURE__ */ o("tbody", { children: y.map((t) => /* @__PURE__ */ o("tr", {
|
|
57
57
|
onClick: p ? () => p(t) : void 0,
|
|
58
|
-
className: e("group border-b border-border-seam/60 last:border-0", p && "cursor-pointer transition-colors hover:bg-bg-sunken/60"),
|
|
58
|
+
className: e("group border-b border-border-seam/60 last:border-0", p && "cursor-pointer transition-colors hover:bg-bg-sunken/60", m?.(t)),
|
|
59
59
|
children: u.map((n) => /* @__PURE__ */ o("td", {
|
|
60
60
|
className: e("px-4 py-3 text-text-primary align-middle", l[n.align ?? "left"], n.hideOnMobile && "hidden md:table-cell", n.className),
|
|
61
61
|
children: n.render(t)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/ecoimpacthub/components/DataTable.tsx"],"sourcesContent":["/**\n * DataTable — a lightweight, semantic-token table for list pages.\n * @module ecoimpacthub/components\n *\n * Deliberately minimal (no external table lib). Renders a responsive,\n * horizontally-scrollable table wrapped in a GlassCard. Columns declare a\n * header, a render fn, optional alignment, and optionally a `sortValue` to make\n * the column click-sortable.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\nexport interface Column<T> {\n key: string;\n header: ReactNode;\n render: (row: T) => ReactNode;\n align?: 'left' | 'right' | 'center';\n className?: string;\n /** Hide below the `md` breakpoint for a mobile-friendly layout. */\n hideOnMobile?: boolean;\n /** Provide to make this column click-sortable (returns a comparable value). */\n sortValue?: (row: T) => string | number;\n}\n\ninterface DataTableProps<T> {\n columns: Column<T>[];\n rows: T[];\n rowKey: (row: T) => string;\n onRowClick?: (row: T) => void;\n className?: string;\n /** Default sort: a column key + direction. */\n defaultSort?: { key: string; dir: 'asc' | 'desc' };\n}\n\nconst alignClass = {\n left: 'text-left',\n right: 'text-right',\n center: 'text-center',\n} as const;\n\nexport function DataTable<T>({\n columns,\n rows,\n rowKey,\n onRowClick,\n className,\n defaultSort,\n}: DataTableProps<T>) {\n const [sort, setSort] = useState<{ key: string; dir: 'asc' | 'desc' } | null>(\n defaultSort ?? null\n );\n\n const sortedRows = useMemo(() => {\n if (!sort) return rows;\n const col = columns.find((c) => c.key === sort.key);\n if (!col?.sortValue) return rows;\n const dir = sort.dir === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const va = col.sortValue!(a);\n const vb = col.sortValue!(b);\n if (va < vb) return -1 * dir;\n if (va > vb) return 1 * dir;\n return 0;\n });\n }, [rows, sort, columns]);\n\n const toggleSort = (key: string) => {\n setSort((prev) =>\n prev?.key === key ? { key, dir: prev.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'asc' }\n );\n };\n\n return (\n <GlassCard treatment=\"plain\" className={cn('overflow-hidden', className)}>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam\">\n {columns.map((col) => {\n const sortable = Boolean(col.sortValue);\n const active = sort?.key === col.key;\n return (\n <th\n key={col.key}\n onClick={sortable ? () => toggleSort(col.key) : undefined}\n aria-sort={\n active ? (sort?.dir === 'asc' ? 'ascending' : 'descending') : undefined\n }\n className={cn(\n 'px-4 py-3 font-medium text-text-secondary whitespace-nowrap',\n alignClass[col.align ?? 'left'],\n col.hideOnMobile && 'hidden md:table-cell',\n sortable && 'cursor-pointer select-none hover:text-text-primary'\n )}\n >\n <span\n className={cn(\n 'inline-flex items-center gap-1',\n col.align === 'right' && 'flex-row-reverse'\n )}\n >\n {col.header}\n {sortable &&\n (active ? (\n sort?.dir === 'asc' ? (\n <ChevronUp className=\"h-3.5 w-3.5 text-accent\" />\n ) : (\n <ChevronDown className=\"h-3.5 w-3.5 text-accent\" />\n )\n ) : (\n <ChevronsUpDown className=\"h-3.5 w-3.5 opacity-40\" />\n ))}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {sortedRows.map((row) => (\n <tr\n key={rowKey(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={cn(\n 'group border-b border-border-seam/60 last:border-0',\n onRowClick && 'cursor-pointer transition-colors hover:bg-bg-sunken/60'\n )}\n >\n {columns.map((col) => (\n <td\n key={col.key}\n className={cn(\n 'px-4 py-3 text-text-primary align-middle',\n alignClass[col.align ?? 'left'],\n col.hideOnMobile && 'hidden md:table-cell',\n col.className\n )}\n >\n {col.render(row)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/ecoimpacthub/components/DataTable.tsx"],"sourcesContent":["/**\n * DataTable — a lightweight, semantic-token table for list pages.\n * @module ecoimpacthub/components\n *\n * Deliberately minimal (no external table lib). Renders a responsive,\n * horizontally-scrollable table wrapped in a GlassCard. Columns declare a\n * header, a render fn, optional alignment, and optionally a `sortValue` to make\n * the column click-sortable.\n */\n\nimport { useMemo, useState, type ReactNode } from 'react';\nimport { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\nexport interface Column<T> {\n key: string;\n header: ReactNode;\n render: (row: T) => ReactNode;\n align?: 'left' | 'right' | 'center';\n className?: string;\n /** Hide below the `md` breakpoint for a mobile-friendly layout. */\n hideOnMobile?: boolean;\n /** Provide to make this column click-sortable (returns a comparable value). */\n sortValue?: (row: T) => string | number;\n}\n\ninterface DataTableProps<T> {\n columns: Column<T>[];\n rows: T[];\n rowKey: (row: T) => string;\n onRowClick?: (row: T) => void;\n /** Extra classes for a row (e.g. to highlight readings an AI verdict cites). */\n rowClassName?: (row: T) => string | false | null | undefined;\n className?: string;\n /** Default sort: a column key + direction. */\n defaultSort?: { key: string; dir: 'asc' | 'desc' };\n}\n\nconst alignClass = {\n left: 'text-left',\n right: 'text-right',\n center: 'text-center',\n} as const;\n\nexport function DataTable<T>({\n columns,\n rows,\n rowKey,\n onRowClick,\n rowClassName,\n className,\n defaultSort,\n}: DataTableProps<T>) {\n const [sort, setSort] = useState<{ key: string; dir: 'asc' | 'desc' } | null>(\n defaultSort ?? null\n );\n\n const sortedRows = useMemo(() => {\n if (!sort) return rows;\n const col = columns.find((c) => c.key === sort.key);\n if (!col?.sortValue) return rows;\n const dir = sort.dir === 'asc' ? 1 : -1;\n return [...rows].sort((a, b) => {\n const va = col.sortValue!(a);\n const vb = col.sortValue!(b);\n if (va < vb) return -1 * dir;\n if (va > vb) return 1 * dir;\n return 0;\n });\n }, [rows, sort, columns]);\n\n const toggleSort = (key: string) => {\n setSort((prev) =>\n prev?.key === key ? { key, dir: prev.dir === 'asc' ? 'desc' : 'asc' } : { key, dir: 'asc' }\n );\n };\n\n return (\n <GlassCard treatment=\"plain\" className={cn('overflow-hidden', className)}>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam\">\n {columns.map((col) => {\n const sortable = Boolean(col.sortValue);\n const active = sort?.key === col.key;\n return (\n <th\n key={col.key}\n onClick={sortable ? () => toggleSort(col.key) : undefined}\n aria-sort={\n active ? (sort?.dir === 'asc' ? 'ascending' : 'descending') : undefined\n }\n className={cn(\n 'px-4 py-3 font-medium text-text-secondary whitespace-nowrap',\n alignClass[col.align ?? 'left'],\n col.hideOnMobile && 'hidden md:table-cell',\n sortable && 'cursor-pointer select-none hover:text-text-primary'\n )}\n >\n <span\n className={cn(\n 'inline-flex items-center gap-1',\n col.align === 'right' && 'flex-row-reverse'\n )}\n >\n {col.header}\n {sortable &&\n (active ? (\n sort?.dir === 'asc' ? (\n <ChevronUp className=\"h-3.5 w-3.5 text-accent\" />\n ) : (\n <ChevronDown className=\"h-3.5 w-3.5 text-accent\" />\n )\n ) : (\n <ChevronsUpDown className=\"h-3.5 w-3.5 opacity-40\" />\n ))}\n </span>\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {sortedRows.map((row) => (\n <tr\n key={rowKey(row)}\n onClick={onRowClick ? () => onRowClick(row) : undefined}\n className={cn(\n 'group border-b border-border-seam/60 last:border-0',\n onRowClick && 'cursor-pointer transition-colors hover:bg-bg-sunken/60',\n rowClassName?.(row)\n )}\n >\n {columns.map((col) => (\n <td\n key={col.key}\n className={cn(\n 'px-4 py-3 text-text-primary align-middle',\n alignClass[col.align ?? 'left'],\n col.hideOnMobile && 'hidden md:table-cell',\n col.className\n )}\n >\n {col.render(row)}\n </td>\n ))}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;AAuCA,IAAM,IAAa;CACjB,MAAM;CACN,OAAO;CACP,QAAQ;AACV;AAEA,SAAgB,EAAa,EAC3B,YACA,SACA,WACA,eACA,iBACA,cACA,kBACoB;CACpB,IAAM,CAAC,GAAM,KAAW,EACtB,KAAe,IACjB,GAEM,IAAa,QAAc;EAC/B,IAAI,CAAC,GAAM,OAAO;EAClB,IAAM,IAAM,EAAQ,MAAM,MAAM,EAAE,QAAQ,EAAK,GAAG;EAClD,IAAI,CAAC,GAAK,WAAW,OAAO;EAC5B,IAAM,IAAM,EAAK,QAAQ,QAAQ,IAAI;EACrC,OAAO,CAAC,GAAG,CAAI,EAAE,MAAM,GAAG,MAAM;GAC9B,IAAM,IAAK,EAAI,UAAW,CAAC,GACrB,IAAK,EAAI,UAAW,CAAC;GAG3B,OAFI,IAAK,IAAW,KAAK,IACrB,IAAK,IAAW,IAAI,IACjB;EACT,CAAC;CACH,GAAG;EAAC;EAAM;EAAM;CAAO,CAAC,GAElB,KAAc,MAAgB;EAClC,GAAS,MACP,GAAM,QAAQ,IAAM;GAAE;GAAK,KAAK,EAAK,QAAQ,QAAQ,SAAS;EAAM,IAAI;GAAE;GAAK,KAAK;EAAM,CAC5F;CACF;CAEA,OACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAW,EAAG,mBAAmB,CAAS;YACrE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACE,kBAAC,SAAD,EAAA,UACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAQ,KAAK,MAAQ;MACpB,IAAM,IAAW,EAAQ,EAAI,WACvB,IAAS,GAAM,QAAQ,EAAI;MACjC,OACE,kBAAC,MAAD;OAEE,SAAS,UAAiB,EAAW,EAAI,GAAG,IAAI,KAAA;OAChD,aACE,IAAU,GAAM,QAAQ,QAAQ,cAAc,eAAgB,KAAA;OAEhE,WAAW,EACT,+DACA,EAAW,EAAI,SAAS,SACxB,EAAI,gBAAgB,wBACpB,KAAY,oDACd;iBAEA,kBAAC,QAAD;QACE,WAAW,EACT,kCACA,EAAI,UAAU,WAAW,kBAC3B;kBAJF,CAMG,EAAI,QACJ,MACE,IACC,GAAM,QAAQ,QACZ,kBAAC,GAAD,EAAW,WAAU,0BAA2B,CAAA,IAEhD,kBAAC,GAAD,EAAa,WAAU,0BAA2B,CAAA,IAGpD,kBAAC,GAAD,EAAgB,WAAU,yBAA0B,CAAA,EAEpD;;MACJ,GA9BG,EAAI,GA8BP;KAER,CAAC;IACC,CAAA,EACC,CAAA,GACP,kBAAC,SAAD,EAAA,UACG,EAAW,KAAK,MACf,kBAAC,MAAD;KAEE,SAAS,UAAmB,EAAW,CAAG,IAAI,KAAA;KAC9C,WAAW,EACT,sDACA,KAAc,0DACd,IAAe,CAAG,CACpB;eAEC,EAAQ,KAAK,MACZ,kBAAC,MAAD;MAEE,WAAW,EACT,4CACA,EAAW,EAAI,SAAS,SACxB,EAAI,gBAAgB,wBACpB,EAAI,SACN;gBAEC,EAAI,OAAO,CAAG;KACb,GATG,EAAI,GASP,CACL;IACC,GArBG,EAAO,CAAG,CAqBb,CACL,EACI,CAAA,CACF;;EACJ,CAAA;CACI,CAAA;AAEf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/components/StatusBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"StatusBadge.d.ts","sourceRoot":"","sources":["../../../src/ecoimpacthub/components/StatusBadge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqGD,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAsB5C,CAAC"}
|
|
@@ -30,6 +30,12 @@ var i = {
|
|
|
30
30
|
OUTSIDE_RANGE: "warning",
|
|
31
31
|
BREACH: "destructive",
|
|
32
32
|
OK: "success",
|
|
33
|
+
REAL_EVENT: "destructive",
|
|
34
|
+
SENSOR_FAULT: "warning",
|
|
35
|
+
INSUFFICIENT_DATA: "secondary",
|
|
36
|
+
SPIKE: "warning",
|
|
37
|
+
DROP: "info",
|
|
38
|
+
FLATLINE: "secondary",
|
|
33
39
|
LEAST_CONCERN: "success",
|
|
34
40
|
NEAR_THREATENED: "info",
|
|
35
41
|
VULNERABLE: "warning",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/ecoimpacthub/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module ecoimpacthub/components\n *\n * A colored badge for EcoImpactHub domain statuses (site status, sensor status,\n * threshold severity, conservation status, habitat-project lifecycle, carbon\n * entry status, reduction-target status, report status/framework, emission\n * scope) with semantic color mapping. Accepts the raw server enum value.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { formatEnumLabel } from '../utils/formatters';\n\ninterface StatusBadgeProps {\n status: string;\n className?: string;\n}\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\n// Statuses that represent a \"live\" / in-progress condition get a subtle,\n// opacity-only glow pulse (reduced-motion safe via motion-safe:).\nconst liveStatuses = new Set(['IN_PROGRESS', 'ONLINE', 'ACTIVE']);\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Site status\n ACTIVE: 'success',\n MONITORING_PAUSED: 'warning',\n ARCHIVED: 'outline',\n\n // Sensor status\n ONLINE: 'success',\n OFFLINE: 'destructive',\n DEGRADED: 'warning',\n MAINTENANCE: 'info',\n RETIRED: 'outline',\n\n // Threshold severity / generic severity\n INFO: 'secondary',\n WARNING: 'warning',\n CRITICAL: 'destructive',\n\n // Threshold direction\n ABOVE: 'warning',\n BELOW: 'info',\n OUTSIDE_RANGE: 'warning',\n\n // Measurement / breach\n BREACH: 'destructive',\n OK: 'success',\n\n // Conservation status (IUCN-style)\n LEAST_CONCERN: 'success',\n NEAR_THREATENED: 'info',\n VULNERABLE: 'warning',\n ENDANGERED: 'destructive',\n CRITICALLY_ENDANGERED: 'destructive',\n EXTINCT_IN_WILD: 'outline',\n DATA_DEFICIENT: 'secondary',\n NOT_EVALUATED: 'secondary',\n\n // Habitat-project lifecycle\n PLANNED: 'secondary',\n MONITORING: 'info',\n COMPLETED: 'success',\n ON_HOLD: 'warning',\n CANCELLED: 'destructive',\n\n // Carbon-entry status\n DRAFT: 'secondary',\n RECORDED: 'info',\n VERIFIED: 'success',\n ADJUSTED: 'warning',\n\n // Report status\n IN_REVIEW: 'warning',\n PUBLISHED: 'success',\n\n // Reduction-target status\n PROPOSED: 'secondary',\n ACHIEVED: 'success',\n MISSED: 'destructive',\n\n // Emission scope\n SCOPE_1: 'destructive',\n SCOPE_2: 'warning',\n SCOPE_3: 'info',\n\n // Report frameworks (neutral badges)\n GHG_PROTOCOL: 'outline',\n GRI: 'outline',\n TCFD: 'outline',\n CDP: 'outline',\n SASB: 'outline',\n CSRD: 'outline',\n ISSB: 'outline',\n EU_TAXONOMY: 'outline',\n CUSTOM: 'secondary',\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, className }) => {\n const key = status.toUpperCase();\n const variant = statusVariantMap[key] ?? 'secondary';\n const isLive = liveStatuses.has(key);\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse\"\n />\n )}\n {formatEnumLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;;AAqBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;AACX,GAIM,IAAe,IAAI,IAAI;CAAC;CAAe;CAAU;AAAQ,CAAC,GAE1D,IAAiD;CAErD,QAAQ;CACR,mBAAmB;CACnB,UAAU;CAGV,QAAQ;CACR,SAAS;CACT,UAAU;CACV,aAAa;CACb,SAAS;CAGT,MAAM;CACN,SAAS;CACT,UAAU;CAGV,OAAO;CACP,OAAO;CACP,eAAe;CAGf,QAAQ;CACR,IAAI;CAGJ,eAAe;CACf,iBAAiB;CACjB,YAAY;CACZ,YAAY;CACZ,uBAAuB;CACvB,iBAAiB;CACjB,gBAAgB;CAChB,eAAe;CAGf,SAAS;CACT,YAAY;CACZ,WAAW;CACX,SAAS;CACT,WAAW;CAGX,OAAO;CACP,UAAU;CACV,UAAU;CACV,UAAU;CAGV,WAAW;CACX,WAAW;CAGX,UAAU;CACV,UAAU;CACV,QAAQ;CAGR,SAAS;CACT,SAAS;CACT,SAAS;CAGT,cAAc;CACd,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,MAAM;CACN,MAAM;CACN,aAAa;CACb,QAAQ;AACV,GAEa,KAAqC,EAAE,WAAQ,mBAAgB;CAC1E,IAAM,IAAM,EAAO,YAAY,GACzB,IAAU,EAAiB,MAAQ,aACnC,IAAS,EAAa,IAAI,CAAG;CAEnC,OACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAc,IACd,CACF;YALF,CAOG,KACC,kBAAC,QAAD;GACE,eAAY;GACZ,WAAU;EACX,CAAA,GAEF,EAAgB,CAAM,CACnB;;AAEV"}
|
|
1
|
+
{"version":3,"file":"StatusBadge.js","names":[],"sources":["../../../src/ecoimpacthub/components/StatusBadge.tsx"],"sourcesContent":["/**\n * Status Badge Component\n * @module ecoimpacthub/components\n *\n * A colored badge for EcoImpactHub domain statuses (site status, sensor status,\n * threshold severity, conservation status, habitat-project lifecycle, carbon\n * entry status, reduction-target status, report status/framework, emission\n * scope) with semantic color mapping. Accepts the raw server enum value.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { formatEnumLabel } from '../utils/formatters';\n\ninterface StatusBadgeProps {\n status: string;\n className?: string;\n}\n\ntype BadgeVariant = 'success' | 'warning' | 'destructive' | 'secondary' | 'info' | 'outline';\n\nconst variantStyles: Record<BadgeVariant, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text',\n destructive: 'bg-status-error-bg-subtle text-status-error-text',\n secondary: 'bg-bg-sunken text-text-secondary',\n info: 'bg-[var(--color-accent-soft)] text-text-primary',\n outline: 'border border-border-seam text-text-primary',\n};\n\n// Statuses that represent a \"live\" / in-progress condition get a subtle,\n// opacity-only glow pulse (reduced-motion safe via motion-safe:).\nconst liveStatuses = new Set(['IN_PROGRESS', 'ONLINE', 'ACTIVE']);\n\nconst statusVariantMap: Record<string, BadgeVariant> = {\n // Site status\n ACTIVE: 'success',\n MONITORING_PAUSED: 'warning',\n ARCHIVED: 'outline',\n\n // Sensor status\n ONLINE: 'success',\n OFFLINE: 'destructive',\n DEGRADED: 'warning',\n MAINTENANCE: 'info',\n RETIRED: 'outline',\n\n // Threshold severity / generic severity\n INFO: 'secondary',\n WARNING: 'warning',\n CRITICAL: 'destructive',\n\n // Threshold direction\n ABOVE: 'warning',\n BELOW: 'info',\n OUTSIDE_RANGE: 'warning',\n\n // Measurement / breach\n BREACH: 'destructive',\n OK: 'success',\n\n // AI alert triage classification + anomaly kinds\n REAL_EVENT: 'destructive',\n SENSOR_FAULT: 'warning',\n INSUFFICIENT_DATA: 'secondary',\n SPIKE: 'warning',\n DROP: 'info',\n FLATLINE: 'secondary',\n\n // Conservation status (IUCN-style)\n LEAST_CONCERN: 'success',\n NEAR_THREATENED: 'info',\n VULNERABLE: 'warning',\n ENDANGERED: 'destructive',\n CRITICALLY_ENDANGERED: 'destructive',\n EXTINCT_IN_WILD: 'outline',\n DATA_DEFICIENT: 'secondary',\n NOT_EVALUATED: 'secondary',\n\n // Habitat-project lifecycle\n PLANNED: 'secondary',\n MONITORING: 'info',\n COMPLETED: 'success',\n ON_HOLD: 'warning',\n CANCELLED: 'destructive',\n\n // Carbon-entry status\n DRAFT: 'secondary',\n RECORDED: 'info',\n VERIFIED: 'success',\n ADJUSTED: 'warning',\n\n // Report status\n IN_REVIEW: 'warning',\n PUBLISHED: 'success',\n\n // Reduction-target status\n PROPOSED: 'secondary',\n ACHIEVED: 'success',\n MISSED: 'destructive',\n\n // Emission scope\n SCOPE_1: 'destructive',\n SCOPE_2: 'warning',\n SCOPE_3: 'info',\n\n // Report frameworks (neutral badges)\n GHG_PROTOCOL: 'outline',\n GRI: 'outline',\n TCFD: 'outline',\n CDP: 'outline',\n SASB: 'outline',\n CSRD: 'outline',\n ISSB: 'outline',\n EU_TAXONOMY: 'outline',\n CUSTOM: 'secondary',\n};\n\nexport const StatusBadge: FC<StatusBadgeProps> = ({ status, className }) => {\n const key = status.toUpperCase();\n const variant = statusVariantMap[key] ?? 'secondary';\n const isLive = liveStatuses.has(key);\n\n return (\n <span\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium',\n variantStyles[variant],\n className\n )}\n >\n {isLive && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 rounded-full bg-current motion-safe:animate-vc-glow-pulse\"\n />\n )}\n {formatEnumLabel(status)}\n </span>\n );\n};\n"],"mappings":";;;;AAqBA,IAAM,IAA8C;CAClD,SAAS;CACT,SAAS;CACT,aAAa;CACb,WAAW;CACX,MAAM;CACN,SAAS;AACX,GAIM,IAAe,IAAI,IAAI;CAAC;CAAe;CAAU;AAAQ,CAAC,GAE1D,IAAiD;CAErD,QAAQ;CACR,mBAAmB;CACnB,UAAU;CAGV,QAAQ;CACR,SAAS;CACT,UAAU;CACV,aAAa;CACb,SAAS;CAGT,MAAM;CACN,SAAS;CACT,UAAU;CAGV,OAAO;CACP,OAAO;CACP,eAAe;CAGf,QAAQ;CACR,IAAI;CAGJ,YAAY;CACZ,cAAc;CACd,mBAAmB;CACnB,OAAO;CACP,MAAM;CACN,UAAU;CAGV,eAAe;CACf,iBAAiB;CACjB,YAAY;CACZ,YAAY;CACZ,uBAAuB;CACvB,iBAAiB;CACjB,gBAAgB;CAChB,eAAe;CAGf,SAAS;CACT,YAAY;CACZ,WAAW;CACX,SAAS;CACT,WAAW;CAGX,OAAO;CACP,UAAU;CACV,UAAU;CACV,UAAU;CAGV,WAAW;CACX,WAAW;CAGX,UAAU;CACV,UAAU;CACV,QAAQ;CAGR,SAAS;CACT,SAAS;CACT,SAAS;CAGT,cAAc;CACd,KAAK;CACL,MAAM;CACN,KAAK;CACL,MAAM;CACN,MAAM;CACN,MAAM;CACN,aAAa;CACb,QAAQ;AACV,GAEa,KAAqC,EAAE,WAAQ,mBAAgB;CAC1E,IAAM,IAAM,EAAO,YAAY,GACzB,IAAU,EAAiB,MAAQ,aACnC,IAAS,EAAa,IAAI,CAAG;CAEnC,OACE,kBAAC,QAAD;EACE,WAAW,EACT,mFACA,EAAc,IACd,CACF;YALF,CAOG,KACC,kBAAC,QAAD;GACE,eAAY;GACZ,WAAU;EACX,CAAA,GAEF,EAAgB,CAAM,CACnB;;AAEV"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { EcoSensor } from '../../types';
|
|
3
|
+
|
|
4
|
+
interface AiAnomalyPanelProps {
|
|
5
|
+
siteId: string;
|
|
6
|
+
/** Parameters the site has readings or thresholds for. */
|
|
7
|
+
parameters: string[];
|
|
8
|
+
sensors?: EcoSensor[];
|
|
9
|
+
}
|
|
10
|
+
export declare const AiAnomalyPanel: FC<AiAnomalyPanelProps>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=AiAnomalyPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiAnomalyPanel.d.ts","sourceRoot":"","sources":["../../../../src/ecoimpacthub/components/ai/AiAnomalyPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAqB,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAyBnD,OAAO,KAAK,EAA6B,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxE,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,EAAE,CAAC;CACvB;AAeD,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAsPlD,CAAC"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { formatDateTime as e, formatNumber as t } from "../../utils/formatters.js";
|
|
2
|
+
import { StatusBadge as n } from "../StatusBadge.js";
|
|
3
|
+
import { useEcoMeasurements as r } from "../../hooks/useEcoImpactHubApi.js";
|
|
4
|
+
import { useTr as i } from "../../hooks/useTr.js";
|
|
5
|
+
import { ANOMALY_PERIOD_PRESETS as a, groupAnomalies as o, periodRange as s, resolveCitedReadings as c } from "../../utils/aiMonitoring.js";
|
|
6
|
+
import { useEcoAiDetectAnomalies as l, useEcoAiErrorReporter as u } from "../../hooks/useEcoAiMonitoring.js";
|
|
7
|
+
import "../../hooks/index.js";
|
|
8
|
+
import { AiCreditHint as d, AiErrorNotice as f, CitedReadingsList as p } from "./AiShared.js";
|
|
9
|
+
import { useMemo as m, useState as h } from "react";
|
|
10
|
+
import { Activity as g, ScanSearch as _ } from "lucide-react";
|
|
11
|
+
import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
12
|
+
import { Button as x, GlassCard as S, Label as C, Select as w, SelectContent as T, SelectItem as E, SelectTrigger as D, SelectValue as O } from "@burdenoff/fe-libs/ui";
|
|
13
|
+
//#region src/ecoimpacthub/components/ai/AiAnomalyPanel.tsx
|
|
14
|
+
var k = {
|
|
15
|
+
"7d": ["ecoAi.anomalies.period7d", "Last 7 days"],
|
|
16
|
+
"30d": ["ecoAi.anomalies.period30d", "Last 30 days"],
|
|
17
|
+
"90d": ["ecoAi.anomalies.period90d", "Last 90 days"],
|
|
18
|
+
"365d": ["ecoAi.anomalies.period365d", "Last 12 months"]
|
|
19
|
+
}, A = {
|
|
20
|
+
SPIKE: ["ecoAi.anomalies.kindSpike", "Spikes"],
|
|
21
|
+
DROP: ["ecoAi.anomalies.kindDrop", "Drops"],
|
|
22
|
+
FLATLINE: ["ecoAi.anomalies.kindFlatline", "Flatlines"]
|
|
23
|
+
}, j = ({ siteId: j, parameters: M, sensors: N = [] }) => {
|
|
24
|
+
let P = i(), [F, I] = h(""), [L, R] = h("30d"), z = F || M[0] || "", B = l(), V = u(), [H, U] = h(null), [W, G] = h(null), [K, q] = h(null), J = r({
|
|
25
|
+
siteId: j,
|
|
26
|
+
parameter: H?.parameter ?? z,
|
|
27
|
+
pagination: {
|
|
28
|
+
limit: 100,
|
|
29
|
+
offset: 0
|
|
30
|
+
}
|
|
31
|
+
}), Y = m(() => J.data?.items ?? [], [J.data]), X = m(() => H ? o(H.anomalies) : [], [H]), Z = (e) => e && N.find((t) => t.id === e)?.name || null;
|
|
32
|
+
return /* @__PURE__ */ y("div", {
|
|
33
|
+
"data-testid": "eco-ai-anomalies",
|
|
34
|
+
children: /* @__PURE__ */ b(S, {
|
|
35
|
+
treatment: "plain",
|
|
36
|
+
className: "p-5",
|
|
37
|
+
children: [
|
|
38
|
+
M.length === 0 ? /* @__PURE__ */ y("p", {
|
|
39
|
+
className: "text-sm text-text-secondary",
|
|
40
|
+
children: P("ecoAi.anomalies.noParameters", "Record measurements at this site to analyse them for anomalies.")
|
|
41
|
+
}) : /* @__PURE__ */ b(v, { children: [/* @__PURE__ */ b("div", {
|
|
42
|
+
className: "flex flex-wrap items-end gap-3",
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ b("div", {
|
|
45
|
+
className: "grid gap-1.5",
|
|
46
|
+
children: [/* @__PURE__ */ y(C, {
|
|
47
|
+
htmlFor: "ecoAiAnomalyParam",
|
|
48
|
+
children: P("ecoAi.anomalies.parameter", "Parameter")
|
|
49
|
+
}), /* @__PURE__ */ b(w, {
|
|
50
|
+
value: z,
|
|
51
|
+
onValueChange: (e) => {
|
|
52
|
+
I(e), U(null), G(null);
|
|
53
|
+
},
|
|
54
|
+
children: [/* @__PURE__ */ y(D, {
|
|
55
|
+
id: "ecoAiAnomalyParam",
|
|
56
|
+
className: "w-48",
|
|
57
|
+
children: /* @__PURE__ */ y(O, {})
|
|
58
|
+
}), /* @__PURE__ */ y(T, { children: M.map((e) => /* @__PURE__ */ y(E, {
|
|
59
|
+
value: e,
|
|
60
|
+
children: e
|
|
61
|
+
}, e)) })]
|
|
62
|
+
})]
|
|
63
|
+
}),
|
|
64
|
+
/* @__PURE__ */ b("div", {
|
|
65
|
+
className: "grid gap-1.5",
|
|
66
|
+
children: [/* @__PURE__ */ y(C, {
|
|
67
|
+
htmlFor: "ecoAiAnomalyPeriod",
|
|
68
|
+
children: P("ecoAi.anomalies.period", "Period")
|
|
69
|
+
}), /* @__PURE__ */ b(w, {
|
|
70
|
+
value: L,
|
|
71
|
+
onValueChange: (e) => {
|
|
72
|
+
R(e), U(null), G(null);
|
|
73
|
+
},
|
|
74
|
+
children: [/* @__PURE__ */ y(D, {
|
|
75
|
+
id: "ecoAiAnomalyPeriod",
|
|
76
|
+
className: "w-44",
|
|
77
|
+
children: /* @__PURE__ */ y(O, {})
|
|
78
|
+
}), /* @__PURE__ */ y(T, { children: a.map((e) => /* @__PURE__ */ y(E, {
|
|
79
|
+
value: e,
|
|
80
|
+
children: P(k[e][0], k[e][1])
|
|
81
|
+
}, e)) })]
|
|
82
|
+
})]
|
|
83
|
+
}),
|
|
84
|
+
/* @__PURE__ */ b(x, {
|
|
85
|
+
onClick: async () => {
|
|
86
|
+
if (z) {
|
|
87
|
+
G(null), q(null);
|
|
88
|
+
try {
|
|
89
|
+
U(await B.mutateAsync({
|
|
90
|
+
siteId: j,
|
|
91
|
+
parameter: z,
|
|
92
|
+
...s(L)
|
|
93
|
+
}));
|
|
94
|
+
} catch (e) {
|
|
95
|
+
U(null), G(V(e, "EcoAiDetectAnomalies", P("ecoAi.anomalies.failed", "Anomaly detection failed.")));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
disabled: !z || B.isPending,
|
|
100
|
+
"data-testid": "eco-ai-anomalies-run",
|
|
101
|
+
children: [/* @__PURE__ */ y(_, { className: "mr-1.5 h-4 w-4" }), B.isPending ? P("ecoAi.anomalies.running", "Analysing…") : P("ecoAi.anomalies.run", "Detect anomalies")]
|
|
102
|
+
})
|
|
103
|
+
]
|
|
104
|
+
}), /* @__PURE__ */ y(d, { className: "mt-2" })] }),
|
|
105
|
+
W && /* @__PURE__ */ y(f, {
|
|
106
|
+
error: W,
|
|
107
|
+
className: "mt-4"
|
|
108
|
+
}),
|
|
109
|
+
H && H.status === "INSUFFICIENT_DATA" && /* @__PURE__ */ b("div", {
|
|
110
|
+
className: "mt-4 rounded-lg bg-bg-sunken/60 p-4",
|
|
111
|
+
"data-testid": "eco-ai-anomalies-insufficient",
|
|
112
|
+
children: [
|
|
113
|
+
/* @__PURE__ */ y("p", {
|
|
114
|
+
className: "text-sm font-semibold text-text-primary",
|
|
115
|
+
children: P("ecoAi.anomalies.notEnoughData", "Not enough data")
|
|
116
|
+
}),
|
|
117
|
+
/* @__PURE__ */ y("p", {
|
|
118
|
+
className: "mt-1 text-sm text-text-secondary",
|
|
119
|
+
children: P("ecoAi.anomalies.notEnoughDataBody", "Only {{count}} reading(s) of {{parameter}} in this period, too few to judge what is normal. No trend is shown.", {
|
|
120
|
+
count: H.readingsAnalysed,
|
|
121
|
+
parameter: H.parameter
|
|
122
|
+
})
|
|
123
|
+
}),
|
|
124
|
+
H.notes.length > 0 && /* @__PURE__ */ y("ul", {
|
|
125
|
+
className: "mt-2 list-disc space-y-0.5 pl-5 text-xs text-text-tertiary",
|
|
126
|
+
children: H.notes.map((e, t) => /* @__PURE__ */ y("li", { children: e }, `${t}:${e}`))
|
|
127
|
+
})
|
|
128
|
+
]
|
|
129
|
+
}),
|
|
130
|
+
H && H.status === "OK" && /* @__PURE__ */ b("div", {
|
|
131
|
+
className: "mt-4 grid gap-4",
|
|
132
|
+
"data-testid": "eco-ai-anomalies-result",
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ b("p", {
|
|
135
|
+
className: "text-xs text-text-tertiary",
|
|
136
|
+
children: [P("ecoAi.anomalies.summary", "{{count}} reading(s) analysed · baseline median {{median}} {{unit}}", {
|
|
137
|
+
count: H.readingsAnalysed,
|
|
138
|
+
median: t(H.baseline?.median ?? null),
|
|
139
|
+
unit: H.unit ?? ""
|
|
140
|
+
}), H.truncated && ` · ${P("ecoAi.anomalies.truncated", "the series was truncated to the most recent readings")}`]
|
|
141
|
+
}),
|
|
142
|
+
X.length === 0 ? /* @__PURE__ */ b("p", {
|
|
143
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
144
|
+
"data-testid": "eco-ai-anomalies-none",
|
|
145
|
+
children: [/* @__PURE__ */ y(g, { className: "h-4 w-4 text-status-success-text" }), P("ecoAi.anomalies.none", "No spikes, drops or flatlines in this period.")]
|
|
146
|
+
}) : X.map((r) => /* @__PURE__ */ b("div", { children: [/* @__PURE__ */ b("h3", {
|
|
147
|
+
className: "mb-2 flex items-center gap-2 text-sm font-semibold text-text-primary",
|
|
148
|
+
children: [
|
|
149
|
+
/* @__PURE__ */ y(n, { status: r.kind }),
|
|
150
|
+
P(A[r.kind]?.[0] ?? r.kind, A[r.kind]?.[1] ?? r.kind),
|
|
151
|
+
/* @__PURE__ */ b("span", {
|
|
152
|
+
className: "text-text-tertiary",
|
|
153
|
+
children: [
|
|
154
|
+
"(",
|
|
155
|
+
r.items.length,
|
|
156
|
+
")"
|
|
157
|
+
]
|
|
158
|
+
})
|
|
159
|
+
]
|
|
160
|
+
}), /* @__PURE__ */ y("ul", {
|
|
161
|
+
className: "divide-y divide-border-seam/60 rounded-lg border border-border-seam",
|
|
162
|
+
children: r.items.map((n) => {
|
|
163
|
+
let r = K === n.measurementId;
|
|
164
|
+
return /* @__PURE__ */ b("li", {
|
|
165
|
+
"data-testid": "eco-ai-anomaly",
|
|
166
|
+
children: [/* @__PURE__ */ b("button", {
|
|
167
|
+
type: "button",
|
|
168
|
+
className: "flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm hover:bg-bg-sunken/60",
|
|
169
|
+
"aria-expanded": r,
|
|
170
|
+
onClick: () => q(r ? null : n.measurementId),
|
|
171
|
+
children: [/* @__PURE__ */ b("span", {
|
|
172
|
+
className: "tabular-nums text-text-primary",
|
|
173
|
+
children: [
|
|
174
|
+
t(n.value),
|
|
175
|
+
" ",
|
|
176
|
+
H.unit ?? "",
|
|
177
|
+
Z(n.sensorId) && /* @__PURE__ */ y("span", {
|
|
178
|
+
className: "ml-2 text-xs text-text-tertiary",
|
|
179
|
+
children: Z(n.sensorId)
|
|
180
|
+
})
|
|
181
|
+
]
|
|
182
|
+
}), /* @__PURE__ */ b("span", {
|
|
183
|
+
className: "flex items-center gap-3 text-xs text-text-tertiary",
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ y("span", { children: n.kind === "FLATLINE" ? P("ecoAi.anomalies.runLength", "run of {{n}}", { n: t(n.score, { maximumFractionDigits: 1 }) }) : P("ecoAi.anomalies.score", "z {{z}}", { z: t(n.score, { maximumFractionDigits: 1 }) }) }),
|
|
186
|
+
/* @__PURE__ */ y("span", { children: e(n.recordedAt) }),
|
|
187
|
+
/* @__PURE__ */ y("span", { children: P("ecoAi.anomalies.citedCount", "{{n}} cited", { n: n.evidenceMeasurementIds.length }) })
|
|
188
|
+
]
|
|
189
|
+
})]
|
|
190
|
+
}), r && /* @__PURE__ */ y("div", {
|
|
191
|
+
className: "px-3 pb-3",
|
|
192
|
+
children: /* @__PURE__ */ y(p, {
|
|
193
|
+
readings: c(n.evidenceMeasurementIds, Y),
|
|
194
|
+
focusId: n.measurementId
|
|
195
|
+
})
|
|
196
|
+
})]
|
|
197
|
+
}, n.measurementId);
|
|
198
|
+
})
|
|
199
|
+
})] }, r.kind)),
|
|
200
|
+
H.notes.length > 0 && /* @__PURE__ */ y("ul", {
|
|
201
|
+
className: "list-disc space-y-0.5 pl-5 text-xs text-text-tertiary",
|
|
202
|
+
children: H.notes.map((e, t) => /* @__PURE__ */ y("li", { children: e }, `${t}:${e}`))
|
|
203
|
+
})
|
|
204
|
+
]
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
})
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
//#endregion
|
|
211
|
+
export { j as AiAnomalyPanel };
|
|
212
|
+
|
|
213
|
+
//# sourceMappingURL=AiAnomalyPanel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiAnomalyPanel.js","names":[],"sources":["../../../../src/ecoimpacthub/components/ai/AiAnomalyPanel.tsx"],"sourcesContent":["/**\n * AI anomaly panel (site detail)\n * @module ecoimpacthub/components/ai\n *\n * Runs `ecoAiDetectAnomalies` for one parameter at a site over a chosen\n * period and lists spikes, drops and flatlines with the readings each one\n * cites. INSUFFICIENT_DATA renders an explicit \"Not enough data\" state and\n * never a trend. Detection is metered, so it runs only on the user's click.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport { Activity, ScanSearch } from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@burdenoff/fe-libs/ui';\n\nimport { StatusBadge } from '../StatusBadge';\nimport { useEcoAiDetectAnomalies, useEcoAiErrorReporter, useEcoMeasurements } from '../../hooks';\nimport { useTr } from '../../hooks/useTr';\nimport {\n ANOMALY_PERIOD_PRESETS,\n groupAnomalies,\n periodRange,\n resolveCitedReadings,\n type AnomalyPeriodPreset,\n type EcoAiErrorInfo,\n} from '../../utils/aiMonitoring';\nimport { formatDateTime, formatNumber } from '../../utils/formatters';\nimport type { EcoAnomalyDetectionResult, EcoSensor } from '../../types';\nimport { AiCreditHint, AiErrorNotice, CitedReadingsList } from './AiShared';\n\ninterface AiAnomalyPanelProps {\n siteId: string;\n /** Parameters the site has readings or thresholds for. */\n parameters: string[];\n sensors?: EcoSensor[];\n}\n\nconst PERIOD_LABELS: Record<AnomalyPeriodPreset, [string, string]> = {\n '7d': ['ecoAi.anomalies.period7d', 'Last 7 days'],\n '30d': ['ecoAi.anomalies.period30d', 'Last 30 days'],\n '90d': ['ecoAi.anomalies.period90d', 'Last 90 days'],\n '365d': ['ecoAi.anomalies.period365d', 'Last 12 months'],\n};\n\nconst KIND_LABELS: Record<string, [string, string]> = {\n SPIKE: ['ecoAi.anomalies.kindSpike', 'Spikes'],\n DROP: ['ecoAi.anomalies.kindDrop', 'Drops'],\n FLATLINE: ['ecoAi.anomalies.kindFlatline', 'Flatlines'],\n};\n\nexport const AiAnomalyPanel: FC<AiAnomalyPanelProps> = ({ siteId, parameters, sensors = [] }) => {\n const tr = useTr();\n const [parameter, setParameter] = useState<string>('');\n const [period, setPeriod] = useState<AnomalyPeriodPreset>('30d');\n const activeParameter = parameter || parameters[0] || '';\n\n const detect = useEcoAiDetectAnomalies();\n const reportAiError = useEcoAiErrorReporter();\n const [result, setResult] = useState<EcoAnomalyDetectionResult | null>(null);\n const [aiError, setAiError] = useState<EcoAiErrorInfo | null>(null);\n const [expanded, setExpanded] = useState<string | null>(null);\n\n // Readings for the analysed parameter resolve cited ids to values.\n const historyQuery = useEcoMeasurements({\n siteId,\n parameter: result?.parameter ?? activeParameter,\n pagination: { limit: 100, offset: 0 },\n });\n const readings = useMemo(() => historyQuery.data?.items ?? [], [historyQuery.data]);\n const groups = useMemo(() => (result ? groupAnomalies(result.anomalies) : []), [result]);\n const sensorName = (id?: string | null) => (id && sensors.find((s) => s.id === id)?.name) || null;\n\n const run = async () => {\n if (!activeParameter) return;\n setAiError(null);\n setExpanded(null);\n try {\n const out = await detect.mutateAsync({\n siteId,\n parameter: activeParameter,\n ...periodRange(period),\n });\n setResult(out);\n } catch (err) {\n setResult(null);\n setAiError(\n reportAiError(\n err,\n 'EcoAiDetectAnomalies',\n tr('ecoAi.anomalies.failed', 'Anomaly detection failed.')\n )\n );\n }\n };\n\n return (\n <div data-testid=\"eco-ai-anomalies\">\n <GlassCard treatment=\"plain\" className=\"p-5\">\n {parameters.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ecoAi.anomalies.noParameters',\n 'Record measurements at this site to analyse them for anomalies.'\n )}\n </p>\n ) : (\n <>\n <div className=\"flex flex-wrap items-end gap-3\">\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"ecoAiAnomalyParam\">\n {tr('ecoAi.anomalies.parameter', 'Parameter')}\n </Label>\n <Select\n value={activeParameter}\n onValueChange={(v) => {\n setParameter(v);\n setResult(null);\n setAiError(null);\n }}\n >\n <SelectTrigger id=\"ecoAiAnomalyParam\" className=\"w-48\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {parameters.map((p) => (\n <SelectItem key={p} value={p}>\n {p}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <div className=\"grid gap-1.5\">\n <Label htmlFor=\"ecoAiAnomalyPeriod\">{tr('ecoAi.anomalies.period', 'Period')}</Label>\n <Select\n value={period}\n onValueChange={(v) => {\n setPeriod(v as AnomalyPeriodPreset);\n setResult(null);\n setAiError(null);\n }}\n >\n <SelectTrigger id=\"ecoAiAnomalyPeriod\" className=\"w-44\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ANOMALY_PERIOD_PRESETS.map((p) => (\n <SelectItem key={p} value={p}>\n {tr(PERIOD_LABELS[p][0], PERIOD_LABELS[p][1])}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n <Button\n onClick={run}\n disabled={!activeParameter || detect.isPending}\n data-testid=\"eco-ai-anomalies-run\"\n >\n <ScanSearch className=\"mr-1.5 h-4 w-4\" />\n {detect.isPending\n ? tr('ecoAi.anomalies.running', 'Analysing…')\n : tr('ecoAi.anomalies.run', 'Detect anomalies')}\n </Button>\n </div>\n <AiCreditHint className=\"mt-2\" />\n </>\n )}\n\n {aiError && <AiErrorNotice error={aiError} className=\"mt-4\" />}\n\n {result && result.status === 'INSUFFICIENT_DATA' && (\n <div\n className=\"mt-4 rounded-lg bg-bg-sunken/60 p-4\"\n data-testid=\"eco-ai-anomalies-insufficient\"\n >\n <p className=\"text-sm font-semibold text-text-primary\">\n {tr('ecoAi.anomalies.notEnoughData', 'Not enough data')}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {tr(\n 'ecoAi.anomalies.notEnoughDataBody',\n 'Only {{count}} reading(s) of {{parameter}} in this period, too few to judge what is normal. No trend is shown.',\n { count: result.readingsAnalysed, parameter: result.parameter }\n )}\n </p>\n {result.notes.length > 0 && (\n <ul className=\"mt-2 list-disc space-y-0.5 pl-5 text-xs text-text-tertiary\">\n {result.notes.map((n, i) => (\n <li key={`${i}:${n}`}>{n}</li>\n ))}\n </ul>\n )}\n </div>\n )}\n\n {result && result.status === 'OK' && (\n <div className=\"mt-4 grid gap-4\" data-testid=\"eco-ai-anomalies-result\">\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'ecoAi.anomalies.summary',\n '{{count}} reading(s) analysed · baseline median {{median}} {{unit}}',\n {\n count: result.readingsAnalysed,\n median: formatNumber(result.baseline?.median ?? null),\n unit: result.unit ?? '',\n }\n )}\n {result.truncated &&\n ` · ${tr('ecoAi.anomalies.truncated', 'the series was truncated to the most recent readings')}`}\n </p>\n\n {groups.length === 0 ? (\n <p\n className=\"flex items-center gap-2 text-sm text-text-secondary\"\n data-testid=\"eco-ai-anomalies-none\"\n >\n <Activity className=\"h-4 w-4 text-status-success-text\" />\n {tr('ecoAi.anomalies.none', 'No spikes, drops or flatlines in this period.')}\n </p>\n ) : (\n groups.map((group) => (\n <div key={group.kind}>\n <h3 className=\"mb-2 flex items-center gap-2 text-sm font-semibold text-text-primary\">\n <StatusBadge status={group.kind} />\n {tr(\n KIND_LABELS[group.kind]?.[0] ?? group.kind,\n KIND_LABELS[group.kind]?.[1] ?? group.kind\n )}\n <span className=\"text-text-tertiary\">({group.items.length})</span>\n </h3>\n <ul className=\"divide-y divide-border-seam/60 rounded-lg border border-border-seam\">\n {group.items.map((a) => {\n const open = expanded === a.measurementId;\n return (\n <li key={a.measurementId} data-testid=\"eco-ai-anomaly\">\n <button\n type=\"button\"\n className=\"flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm hover:bg-bg-sunken/60\"\n aria-expanded={open}\n onClick={() => setExpanded(open ? null : a.measurementId)}\n >\n <span className=\"tabular-nums text-text-primary\">\n {formatNumber(a.value)} {result.unit ?? ''}\n {sensorName(a.sensorId) && (\n <span className=\"ml-2 text-xs text-text-tertiary\">\n {sensorName(a.sensorId)}\n </span>\n )}\n </span>\n <span className=\"flex items-center gap-3 text-xs text-text-tertiary\">\n <span>\n {a.kind === 'FLATLINE'\n ? tr('ecoAi.anomalies.runLength', 'run of {{n}}', {\n n: formatNumber(a.score, { maximumFractionDigits: 1 }),\n })\n : tr('ecoAi.anomalies.score', 'z {{z}}', {\n z: formatNumber(a.score, { maximumFractionDigits: 1 }),\n })}\n </span>\n <span>{formatDateTime(a.recordedAt)}</span>\n <span>\n {tr('ecoAi.anomalies.citedCount', '{{n}} cited', {\n n: a.evidenceMeasurementIds.length,\n })}\n </span>\n </span>\n </button>\n {open && (\n <div className=\"px-3 pb-3\">\n <CitedReadingsList\n readings={resolveCitedReadings(a.evidenceMeasurementIds, readings)}\n focusId={a.measurementId}\n />\n </div>\n )}\n </li>\n );\n })}\n </ul>\n </div>\n ))\n )}\n\n {result.notes.length > 0 && (\n <ul className=\"list-disc space-y-0.5 pl-5 text-xs text-text-tertiary\">\n {result.notes.map((n, i) => (\n <li key={`${i}:${n}`}>{n}</li>\n ))}\n </ul>\n )}\n </div>\n )}\n </GlassCard>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { CitedReading, EcoAiErrorInfo } from '../../utils/aiMonitoring';
|
|
3
|
+
|
|
4
|
+
/** "Uses 1 AI credit" hint shown next to every metered AI action. */
|
|
5
|
+
export declare const AiCreditHint: FC<{
|
|
6
|
+
className?: string;
|
|
7
|
+
}>;
|
|
8
|
+
/** Inline notice for a failed AI run (quota, feature disabled, or other). */
|
|
9
|
+
export declare const AiErrorNotice: FC<{
|
|
10
|
+
error: EcoAiErrorInfo;
|
|
11
|
+
className?: string;
|
|
12
|
+
}>;
|
|
13
|
+
interface CitedReadingsListProps {
|
|
14
|
+
readings: CitedReading[];
|
|
15
|
+
/** The reading the verdict / anomaly is about; marked in the list. */
|
|
16
|
+
focusId?: string;
|
|
17
|
+
emptyLabel?: string;
|
|
18
|
+
}
|
|
19
|
+
/** The readings an AI verdict cites, resolved against loaded measurements. */
|
|
20
|
+
export declare const CitedReadingsList: FC<CitedReadingsListProps>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=AiShared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiShared.d.ts","sourceRoot":"","sources":["../../../../src/ecoimpacthub/components/ai/AiShared.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI7E,qEAAqE;AACrE,eAAO,MAAM,YAAY,EAAE,EAAE,CAAC;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAQnD,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC;IAAE,KAAK,EAAE,cAAc,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CA+B3E,CAAC;AAEF,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,8EAA8E;AAC9E,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAgDxD,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { cn as e } from "../../utils/cn.js";
|
|
2
|
+
import { formatDateTime as t, formatNumber as n } from "../../utils/formatters.js";
|
|
3
|
+
import { useTr as r } from "../../hooks/useTr.js";
|
|
4
|
+
import { AlertTriangle as i, Sparkles as a } from "lucide-react";
|
|
5
|
+
import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
6
|
+
//#region src/ecoimpacthub/components/ai/AiShared.tsx
|
|
7
|
+
var l = ({ className: t }) => {
|
|
8
|
+
let n = r();
|
|
9
|
+
return /* @__PURE__ */ c("p", {
|
|
10
|
+
className: e("flex items-center gap-1 text-xs text-text-tertiary", t),
|
|
11
|
+
children: [/* @__PURE__ */ s(a, {
|
|
12
|
+
className: "h-3.5 w-3.5 text-accent",
|
|
13
|
+
"aria-hidden": "true"
|
|
14
|
+
}), n("ecoAi.creditHint", "Each run uses 1 AI credit.")]
|
|
15
|
+
});
|
|
16
|
+
}, u = ({ error: t, className: n }) => {
|
|
17
|
+
let a = r(), o = t.kind === "quota" ? a("ecoAi.errors.quotaTitle", "AI credits used up") : t.kind === "disabled" ? a("ecoAi.errors.disabledTitle", "AI monitoring is unavailable") : a("ecoAi.errors.genericTitle", "The AI run failed");
|
|
18
|
+
return /* @__PURE__ */ c("div", {
|
|
19
|
+
role: "alert",
|
|
20
|
+
"data-testid": "eco-ai-error",
|
|
21
|
+
"data-kind": t.kind,
|
|
22
|
+
className: e("flex items-start gap-2 rounded-lg p-3 text-sm", t.kind === "other" ? "bg-status-error-bg-subtle text-status-error-text" : "bg-status-warning-bg-subtle text-status-warning-text", n),
|
|
23
|
+
children: [/* @__PURE__ */ s(i, {
|
|
24
|
+
className: "mt-0.5 h-4 w-4 shrink-0",
|
|
25
|
+
"aria-hidden": "true"
|
|
26
|
+
}), /* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("p", {
|
|
27
|
+
className: "font-medium",
|
|
28
|
+
children: o
|
|
29
|
+
}), /* @__PURE__ */ s("p", {
|
|
30
|
+
className: "mt-0.5",
|
|
31
|
+
children: t.message
|
|
32
|
+
})] })]
|
|
33
|
+
});
|
|
34
|
+
}, d = ({ readings: i, focusId: a, emptyLabel: l }) => {
|
|
35
|
+
let u = r();
|
|
36
|
+
return i.length === 0 ? /* @__PURE__ */ s("p", {
|
|
37
|
+
className: "text-xs text-text-tertiary",
|
|
38
|
+
children: l ?? u("ecoAi.cited.none", "No readings cited.")
|
|
39
|
+
}) : /* @__PURE__ */ s("ul", {
|
|
40
|
+
className: "divide-y divide-border-seam/60 rounded-lg border border-border-seam",
|
|
41
|
+
children: i.map(({ id: r, measurement: i }) => /* @__PURE__ */ s("li", {
|
|
42
|
+
"data-testid": "eco-ai-cited-reading",
|
|
43
|
+
"data-measurement-id": r,
|
|
44
|
+
className: e("flex items-center justify-between gap-3 px-3 py-2 text-xs", r === a && "bg-[var(--color-accent-soft)]"),
|
|
45
|
+
children: i ? /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ c("span", {
|
|
46
|
+
className: "tabular-nums text-text-primary",
|
|
47
|
+
children: [
|
|
48
|
+
n(i.value),
|
|
49
|
+
" ",
|
|
50
|
+
i.unit,
|
|
51
|
+
r === a && /* @__PURE__ */ s("span", {
|
|
52
|
+
className: "ml-2 font-medium text-text-secondary",
|
|
53
|
+
children: u("ecoAi.cited.thisReading", "this reading")
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
}), /* @__PURE__ */ s("span", {
|
|
57
|
+
className: "text-text-tertiary",
|
|
58
|
+
children: t(i.recordedAt)
|
|
59
|
+
})] }) : /* @__PURE__ */ s("span", {
|
|
60
|
+
className: "font-mono text-text-tertiary",
|
|
61
|
+
title: r,
|
|
62
|
+
children: u("ecoAi.cited.olderReading", "Reading {{id}} (outside the loaded window)", { id: r.slice(0, 8) })
|
|
63
|
+
})
|
|
64
|
+
}, r))
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
export { l as AiCreditHint, u as AiErrorNotice, d as CitedReadingsList };
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=AiShared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiShared.js","names":[],"sources":["../../../../src/ecoimpacthub/components/ai/AiShared.tsx"],"sourcesContent":["/**\n * Small building blocks shared by the AI monitoring surfaces.\n * @module ecoimpacthub/components/ai\n */\n\nimport type { FC } from 'react';\nimport { AlertTriangle, Sparkles } from 'lucide-react';\n\nimport { useTr } from '../../hooks/useTr';\nimport type { CitedReading, EcoAiErrorInfo } from '../../utils/aiMonitoring';\nimport { formatDateTime, formatNumber } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\n/** \"Uses 1 AI credit\" hint shown next to every metered AI action. */\nexport const AiCreditHint: FC<{ className?: string }> = ({ className }) => {\n const tr = useTr();\n return (\n <p className={cn('flex items-center gap-1 text-xs text-text-tertiary', className)}>\n <Sparkles className=\"h-3.5 w-3.5 text-accent\" aria-hidden=\"true\" />\n {tr('ecoAi.creditHint', 'Each run uses 1 AI credit.')}\n </p>\n );\n};\n\n/** Inline notice for a failed AI run (quota, feature disabled, or other). */\nexport const AiErrorNotice: FC<{ error: EcoAiErrorInfo; className?: string }> = ({\n error,\n className,\n}) => {\n const tr = useTr();\n const title =\n error.kind === 'quota'\n ? tr('ecoAi.errors.quotaTitle', 'AI credits used up')\n : error.kind === 'disabled'\n ? tr('ecoAi.errors.disabledTitle', 'AI monitoring is unavailable')\n : tr('ecoAi.errors.genericTitle', 'The AI run failed');\n return (\n <div\n role=\"alert\"\n data-testid=\"eco-ai-error\"\n data-kind={error.kind}\n className={cn(\n 'flex items-start gap-2 rounded-lg p-3 text-sm',\n error.kind === 'other'\n ? 'bg-status-error-bg-subtle text-status-error-text'\n : 'bg-status-warning-bg-subtle text-status-warning-text',\n className\n )}\n >\n <AlertTriangle className=\"mt-0.5 h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n <div>\n <p className=\"font-medium\">{title}</p>\n <p className=\"mt-0.5\">{error.message}</p>\n </div>\n </div>\n );\n};\n\ninterface CitedReadingsListProps {\n readings: CitedReading[];\n /** The reading the verdict / anomaly is about; marked in the list. */\n focusId?: string;\n emptyLabel?: string;\n}\n\n/** The readings an AI verdict cites, resolved against loaded measurements. */\nexport const CitedReadingsList: FC<CitedReadingsListProps> = ({\n readings,\n focusId,\n emptyLabel,\n}) => {\n const tr = useTr();\n if (readings.length === 0) {\n return (\n <p className=\"text-xs text-text-tertiary\">\n {emptyLabel ?? tr('ecoAi.cited.none', 'No readings cited.')}\n </p>\n );\n }\n return (\n <ul className=\"divide-y divide-border-seam/60 rounded-lg border border-border-seam\">\n {readings.map(({ id, measurement }) => (\n <li\n key={id}\n data-testid=\"eco-ai-cited-reading\"\n data-measurement-id={id}\n className={cn(\n 'flex items-center justify-between gap-3 px-3 py-2 text-xs',\n id === focusId && 'bg-[var(--color-accent-soft)]'\n )}\n >\n {measurement ? (\n <>\n <span className=\"tabular-nums text-text-primary\">\n {formatNumber(measurement.value)} {measurement.unit}\n {id === focusId && (\n <span className=\"ml-2 font-medium text-text-secondary\">\n {tr('ecoAi.cited.thisReading', 'this reading')}\n </span>\n )}\n </span>\n <span className=\"text-text-tertiary\">{formatDateTime(measurement.recordedAt)}</span>\n </>\n ) : (\n <span className=\"font-mono text-text-tertiary\" title={id}>\n {tr('ecoAi.cited.olderReading', 'Reading {{id}} (outside the loaded window)', {\n id: id.slice(0, 8),\n })}\n </span>\n )}\n </li>\n ))}\n </ul>\n );\n};\n"],"mappings":";;;;;;AAcA,IAAa,KAA4C,EAAE,mBAAgB;CACzE,IAAM,IAAK,EAAM;CACjB,OACE,kBAAC,KAAD;EAAG,WAAW,EAAG,sDAAsD,CAAS;YAAhF,CACE,kBAAC,GAAD;GAAU,WAAU;GAA0B,eAAY;EAAQ,CAAA,GACjE,EAAG,oBAAoB,4BAA4B,CACnD;;AAEP,GAGa,KAAoE,EAC/E,UACA,mBACI;CACJ,IAAM,IAAK,EAAM,GACX,IACJ,EAAM,SAAS,UACX,EAAG,2BAA2B,oBAAoB,IAClD,EAAM,SAAS,aACb,EAAG,8BAA8B,8BAA8B,IAC/D,EAAG,6BAA6B,mBAAmB;CAC3D,OACE,kBAAC,OAAD;EACE,MAAK;EACL,eAAY;EACZ,aAAW,EAAM;EACjB,WAAW,EACT,iDACA,EAAM,SAAS,UACX,qDACA,wDACJ,CACF;YAVF,CAYE,kBAAC,GAAD;GAAe,WAAU;GAA0B,eAAY;EAAQ,CAAA,GACvE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;GAAG,WAAU;aAAe;EAAS,CAAA,GACrC,kBAAC,KAAD;GAAG,WAAU;aAAU,EAAM;EAAW,CAAA,CACrC,EAAA,CAAA,CACF;;AAET,GAUa,KAAiD,EAC5D,aACA,YACA,oBACI;CACJ,IAAM,IAAK,EAAM;CAQjB,OAPI,EAAS,WAAW,IAEpB,kBAAC,KAAD;EAAG,WAAU;YACV,KAAc,EAAG,oBAAoB,oBAAoB;CACzD,CAAA,IAIL,kBAAC,MAAD;EAAI,WAAU;YACX,EAAS,KAAK,EAAE,OAAI,qBACnB,kBAAC,MAAD;GAEE,eAAY;GACZ,uBAAqB;GACrB,WAAW,EACT,6DACA,MAAO,KAAW,+BACpB;aAEC,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,QAAD;IAAM,WAAU;cAAhB;KACG,EAAa,EAAY,KAAK;KAAE;KAAE,EAAY;KAC9C,MAAO,KACN,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAG,2BAA2B,cAAc;KACzC,CAAA;IAEJ;OACN,kBAAC,QAAD;IAAM,WAAU;cAAsB,EAAe,EAAY,UAAU;GAAQ,CAAA,CACnF,EAAA,CAAA,IAEF,kBAAC,QAAD;IAAM,WAAU;IAA+B,OAAO;cACnD,EAAG,4BAA4B,8CAA8C,EAC5E,IAAI,EAAG,MAAM,GAAG,CAAC,EACnB,CAAC;GACG,CAAA;EAEN,GA3BG,CA2BH,CACL;CACC,CAAA;AAER"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { ThresholdPrefill } from '../../utils/aiMonitoring';
|
|
3
|
+
|
|
4
|
+
interface AiThresholdSuggestProps {
|
|
5
|
+
siteId: string;
|
|
6
|
+
parameter: string;
|
|
7
|
+
onSuggest: (prefill: ThresholdPrefill) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare const AiThresholdSuggest: FC<AiThresholdSuggestProps>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=AiThresholdSuggest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiThresholdSuggest.d.ts","sourceRoot":"","sources":["../../../../src/ecoimpacthub/components/ai/AiThresholdSuggest.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAY,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAM1C,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,0BAA0B,CAAC;AAIlC,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAChD;AAED,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,uBAAuB,CAqH1D,CAAC"}
|