@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.
- package/dist/analytics/AnalyticsCard.d.ts.map +1 -1
- package/dist/analytics/AnalyticsControls.d.ts.map +1 -1
- package/dist/analytics/DateRangeControl.d.ts +14 -0
- package/dist/analytics/DateRangeControl.d.ts.map +1 -0
- package/dist/analytics/StatementTable.d.ts +22 -0
- package/dist/analytics/StatementTable.d.ts.map +1 -0
- package/dist/analytics/context.d.ts +8 -0
- package/dist/analytics/context.d.ts.map +1 -1
- package/dist/analytics/derive.d.ts +13 -0
- package/dist/analytics/derive.d.ts.map +1 -1
- package/dist/analytics/index.d.ts +7 -1
- package/dist/analytics/index.d.ts.map +1 -1
- package/dist/analytics/statement.d.ts +58 -0
- package/dist/analytics/statement.d.ts.map +1 -0
- package/dist/{chunk-E44WOPCR.js → chunk-HO2P2XB4.js} +428 -58
- package/dist/chunk-HO2P2XB4.js.map +1 -0
- package/dist/{chunk-7G3O54CC.js → chunk-L4TXDKMN.js} +5 -5
- package/dist/chunk-L4TXDKMN.js.map +1 -0
- package/dist/{chunk-DHNUZF2X.js → chunk-OWN3GFDG.js} +4 -3
- package/dist/chunk-OWN3GFDG.js.map +1 -0
- package/dist/{chunk-PQUS22JE.js → chunk-RRNMNFLF.js} +483 -794
- package/dist/chunk-RRNMNFLF.js.map +1 -0
- package/dist/chunk-UP7WVKSU.js +631 -0
- package/dist/chunk-UP7WVKSU.js.map +1 -0
- package/dist/{chunk-ZSOT7KTE.js → chunk-XEUIYSW2.js} +321 -361
- package/dist/chunk-XEUIYSW2.js.map +1 -0
- package/dist/{chunk-SUSW4LSW.js → chunk-XKFYX3DG.js} +5 -5
- package/dist/{chunk-SUSW4LSW.js.map → chunk-XKFYX3DG.js.map} +1 -1
- package/dist/dashboard/index.js +4 -4
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +672 -87
- package/dist/index.js.map +1 -1
- package/dist/layout/AppShell.d.ts +7 -1
- package/dist/layout/AppShell.d.ts.map +1 -1
- package/dist/layout/ShellSearch.d.ts +53 -0
- package/dist/layout/ShellSearch.d.ts.map +1 -0
- package/dist/layout/Sidebar.d.ts +10 -6
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Takeover.d.ts.map +1 -1
- package/dist/layout/index.d.ts +1 -0
- package/dist/layout/index.d.ts.map +1 -1
- package/dist/layout/index.js +4 -4
- package/dist/primitives/data-table.d.ts.map +1 -1
- package/dist/primitives/date-range-chip.d.ts +32 -0
- package/dist/primitives/date-range-chip.d.ts.map +1 -0
- package/dist/primitives/header-tabs.d.ts.map +1 -1
- package/dist/primitives/index.d.ts +2 -1
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +5 -5
- package/dist/primitives/list-view.d.ts +43 -1
- package/dist/primitives/list-view.d.ts.map +1 -1
- package/dist/primitives/modal.d.ts +2 -0
- package/dist/primitives/modal.d.ts.map +1 -1
- package/dist/primitives/page-actions.d.ts +14 -0
- package/dist/primitives/page-actions.d.ts.map +1 -1
- package/dist/primitives/page-toolbar.d.ts +16 -1
- package/dist/primitives/page-toolbar.d.ts.map +1 -1
- package/dist/primitives/segmented-control.d.ts.map +1 -1
- package/dist/stores/layout.store.d.ts +20 -1
- package/dist/stores/layout.store.d.ts.map +1 -1
- package/dist/styles.css +58 -0
- package/package.json +2 -2
- package/dist/chunk-7G3O54CC.js.map +0 -1
- package/dist/chunk-DHNUZF2X.js.map +0 -1
- package/dist/chunk-E44WOPCR.js.map +0 -1
- package/dist/chunk-HKGTF2YA.js +0 -117
- package/dist/chunk-HKGTF2YA.js.map +0 -1
- package/dist/chunk-PQUS22JE.js.map +0 -1
- package/dist/chunk-ZSOT7KTE.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { Badge, Breadcrumb, BulkActionBar, ChevronPager, CurrencyInput, DatePicker, DotBadge, DraftCommitBar, FilterCheckBox, FilterCheckList, FilterCheckRow, FilterChip, FilterSection,
|
|
3
|
-
export { AppShellSkeleton, ContentSplit, ContentSplitTrigger, PageTrail, Takeover, navigateToCrumb, usePageTrail, usePageTrailParent, useTakeoverClose } from './chunk-
|
|
4
|
-
export {
|
|
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-
|
|
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-
|
|
8
|
-
import { Skeleton, ToolbarButton, PageToolbar, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, Card,
|
|
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-
|
|
10
|
-
export { PageHeaderOutlet, PageTitlePortal, useHasPageActions, useHasPageHeaderBack, useHasPageHeaderHost, useHasPageHeaderOutlet, useLayoutStore, usePageHeader, usePageHeaderClaimed, usePageHeaderHost, useRegisterPageActions } from './chunk-
|
|
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-
|
|
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,
|
|
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.
|
|
234
|
+
return `Q${Math.floor(d.getUTCMonth() / 3) + 1} ${d.getUTCFullYear()}`;
|
|
233
235
|
case "year":
|
|
234
|
-
return String(d.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
924
|
-
/* @__PURE__ */ jsx(
|
|
925
|
-
|
|
926
|
-
|
|
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__ */
|
|
929
|
-
|
|
930
|
-
|
|
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":
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
}
|
|
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
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
/* @__PURE__ */
|
|
1097
|
-
"
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
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
|