@carlonicora/nextjs-jsonapi 2.4.0 → 2.5.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 (85) hide show
  1. package/dist/{BlockNoteEditor-7WGCH2KB.js → BlockNoteEditor-TIMGYKAA.js} +19 -19
  2. package/dist/{BlockNoteEditor-7WGCH2KB.js.map → BlockNoteEditor-TIMGYKAA.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-GWAV5ESO.mjs → BlockNoteEditor-YFJMXJEU.mjs} +4 -4
  4. package/dist/billing/index.js +362 -362
  5. package/dist/billing/index.mjs +3 -3
  6. package/dist/{chunk-GYGMEDVS.js → chunk-7HKJNUW7.js} +755 -746
  7. package/dist/chunk-7HKJNUW7.js.map +1 -0
  8. package/dist/{chunk-3LVSA7IM.mjs → chunk-DJWNNV52.mjs} +2 -2
  9. package/dist/{chunk-LCCRUWWY.mjs → chunk-FS4RVV3K.mjs} +241 -1
  10. package/dist/chunk-FS4RVV3K.mjs.map +1 -0
  11. package/dist/{chunk-M2EGUO2F.mjs → chunk-G4YS52SO.mjs} +16 -7
  12. package/dist/chunk-G4YS52SO.mjs.map +1 -0
  13. package/dist/{chunk-TOKHHZSP.js → chunk-OTEDZ6YW.js} +7 -7
  14. package/dist/{chunk-TOKHHZSP.js.map → chunk-OTEDZ6YW.js.map} +1 -1
  15. package/dist/{chunk-V66AZWPG.js → chunk-RTOC53EZ.js} +246 -6
  16. package/dist/chunk-RTOC53EZ.js.map +1 -0
  17. package/dist/client/index.js +4 -4
  18. package/dist/client/index.mjs +3 -3
  19. package/dist/components/index.d.mts +16 -1
  20. package/dist/components/index.d.ts +16 -1
  21. package/dist/components/index.js +4 -4
  22. package/dist/components/index.mjs +3 -3
  23. package/dist/{tokenusage-admin.module-5wJ_tZTj.d.ts → config-BIjrg5wd.d.ts} +39 -1
  24. package/dist/{tokenusage-admin.module-DQVT4O4j.d.mts → config-BRUjtCd1.d.mts} +39 -1
  25. package/dist/contexts/index.js +4 -4
  26. package/dist/contexts/index.mjs +3 -3
  27. package/dist/core/index.d.mts +5 -2
  28. package/dist/core/index.d.ts +5 -2
  29. package/dist/core/index.js +8 -2
  30. package/dist/core/index.js.map +1 -1
  31. package/dist/core/index.mjs +7 -1
  32. package/dist/features/help/index.js +37 -37
  33. package/dist/features/help/index.mjs +3 -3
  34. package/dist/features/tokenusage/index.d.mts +413 -14
  35. package/dist/features/tokenusage/index.d.ts +413 -14
  36. package/dist/features/tokenusage/index.js +481 -120
  37. package/dist/features/tokenusage/index.js.map +1 -1
  38. package/dist/features/tokenusage/index.mjs +431 -70
  39. package/dist/features/tokenusage/index.mjs.map +1 -1
  40. package/dist/index.d.mts +2 -2
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +9 -3
  43. package/dist/index.js.map +1 -1
  44. package/dist/index.mjs +8 -2
  45. package/dist/server/index.js +3 -3
  46. package/dist/server/index.mjs +1 -1
  47. package/package.json +1 -1
  48. package/src/core/index.ts +10 -0
  49. package/src/core/registry/ModuleRegistry.ts +4 -0
  50. package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +20 -4
  51. package/src/features/tokenusage/components/TokenUsageAdminTiles.tsx +31 -18
  52. package/src/features/tokenusage/components/TokenUsageBreakdownTable.tsx +13 -13
  53. package/src/features/tokenusage/components/TokenUsageRankedBar.tsx +24 -7
  54. package/src/features/tokenusage/components/TokenUsageReportContainer.tsx +102 -0
  55. package/src/features/tokenusage/components/TokenUsageReportFilterBar.tsx +34 -0
  56. package/src/features/tokenusage/components/TokenUsageReportTiles.tsx +161 -0
  57. package/src/features/tokenusage/components/TokenUsageTimelineChart.tsx +15 -13
  58. package/src/features/tokenusage/components/__tests__/TokenUsageAdminTiles.spec.tsx +4 -2
  59. package/src/features/tokenusage/components/__tests__/TokenUsageRankedBar.spec.tsx +3 -1
  60. package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +6 -3
  61. package/src/features/tokenusage/contexts/TokenUsageReportContext.tsx +199 -0
  62. package/src/features/tokenusage/data/TokenUsageReportService.ts +65 -0
  63. package/src/features/tokenusage/data/index.ts +9 -0
  64. package/src/features/tokenusage/data/tokenusage-report-breakdown.interface.ts +12 -0
  65. package/src/features/tokenusage/data/tokenusage-report-breakdown.ts +94 -0
  66. package/src/features/tokenusage/data/tokenusage-report-summary.interface.ts +12 -0
  67. package/src/features/tokenusage/data/tokenusage-report-summary.ts +87 -0
  68. package/src/features/tokenusage/data/tokenusage-report-timeline.interface.ts +13 -0
  69. package/src/features/tokenusage/data/tokenusage-report-timeline.ts +94 -0
  70. package/src/features/tokenusage/data/tokenusage-report.types.ts +51 -0
  71. package/src/features/tokenusage/i18n-keys.ts +28 -0
  72. package/src/features/tokenusage/index.ts +18 -0
  73. package/src/features/tokenusage/lib/config.ts +22 -0
  74. package/src/features/tokenusage/lib/formatters.ts +100 -0
  75. package/src/features/tokenusage/lib/metrics.ts +8 -26
  76. package/src/features/tokenusage/lib/palette.ts +41 -14
  77. package/src/features/tokenusage/tokenusage-admin.module.ts +4 -0
  78. package/src/features/tokenusage/tokenusage.modules.ts +40 -0
  79. package/src/shadcnui/ui/chart.tsx +30 -3
  80. package/dist/chunk-GYGMEDVS.js.map +0 -1
  81. package/dist/chunk-LCCRUWWY.mjs.map +0 -1
  82. package/dist/chunk-M2EGUO2F.mjs.map +0 -1
  83. package/dist/chunk-V66AZWPG.js.map +0 -1
  84. /package/dist/{BlockNoteEditor-GWAV5ESO.mjs.map → BlockNoteEditor-YFJMXJEU.mjs.map} +0 -0
  85. /package/dist/{chunk-3LVSA7IM.mjs.map → chunk-DJWNNV52.mjs.map} +0 -0
@@ -26,8 +26,8 @@ import {
26
26
  TableHeader,
27
27
  TableRow,
28
28
  usePageUrlGenerator
29
- } from "../../chunk-M2EGUO2F.mjs";
30
- import "../../chunk-3LVSA7IM.mjs";
29
+ } from "../../chunk-G4YS52SO.mjs";
30
+ import "../../chunk-DJWNNV52.mjs";
31
31
  import {
32
32
  AbstractService,
33
33
  EndpointCreator,
@@ -38,8 +38,14 @@ import {
38
38
  TokenUsageAdminSummaryModule,
39
39
  TokenUsageAdminTimeline,
40
40
  TokenUsageAdminTimelineModule,
41
- cn
42
- } from "../../chunk-LCCRUWWY.mjs";
41
+ TokenUsageReportBreakdown,
42
+ TokenUsageReportSummary,
43
+ TokenUsageReportTimeline,
44
+ cn,
45
+ configureTokenUsage,
46
+ getTokenUsageCurrency,
47
+ tokenUsageModules
48
+ } from "../../chunk-FS4RVV3K.mjs";
43
49
  import "../../chunk-AUXK7QSA.mjs";
44
50
  import "../../chunk-C7C7VY4F.mjs";
45
51
  import "../../chunk-E5YA5Z5X.mjs";
@@ -209,7 +215,8 @@ var TokenUsageAdminProvider = /* @__PURE__ */ __name(({
209
215
  initialCompanyId,
210
216
  initialFrom,
211
217
  initialTo,
212
- topN = DEFAULT_TOP_N
218
+ topN = DEFAULT_TOP_N,
219
+ pageUrl = TOKEN_USAGE_ADMIN_PAGE_URL
213
220
  }) => {
214
221
  const t = useTranslations2();
215
222
  const generateUrl = usePageUrlGenerator();
@@ -281,7 +288,7 @@ var TokenUsageAdminProvider = /* @__PURE__ */ __name(({
281
288
  const breadcrumb = /* @__PURE__ */ __name(() => [
282
289
  {
283
290
  name: t("token_usage.admin.title"),
284
- href: generateUrl({ page: TOKEN_USAGE_ADMIN_PAGE_URL })
291
+ href: generateUrl({ page: pageUrl })
285
292
  }
286
293
  ], "breadcrumb");
287
294
  const title = /* @__PURE__ */ __name(() => ({
@@ -342,37 +349,62 @@ var useTokenUsageAdmin = /* @__PURE__ */ __name(() => {
342
349
 
343
350
  // src/features/tokenusage/components/TokenUsageAdminContainer.tsx
344
351
  import { useTranslations as useTranslations7 } from "next-intl";
345
- import { useMemo as useMemo4 } from "react";
352
+ import { useMemo as useMemo5 } from "react";
346
353
 
347
354
  // src/features/tokenusage/components/TokenUsageAdminTiles.tsx
348
355
  import { ArrowDownIcon, ArrowUpIcon } from "lucide-react";
349
356
  import { useTranslations as useTranslations3 } from "next-intl";
350
357
 
358
+ // src/features/tokenusage/lib/formatters.ts
359
+ import { useLocale } from "next-intl";
360
+ import { useMemo as useMemo2 } from "react";
361
+ function createUsageFormatters(locale, currency) {
362
+ const symbol = new Intl.NumberFormat(locale, { style: "currency", currency, currencyDisplay: "narrowSymbol" }).formatToParts(0).find((part) => part.type === "currency")?.value ?? currency;
363
+ const decimal = /* @__PURE__ */ __name((value, decimals) => value.toLocaleString(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }), "decimal");
364
+ const dayFormat = new Intl.DateTimeFormat(locale, { day: "numeric", month: "short", timeZone: "UTC" });
365
+ const monthFormat = new Intl.DateTimeFormat(locale, { month: "short", year: "numeric", timeZone: "UTC" });
366
+ const compactFormat = new Intl.NumberFormat(locale, { notation: "compact", maximumFractionDigits: 1 });
367
+ const collator = new Intl.Collator(locale);
368
+ return {
369
+ decimal,
370
+ metricValue(value, metric) {
371
+ if (metric === "cost") return `${symbol} ${decimal(value, 2)}`;
372
+ if (metric === "credits") return decimal(value, 1);
373
+ return decimal(value, 0);
374
+ },
375
+ currency(value, decimals) {
376
+ return `${symbol} ${decimal(value, decimals)}`;
377
+ },
378
+ percent(value, decimals = 1) {
379
+ return `${decimal(value, decimals)} %`;
380
+ },
381
+ compact(value) {
382
+ return compactFormat.format(value);
383
+ },
384
+ bucketDate(iso, granularity) {
385
+ const date = /* @__PURE__ */ new Date(`${iso}T00:00:00.000Z`);
386
+ return granularity === "month" ? monthFormat.format(date) : dayFormat.format(date);
387
+ },
388
+ compare(a, b) {
389
+ return collator.compare(a, b);
390
+ }
391
+ };
392
+ }
393
+ __name(createUsageFormatters, "createUsageFormatters");
394
+ function useUsageFormatters() {
395
+ const locale = useLocale();
396
+ const currency = getTokenUsageCurrency();
397
+ return useMemo2(() => createUsageFormatters(locale, currency), [locale, currency]);
398
+ }
399
+ __name(useUsageFormatters, "useUsageFormatters");
400
+
351
401
  // src/features/tokenusage/lib/metrics.ts
352
- var LOCALE = "it-IT";
353
402
  function metricValue(row, metric) {
354
403
  if (metric === "cost") return row.cost;
355
404
  if (metric === "credits") return row.credits;
356
405
  return row.tokensIn + row.tokensOut;
357
406
  }
358
407
  __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
408
  function cacheHitPercentage(cached, tokensIn) {
377
409
  if (!tokensIn) return 0;
378
410
  return cached / tokensIn * 100;
@@ -389,6 +421,7 @@ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
389
421
  var ZERO = { cost: 0, credits: 0, tokensIn: 0, tokensOut: 0, cached: 0, calls: 0 };
390
422
  function TokenUsageAdminTiles({ summary, metric, singleCustomerMode }) {
391
423
  const t = useTranslations3();
424
+ const { decimal, metricValue: formatValue, currency, percent } = useUsageFormatters();
392
425
  const rowFor = /* @__PURE__ */ __name((scope, window) => summary.find((r) => r.scope === scope && r.window === window) ?? ZERO, "rowFor");
393
426
  const customerCurrent = rowFor("customer", "current");
394
427
  const customerPrevious = rowFor("customer", "previous");
@@ -405,9 +438,10 @@ function TokenUsageAdminTiles({ summary, metric, singleCustomerMode }) {
405
438
  {
406
439
  testId: "tile-customer",
407
440
  label: t("token_usage.admin.customer_spend"),
408
- value: formatMetricValue(metricValue(customerCurrent, metric), metric),
441
+ value: formatValue(metricValue(customerCurrent, metric), metric),
409
442
  delta: percentageDelta(metricValue(customerCurrent, metric), metricValue(customerPrevious, metric)),
410
- previousLabel: t("token_usage.admin.vs_previous")
443
+ previousLabel: t("token_usage.admin.vs_previous"),
444
+ decimal
411
445
  }
412
446
  ),
413
447
  !singleCustomerMode && /* @__PURE__ */ jsx3(
@@ -415,9 +449,10 @@ function TokenUsageAdminTiles({ summary, metric, singleCustomerMode }) {
415
449
  {
416
450
  testId: "tile-platform",
417
451
  label: t("token_usage.admin.platform_spend"),
418
- value: formatMetricValue(metricValue(platformCurrent, metric), metric),
452
+ value: formatValue(metricValue(platformCurrent, metric), metric),
419
453
  delta: percentageDelta(metricValue(platformCurrent, metric), metricValue(platformPrevious, metric)),
420
- previousLabel: t("token_usage.admin.vs_previous")
454
+ previousLabel: t("token_usage.admin.vs_previous"),
455
+ decimal
421
456
  }
422
457
  )
423
458
  ] }),
@@ -427,8 +462,9 @@ function TokenUsageAdminTiles({ summary, metric, singleCustomerMode }) {
427
462
  {
428
463
  testId: "tile-total",
429
464
  label: t("token_usage.admin.total_cost"),
430
- value: formatMetricValue(metricValue(totalCurrent, metric), metric),
431
- delta: percentageDelta(metricValue(totalCurrent, metric), metricValue(totalPrevious, metric))
465
+ value: formatValue(metricValue(totalCurrent, metric), metric),
466
+ delta: percentageDelta(metricValue(totalCurrent, metric), metricValue(totalPrevious, metric)),
467
+ decimal
432
468
  }
433
469
  ),
434
470
  /* @__PURE__ */ jsx3(
@@ -436,7 +472,8 @@ function TokenUsageAdminTiles({ summary, metric, singleCustomerMode }) {
436
472
  {
437
473
  testId: "tile-avg-per-call",
438
474
  label: t("token_usage.admin.avg_per_call"),
439
- value: `\u20AC ${formatDecimal(averagePerCall, 4)}`
475
+ value: currency(averagePerCall, 4),
476
+ decimal
440
477
  }
441
478
  ),
442
479
  /* @__PURE__ */ jsx3(
@@ -444,7 +481,8 @@ function TokenUsageAdminTiles({ summary, metric, singleCustomerMode }) {
444
481
  {
445
482
  testId: "tile-cache-hit",
446
483
  label: t("token_usage.admin.cache_hit"),
447
- value: formatPercent(cacheHit)
484
+ value: percent(cacheHit),
485
+ decimal
448
486
  }
449
487
  )
450
488
  ] })
@@ -456,13 +494,14 @@ function LeadTile({
456
494
  label,
457
495
  value,
458
496
  delta,
459
- previousLabel
497
+ previousLabel,
498
+ decimal
460
499
  }) {
461
500
  return /* @__PURE__ */ jsx3(Card, { "data-testid": testId, children: /* @__PURE__ */ jsxs2(CardContent, { className: "grid gap-1", children: [
462
501
  /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground text-xs", children: label }),
463
502
  /* @__PURE__ */ jsx3("span", { className: "text-primary text-xl font-semibold tabular-nums", children: value }),
464
503
  /* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-1", children: [
465
- /* @__PURE__ */ jsx3(Delta, { testId: `${testId}-delta`, delta }),
504
+ /* @__PURE__ */ jsx3(Delta, { testId: `${testId}-delta`, delta, decimal }),
466
505
  /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground text-xs", children: previousLabel })
467
506
  ] })
468
507
  ] }) });
@@ -472,18 +511,23 @@ function SupportingTile({
472
511
  testId,
473
512
  label,
474
513
  value,
475
- delta
514
+ delta,
515
+ decimal
476
516
  }) {
477
517
  return /* @__PURE__ */ jsx3(Card, { "data-testid": testId, size: "sm", children: /* @__PURE__ */ jsxs2(CardContent, { className: "grid gap-1", children: [
478
518
  /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground text-xs", children: label }),
479
519
  /* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-2", children: [
480
520
  /* @__PURE__ */ jsx3("span", { className: "text-sm font-medium tabular-nums", children: value }),
481
- delta !== void 0 && /* @__PURE__ */ jsx3(Delta, { testId: `${testId}-delta`, delta })
521
+ delta !== void 0 && /* @__PURE__ */ jsx3(Delta, { testId: `${testId}-delta`, delta, decimal })
482
522
  ] })
483
523
  ] }) });
484
524
  }
485
525
  __name(SupportingTile, "SupportingTile");
486
- function Delta({ testId, delta }) {
526
+ function Delta({
527
+ testId,
528
+ delta,
529
+ decimal
530
+ }) {
487
531
  if (delta === void 0) {
488
532
  return /* @__PURE__ */ jsx3("span", { "data-testid": testId, className: "text-muted-foreground text-xs", children: "\u2014" });
489
533
  }
@@ -499,7 +543,7 @@ function Delta({ testId, delta }) {
499
543
  ),
500
544
  children: [
501
545
  /* @__PURE__ */ jsx3(Icon, { "aria-hidden": true, className: "size-3" }),
502
- formatDecimal(Math.abs(delta), 0),
546
+ decimal(Math.abs(delta), 0),
503
547
  " %"
504
548
  ]
505
549
  }
@@ -510,13 +554,14 @@ __name(Delta, "Delta");
510
554
  // src/features/tokenusage/components/TokenUsageBreakdownTable.tsx
511
555
  import { ArrowDownIcon as ArrowDownIcon2, ArrowUpIcon as ArrowUpIcon2 } from "lucide-react";
512
556
  import { useTranslations as useTranslations4 } from "next-intl";
513
- import { useMemo as useMemo2, useState as useState2 } from "react";
557
+ import { useMemo as useMemo3, useState as useState2 } from "react";
514
558
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
515
559
  function TokenUsageBreakdownTable({ rows, metric }) {
516
560
  const t = useTranslations4();
561
+ const { decimal, metricValue: formatValue, percent, compare } = useUsageFormatters();
517
562
  const [sort, setSort] = useState2(void 0);
518
563
  const showActiveUsers = rows.some((row) => row.activeUsers !== void 0);
519
- const total = useMemo2(() => rows.reduce((sum, row) => sum + metricValue(row, metric), 0), [rows, metric]);
564
+ const total = useMemo3(() => rows.reduce((sum, row) => sum + metricValue(row, metric), 0), [rows, metric]);
520
565
  const sortValue = /* @__PURE__ */ __name((row, key) => {
521
566
  switch (key) {
522
567
  case "label":
@@ -533,18 +578,18 @@ function TokenUsageBreakdownTable({ rows, metric }) {
533
578
  return row[key];
534
579
  }
535
580
  }, "sortValue");
536
- const sortedRows = useMemo2(() => {
581
+ const sortedRows = useMemo3(() => {
537
582
  if (!sort) return rows;
538
583
  const factor = sort.direction === "desc" ? -1 : 1;
539
584
  return [...rows].sort((a, b) => {
540
585
  const left = sortValue(a, sort.key);
541
586
  const right = sortValue(b, sort.key);
542
587
  if (typeof left === "string" || typeof right === "string") {
543
- return String(left).localeCompare(String(right), "it-IT") * factor;
588
+ return compare(String(left), String(right)) * factor;
544
589
  }
545
590
  return (left - right) * factor;
546
591
  });
547
- }, [rows, sort, metric]);
592
+ }, [rows, sort, metric, compare]);
548
593
  const toggleSort = /* @__PURE__ */ __name((key) => setSort(
549
594
  (current) => current?.key === key ? { key, direction: current.direction === "desc" ? "asc" : "desc" } : { key, direction: "desc" }
550
595
  ), "toggleSort");
@@ -588,14 +633,14 @@ function TokenUsageBreakdownTable({ rows, metric }) {
588
633
  return /* @__PURE__ */ jsxs3(TableRow, { "data-testid": `breakdown-row-${row.id}`, children: [
589
634
  /* @__PURE__ */ jsx4(TableCell, { className: "text-xs", children: row.id === "other" ? t("token_usage.admin.other") : row.label }),
590
635
  /* @__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) })
636
+ showActiveUsers && /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: row.activeUsers === void 0 ? "" : decimal(row.activeUsers, 0) }),
637
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: decimal(row.calls, 0) }),
638
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: decimal(row.tokensIn, 0) }),
639
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: decimal(row.tokensOut, 0) }),
640
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: percent(cacheHitPercentage(row.cached, row.tokensIn)) }),
641
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatValue(row.cost, "cost") }),
642
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: formatValue(row.credits, "credits") }),
643
+ /* @__PURE__ */ jsx4(TableCell, { className: "text-right text-xs tabular-nums", children: percent(share) })
599
644
  ] }, row.id);
600
645
  }) })
601
646
  ] }) })
@@ -654,6 +699,7 @@ __name(seriesColor, "seriesColor");
654
699
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
655
700
  function TokenUsageRankedBar({ rows, metric, emptyLabel, labelsAreOperationTypes = false }) {
656
701
  const t = useTranslations5();
702
+ const { metricValue: formatValue, percent } = useUsageFormatters();
657
703
  const renderLabel = /* @__PURE__ */ __name((row) => {
658
704
  if (row.id === "other") return t("token_usage.admin.other");
659
705
  if (!labelsAreOperationTypes) return row.label;
@@ -688,13 +734,13 @@ function TokenUsageRankedBar({ rows, metric, emptyLabel, labelsAreOperationTypes
688
734
  }
689
735
  ) })
690
736
  ] }),
691
- /* @__PURE__ */ jsx5("span", { "data-testid": `ranked-value-${row.id}`, className: "text-right text-xs tabular-nums", children: formatMetricValue(value, metric) }),
737
+ /* @__PURE__ */ jsx5("span", { "data-testid": `ranked-value-${row.id}`, className: "text-right text-xs tabular-nums", children: formatValue(value, metric) }),
692
738
  /* @__PURE__ */ jsx5(
693
739
  "span",
694
740
  {
695
741
  "data-testid": `ranked-share-${row.id}`,
696
742
  className: "text-muted-foreground w-16 text-right text-xs tabular-nums",
697
- children: formatPercent(share)
743
+ children: percent(share)
698
744
  }
699
745
  )
700
746
  ] }, row.id);
@@ -705,7 +751,7 @@ __name(TokenUsageRankedBar, "TokenUsageRankedBar");
705
751
  // src/features/tokenusage/components/TokenUsageTimelineChart.tsx
706
752
  import { useTranslations as useTranslations6 } from "next-intl";
707
753
  import { useTheme } from "next-themes";
708
- import { useMemo as useMemo3 } from "react";
754
+ import { useMemo as useMemo4 } from "react";
709
755
  import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
710
756
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
711
757
  var OTHER_SERIES = "other";
@@ -732,13 +778,12 @@ var inferGranularity = /* @__PURE__ */ __name((buckets) => {
732
778
  if (smallestGap >= 7) return "week";
733
779
  return "day";
734
780
  }, "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
781
  function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
738
782
  const t = useTranslations6();
783
+ const { compact, bucketDate, metricValue: formatValue } = useUsageFormatters();
739
784
  const { resolvedTheme } = useTheme();
740
785
  const mode = resolvedTheme === "dark" ? "dark" : "light";
741
- const { chartData, seriesKeys } = useMemo3(() => {
786
+ const { chartData, seriesKeys } = useMemo4(() => {
742
787
  if (rows.length === 0) return { chartData: [], seriesKeys: [] };
743
788
  const totals = /* @__PURE__ */ new Map();
744
789
  for (const row of rows) totals.set(row.series, (totals.get(row.series) ?? 0) + metricValue2(row, metric));
@@ -766,7 +811,7 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
766
811
  const seriesLabel = /* @__PURE__ */ __name((series) => {
767
812
  if (series === OTHER_SERIES) {
768
813
  const key = "token_usage.series.other";
769
- return t.has(key) ? t(key) : "Altro";
814
+ return t.has(key) ? t(key) : "Other";
770
815
  }
771
816
  if (stackBy === "company") return series;
772
817
  return operationLabel(
@@ -775,16 +820,15 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
775
820
  (key) => t.has(key)
776
821
  );
777
822
  }, "seriesLabel");
778
- const chartConfig = useMemo3(
823
+ const chartConfig = useMemo4(
779
824
  () => Object.fromEntries(seriesKeys.map((series) => [series, { label: seriesLabel(series) }])),
780
825
  [seriesKeys, stackBy]
781
826
  );
782
827
  if (chartData.length === 0) {
783
828
  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" });
829
+ return /* @__PURE__ */ jsx6("p", { className: className ? `text-muted-foreground text-sm ${className}` : "text-muted-foreground text-sm", children: t.has(key) ? t(key) : "No data in the selected period" });
785
830
  }
786
831
  const granularity = inferGranularity(chartData.map((entry) => entry.bucket));
787
- const formatBucket = bucketFormatter(granularity);
788
832
  const seriesColorFor = /* @__PURE__ */ __name((series, index) => series === OTHER_SERIES ? OTHER_COLOR : seriesColor(index, mode), "seriesColorFor");
789
833
  return /* @__PURE__ */ jsxs5("div", { className, children: [
790
834
  /* @__PURE__ */ jsx6("span", { className: "sr-only", "data-testid": "timeline-data", children: JSON.stringify(chartData) }),
@@ -799,7 +843,7 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
799
843
  axisLine: false,
800
844
  tickMargin: 8,
801
845
  minTickGap: 16,
802
- tickFormatter: (value) => formatBucket.format(/* @__PURE__ */ new Date(`${value}T00:00:00.000Z`))
846
+ tickFormatter: (value) => bucketDate(value, granularity)
803
847
  }
804
848
  ),
805
849
  /* @__PURE__ */ jsx6(
@@ -809,7 +853,7 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
809
853
  axisLine: false,
810
854
  tickMargin: 8,
811
855
  width: 48,
812
- tickFormatter: (value) => compactNumber.format(value)
856
+ tickFormatter: (value) => compact(value)
813
857
  }
814
858
  ),
815
859
  /* @__PURE__ */ jsx6(
@@ -818,7 +862,9 @@ function TokenUsageTimelineChart({ rows, metric, stackBy, className }) {
818
862
  content: /* @__PURE__ */ jsx6(
819
863
  ChartTooltipContent,
820
864
  {
821
- labelFormatter: (value) => formatBucket.format(/* @__PURE__ */ new Date(`${String(value)}T00:00:00.000Z`))
865
+ labelFormatter: (value) => bucketDate(String(value), granularity),
866
+ valueFormatter: (value) => formatValue(Number(value), metric),
867
+ hideZeroValues: true
822
868
  }
823
869
  )
824
870
  }
@@ -860,7 +906,12 @@ function TokenUsageAdminContainer() {
860
906
  isLoading,
861
907
  error
862
908
  } = useTokenUsageAdmin();
863
- const stackByItems = useMemo4(
909
+ const platformIsEmpty = useMemo5(
910
+ () => summary.filter((row) => row.scope === "platform").every((row) => row.cost === 0 && row.credits === 0 && row.tokensIn === 0 && row.tokensOut === 0),
911
+ [summary]
912
+ );
913
+ const hidePlatform = singleCustomerMode || platformIsEmpty;
914
+ const stackByItems = useMemo5(
864
915
  () => ({
865
916
  scope: t("token_usage.admin.stack.scope"),
866
917
  type: t("token_usage.admin.stack.type"),
@@ -874,7 +925,7 @@ function TokenUsageAdminContainer() {
874
925
  if (isLoading) return /* @__PURE__ */ jsx7(RoundPageContainer, { fullWidth: true, forceHeader: true });
875
926
  const emptyLabel = t("token_usage.admin.no_data");
876
927
  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 }),
928
+ /* @__PURE__ */ jsx7(TokenUsageAdminTiles, { summary, metric: filters.metric, singleCustomerMode: hidePlatform }),
878
929
  /* @__PURE__ */ jsxs6(Card, { children: [
879
930
  /* @__PURE__ */ jsxs6(CardHeader, { children: [
880
931
  /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.usage_over_time") }),
@@ -908,7 +959,7 @@ function TokenUsageAdminContainer() {
908
959
  /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(TokenUsageRankedBar, { rows: byUser, metric: filters.metric, emptyLabel }) })
909
960
  ] })
910
961
  ] }),
911
- /* @__PURE__ */ jsxs6("div", { className: cn("grid gap-4", !singleCustomerMode && "md:grid-cols-2"), children: [
962
+ /* @__PURE__ */ jsxs6("div", { className: cn("grid gap-4", !hidePlatform && "md:grid-cols-2"), children: [
912
963
  /* @__PURE__ */ jsxs6(Card, { children: [
913
964
  /* @__PURE__ */ jsx7(CardHeader, { children: /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.customer_by_operation") }) }),
914
965
  /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(
@@ -921,7 +972,7 @@ function TokenUsageAdminContainer() {
921
972
  }
922
973
  ) })
923
974
  ] }),
924
- !singleCustomerMode && /* @__PURE__ */ jsxs6(Card, { children: [
975
+ !hidePlatform && /* @__PURE__ */ jsxs6(Card, { children: [
925
976
  /* @__PURE__ */ jsx7(CardHeader, { children: /* @__PURE__ */ jsx7(CardTitle, { children: t("token_usage.admin.platform_by_operation") }) }),
926
977
  /* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsx7(
927
978
  TokenUsageRankedBar,
@@ -942,6 +993,49 @@ function TokenUsageAdminContainer() {
942
993
  }
943
994
  __name(TokenUsageAdminContainer, "TokenUsageAdminContainer");
944
995
 
996
+ // src/features/tokenusage/data/TokenUsageReportService.ts
997
+ function withFilters2(endpoint, filters) {
998
+ endpoint.addAdditionalParam("from", filters.from);
999
+ endpoint.addAdditionalParam("to", filters.to);
1000
+ return endpoint;
1001
+ }
1002
+ __name(withFilters2, "withFilters");
1003
+ var TokenUsageReportService = class extends AbstractService {
1004
+ static {
1005
+ __name(this, "TokenUsageReportService");
1006
+ }
1007
+ /** Two rows: window "current" and "previous". */
1008
+ static async getSummary(filters) {
1009
+ const endpoint = withFilters2(new EndpointCreator({ endpoint: Modules.TokenUsageReportSummary }), filters);
1010
+ return this.callApi({
1011
+ type: Modules.TokenUsageReportSummary,
1012
+ method: "GET" /* GET */,
1013
+ endpoint: endpoint.generate()
1014
+ });
1015
+ }
1016
+ static async getTimeline(params) {
1017
+ const endpoint = withFilters2(new EndpointCreator({ endpoint: Modules.TokenUsageReportTimeline }), params);
1018
+ endpoint.addAdditionalParam("granularity", params.granularity);
1019
+ return this.callApi({
1020
+ type: Modules.TokenUsageReportTimeline,
1021
+ method: "GET" /* GET */,
1022
+ endpoint: endpoint.generate()
1023
+ });
1024
+ }
1025
+ static async getBreakdown(params) {
1026
+ const endpoint = withFilters2(new EndpointCreator({ endpoint: Modules.TokenUsageReportBreakdown }), params);
1027
+ endpoint.addAdditionalParam("dimension", params.dimension);
1028
+ if (params.targetLabel) endpoint.addAdditionalParam("targetLabel", params.targetLabel);
1029
+ endpoint.addAdditionalParam("metric", params.metric);
1030
+ if (params.limit !== void 0) endpoint.addAdditionalParam("limit", String(params.limit));
1031
+ return this.callApi({
1032
+ type: Modules.TokenUsageReportBreakdown,
1033
+ method: "GET" /* GET */,
1034
+ endpoint: endpoint.generate()
1035
+ });
1036
+ }
1037
+ };
1038
+
945
1039
  // src/features/tokenusage/i18n-keys.ts
946
1040
  var TOKEN_USAGE_ADMIN_I18N_KEYS = [
947
1041
  // Page + KPI tiles
@@ -991,8 +1085,261 @@ var TOKEN_USAGE_ADMIN_I18N_KEYS = [
991
1085
  "token_usage.series.other",
992
1086
  "token_usage.timeline.empty"
993
1087
  ];
1088
+ var TOKEN_USAGE_REPORT_I18N_KEYS = [
1089
+ // Page + KPI tiles
1090
+ "token_usage.report.title",
1091
+ "token_usage.report.used_in_period",
1092
+ "token_usage.report.vs_previous",
1093
+ "token_usage.report.monthly_left",
1094
+ "token_usage.report.extra_credits",
1095
+ "token_usage.report.calls",
1096
+ // Panel titles
1097
+ "token_usage.report.usage_over_time",
1098
+ "token_usage.report.by_operation",
1099
+ // Shared states
1100
+ "token_usage.report.no_data"
1101
+ ];
1102
+
1103
+ // src/features/tokenusage/contexts/TokenUsageReportContext.tsx
1104
+ import { useTranslations as useTranslations8 } from "next-intl";
1105
+ import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect2, useMemo as useMemo6, useState as useState3 } from "react";
1106
+
1107
+ // src/features/tokenusage/components/TokenUsageReportFilterBar.tsx
1108
+ import { jsx as jsx8 } from "react/jsx-runtime";
1109
+ function TokenUsageReportFilterBar({ onChange }) {
1110
+ return /* @__PURE__ */ jsx8("div", { className: "flex flex-wrap items-center gap-2", children: /* @__PURE__ */ jsx8(
1111
+ DateRangeSelector,
1112
+ {
1113
+ onDateChange: (range) => {
1114
+ if (!range?.from || !range?.to) return;
1115
+ onChange({ from: range.from.toISOString(), to: range.to.toISOString() });
1116
+ }
1117
+ }
1118
+ ) });
1119
+ }
1120
+ __name(TokenUsageReportFilterBar, "TokenUsageReportFilterBar");
1121
+
1122
+ // src/features/tokenusage/contexts/TokenUsageReportContext.tsx
1123
+ import { jsx as jsx9 } from "react/jsx-runtime";
1124
+ var TOKEN_USAGE_REPORT_PAGE_URL = "/tokenusage";
1125
+ var DEFAULT_TOP_N2 = 10;
1126
+ var REPORT_METRIC = "credits";
1127
+ var TokenUsageReportContext = createContext2(void 0);
1128
+ function defaultRange2() {
1129
+ const now = /* @__PURE__ */ new Date();
1130
+ const start = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
1131
+ return { from: start.toISOString(), to: now.toISOString() };
1132
+ }
1133
+ __name(defaultRange2, "defaultRange");
1134
+ var TokenUsageReportProvider = /* @__PURE__ */ __name(({
1135
+ children,
1136
+ targetLabel,
1137
+ targetPanelTitleKey,
1138
+ initialFrom,
1139
+ initialTo,
1140
+ topN = DEFAULT_TOP_N2,
1141
+ pageUrl = TOKEN_USAGE_REPORT_PAGE_URL
1142
+ }) => {
1143
+ const t = useTranslations8();
1144
+ const generateUrl = usePageUrlGenerator();
1145
+ const [filters, setFilterState] = useState3(() => {
1146
+ const range = defaultRange2();
1147
+ return { from: initialFrom ?? range.from, to: initialTo ?? range.to };
1148
+ });
1149
+ const [summary, setSummary] = useState3([]);
1150
+ const [timeline, setTimeline] = useState3([]);
1151
+ const [byOperation, setByOperation] = useState3([]);
1152
+ const [byTarget, setByTarget] = useState3([]);
1153
+ const [isLoading, setIsLoading] = useState3(true);
1154
+ const [error, setError] = useState3(null);
1155
+ const { from, to } = filters;
1156
+ useEffect2(() => {
1157
+ let cancelled = false;
1158
+ setIsLoading(true);
1159
+ setError(null);
1160
+ const base = { from, to };
1161
+ Promise.all([
1162
+ TokenUsageReportService.getSummary(base),
1163
+ // granularity is pinned to "day": week/month bucketing buys nothing on the
1164
+ // ranges a single tenant browses.
1165
+ TokenUsageReportService.getTimeline({ ...base, granularity: "day" }),
1166
+ TokenUsageReportService.getBreakdown({ ...base, dimension: "operation", metric: REPORT_METRIC, limit: topN }),
1167
+ // No targetLabel means the host app never opted in, so the request is
1168
+ // skipped entirely rather than issued and discarded.
1169
+ targetLabel ? TokenUsageReportService.getBreakdown({
1170
+ ...base,
1171
+ dimension: "target",
1172
+ targetLabel,
1173
+ metric: REPORT_METRIC,
1174
+ limit: topN
1175
+ }) : Promise.resolve([])
1176
+ ]).then(([nextSummary, nextTimeline, nextByOperation, nextByTarget]) => {
1177
+ if (cancelled) return;
1178
+ setSummary(nextSummary ?? []);
1179
+ setTimeline(nextTimeline ?? []);
1180
+ setByOperation(nextByOperation ?? []);
1181
+ setByTarget(nextByTarget ?? []);
1182
+ }).catch((err) => {
1183
+ if (cancelled) return;
1184
+ console.error("Failed to load token usage:", err);
1185
+ setError(err instanceof Error ? err.message : String(err));
1186
+ }).finally(() => {
1187
+ if (cancelled) return;
1188
+ setIsLoading(false);
1189
+ });
1190
+ return () => {
1191
+ cancelled = true;
1192
+ };
1193
+ }, [from, to, targetLabel, topN]);
1194
+ const setFilters = useCallback2((next) => {
1195
+ setFilterState((prev) => ({ ...prev, ...next }));
1196
+ }, []);
1197
+ const breadcrumb = /* @__PURE__ */ __name(() => [
1198
+ { name: t("token_usage.report.title"), href: generateUrl({ page: pageUrl }) }
1199
+ ], "breadcrumb");
1200
+ const title = /* @__PURE__ */ __name(() => ({
1201
+ type: t("token_usage.report.title"),
1202
+ functions: /* @__PURE__ */ jsx9(TokenUsageReportFilterBar, { onChange: setFilters }, "tokenUsageReportFilterBar")
1203
+ }), "title");
1204
+ const contextValue = useMemo6(
1205
+ () => ({ summary, timeline, byOperation, byTarget, targetPanelTitleKey, filters, setFilters, isLoading, error }),
1206
+ [summary, timeline, byOperation, byTarget, targetPanelTitleKey, filters, setFilters, isLoading, error]
1207
+ );
1208
+ return /* @__PURE__ */ jsx9(SharedProvider, { value: { breadcrumbs: breadcrumb(), title: title() }, children: /* @__PURE__ */ jsx9(TokenUsageReportContext.Provider, { value: contextValue, children }) });
1209
+ }, "TokenUsageReportProvider");
1210
+ var useTokenUsageReport = /* @__PURE__ */ __name(() => {
1211
+ const ctx = useContext2(TokenUsageReportContext);
1212
+ if (!ctx) {
1213
+ throw new Error("useTokenUsageReport() called outside <TokenUsageReportProvider>.");
1214
+ }
1215
+ return ctx;
1216
+ }, "useTokenUsageReport");
1217
+
1218
+ // src/features/tokenusage/components/TokenUsageReportContainer.tsx
1219
+ import { useTranslations as useTranslations10 } from "next-intl";
1220
+
1221
+ // src/features/tokenusage/components/TokenUsageReportTiles.tsx
1222
+ import { ArrowDownIcon as ArrowDownIcon3, ArrowUpIcon as ArrowUpIcon3 } from "lucide-react";
1223
+ import { useTranslations as useTranslations9 } from "next-intl";
1224
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
1225
+ var ZERO2 = { cost: 0, credits: 0, tokensIn: 0, tokensOut: 0, cached: 0, calls: 0 };
1226
+ function TokenUsageReportTiles({ summary, metric, balances }) {
1227
+ const t = useTranslations9();
1228
+ const { decimal, metricValue: formatValue } = useUsageFormatters();
1229
+ const rowFor = /* @__PURE__ */ __name((window) => summary.find((row) => row.window === window) ?? ZERO2, "rowFor");
1230
+ const current = rowFor("current");
1231
+ const previous = rowFor("previous");
1232
+ const monthlyPercentage = balances && balances.monthlyCredits > 0 ? balances.availableMonthlyCredits / balances.monthlyCredits * 100 : 0;
1233
+ const monthlyColor = monthlyPercentage > 75 ? "text-success" : monthlyPercentage >= 5 ? "text-warning" : "text-destructive";
1234
+ const whole = /* @__PURE__ */ __name((value) => decimal(Math.max(0, Math.floor(value)), 0), "whole");
1235
+ return /* @__PURE__ */ jsxs7("div", { className: "grid gap-3", children: [
1236
+ /* @__PURE__ */ jsx10(Card, { "data-testid": "tile-used", children: /* @__PURE__ */ jsxs7(CardContent, { className: "grid gap-1", children: [
1237
+ /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.used_in_period") }),
1238
+ /* @__PURE__ */ jsx10("span", { className: "text-primary text-xl font-semibold tabular-nums", children: formatValue(metricValue(current, metric), metric) }),
1239
+ /* @__PURE__ */ jsxs7("span", { className: "flex items-center gap-1", children: [
1240
+ /* @__PURE__ */ jsx10(
1241
+ Delta2,
1242
+ {
1243
+ testId: "tile-used-delta",
1244
+ delta: percentageDelta(metricValue(current, metric), metricValue(previous, metric)),
1245
+ decimal
1246
+ }
1247
+ ),
1248
+ /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.vs_previous") })
1249
+ ] })
1250
+ ] }) }),
1251
+ /* @__PURE__ */ jsxs7("div", { className: "grid gap-3 sm:grid-cols-3", children: [
1252
+ balances && /* @__PURE__ */ jsx10(Card, { "data-testid": "tile-monthly", size: "sm", children: /* @__PURE__ */ jsxs7(CardContent, { className: "grid gap-1", children: [
1253
+ /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.monthly_left") }),
1254
+ /* @__PURE__ */ jsxs7("span", { className: "flex items-baseline gap-1", children: [
1255
+ /* @__PURE__ */ jsx10("span", { "data-testid": "tile-monthly-value", className: cn("text-sm font-medium tabular-nums", monthlyColor), children: whole(balances.availableMonthlyCredits) }),
1256
+ /* @__PURE__ */ jsxs7("span", { className: "text-muted-foreground text-xs tabular-nums", children: [
1257
+ "/ ",
1258
+ whole(balances.monthlyCredits)
1259
+ ] })
1260
+ ] })
1261
+ ] }) }),
1262
+ balances && /* @__PURE__ */ jsx10(Card, { "data-testid": "tile-extra", size: "sm", children: /* @__PURE__ */ jsxs7(CardContent, { className: "grid gap-1", children: [
1263
+ /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.extra_credits") }),
1264
+ /* @__PURE__ */ jsx10("span", { className: "text-sm font-medium tabular-nums", children: whole(balances.availableExtraCredits) })
1265
+ ] }) }),
1266
+ /* @__PURE__ */ jsx10(Card, { "data-testid": "tile-calls", size: "sm", children: /* @__PURE__ */ jsxs7(CardContent, { className: "grid gap-1", children: [
1267
+ /* @__PURE__ */ jsx10("span", { className: "text-muted-foreground text-xs", children: t("token_usage.report.calls") }),
1268
+ /* @__PURE__ */ jsx10("span", { className: "text-sm font-medium tabular-nums", children: decimal(current.calls, 0) })
1269
+ ] }) })
1270
+ ] })
1271
+ ] });
1272
+ }
1273
+ __name(TokenUsageReportTiles, "TokenUsageReportTiles");
1274
+ function Delta2({
1275
+ testId,
1276
+ delta,
1277
+ decimal
1278
+ }) {
1279
+ if (delta === void 0) {
1280
+ return /* @__PURE__ */ jsx10("span", { "data-testid": testId, className: "text-muted-foreground text-xs", children: "\u2014" });
1281
+ }
1282
+ const increased = delta >= 0;
1283
+ const Icon = increased ? ArrowUpIcon3 : ArrowDownIcon3;
1284
+ return /* @__PURE__ */ jsxs7(
1285
+ "span",
1286
+ {
1287
+ "data-testid": testId,
1288
+ className: cn(
1289
+ "inline-flex items-center gap-0.5 text-xs tabular-nums",
1290
+ increased ? "text-warning" : "text-success"
1291
+ ),
1292
+ children: [
1293
+ /* @__PURE__ */ jsx10(Icon, { "aria-hidden": true, className: "size-3" }),
1294
+ decimal(Math.abs(delta), 0),
1295
+ " %"
1296
+ ]
1297
+ }
1298
+ );
1299
+ }
1300
+ __name(Delta2, "Delta");
1301
+
1302
+ // src/features/tokenusage/components/TokenUsageReportContainer.tsx
1303
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1304
+ function TokenUsageReportContainer({ balances = null }) {
1305
+ const t = useTranslations10();
1306
+ const { summary, timeline, byOperation, byTarget, isLoading, error, targetPanelTitleKey } = useTokenUsageReport();
1307
+ if (error) {
1308
+ return /* @__PURE__ */ jsx11(RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ jsx11("div", { className: "p-4", children: /* @__PURE__ */ jsx11(Card, { children: /* @__PURE__ */ jsx11(CardContent, { children: /* @__PURE__ */ jsx11("p", { className: "text-destructive text-xs/relaxed", children: error }) }) }) }) });
1309
+ }
1310
+ if (isLoading) return /* @__PURE__ */ jsx11(RoundPageContainer, { fullWidth: true, forceHeader: true });
1311
+ const emptyLabel = t("token_usage.report.no_data");
1312
+ const targetTitle = targetPanelTitleKey && byTarget.length > 0 ? t(targetPanelTitleKey) : void 0;
1313
+ return /* @__PURE__ */ jsx11(RoundPageContainer, { fullWidth: true, forceHeader: true, children: /* @__PURE__ */ jsxs8("div", { className: "flex w-full flex-col gap-4 p-4", children: [
1314
+ /* @__PURE__ */ jsx11(TokenUsageReportTiles, { summary, metric: "credits", balances }),
1315
+ /* @__PURE__ */ jsxs8(Card, { children: [
1316
+ /* @__PURE__ */ jsx11(CardHeader, { children: /* @__PURE__ */ jsx11(CardTitle, { children: t("token_usage.report.usage_over_time") }) }),
1317
+ /* @__PURE__ */ jsx11(CardContent, { children: /* @__PURE__ */ jsx11(TokenUsageTimelineChart, { rows: timeline, metric: "credits", stackBy: "type" }) })
1318
+ ] }),
1319
+ /* @__PURE__ */ jsxs8("div", { className: cn("grid gap-4", targetTitle && "md:grid-cols-2"), children: [
1320
+ /* @__PURE__ */ jsxs8(Card, { children: [
1321
+ /* @__PURE__ */ jsx11(CardHeader, { children: /* @__PURE__ */ jsx11(CardTitle, { children: t("token_usage.report.by_operation") }) }),
1322
+ /* @__PURE__ */ jsx11(CardContent, { children: /* @__PURE__ */ jsx11(
1323
+ TokenUsageRankedBar,
1324
+ {
1325
+ rows: byOperation,
1326
+ metric: "credits",
1327
+ emptyLabel,
1328
+ labelsAreOperationTypes: true
1329
+ }
1330
+ ) })
1331
+ ] }),
1332
+ targetTitle && /* @__PURE__ */ jsxs8(Card, { children: [
1333
+ /* @__PURE__ */ jsx11(CardHeader, { children: /* @__PURE__ */ jsx11(CardTitle, { children: targetTitle }) }),
1334
+ /* @__PURE__ */ jsx11(CardContent, { children: /* @__PURE__ */ jsx11(TokenUsageRankedBar, { rows: byTarget, metric: "credits", emptyLabel }) })
1335
+ ] })
1336
+ ] })
1337
+ ] }) });
1338
+ }
1339
+ __name(TokenUsageReportContainer, "TokenUsageReportContainer");
994
1340
  export {
995
1341
  TOKEN_USAGE_ADMIN_I18N_KEYS,
1342
+ TOKEN_USAGE_REPORT_I18N_KEYS,
996
1343
  TokenUsageAdminBreakdown,
997
1344
  TokenUsageAdminBreakdownModule,
998
1345
  TokenUsageAdminContainer,
@@ -1006,7 +1353,21 @@ export {
1006
1353
  TokenUsageAdminTimelineModule,
1007
1354
  TokenUsageBreakdownTable,
1008
1355
  TokenUsageRankedBar,
1356
+ TokenUsageReportBreakdown,
1357
+ TokenUsageReportContainer,
1358
+ TokenUsageReportFilterBar,
1359
+ TokenUsageReportProvider,
1360
+ TokenUsageReportService,
1361
+ TokenUsageReportSummary,
1362
+ TokenUsageReportTiles,
1363
+ TokenUsageReportTimeline,
1009
1364
  TokenUsageTimelineChart,
1010
- useTokenUsageAdmin
1365
+ configureTokenUsage,
1366
+ createUsageFormatters,
1367
+ getTokenUsageCurrency,
1368
+ tokenUsageModules,
1369
+ useTokenUsageAdmin,
1370
+ useTokenUsageReport,
1371
+ useUsageFormatters
1011
1372
  };
1012
1373
  //# sourceMappingURL=index.mjs.map