@fayz-ai/ui 0.22.0 → 0.23.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 (71) hide show
  1. package/dist/analytics/AnalyticsCard.d.ts.map +1 -1
  2. package/dist/analytics/AnalyticsControls.d.ts.map +1 -1
  3. package/dist/analytics/DateRangeControl.d.ts +14 -0
  4. package/dist/analytics/DateRangeControl.d.ts.map +1 -0
  5. package/dist/analytics/StatementTable.d.ts +22 -0
  6. package/dist/analytics/StatementTable.d.ts.map +1 -0
  7. package/dist/analytics/context.d.ts +8 -0
  8. package/dist/analytics/context.d.ts.map +1 -1
  9. package/dist/analytics/derive.d.ts +13 -0
  10. package/dist/analytics/derive.d.ts.map +1 -1
  11. package/dist/analytics/index.d.ts +7 -1
  12. package/dist/analytics/index.d.ts.map +1 -1
  13. package/dist/analytics/statement.d.ts +58 -0
  14. package/dist/analytics/statement.d.ts.map +1 -0
  15. package/dist/{chunk-E44WOPCR.js → chunk-HO2P2XB4.js} +428 -58
  16. package/dist/chunk-HO2P2XB4.js.map +1 -0
  17. package/dist/{chunk-7G3O54CC.js → chunk-L4TXDKMN.js} +5 -5
  18. package/dist/chunk-L4TXDKMN.js.map +1 -0
  19. package/dist/{chunk-DHNUZF2X.js → chunk-OWN3GFDG.js} +4 -3
  20. package/dist/chunk-OWN3GFDG.js.map +1 -0
  21. package/dist/{chunk-PQUS22JE.js → chunk-RRNMNFLF.js} +483 -794
  22. package/dist/chunk-RRNMNFLF.js.map +1 -0
  23. package/dist/chunk-UP7WVKSU.js +631 -0
  24. package/dist/chunk-UP7WVKSU.js.map +1 -0
  25. package/dist/{chunk-ZSOT7KTE.js → chunk-XEUIYSW2.js} +321 -361
  26. package/dist/chunk-XEUIYSW2.js.map +1 -0
  27. package/dist/{chunk-SUSW4LSW.js → chunk-XKFYX3DG.js} +5 -5
  28. package/dist/{chunk-SUSW4LSW.js.map → chunk-XKFYX3DG.js.map} +1 -1
  29. package/dist/dashboard/index.js +4 -4
  30. package/dist/icons.js +1 -1
  31. package/dist/index.d.ts +2 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +672 -87
  34. package/dist/index.js.map +1 -1
  35. package/dist/layout/AppShell.d.ts +7 -1
  36. package/dist/layout/AppShell.d.ts.map +1 -1
  37. package/dist/layout/ShellSearch.d.ts +53 -0
  38. package/dist/layout/ShellSearch.d.ts.map +1 -0
  39. package/dist/layout/Sidebar.d.ts +10 -6
  40. package/dist/layout/Sidebar.d.ts.map +1 -1
  41. package/dist/layout/Takeover.d.ts.map +1 -1
  42. package/dist/layout/index.d.ts +1 -0
  43. package/dist/layout/index.d.ts.map +1 -1
  44. package/dist/layout/index.js +4 -4
  45. package/dist/primitives/data-table.d.ts.map +1 -1
  46. package/dist/primitives/date-range-chip.d.ts +32 -0
  47. package/dist/primitives/date-range-chip.d.ts.map +1 -0
  48. package/dist/primitives/header-tabs.d.ts.map +1 -1
  49. package/dist/primitives/index.d.ts +2 -1
  50. package/dist/primitives/index.d.ts.map +1 -1
  51. package/dist/primitives/index.js +5 -5
  52. package/dist/primitives/list-view.d.ts +43 -1
  53. package/dist/primitives/list-view.d.ts.map +1 -1
  54. package/dist/primitives/modal.d.ts +2 -0
  55. package/dist/primitives/modal.d.ts.map +1 -1
  56. package/dist/primitives/page-actions.d.ts +14 -0
  57. package/dist/primitives/page-actions.d.ts.map +1 -1
  58. package/dist/primitives/page-toolbar.d.ts +16 -1
  59. package/dist/primitives/page-toolbar.d.ts.map +1 -1
  60. package/dist/primitives/segmented-control.d.ts.map +1 -1
  61. package/dist/stores/layout.store.d.ts +20 -1
  62. package/dist/stores/layout.store.d.ts.map +1 -1
  63. package/dist/styles.css +58 -0
  64. package/package.json +2 -2
  65. package/dist/chunk-7G3O54CC.js.map +0 -1
  66. package/dist/chunk-DHNUZF2X.js.map +0 -1
  67. package/dist/chunk-E44WOPCR.js.map +0 -1
  68. package/dist/chunk-HKGTF2YA.js +0 -117
  69. package/dist/chunk-HKGTF2YA.js.map +0 -1
  70. package/dist/chunk-PQUS22JE.js.map +0 -1
  71. package/dist/chunk-ZSOT7KTE.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,23 +1,24 @@
1
- import { Popover, PopoverTrigger, PopoverContent, Input, Hint, DatePicker } from './chunk-ZSOT7KTE.js';
2
- export { Badge, Breadcrumb, BulkActionBar, ChevronPager, CurrencyInput, DatePicker, DotBadge, DraftCommitBar, FilterCheckBox, FilterCheckList, FilterCheckRow, FilterChip, FilterSection, Hint, Input, ListView, Markdown, MarkdownEditor, MiniCalendar, Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, MultiSelect, PageActionBar, PhoneField, PhoneLink, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, RefreshButton, RowActions, SearchCombobox, SearchField, SearchSelect, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, StatTiles, StockEntryForm, Tabs, TabsContent, TabsList, TabsTrigger, Thumbnail, TimePicker, ToastProvider, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, WHATSAPP_BRAND_COLOR, WhatsAppIcon, badgeVariants, formatPhone, normalizePhone, renderMarkdown, setRowActionsLabel, toLocalISODate, toast, useRowSelection } from './chunk-ZSOT7KTE.js';
3
- export { AppShellSkeleton, ContentSplit, ContentSplitTrigger, PageTrail, Takeover, navigateToCrumb, usePageTrail, usePageTrailParent, useTakeoverClose } from './chunk-7G3O54CC.js';
4
- export { AppShell, BackStyleProvider, HeaderTabs, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeader, PageHeaderActions, PageHeaderCrumb, PageRail, PageTabs, PageTransition, RailNavItem, SaveBar, SaveBarProvider, SegmentedControl, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, Sidebar, SubpageHeader, Topbar, bottomNavReachesAll, useBackHandler, useBackStyle, useModuleHeaderActionsSlot, useModuleHeaderSlot, useModuleLayout, useNavTransition, useRailRow, useSaveBar } from './chunk-PQUS22JE.js';
1
+ import { Input, DatePicker } from './chunk-XEUIYSW2.js';
2
+ export { Badge, Breadcrumb, BulkActionBar, ChevronPager, CurrencyInput, DatePicker, DateRangeChip, DotBadge, DraftCommitBar, FilterCheckBox, FilterCheckList, FilterCheckRow, FilterChip, FilterSection, Input, ListView, Markdown, MarkdownEditor, MiniCalendar, Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, MultiSelect, PhoneField, PhoneLink, RefreshButton, RowActions, SearchCombobox, SearchField, SearchSelect, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, StatTiles, StockEntryForm, Tabs, TabsContent, TabsList, TabsTrigger, Thumbnail, TimePicker, ToastProvider, WHATSAPP_BRAND_COLOR, WhatsAppIcon, badgeVariants, formatPhone, normalizePhone, renderMarkdown, setRowActionsLabel, toLocalISODate, toast, useRowSelection } from './chunk-XEUIYSW2.js';
3
+ export { AppShellSkeleton, ContentSplit, ContentSplitTrigger, PageTrail, Takeover, navigateToCrumb, usePageTrail, usePageTrailParent, useTakeoverClose } from './chunk-L4TXDKMN.js';
4
+ export { BackStyleProvider, HeaderTabs, ModuleLayoutProvider, ModulePage, NavTransitionProvider, PageHeaderActions, PageRail, PageTabs, PageTransition, RailNavItem, SegmentedControl, Sheet, SheetBody, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger, SubpageHeader, useBackStyle, useModuleLayout, useNavTransition } from './chunk-UP7WVKSU.js';
5
5
  export { ThemeProvider, createFayzTheme, createTheme, darkTheme, fayzThemePresets, lightTheme, useTheme, useThemeStore } from './chunk-VDAYIQX4.js';
6
- import { ChartWidget, Sparkline, DashboardGrid } from './chunk-SUSW4LSW.js';
7
- export { ChartWidget, DEFAULT_RANGE_OPTIONS, DashboardCanvas, DashboardCustomizeMenu, DashboardGrid, DashboardNavigateProvider, DashboardRangeControl, DashboardRangeProvider, GoldCard, KpiCard, OnboardingWidget, SidebarOnboarding, TableWidget, WidgetGrid, defineChartWidget, defineCustomWidget, defineKpiWidget, defineOnboardingWidget, defineTableWidget, formatKpiValue, renderIcon, resolveDashboardLayout, useDashboardNavigate, useDashboardPreferences, useDashboardRange, useOnboardingProgress } from './chunk-SUSW4LSW.js';
8
- import { Skeleton, ToolbarButton, PageToolbar, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, Card, CardContent, RangeControl, DropdownSeparator } from './chunk-E44WOPCR.js';
9
- export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DataTable, Dropdown, DropdownCheckboxItem, DropdownContent, DropdownGroup, DropdownItem, DropdownLabel, DropdownPortal, DropdownRadioGroup, DropdownRadioItem, DropdownSeparator, DropdownShortcut, DropdownSub, DropdownSubContent, DropdownSubTrigger, DropdownTrigger, PageToolbar, RangeControl, Skeleton, TableSkeleton, ToolbarAction, ToolbarButton, WIDE_QUERY, useIsWide } from './chunk-E44WOPCR.js';
10
- export { PageHeaderOutlet, PageTitlePortal, useHasPageActions, useHasPageHeaderBack, useHasPageHeaderHost, useHasPageHeaderOutlet, useLayoutStore, usePageHeader, usePageHeaderClaimed, usePageHeaderHost, useRegisterPageActions } from './chunk-HKGTF2YA.js';
6
+ import { ChartWidget, Sparkline, DashboardGrid } from './chunk-XKFYX3DG.js';
7
+ export { ChartWidget, DEFAULT_RANGE_OPTIONS, DashboardCanvas, DashboardCustomizeMenu, DashboardGrid, DashboardNavigateProvider, DashboardRangeControl, DashboardRangeProvider, GoldCard, KpiCard, OnboardingWidget, SidebarOnboarding, TableWidget, WidgetGrid, defineChartWidget, defineCustomWidget, defineKpiWidget, defineOnboardingWidget, defineTableWidget, formatKpiValue, renderIcon, resolveDashboardLayout, useDashboardNavigate, useDashboardPreferences, useDashboardRange, useOnboardingProgress } from './chunk-XKFYX3DG.js';
8
+ import { Skeleton, useToolbarSurface, useToolbarPanelClose, Popover, PopoverTrigger, ToolbarButton, PopoverContent, PageToolbar, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, RangeControl, DropdownSeparator, Card, Hint, CardContent, Checkbox } from './chunk-HO2P2XB4.js';
9
+ export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, Checkbox, DataTable, Dropdown, DropdownCheckboxItem, DropdownContent, DropdownGroup, DropdownItem, DropdownLabel, DropdownPortal, DropdownRadioGroup, DropdownRadioItem, DropdownSeparator, DropdownShortcut, DropdownSub, DropdownSubContent, DropdownSubTrigger, DropdownTrigger, Hint, PageActionBar, PageToolbar, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, RangeControl, Skeleton, TableSkeleton, ToolbarAction, ToolbarButton, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, WIDE_QUERY, useIsWide, useToolbarPanelClose, useToolbarSurface } from './chunk-HO2P2XB4.js';
10
+ export { AppShell, PageHeader, PageHeaderCrumb, PageHeaderOutlet, PageTitlePortal, SaveBar, SaveBarProvider, ShellSearch, Sidebar, Topbar, bottomNavReachesAll, useBackHandler, useHasPageActions, useHasPageHeaderBack, useHasPageHeaderHost, useHasPageHeaderOutlet, useLayoutStore, useModuleHeaderActionsSlot, useModuleHeaderSlot, usePageHeader, usePageHeaderClaimed, usePageHeaderHost, useRailRow, useRegisterPageActions, useSaveBar } from './chunk-RRNMNFLF.js';
11
11
  import { errorMessage } from './chunk-OB2ULDBD.js';
12
12
  export { Wizard, WizardField, errorMessage, useWizard } from './chunk-OB2ULDBD.js';
13
13
  export { ConfirmDialog, ImageDrop, OptionCards, OptionChips, StepProgress, Switch } from './chunk-GLRLWBBF.js';
14
14
  import { cn, Button } from './chunk-OKZ6WQB3.js';
15
15
  export { Button, buttonVariants, cn } from './chunk-OKZ6WQB3.js';
16
- export { BRAND_MARKS, ChefControlMark, ICON_MAP, getLucideIcon, registerIcons } from './chunk-DHNUZF2X.js';
16
+ export { BRAND_MARKS, ChefControlMark, ICON_MAP, getLucideIcon, registerIcons } from './chunk-OWN3GFDG.js';
17
17
  import * as React2 from 'react';
18
+ import React2__default from 'react';
18
19
  import * as AvatarPrimitive from '@radix-ui/react-avatar';
19
20
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
20
- import { Construction, DatabaseZap, AlertCircle, SlidersHorizontal, Search, ChevronRight, GitCompareArrows, Check, X, TrendingUp, TrendingDown, Info, ArrowUpRight } from 'lucide-react';
21
+ import { Construction, DatabaseZap, AlertCircle, Search, ChevronRight, SlidersHorizontal, GitCompareArrows, Check, X, TrendingUp, TrendingDown, Info, ArrowUpRight } from 'lucide-react';
21
22
  import { useTranslation, getCurrentLocale } from '@fayz-ai/core';
22
23
  import { createPortal } from 'react-dom';
23
24
  import { create } from 'zustand';
@@ -223,17 +224,18 @@ function formatBucket(value, grain, locale = "pt-BR") {
223
224
  if (value == null) return "\u2014";
224
225
  const d = new Date(String(value));
225
226
  if (Number.isNaN(d.getTime())) return String(value);
227
+ const utc = { timeZone: "UTC" };
226
228
  switch (grain) {
227
229
  case "hour":
228
230
  return d.toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" });
229
231
  case "month":
230
- return d.toLocaleDateString(locale, { month: "short", year: "2-digit" });
232
+ return d.toLocaleDateString(locale, { ...utc, month: "short", year: "2-digit" });
231
233
  case "quarter":
232
- return `Q${Math.floor(d.getMonth() / 3) + 1} ${d.getFullYear()}`;
234
+ return `Q${Math.floor(d.getUTCMonth() / 3) + 1} ${d.getUTCFullYear()}`;
233
235
  case "year":
234
- return String(d.getFullYear());
236
+ return String(d.getUTCFullYear());
235
237
  default:
236
- return d.toLocaleDateString(locale, { day: "2-digit", month: "short" });
238
+ return d.toLocaleDateString(locale, { ...utc, day: "2-digit", month: "short" });
237
239
  }
238
240
  }
239
241
  function toBreakdown(rows, categoryKey, measureKey, format, currency, limit) {
@@ -536,8 +538,454 @@ function useAnalyticsCardRows(card) {
536
538
  return rows.slice(0, limit);
537
539
  }, [provider, state, card]);
538
540
  }
541
+ var STATEMENT_ROW_CAP = 5e3;
542
+ function useAnalyticsAllRows(card, enabled = true) {
543
+ const fetchRows = useAnalyticsCardRows(card);
544
+ const { state } = useAnalyticsContext();
545
+ const [rows, setRows] = React2.useState([]);
546
+ const [loading, setLoading] = React2.useState(enabled);
547
+ const [error, setError] = React2.useState(null);
548
+ const key = JSON.stringify([
549
+ card.id,
550
+ state.dateRange,
551
+ state.filters,
552
+ state.search,
553
+ state.sortBy,
554
+ state.sortDir
555
+ ]);
556
+ React2.useEffect(() => {
557
+ if (!enabled) {
558
+ setLoading(false);
559
+ return;
560
+ }
561
+ let live = true;
562
+ setLoading(true);
563
+ setError(null);
564
+ fetchRows(STATEMENT_ROW_CAP).then((r) => {
565
+ if (live) {
566
+ setRows(r);
567
+ setLoading(false);
568
+ }
569
+ }).catch((e) => {
570
+ if (live) {
571
+ setError(errorMessage(e));
572
+ setLoading(false);
573
+ }
574
+ });
575
+ return () => {
576
+ live = false;
577
+ };
578
+ }, [key, enabled]);
579
+ return { rows, loading, error };
580
+ }
539
581
  var EXPORT_PAGE_SIZE = 1e3;
540
582
  var EXPORT_ROW_CAP = 2e4;
583
+
584
+ // src/analytics/statement.ts
585
+ function compareCodes(a, b) {
586
+ const pa = a.split(".");
587
+ const pb = b.split(".");
588
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
589
+ const sa = pa[i];
590
+ const sb = pb[i];
591
+ if (sa === void 0) return -1;
592
+ if (sb === void 0) return 1;
593
+ const na = Number(sa);
594
+ const nb = Number(sb);
595
+ if (Number.isFinite(na) && Number.isFinite(nb)) {
596
+ if (na !== nb) return na - nb;
597
+ } else if (sa !== sb) {
598
+ return sa < sb ? -1 : 1;
599
+ }
600
+ }
601
+ return 0;
602
+ }
603
+ function parentCode(code) {
604
+ const i = code.lastIndexOf(".");
605
+ return i < 0 ? "" : code.slice(0, i);
606
+ }
607
+ var UNCLASSIFIED = "\0sem-classificacao";
608
+ function emptyNode(code, label, synthetic) {
609
+ return {
610
+ key: code || label,
611
+ code,
612
+ label,
613
+ depth: code ? code.split(".").length - 1 : 0,
614
+ synthetic,
615
+ own: {},
616
+ rollup: {},
617
+ total: 0,
618
+ children: []
619
+ };
620
+ }
621
+ function add(bag, period, value) {
622
+ bag[period] = (bag[period] ?? 0) + value;
623
+ }
624
+ function rollup(node, periods) {
625
+ node.children.sort((a, b) => compareCodes(a.code, b.code));
626
+ node.rollup = { ...node.own };
627
+ for (const child of node.children) {
628
+ rollup(child, periods);
629
+ for (const p of periods) {
630
+ if (child.rollup[p]) add(node.rollup, p, child.rollup[p]);
631
+ }
632
+ }
633
+ node.total = periods.reduce((acc, p) => acc + (node.rollup[p] ?? 0), 0);
634
+ }
635
+ function buildStatement(rows, def) {
636
+ const periodSet = /* @__PURE__ */ new Set();
637
+ const bySection = /* @__PURE__ */ new Map();
638
+ for (const s of def.sections) bySection.set(s.value, /* @__PURE__ */ new Map());
639
+ for (const row of rows) {
640
+ const section = String(row[def.sectionKey] ?? "");
641
+ const bucket = bySection.get(section);
642
+ if (!bucket) continue;
643
+ const period = String(row[def.periodKey] ?? "");
644
+ const estrutura = !period;
645
+ if (!estrutura) periodSet.add(period);
646
+ const value = Number(row[def.valueKey] ?? 0);
647
+ if (!Number.isFinite(value)) continue;
648
+ const code = String(row[def.codeKey] ?? "").trim();
649
+ const label = String(row[def.labelKey] ?? "").trim();
650
+ if (estrutura) {
651
+ if (!code) continue;
652
+ let node2 = bucket.get(code);
653
+ if (!node2) {
654
+ node2 = emptyNode(code, label || code, false);
655
+ bucket.set(code, node2);
656
+ } else {
657
+ node2.synthetic = false;
658
+ if (label) node2.label = label;
659
+ }
660
+ continue;
661
+ }
662
+ if (!code) {
663
+ const spec = def.sections.find((sec) => sec.value === section);
664
+ if (!spec?.unclassifiedLabel) continue;
665
+ let node2 = bucket.get(UNCLASSIFIED);
666
+ if (!node2) {
667
+ node2 = emptyNode("", spec.unclassifiedLabel, false);
668
+ node2.key = `${section}${UNCLASSIFIED}`;
669
+ bucket.set(UNCLASSIFIED, node2);
670
+ }
671
+ add(node2.own, period, value);
672
+ continue;
673
+ }
674
+ let node = bucket.get(code);
675
+ if (!node) {
676
+ node = emptyNode(code, label || code, false);
677
+ bucket.set(code, node);
678
+ } else if (node.synthetic) {
679
+ node.synthetic = false;
680
+ if (label) node.label = label;
681
+ }
682
+ add(node.own, period, value);
683
+ }
684
+ const periods = [...periodSet].sort();
685
+ const lines = [];
686
+ const sectionTotals = /* @__PURE__ */ new Map();
687
+ for (const spec of def.sections) {
688
+ const bucket = bySection.get(spec.value);
689
+ for (const code of [...bucket.keys()]) {
690
+ if (code === UNCLASSIFIED) continue;
691
+ let parent = parentCode(code);
692
+ while (parent && !bucket.has(parent)) {
693
+ bucket.set(parent, emptyNode(parent, parent, true));
694
+ parent = parentCode(parent);
695
+ }
696
+ }
697
+ const roots = [];
698
+ for (const [code, node] of bucket) {
699
+ if (code === UNCLASSIFIED) continue;
700
+ const parent = parentCode(code);
701
+ if (parent && bucket.has(parent)) bucket.get(parent).children.push(node);
702
+ else roots.push(node);
703
+ }
704
+ const totals = {};
705
+ for (const node of roots) {
706
+ rollup(node, periods);
707
+ for (const p of periods) if (node.rollup[p]) add(totals, p, node.rollup[p]);
708
+ }
709
+ const orphan = bucket.get(UNCLASSIFIED);
710
+ if (orphan) {
711
+ rollup(orphan, periods);
712
+ for (const p of periods) if (orphan.rollup[p]) add(totals, p, orphan.rollup[p]);
713
+ }
714
+ roots.sort((a, b) => compareCodes(a.code, b.code));
715
+ const nodes = orphan ? [...roots, orphan] : roots;
716
+ const total = periods.reduce((acc, p) => acc + (totals[p] ?? 0), 0);
717
+ sectionTotals.set(spec.value, { totals, total });
718
+ lines.push({ kind: "section", section: spec.value, label: spec.label, nodes, totals, total });
719
+ }
720
+ const out = [];
721
+ for (const line of def.lines) {
722
+ if (line.kind === "section") {
723
+ const block = lines.find((l) => l.kind === "section" && l.section === line.section);
724
+ if (block) out.push(block);
725
+ continue;
726
+ }
727
+ const totals = {};
728
+ for (const [section, sign] of Object.entries(line.of)) {
729
+ const s = sectionTotals.get(section);
730
+ if (!s) continue;
731
+ for (const p of periods) if (s.totals[p]) add(totals, p, s.totals[p] * sign);
732
+ }
733
+ out.push({
734
+ kind: "total",
735
+ label: line.label,
736
+ emphasis: line.emphasis,
737
+ totals,
738
+ total: periods.reduce((acc, p) => acc + (totals[p] ?? 0), 0)
739
+ });
740
+ }
741
+ return { periods, lines: out };
742
+ }
743
+ function flattenNodes(nodes, open, out = []) {
744
+ for (const node of nodes) {
745
+ out.push(node);
746
+ if (node.children.length && open(node)) flattenNodes(node.children, open, out);
747
+ }
748
+ return out;
749
+ }
750
+ function defaultOpenKeys(lines, depth) {
751
+ const open = /* @__PURE__ */ new Set();
752
+ const walk = (nodes) => {
753
+ for (const n of nodes) {
754
+ if (n.depth < depth) open.add(n.key);
755
+ walk(n.children);
756
+ }
757
+ };
758
+ for (const line of lines) if (line.kind === "section") walk(line.nodes);
759
+ return open;
760
+ }
761
+ var EMPTY = "\u2014";
762
+ function cell(value, currency) {
763
+ if (value === void 0) return EMPTY;
764
+ return formatAnalyticsValue(value, "currency", currency);
765
+ }
766
+ function Row({
767
+ label,
768
+ code,
769
+ depth,
770
+ values,
771
+ total,
772
+ periods,
773
+ currency,
774
+ expandable,
775
+ open,
776
+ onToggle,
777
+ tone,
778
+ synthetic
779
+ }) {
780
+ return /* @__PURE__ */ jsxs(
781
+ "tr",
782
+ {
783
+ onClick: expandable ? onToggle : void 0,
784
+ className: cn(
785
+ "border-b border-border/50 last:border-b-0",
786
+ tone === "strong" && "bg-muted/60 font-semibold",
787
+ tone === "rule" && "border-t-2 border-foreground/70 bg-muted/40 font-semibold",
788
+ tone === "section" && "bg-muted/30 font-medium",
789
+ !tone && "hover:bg-muted/30",
790
+ expandable && "cursor-pointer select-none"
791
+ ),
792
+ children: [
793
+ /* @__PURE__ */ jsx(
794
+ "th",
795
+ {
796
+ scope: "row",
797
+ className: "sticky left-0 z-10 w-full bg-inherit px-3 py-2 text-left font-inherit",
798
+ style: { paddingLeft: `${12 + depth * 16}px` },
799
+ children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
800
+ expandable ? /* @__PURE__ */ jsxs(
801
+ "button",
802
+ {
803
+ type: "button",
804
+ tabIndex: -1,
805
+ onClick: (e) => {
806
+ e.stopPropagation();
807
+ onToggle?.();
808
+ },
809
+ "aria-expanded": open,
810
+ className: "-ml-1 rounded p-0.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
811
+ children: [
812
+ /* @__PURE__ */ jsx(
813
+ ChevronRight,
814
+ {
815
+ className: cn("h-3.5 w-3.5 transition-transform", open && "rotate-90"),
816
+ "aria-hidden": true
817
+ }
818
+ ),
819
+ /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
820
+ open ? "Recolher" : "Expandir",
821
+ " ",
822
+ label
823
+ ] })
824
+ ]
825
+ }
826
+ ) : /* @__PURE__ */ jsx("span", { className: "inline-block w-[18px]", "aria-hidden": true }),
827
+ code ? /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground", children: code }) : null,
828
+ label && label !== code ? /* @__PURE__ */ jsx("span", { className: cn("truncate", synthetic && "text-muted-foreground"), title: label, children: label }) : null
829
+ ] })
830
+ }
831
+ ),
832
+ periods.map((p) => /* @__PURE__ */ jsx("td", { className: "w-px whitespace-nowrap px-3 py-2 text-right tabular-nums", children: cell(values[p], currency) }, p)),
833
+ /* @__PURE__ */ jsx("td", { className: "w-px whitespace-nowrap border-l border-border px-3 py-2 text-right font-medium tabular-nums", children: cell(total || void 0, currency) })
834
+ ]
835
+ }
836
+ );
837
+ }
838
+ function StatementSkeleton({ rows, dense }) {
839
+ const cols = 4;
840
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-lg border", "aria-hidden": true, children: [
841
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 border-b bg-muted/50 px-3 py-2.5", children: [
842
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-24" }),
843
+ /* @__PURE__ */ jsx("div", { className: "ml-auto flex gap-3", children: Array.from({ length: cols }).map((_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-16" }, i)) })
844
+ ] }),
845
+ Array.from({ length: rows }).map((_, i) => {
846
+ const total = i === 0 || i === Math.floor(rows / 2);
847
+ return /* @__PURE__ */ jsxs(
848
+ "div",
849
+ {
850
+ className: cn(
851
+ "flex items-center gap-3 border-b border-border/50 px-3 last:border-b-0",
852
+ dense ? "py-1.5" : "py-2.5",
853
+ total && "bg-muted/40"
854
+ ),
855
+ children: [
856
+ /* @__PURE__ */ jsx(
857
+ Skeleton,
858
+ {
859
+ className: cn("h-3.5", total ? "w-48" : "w-36"),
860
+ style: { marginLeft: total ? 0 : `${i % 3 * 16}px` }
861
+ }
862
+ ),
863
+ /* @__PURE__ */ jsx("div", { className: "ml-auto flex gap-3", children: Array.from({ length: cols }).map((_2, c) => /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-16" }, c)) })
864
+ ]
865
+ },
866
+ i
867
+ );
868
+ })
869
+ ] });
870
+ }
871
+ function StatementTable({
872
+ rows,
873
+ def,
874
+ currency,
875
+ formatPeriod,
876
+ totalLabel = "Total",
877
+ accountLabel = "Conta",
878
+ emptyMessage = "Sem lan\xE7amentos no per\xEDodo.",
879
+ className,
880
+ dense,
881
+ loading,
882
+ skeletonRows
883
+ }) {
884
+ const statement = React2__default.useMemo(() => buildStatement(rows, def), [rows, def]);
885
+ const [open, setOpen] = React2__default.useState(() => /* @__PURE__ */ new Set());
886
+ const [touched, setTouched] = React2__default.useState(false);
887
+ React2__default.useEffect(() => {
888
+ if (touched) return;
889
+ setOpen(defaultOpenKeys(statement.lines, def.defaultDepth ?? 1));
890
+ }, [statement, def.defaultDepth, touched]);
891
+ const toggle = React2__default.useCallback((key) => {
892
+ setTouched(true);
893
+ setOpen((prev) => {
894
+ const next = new Set(prev);
895
+ if (next.has(key)) next.delete(key);
896
+ else next.add(key);
897
+ return next;
898
+ });
899
+ }, []);
900
+ const setAll = React2__default.useCallback((expand) => {
901
+ setTouched(true);
902
+ if (!expand) {
903
+ setOpen(/* @__PURE__ */ new Set());
904
+ return;
905
+ }
906
+ const all = /* @__PURE__ */ new Set();
907
+ const walk = (nodes) => {
908
+ for (const n of nodes) {
909
+ all.add(n.key);
910
+ walk(n.children);
911
+ }
912
+ };
913
+ for (const line of statement.lines) if (line.kind === "section") walk(line.nodes);
914
+ setOpen(all);
915
+ }, [statement]);
916
+ const { periods, lines } = statement;
917
+ if (loading) {
918
+ return /* @__PURE__ */ jsx(StatementSkeleton, { rows: skeletonRows ?? (dense ? 5 : 10), dense });
919
+ }
920
+ if (periods.length === 0) {
921
+ return /* @__PURE__ */ jsx("div", { className: "flex min-h-[120px] items-center justify-center rounded-lg border border-dashed text-sm text-muted-foreground", children: emptyMessage });
922
+ }
923
+ const isOpen = (n) => open.has(n.key);
924
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
925
+ !dense ? /* @__PURE__ */ jsxs("div", { className: "no-print flex gap-2", children: [
926
+ /* @__PURE__ */ jsx(
927
+ "button",
928
+ {
929
+ type: "button",
930
+ onClick: () => setAll(true),
931
+ className: "rounded-md border px-2.5 py-1 text-xs font-medium transition-colors hover:bg-muted",
932
+ children: "Expandir tudo"
933
+ }
934
+ ),
935
+ /* @__PURE__ */ jsx(
936
+ "button",
937
+ {
938
+ type: "button",
939
+ onClick: () => setAll(false),
940
+ className: "rounded-md border px-2.5 py-1 text-xs font-medium transition-colors hover:bg-muted",
941
+ children: "Recolher tudo"
942
+ }
943
+ )
944
+ ] }) : null,
945
+ /* @__PURE__ */ jsx("div", { className: "print-flow overflow-x-auto rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: cn("w-full border-collapse", dense ? "text-xs" : "text-sm"), children: [
946
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
947
+ /* @__PURE__ */ jsx("th", { scope: "col", className: "sticky left-0 z-10 w-full bg-muted/50 px-3 py-2 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground", children: accountLabel }),
948
+ periods.map((p) => /* @__PURE__ */ jsx("th", { scope: "col", className: "w-px whitespace-nowrap px-3 py-2 text-right text-xs font-medium uppercase tracking-wide text-muted-foreground", children: formatPeriod ? formatPeriod(p) : p }, p)),
949
+ /* @__PURE__ */ jsx("th", { scope: "col", className: "w-px whitespace-nowrap border-l border-border px-3 py-2 text-right text-xs font-medium uppercase tracking-wide text-muted-foreground", children: totalLabel })
950
+ ] }) }),
951
+ /* @__PURE__ */ jsx("tbody", { children: lines.map((line, i) => {
952
+ if (line.kind === "total") {
953
+ return /* @__PURE__ */ jsx(
954
+ Row,
955
+ {
956
+ label: line.label,
957
+ depth: 0,
958
+ values: line.totals,
959
+ total: line.total,
960
+ periods,
961
+ currency,
962
+ tone: line.emphasis ?? "strong"
963
+ },
964
+ `total-${i}-${line.label}`
965
+ );
966
+ }
967
+ const visible = flattenNodes(line.nodes, isOpen);
968
+ return /* @__PURE__ */ jsx(React2__default.Fragment, { children: visible.map((node) => /* @__PURE__ */ jsx(
969
+ Row,
970
+ {
971
+ label: node.label,
972
+ code: node.code || void 0,
973
+ depth: node.depth,
974
+ values: node.rollup,
975
+ total: node.total,
976
+ periods,
977
+ currency,
978
+ expandable: node.children.length > 0,
979
+ open: isOpen(node),
980
+ onToggle: () => toggle(node.key),
981
+ synthetic: node.synthetic
982
+ },
983
+ node.key
984
+ )) }, `section-${line.section}`);
985
+ }) })
986
+ ] }) })
987
+ ] });
988
+ }
541
989
  var CHART_TYPES = { line: "line", bar: "bar", area: "area", donut: "pie" };
542
990
  function CardChrome({
543
991
  card,
@@ -657,7 +1105,7 @@ function AnalyticsCard({ card, className, hideOpen }) {
657
1105
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Not connected yet" })
658
1106
  ] });
659
1107
  }
660
- if (loading && !result) {
1108
+ if (loading) {
661
1109
  return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
662
1110
  /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-24" }),
663
1111
  /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16" })
@@ -717,6 +1165,19 @@ function AnalyticsCard({ card, className, hideOpen }) {
717
1165
  }
718
1166
  case "table":
719
1167
  return /* @__PURE__ */ jsx(MiniTable, { rows, card, currency, limit: card.viz.limit ?? 5 });
1168
+ case "statement": {
1169
+ if (!card.viz.statement) return /* @__PURE__ */ jsx(EmptyState, {});
1170
+ return /* @__PURE__ */ jsx(
1171
+ StatementTable,
1172
+ {
1173
+ rows,
1174
+ def: { ...card.viz.statement, defaultDepth: 0 },
1175
+ currency,
1176
+ formatPeriod: (p) => formatBucket(p, "month"),
1177
+ dense: true
1178
+ }
1179
+ );
1180
+ }
720
1181
  case "funnel": {
721
1182
  const measure = measures[0];
722
1183
  if (!measure || !category) return /* @__PURE__ */ jsx(EmptyState, {});
@@ -876,22 +1337,25 @@ function FilterValues({
876
1337
  return /* @__PURE__ */ jsx("p", { className: "px-2 py-1.5 text-xs text-muted-foreground", children: t("analytics.filters.noOptions") });
877
1338
  }
878
1339
  return /* @__PURE__ */ jsx("div", { className: "max-h-56 space-y-0.5 overflow-y-auto", children: options.map((o) => /* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-muted", children: [
879
- /* @__PURE__ */ jsx(
880
- "input",
1340
+ multi ? /* @__PURE__ */ jsx(
1341
+ Checkbox,
881
1342
  {
882
- type: multi ? "checkbox" : "radio",
883
- name: filter.key,
884
1343
  checked: selected.has(o.value),
1344
+ "aria-label": o.label,
885
1345
  onChange: () => {
886
- if (!multi) {
887
- onChange(filter.key, selected.has(o.value) ? void 0 : o.value);
888
- return;
889
- }
890
1346
  const next = new Set(selected);
891
1347
  if (next.has(o.value)) next.delete(o.value);
892
1348
  else next.add(o.value);
893
1349
  onChange(filter.key, [...next]);
894
- },
1350
+ }
1351
+ }
1352
+ ) : /* @__PURE__ */ jsx(
1353
+ "input",
1354
+ {
1355
+ type: "radio",
1356
+ name: filter.key,
1357
+ checked: selected.has(o.value),
1358
+ onChange: () => onChange(filter.key, selected.has(o.value) ? void 0 : o.value),
895
1359
  className: "h-3.5 w-3.5"
896
1360
  }
897
1361
  ),
@@ -901,13 +1365,16 @@ function FilterValues({
901
1365
  function AnalyticsFilterPicker({ filters }) {
902
1366
  const t = useTranslation();
903
1367
  const { state, setFilters, clearFilters } = useAnalyticsContext();
1368
+ const surface = useToolbarSurface();
1369
+ const inline = surface === "panel";
1370
+ const closePanel = useToolbarPanelClose();
904
1371
  const [open, setOpen] = React2.useState(false);
905
1372
  const [search, setSearch] = React2.useState("");
906
1373
  const [openGroup, setOpenGroup] = React2.useState(null);
907
1374
  const [draft, setDraft] = React2.useState(state.filters);
908
1375
  React2.useEffect(() => {
909
- if (open) setDraft(state.filters);
910
- }, [open, state.filters]);
1376
+ if (open || inline) setDraft(state.filters);
1377
+ }, [open, inline, state.filters]);
911
1378
  const groups = React2.useMemo(() => {
912
1379
  const q = search.trim().toLowerCase();
913
1380
  const matching = q ? filters.filter((f) => f.label.toLowerCase().includes(q)) : filters;
@@ -920,39 +1387,36 @@ function AnalyticsFilterPicker({ filters }) {
920
1387
  }, [filters, search, t]);
921
1388
  const activeCount = Object.keys(state.filters).length;
922
1389
  if (filters.length === 0) return null;
923
- return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
924
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(ToolbarButton, { icon: SlidersHorizontal, caret: false, children: [
925
- t("analytics.filters"),
926
- activeCount > 0 ? /* @__PURE__ */ jsx("span", { className: "ml-0.5 rounded-full bg-primary px-1.5 text-[10px] font-semibold text-primary-foreground", children: activeCount }) : null
1390
+ const panel = /* @__PURE__ */ jsxs(Fragment, { children: [
1391
+ /* @__PURE__ */ jsx("div", { className: inline ? "pb-2" : "border-b p-2", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1392
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
1393
+ /* @__PURE__ */ jsx(
1394
+ Input,
1395
+ {
1396
+ value: search,
1397
+ onChange: (e) => setSearch(e.target.value),
1398
+ placeholder: t("analytics.filters.search"),
1399
+ className: "h-8 pl-8 text-sm"
1400
+ }
1401
+ )
927
1402
  ] }) }),
928
- /* @__PURE__ */ jsxs(PopoverContent, { align: "end", className: "w-80 p-0", children: [
929
- /* @__PURE__ */ jsx("div", { className: "border-b p-2", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
930
- /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
931
- /* @__PURE__ */ jsx(
932
- Input,
933
- {
934
- value: search,
935
- onChange: (e) => setSearch(e.target.value),
936
- placeholder: t("analytics.filters.search"),
937
- className: "h-8 pl-8 text-sm"
938
- }
939
- )
940
- ] }) }),
941
- /* @__PURE__ */ jsx("div", { className: "max-h-80 overflow-y-auto p-1", children: groups.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: t("analytics.filters.none") }) : groups.map(([group, groupFilters]) => /* @__PURE__ */ jsxs("div", { children: [
942
- /* @__PURE__ */ jsxs(
1403
+ /* @__PURE__ */ jsx("div", { className: inline ? "min-h-0 flex-1 overflow-y-auto" : "max-h-80 overflow-y-auto p-1", children: groups.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: t("analytics.filters.none") }) : groups.map(([group, groupFilters]) => {
1404
+ const expanded = inline || openGroup === group;
1405
+ return /* @__PURE__ */ jsxs("div", { children: [
1406
+ inline ? /* @__PURE__ */ jsx("p", { className: "px-1 pb-1 pt-3 text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: group }) : /* @__PURE__ */ jsxs(
943
1407
  "button",
944
1408
  {
945
1409
  type: "button",
946
1410
  onClick: () => setOpenGroup((g) => g === group ? null : group),
947
- "aria-expanded": openGroup === group,
1411
+ "aria-expanded": expanded,
948
1412
  className: "flex w-full items-center justify-between rounded-md px-2 py-2 text-sm hover:bg-muted",
949
1413
  children: [
950
1414
  /* @__PURE__ */ jsx("span", { className: "font-medium", children: group }),
951
- /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3.5 w-3.5 text-muted-foreground transition-transform", openGroup === group && "rotate-90") })
1415
+ /* @__PURE__ */ jsx(ChevronRight, { className: cn("h-3.5 w-3.5 text-muted-foreground transition-transform", expanded && "rotate-90") })
952
1416
  ]
953
1417
  }
954
1418
  ),
955
- openGroup === group ? /* @__PURE__ */ jsx("div", { className: "space-y-3 px-2 pb-3 pt-1", children: groupFilters.map((f) => /* @__PURE__ */ jsxs("div", { children: [
1419
+ expanded ? /* @__PURE__ */ jsx("div", { className: inline ? "space-y-4 px-1 pb-2" : "space-y-3 px-2 pb-3 pt-1", children: groupFilters.map((f) => /* @__PURE__ */ jsxs("div", { children: [
956
1420
  /* @__PURE__ */ jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: f.label }),
957
1421
  /* @__PURE__ */ jsx(
958
1422
  FilterValues,
@@ -968,35 +1432,79 @@ function AnalyticsFilterPicker({ filters }) {
968
1432
  }
969
1433
  )
970
1434
  ] }, f.key)) }) : null
971
- ] }, group)) }),
972
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-t p-2", children: [
973
- /* @__PURE__ */ jsx(
974
- Button,
975
- {
976
- variant: "ghost",
977
- size: "sm",
978
- onClick: () => {
979
- setDraft({});
980
- clearFilters();
981
- setOpen(false);
982
- },
983
- children: t("analytics.filters.clear")
984
- }
985
- ),
986
- /* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => {
987
- setFilters(draft);
988
- setOpen(false);
989
- }, children: t("analytics.filters.apply") })
990
- ] })
1435
+ ] }, group);
1436
+ }) }),
1437
+ /* @__PURE__ */ jsxs("div", { className: cn(
1438
+ "flex items-center justify-between gap-2 border-t",
1439
+ inline ? "mt-2 pt-3" : "p-2"
1440
+ ), children: [
1441
+ /* @__PURE__ */ jsx(
1442
+ Button,
1443
+ {
1444
+ variant: "ghost",
1445
+ size: "sm",
1446
+ onClick: () => {
1447
+ setDraft({});
1448
+ clearFilters();
1449
+ setOpen(false);
1450
+ closePanel();
1451
+ },
1452
+ children: t("analytics.filters.clear")
1453
+ }
1454
+ ),
1455
+ /* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => {
1456
+ setFilters(draft);
1457
+ setOpen(false);
1458
+ closePanel();
1459
+ }, children: t("analytics.filters.apply") })
991
1460
  ] })
992
1461
  ] });
1462
+ if (inline) return /* @__PURE__ */ jsx("div", { className: "flex min-h-0 flex-col", children: panel });
1463
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
1464
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(ToolbarButton, { icon: SlidersHorizontal, caret: false, children: [
1465
+ t("analytics.filters"),
1466
+ activeCount > 0 ? /* @__PURE__ */ jsx("span", { className: "ml-0.5 rounded-full bg-primary px-1.5 text-[10px] font-semibold text-primary-foreground", children: activeCount }) : null
1467
+ ] }) }),
1468
+ /* @__PURE__ */ jsx(PopoverContent, { align: "end", className: "w-80 p-0", children: panel })
1469
+ ] });
993
1470
  }
994
1471
  function AnalyticsRangeControl() {
995
1472
  const t = useTranslation();
1473
+ const surface = useToolbarSurface();
996
1474
  const locale = getCurrentLocale();
997
1475
  const { state, preset, setPreset, setDateRange } = useAnalyticsContext();
998
1476
  const custom = preset === "custom";
999
1477
  const span = formatSpan(state.dateRange.from, state.dateRange.to, locale);
1478
+ if (surface === "panel") {
1479
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1480
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1", children: [
1481
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t("analytics.range.from") }),
1482
+ /* @__PURE__ */ jsx(
1483
+ DatePicker,
1484
+ {
1485
+ value: state.dateRange.from,
1486
+ onChange: (from) => setDateRange({
1487
+ from,
1488
+ to: from > state.dateRange.to ? from : state.dateRange.to
1489
+ })
1490
+ }
1491
+ )
1492
+ ] }),
1493
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1", children: [
1494
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t("analytics.range.to") }),
1495
+ /* @__PURE__ */ jsx(
1496
+ DatePicker,
1497
+ {
1498
+ value: state.dateRange.to,
1499
+ onChange: (to) => setDateRange({
1500
+ from: to < state.dateRange.from ? to : state.dateRange.from,
1501
+ to
1502
+ })
1503
+ }
1504
+ )
1505
+ ] })
1506
+ ] });
1507
+ }
1000
1508
  return /* @__PURE__ */ jsx(
1001
1509
  RangeControl,
1002
1510
  {
@@ -1087,23 +1595,100 @@ function ActiveFilterChip({ label, value, filterKey }) {
1087
1595
  const t = useTranslation();
1088
1596
  const { setFilter } = useAnalyticsContext();
1089
1597
  const text = Array.isArray(value) ? value.length === 1 ? String(value[0]) : t("analytics.filters.selected", { count: value.length }) : String(value);
1090
- return /* @__PURE__ */ jsxs("span", { className: "inline-flex h-[26px] shrink-0 items-center gap-1 rounded-full border border-primary/30 bg-primary/10 py-1 pl-2.5 pr-1 text-xs font-medium text-primary", children: [
1091
- /* @__PURE__ */ jsxs("span", { className: "opacity-70", children: [
1092
- label,
1093
- ":"
1094
- ] }),
1095
- /* @__PURE__ */ jsx("span", { className: "max-w-[10rem] truncate", children: text }),
1096
- /* @__PURE__ */ jsx(
1097
- "button",
1098
- {
1099
- type: "button",
1100
- onClick: () => setFilter(filterKey, void 0),
1101
- "aria-label": `${t("analytics.filters.remove")}: ${label}`,
1102
- className: "rounded-full p-0.5 transition-colors hover:bg-primary/20",
1103
- children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
1104
- }
1105
- )
1106
- ] });
1598
+ return (
1599
+ // `min-w-0` no lugar de `shrink-0`: na faixa estreita do celular a pastilha
1600
+ // inteira não cabia, e o que sobrava fora da tela era justamente o × — um
1601
+ // filtro visível que não se consegue tirar é pior do que um escondido.
1602
+ // Encolhendo, quem encurta é o VALOR (truncate), e o botão de remover é
1603
+ // `shrink-0`, então sobrevive a qualquer largura.
1604
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex h-[26px] min-w-0 items-center gap-1 rounded-full border border-primary/30 bg-primary/10 py-1 pl-2.5 pr-1 text-xs font-medium text-primary", children: [
1605
+ /* @__PURE__ */ jsxs("span", { className: "shrink-0 opacity-70", children: [
1606
+ label,
1607
+ ":"
1608
+ ] }),
1609
+ /* @__PURE__ */ jsx("span", { className: "max-w-[10rem] truncate", children: text }),
1610
+ /* @__PURE__ */ jsx(
1611
+ "button",
1612
+ {
1613
+ type: "button",
1614
+ onClick: () => setFilter(filterKey, void 0),
1615
+ "aria-label": `${t("analytics.filters.remove")}: ${label}`,
1616
+ className: "shrink-0 rounded-full p-0.5 transition-colors hover:bg-primary/20",
1617
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
1618
+ }
1619
+ )
1620
+ ] })
1621
+ );
1622
+ }
1623
+ var DATE_RANGE_PRESETS = [
1624
+ "today",
1625
+ "yesterday",
1626
+ "last_7_days",
1627
+ "last_30_days",
1628
+ "last_90_days",
1629
+ "this_month",
1630
+ "last_month",
1631
+ "this_year"
1632
+ ];
1633
+ function formatSpan2(from, to, locale) {
1634
+ const fmt = new Intl.DateTimeFormat(locale, { day: "2-digit", month: "short" });
1635
+ const a = fmt.format(/* @__PURE__ */ new Date(`${from}T12:00:00`));
1636
+ const b = fmt.format(/* @__PURE__ */ new Date(`${to}T12:00:00`));
1637
+ return a === b ? a : `${a} \u2013 ${b}`;
1638
+ }
1639
+ function DateRangeControl({
1640
+ value,
1641
+ onChange,
1642
+ preset = "custom",
1643
+ onPresetChange,
1644
+ presets = DATE_RANGE_PRESETS
1645
+ }) {
1646
+ const t = useTranslation();
1647
+ const locale = getCurrentLocale();
1648
+ const custom = preset === "custom";
1649
+ const pick = (p) => {
1650
+ onPresetChange?.(p);
1651
+ onChange(resolveRangePreset(p));
1652
+ };
1653
+ return /* @__PURE__ */ jsx(
1654
+ RangeControl,
1655
+ {
1656
+ options: presets.map((p) => ({ value: p, label: t(`analytics.range.${p}`) })),
1657
+ value: preset,
1658
+ onChange: (p) => pick(p),
1659
+ label: custom ? t("analytics.range.custom") : void 0,
1660
+ span: custom ? void 0 : formatSpan2(value.from, value.to, locale),
1661
+ extra: /* @__PURE__ */ jsxs(Fragment, { children: [
1662
+ /* @__PURE__ */ jsx(DropdownSeparator, {}),
1663
+ /* @__PURE__ */ jsxs(DropdownItem, { onSelect: () => {
1664
+ onPresetChange?.("custom");
1665
+ onChange({ ...value });
1666
+ }, children: [
1667
+ t("analytics.range.custom"),
1668
+ custom ? /* @__PURE__ */ jsx(Check, { className: "ml-auto h-3.5 w-3.5 shrink-0 pl-1" }) : null
1669
+ ] })
1670
+ ] }),
1671
+ children: custom ? /* @__PURE__ */ jsxs(Fragment, { children: [
1672
+ /* @__PURE__ */ jsx(
1673
+ DatePicker,
1674
+ {
1675
+ variant: "toolbar",
1676
+ value: value.from,
1677
+ onChange: (from) => onChange({ from, to: from > value.to ? from : value.to })
1678
+ }
1679
+ ),
1680
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2013" }),
1681
+ /* @__PURE__ */ jsx(
1682
+ DatePicker,
1683
+ {
1684
+ variant: "toolbar",
1685
+ value: value.to,
1686
+ onChange: (to) => onChange({ from: to < value.from ? to : value.from, to })
1687
+ }
1688
+ )
1689
+ ] }) : null
1690
+ }
1691
+ );
1107
1692
  }
1108
1693
  var usePracticeStore = create((set, get) => ({
1109
1694
  title: null,
@@ -1710,6 +2295,6 @@ function Footprint({ n, label, top, left, done, next }) {
1710
2295
  );
1711
2296
  }
1712
2297
 
1713
- export { AnalyticsCard, AnalyticsContextProvider, AnalyticsControls, AnalyticsFilterPicker, AnalyticsGrid, Avatar, AvatarFallback, AvatarImage, BATCH_WINDOW_MS, PracticeOverlay, TourSpotlight, autoGrain, collectFilters, comparisonRange, createBatchingProvider, currentStop, deltaPercent, fireConfetti, formatAnalyticsValue, formatBucket, formatCompact, formatDuration, getTourNavigator, isEngineMissing, nextPracticeStep, num, parseDay, rangeLengthDays, resolveRangePreset, resolveRatios, resolveRowRatios, setTourNavigator, toBreakdown, toChartData, toDayString, toSpark, tourAnchor, trendOf, useAnalyticsCard, useAnalyticsCardRows, useAnalyticsContext, useAnalyticsContextOptional, usePracticeStore, useTourStore, vizCategory, vizMeasures };
2298
+ export { AnalyticsCard, AnalyticsContextProvider, AnalyticsControls, AnalyticsFilterPicker, AnalyticsGrid, Avatar, AvatarFallback, AvatarImage, BATCH_WINDOW_MS, DATE_RANGE_PRESETS, DateRangeControl, PracticeOverlay, STATEMENT_ROW_CAP, StatementTable, TourSpotlight, autoGrain, buildStatement, collectFilters, compareCodes, comparisonRange, createBatchingProvider, currentStop, defaultOpenKeys, deltaPercent, fireConfetti, flattenNodes, formatAnalyticsValue, formatBucket, formatCompact, formatDuration, getTourNavigator, isEngineMissing, nextPracticeStep, num, parentCode, parseDay, rangeLengthDays, resolveRangePreset, resolveRatios, resolveRowRatios, setTourNavigator, toBreakdown, toChartData, toDayString, toSpark, tourAnchor, trendOf, useAnalyticsAllRows, useAnalyticsCard, useAnalyticsCardRows, useAnalyticsContext, useAnalyticsContextOptional, usePracticeStore, useTourStore, vizCategory, vizMeasures };
1714
2299
  //# sourceMappingURL=index.js.map
1715
2300
  //# sourceMappingURL=index.js.map