@carlonicora/nextjs-jsonapi 1.141.3 → 1.143.0

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.
Files changed (117) hide show
  1. package/dist/AbstractService-B0T7h8fX.d.mts +124 -0
  2. package/dist/AbstractService-Cew0PD0L.d.ts +124 -0
  3. package/dist/{AssistantMessageInterface-DH1QwtC_.d.mts → AssistantMessageInterface-Ce8FcllX.d.mts} +1 -1
  4. package/dist/{AssistantMessageInterface-giWvsOrX.d.ts → AssistantMessageInterface-Dh0BpbP2.d.ts} +1 -1
  5. package/dist/{AuthComponent-DdxCFgUZ.d.mts → AuthComponent-Cik96ElA.d.mts} +1 -1
  6. package/dist/{AuthComponent-Cd7lcYif.d.ts → AuthComponent-DTTKu3VK.d.ts} +1 -1
  7. package/dist/{BlockNoteEditor-JTKEBS2W.mjs → BlockNoteEditor-34QM4UR5.mjs} +4 -4
  8. package/dist/{BlockNoteEditor-4OXY6BKF.js → BlockNoteEditor-LMKWUOJG.js} +19 -19
  9. package/dist/{BlockNoteEditor-4OXY6BKF.js.map → BlockNoteEditor-LMKWUOJG.js.map} +1 -1
  10. package/dist/{auth.interface-yivTb46w.d.ts → auth.interface-COmBjh89.d.ts} +1 -1
  11. package/dist/{auth.interface-r3XKYOrQ.d.mts → auth.interface-CkmZqat3.d.mts} +1 -1
  12. package/dist/billing/index.js +357 -357
  13. package/dist/billing/index.mjs +3 -3
  14. package/dist/{chunk-5GVY2M5X.mjs → chunk-BIL2WQEV.mjs} +264 -18
  15. package/dist/chunk-BIL2WQEV.mjs.map +1 -0
  16. package/dist/{chunk-AGKZCRG2.js → chunk-DS4IBFWA.js} +269 -23
  17. package/dist/chunk-DS4IBFWA.js.map +1 -0
  18. package/dist/{chunk-4TGGHNWL.js → chunk-F6PZZ4KI.js} +752 -752
  19. package/dist/chunk-F6PZZ4KI.js.map +1 -0
  20. package/dist/{chunk-KXTTYXJ6.mjs → chunk-HN5ED7OF.mjs} +2 -2
  21. package/dist/{chunk-K76QSQTC.mjs → chunk-UGSOBBZH.mjs} +28 -28
  22. package/dist/chunk-UGSOBBZH.mjs.map +1 -0
  23. package/dist/{chunk-SE2YSAER.js → chunk-UXJAS6C5.js} +7 -7
  24. package/dist/{chunk-SE2YSAER.js.map → chunk-UXJAS6C5.js.map} +1 -1
  25. package/dist/client/index.d.mts +5 -5
  26. package/dist/client/index.d.ts +5 -5
  27. package/dist/client/index.js +4 -4
  28. package/dist/client/index.mjs +3 -3
  29. package/dist/components/index.d.mts +6 -6
  30. package/dist/components/index.d.ts +6 -6
  31. package/dist/components/index.js +4 -4
  32. package/dist/components/index.mjs +3 -3
  33. package/dist/{config-BrkU_Ynz.d.ts → config-Cd5xTEfZ.d.ts} +1 -1
  34. package/dist/{config-snd2ToEB.d.mts → config-YyhFHxkU.d.mts} +1 -1
  35. package/dist/contexts/index.d.mts +4 -4
  36. package/dist/contexts/index.d.ts +4 -4
  37. package/dist/contexts/index.js +4 -4
  38. package/dist/contexts/index.mjs +3 -3
  39. package/dist/core/index.d.mts +24 -60
  40. package/dist/core/index.d.ts +24 -60
  41. package/dist/core/index.js +8 -2
  42. package/dist/core/index.js.map +1 -1
  43. package/dist/core/index.mjs +7 -1
  44. package/dist/features/help/index.d.mts +2 -2
  45. package/dist/features/help/index.d.ts +2 -2
  46. package/dist/features/help/index.js +37 -37
  47. package/dist/features/help/index.mjs +3 -3
  48. package/dist/features/tokenusage/index.css +29 -0
  49. package/dist/features/tokenusage/index.css.map +1 -0
  50. package/dist/features/tokenusage/index.d.mts +388 -0
  51. package/dist/features/tokenusage/index.d.ts +388 -0
  52. package/dist/features/tokenusage/index.js +1012 -0
  53. package/dist/features/tokenusage/index.js.map +1 -0
  54. package/dist/features/tokenusage/index.mjs +1012 -0
  55. package/dist/features/tokenusage/index.mjs.map +1 -0
  56. package/dist/index.d.mts +11 -9
  57. package/dist/index.d.ts +11 -9
  58. package/dist/index.js +9 -3
  59. package/dist/index.js.map +1 -1
  60. package/dist/index.mjs +8 -2
  61. package/dist/{notification.interface-LKFmZndR.d.ts → notification.interface--27exGde.d.ts} +6 -6
  62. package/dist/{notification.interface-B3kNUTZs.d.mts → notification.interface-CG1vqoxg.d.mts} +6 -6
  63. package/dist/{s3.service-BrmeMjtk.d.ts → s3.service-CpwA3sl_.d.ts} +4 -125
  64. package/dist/{s3.service-CXKV_OT-.d.mts → s3.service-Dt6MfuHr.d.mts} +4 -125
  65. package/dist/server/index.d.mts +5 -4
  66. package/dist/server/index.d.ts +5 -4
  67. package/dist/server/index.js +3 -3
  68. package/dist/server/index.mjs +1 -1
  69. package/dist/tokenusage-admin.module-5wJ_tZTj.d.ts +61 -0
  70. package/dist/tokenusage-admin.module-DQVT4O4j.d.mts +61 -0
  71. package/dist/{types-CQSjy7et.d.mts → types-Bq_UA8Lg.d.mts} +1 -1
  72. package/dist/{types-DHOxe8rc.d.ts → types-bsm8Fp55.d.ts} +1 -1
  73. package/dist/{usePageUrlGenerator-tjq2mlDV.d.ts → usePageUrlGenerator-J_ECv-oQ.d.ts} +1 -1
  74. package/dist/{usePageUrlGenerator-uOnyJ6j2.d.mts → usePageUrlGenerator-uKBjAKqC.d.mts} +1 -1
  75. package/dist/{useSocket-BsbaGPqE.d.ts → useSocket-DgGAQW6h.d.ts} +1 -1
  76. package/dist/{useSocket-BSjn1Rlb.d.mts → useSocket-M3YY0aTS.d.mts} +1 -1
  77. package/package.json +6 -1
  78. package/src/core/index.ts +5 -0
  79. package/src/core/registry/ModuleRegistry.ts +4 -0
  80. package/src/features/company/components/details/TokenStatusIndicator.tsx +9 -9
  81. package/src/features/company/data/company.interface.ts +6 -6
  82. package/src/features/company/data/company.ts +17 -17
  83. package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +189 -0
  84. package/src/features/tokenusage/components/TokenUsageAdminFilterBar.tsx +138 -0
  85. package/src/features/tokenusage/components/TokenUsageAdminTiles.tsx +177 -0
  86. package/src/features/tokenusage/components/TokenUsageBreakdownTable.tsx +160 -0
  87. package/src/features/tokenusage/components/TokenUsageRankedBar.tsx +99 -0
  88. package/src/features/tokenusage/components/TokenUsageTimelineChart.tsx +225 -0
  89. package/src/features/tokenusage/components/__tests__/TokenUsageAdminTiles.spec.tsx +42 -0
  90. package/src/features/tokenusage/components/__tests__/TokenUsageRankedBar.spec.tsx +41 -0
  91. package/src/features/tokenusage/components/__tests__/TokenUsageTimelineChart.spec.tsx +47 -0
  92. package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +252 -0
  93. package/src/features/tokenusage/contexts/__tests__/TokenUsageAdminContext.spec.tsx +80 -0
  94. package/src/features/tokenusage/data/TokenUsageAdminService.ts +54 -0
  95. package/src/features/tokenusage/data/__tests__/tokenusage-admin-models.spec.ts +147 -0
  96. package/src/features/tokenusage/data/index.ts +8 -0
  97. package/src/features/tokenusage/data/tokenusage-admin-breakdown.interface.ts +18 -0
  98. package/src/features/tokenusage/data/tokenusage-admin-breakdown.ts +115 -0
  99. package/src/features/tokenusage/data/tokenusage-admin-summary.interface.ts +14 -0
  100. package/src/features/tokenusage/data/tokenusage-admin-summary.ts +90 -0
  101. package/src/features/tokenusage/data/tokenusage-admin-timeline.interface.ts +13 -0
  102. package/src/features/tokenusage/data/tokenusage-admin-timeline.ts +94 -0
  103. package/src/features/tokenusage/data/tokenusage-admin.types.ts +56 -0
  104. package/src/features/tokenusage/i18n-keys.ts +65 -0
  105. package/src/features/tokenusage/index.ts +30 -0
  106. package/src/features/tokenusage/lib/metrics.ts +69 -0
  107. package/src/features/tokenusage/lib/operation-label.ts +41 -0
  108. package/src/features/tokenusage/lib/palette.ts +197 -0
  109. package/src/features/tokenusage/tokenusage-admin.module.ts +22 -0
  110. package/src/features/user/contexts/CurrentUserContext.tsx +18 -18
  111. package/src/features/user/contexts/__tests__/CurrentUserContext.spec.tsx +21 -21
  112. package/dist/chunk-4TGGHNWL.js.map +0 -1
  113. package/dist/chunk-5GVY2M5X.mjs.map +0 -1
  114. package/dist/chunk-AGKZCRG2.js.map +0 -1
  115. package/dist/chunk-K76QSQTC.mjs.map +0 -1
  116. /package/dist/{BlockNoteEditor-JTKEBS2W.mjs.map → BlockNoteEditor-34QM4UR5.mjs.map} +0 -0
  117. /package/dist/{chunk-KXTTYXJ6.mjs.map → chunk-HN5ED7OF.mjs.map} +0 -0
@@ -0,0 +1,1012 @@
1
+ "use client";
2
+ import {
3
+ Button,
4
+ Card,
5
+ CardAction,
6
+ CardContent,
7
+ CardHeader,
8
+ CardTitle,
9
+ ChartContainer,
10
+ ChartLegend,
11
+ ChartLegendContent,
12
+ ChartTooltip,
13
+ ChartTooltipContent,
14
+ DateRangeSelector,
15
+ RoundPageContainer,
16
+ Select,
17
+ SelectContent,
18
+ SelectItem,
19
+ SelectTrigger,
20
+ SelectValue,
21
+ SharedProvider,
22
+ Table,
23
+ TableBody,
24
+ TableCell,
25
+ TableHead,
26
+ TableHeader,
27
+ TableRow,
28
+ usePageUrlGenerator
29
+ } from "../../chunk-UGSOBBZH.mjs";
30
+ import "../../chunk-HN5ED7OF.mjs";
31
+ import {
32
+ AbstractService,
33
+ EndpointCreator,
34
+ Modules,
35
+ TokenUsageAdminBreakdown,
36
+ TokenUsageAdminBreakdownModule,
37
+ TokenUsageAdminSummary,
38
+ TokenUsageAdminSummaryModule,
39
+ TokenUsageAdminTimeline,
40
+ TokenUsageAdminTimelineModule,
41
+ cn
42
+ } from "../../chunk-BIL2WQEV.mjs";
43
+ import "../../chunk-AUXK7QSA.mjs";
44
+ import "../../chunk-C7C7VY4F.mjs";
45
+ import "../../chunk-Z2CB63VG.mjs";
46
+ import "../../chunk-VOXD3ZLY.mjs";
47
+ import {
48
+ __name
49
+ } from "../../chunk-PAWJFY3S.mjs";
50
+
51
+ // src/features/tokenusage/contexts/TokenUsageAdminContext.tsx
52
+ import { useTranslations as useTranslations2 } from "next-intl";
53
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
54
+
55
+ // src/features/tokenusage/components/TokenUsageAdminFilterBar.tsx
56
+ import { useTranslations } from "next-intl";
57
+ import { jsx, jsxs } from "react/jsx-runtime";
58
+ var ALL_COMPANIES = "all";
59
+ function TokenUsageAdminFilterBar({ granularity, companyId, metric, companies, onChange }) {
60
+ const t = useTranslations();
61
+ const companyItems = {
62
+ [ALL_COMPANIES]: t("token_usage.admin.all_companies"),
63
+ ...Object.fromEntries(companies.map((company) => [company.id, company.label]))
64
+ };
65
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
66
+ /* @__PURE__ */ jsx(
67
+ DateRangeSelector,
68
+ {
69
+ onDateChange: (range) => {
70
+ if (!range?.from || !range?.to) return;
71
+ onChange({ from: range.from.toISOString(), to: range.to.toISOString() });
72
+ }
73
+ }
74
+ ),
75
+ /* @__PURE__ */ jsx(
76
+ Segmented,
77
+ {
78
+ ariaLabel: t("token_usage.admin.granularity.label"),
79
+ value: granularity,
80
+ options: [
81
+ { value: "day", label: t("token_usage.admin.granularity.day") },
82
+ { value: "week", label: t("token_usage.admin.granularity.week") },
83
+ { value: "month", label: t("token_usage.admin.granularity.month") }
84
+ ],
85
+ onSelect: (value) => onChange({ granularity: value })
86
+ }
87
+ ),
88
+ /* @__PURE__ */ jsx(
89
+ Segmented,
90
+ {
91
+ ariaLabel: t("token_usage.admin.metric.label"),
92
+ value: metric,
93
+ options: [
94
+ { value: "cost", label: t("token_usage.admin.metric.cost") },
95
+ { value: "credits", label: t("token_usage.admin.metric.credits") },
96
+ { value: "tokens", label: t("token_usage.admin.metric.tokens") }
97
+ ],
98
+ onSelect: (value) => onChange({ metric: value })
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsxs(
102
+ Select,
103
+ {
104
+ items: companyItems,
105
+ value: companyId ?? ALL_COMPANIES,
106
+ onValueChange: (value) => onChange({ companyId: !value || value === ALL_COMPANIES ? void 0 : value }),
107
+ children: [
108
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-56", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("token_usage.admin.all_companies") }) }),
109
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
110
+ /* @__PURE__ */ jsx(SelectItem, { value: ALL_COMPANIES, children: t("token_usage.admin.all_companies") }),
111
+ companies.map((company) => /* @__PURE__ */ jsx(SelectItem, { value: company.id, children: company.label }, company.id))
112
+ ] })
113
+ ]
114
+ }
115
+ )
116
+ ] });
117
+ }
118
+ __name(TokenUsageAdminFilterBar, "TokenUsageAdminFilterBar");
119
+ function Segmented({
120
+ ariaLabel,
121
+ value,
122
+ options,
123
+ onSelect
124
+ }) {
125
+ return /* @__PURE__ */ jsx(
126
+ "div",
127
+ {
128
+ role: "group",
129
+ "aria-label": ariaLabel,
130
+ className: "border-border inline-flex items-center gap-0.5 rounded-md border p-0.5",
131
+ children: options.map((option) => {
132
+ const selected = option.value === value;
133
+ return /* @__PURE__ */ jsx(
134
+ Button,
135
+ {
136
+ type: "button",
137
+ size: "sm",
138
+ variant: selected ? "default" : "ghost",
139
+ "aria-pressed": selected,
140
+ className: cn(!selected && "text-muted-foreground"),
141
+ onClick: () => onSelect(option.value),
142
+ children: option.label
143
+ },
144
+ option.value
145
+ );
146
+ })
147
+ }
148
+ );
149
+ }
150
+ __name(Segmented, "Segmented");
151
+
152
+ // src/features/tokenusage/data/TokenUsageAdminService.ts
153
+ function withFilters(endpoint, filters) {
154
+ endpoint.addAdditionalParam("from", filters.from);
155
+ endpoint.addAdditionalParam("to", filters.to);
156
+ if (filters.companyId) endpoint.addAdditionalParam("companyId", filters.companyId);
157
+ return endpoint;
158
+ }
159
+ __name(withFilters, "withFilters");
160
+ var TokenUsageAdminService = class extends AbstractService {
161
+ static {
162
+ __name(this, "TokenUsageAdminService");
163
+ }
164
+ /** Six rows: {customer, platform, total} × {current, previous}. */
165
+ static async getSummary(filters) {
166
+ const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageAdminSummary }), filters);
167
+ return this.callApi({
168
+ type: Modules.TokenUsageAdminSummary,
169
+ method: "GET" /* GET */,
170
+ endpoint: endpoint.generate()
171
+ });
172
+ }
173
+ static async getTimeline(params) {
174
+ const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageAdminTimeline }), params);
175
+ endpoint.addAdditionalParam("granularity", params.granularity);
176
+ endpoint.addAdditionalParam("stackBy", params.stackBy);
177
+ return this.callApi({
178
+ type: Modules.TokenUsageAdminTimeline,
179
+ method: "GET" /* GET */,
180
+ endpoint: endpoint.generate()
181
+ });
182
+ }
183
+ static async getBreakdown(params) {
184
+ const endpoint = withFilters(new EndpointCreator({ endpoint: Modules.TokenUsageAdminBreakdown }), params);
185
+ endpoint.addAdditionalParam("dimension", params.dimension);
186
+ endpoint.addAdditionalParam("scope", params.scope);
187
+ if (params.limit !== void 0) endpoint.addAdditionalParam("limit", String(params.limit));
188
+ return this.callApi({
189
+ type: Modules.TokenUsageAdminBreakdown,
190
+ method: "GET" /* GET */,
191
+ endpoint: endpoint.generate()
192
+ });
193
+ }
194
+ };
195
+
196
+ // src/features/tokenusage/contexts/TokenUsageAdminContext.tsx
197
+ import { jsx as jsx2 } from "react/jsx-runtime";
198
+ var TOKEN_USAGE_ADMIN_PAGE_URL = "/administration/token-usage";
199
+ var DEFAULT_TOP_N = 10;
200
+ var TokenUsageAdminContext = createContext(void 0);
201
+ function defaultRange() {
202
+ const now = /* @__PURE__ */ new Date();
203
+ const start = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
204
+ return { from: start.toISOString(), to: now.toISOString() };
205
+ }
206
+ __name(defaultRange, "defaultRange");
207
+ var TokenUsageAdminProvider = /* @__PURE__ */ __name(({
208
+ children,
209
+ initialCompanyId,
210
+ initialFrom,
211
+ initialTo,
212
+ topN = DEFAULT_TOP_N
213
+ }) => {
214
+ const t = useTranslations2();
215
+ const generateUrl = usePageUrlGenerator();
216
+ const [filters, setFilterState] = useState(() => {
217
+ const range = defaultRange();
218
+ return {
219
+ from: initialFrom ?? range.from,
220
+ to: initialTo ?? range.to,
221
+ granularity: "day",
222
+ stackBy: "scope",
223
+ companyId: initialCompanyId,
224
+ metric: "cost"
225
+ };
226
+ });
227
+ const [summary, setSummary] = useState([]);
228
+ const [timeline, setTimeline] = useState([]);
229
+ const [byCompany, setByCompany] = useState([]);
230
+ const [byUser, setByUser] = useState([]);
231
+ const [byOperation, setByOperation] = useState([]);
232
+ const [byCustomerOperation, setByCustomerOperation] = useState([]);
233
+ const [isLoading, setIsLoading] = useState(true);
234
+ const [error, setError] = useState(null);
235
+ const { from, to, granularity, stackBy, companyId, metric } = filters;
236
+ const singleCustomerMode = Boolean(companyId);
237
+ useEffect(() => {
238
+ let cancelled = false;
239
+ setIsLoading(true);
240
+ setError(null);
241
+ const base = { from, to, companyId };
242
+ Promise.all([
243
+ TokenUsageAdminService.getSummary(base),
244
+ TokenUsageAdminService.getTimeline({ ...base, granularity, stackBy }),
245
+ TokenUsageAdminService.getBreakdown({ ...base, dimension: "company", scope: "customer", limit: topN }),
246
+ TokenUsageAdminService.getBreakdown({ ...base, dimension: "user", scope: "customer", limit: topN }),
247
+ // Platform spend is, by definition, the usage with no owning company, so
248
+ // filtering it by one is meaningless — the call is skipped entirely rather
249
+ // than issued and discarded.
250
+ companyId ? Promise.resolve([]) : TokenUsageAdminService.getBreakdown({ ...base, dimension: "operation", scope: "platform", limit: topN }),
251
+ // Customer spend by operation — unlike the platform panel this one IS
252
+ // meaningful under a company filter, so it is always requested.
253
+ TokenUsageAdminService.getBreakdown({ ...base, dimension: "operation", scope: "customer", limit: topN })
254
+ ]).then(([nextSummary, nextTimeline, nextByCompany, nextByUser, nextByOperation, nextByCustomerOperation]) => {
255
+ if (cancelled) return;
256
+ setSummary(nextSummary ?? []);
257
+ setTimeline(nextTimeline ?? []);
258
+ setByCompany(nextByCompany ?? []);
259
+ setByUser(nextByUser ?? []);
260
+ setByOperation(nextByOperation ?? []);
261
+ setByCustomerOperation(nextByCustomerOperation ?? []);
262
+ }).catch((err) => {
263
+ if (cancelled) return;
264
+ console.error("Failed to load administrative token usage:", err);
265
+ setError(err instanceof Error ? err.message : String(err));
266
+ }).finally(() => {
267
+ if (cancelled) return;
268
+ setIsLoading(false);
269
+ });
270
+ return () => {
271
+ cancelled = true;
272
+ };
273
+ }, [from, to, granularity, stackBy, companyId, topN]);
274
+ const setFilters = useCallback((next) => {
275
+ setFilterState((prev) => ({ ...prev, ...next }));
276
+ }, []);
277
+ const companies = useMemo(
278
+ () => byCompany.filter((row) => row.id !== "other").map((row) => ({ id: row.id, label: row.label })),
279
+ [byCompany]
280
+ );
281
+ const breadcrumb = /* @__PURE__ */ __name(() => [
282
+ {
283
+ name: t("token_usage.admin.title"),
284
+ href: generateUrl({ page: TOKEN_USAGE_ADMIN_PAGE_URL })
285
+ }
286
+ ], "breadcrumb");
287
+ const title = /* @__PURE__ */ __name(() => ({
288
+ type: t("token_usage.admin.title"),
289
+ functions: /* @__PURE__ */ jsx2(
290
+ TokenUsageAdminFilterBar,
291
+ {
292
+ from,
293
+ to,
294
+ granularity,
295
+ companyId,
296
+ metric,
297
+ companies,
298
+ onChange: setFilters
299
+ },
300
+ "tokenUsageAdminFilterBar"
301
+ )
302
+ }), "title");
303
+ const contextValue = useMemo(
304
+ () => ({
305
+ summary,
306
+ timeline,
307
+ byCompany,
308
+ byUser,
309
+ byOperation,
310
+ byCustomerOperation,
311
+ companies,
312
+ filters,
313
+ setFilters,
314
+ singleCustomerMode,
315
+ isLoading,
316
+ error
317
+ }),
318
+ [
319
+ summary,
320
+ timeline,
321
+ byCompany,
322
+ byUser,
323
+ byOperation,
324
+ byCustomerOperation,
325
+ companies,
326
+ filters,
327
+ setFilters,
328
+ singleCustomerMode,
329
+ isLoading,
330
+ error
331
+ ]
332
+ );
333
+ return /* @__PURE__ */ jsx2(SharedProvider, { value: { breadcrumbs: breadcrumb(), title: title() }, children: /* @__PURE__ */ jsx2(TokenUsageAdminContext.Provider, { value: contextValue, children }) });
334
+ }, "TokenUsageAdminProvider");
335
+ var useTokenUsageAdmin = /* @__PURE__ */ __name(() => {
336
+ const ctx = useContext(TokenUsageAdminContext);
337
+ if (!ctx) {
338
+ throw new Error("useTokenUsageAdmin() called outside <TokenUsageAdminProvider>.");
339
+ }
340
+ return ctx;
341
+ }, "useTokenUsageAdmin");
342
+
343
+ // src/features/tokenusage/components/TokenUsageAdminContainer.tsx
344
+ import { useTranslations as useTranslations7 } from "next-intl";
345
+ import { useMemo as useMemo4 } from "react";
346
+
347
+ // src/features/tokenusage/components/TokenUsageAdminTiles.tsx
348
+ import { ArrowDownIcon, ArrowUpIcon } from "lucide-react";
349
+ import { useTranslations as useTranslations3 } from "next-intl";
350
+
351
+ // src/features/tokenusage/lib/metrics.ts
352
+ var LOCALE = "it-IT";
353
+ function metricValue(row, metric) {
354
+ if (metric === "cost") return row.cost;
355
+ if (metric === "credits") return row.credits;
356
+ return row.tokensIn + row.tokensOut;
357
+ }
358
+ __name(metricValue, "metricValue");
359
+ function formatDecimal(value, decimals) {
360
+ return value.toLocaleString(LOCALE, {
361
+ minimumFractionDigits: decimals,
362
+ maximumFractionDigits: decimals
363
+ });
364
+ }
365
+ __name(formatDecimal, "formatDecimal");
366
+ function formatMetricValue(value, metric) {
367
+ if (metric === "cost") return `\u20AC ${formatDecimal(value, 2)}`;
368
+ if (metric === "credits") return formatDecimal(value, 2);
369
+ return formatDecimal(value, 0);
370
+ }
371
+ __name(formatMetricValue, "formatMetricValue");
372
+ function formatPercent(value, decimals = 1) {
373
+ return `${formatDecimal(value, decimals)} %`;
374
+ }
375
+ __name(formatPercent, "formatPercent");
376
+ function cacheHitPercentage(cached, tokensIn) {
377
+ if (!tokensIn) return 0;
378
+ return cached / tokensIn * 100;
379
+ }
380
+ __name(cacheHitPercentage, "cacheHitPercentage");
381
+ function percentageDelta(current, previous) {
382
+ if (!previous) return void 0;
383
+ return Math.round((current - previous) / previous * 100);
384
+ }
385
+ __name(percentageDelta, "percentageDelta");
386
+
387
+ // src/features/tokenusage/components/TokenUsageAdminTiles.tsx
388
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
389
+ var ZERO = { cost: 0, credits: 0, tokensIn: 0, tokensOut: 0, cached: 0, calls: 0 };
390
+ function TokenUsageAdminTiles({ summary, metric, singleCustomerMode }) {
391
+ const t = useTranslations3();
392
+ const rowFor = /* @__PURE__ */ __name((scope, window) => summary.find((r) => r.scope === scope && r.window === window) ?? ZERO, "rowFor");
393
+ const customerCurrent = rowFor("customer", "current");
394
+ const customerPrevious = rowFor("customer", "previous");
395
+ const platformCurrent = rowFor("platform", "current");
396
+ const platformPrevious = rowFor("platform", "previous");
397
+ const totalCurrent = rowFor("total", "current");
398
+ const totalPrevious = rowFor("total", "previous");
399
+ const averagePerCall = totalCurrent.calls ? totalCurrent.cost / totalCurrent.calls : 0;
400
+ const cacheHit = cacheHitPercentage(totalCurrent.cached, totalCurrent.tokensIn);
401
+ return /* @__PURE__ */ jsxs2("div", { className: "grid gap-3", children: [
402
+ /* @__PURE__ */ jsxs2("div", { className: cn("grid gap-3", singleCustomerMode ? "sm:grid-cols-1" : "sm:grid-cols-2"), children: [
403
+ /* @__PURE__ */ jsx3(
404
+ LeadTile,
405
+ {
406
+ testId: "tile-customer",
407
+ label: t("token_usage.admin.customer_spend"),
408
+ value: formatMetricValue(metricValue(customerCurrent, metric), metric),
409
+ delta: percentageDelta(metricValue(customerCurrent, metric), metricValue(customerPrevious, metric)),
410
+ previousLabel: t("token_usage.admin.vs_previous")
411
+ }
412
+ ),
413
+ !singleCustomerMode && /* @__PURE__ */ jsx3(
414
+ LeadTile,
415
+ {
416
+ testId: "tile-platform",
417
+ label: t("token_usage.admin.platform_spend"),
418
+ value: formatMetricValue(metricValue(platformCurrent, metric), metric),
419
+ delta: percentageDelta(metricValue(platformCurrent, metric), metricValue(platformPrevious, metric)),
420
+ previousLabel: t("token_usage.admin.vs_previous")
421
+ }
422
+ )
423
+ ] }),
424
+ /* @__PURE__ */ jsxs2("div", { className: "grid gap-3 sm:grid-cols-3", children: [
425
+ /* @__PURE__ */ jsx3(
426
+ SupportingTile,
427
+ {
428
+ testId: "tile-total",
429
+ label: t("token_usage.admin.total_cost"),
430
+ value: formatMetricValue(metricValue(totalCurrent, metric), metric),
431
+ delta: percentageDelta(metricValue(totalCurrent, metric), metricValue(totalPrevious, metric))
432
+ }
433
+ ),
434
+ /* @__PURE__ */ jsx3(
435
+ SupportingTile,
436
+ {
437
+ testId: "tile-avg-per-call",
438
+ label: t("token_usage.admin.avg_per_call"),
439
+ value: `\u20AC ${formatDecimal(averagePerCall, 4)}`
440
+ }
441
+ ),
442
+ /* @__PURE__ */ jsx3(
443
+ SupportingTile,
444
+ {
445
+ testId: "tile-cache-hit",
446
+ label: t("token_usage.admin.cache_hit"),
447
+ value: formatPercent(cacheHit)
448
+ }
449
+ )
450
+ ] })
451
+ ] });
452
+ }
453
+ __name(TokenUsageAdminTiles, "TokenUsageAdminTiles");
454
+ function LeadTile({
455
+ testId,
456
+ label,
457
+ value,
458
+ delta,
459
+ previousLabel
460
+ }) {
461
+ return /* @__PURE__ */ jsx3(Card, { "data-testid": testId, children: /* @__PURE__ */ jsxs2(CardContent, { className: "grid gap-1", children: [
462
+ /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground text-xs", children: label }),
463
+ /* @__PURE__ */ jsx3("span", { className: "text-primary text-xl font-semibold tabular-nums", children: value }),
464
+ /* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-1", children: [
465
+ /* @__PURE__ */ jsx3(Delta, { testId: `${testId}-delta`, delta }),
466
+ /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground text-xs", children: previousLabel })
467
+ ] })
468
+ ] }) });
469
+ }
470
+ __name(LeadTile, "LeadTile");
471
+ function SupportingTile({
472
+ testId,
473
+ label,
474
+ value,
475
+ delta
476
+ }) {
477
+ return /* @__PURE__ */ jsx3(Card, { "data-testid": testId, size: "sm", children: /* @__PURE__ */ jsxs2(CardContent, { className: "grid gap-1", children: [
478
+ /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground text-xs", children: label }),
479
+ /* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-2", children: [
480
+ /* @__PURE__ */ jsx3("span", { className: "text-sm font-medium tabular-nums", children: value }),
481
+ delta !== void 0 && /* @__PURE__ */ jsx3(Delta, { testId: `${testId}-delta`, delta })
482
+ ] })
483
+ ] }) });
484
+ }
485
+ __name(SupportingTile, "SupportingTile");
486
+ function Delta({ testId, delta }) {
487
+ if (delta === void 0) {
488
+ return /* @__PURE__ */ jsx3("span", { "data-testid": testId, className: "text-muted-foreground text-xs", children: "\u2014" });
489
+ }
490
+ const increased = delta >= 0;
491
+ const Icon = increased ? ArrowUpIcon : ArrowDownIcon;
492
+ return /* @__PURE__ */ jsxs2(
493
+ "span",
494
+ {
495
+ "data-testid": testId,
496
+ className: cn(
497
+ "inline-flex items-center gap-0.5 text-xs tabular-nums",
498
+ increased ? "text-success" : "text-destructive"
499
+ ),
500
+ children: [
501
+ /* @__PURE__ */ jsx3(Icon, { "aria-hidden": true, className: "size-3" }),
502
+ formatDecimal(Math.abs(delta), 0),
503
+ " %"
504
+ ]
505
+ }
506
+ );
507
+ }
508
+ __name(Delta, "Delta");
509
+
510
+ // src/features/tokenusage/components/TokenUsageBreakdownTable.tsx
511
+ import { ArrowDownIcon as ArrowDownIcon2, ArrowUpIcon as ArrowUpIcon2 } from "lucide-react";
512
+ import { useTranslations as useTranslations4 } from "next-intl";
513
+ import { useMemo as useMemo2, useState as useState2 } from "react";
514
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
515
+ function TokenUsageBreakdownTable({ rows, metric }) {
516
+ const t = useTranslations4();
517
+ const [sort, setSort] = useState2(void 0);
518
+ const showActiveUsers = rows.some((row) => row.activeUsers !== void 0);
519
+ const total = useMemo2(() => rows.reduce((sum, row) => sum + metricValue(row, metric), 0), [rows, metric]);
520
+ const sortValue = /* @__PURE__ */ __name((row, key) => {
521
+ switch (key) {
522
+ case "label":
523
+ return row.label ?? "";
524
+ case "sublabel":
525
+ return row.sublabel ?? "";
526
+ case "activeUsers":
527
+ return row.activeUsers ?? 0;
528
+ case "cacheHit":
529
+ return cacheHitPercentage(row.cached, row.tokensIn);
530
+ case "share":
531
+ return metricValue(row, metric);
532
+ default:
533
+ return row[key];
534
+ }
535
+ }, "sortValue");
536
+ const sortedRows = useMemo2(() => {
537
+ if (!sort) return rows;
538
+ const factor = sort.direction === "desc" ? -1 : 1;
539
+ return [...rows].sort((a, b) => {
540
+ const left = sortValue(a, sort.key);
541
+ const right = sortValue(b, sort.key);
542
+ if (typeof left === "string" || typeof right === "string") {
543
+ return String(left).localeCompare(String(right), "it-IT") * factor;
544
+ }
545
+ return (left - right) * factor;
546
+ });
547
+ }, [rows, sort, metric]);
548
+ const toggleSort = /* @__PURE__ */ __name((key) => setSort(
549
+ (current) => current?.key === key ? { key, direction: current.direction === "desc" ? "asc" : "desc" } : { key, direction: "desc" }
550
+ ), "toggleSort");
551
+ const columns = [
552
+ { key: "label", label: t("token_usage.admin.columns.label"), numeric: false },
553
+ { key: "sublabel", label: t("token_usage.admin.columns.sublabel"), numeric: false },
554
+ ...showActiveUsers ? [{ key: "activeUsers", label: t("token_usage.admin.columns.active_users"), numeric: true }] : [],
555
+ { key: "calls", label: t("token_usage.admin.columns.calls"), numeric: true },
556
+ { key: "tokensIn", label: t("token_usage.admin.columns.tokens_in"), numeric: true },
557
+ { key: "tokensOut", label: t("token_usage.admin.columns.tokens_out"), numeric: true },
558
+ // Reuses the tile's key rather than minting a column-specific one — same
559
+ // metric, same words.
560
+ { key: "cacheHit", label: t("token_usage.admin.cache_hit"), numeric: true },
561
+ { key: "cost", label: t("token_usage.admin.columns.cost"), numeric: true },
562
+ { key: "credits", label: t("token_usage.admin.columns.credits"), numeric: true },
563
+ { key: "share", label: t("token_usage.admin.columns.share"), numeric: true }
564
+ ];
565
+ return (
566
+ // The package's <Table> wraps itself in an `overflow-x-clip` container, which
567
+ // would swallow the overflow before this scroller ever saw it — hence the
568
+ // child override. Wide metric tables must scroll, never widen the page.
569
+ /* @__PURE__ */ jsx4("div", { className: "overflow-x-auto [&_[data-slot=table-container]]:overflow-x-visible", children: /* @__PURE__ */ jsxs3(Table, { children: [
570
+ /* @__PURE__ */ jsx4(TableHeader, { children: /* @__PURE__ */ jsx4(TableRow, { children: columns.map((column) => /* @__PURE__ */ jsx4(TableHead, { className: cn(column.numeric && "text-right"), children: /* @__PURE__ */ jsxs3(
571
+ "button",
572
+ {
573
+ type: "button",
574
+ onClick: () => toggleSort(column.key),
575
+ className: cn(
576
+ "hover:text-primary inline-flex items-center gap-1 text-xs font-medium",
577
+ column.numeric && "flex-row-reverse"
578
+ ),
579
+ children: [
580
+ column.label,
581
+ sort?.key === column.key && (sort.direction === "desc" ? /* @__PURE__ */ jsx4(ArrowDownIcon2, { "aria-hidden": true, className: "size-3" }) : /* @__PURE__ */ jsx4(ArrowUpIcon2, { "aria-hidden": true, className: "size-3" }))
582
+ ]
583
+ }
584
+ ) }, column.key)) }) }),
585
+ /* @__PURE__ */ jsx4(TableBody, { children: sortedRows.map((row) => {
586
+ const value = metricValue(row, metric);
587
+ const share = total > 0 ? value / total * 100 : 0;
588
+ return /* @__PURE__ */ jsxs3(TableRow, { "data-testid": `breakdown-row-${row.id}`, children: [
589
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-xs", children: row.id === "other" ? t("token_usage.admin.other") : row.label }),
590
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-muted-foreground text-xs", children: row.sublabel ?? "" }),
591
+ showActiveUsers && /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: row.activeUsers === void 0 ? "" : formatDecimal(row.activeUsers, 0) }),
592
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatDecimal(row.calls, 0) }),
593
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatDecimal(row.tokensIn, 0) }),
594
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatDecimal(row.tokensOut, 0) }),
595
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatPercent(cacheHitPercentage(row.cached, row.tokensIn)) }),
596
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatMetricValue(row.cost, "cost") }),
597
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatMetricValue(row.credits, "credits") }),
598
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatPercent(share) })
599
+ ] }, row.id);
600
+ }) })
601
+ ] }) })
602
+ );
603
+ }
604
+ __name(TokenUsageBreakdownTable, "TokenUsageBreakdownTable");
605
+
606
+ // src/features/tokenusage/components/TokenUsageRankedBar.tsx
607
+ import { useTranslations as useTranslations5 } from "next-intl";
608
+
609
+ // src/features/tokenusage/lib/operation-label.ts
610
+ var OPERATION_LABEL_NAMESPACE = "token_usage.types";
611
+ var toCamelCase = /* @__PURE__ */ __name((value) => {
612
+ const parts = value.split(/[^a-zA-Z0-9]+/).filter(Boolean);
613
+ if (parts.length === 0) return value;
614
+ return parts.map(
615
+ (part, index) => index === 0 ? part.charAt(0).toLowerCase() + part.slice(1) : part.charAt(0).toUpperCase() + part.slice(1)
616
+ ).join("");
617
+ }, "toCamelCase");
618
+ var operationLabel = /* @__PURE__ */ __name((type, translate, has) => {
619
+ const key = `${OPERATION_LABEL_NAMESPACE}.${toCamelCase(type)}`;
620
+ return has(key) ? translate(key) : type;
621
+ }, "operationLabel");
622
+
623
+ // src/features/tokenusage/lib/palette.ts
624
+ var CATEGORICAL_LIGHT = [
625
+ "#2a78d6",
626
+ "#eb6834",
627
+ "#1baf7a",
628
+ "#eda100",
629
+ "#e87ba4",
630
+ "#008300",
631
+ "#4a3aa7"
632
+ ];
633
+ var CATEGORICAL_DARK = [
634
+ "#3987e5",
635
+ "#d95926",
636
+ "#199e70",
637
+ "#c98500",
638
+ "#d55181",
639
+ "#008300",
640
+ "#9085e9"
641
+ ];
642
+ var CATEGORICAL_CEILING = CATEGORICAL_LIGHT.length;
643
+ var OTHER_COLOR = "#898781";
644
+ var SEQUENTIAL_RAMP_LIGHT = ["#104281", "#1c5cab", "#2a78d6", "#5598e7", "#86b6ef"];
645
+ var SEQUENTIAL_RAMP = SEQUENTIAL_RAMP_LIGHT;
646
+ var categoricalSet = /* @__PURE__ */ __name((mode) => mode === "dark" ? CATEGORICAL_DARK : CATEGORICAL_LIGHT, "categoricalSet");
647
+ function seriesColor(index, mode = "light") {
648
+ if (index < 0 || index >= CATEGORICAL_CEILING) return OTHER_COLOR;
649
+ return categoricalSet(mode)[index];
650
+ }
651
+ __name(seriesColor, "seriesColor");
652
+
653
+ // src/features/tokenusage/components/TokenUsageRankedBar.tsx
654
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
655
+ function TokenUsageRankedBar({ rows, metric, emptyLabel, labelsAreOperationTypes = false }) {
656
+ const t = useTranslations5();
657
+ const renderLabel = /* @__PURE__ */ __name((row) => {
658
+ if (row.id === "other") return t("token_usage.admin.other");
659
+ if (!labelsAreOperationTypes) return row.label;
660
+ return operationLabel(
661
+ row.label,
662
+ (key) => t(key),
663
+ (key) => t.has(key)
664
+ );
665
+ }, "renderLabel");
666
+ if (rows.length === 0) {
667
+ return /* @__PURE__ */ jsx5("p", { className: "text-muted-foreground py-6 text-center text-sm", children: emptyLabel });
668
+ }
669
+ const values = rows.map((row) => metricValue(row, metric));
670
+ const total = values.reduce((sum, value) => sum + value, 0);
671
+ const max = Math.max(...values, 0);
672
+ return /* @__PURE__ */ jsx5("div", { className: "grid gap-0.5", children: rows.map((row, index) => {
673
+ const value = values[index];
674
+ const isOther = row.id === "other";
675
+ const step = isOther ? SEQUENTIAL_RAMP.length - 1 : Math.min(index, SEQUENTIAL_RAMP.length - 1);
676
+ const width = max > 0 ? value / max * 100 : 0;
677
+ const share = total > 0 ? value / total * 100 : 0;
678
+ return /* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-[minmax(0,1fr)_auto_auto] items-center gap-x-3 gap-y-0.5 py-0.5", children: [
679
+ /* @__PURE__ */ jsxs4("div", { className: "grid min-w-0 gap-0.5", children: [
680
+ /* @__PURE__ */ jsx5("span", { className: "text-muted-foreground truncate text-xs", children: renderLabel(row) }),
681
+ /* @__PURE__ */ jsx5("div", { className: "bg-muted h-2 w-full overflow-hidden rounded-r-[4px]", children: /* @__PURE__ */ jsx5(
682
+ "div",
683
+ {
684
+ "data-testid": `ranked-fill-${row.id}`,
685
+ "data-ramp-step": String(step),
686
+ className: "h-full rounded-r-[4px]",
687
+ style: { width: `${width}%`, backgroundColor: SEQUENTIAL_RAMP[step] }
688
+ }
689
+ ) })
690
+ ] }),
691
+ /* @__PURE__ */ jsx5("span", { "data-testid": `ranked-value-${row.id}`, className: "text-right text-xs tabular-nums", children: formatMetricValue(value, metric) }),
692
+ /* @__PURE__ */ jsx5(
693
+ "span",
694
+ {
695
+ "data-testid": `ranked-share-${row.id}`,
696
+ className: "text-muted-foreground w-16 text-right text-xs tabular-nums",
697
+ children: formatPercent(share)
698
+ }
699
+ )
700
+ ] }, row.id);
701
+ }) });
702
+ }
703
+ __name(TokenUsageRankedBar, "TokenUsageRankedBar");
704
+
705
+ // src/features/tokenusage/components/TokenUsageTimelineChart.tsx
706
+ import { useTranslations as useTranslations6 } from "next-intl";
707
+ import { useTheme } from "next-themes";
708
+ import { useMemo as useMemo3 } from "react";
709
+ import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
710
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
711
+ var OTHER_SERIES = "other";
712
+ var bucketKey = /* @__PURE__ */ __name((date) => {
713
+ const y = date.getUTCFullYear();
714
+ const m = `${date.getUTCMonth() + 1}`.padStart(2, "0");
715
+ const d = `${date.getUTCDate()}`.padStart(2, "0");
716
+ return `${y}-${m}-${d}`;
717
+ }, "bucketKey");
718
+ var metricValue2 = /* @__PURE__ */ __name((row, metric) => {
719
+ if (metric === "cost") return row.cost;
720
+ if (metric === "credits") return row.credits;
721
+ return row.tokensIn + row.tokensOut;
722
+ }, "metricValue");
723
+ var inferGranularity = /* @__PURE__ */ __name((buckets) => {
724
+ if (buckets.length < 2) return "day";
725
+ const dayMs = 24 * 60 * 60 * 1e3;
726
+ let smallestGap = Number.POSITIVE_INFINITY;
727
+ for (let i = 1; i < buckets.length; i += 1) {
728
+ const gap = (Date.parse(buckets[i]) - Date.parse(buckets[i - 1])) / dayMs;
729
+ if (gap > 0 && gap < smallestGap) smallestGap = gap;
730
+ }
731
+ if (smallestGap >= 28) return "month";
732
+ if (smallestGap >= 7) return "week";
733
+ return "day";
734
+ }, "inferGranularity");
735
+ var bucketFormatter = /* @__PURE__ */ __name((granularity) => granularity === "month" ? new Intl.DateTimeFormat("it-IT", { month: "short", year: "numeric", timeZone: "UTC" }) : new Intl.DateTimeFormat("it-IT", { day: "numeric", month: "short", timeZone: "UTC" }), "bucketFormatter");
736
+ var compactNumber = new Intl.NumberFormat("it-IT", { notation: "compact", maximumFractionDigits: 1 });
737
+ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
738
+ const t = useTranslations6();
739
+ const { resolvedTheme } = useTheme();
740
+ const mode = resolvedTheme === "dark" ? "dark" : "light";
741
+ const { chartData, seriesKeys } = useMemo3(() => {
742
+ if (rows.length === 0) return { chartData: [], seriesKeys: [] };
743
+ const totals = /* @__PURE__ */ new Map();
744
+ for (const row of rows) totals.set(row.series, (totals.get(row.series) ?? 0) + metricValue2(row, metric));
745
+ const ranked = [...totals.entries()].sort((a, b) => b[1] - a[1]).map(([series]) => series);
746
+ const named = ranked.slice(0, CATEGORICAL_CEILING);
747
+ const folded = new Set(ranked.slice(CATEGORICAL_CEILING));
748
+ const keys = folded.size > 0 ? [...named, OTHER_SERIES] : named;
749
+ const byBucket = /* @__PURE__ */ new Map();
750
+ for (const row of rows) {
751
+ const key = bucketKey(row.bucket);
752
+ let entry = byBucket.get(key);
753
+ if (!entry) {
754
+ entry = { bucket: key };
755
+ for (const series2 of keys) entry[series2] = 0;
756
+ byBucket.set(key, entry);
757
+ }
758
+ const series = folded.has(row.series) ? OTHER_SERIES : row.series;
759
+ entry[series] = entry[series] + metricValue2(row, metric);
760
+ }
761
+ return {
762
+ chartData: [...byBucket.values()].sort((a, b) => a.bucket.localeCompare(b.bucket)),
763
+ seriesKeys: keys
764
+ };
765
+ }, [rows, metric]);
766
+ const seriesLabel = /* @__PURE__ */ __name((series) => {
767
+ if (series === OTHER_SERIES) {
768
+ const key = "token_usage.series.other";
769
+ return t.has(key) ? t(key) : "Altro";
770
+ }
771
+ if (stackBy === "company") return series;
772
+ return operationLabel(
773
+ series,
774
+ (key) => t(key),
775
+ (key) => t.has(key)
776
+ );
777
+ }, "seriesLabel");
778
+ const chartConfig = useMemo3(
779
+ () => Object.fromEntries(seriesKeys.map((series) => [series, { label: seriesLabel(series) }])),
780
+ [seriesKeys, stackBy]
781
+ );
782
+ if (chartData.length === 0) {
783
+ const key = "token_usage.timeline.empty";
784
+ return /* @__PURE__ */ jsx6("p", { className: className ? `text-muted-foreground text-sm ${className}` : "text-muted-foreground text-sm", children: t.has(key) ? t(key) : "Nessun dato nel periodo selezionato" });
785
+ }
786
+ const granularity = inferGranularity(chartData.map((entry) => entry.bucket));
787
+ const formatBucket = bucketFormatter(granularity);
788
+ const seriesColorFor = /* @__PURE__ */ __name((series, index) => series === OTHER_SERIES ? OTHER_COLOR : seriesColor(index, mode), "seriesColorFor");
789
+ return /* @__PURE__ */ jsxs5("div", { className, children: [
790
+ /* @__PURE__ */ jsx6("span", { className: "sr-only", "data-testid": "timeline-data", children: JSON.stringify(chartData) }),
791
+ /* @__PURE__ */ jsx6("span", { className: "sr-only", "data-testid": "timeline-series", children: JSON.stringify(seriesKeys) }),
792
+ /* @__PURE__ */ jsx6(ChartContainer, { config: chartConfig, className: "aspect-auto h-72 w-full", children: /* @__PURE__ */ jsxs5(BarChart, { accessibilityLayer: true, data: chartData, margin: { top: 8, right: 8, bottom: 0, left: 0 }, children: [
793
+ /* @__PURE__ */ jsx6(CartesianGrid, { vertical: false }),
794
+ /* @__PURE__ */ jsx6(
795
+ XAxis,
796
+ {
797
+ dataKey: "bucket",
798
+ tickLine: false,
799
+ axisLine: false,
800
+ tickMargin: 8,
801
+ minTickGap: 16,
802
+ tickFormatter: (value) => formatBucket.format(/* @__PURE__ */ new Date(`${value}T00:00:00.000Z`))
803
+ }
804
+ ),
805
+ /* @__PURE__ */ jsx6(
806
+ YAxis,
807
+ {
808
+ tickLine: false,
809
+ axisLine: false,
810
+ tickMargin: 8,
811
+ width: 48,
812
+ tickFormatter: (value) => compactNumber.format(value)
813
+ }
814
+ ),
815
+ /* @__PURE__ */ jsx6(
816
+ ChartTooltip,
817
+ {
818
+ content: /* @__PURE__ */ jsx6(
819
+ ChartTooltipContent,
820
+ {
821
+ labelFormatter: (value) => formatBucket.format(/* @__PURE__ */ new Date(`${String(value)}T00:00:00.000Z`))
822
+ }
823
+ )
824
+ }
825
+ ),
826
+ seriesKeys.length >= 2 ? /* @__PURE__ */ jsx6(ChartLegend, { content: /* @__PURE__ */ jsx6(ChartLegendContent, {}) }) : null,
827
+ seriesKeys.map((series, index) => /* @__PURE__ */ jsx6(
828
+ Bar,
829
+ {
830
+ dataKey: series,
831
+ stackId: "usage",
832
+ maxBarSize: 28,
833
+ fill: seriesColorFor(series, index),
834
+ stroke: "var(--background)",
835
+ strokeWidth: 2,
836
+ radius: index === seriesKeys.length - 1 ? [4, 4, 0, 0] : 0
837
+ },
838
+ series
839
+ ))
840
+ ] }) })
841
+ ] });
842
+ }
843
+ __name(TokenUsageTimelineChart, "TokenUsageTimelineChart");
844
+
845
+ // src/features/tokenusage/components/TokenUsageAdminContainer.tsx
846
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
847
+ var STACK_BY_VALUES = ["scope", "type", "company"];
848
+ function TokenUsageAdminContainer() {
849
+ const t = useTranslations7();
850
+ const {
851
+ summary,
852
+ timeline,
853
+ byCompany,
854
+ byUser,
855
+ byOperation,
856
+ byCustomerOperation,
857
+ filters,
858
+ setFilters,
859
+ singleCustomerMode,
860
+ isLoading,
861
+ error
862
+ } = useTokenUsageAdmin();
863
+ const stackByItems = useMemo4(
864
+ () => ({
865
+ scope: t("token_usage.admin.stack.scope"),
866
+ type: t("token_usage.admin.stack.type"),
867
+ company: t("token_usage.admin.stack.company")
868
+ }),
869
+ [t]
870
+ );
871
+ if (error) {
872
+ return /* @__PURE__ */ jsx7(RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ jsx7("div", { className: "p-4", children: /* @__PURE__ */ jsx7(Card, { children: /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7("p", { className: "text-destructive text-xs/relaxed", children: error }) }) }) }) });
873
+ }
874
+ if (isLoading) return /* @__PURE__ */ jsx7(RoundPageContainer, { fullWidth: true, forceHeader: true });
875
+ const emptyLabel = t("token_usage.admin.no_data");
876
+ return /* @__PURE__ */ jsx7(RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ jsxs6("div", { className: "flex w-full flex-col gap-4 p-4", children: [
877
+ /* @__PURE__ */ jsx7(TokenUsageAdminTiles, { summary, metric: filters.metric, singleCustomerMode }),
878
+ /* @__PURE__ */ jsxs6(Card, { children: [
879
+ /* @__PURE__ */ jsxs6(CardHeader, { children: [
880
+ /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.usage_over_time") }),
881
+ /* @__PURE__ */ jsx7(CardAction, { children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
882
+ /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground text-xs", children: t("token_usage.admin.stack_by") }),
883
+ /* @__PURE__ */ jsxs6(
884
+ Select,
885
+ {
886
+ items: stackByItems,
887
+ value: filters.stackBy,
888
+ onValueChange: (value) => {
889
+ if (value) setFilters({ stackBy: value });
890
+ },
891
+ children: [
892
+ /* @__PURE__ */ jsx7(SelectTrigger, { size: "sm", className: "w-36", children: /* @__PURE__ */ jsx7(SelectValue, {}) }),
893
+ /* @__PURE__ */ jsx7(SelectContent, { children: STACK_BY_VALUES.map((value) => /* @__PURE__ */ jsx7(SelectItem, { value, children: stackByItems[value] }, value)) })
894
+ ]
895
+ }
896
+ )
897
+ ] }) })
898
+ ] }),
899
+ /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(TokenUsageTimelineChart, { rows: timeline, metric: filters.metric, stackBy: filters.stackBy }) })
900
+ ] }),
901
+ /* @__PURE__ */ jsxs6("div", { className: "grid gap-4 md:grid-cols-2", children: [
902
+ /* @__PURE__ */ jsxs6(Card, { children: [
903
+ /* @__PURE__ */ jsx7(CardHeader, { children: /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.by_company") }) }),
904
+ /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(TokenUsageRankedBar, { rows: byCompany, metric: filters.metric, emptyLabel }) })
905
+ ] }),
906
+ /* @__PURE__ */ jsxs6(Card, { children: [
907
+ /* @__PURE__ */ jsx7(CardHeader, { children: /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.by_user") }) }),
908
+ /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(TokenUsageRankedBar, { rows: byUser, metric: filters.metric, emptyLabel }) })
909
+ ] })
910
+ ] }),
911
+ /* @__PURE__ */ jsxs6("div", { className: cn("grid gap-4", !singleCustomerMode && "md:grid-cols-2"), children: [
912
+ /* @__PURE__ */ jsxs6(Card, { children: [
913
+ /* @__PURE__ */ jsx7(CardHeader, { children: /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.customer_by_operation") }) }),
914
+ /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(
915
+ TokenUsageRankedBar,
916
+ {
917
+ rows: byCustomerOperation,
918
+ metric: filters.metric,
919
+ emptyLabel,
920
+ labelsAreOperationTypes: true
921
+ }
922
+ ) })
923
+ ] }),
924
+ !singleCustomerMode && /* @__PURE__ */ jsxs6(Card, { children: [
925
+ /* @__PURE__ */ jsx7(CardHeader, { children: /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.platform_by_operation") }) }),
926
+ /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(
927
+ TokenUsageRankedBar,
928
+ {
929
+ rows: byOperation,
930
+ metric: filters.metric,
931
+ emptyLabel,
932
+ labelsAreOperationTypes: true
933
+ }
934
+ ) })
935
+ ] })
936
+ ] }),
937
+ /* @__PURE__ */ jsxs6(Card, { children: [
938
+ /* @__PURE__ */ jsx7(CardHeader, { children: /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.detail") }) }),
939
+ /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(TokenUsageBreakdownTable, { rows: byCompany, metric: filters.metric }) })
940
+ ] })
941
+ ] }) });
942
+ }
943
+ __name(TokenUsageAdminContainer, "TokenUsageAdminContainer");
944
+
945
+ // src/features/tokenusage/i18n-keys.ts
946
+ var TOKEN_USAGE_ADMIN_I18N_KEYS = [
947
+ // Page + KPI tiles
948
+ "token_usage.admin.title",
949
+ "token_usage.admin.customer_spend",
950
+ "token_usage.admin.platform_spend",
951
+ "token_usage.admin.total_cost",
952
+ "token_usage.admin.avg_per_call",
953
+ "token_usage.admin.cache_hit",
954
+ "token_usage.admin.vs_previous",
955
+ // Panel titles
956
+ "token_usage.admin.usage_over_time",
957
+ "token_usage.admin.by_company",
958
+ "token_usage.admin.by_user",
959
+ "token_usage.admin.customer_by_operation",
960
+ "token_usage.admin.platform_by_operation",
961
+ "token_usage.admin.detail",
962
+ // Shared states
963
+ "token_usage.admin.other",
964
+ "token_usage.admin.no_data",
965
+ // Filter bar
966
+ "token_usage.admin.all_companies",
967
+ "token_usage.admin.granularity.label",
968
+ "token_usage.admin.granularity.day",
969
+ "token_usage.admin.granularity.week",
970
+ "token_usage.admin.granularity.month",
971
+ "token_usage.admin.metric.label",
972
+ "token_usage.admin.metric.cost",
973
+ "token_usage.admin.metric.credits",
974
+ "token_usage.admin.metric.tokens",
975
+ // Timeline stacking control
976
+ "token_usage.admin.stack_by",
977
+ "token_usage.admin.stack.scope",
978
+ "token_usage.admin.stack.type",
979
+ "token_usage.admin.stack.company",
980
+ // Breakdown table columns
981
+ "token_usage.admin.columns.label",
982
+ "token_usage.admin.columns.sublabel",
983
+ "token_usage.admin.columns.calls",
984
+ "token_usage.admin.columns.tokens_in",
985
+ "token_usage.admin.columns.tokens_out",
986
+ "token_usage.admin.columns.cost",
987
+ "token_usage.admin.columns.credits",
988
+ "token_usage.admin.columns.share",
989
+ "token_usage.admin.columns.active_users",
990
+ // Timeline chart
991
+ "token_usage.series.other",
992
+ "token_usage.timeline.empty"
993
+ ];
994
+ export {
995
+ TOKEN_USAGE_ADMIN_I18N_KEYS,
996
+ TokenUsageAdminBreakdown,
997
+ TokenUsageAdminBreakdownModule,
998
+ TokenUsageAdminContainer,
999
+ TokenUsageAdminFilterBar,
1000
+ TokenUsageAdminProvider,
1001
+ TokenUsageAdminService,
1002
+ TokenUsageAdminSummary,
1003
+ TokenUsageAdminSummaryModule,
1004
+ TokenUsageAdminTiles,
1005
+ TokenUsageAdminTimeline,
1006
+ TokenUsageAdminTimelineModule,
1007
+ TokenUsageBreakdownTable,
1008
+ TokenUsageRankedBar,
1009
+ TokenUsageTimelineChart,
1010
+ useTokenUsageAdmin
1011
+ };
1012
+ //# sourceMappingURL=index.mjs.map