@burdenoff/microfe-adaptercloud 2026.825.4 → 2026.829.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/adaptercloud/AdapterCloudRoot.d.ts.map +1 -1
- package/dist/adaptercloud/AdapterCloudRoot.js +52 -55
- package/dist/adaptercloud/AdapterCloudRoot.js.map +1 -1
- package/dist/adaptercloud/components/ActiveSwitch.d.ts +17 -0
- package/dist/adaptercloud/components/ActiveSwitch.d.ts.map +1 -0
- package/dist/adaptercloud/components/ActiveSwitch.js +28 -0
- package/dist/adaptercloud/components/ActiveSwitch.js.map +1 -0
- package/dist/adaptercloud/components/index.d.ts +1 -0
- package/dist/adaptercloud/components/index.d.ts.map +1 -1
- package/dist/adaptercloud/hooks/index.d.ts +1 -0
- package/dist/adaptercloud/hooks/index.d.ts.map +1 -1
- package/dist/adaptercloud/hooks/useAdapterCloudApi.d.ts +28 -2
- package/dist/adaptercloud/hooks/useAdapterCloudApi.d.ts.map +1 -1
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js +318 -291
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/hooks/useTr.d.ts +2 -0
- package/dist/adaptercloud/hooks/useTr.d.ts.map +1 -0
- package/dist/adaptercloud/hooks/useTr.js +14 -0
- package/dist/adaptercloud/hooks/useTr.js.map +1 -0
- package/dist/adaptercloud/pages/OverviewPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js +117 -120
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js +6 -5
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +217 -104
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +6 -5
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +183 -156
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.js +108 -107
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +141 -131
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +171 -117
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +85 -66
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +148 -121
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/violations/ViolationsListPage.d.ts.map +1 -1
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js +145 -144
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
- package/dist/utils/pageContext.d.ts +0 -2
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +1 -3
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
|
@@ -6,26 +6,30 @@ import { DataState as i } from "../../components/DataState.js";
|
|
|
6
6
|
import { BarChart as a } from "../../components/viz/BarChart.js";
|
|
7
7
|
import { Sparkline as o } from "../../components/viz/Sparkline.js";
|
|
8
8
|
import "../../components/viz/index.js";
|
|
9
|
-
import {
|
|
10
|
-
import { formatDate as
|
|
11
|
-
import { useCallback as
|
|
12
|
-
import { useI18n as
|
|
13
|
-
import { DollarSign as
|
|
14
|
-
import { Fragment as
|
|
15
|
-
import { EmphasisPanel as
|
|
9
|
+
import { useConnections as s, useCostRecords as c, useCostSummary as l } from "../../hooks/useAdapterCloudApi.js";
|
|
10
|
+
import { formatDate as u } from "../../utils/formatters.js";
|
|
11
|
+
import { useCallback as d, useMemo as f, useState as p } from "react";
|
|
12
|
+
import { useI18n as m } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
13
|
+
import { DollarSign as h, Receipt as g, Search as _, X as v } from "lucide-react";
|
|
14
|
+
import { Fragment as y, jsx as b, jsxs as x } from "react/jsx-runtime";
|
|
15
|
+
import { EmphasisPanel as S, GlassCard as C, ResponsiveTable as w } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/adaptercloud/pages/costs/CostsPage.tsx
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
var T = 100;
|
|
18
|
+
function E() {
|
|
19
|
+
let { navigateTo: E } = e(), { t: D } = m(), O = d((e, t) => {
|
|
20
|
+
let n = D(e);
|
|
20
21
|
return n === e ? t : n;
|
|
21
|
-
}, [
|
|
22
|
+
}, [D]), [k, A] = p(""), [j, M] = p(""), [N, P] = p(""), F = s({ pagination: { limit: T } }).data?.items ?? [], I = l(N || void 0), L = c({
|
|
23
|
+
connectionId: N || void 0,
|
|
24
|
+
service: j || void 0
|
|
25
|
+
}), R = I.data?.currency ?? "USD", z = f(() => [...I.data?.lines ?? []].sort((e, t) => t.total - e.total), [I.data]), B = f(() => z.map((e) => ({
|
|
22
26
|
key: e.service,
|
|
23
27
|
label: e.service,
|
|
24
28
|
value: e.total,
|
|
25
|
-
display: r(e.total,
|
|
26
|
-
})), [
|
|
29
|
+
display: r(e.total, R)
|
|
30
|
+
})), [z, R]), V = f(() => {
|
|
27
31
|
let e = /* @__PURE__ */ new Map();
|
|
28
|
-
for (let t of
|
|
32
|
+
for (let t of L.data ?? []) {
|
|
29
33
|
let n = new Date(t.periodStart);
|
|
30
34
|
if (Number.isNaN(n.getTime())) continue;
|
|
31
35
|
let r = `${n.getUTCFullYear()}-${String(n.getUTCMonth() + 1).padStart(2, "0")}`;
|
|
@@ -36,43 +40,43 @@ function w() {
|
|
|
36
40
|
points: t,
|
|
37
41
|
deltaPct: r > 0 ? Math.round((n - r) / r * 100) : null
|
|
38
42
|
};
|
|
39
|
-
}, [
|
|
40
|
-
e.preventDefault(),
|
|
41
|
-
}, [
|
|
42
|
-
|
|
43
|
-
}, []),
|
|
43
|
+
}, [L.data]), H = d((e) => {
|
|
44
|
+
e.preventDefault(), M(k.trim());
|
|
45
|
+
}, [k]), U = d(() => {
|
|
46
|
+
A(""), M("");
|
|
47
|
+
}, []), W = [
|
|
44
48
|
{
|
|
45
49
|
key: "service",
|
|
46
|
-
header:
|
|
50
|
+
header: O("adaptercloud.costs.service", "Service"),
|
|
47
51
|
priority: "primary",
|
|
48
|
-
cell: (e) => /* @__PURE__ */
|
|
52
|
+
cell: (e) => /* @__PURE__ */ b("span", {
|
|
49
53
|
className: "font-medium text-text-primary",
|
|
50
54
|
children: e.service
|
|
51
55
|
})
|
|
52
56
|
},
|
|
53
57
|
{
|
|
54
58
|
key: "amount",
|
|
55
|
-
header:
|
|
59
|
+
header: O("adaptercloud.costs.amount", "Amount"),
|
|
56
60
|
priority: "primary",
|
|
57
|
-
cell: (e) => /* @__PURE__ */
|
|
61
|
+
cell: (e) => /* @__PURE__ */ b("span", {
|
|
58
62
|
className: "tabular-nums text-text-primary",
|
|
59
63
|
children: r(e.amount, e.currency)
|
|
60
64
|
})
|
|
61
65
|
},
|
|
62
66
|
{
|
|
63
67
|
key: "period",
|
|
64
|
-
header:
|
|
68
|
+
header: O("adaptercloud.costs.period", "Period"),
|
|
65
69
|
priority: "secondary",
|
|
66
|
-
cell: (e) => /* @__PURE__ */
|
|
70
|
+
cell: (e) => /* @__PURE__ */ x("span", {
|
|
67
71
|
className: "text-sm text-text-secondary",
|
|
68
72
|
children: [
|
|
69
|
-
|
|
73
|
+
u(e.periodStart, {
|
|
70
74
|
hour: void 0,
|
|
71
75
|
minute: void 0
|
|
72
76
|
}),
|
|
73
77
|
" –",
|
|
74
78
|
" ",
|
|
75
|
-
|
|
79
|
+
u(e.periodEnd, {
|
|
76
80
|
hour: void 0,
|
|
77
81
|
minute: void 0
|
|
78
82
|
})
|
|
@@ -81,163 +85,186 @@ function w() {
|
|
|
81
85
|
},
|
|
82
86
|
{
|
|
83
87
|
key: "resource",
|
|
84
|
-
header:
|
|
88
|
+
header: O("adaptercloud.costs.resource", "Resource"),
|
|
85
89
|
priority: "secondary",
|
|
86
|
-
cell: (e) => e.resource?.name ? /* @__PURE__ */
|
|
90
|
+
cell: (e) => e.resource?.name ? /* @__PURE__ */ b("button", {
|
|
87
91
|
type: "button",
|
|
88
|
-
onClick: () => e.resourceId &&
|
|
92
|
+
onClick: () => e.resourceId && E(`/resources/${e.resourceId}`),
|
|
89
93
|
className: "max-w-[12rem] truncate text-left text-sm text-accent-blue hover:underline",
|
|
90
94
|
children: e.resource.name
|
|
91
|
-
}) : e.resourceId ? /* @__PURE__ */
|
|
95
|
+
}) : e.resourceId ? /* @__PURE__ */ x("span", {
|
|
92
96
|
className: "font-mono text-xs text-text-secondary",
|
|
93
97
|
children: [e.resourceId.slice(0, 10), "…"]
|
|
94
|
-
}) : /* @__PURE__ */
|
|
98
|
+
}) : /* @__PURE__ */ b("span", {
|
|
95
99
|
className: "text-text-tertiary",
|
|
96
100
|
children: "—"
|
|
97
101
|
})
|
|
98
102
|
},
|
|
99
103
|
{
|
|
100
104
|
key: "connection",
|
|
101
|
-
header:
|
|
105
|
+
header: O("adaptercloud.costs.connection", "Connection"),
|
|
102
106
|
priority: "hidden-on-mobile",
|
|
103
|
-
cell: (e) => e.connection?.name ? /* @__PURE__ */
|
|
107
|
+
cell: (e) => e.connection?.name ? /* @__PURE__ */ b("span", {
|
|
104
108
|
className: "max-w-[12rem] truncate text-sm text-text-primary",
|
|
105
109
|
children: e.connection.name
|
|
106
|
-
}) : /* @__PURE__ */
|
|
110
|
+
}) : /* @__PURE__ */ b("span", {
|
|
107
111
|
className: "text-text-tertiary",
|
|
108
112
|
children: "—"
|
|
109
113
|
})
|
|
110
114
|
}
|
|
111
115
|
];
|
|
112
|
-
return /* @__PURE__ */
|
|
113
|
-
title:
|
|
114
|
-
description:
|
|
115
|
-
children: [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
label: E("adaptercloud.costs.totalSpend", "Total Spend")
|
|
135
|
-
}), P.slice(0, 3).map((e) => /* @__PURE__ */ y(n, {
|
|
136
|
-
icon: /* @__PURE__ */ y(h, { className: "h-5 w-5" }),
|
|
137
|
-
value: r(e.total, N),
|
|
138
|
-
label: e.service
|
|
139
|
-
}, e.service))]
|
|
116
|
+
return /* @__PURE__ */ x(t, {
|
|
117
|
+
title: O("adaptercloud.costs.title", "FinOps"),
|
|
118
|
+
description: O("adaptercloud.costs.subtitle", "Spend attributed to the same resources you operate — cost and topology share one model."),
|
|
119
|
+
children: [
|
|
120
|
+
/* @__PURE__ */ x("div", {
|
|
121
|
+
className: "mb-6 flex flex-wrap items-center gap-3",
|
|
122
|
+
children: [/* @__PURE__ */ b("label", {
|
|
123
|
+
htmlFor: "ada-cost-connection",
|
|
124
|
+
className: "text-sm text-text-secondary",
|
|
125
|
+
children: O("adaptercloud.costs.connection", "Connection")
|
|
126
|
+
}), /* @__PURE__ */ x("select", {
|
|
127
|
+
id: "ada-cost-connection",
|
|
128
|
+
value: N,
|
|
129
|
+
onChange: (e) => P(e.target.value),
|
|
130
|
+
className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
131
|
+
children: [/* @__PURE__ */ b("option", {
|
|
132
|
+
value: "",
|
|
133
|
+
children: O("adaptercloud.costs.allConnections", "All connections")
|
|
134
|
+
}), F.map((e) => /* @__PURE__ */ b("option", {
|
|
135
|
+
value: e.id,
|
|
136
|
+
children: e.name
|
|
137
|
+
}, e.id))]
|
|
140
138
|
})]
|
|
141
|
-
}),
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
139
|
+
}),
|
|
140
|
+
/* @__PURE__ */ x(i, {
|
|
141
|
+
isLoading: I.isLoading,
|
|
142
|
+
isError: I.isError,
|
|
143
|
+
error: I.error,
|
|
144
|
+
onRetry: () => void I.refetch(),
|
|
145
|
+
children: [/* @__PURE__ */ x(S, {
|
|
146
|
+
className: "mb-8",
|
|
147
|
+
children: [/* @__PURE__ */ b("div", {
|
|
148
|
+
className: "mb-4 flex items-center justify-between",
|
|
149
|
+
children: /* @__PURE__ */ b("h2", {
|
|
150
|
+
className: "text-lg font-semibold text-text-primary",
|
|
151
|
+
children: O("adaptercloud.costs.summary", "Cost Summary")
|
|
153
152
|
})
|
|
153
|
+
}), /* @__PURE__ */ x("div", {
|
|
154
|
+
className: "grid grid-cols-2 gap-4 lg:grid-cols-4",
|
|
155
|
+
children: [/* @__PURE__ */ b(n, {
|
|
156
|
+
featured: !0,
|
|
157
|
+
icon: /* @__PURE__ */ b(h, { className: "h-5 w-5" }),
|
|
158
|
+
value: I.data ? r(I.data.total, R) : "—",
|
|
159
|
+
label: O("adaptercloud.costs.totalSpend", "Total Spend")
|
|
160
|
+
}), z.slice(0, 3).map((e) => /* @__PURE__ */ b(n, {
|
|
161
|
+
icon: /* @__PURE__ */ b(g, { className: "h-5 w-5" }),
|
|
162
|
+
value: r(e.total, R),
|
|
163
|
+
label: e.service
|
|
164
|
+
}, e.service))]
|
|
154
165
|
})]
|
|
155
|
-
}),
|
|
156
|
-
className: "mb-
|
|
157
|
-
children:
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
className: "
|
|
164
|
-
children:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
I.deltaPct > 0 ? "▲" : "▼",
|
|
169
|
-
" ",
|
|
170
|
-
Math.abs(I.deltaPct),
|
|
171
|
-
"%",
|
|
172
|
-
" ",
|
|
173
|
-
E("adaptercloud.costs.vsPrev", "vs prev")
|
|
174
|
-
]
|
|
166
|
+
}), z.length > 0 && /* @__PURE__ */ x("div", {
|
|
167
|
+
className: "mb-8 grid grid-cols-1 gap-6 lg:grid-cols-3",
|
|
168
|
+
children: [/* @__PURE__ */ x("section", {
|
|
169
|
+
className: "lg:col-span-2",
|
|
170
|
+
children: [/* @__PURE__ */ b("h2", {
|
|
171
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
172
|
+
children: O("adaptercloud.costs.byService", "Spend by Service")
|
|
173
|
+
}), /* @__PURE__ */ b(C, {
|
|
174
|
+
className: "p-5",
|
|
175
|
+
children: /* @__PURE__ */ b(a, {
|
|
176
|
+
data: B,
|
|
177
|
+
labelWidthClassName: "w-24 sm:w-32"
|
|
178
|
+
})
|
|
175
179
|
})]
|
|
176
|
-
}), /* @__PURE__ */
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
})
|
|
180
|
-
className: "
|
|
181
|
-
children:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
onClick: R,
|
|
206
|
-
"aria-label": E("adaptercloud.costs.clearServiceFilter", "Clear service filter"),
|
|
207
|
-
className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
208
|
-
children: /* @__PURE__ */ y(_, { className: "h-4 w-4" })
|
|
209
|
-
})]
|
|
210
|
-
}), /* @__PURE__ */ y("button", {
|
|
211
|
-
type: "submit",
|
|
212
|
-
"aria-label": E("adaptercloud.costs.applyServiceFilter", "Apply service filter"),
|
|
213
|
-
className: "inline-flex shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-text-primary transition-colors hover:bg-accent focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
214
|
-
children: /* @__PURE__ */ y(g, { className: "h-4 w-4" })
|
|
180
|
+
}), /* @__PURE__ */ x("section", { children: [/* @__PURE__ */ b("h2", {
|
|
181
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
182
|
+
children: O("adaptercloud.costs.trend", "Monthly Trend")
|
|
183
|
+
}), /* @__PURE__ */ b(C, {
|
|
184
|
+
className: "flex h-[calc(100%-2.75rem)] flex-col justify-between p-5",
|
|
185
|
+
children: V.points.length >= 2 ? /* @__PURE__ */ x(y, { children: [/* @__PURE__ */ x("div", {
|
|
186
|
+
className: "flex items-baseline gap-2",
|
|
187
|
+
children: [/* @__PURE__ */ b("span", {
|
|
188
|
+
className: "text-2xl font-bold tabular-nums text-text-primary",
|
|
189
|
+
children: r(V.points[V.points.length - 1] ?? 0, R)
|
|
190
|
+
}), V.deltaPct !== null && /* @__PURE__ */ x("span", {
|
|
191
|
+
className: `text-xs font-semibold tabular-nums ${V.deltaPct > 0 ? "text-status-error-text" : "text-status-success-text"}`,
|
|
192
|
+
children: [
|
|
193
|
+
V.deltaPct > 0 ? "▲" : "▼",
|
|
194
|
+
" ",
|
|
195
|
+
Math.abs(V.deltaPct),
|
|
196
|
+
"%",
|
|
197
|
+
" ",
|
|
198
|
+
O("adaptercloud.costs.vsPrev", "vs prev")
|
|
199
|
+
]
|
|
200
|
+
})]
|
|
201
|
+
}), /* @__PURE__ */ b(o, {
|
|
202
|
+
points: V.points,
|
|
203
|
+
className: "mt-4 h-16 w-full text-accent-blue"
|
|
204
|
+
})] }) : /* @__PURE__ */ b("p", {
|
|
205
|
+
className: "py-6 text-center text-sm text-text-secondary",
|
|
206
|
+
children: O("adaptercloud.costs.trendEmpty", "Ingest at least two months of cost to see a trend.")
|
|
207
|
+
})
|
|
208
|
+
})] })]
|
|
215
209
|
})]
|
|
216
|
-
})]
|
|
217
|
-
}), /* @__PURE__ */ y(i, {
|
|
218
|
-
isLoading: M.isLoading,
|
|
219
|
-
isError: M.isError,
|
|
220
|
-
isEmpty: (M.data ?? []).length === 0,
|
|
221
|
-
error: M.error,
|
|
222
|
-
onRetry: () => void M.refetch(),
|
|
223
|
-
emptyTitle: E("adaptercloud.costs.emptyTitle", "No cost records"),
|
|
224
|
-
emptyDescription: E("adaptercloud.costs.emptyDescription", "Run a cost-ingest sync against a connection to populate the ledger."),
|
|
225
|
-
emptyIcon: /* @__PURE__ */ y(h, { className: "h-10 w-10" }),
|
|
226
|
-
emptyAction: /* @__PURE__ */ y("button", {
|
|
227
|
-
type: "button",
|
|
228
|
-
onClick: () => w("/connections"),
|
|
229
|
-
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
|
|
230
|
-
children: E("adaptercloud.costs.goToConnections", "Go to connections")
|
|
231
210
|
}),
|
|
232
|
-
children: /* @__PURE__ */
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
211
|
+
/* @__PURE__ */ x("section", { children: [/* @__PURE__ */ x("div", {
|
|
212
|
+
className: "mb-4 flex flex-wrap items-center justify-between gap-3",
|
|
213
|
+
children: [/* @__PURE__ */ b("h2", {
|
|
214
|
+
className: "text-lg font-semibold text-text-primary",
|
|
215
|
+
children: O("adaptercloud.costs.records", "Cost Records")
|
|
216
|
+
}), /* @__PURE__ */ x("form", {
|
|
217
|
+
onSubmit: H,
|
|
218
|
+
role: "search",
|
|
219
|
+
className: "flex w-full items-center gap-2 sm:w-auto",
|
|
220
|
+
children: [/* @__PURE__ */ x("div", {
|
|
221
|
+
className: "relative w-full sm:w-56",
|
|
222
|
+
children: [/* @__PURE__ */ b("input", {
|
|
223
|
+
type: "text",
|
|
224
|
+
value: k,
|
|
225
|
+
onChange: (e) => A(e.target.value),
|
|
226
|
+
"aria-label": O("adaptercloud.costs.filterService", "Filter by service"),
|
|
227
|
+
placeholder: O("adaptercloud.costs.filterServicePlaceholder", "Filter by service…"),
|
|
228
|
+
className: "w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-3 pr-9 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
229
|
+
}), j && /* @__PURE__ */ b("button", {
|
|
230
|
+
type: "button",
|
|
231
|
+
onClick: U,
|
|
232
|
+
"aria-label": O("adaptercloud.costs.clearServiceFilter", "Clear service filter"),
|
|
233
|
+
className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
234
|
+
children: /* @__PURE__ */ b(v, { className: "h-4 w-4" })
|
|
235
|
+
})]
|
|
236
|
+
}), /* @__PURE__ */ b("button", {
|
|
237
|
+
type: "submit",
|
|
238
|
+
"aria-label": O("adaptercloud.costs.applyServiceFilter", "Apply service filter"),
|
|
239
|
+
className: "inline-flex shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-text-primary transition-colors hover:bg-accent focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
240
|
+
children: /* @__PURE__ */ b(_, { className: "h-4 w-4" })
|
|
241
|
+
})]
|
|
242
|
+
})]
|
|
243
|
+
}), /* @__PURE__ */ b(i, {
|
|
244
|
+
isLoading: L.isLoading,
|
|
245
|
+
isError: L.isError,
|
|
246
|
+
isEmpty: (L.data ?? []).length === 0,
|
|
247
|
+
error: L.error,
|
|
248
|
+
onRetry: () => void L.refetch(),
|
|
249
|
+
emptyTitle: O("adaptercloud.costs.emptyTitle", "No cost records"),
|
|
250
|
+
emptyDescription: O("adaptercloud.costs.emptyDescription", "Run a cost-ingest sync against a connection to populate the ledger."),
|
|
251
|
+
emptyIcon: /* @__PURE__ */ b(g, { className: "h-10 w-10" }),
|
|
252
|
+
emptyAction: /* @__PURE__ */ b("button", {
|
|
253
|
+
type: "button",
|
|
254
|
+
onClick: () => E("/connections"),
|
|
255
|
+
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
|
|
256
|
+
children: O("adaptercloud.costs.goToConnections", "Go to connections")
|
|
257
|
+
}),
|
|
258
|
+
children: /* @__PURE__ */ b(w, {
|
|
259
|
+
data: L.data ?? [],
|
|
260
|
+
columns: W,
|
|
261
|
+
rowKey: (e) => e.id
|
|
262
|
+
})
|
|
263
|
+
})] })
|
|
264
|
+
]
|
|
238
265
|
});
|
|
239
266
|
}
|
|
240
267
|
//#endregion
|
|
241
|
-
export {
|
|
268
|
+
export { E as CostsPage };
|
|
242
269
|
|
|
243
270
|
//# sourceMappingURL=CostsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CostsPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/costs/CostsPage.tsx"],"sourcesContent":["/**\n * Costs (FinOps) Page\n * @module adaptercloud/pages/costs\n *\n * Workspace FinOps: the cost summary by service (`adaCostSummary`) plus the raw\n * cost-record ledger (`adaListCostRecords`), optionally filtered by service.\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { DollarSign, Search, Receipt, X } from 'lucide-react';\nimport {\n EmphasisPanel,\n GlassCard,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { DataState } from '../../components/DataState';\nimport { BarChart, Sparkline } from '../../components/viz';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useCostSummary, useCostRecords } from '../../hooks/useAdapterCloudApi';\nimport { formatMoney } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport type { AdaCostRecord } from '../../types';\n\nexport function CostsPage() {\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const [serviceInput, setServiceInput] = useState('');\n const [appliedService, setAppliedService] = useState('');\n\n const summary = useCostSummary();\n const records = useCostRecords({ service: appliedService || undefined });\n\n const currency = summary.data?.currency ?? 'USD';\n\n const sortedLines = useMemo(\n () => [...(summary.data?.lines ?? [])].sort((a, b) => b.total - a.total),\n [summary.data]\n );\n\n // Cost-by-service bars for the BarChart (service · magnitude · $).\n const serviceBars = useMemo(\n () =>\n sortedLines.map((line) => ({\n key: line.service,\n label: line.service,\n value: line.total,\n display: formatMoney(line.total, currency),\n })),\n [sortedLines, currency]\n );\n\n // Monthly cost trend, derived from the raw ledger (periodStart → YYYY-MM buckets).\n const trend = useMemo(() => {\n const buckets = new Map<string, number>();\n for (const r of records.data ?? []) {\n const d = new Date(r.periodStart);\n if (Number.isNaN(d.getTime())) continue;\n const key = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`;\n buckets.set(key, (buckets.get(key) ?? 0) + r.amount);\n }\n const series = [...buckets.entries()].sort(([a], [b]) => a.localeCompare(b));\n const points = series.map(([, v]) => v);\n const last = points[points.length - 1] ?? 0;\n const prev = points[points.length - 2] ?? 0;\n const deltaPct = prev > 0 ? Math.round(((last - prev) / prev) * 100) : null;\n return { points, deltaPct };\n }, [records.data]);\n\n const onSearch = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setAppliedService(serviceInput.trim());\n },\n [serviceInput]\n );\n\n const clearService = useCallback(() => {\n setServiceInput('');\n setAppliedService('');\n }, []);\n\n const recordColumns: ResponsiveTableColumn<AdaCostRecord>[] = [\n {\n key: 'service',\n header: t('adaptercloud.costs.service', 'Service'),\n priority: 'primary',\n cell: (r) => <span className=\"font-medium text-text-primary\">{r.service}</span>,\n },\n {\n key: 'amount',\n header: t('adaptercloud.costs.amount', 'Amount'),\n priority: 'primary',\n cell: (r) => (\n <span className=\"tabular-nums text-text-primary\">{formatMoney(r.amount, r.currency)}</span>\n ),\n },\n {\n key: 'period',\n header: t('adaptercloud.costs.period', 'Period'),\n priority: 'secondary',\n cell: (r) => (\n <span className=\"text-sm text-text-secondary\">\n {formatDate(r.periodStart, { hour: undefined, minute: undefined })} –{' '}\n {formatDate(r.periodEnd, { hour: undefined, minute: undefined })}\n </span>\n ),\n },\n {\n key: 'resource',\n header: t('adaptercloud.costs.resource', 'Resource'),\n priority: 'secondary',\n cell: (r) =>\n r.resource?.name ? (\n <button\n type=\"button\"\n onClick={() => r.resourceId && navigateTo(`/resources/${r.resourceId}`)}\n className=\"max-w-[12rem] truncate text-left text-sm text-accent-blue hover:underline\"\n >\n {r.resource.name}\n </button>\n ) : r.resourceId ? (\n <span className=\"font-mono text-xs text-text-secondary\">\n {r.resourceId.slice(0, 10)}…\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'connection',\n header: t('adaptercloud.costs.connection', 'Connection'),\n priority: 'hidden-on-mobile',\n cell: (r) =>\n r.connection?.name ? (\n <span className=\"max-w-[12rem] truncate text-sm text-text-primary\">\n {r.connection.name}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('adaptercloud.costs.title', 'FinOps')}\n description={t(\n 'adaptercloud.costs.subtitle',\n 'Spend attributed to the same resources you operate — cost and topology share one model.'\n )}\n >\n <DataState\n isLoading={summary.isLoading}\n isError={summary.isError}\n error={summary.error}\n onRetry={() => void summary.refetch()}\n >\n {/* Total + by-service */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.summary', 'Cost Summary')}\n </h2>\n </div>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n featured\n icon={<DollarSign className=\"h-5 w-5\" />}\n value={summary.data ? formatMoney(summary.data.total, currency) : '—'}\n label={t('adaptercloud.costs.totalSpend', 'Total Spend')}\n />\n {sortedLines.slice(0, 3).map((line) => (\n <MetricsCard\n key={line.service}\n icon={<Receipt className=\"h-5 w-5\" />}\n value={formatMoney(line.total, currency)}\n label={line.service}\n />\n ))}\n </div>\n </EmphasisPanel>\n\n {/* Full by-service breakdown + monthly trend */}\n {sortedLines.length > 0 && (\n <div className=\"mb-8 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <section className=\"lg:col-span-2\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.byService', 'Spend by Service')}\n </h2>\n <GlassCard className=\"p-5\">\n <BarChart data={serviceBars} labelWidthClassName=\"w-24 sm:w-32\" />\n </GlassCard>\n </section>\n\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.trend', 'Monthly Trend')}\n </h2>\n <GlassCard className=\"flex h-[calc(100%-2.75rem)] flex-col justify-between p-5\">\n {trend.points.length >= 2 ? (\n <>\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatMoney(trend.points[trend.points.length - 1] ?? 0, currency)}\n </span>\n {trend.deltaPct !== null && (\n <span\n className={`text-xs font-semibold tabular-nums ${\n trend.deltaPct > 0\n ? 'text-status-error-text'\n : 'text-status-success-text'\n }`}\n >\n {trend.deltaPct > 0 ? '▲' : '▼'} {Math.abs(trend.deltaPct)}%{' '}\n {t('adaptercloud.costs.vsPrev', 'vs prev')}\n </span>\n )}\n </div>\n <Sparkline\n points={trend.points}\n className=\"mt-4 h-16 w-full text-accent-blue\"\n />\n </>\n ) : (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t(\n 'adaptercloud.costs.trendEmpty',\n 'Ingest at least two months of cost to see a trend.'\n )}\n </p>\n )}\n </GlassCard>\n </section>\n </div>\n )}\n </DataState>\n\n {/* Cost records ledger */}\n <section>\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.records', 'Cost Records')}\n </h2>\n <form\n onSubmit={onSearch}\n role=\"search\"\n className=\"flex w-full items-center gap-2 sm:w-auto\"\n >\n <div className=\"relative w-full sm:w-56\">\n <input\n type=\"text\"\n value={serviceInput}\n onChange={(e) => setServiceInput(e.target.value)}\n aria-label={t('adaptercloud.costs.filterService', 'Filter by service')}\n placeholder={t('adaptercloud.costs.filterServicePlaceholder', 'Filter by service…')}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-3 pr-9 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {appliedService && (\n <button\n type=\"button\"\n onClick={clearService}\n aria-label={t('adaptercloud.costs.clearServiceFilter', 'Clear service filter')}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n <button\n type=\"submit\"\n aria-label={t('adaptercloud.costs.applyServiceFilter', 'Apply service filter')}\n className=\"inline-flex shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-text-primary transition-colors hover:bg-accent focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <Search className=\"h-4 w-4\" />\n </button>\n </form>\n </div>\n\n <DataState\n isLoading={records.isLoading}\n isError={records.isError}\n isEmpty={(records.data ?? []).length === 0}\n error={records.error}\n onRetry={() => void records.refetch()}\n emptyTitle={t('adaptercloud.costs.emptyTitle', 'No cost records')}\n emptyDescription={t(\n 'adaptercloud.costs.emptyDescription',\n 'Run a cost-ingest sync against a connection to populate the ledger.'\n )}\n emptyIcon={<Receipt className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n {t('adaptercloud.costs.goToConnections', 'Go to connections')}\n </button>\n }\n >\n <ResponsiveTable data={records.data ?? []} columns={recordColumns} rowKey={(r) => r.id} />\n </DataState>\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CostsPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/costs/CostsPage.tsx"],"sourcesContent":["/**\n * Costs (FinOps) Page\n * @module adaptercloud/pages/costs\n *\n * Workspace FinOps: the cost summary by service (`adaCostSummary`) plus the raw\n * cost-record ledger (`adaListCostRecords`), optionally filtered by service.\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { DollarSign, Search, Receipt, X } from 'lucide-react';\nimport {\n EmphasisPanel,\n GlassCard,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { DataState } from '../../components/DataState';\nimport { BarChart, Sparkline } from '../../components/viz';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useConnections, useCostSummary, useCostRecords } from '../../hooks/useAdapterCloudApi';\nimport { formatMoney } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport type { AdaCostRecord } from '../../types';\n\n/** How many connections the scope dropdown offers (one page of the inventory). */\nconst CONNECTION_OPTIONS_LIMIT = 100;\n\nexport function CostsPage() {\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const [serviceInput, setServiceInput] = useState('');\n const [appliedService, setAppliedService] = useState('');\n const [connectionId, setConnectionId] = useState('');\n\n // `adaCostSummary(connectionId)` and `adaListCostRecords(connectionId)` both\n // scope server-side, so one control narrows the total, the by-service\n // breakdown, the trend and the ledger consistently.\n const connectionsQuery = useConnections({ pagination: { limit: CONNECTION_OPTIONS_LIMIT } });\n const connectionOptions = connectionsQuery.data?.items ?? [];\n\n const summary = useCostSummary(connectionId || undefined);\n const records = useCostRecords({\n connectionId: connectionId || undefined,\n service: appliedService || undefined,\n });\n\n const currency = summary.data?.currency ?? 'USD';\n\n const sortedLines = useMemo(\n () => [...(summary.data?.lines ?? [])].sort((a, b) => b.total - a.total),\n [summary.data]\n );\n\n // Cost-by-service bars for the BarChart (service · magnitude · $).\n const serviceBars = useMemo(\n () =>\n sortedLines.map((line) => ({\n key: line.service,\n label: line.service,\n value: line.total,\n display: formatMoney(line.total, currency),\n })),\n [sortedLines, currency]\n );\n\n // Monthly cost trend, derived from the raw ledger (periodStart → YYYY-MM buckets).\n const trend = useMemo(() => {\n const buckets = new Map<string, number>();\n for (const r of records.data ?? []) {\n const d = new Date(r.periodStart);\n if (Number.isNaN(d.getTime())) continue;\n const key = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, '0')}`;\n buckets.set(key, (buckets.get(key) ?? 0) + r.amount);\n }\n const series = [...buckets.entries()].sort(([a], [b]) => a.localeCompare(b));\n const points = series.map(([, v]) => v);\n const last = points[points.length - 1] ?? 0;\n const prev = points[points.length - 2] ?? 0;\n const deltaPct = prev > 0 ? Math.round(((last - prev) / prev) * 100) : null;\n return { points, deltaPct };\n }, [records.data]);\n\n const onSearch = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setAppliedService(serviceInput.trim());\n },\n [serviceInput]\n );\n\n const clearService = useCallback(() => {\n setServiceInput('');\n setAppliedService('');\n }, []);\n\n const recordColumns: ResponsiveTableColumn<AdaCostRecord>[] = [\n {\n key: 'service',\n header: t('adaptercloud.costs.service', 'Service'),\n priority: 'primary',\n cell: (r) => <span className=\"font-medium text-text-primary\">{r.service}</span>,\n },\n {\n key: 'amount',\n header: t('adaptercloud.costs.amount', 'Amount'),\n priority: 'primary',\n cell: (r) => (\n <span className=\"tabular-nums text-text-primary\">{formatMoney(r.amount, r.currency)}</span>\n ),\n },\n {\n key: 'period',\n header: t('adaptercloud.costs.period', 'Period'),\n priority: 'secondary',\n cell: (r) => (\n <span className=\"text-sm text-text-secondary\">\n {formatDate(r.periodStart, { hour: undefined, minute: undefined })} –{' '}\n {formatDate(r.periodEnd, { hour: undefined, minute: undefined })}\n </span>\n ),\n },\n {\n key: 'resource',\n header: t('adaptercloud.costs.resource', 'Resource'),\n priority: 'secondary',\n cell: (r) =>\n r.resource?.name ? (\n <button\n type=\"button\"\n onClick={() => r.resourceId && navigateTo(`/resources/${r.resourceId}`)}\n className=\"max-w-[12rem] truncate text-left text-sm text-accent-blue hover:underline\"\n >\n {r.resource.name}\n </button>\n ) : r.resourceId ? (\n <span className=\"font-mono text-xs text-text-secondary\">\n {r.resourceId.slice(0, 10)}…\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'connection',\n header: t('adaptercloud.costs.connection', 'Connection'),\n priority: 'hidden-on-mobile',\n cell: (r) =>\n r.connection?.name ? (\n <span className=\"max-w-[12rem] truncate text-sm text-text-primary\">\n {r.connection.name}\n </span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('adaptercloud.costs.title', 'FinOps')}\n description={t(\n 'adaptercloud.costs.subtitle',\n 'Spend attributed to the same resources you operate — cost and topology share one model.'\n )}\n >\n {/* Estate scope — narrows every panel on this page to one connection. */}\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <label htmlFor=\"ada-cost-connection\" className=\"text-sm text-text-secondary\">\n {t('adaptercloud.costs.connection', 'Connection')}\n </label>\n <select\n id=\"ada-cost-connection\"\n value={connectionId}\n onChange={(e) => setConnectionId(e.target.value)}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.costs.allConnections', 'All connections')}</option>\n {connectionOptions.map((c) => (\n <option key={c.id} value={c.id}>\n {c.name}\n </option>\n ))}\n </select>\n </div>\n\n <DataState\n isLoading={summary.isLoading}\n isError={summary.isError}\n error={summary.error}\n onRetry={() => void summary.refetch()}\n >\n {/* Total + by-service */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.summary', 'Cost Summary')}\n </h2>\n </div>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n featured\n icon={<DollarSign className=\"h-5 w-5\" />}\n value={summary.data ? formatMoney(summary.data.total, currency) : '—'}\n label={t('adaptercloud.costs.totalSpend', 'Total Spend')}\n />\n {sortedLines.slice(0, 3).map((line) => (\n <MetricsCard\n key={line.service}\n icon={<Receipt className=\"h-5 w-5\" />}\n value={formatMoney(line.total, currency)}\n label={line.service}\n />\n ))}\n </div>\n </EmphasisPanel>\n\n {/* Full by-service breakdown + monthly trend */}\n {sortedLines.length > 0 && (\n <div className=\"mb-8 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n <section className=\"lg:col-span-2\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.byService', 'Spend by Service')}\n </h2>\n <GlassCard className=\"p-5\">\n <BarChart data={serviceBars} labelWidthClassName=\"w-24 sm:w-32\" />\n </GlassCard>\n </section>\n\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.trend', 'Monthly Trend')}\n </h2>\n <GlassCard className=\"flex h-[calc(100%-2.75rem)] flex-col justify-between p-5\">\n {trend.points.length >= 2 ? (\n <>\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatMoney(trend.points[trend.points.length - 1] ?? 0, currency)}\n </span>\n {trend.deltaPct !== null && (\n <span\n className={`text-xs font-semibold tabular-nums ${\n trend.deltaPct > 0\n ? 'text-status-error-text'\n : 'text-status-success-text'\n }`}\n >\n {trend.deltaPct > 0 ? '▲' : '▼'} {Math.abs(trend.deltaPct)}%{' '}\n {t('adaptercloud.costs.vsPrev', 'vs prev')}\n </span>\n )}\n </div>\n <Sparkline\n points={trend.points}\n className=\"mt-4 h-16 w-full text-accent-blue\"\n />\n </>\n ) : (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {t(\n 'adaptercloud.costs.trendEmpty',\n 'Ingest at least two months of cost to see a trend.'\n )}\n </p>\n )}\n </GlassCard>\n </section>\n </div>\n )}\n </DataState>\n\n {/* Cost records ledger */}\n <section>\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.records', 'Cost Records')}\n </h2>\n <form\n onSubmit={onSearch}\n role=\"search\"\n className=\"flex w-full items-center gap-2 sm:w-auto\"\n >\n <div className=\"relative w-full sm:w-56\">\n <input\n type=\"text\"\n value={serviceInput}\n onChange={(e) => setServiceInput(e.target.value)}\n aria-label={t('adaptercloud.costs.filterService', 'Filter by service')}\n placeholder={t('adaptercloud.costs.filterServicePlaceholder', 'Filter by service…')}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2 pl-3 pr-9 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {appliedService && (\n <button\n type=\"button\"\n onClick={clearService}\n aria-label={t('adaptercloud.costs.clearServiceFilter', 'Clear service filter')}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n )}\n </div>\n <button\n type=\"submit\"\n aria-label={t('adaptercloud.costs.applyServiceFilter', 'Apply service filter')}\n className=\"inline-flex shrink-0 items-center justify-center rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-text-primary transition-colors hover:bg-accent focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <Search className=\"h-4 w-4\" />\n </button>\n </form>\n </div>\n\n <DataState\n isLoading={records.isLoading}\n isError={records.isError}\n isEmpty={(records.data ?? []).length === 0}\n error={records.error}\n onRetry={() => void records.refetch()}\n emptyTitle={t('adaptercloud.costs.emptyTitle', 'No cost records')}\n emptyDescription={t(\n 'adaptercloud.costs.emptyDescription',\n 'Run a cost-ingest sync against a connection to populate the ledger.'\n )}\n emptyIcon={<Receipt className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n {t('adaptercloud.costs.goToConnections', 'Go to connections')}\n </button>\n }\n >\n <ResponsiveTable data={records.data ?? []} columns={recordColumns} rowKey={(r) => r.id} />\n </DataState>\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DriftListPage.d.ts","sourceRoot":"","sources":["../../../../src/adaptercloud/pages/drift/DriftListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAsCH,wBAAgB,aAAa,
|
|
1
|
+
{"version":3,"file":"DriftListPage.d.ts","sourceRoot":"","sources":["../../../../src/adaptercloud/pages/drift/DriftListPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAsCH,wBAAgB,aAAa,gCA2P5B"}
|