@timbal-ai/timbal-react 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -1
- package/README.md +33 -0
- package/dist/app.cjs +884 -642
- package/dist/app.d.cts +4 -4
- package/dist/app.d.ts +4 -4
- package/dist/app.esm.js +6 -6
- package/dist/{chart-artifact-2OTDTRwM.d.ts → chart-artifact-BYl5C-dk.d.ts} +90 -29
- package/dist/{chart-artifact-CS3qyGIY.d.cts → chart-artifact-Dpt4t5sf.d.cts} +90 -29
- package/dist/{chat-ClmzWzCX.d.cts → chat-DDsp-Vzz.d.cts} +1 -1
- package/dist/{chat-ClmzWzCX.d.ts → chat-DDsp-Vzz.d.ts} +1 -1
- package/dist/chat.cjs +26 -26
- package/dist/chat.d.cts +3 -3
- package/dist/chat.d.ts +3 -3
- package/dist/chat.esm.js +3 -3
- package/dist/{chunk-TZI3ID3C.esm.js → chunk-24B4I4XC.esm.js} +3 -3
- package/dist/{chunk-QIABF4KB.esm.js → chunk-ELEY66OH.esm.js} +2 -2
- package/dist/{chunk-WMKPT5BV.esm.js → chunk-HSL36SJ4.esm.js} +6 -6
- package/dist/chunk-JJOO4PR5.esm.js +391 -0
- package/dist/{chunk-AZL2WANO.esm.js → chunk-MBS7XHV2.esm.js} +28 -28
- package/dist/{chunk-5ECRZ5O7.esm.js → chunk-NO5AWNWT.esm.js} +224 -57
- package/dist/{chunk-ZNYAETFD.esm.js → chunk-R4RQT2XQ.esm.js} +2 -2
- package/dist/{chunk-JYDJRGDE.esm.js → chunk-TMP7RIA7.esm.js} +2 -2
- package/dist/{chunk-SZDYIRMB.esm.js → chunk-UVPXH4MB.esm.js} +647 -532
- package/dist/{chunk-IGHBLJV3.esm.js → chunk-WQIQW7EM.esm.js} +3 -2
- package/dist/{chunk-B4XAC4G7.esm.js → chunk-YYEI6XME.esm.js} +361 -527
- package/dist/{circular-progress-CDsJwIPF.d.cts → circular-progress-B9nnwzCu.d.cts} +1 -1
- package/dist/{circular-progress-CDsJwIPF.d.ts → circular-progress-B9nnwzCu.d.ts} +1 -1
- package/dist/cli/timbal-ui-lint.mjs +503 -0
- package/dist/index.cjs +1358 -856
- package/dist/index.d.cts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.esm.js +40 -20
- package/dist/{kanban-U5xNe9py.d.cts → kanban-FFBeaZPS.d.cts} +4 -4
- package/dist/{kanban-U5xNe9py.d.ts → kanban-FFBeaZPS.d.ts} +4 -4
- package/dist/{layout-B8r6Jbat.d.ts → layout-CuKeSY74.d.ts} +1 -1
- package/dist/{layout-Cu7Ijn04.d.cts → layout-PzVwkJyL.d.cts} +1 -1
- package/dist/site.cjs +71 -0
- package/dist/site.d.cts +15 -1
- package/dist/site.d.ts +15 -1
- package/dist/site.esm.js +12 -311
- package/dist/studio.cjs +31 -31
- package/dist/studio.d.cts +2 -2
- package/dist/studio.d.ts +2 -2
- package/dist/studio.esm.js +7 -7
- package/dist/{timbal-v2-button-B7vPs7gg.d.ts → timbal-v2-button-DCAZNyUx.d.cts} +1 -1
- package/dist/{timbal-v2-button-B7vPs7gg.d.cts → timbal-v2-button-DCAZNyUx.d.ts} +1 -1
- package/dist/ui.cjs +77 -77
- package/dist/ui.d.cts +3 -3
- package/dist/ui.d.ts +3 -3
- package/dist/ui.esm.js +15 -15
- package/dist/{welcome-NXZlcihe.d.cts → welcome-B00oH5Io.d.cts} +1 -1
- package/dist/{welcome-DduQAC4K.d.ts → welcome-DU-4NTjZ.d.ts} +1 -1
- package/package.json +13 -3
|
@@ -4,32 +4,36 @@ import {
|
|
|
4
4
|
ShellInsetProvider,
|
|
5
5
|
studioChromeShellStyle,
|
|
6
6
|
studioSidebarWidthTransition
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-ELEY66OH.esm.js";
|
|
8
8
|
import {
|
|
9
9
|
ChartArtifactView,
|
|
10
10
|
LineAreaChart,
|
|
11
11
|
Thread,
|
|
12
12
|
TimbalRuntimeProvider,
|
|
13
|
+
formatCompact,
|
|
13
14
|
monotoneAreaPath,
|
|
14
15
|
monotoneLinePath,
|
|
15
16
|
studioIntegrationCardClass,
|
|
16
17
|
studioTopbarPillHeightClass,
|
|
17
18
|
toNum
|
|
18
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-WQIQW7EM.esm.js";
|
|
19
20
|
import {
|
|
20
21
|
Checkbox,
|
|
21
22
|
CopyButton,
|
|
22
23
|
Popover,
|
|
23
24
|
PopoverContent,
|
|
24
25
|
PopoverTrigger,
|
|
26
|
+
Select,
|
|
27
|
+
SelectContent,
|
|
28
|
+
SelectItem,
|
|
29
|
+
SelectTrigger,
|
|
30
|
+
SelectValue,
|
|
25
31
|
Skeleton
|
|
26
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-NO5AWNWT.esm.js";
|
|
27
33
|
import {
|
|
28
34
|
PillSegmentedTabs
|
|
29
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-R4RQT2XQ.esm.js";
|
|
30
36
|
import {
|
|
31
|
-
Avatar,
|
|
32
|
-
AvatarFallback,
|
|
33
37
|
Button,
|
|
34
38
|
Dialog,
|
|
35
39
|
DialogContent,
|
|
@@ -41,7 +45,7 @@ import {
|
|
|
41
45
|
TIMBAL_V2_SWITCH_TRACK_OFF,
|
|
42
46
|
TimbalV2Button,
|
|
43
47
|
controlClass
|
|
44
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-MBS7XHV2.esm.js";
|
|
45
49
|
import {
|
|
46
50
|
cn
|
|
47
51
|
} from "./chunk-EDEKQYSU.esm.js";
|
|
@@ -220,7 +224,11 @@ var HOUSE_RULES = [
|
|
|
220
224
|
{
|
|
221
225
|
id: "compose-from-blocks",
|
|
222
226
|
rule: "Build from premade blocks (MetricRow, MetricChartCard, DataTable, IntegrationCard). Drop to raw primitives only when no block fits.",
|
|
223
|
-
why: "Slop appears the moment generation falls below the curated block layer."
|
|
227
|
+
why: "Slop appears the moment generation falls below the curated block layer.",
|
|
228
|
+
// "Should have used a block" is a judgement about absence, not a textual
|
|
229
|
+
// pattern — no high-precision deterministic check exists, so this stays
|
|
230
|
+
// prompt-only rather than risk false-positives blocking valid UIs.
|
|
231
|
+
enforcement: "prompt-only"
|
|
224
232
|
},
|
|
225
233
|
{
|
|
226
234
|
id: "use-kit-controls",
|
|
@@ -301,7 +309,7 @@ The content region is a **padded scroll area** by default \u2014 great for stack
|
|
|
301
309
|
- Give the filling child **\`min-h-0 flex-1\`** (or \`h-full\`) so its own scroll/footer resolves \u2014 e.g. \`<TimbalChat className="min-h-0 flex-1" />\`, or a two-pane row where each pane is \`min-h-0 overflow-y-auto\`.
|
|
302
310
|
|
|
303
311
|
\`\`\`tsx
|
|
304
|
-
<AppShell contentFill topbar
|
|
312
|
+
<AppShell contentFill> {/* no global topbar / theme switch */}
|
|
305
313
|
<Page fill> {/* headerless: omit title */}
|
|
306
314
|
<TimbalChat workforceId="\u2026" className="min-h-0 flex-1" />
|
|
307
315
|
</Page>
|
|
@@ -321,7 +329,7 @@ Presentational groups \u2014 import from the package root, not from these paths:
|
|
|
321
329
|
|
|
322
330
|
| Folder | Components |
|
|
323
331
|
|--------|------------|
|
|
324
|
-
| \`data/\` | \`MetricRow\`, \`MetricChartCard\`, \`MetricTile\`, \`DataTable\`, \`FilterBar\`, \`FilterField\`, \`ChartPanel\` |
|
|
332
|
+
| \`data/\` | \`MetricRow\`, \`MetricChartCard\`, \`MetricTile\`, \`DataTable\`, \`FilterBar\`, \`FilterField\`, \`FilterDropdown\`, \`ChartPanel\` |
|
|
325
333
|
| \`integrations/\` | \`IntegrationCard\`, \`ConnectionRow\`, \`ConnectionRowList\`, \`IntegrationsEmptyState\`, \`PlanBadge\` |
|
|
326
334
|
| \`settings/\` | \`SettingsSection\`, \`FieldRow\`, \`DangerZone\`, \`FloatingUnsavedChangesBar\` |
|
|
327
335
|
| \`surfaces/\` | \`StatTile\`, \`InfoCard\`, \`AlertCard\`, \`CatalogCard\`, \`ResourceCard\`, \`DescriptionList\`, \`ExpandableSection\`, \`StatusDot\`, \`StatusBadge\`, \`EmptyState\` |
|
|
@@ -390,6 +398,7 @@ The cause of slop is dropping **below** the curated block layer into raw primiti
|
|
|
390
398
|
| \`StatusBadge\` | Status pill: \`tone\` (\`default\`\\|\`primary\`\\|\`success\`\\|\`warn\`\\|\`danger\`\\|\`muted\`), children. Use \`danger\` for critical/error severity. |
|
|
391
399
|
| \`FilterBar\` | Horizontal filter row \u2014 bottom-aligns controls. Mix \`SearchInput\` with labeled \`FilterField\` + \`Select\` (or \`Field\` + \`Select\`); labels sit above, control baselines match. |
|
|
392
400
|
| \`FilterField\` | Optional label wrapper for a filter control inside \`FilterBar\` (severity, status, \u2026). Omit \`label\` for search-only fields. |
|
|
401
|
+
| \`FilterDropdown\` | Single-button **multi-facet** filter popover for dense list/table views \u2014 **data-driven**: pass \`fields\` describing your **actual columns** (each \`{ id, label, type }\` where \`type\` is \`multiselect\` \\| \`text\` \\| \`daterange\` \\| \`numeric\`; \`multiselect\` takes \`options: [{ value, label, hint?, icon? }]\`). State is keyed by field \`id\` \u2014 controlled (\`value\` + \`onChange\`) or uncontrolled (\`defaultValue\`). Renders **removable active-filter pills** next to the trigger by default (\`showActiveChips\`); wire \`onChange\` to actually filter your rows. **Always derive \`fields\` from the table's columns/data; never ship the default example facets.** Use when one \`FilterBar\` row isn't enough. |
|
|
393
402
|
| \`SearchInput\` | Filter field with consistent app styling. |
|
|
394
403
|
| \`DataTable\` | Sortable table: \`columns\`, \`rows\`, \`getRowKey\`, optional \`sort\` / \`onSortChange\`, \`emptyTitle\`, \`showRowCount\`, \`caption\`, \`truncate: true\` on columns with long text. **Scales:** \`pageSize\` (built-in client pager), \`selectable\` + \`onSelectionChange\` (checkbox column for bulk actions), \`loading\` (skeleton rows). \`onRowClick\` for row \u2192 detail (open a \`Sheet\`). |
|
|
395
404
|
| \`Avatar\` / \`AvatarFallback\` | User initials: \`variant="secondary"\` (or \`primary\` / \`chart\` alias) on **both** \`Avatar\` and \`AvatarFallback\` \u2014 same chrome as catalog **Action** buttons (\`Button variant="secondary"\`: elevated gradient, \`border-border\`, \`shadow-card\`, \`text-foreground\`). Never dark primary CTA fill or raw \`bg-blue-600\`. |
|
|
@@ -404,6 +413,8 @@ The cause of slop is dropping **below** the curated block layer into raw primiti
|
|
|
404
413
|
|
|
405
414
|
Charts run on **recharts** with shadcn \`ChartContainer\` / \`ChartTooltipContent\` chrome (see \`src/ui/chart.tsx\`). Series colors default to \`--chart-1..6\`; override those CSS tokens to rebrand every chart.
|
|
406
415
|
|
|
416
|
+
> **React 19 requirement \u2014 do not hand-roll SVG charts to work around this.** recharts under React 19 crashes (\`Cannot assign to read only property 'lanes'\`, blank route) when \`immer\` resolves to **11.0.0**. The fix is a dependency override in the app's \`package.json\` \u2014 \`"overrides": { "immer": ">=11.0.1" }\` (Yarn: \`"resolutions"\`) \u2014 **not** a code change. Always keep using \`LineAreaChart\` / \`PieChart\` / \`ChartPanel\`; never replace them with raw SVG/CSS charts.
|
|
417
|
+
|
|
407
418
|
| Component | Use for |
|
|
408
419
|
|-----------|---------|
|
|
409
420
|
| \`LineAreaChart\` | Cartesian engine (shadcn-style chrome). Bar fills use theme gradients automatically. Props: \`data\`, \`xKey\`, \`series: [{ dataKey, label?, color? }]\`, \`variant\` (\`area\`\\|\`line\`\\|\`bar\`), \`orientation\` (\`horizontal\` for horizontal bars), \`stacked\`, \`curve\`, \`dots\`, \`gridLines\`, \`tooltipIndicator\`, \`layout\` (\`flush\` \u2014 hides axes by default; category + values on hover tooltip), \`showXAxis\` / \`showYAxis\` to opt back in, \`clipTicks\` (truncates long axis labels when axes are on), \`height\`, \`showLegend\`, \`formatX\`, \`formatValue\`, \`ariaLabel\`. |
|
|
@@ -448,6 +459,21 @@ Charts run on **recharts** with shadcn \`ChartContainer\` / \`ChartTooltipConten
|
|
|
448
459
|
| \`Banner\` | Page-level announcement bar: \`tone\` (\`default\`\\|\`primary\`\\|\`success\`\\|\`warn\`\\|\`danger\`), \`icon\`, \`title\`, body as children, right-aligned \`actions\`, \`onDismiss\` (renders the dismiss X). For in-form/field messages use \`InfoCard\` or \`Alert\` instead. |
|
|
449
460
|
| \`Timeline\` | Vertical event log: \`items: [{ id, title, description?, meta?, tone?, icon? }]\`. Presentational \u2014 pass already-formatted timestamps in \`meta\`. |
|
|
450
461
|
|
|
462
|
+
#### More \`/ui\` primitives (import from \`/ui\` or the package root)
|
|
463
|
+
|
|
464
|
+
These ship in the same design system but aren't re-exported from \`/app\`. Reach for them before hand-rolling \u2014 they're all dependency-free and on the shared tokens / control surface.
|
|
465
|
+
|
|
466
|
+
| Component | Use for |
|
|
467
|
+
|-----------|---------|
|
|
468
|
+
| \`Stepper\` | Ordered step indicator for wizards / onboarding (horizontal or vertical; complete / active / upcoming states). |
|
|
469
|
+
| \`Rating\` | Star rating \u2014 interactive (keyboard + hover preview) or \`readOnly\`; controlled or uncontrolled. |
|
|
470
|
+
| \`NumberField\` | Numeric input with \u2212/+ steppers on the control surface; clamps to \`min\`/\`max\`, steps by \`step\`. |
|
|
471
|
+
| \`TagInput\` | Chips / token input; commits on Enter/comma, removes on Backspace, optional \`dedupe\`/\`max\`. |
|
|
472
|
+
| \`AvatarGroup\` | Overlapping avatar stack with an optional \`+N\` overflow chip (\`max\`, \`spacing\`). |
|
|
473
|
+
| \`CircularProgress\` | Lightweight SVG progress ring \u2014 determinate (optional center label) or indeterminate. |
|
|
474
|
+
| \`CopyButton\` | Click-to-copy with a transient check confirmation; icon-only or with a label. |
|
|
475
|
+
| \`Snippet\` | Single-line code / command on the elevated surface with a built-in copy button. |
|
|
476
|
+
|
|
451
477
|
Studio chrome (\`StudioSidebar\`, \`ModeToggle\`, \u2026) lives in \`@timbal-ai/timbal-react/studio\` \u2014 optional, not required for every dashboard.
|
|
452
478
|
|
|
453
479
|
### Block recipes \u2014 compose these (don't clone wholesale)
|
|
@@ -514,6 +540,7 @@ import {
|
|
|
514
540
|
DataTable,
|
|
515
541
|
FilterBar,
|
|
516
542
|
FilterField,
|
|
543
|
+
FilterDropdown,
|
|
517
544
|
AlertCard,
|
|
518
545
|
CatalogCard,
|
|
519
546
|
} from "@timbal-ai/timbal-react/app";
|
|
@@ -568,6 +595,8 @@ var GRADIENT_DIRECTIONS = /* @__PURE__ */ new Set([
|
|
|
568
595
|
var ICON_IMPORT_RE = /from\s+["']lucide-react["']/;
|
|
569
596
|
var RAW_CONTROL_SURFACE_RE = /\bborder-input\b/;
|
|
570
597
|
var COLORED_HOVER_RE = /\bhover:(?:bg|from|to|via)-(?:primary|destructive|success|warn|danger|blue|emerald|green|amber|red|indigo|violet|purple|pink|rose|sky|cyan|teal|lime|yellow|orange|fuchsia)\b/;
|
|
598
|
+
var TREND_CONTEXT_RE = /\b(?:trend|delta|TrendingUp|TrendingDown|ArrowUp|ArrowDown|ArrowUpRight|ArrowDownRight|MoveUp|MoveDown)\b|[+\-]\d+(?:\.\d+)?\s*%/;
|
|
599
|
+
var TREND_COLOR_RE = /\b(?:text|bg|border)-(?:success|destructive|emerald|green|lime|teal|red|rose|orange|amber)(?:-\d{2,3})?(?:\/\d{1,3})?\b/;
|
|
571
600
|
var RESERVED_GRADIENT_SET = new Set(RESERVED_GRADIENT_TOKENS);
|
|
572
601
|
function stripVariants(util) {
|
|
573
602
|
return util.replace(/^(?:[a-z-]+:)*/, "");
|
|
@@ -611,6 +640,16 @@ function lintGeneratedUi(source, options = {}) {
|
|
|
611
640
|
if (cardMatch) {
|
|
612
641
|
const isSelfClosing = /\/>/.test(line) && line.indexOf(cardMatch[0]) < line.indexOf("/>");
|
|
613
642
|
if (!isSelfClosing) {
|
|
643
|
+
if (openCards.length > 0) {
|
|
644
|
+
const parentCard = openCards[openCards.length - 1];
|
|
645
|
+
findings.push({
|
|
646
|
+
rule: "no-card-in-card",
|
|
647
|
+
severity: "warn",
|
|
648
|
+
line: lineNo,
|
|
649
|
+
message: `Card inside card. A <${cardMatch[1]}> is nested inside the <${parentCard.type}> opened on L${parentCard.line}. Double borders/shadows add no information \u2014 group with spacing or a <Section> instead.`,
|
|
650
|
+
snippet: line.trim().slice(0, 120)
|
|
651
|
+
});
|
|
652
|
+
}
|
|
614
653
|
openCards.push({ type: cardMatch[1], line: lineNo });
|
|
615
654
|
}
|
|
616
655
|
}
|
|
@@ -683,6 +722,15 @@ function lintGeneratedUi(source, options = {}) {
|
|
|
683
722
|
snippet: line.trim().slice(0, 120)
|
|
684
723
|
});
|
|
685
724
|
}
|
|
725
|
+
if (TREND_CONTEXT_RE.test(line) && TREND_COLOR_RE.test(line)) {
|
|
726
|
+
findings.push({
|
|
727
|
+
rule: "neutral-trend",
|
|
728
|
+
severity: "warn",
|
|
729
|
+
line: lineNo,
|
|
730
|
+
message: "Colored trend indicator. House style: don't tint deltas green/red on every metric \u2014 show a trend only when the change is the point, and keep it muted (text-muted-foreground).",
|
|
731
|
+
snippet: line.trim().slice(0, 120)
|
|
732
|
+
});
|
|
733
|
+
}
|
|
686
734
|
if (BOLD_VALUE_RE.test(line)) {
|
|
687
735
|
findings.push({
|
|
688
736
|
rule: "bold-metric",
|
|
@@ -1648,7 +1696,11 @@ function useAppDensityClass(key, override) {
|
|
|
1648
1696
|
}
|
|
1649
1697
|
|
|
1650
1698
|
// src/charts/sparkline.tsx
|
|
1651
|
-
import {
|
|
1699
|
+
import {
|
|
1700
|
+
useId,
|
|
1701
|
+
useRef,
|
|
1702
|
+
useState
|
|
1703
|
+
} from "react";
|
|
1652
1704
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1653
1705
|
var Sparkline = ({
|
|
1654
1706
|
data,
|
|
@@ -1659,30 +1711,36 @@ var Sparkline = ({
|
|
|
1659
1711
|
height = 28,
|
|
1660
1712
|
strokeWidth = 1.5,
|
|
1661
1713
|
className,
|
|
1662
|
-
ariaLabel = "Trend"
|
|
1714
|
+
ariaLabel = "Trend",
|
|
1715
|
+
interactive = false,
|
|
1716
|
+
labels,
|
|
1717
|
+
formatValue
|
|
1663
1718
|
}) => {
|
|
1664
1719
|
const uid = useId();
|
|
1720
|
+
const containerRef = useRef(null);
|
|
1721
|
+
const [activeIndex, setActiveIndex] = useState(null);
|
|
1665
1722
|
const values = data.map((d) => typeof d === "number" ? d : toNum(d[dataKey]));
|
|
1666
1723
|
if (values.length === 0) {
|
|
1667
1724
|
return /* @__PURE__ */ jsx3("span", { className: cn("inline-block", className), style: { width, height } });
|
|
1668
1725
|
}
|
|
1669
|
-
const
|
|
1726
|
+
const padX = 0;
|
|
1727
|
+
const padY = strokeWidth + 1;
|
|
1670
1728
|
const min = Math.min(...values);
|
|
1671
1729
|
const max = Math.max(...values);
|
|
1672
1730
|
const range = max - min || 1;
|
|
1673
|
-
const innerW = width -
|
|
1674
|
-
const innerH = height -
|
|
1731
|
+
const innerW = width - padX * 2;
|
|
1732
|
+
const innerH = height - padY * 2;
|
|
1675
1733
|
const points = values.map((v, i) => ({
|
|
1676
|
-
x:
|
|
1677
|
-
y:
|
|
1734
|
+
x: padX + (values.length > 1 ? i / (values.length - 1) * innerW : innerW / 2),
|
|
1735
|
+
y: padY + innerH - (v - min) / range * innerH
|
|
1678
1736
|
}));
|
|
1679
|
-
|
|
1737
|
+
const svg = /* @__PURE__ */ jsxs2(
|
|
1680
1738
|
"svg",
|
|
1681
1739
|
{
|
|
1682
1740
|
width,
|
|
1683
1741
|
height,
|
|
1684
1742
|
viewBox: `0 0 ${width} ${height}`,
|
|
1685
|
-
className: cn("block", className),
|
|
1743
|
+
className: cn("block", interactive ? "h-full w-full" : className),
|
|
1686
1744
|
role: "img",
|
|
1687
1745
|
"aria-label": ariaLabel,
|
|
1688
1746
|
preserveAspectRatio: "none",
|
|
@@ -1692,7 +1750,7 @@ var Sparkline = ({
|
|
|
1692
1750
|
/* @__PURE__ */ jsx3("stop", { offset: "0%", style: { stopColor: color, stopOpacity: 0.25 } }),
|
|
1693
1751
|
/* @__PURE__ */ jsx3("stop", { offset: "100%", style: { stopColor: color, stopOpacity: 0 } })
|
|
1694
1752
|
] }) }),
|
|
1695
|
-
/* @__PURE__ */ jsx3("path", { d: monotoneAreaPath(points, height -
|
|
1753
|
+
/* @__PURE__ */ jsx3("path", { d: monotoneAreaPath(points, height - padY), fill: `url(#${uid}-spark)` })
|
|
1696
1754
|
] }),
|
|
1697
1755
|
/* @__PURE__ */ jsx3(
|
|
1698
1756
|
"path",
|
|
@@ -1704,7 +1762,81 @@ var Sparkline = ({
|
|
|
1704
1762
|
strokeLinecap: "round",
|
|
1705
1763
|
strokeLinejoin: "round"
|
|
1706
1764
|
}
|
|
1707
|
-
)
|
|
1765
|
+
),
|
|
1766
|
+
interactive && activeIndex != null && points[activeIndex] ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1767
|
+
/* @__PURE__ */ jsx3(
|
|
1768
|
+
"line",
|
|
1769
|
+
{
|
|
1770
|
+
x1: points[activeIndex].x,
|
|
1771
|
+
x2: points[activeIndex].x,
|
|
1772
|
+
y1: 0,
|
|
1773
|
+
y2: height,
|
|
1774
|
+
stroke: color,
|
|
1775
|
+
strokeWidth: 1,
|
|
1776
|
+
strokeOpacity: 0.3,
|
|
1777
|
+
vectorEffect: "non-scaling-stroke"
|
|
1778
|
+
}
|
|
1779
|
+
),
|
|
1780
|
+
/* @__PURE__ */ jsx3(
|
|
1781
|
+
"circle",
|
|
1782
|
+
{
|
|
1783
|
+
cx: points[activeIndex].x,
|
|
1784
|
+
cy: points[activeIndex].y,
|
|
1785
|
+
r: 2.75,
|
|
1786
|
+
fill: color,
|
|
1787
|
+
stroke: "var(--background, #fff)",
|
|
1788
|
+
strokeWidth: 1.5,
|
|
1789
|
+
vectorEffect: "non-scaling-stroke"
|
|
1790
|
+
}
|
|
1791
|
+
)
|
|
1792
|
+
] }) : null
|
|
1793
|
+
]
|
|
1794
|
+
}
|
|
1795
|
+
);
|
|
1796
|
+
if (!interactive) return svg;
|
|
1797
|
+
const onMove = (e) => {
|
|
1798
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1799
|
+
if (rect.width === 0) return;
|
|
1800
|
+
const fraction = (e.clientX - rect.left) / rect.width;
|
|
1801
|
+
const index = Math.max(
|
|
1802
|
+
0,
|
|
1803
|
+
Math.min(values.length - 1, Math.round(fraction * (values.length - 1)))
|
|
1804
|
+
);
|
|
1805
|
+
setActiveIndex(index);
|
|
1806
|
+
};
|
|
1807
|
+
const active = activeIndex != null ? points[activeIndex] : null;
|
|
1808
|
+
const formattedValue = activeIndex != null ? formatValue ? formatValue(values[activeIndex], activeIndex) : formatCompact(values[activeIndex]) : null;
|
|
1809
|
+
return /* @__PURE__ */ jsxs2(
|
|
1810
|
+
"span",
|
|
1811
|
+
{
|
|
1812
|
+
ref: containerRef,
|
|
1813
|
+
className: cn("relative block touch-none", className),
|
|
1814
|
+
style: { width: "100%", height: "100%" },
|
|
1815
|
+
onPointerMove: onMove,
|
|
1816
|
+
onPointerLeave: () => setActiveIndex(null),
|
|
1817
|
+
children: [
|
|
1818
|
+
svg,
|
|
1819
|
+
active ? /* @__PURE__ */ jsxs2(
|
|
1820
|
+
"span",
|
|
1821
|
+
{
|
|
1822
|
+
"aria-hidden": true,
|
|
1823
|
+
className: cn(
|
|
1824
|
+
"pointer-events-none absolute z-30 -translate-x-1/2 -translate-y-full whitespace-nowrap",
|
|
1825
|
+
"rounded-xl border px-3 py-2 text-[11px] font-medium leading-none tabular-nums shadow-[0_12px_40px_-10px_rgba(0,0,0,0.55)]",
|
|
1826
|
+
"border-white/10 bg-gradient-to-b from-neutral-800 to-neutral-950 text-white",
|
|
1827
|
+
"dark:border-black/10 dark:from-white dark:to-neutral-100 dark:text-neutral-900"
|
|
1828
|
+
),
|
|
1829
|
+
style: {
|
|
1830
|
+
left: `${active.x / width * 100}%`,
|
|
1831
|
+
top: `${active.y / height * 100}%`,
|
|
1832
|
+
marginTop: -8
|
|
1833
|
+
},
|
|
1834
|
+
children: [
|
|
1835
|
+
labels?.[activeIndex] != null ? /* @__PURE__ */ jsx3("span", { className: "mr-1.5 text-neutral-300 dark:text-neutral-500", children: labels[activeIndex] }) : null,
|
|
1836
|
+
/* @__PURE__ */ jsx3("span", { children: formattedValue })
|
|
1837
|
+
]
|
|
1838
|
+
}
|
|
1839
|
+
) : null
|
|
1708
1840
|
]
|
|
1709
1841
|
}
|
|
1710
1842
|
);
|
|
@@ -1770,6 +1902,15 @@ var inlineTrendToneClass = {
|
|
|
1770
1902
|
down: "text-rose-500/90 dark:text-rose-400/95 font-medium",
|
|
1771
1903
|
neutral: "text-muted-foreground/80"
|
|
1772
1904
|
};
|
|
1905
|
+
var sparklineToneColor = {
|
|
1906
|
+
up: "var(--primary, #3b82f6)",
|
|
1907
|
+
down: "var(--destructive, #f43f5e)",
|
|
1908
|
+
neutral: "var(--muted-foreground, #64748b)"
|
|
1909
|
+
};
|
|
1910
|
+
var sparklineBandBleed = {
|
|
1911
|
+
default: "-mx-4 -mb-3 h-10",
|
|
1912
|
+
compact: "-mx-3 -mb-2 h-8"
|
|
1913
|
+
};
|
|
1773
1914
|
var activeToneClass = {
|
|
1774
1915
|
default: "bg-foreground dark:bg-white",
|
|
1775
1916
|
primary: "bg-primary",
|
|
@@ -1799,8 +1940,10 @@ var MetricTile = ({
|
|
|
1799
1940
|
ariaLabel,
|
|
1800
1941
|
className
|
|
1801
1942
|
}) => {
|
|
1943
|
+
const density = useAppDensity();
|
|
1802
1944
|
const metricTileBaseClass = useAppDensityClass("metricTile");
|
|
1803
1945
|
const hasSparkline = Boolean(sparkline || sparklineData);
|
|
1946
|
+
const bandBleed = sparklineBandBleed[density === "compact" ? "compact" : "default"];
|
|
1804
1947
|
const content = /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1805
1948
|
active ? /* @__PURE__ */ jsx5(
|
|
1806
1949
|
"span",
|
|
@@ -1812,17 +1955,6 @@ var MetricTile = ({
|
|
|
1812
1955
|
)
|
|
1813
1956
|
}
|
|
1814
1957
|
) : null,
|
|
1815
|
-
hasSparkline ? /* @__PURE__ */ jsx5("div", { className: "absolute inset-x-0 bottom-0.5 h-9 w-full overflow-hidden pointer-events-none z-0 opacity-45 dark:opacity-35 select-none", children: sparkline ?? /* @__PURE__ */ jsx5(
|
|
1816
|
-
Sparkline,
|
|
1817
|
-
{
|
|
1818
|
-
data: sparklineData,
|
|
1819
|
-
width: 160,
|
|
1820
|
-
height: 36,
|
|
1821
|
-
className: "w-full h-full",
|
|
1822
|
-
color: trendTone === "up" ? "var(--primary, #3b82f6)" : trendTone === "down" ? "var(--destructive, #f43f5e)" : "var(--muted-foreground, #64748b)",
|
|
1823
|
-
...sparklineConfig
|
|
1824
|
-
}
|
|
1825
|
-
) }) : null,
|
|
1826
1958
|
/* @__PURE__ */ jsxs4("div", { className: "relative z-10 flex flex-col gap-1 w-full text-left", children: [
|
|
1827
1959
|
/* @__PURE__ */ jsx5("span", { className: "text-xs font-semibold text-muted-foreground/80 tracking-tight", children: label }),
|
|
1828
1960
|
/* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-2", children: [
|
|
@@ -1841,7 +1973,28 @@ var MetricTile = ({
|
|
|
1841
1973
|
}
|
|
1842
1974
|
) : null
|
|
1843
1975
|
] })
|
|
1844
|
-
] })
|
|
1976
|
+
] }),
|
|
1977
|
+
hasSparkline ? /* @__PURE__ */ jsx5(
|
|
1978
|
+
"div",
|
|
1979
|
+
{
|
|
1980
|
+
className: cn(
|
|
1981
|
+
"relative z-10 mt-2",
|
|
1982
|
+
bandBleed
|
|
1983
|
+
),
|
|
1984
|
+
children: sparkline ?? /* @__PURE__ */ jsx5(
|
|
1985
|
+
Sparkline,
|
|
1986
|
+
{
|
|
1987
|
+
data: sparklineData,
|
|
1988
|
+
width: 160,
|
|
1989
|
+
height: 40,
|
|
1990
|
+
interactive: true,
|
|
1991
|
+
className: "h-full w-full opacity-90",
|
|
1992
|
+
color: sparklineToneColor[trendTone],
|
|
1993
|
+
...sparklineConfig
|
|
1994
|
+
}
|
|
1995
|
+
)
|
|
1996
|
+
}
|
|
1997
|
+
) : null
|
|
1845
1998
|
] });
|
|
1846
1999
|
const divider = showDivider ? metricCellDividerClass : void 0;
|
|
1847
2000
|
if (onSelect) {
|
|
@@ -1893,7 +2046,7 @@ function useAppShellNav() {
|
|
|
1893
2046
|
|
|
1894
2047
|
// src/app/layout/AppShell.tsx
|
|
1895
2048
|
import { motion, useReducedMotion } from "motion/react";
|
|
1896
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2049
|
+
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
1897
2050
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1898
2051
|
var floatingTriggerClass = cn(
|
|
1899
2052
|
"aui-app-shell-chat-trigger-fixed fixed z-50 rounded-full px-5 py-2.5 text-sm font-medium shadow-card-elevated",
|
|
@@ -1917,7 +2070,7 @@ var AppShellBody = ({
|
|
|
1917
2070
|
insetExpanded,
|
|
1918
2071
|
children
|
|
1919
2072
|
}) => {
|
|
1920
|
-
const [isMobile, setIsMobile] =
|
|
2073
|
+
const [isMobile, setIsMobile] = useState2(() => {
|
|
1921
2074
|
if (typeof window === "undefined") return false;
|
|
1922
2075
|
return window.innerWidth < 768;
|
|
1923
2076
|
});
|
|
@@ -1997,7 +2150,7 @@ var AppShell = ({
|
|
|
1997
2150
|
}) => {
|
|
1998
2151
|
const topbarContent = topbar ?? header;
|
|
1999
2152
|
const hasChat = Boolean(chat);
|
|
2000
|
-
const [uncontrolledNavOpen, setUncontrolledNavOpen] =
|
|
2153
|
+
const [uncontrolledNavOpen, setUncontrolledNavOpen] = useState2(defaultNavOpen);
|
|
2001
2154
|
const isNavControlled = navOpenProp !== void 0;
|
|
2002
2155
|
const navOpen = isNavControlled ? navOpenProp : uncontrolledNavOpen;
|
|
2003
2156
|
const setNavOpen = useCallback(
|
|
@@ -2012,7 +2165,7 @@ var AppShell = ({
|
|
|
2012
2165
|
() => ({ open: navOpen, setOpen: setNavOpen, toggle: toggleNav }),
|
|
2013
2166
|
[navOpen, setNavOpen, toggleNav]
|
|
2014
2167
|
);
|
|
2015
|
-
const [uncontrolledOpen, setUncontrolledOpen] =
|
|
2168
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState2(defaultChatOpen);
|
|
2016
2169
|
const isChatControlled = chatOpenProp !== void 0;
|
|
2017
2170
|
const chatOpen = isChatControlled ? chatOpenProp : uncontrolledOpen;
|
|
2018
2171
|
const setChatOpen = useCallback(
|
|
@@ -2027,7 +2180,7 @@ var AppShell = ({
|
|
|
2027
2180
|
const toggleChat = useCallback(() => {
|
|
2028
2181
|
setChatOpen(!chatOpen);
|
|
2029
2182
|
}, [chatOpen, setChatOpen]);
|
|
2030
|
-
const [insetPaddingPx, setInsetPaddingPx] =
|
|
2183
|
+
const [insetPaddingPx, setInsetPaddingPx] = useState2(
|
|
2031
2184
|
sidebar ? SIDEBAR_INSET_PX_EXPANDED : 0
|
|
2032
2185
|
);
|
|
2033
2186
|
const reportShellInset = useCallback((insetPx) => {
|
|
@@ -2540,7 +2693,7 @@ var StatusBadge = ({
|
|
|
2540
2693
|
"span",
|
|
2541
2694
|
{
|
|
2542
2695
|
className: cn(
|
|
2543
|
-
"aui-app-status-badge inline-flex items-center gap-1.5 rounded-full px-2 py-0.5",
|
|
2696
|
+
"aui-app-status-badge inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5",
|
|
2544
2697
|
"text-xs font-medium leading-none ring-1 ring-inset",
|
|
2545
2698
|
statusBadgeToneClass[tone],
|
|
2546
2699
|
className
|
|
@@ -2755,7 +2908,7 @@ var DescriptionList = ({
|
|
|
2755
2908
|
);
|
|
2756
2909
|
|
|
2757
2910
|
// src/app/surfaces/ExpandableSection.tsx
|
|
2758
|
-
import { useId as useId2, useState as
|
|
2911
|
+
import { useId as useId2, useState as useState3 } from "react";
|
|
2759
2912
|
import { AnimatePresence, motion as motion2, useReducedMotion as useReducedMotion2 } from "motion/react";
|
|
2760
2913
|
import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2761
2914
|
var Chevron = ({ open }) => /* @__PURE__ */ jsx24(
|
|
@@ -2787,7 +2940,7 @@ var ExpandableSection = ({
|
|
|
2787
2940
|
}) => {
|
|
2788
2941
|
const reduceMotion = useReducedMotion2();
|
|
2789
2942
|
const panelId = useId2();
|
|
2790
|
-
const [internalOpen, setInternalOpen] =
|
|
2943
|
+
const [internalOpen, setInternalOpen] = useState3(defaultOpen);
|
|
2791
2944
|
const open = openProp ?? internalOpen;
|
|
2792
2945
|
const toggle = () => {
|
|
2793
2946
|
if (openProp == null) setInternalOpen((o) => !o);
|
|
@@ -3147,7 +3300,7 @@ var FieldRow = ({
|
|
|
3147
3300
|
};
|
|
3148
3301
|
|
|
3149
3302
|
// src/app/settings/FloatingUnsavedChangesBar.tsx
|
|
3150
|
-
import { useEffect as useEffect2, useState as
|
|
3303
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
3151
3304
|
import { createPortal } from "react-dom";
|
|
3152
3305
|
import { AnimatePresence as AnimatePresence2, motion as motion3, useReducedMotion as useReducedMotion3 } from "motion/react";
|
|
3153
3306
|
import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
@@ -3164,7 +3317,7 @@ var FloatingUnsavedChangesBar = ({
|
|
|
3164
3317
|
className
|
|
3165
3318
|
}) => {
|
|
3166
3319
|
const reduceMotion = useReducedMotion3();
|
|
3167
|
-
const [mounted, setMounted] =
|
|
3320
|
+
const [mounted, setMounted] = useState4(false);
|
|
3168
3321
|
useEffect2(() => setMounted(true), []);
|
|
3169
3322
|
if (!mounted || typeof document === "undefined") return null;
|
|
3170
3323
|
return createPortal(
|
|
@@ -3770,505 +3923,467 @@ var FilterField = ({
|
|
|
3770
3923
|
};
|
|
3771
3924
|
|
|
3772
3925
|
// src/app/data/FilterDropdown.tsx
|
|
3773
|
-
import {
|
|
3774
|
-
import {
|
|
3775
|
-
CalendarIcon,
|
|
3776
|
-
ChevronDownIcon as ChevronDownIcon2,
|
|
3777
|
-
ChevronRightIcon,
|
|
3778
|
-
CircleDollarSignIcon,
|
|
3779
|
-
ListFilterIcon,
|
|
3780
|
-
SearchIcon as SearchIcon2,
|
|
3781
|
-
TrendingUpIcon,
|
|
3782
|
-
UserIcon,
|
|
3783
|
-
WalletIcon
|
|
3784
|
-
} from "lucide-react";
|
|
3926
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useState as useState5 } from "react";
|
|
3927
|
+
import { ChevronRightIcon, ListFilterIcon, XIcon as XIcon2 } from "lucide-react";
|
|
3785
3928
|
import { jsx as jsx47, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
3786
|
-
var
|
|
3787
|
-
{ id: "
|
|
3788
|
-
{ id: "
|
|
3789
|
-
{ id: "
|
|
3929
|
+
var DEFAULT_PRESETS = [
|
|
3930
|
+
{ id: "last_7_days", label: "Last 7 days" },
|
|
3931
|
+
{ id: "last_30_days", label: "Last 30 days" },
|
|
3932
|
+
{ id: "last_90_days", label: "Last 90 days" },
|
|
3933
|
+
{ id: "this_month", label: "This month" },
|
|
3934
|
+
{ id: "this_year", label: "This year" },
|
|
3935
|
+
{ id: "custom", label: "Custom range" }
|
|
3790
3936
|
];
|
|
3791
|
-
var
|
|
3792
|
-
{ id: "
|
|
3793
|
-
{ id: "
|
|
3794
|
-
{ id: "
|
|
3937
|
+
var DEFAULT_OPERATORS = [
|
|
3938
|
+
{ id: "gt", label: "Greater than" },
|
|
3939
|
+
{ id: "lt", label: "Less than" },
|
|
3940
|
+
{ id: "eq", label: "Equals" }
|
|
3795
3941
|
];
|
|
3942
|
+
function asArray(v) {
|
|
3943
|
+
return Array.isArray(v) ? v : [];
|
|
3944
|
+
}
|
|
3945
|
+
function asText(v) {
|
|
3946
|
+
return typeof v === "string" ? v : "";
|
|
3947
|
+
}
|
|
3948
|
+
function asDate(v) {
|
|
3949
|
+
return v && !Array.isArray(v) && typeof v === "object" && "preset" in v ? v : { preset: null };
|
|
3950
|
+
}
|
|
3951
|
+
function asNumeric(v) {
|
|
3952
|
+
return v && !Array.isArray(v) && typeof v === "object" && "operator" in v ? v : { operator: "gt", value: "" };
|
|
3953
|
+
}
|
|
3954
|
+
var OPERATOR_SYMBOL = {
|
|
3955
|
+
gt: ">",
|
|
3956
|
+
lt: "<",
|
|
3957
|
+
eq: "="
|
|
3958
|
+
};
|
|
3796
3959
|
function FilterDropdown({
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3960
|
+
fields,
|
|
3961
|
+
value,
|
|
3962
|
+
defaultValue,
|
|
3963
|
+
onChange,
|
|
3964
|
+
label = "Filter",
|
|
3965
|
+
align = "start",
|
|
3966
|
+
showActiveChips = true,
|
|
3801
3967
|
className
|
|
3802
3968
|
}) {
|
|
3803
|
-
const [isOpen, setIsOpen] =
|
|
3804
|
-
const [
|
|
3805
|
-
const [isMobile, setIsMobile] =
|
|
3969
|
+
const [isOpen, setIsOpen] = useState5(false);
|
|
3970
|
+
const [activeId, setActiveId] = useState5(fields[0]?.id ?? null);
|
|
3971
|
+
const [isMobile, setIsMobile] = useState5(false);
|
|
3972
|
+
const isControlled = value !== void 0;
|
|
3973
|
+
const [internal, setInternal] = useState5(defaultValue ?? {});
|
|
3974
|
+
const values = isControlled ? value : internal;
|
|
3806
3975
|
useEffect3(() => {
|
|
3807
3976
|
const checkMobile = () => setIsMobile(window.innerWidth < 768);
|
|
3808
3977
|
checkMobile();
|
|
3809
3978
|
window.addEventListener("resize", checkMobile);
|
|
3810
3979
|
return () => window.removeEventListener("resize", checkMobile);
|
|
3811
3980
|
}, []);
|
|
3812
|
-
const [selectedContacts, setSelectedContacts] = useState4(
|
|
3813
|
-
filters?.contacts ?? initialFilters?.contacts ?? []
|
|
3814
|
-
);
|
|
3815
|
-
const [walletInput, setWalletInput] = useState4(filters?.walletAddress ?? initialFilters?.walletAddress ?? "");
|
|
3816
|
-
const [appliedWallet, setAppliedWallet] = useState4(filters?.walletAddress ?? initialFilters?.walletAddress ?? "");
|
|
3817
|
-
const [selectedDatePreset, setSelectedDatePreset] = useState4(
|
|
3818
|
-
filters?.lastInvoiceDate ?? initialFilters?.lastInvoiceDate ?? null
|
|
3819
|
-
);
|
|
3820
|
-
const [customDateFrom, setCustomDateFrom] = useState4(
|
|
3821
|
-
filters?.customDateRange?.from ?? initialFilters?.customDateRange?.from ?? ""
|
|
3822
|
-
);
|
|
3823
|
-
const [customDateTo, setCustomDateTo] = useState4(
|
|
3824
|
-
filters?.customDateRange?.to ?? initialFilters?.customDateRange?.to ?? ""
|
|
3825
|
-
);
|
|
3826
|
-
const [ltvOperator, setLtvOperator] = useState4(
|
|
3827
|
-
filters?.lifetimeValue?.operator ?? initialFilters?.lifetimeValue?.operator ?? "greater_than"
|
|
3828
|
-
);
|
|
3829
|
-
const [ltvValue, setLtvValue] = useState4(filters?.lifetimeValue?.value ?? initialFilters?.lifetimeValue?.value ?? "");
|
|
3830
|
-
const [isLtvOperatorOpen, setLtvOperatorOpen] = useState4(false);
|
|
3831
|
-
const [outstandingOperator, setOutstandingOperator] = useState4(
|
|
3832
|
-
filters?.outstanding?.operator ?? initialFilters?.outstanding?.operator ?? "greater_than"
|
|
3833
|
-
);
|
|
3834
|
-
const [outstandingValue, setOutstandingValue] = useState4(filters?.outstanding?.value ?? initialFilters?.outstanding?.value ?? "");
|
|
3835
|
-
const [isOutstandingOperatorOpen, setOutstandingOperatorOpen] = useState4(false);
|
|
3836
3981
|
useEffect3(() => {
|
|
3837
|
-
if (
|
|
3838
|
-
|
|
3839
|
-
setWalletInput(filters.walletAddress ?? "");
|
|
3840
|
-
setAppliedWallet(filters.walletAddress ?? "");
|
|
3841
|
-
setSelectedDatePreset(filters.lastInvoiceDate ?? null);
|
|
3842
|
-
setCustomDateFrom(filters.customDateRange?.from ?? "");
|
|
3843
|
-
setCustomDateTo(filters.customDateRange?.to ?? "");
|
|
3844
|
-
setLtvOperator(filters.lifetimeValue?.operator ?? "greater_than");
|
|
3845
|
-
setLtvValue(filters.lifetimeValue?.value ?? "");
|
|
3846
|
-
setOutstandingOperator(filters.outstanding?.operator ?? "greater_than");
|
|
3847
|
-
setOutstandingValue(filters.outstanding?.value ?? "");
|
|
3982
|
+
if (!fields.some((f) => f.id === activeId)) {
|
|
3983
|
+
setActiveId(fields[0]?.id ?? null);
|
|
3848
3984
|
}
|
|
3849
|
-
}, [
|
|
3850
|
-
const
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
const year = refDate.getFullYear();
|
|
3855
|
-
const month = refDate.getMonth();
|
|
3856
|
-
const lastMonthDate = new Date(year, month - 1, 1);
|
|
3857
|
-
const lastMonthLabel = lastMonthDate.toLocaleDateString("en-US", { month: "short", year: "numeric" });
|
|
3858
|
-
const thisMonthLabel = refDate.toLocaleDateString("en-US", { month: "short", year: "numeric" });
|
|
3859
|
-
const thisQuarter = Math.floor(month / 3) + 1;
|
|
3860
|
-
const thisQuarterLabel = `Q${thisQuarter} ${year}`;
|
|
3861
|
-
const lastQuarter = thisQuarter === 1 ? 4 : thisQuarter - 1;
|
|
3862
|
-
const lastQuarterYear = thisQuarter === 1 ? year - 1 : year;
|
|
3863
|
-
const lastQuarterLabel = `Q${lastQuarter} ${lastQuarterYear}`;
|
|
3864
|
-
const thisYearLabel = `${year}`;
|
|
3865
|
-
const last30 = new Date(refDate);
|
|
3866
|
-
last30.setDate(refDate.getDate() - 30);
|
|
3867
|
-
const formatDate = (d) => d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
3868
|
-
const last30Label = `${formatDate(last30)} - ${formatDate(refDate)}`;
|
|
3869
|
-
const last90 = new Date(refDate);
|
|
3870
|
-
last90.setDate(refDate.getDate() - 90);
|
|
3871
|
-
const last90Label = `${formatDate(last90)} - ${formatDate(refDate)}`;
|
|
3872
|
-
return [
|
|
3873
|
-
{ id: "last_month", label: "Last month", date: lastMonthLabel },
|
|
3874
|
-
{ id: "this_month", label: "This month", date: thisMonthLabel },
|
|
3875
|
-
{ id: "this_quarter", label: "This quarter", date: thisQuarterLabel },
|
|
3876
|
-
{ id: "last_quarter", label: "Last quarter", date: lastQuarterLabel },
|
|
3877
|
-
{ id: "this_year", label: "This year", date: thisYearLabel },
|
|
3878
|
-
{ id: "last_30_days", label: "Last 30 days", date: last30Label },
|
|
3879
|
-
{ id: "last_90_days", label: "Last 90 days", date: last90Label },
|
|
3880
|
-
{ id: "custom", label: "Custom range", date: "" }
|
|
3881
|
-
];
|
|
3882
|
-
}, [refDate]);
|
|
3883
|
-
const filteredContacts = useMemo2(() => {
|
|
3884
|
-
if (!contactSearch) return contacts;
|
|
3885
|
-
const query = contactSearch.toLowerCase();
|
|
3886
|
-
return contacts.filter(
|
|
3887
|
-
(c) => c.name.toLowerCase().includes(query) || c.email.toLowerCase().includes(query)
|
|
3888
|
-
);
|
|
3889
|
-
}, [contacts, contactSearch]);
|
|
3890
|
-
const handleContactToggle = (contactName) => {
|
|
3891
|
-
const next = selectedContacts.includes(contactName) ? selectedContacts.filter((name) => name !== contactName) : [...selectedContacts, contactName];
|
|
3892
|
-
setSelectedContacts(next);
|
|
3893
|
-
notifyChanges({ contacts: next });
|
|
3894
|
-
setIsOpen(false);
|
|
3895
|
-
};
|
|
3896
|
-
const handleWalletApply = () => {
|
|
3897
|
-
setAppliedWallet(walletInput);
|
|
3898
|
-
notifyChanges({ walletAddress: walletInput });
|
|
3899
|
-
setIsOpen(false);
|
|
3985
|
+
}, [fields, activeId]);
|
|
3986
|
+
const commit = (id, next) => {
|
|
3987
|
+
const merged = { ...values, [id]: next };
|
|
3988
|
+
if (!isControlled) setInternal(merged);
|
|
3989
|
+
onChange?.(merged);
|
|
3900
3990
|
};
|
|
3901
|
-
const
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
notifyChanges({ walletAddress: "" });
|
|
3905
|
-
setIsOpen(false);
|
|
3991
|
+
const clearAll = () => {
|
|
3992
|
+
if (!isControlled) setInternal({});
|
|
3993
|
+
onChange?.({});
|
|
3906
3994
|
};
|
|
3907
|
-
const
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3995
|
+
const activeIdx = fields.findIndex((f) => f.id === activeId);
|
|
3996
|
+
const activeField = activeIdx >= 0 ? fields[activeIdx] : void 0;
|
|
3997
|
+
const chips = [];
|
|
3998
|
+
for (const field of fields) {
|
|
3999
|
+
const v = values[field.id];
|
|
4000
|
+
if (field.type === "multiselect") {
|
|
4001
|
+
const selected = asArray(v);
|
|
4002
|
+
for (const optionValue of selected) {
|
|
4003
|
+
const opt = field.options?.find((o) => o.value === optionValue);
|
|
4004
|
+
chips.push({
|
|
4005
|
+
id: `${field.id}:${optionValue}`,
|
|
4006
|
+
label: `${field.label}: ${opt?.label ?? optionValue}`,
|
|
4007
|
+
remove: () => commit(field.id, selected.filter((x) => x !== optionValue))
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
} else if (field.type === "text") {
|
|
4011
|
+
const text = asText(v);
|
|
4012
|
+
if (text) {
|
|
4013
|
+
chips.push({
|
|
4014
|
+
id: field.id,
|
|
4015
|
+
label: `${field.label}: ${text}`,
|
|
4016
|
+
remove: () => commit(field.id, "")
|
|
4017
|
+
});
|
|
4018
|
+
}
|
|
4019
|
+
} else if (field.type === "numeric") {
|
|
4020
|
+
const n = asNumeric(v);
|
|
4021
|
+
if (n.value) {
|
|
4022
|
+
chips.push({
|
|
4023
|
+
id: field.id,
|
|
4024
|
+
label: `${field.label} ${OPERATOR_SYMBOL[n.operator]} ${n.value}`,
|
|
4025
|
+
remove: () => commit(field.id, null)
|
|
4026
|
+
});
|
|
4027
|
+
}
|
|
4028
|
+
} else if (field.type === "daterange") {
|
|
4029
|
+
const d = asDate(v);
|
|
4030
|
+
if (d.preset) {
|
|
4031
|
+
const presetLabel = d.preset === "custom" ? `${d.from || "\u2026"} \u2013 ${d.to || "\u2026"}` : (field.presets ?? DEFAULT_PRESETS).find((p) => p.id === d.preset)?.label ?? d.preset;
|
|
4032
|
+
chips.push({
|
|
4033
|
+
id: field.id,
|
|
4034
|
+
label: `${field.label}: ${presetLabel}`,
|
|
4035
|
+
remove: () => commit(field.id, { preset: null })
|
|
4036
|
+
});
|
|
4037
|
+
}
|
|
3912
4038
|
}
|
|
3913
|
-
}
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
4039
|
+
}
|
|
4040
|
+
return /* @__PURE__ */ jsxs36("div", { className: cn("flex flex-wrap items-center gap-2", className), children: [
|
|
4041
|
+
/* @__PURE__ */ jsxs36(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [
|
|
4042
|
+
/* @__PURE__ */ jsx47(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx47(
|
|
4043
|
+
Button,
|
|
4044
|
+
{
|
|
4045
|
+
variant: "outline",
|
|
4046
|
+
size: "sm",
|
|
4047
|
+
className: "border-dashed font-medium text-muted-foreground hover:text-foreground",
|
|
4048
|
+
iconLeading: /* @__PURE__ */ jsx47(ListFilterIcon, { className: "size-4" }),
|
|
4049
|
+
children: label
|
|
4050
|
+
}
|
|
4051
|
+
) }),
|
|
4052
|
+
/* @__PURE__ */ jsx47(
|
|
4053
|
+
PopoverContent,
|
|
4054
|
+
{
|
|
4055
|
+
variant: "list",
|
|
4056
|
+
align,
|
|
4057
|
+
className: "overflow-visible border-none bg-transparent p-0 shadow-none max-w-[calc(100vw-32px)] md:max-w-none",
|
|
4058
|
+
children: /* @__PURE__ */ jsxs36("div", { className: "relative flex flex-col md:flex-row items-stretch md:items-start w-[calc(100vw-32px)] max-w-[340px] md:w-auto md:max-w-none", children: [
|
|
4059
|
+
/* @__PURE__ */ jsx47("div", { className: "w-full md:w-56 rounded-xl border border-border bg-popover p-1.5 shadow-lg", children: fields.map((field) => {
|
|
4060
|
+
const isActive = activeId === field.id;
|
|
4061
|
+
return /* @__PURE__ */ jsxs36(
|
|
4062
|
+
"button",
|
|
4063
|
+
{
|
|
4064
|
+
type: "button",
|
|
4065
|
+
className: cn(
|
|
4066
|
+
"flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm text-left transition-colors outline-none",
|
|
4067
|
+
isActive ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
4068
|
+
),
|
|
4069
|
+
onMouseEnter: () => !isMobile && setActiveId(field.id),
|
|
4070
|
+
onClick: () => setActiveId(field.id),
|
|
4071
|
+
children: [
|
|
4072
|
+
/* @__PURE__ */ jsxs36("span", { className: "flex items-center gap-2", children: [
|
|
4073
|
+
field.icon,
|
|
4074
|
+
/* @__PURE__ */ jsx47("span", { children: field.label })
|
|
4075
|
+
] }),
|
|
4076
|
+
/* @__PURE__ */ jsx47(ChevronRightIcon, { className: "size-4 text-muted-foreground/50" })
|
|
4077
|
+
]
|
|
4078
|
+
},
|
|
4079
|
+
field.id
|
|
4080
|
+
);
|
|
4081
|
+
}) }),
|
|
4082
|
+
activeField && /* @__PURE__ */ jsx47(
|
|
4083
|
+
"div",
|
|
4084
|
+
{
|
|
4085
|
+
className: "relative left-0 mt-2 w-full md:absolute md:left-[calc(100%+6px)] md:w-80 rounded-xl border border-border bg-popover p-3 shadow-lg transition-all duration-150 md:mt-0",
|
|
4086
|
+
style: isMobile ? {} : { top: `${activeIdx * 36 + 6}px` },
|
|
4087
|
+
children: /* @__PURE__ */ jsx47(
|
|
4088
|
+
FilterFieldControl,
|
|
4089
|
+
{
|
|
4090
|
+
field: activeField,
|
|
4091
|
+
value: values[activeField.id],
|
|
4092
|
+
onChange: (next) => commit(activeField.id, next),
|
|
4093
|
+
onClose: () => setIsOpen(false)
|
|
4094
|
+
}
|
|
4095
|
+
)
|
|
4096
|
+
}
|
|
4097
|
+
)
|
|
4098
|
+
] })
|
|
4099
|
+
}
|
|
4100
|
+
)
|
|
4101
|
+
] }),
|
|
4102
|
+
showActiveChips && chips.map((chip) => /* @__PURE__ */ jsx47(FilterChip, { label: chip.label, onRemove: chip.remove }, chip.id)),
|
|
4103
|
+
showActiveChips && chips.length > 0 && /* @__PURE__ */ jsx47(
|
|
4104
|
+
"button",
|
|
3966
4105
|
{
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
iconLeading: /* @__PURE__ */ jsx47(ListFilterIcon, { className: "size-4" }),
|
|
3972
|
-
children: "Filter"
|
|
4106
|
+
type: "button",
|
|
4107
|
+
onClick: clearAll,
|
|
4108
|
+
className: "rounded-full px-3 py-1 text-sm font-medium text-muted-foreground outline-none transition-colors hover:text-foreground",
|
|
4109
|
+
children: "Clear all"
|
|
3973
4110
|
}
|
|
3974
|
-
)
|
|
4111
|
+
)
|
|
4112
|
+
] });
|
|
4113
|
+
}
|
|
4114
|
+
function FilterChip({ label, onRemove }) {
|
|
4115
|
+
return /* @__PURE__ */ jsxs36("span", { className: "inline-flex h-9 items-center gap-1.5 rounded-full border border-border bg-muted/40 pl-3 pr-1.5 text-sm font-medium text-foreground", children: [
|
|
4116
|
+
/* @__PURE__ */ jsx47("span", { className: "truncate", children: label }),
|
|
3975
4117
|
/* @__PURE__ */ jsx47(
|
|
3976
|
-
|
|
4118
|
+
"button",
|
|
3977
4119
|
{
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4120
|
+
type: "button",
|
|
4121
|
+
onClick: onRemove,
|
|
4122
|
+
"aria-label": `Remove ${label}`,
|
|
4123
|
+
className: "flex size-5 items-center justify-center rounded-full text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground",
|
|
4124
|
+
children: /* @__PURE__ */ jsx47(XIcon2, { className: "size-3.5" })
|
|
4125
|
+
}
|
|
4126
|
+
)
|
|
4127
|
+
] });
|
|
4128
|
+
}
|
|
4129
|
+
function FilterFieldControl({
|
|
4130
|
+
field,
|
|
4131
|
+
value,
|
|
4132
|
+
onChange,
|
|
4133
|
+
onClose
|
|
4134
|
+
}) {
|
|
4135
|
+
switch (field.type) {
|
|
4136
|
+
case "multiselect":
|
|
4137
|
+
return /* @__PURE__ */ jsx47(MultiSelectControl, { field, value: asArray(value), onChange });
|
|
4138
|
+
case "text":
|
|
4139
|
+
return /* @__PURE__ */ jsx47(TextControl, { field, value: asText(value), onChange, onClose });
|
|
4140
|
+
case "daterange":
|
|
4141
|
+
return /* @__PURE__ */ jsx47(DateRangeControl, { field, value: asDate(value), onChange, onClose });
|
|
4142
|
+
case "numeric":
|
|
4143
|
+
return /* @__PURE__ */ jsx47(NumericControl, { field, value: asNumeric(value), onChange, onClose });
|
|
4144
|
+
default:
|
|
4145
|
+
return null;
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
function MultiSelectControl({
|
|
4149
|
+
field,
|
|
4150
|
+
value,
|
|
4151
|
+
onChange
|
|
4152
|
+
}) {
|
|
4153
|
+
const options = field.options ?? [];
|
|
4154
|
+
const [search, setSearch] = useState5("");
|
|
4155
|
+
const searchable = field.searchable ?? options.length > 8;
|
|
4156
|
+
const filtered = useMemo2(() => {
|
|
4157
|
+
if (!search) return options;
|
|
4158
|
+
const q = search.toLowerCase();
|
|
4159
|
+
return options.filter(
|
|
4160
|
+
(o) => o.label.toLowerCase().includes(q) || o.hint?.toLowerCase().includes(q)
|
|
4161
|
+
);
|
|
4162
|
+
}, [options, search]);
|
|
4163
|
+
const toggle = (optionValue) => {
|
|
4164
|
+
onChange(
|
|
4165
|
+
value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue]
|
|
4166
|
+
);
|
|
4167
|
+
};
|
|
4168
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4169
|
+
searchable && /* @__PURE__ */ jsx47(
|
|
4170
|
+
SearchInput,
|
|
4171
|
+
{
|
|
4172
|
+
placeholder: field.searchPlaceholder ?? "Search\u2026",
|
|
4173
|
+
value: search,
|
|
4174
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4175
|
+
className: "w-full min-w-0"
|
|
4176
|
+
}
|
|
4177
|
+
),
|
|
4178
|
+
/* @__PURE__ */ jsx47("div", { className: "flex max-h-48 flex-col gap-1 overflow-y-auto pr-1", children: filtered.length === 0 ? /* @__PURE__ */ jsx47("p", { className: "py-4 text-center text-xs text-muted-foreground", children: "No options found" }) : filtered.map((option) => /* @__PURE__ */ jsxs36(
|
|
4179
|
+
"label",
|
|
4180
|
+
{
|
|
4181
|
+
className: "flex cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1.5 transition-colors hover:bg-muted/50",
|
|
4182
|
+
children: [
|
|
4183
|
+
/* @__PURE__ */ jsx47(
|
|
4184
|
+
Checkbox,
|
|
4007
4185
|
{
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
children: [
|
|
4011
|
-
activeMenu === "contact" && /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4012
|
-
/* @__PURE__ */ jsxs36("div", { className: "relative flex items-center", children: [
|
|
4013
|
-
/* @__PURE__ */ jsx47(SearchIcon2, { className: "absolute left-2.5 size-4 text-muted-foreground/60" }),
|
|
4014
|
-
/* @__PURE__ */ jsx47(
|
|
4015
|
-
"input",
|
|
4016
|
-
{
|
|
4017
|
-
type: "text",
|
|
4018
|
-
placeholder: "Search by name or email...",
|
|
4019
|
-
value: contactSearch,
|
|
4020
|
-
onChange: (e) => setContactSearch(e.target.value),
|
|
4021
|
-
className: "w-full rounded-lg border border-border bg-background py-1.5 pl-8 pr-3 text-sm outline-none placeholder:text-muted-foreground/60 focus:border-border"
|
|
4022
|
-
}
|
|
4023
|
-
)
|
|
4024
|
-
] }),
|
|
4025
|
-
/* @__PURE__ */ jsx47("div", { className: "max-h-48 overflow-y-auto flex flex-col gap-1 pr-1", children: filteredContacts.length === 0 ? /* @__PURE__ */ jsx47("p", { className: "py-4 text-center text-xs text-muted-foreground", children: "No contacts found" }) : filteredContacts.map((contact) => {
|
|
4026
|
-
const isChecked = selectedContacts.includes(contact.name);
|
|
4027
|
-
return /* @__PURE__ */ jsxs36(
|
|
4028
|
-
"label",
|
|
4029
|
-
{
|
|
4030
|
-
className: "flex cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1.5 hover:bg-muted/50 transition-colors",
|
|
4031
|
-
children: [
|
|
4032
|
-
/* @__PURE__ */ jsx47(
|
|
4033
|
-
Checkbox,
|
|
4034
|
-
{
|
|
4035
|
-
checked: isChecked,
|
|
4036
|
-
onCheckedChange: () => handleContactToggle(contact.name)
|
|
4037
|
-
}
|
|
4038
|
-
),
|
|
4039
|
-
/* @__PURE__ */ jsx47(Avatar, { variant: "secondary", children: /* @__PURE__ */ jsx47(AvatarFallback, { children: contact.initials }) }),
|
|
4040
|
-
/* @__PURE__ */ jsx47("span", { className: "text-sm font-medium text-foreground", children: contact.name })
|
|
4041
|
-
]
|
|
4042
|
-
},
|
|
4043
|
-
contact.id
|
|
4044
|
-
);
|
|
4045
|
-
}) })
|
|
4046
|
-
] }),
|
|
4047
|
-
activeMenu === "wallet" && /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4048
|
-
/* @__PURE__ */ jsx47("div", { className: "relative flex items-center", children: /* @__PURE__ */ jsx47(
|
|
4049
|
-
"input",
|
|
4050
|
-
{
|
|
4051
|
-
type: "text",
|
|
4052
|
-
placeholder: "Search by wallet...",
|
|
4053
|
-
value: walletInput,
|
|
4054
|
-
onChange: (e) => setWalletInput(e.target.value),
|
|
4055
|
-
className: "w-full rounded-lg border border-border bg-background px-3 py-1.5 text-sm outline-none placeholder:text-muted-foreground/60"
|
|
4056
|
-
}
|
|
4057
|
-
) }),
|
|
4058
|
-
/* @__PURE__ */ jsxs36("div", { className: "flex items-center justify-end gap-2 pt-1 border-t border-border/40", children: [
|
|
4059
|
-
/* @__PURE__ */ jsx47(
|
|
4060
|
-
Button,
|
|
4061
|
-
{
|
|
4062
|
-
variant: "ghost",
|
|
4063
|
-
size: "sm",
|
|
4064
|
-
onClick: handleWalletClear,
|
|
4065
|
-
className: "text-muted-foreground hover:text-foreground h-8 px-3",
|
|
4066
|
-
children: "Clear"
|
|
4067
|
-
}
|
|
4068
|
-
),
|
|
4069
|
-
/* @__PURE__ */ jsx47(
|
|
4070
|
-
Button,
|
|
4071
|
-
{
|
|
4072
|
-
color: "primary",
|
|
4073
|
-
size: "sm",
|
|
4074
|
-
onClick: handleWalletApply,
|
|
4075
|
-
className: "h-8 px-3",
|
|
4076
|
-
children: "Apply"
|
|
4077
|
-
}
|
|
4078
|
-
)
|
|
4079
|
-
] })
|
|
4080
|
-
] }),
|
|
4081
|
-
activeMenu === "date" && /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-1", children: [
|
|
4082
|
-
presets.map((preset) => {
|
|
4083
|
-
const isSelected = selectedDatePreset === preset.id;
|
|
4084
|
-
return /* @__PURE__ */ jsxs36(
|
|
4085
|
-
"button",
|
|
4086
|
-
{
|
|
4087
|
-
type: "button",
|
|
4088
|
-
onClick: () => handleDatePresetSelect(preset.id),
|
|
4089
|
-
className: cn(
|
|
4090
|
-
"flex w-full items-center justify-between rounded-lg px-2.5 py-1.5 text-sm text-left transition-colors outline-none",
|
|
4091
|
-
isSelected ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
4092
|
-
),
|
|
4093
|
-
children: [
|
|
4094
|
-
/* @__PURE__ */ jsx47("span", { children: preset.label }),
|
|
4095
|
-
preset.date && /* @__PURE__ */ jsx47("span", { className: "text-xs text-muted-foreground/70", children: preset.date })
|
|
4096
|
-
]
|
|
4097
|
-
},
|
|
4098
|
-
preset.id
|
|
4099
|
-
);
|
|
4100
|
-
}),
|
|
4101
|
-
selectedDatePreset === "custom" && /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2 mt-2 pt-2 border-t border-border/40", children: [
|
|
4102
|
-
/* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2", children: [
|
|
4103
|
-
/* @__PURE__ */ jsx47(
|
|
4104
|
-
"input",
|
|
4105
|
-
{
|
|
4106
|
-
type: "date",
|
|
4107
|
-
value: customDateFrom,
|
|
4108
|
-
onChange: (e) => setCustomDateFrom(e.target.value),
|
|
4109
|
-
className: "w-full rounded-lg border border-border bg-background px-2 py-1 text-xs outline-none"
|
|
4110
|
-
}
|
|
4111
|
-
),
|
|
4112
|
-
/* @__PURE__ */ jsx47("span", { className: "text-xs text-muted-foreground", children: "to" }),
|
|
4113
|
-
/* @__PURE__ */ jsx47(
|
|
4114
|
-
"input",
|
|
4115
|
-
{
|
|
4116
|
-
type: "date",
|
|
4117
|
-
value: customDateTo,
|
|
4118
|
-
onChange: (e) => setCustomDateTo(e.target.value),
|
|
4119
|
-
className: "w-full rounded-lg border border-border bg-background px-2 py-1 text-xs outline-none"
|
|
4120
|
-
}
|
|
4121
|
-
)
|
|
4122
|
-
] }),
|
|
4123
|
-
/* @__PURE__ */ jsx47("div", { className: "flex justify-end gap-2", children: /* @__PURE__ */ jsx47(
|
|
4124
|
-
Button,
|
|
4125
|
-
{
|
|
4126
|
-
color: "primary",
|
|
4127
|
-
size: "sm",
|
|
4128
|
-
onClick: handleCustomDateApply,
|
|
4129
|
-
className: "h-7 text-xs px-2.5",
|
|
4130
|
-
children: "Apply"
|
|
4131
|
-
}
|
|
4132
|
-
) })
|
|
4133
|
-
] })
|
|
4134
|
-
] }),
|
|
4135
|
-
activeMenu === "ltv" && /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4136
|
-
/* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2", children: [
|
|
4137
|
-
/* @__PURE__ */ jsxs36("div", { className: "relative shrink-0", children: [
|
|
4138
|
-
/* @__PURE__ */ jsxs36(
|
|
4139
|
-
"button",
|
|
4140
|
-
{
|
|
4141
|
-
type: "button",
|
|
4142
|
-
onClick: () => setLtvOperatorOpen(!isLtvOperatorOpen),
|
|
4143
|
-
className: "flex h-9 items-center gap-1 rounded-lg border border-border bg-background px-2.5 text-xs font-normal text-muted-foreground hover:bg-muted/50 hover:text-foreground outline-none whitespace-nowrap",
|
|
4144
|
-
children: [
|
|
4145
|
-
/* @__PURE__ */ jsx47("span", { children: OPERATORS.find((op) => op.id === ltvOperator)?.label.replace("...", "") }),
|
|
4146
|
-
/* @__PURE__ */ jsx47(ChevronDownIcon2, { className: "size-3" })
|
|
4147
|
-
]
|
|
4148
|
-
}
|
|
4149
|
-
),
|
|
4150
|
-
isLtvOperatorOpen && /* @__PURE__ */ jsx47("div", { className: "absolute left-0 top-full z-50 mt-1 w-32 rounded-lg border border-border bg-popover p-1 shadow-md", children: OPERATORS.map((op) => /* @__PURE__ */ jsx47(
|
|
4151
|
-
"button",
|
|
4152
|
-
{
|
|
4153
|
-
type: "button",
|
|
4154
|
-
onClick: () => {
|
|
4155
|
-
setLtvOperator(op.id);
|
|
4156
|
-
setLtvOperatorOpen(false);
|
|
4157
|
-
},
|
|
4158
|
-
className: "w-full rounded-md px-2 py-1 text-left text-xs text-foreground hover:bg-muted outline-none",
|
|
4159
|
-
children: op.label
|
|
4160
|
-
},
|
|
4161
|
-
op.id
|
|
4162
|
-
)) })
|
|
4163
|
-
] }),
|
|
4164
|
-
/* @__PURE__ */ jsx47(
|
|
4165
|
-
"input",
|
|
4166
|
-
{
|
|
4167
|
-
type: "text",
|
|
4168
|
-
placeholder: "0.00",
|
|
4169
|
-
value: ltvValue,
|
|
4170
|
-
onChange: (e) => setLtvValue(e.target.value),
|
|
4171
|
-
className: "h-9 flex-1 min-w-0 rounded-lg border border-border bg-background px-3 py-1 text-sm outline-none placeholder:text-muted-foreground/60"
|
|
4172
|
-
}
|
|
4173
|
-
)
|
|
4174
|
-
] }),
|
|
4175
|
-
/* @__PURE__ */ jsxs36("div", { className: "flex items-center justify-end gap-2 pt-1 border-t border-border/40", children: [
|
|
4176
|
-
/* @__PURE__ */ jsx47(
|
|
4177
|
-
Button,
|
|
4178
|
-
{
|
|
4179
|
-
variant: "ghost",
|
|
4180
|
-
size: "sm",
|
|
4181
|
-
onClick: handleLtvClear,
|
|
4182
|
-
className: "text-muted-foreground hover:text-foreground h-8 px-3",
|
|
4183
|
-
children: "Clear"
|
|
4184
|
-
}
|
|
4185
|
-
),
|
|
4186
|
-
/* @__PURE__ */ jsx47(
|
|
4187
|
-
Button,
|
|
4188
|
-
{
|
|
4189
|
-
color: "primary",
|
|
4190
|
-
size: "sm",
|
|
4191
|
-
onClick: handleLtvApply,
|
|
4192
|
-
className: "h-8 px-3",
|
|
4193
|
-
children: "Apply"
|
|
4194
|
-
}
|
|
4195
|
-
)
|
|
4196
|
-
] })
|
|
4197
|
-
] }),
|
|
4198
|
-
activeMenu === "outstanding" && /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4199
|
-
/* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2", children: [
|
|
4200
|
-
/* @__PURE__ */ jsxs36("div", { className: "relative shrink-0", children: [
|
|
4201
|
-
/* @__PURE__ */ jsxs36(
|
|
4202
|
-
"button",
|
|
4203
|
-
{
|
|
4204
|
-
type: "button",
|
|
4205
|
-
onClick: () => setOutstandingOperatorOpen(!isOutstandingOperatorOpen),
|
|
4206
|
-
className: "flex h-9 items-center gap-1 rounded-lg border border-border bg-background px-2.5 text-xs font-normal text-muted-foreground hover:bg-muted/50 hover:text-foreground outline-none whitespace-nowrap",
|
|
4207
|
-
children: [
|
|
4208
|
-
/* @__PURE__ */ jsx47("span", { children: OPERATORS.find((op) => op.id === outstandingOperator)?.label.replace("...", "") }),
|
|
4209
|
-
/* @__PURE__ */ jsx47(ChevronDownIcon2, { className: "size-3" })
|
|
4210
|
-
]
|
|
4211
|
-
}
|
|
4212
|
-
),
|
|
4213
|
-
isOutstandingOperatorOpen && /* @__PURE__ */ jsx47("div", { className: "absolute left-0 top-full z-50 mt-1 w-32 rounded-lg border border-border bg-popover p-1 shadow-md", children: OPERATORS.map((op) => /* @__PURE__ */ jsx47(
|
|
4214
|
-
"button",
|
|
4215
|
-
{
|
|
4216
|
-
type: "button",
|
|
4217
|
-
onClick: () => {
|
|
4218
|
-
setOutstandingOperator(op.id);
|
|
4219
|
-
setOutstandingOperatorOpen(false);
|
|
4220
|
-
},
|
|
4221
|
-
className: "w-full rounded-md px-2 py-1 text-left text-xs text-foreground hover:bg-muted outline-none",
|
|
4222
|
-
children: op.label
|
|
4223
|
-
},
|
|
4224
|
-
op.id
|
|
4225
|
-
)) })
|
|
4226
|
-
] }),
|
|
4227
|
-
/* @__PURE__ */ jsx47(
|
|
4228
|
-
"input",
|
|
4229
|
-
{
|
|
4230
|
-
type: "text",
|
|
4231
|
-
placeholder: "0.00",
|
|
4232
|
-
value: outstandingValue,
|
|
4233
|
-
onChange: (e) => setOutstandingValue(e.target.value),
|
|
4234
|
-
className: "h-9 flex-1 min-w-0 rounded-lg border border-border bg-background px-3 py-1 text-sm outline-none placeholder:text-muted-foreground/60"
|
|
4235
|
-
}
|
|
4236
|
-
)
|
|
4237
|
-
] }),
|
|
4238
|
-
/* @__PURE__ */ jsxs36("div", { className: "flex items-center justify-end gap-2 pt-1 border-t border-border/40", children: [
|
|
4239
|
-
/* @__PURE__ */ jsx47(
|
|
4240
|
-
Button,
|
|
4241
|
-
{
|
|
4242
|
-
variant: "ghost",
|
|
4243
|
-
size: "sm",
|
|
4244
|
-
onClick: handleOutstandingClear,
|
|
4245
|
-
className: "text-muted-foreground hover:text-foreground h-8 px-3",
|
|
4246
|
-
children: "Clear"
|
|
4247
|
-
}
|
|
4248
|
-
),
|
|
4249
|
-
/* @__PURE__ */ jsx47(
|
|
4250
|
-
Button,
|
|
4251
|
-
{
|
|
4252
|
-
color: "primary",
|
|
4253
|
-
size: "sm",
|
|
4254
|
-
onClick: handleOutstandingApply,
|
|
4255
|
-
className: "h-8 px-3",
|
|
4256
|
-
children: "Apply"
|
|
4257
|
-
}
|
|
4258
|
-
)
|
|
4259
|
-
] })
|
|
4260
|
-
] })
|
|
4261
|
-
]
|
|
4186
|
+
checked: value.includes(option.value),
|
|
4187
|
+
onCheckedChange: () => toggle(option.value)
|
|
4262
4188
|
}
|
|
4263
|
-
)
|
|
4264
|
-
|
|
4189
|
+
),
|
|
4190
|
+
option.icon,
|
|
4191
|
+
/* @__PURE__ */ jsxs36("span", { className: "flex min-w-0 flex-col", children: [
|
|
4192
|
+
/* @__PURE__ */ jsx47("span", { className: "truncate text-sm font-medium text-foreground", children: option.label }),
|
|
4193
|
+
option.hint && /* @__PURE__ */ jsx47("span", { className: "truncate text-xs text-muted-foreground", children: option.hint })
|
|
4194
|
+
] })
|
|
4195
|
+
]
|
|
4196
|
+
},
|
|
4197
|
+
option.value
|
|
4198
|
+
)) })
|
|
4199
|
+
] });
|
|
4200
|
+
}
|
|
4201
|
+
function TextControl({
|
|
4202
|
+
field,
|
|
4203
|
+
value,
|
|
4204
|
+
onChange,
|
|
4205
|
+
onClose
|
|
4206
|
+
}) {
|
|
4207
|
+
const [draft, setDraft] = useState5(value);
|
|
4208
|
+
useEffect3(() => setDraft(value), [value]);
|
|
4209
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4210
|
+
/* @__PURE__ */ jsx47(
|
|
4211
|
+
"input",
|
|
4212
|
+
{
|
|
4213
|
+
type: "text",
|
|
4214
|
+
placeholder: field.placeholder ?? "Type a value\u2026",
|
|
4215
|
+
value: draft,
|
|
4216
|
+
onChange: (e) => setDraft(e.target.value),
|
|
4217
|
+
onKeyDown: (e) => {
|
|
4218
|
+
if (e.key === "Enter") {
|
|
4219
|
+
onChange(draft);
|
|
4220
|
+
onClose();
|
|
4221
|
+
}
|
|
4222
|
+
},
|
|
4223
|
+
className: controlClass({ size: "sm" }, "w-full")
|
|
4224
|
+
}
|
|
4225
|
+
),
|
|
4226
|
+
/* @__PURE__ */ jsx47(
|
|
4227
|
+
ApplyClear,
|
|
4228
|
+
{
|
|
4229
|
+
onClear: () => {
|
|
4230
|
+
setDraft("");
|
|
4231
|
+
onChange("");
|
|
4232
|
+
onClose();
|
|
4233
|
+
},
|
|
4234
|
+
onApply: () => {
|
|
4235
|
+
onChange(draft);
|
|
4236
|
+
onClose();
|
|
4237
|
+
}
|
|
4265
4238
|
}
|
|
4266
4239
|
)
|
|
4267
|
-
] })
|
|
4240
|
+
] });
|
|
4241
|
+
}
|
|
4242
|
+
function DateRangeControl({
|
|
4243
|
+
field,
|
|
4244
|
+
value,
|
|
4245
|
+
onChange,
|
|
4246
|
+
onClose
|
|
4247
|
+
}) {
|
|
4248
|
+
const presets = field.presets ?? DEFAULT_PRESETS;
|
|
4249
|
+
const [from, setFrom] = useState5(value.from ?? "");
|
|
4250
|
+
const [to, setTo] = useState5(value.to ?? "");
|
|
4251
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-1", children: [
|
|
4252
|
+
presets.map((preset) => {
|
|
4253
|
+
const isSelected = value.preset === preset.id;
|
|
4254
|
+
return /* @__PURE__ */ jsxs36(
|
|
4255
|
+
"button",
|
|
4256
|
+
{
|
|
4257
|
+
type: "button",
|
|
4258
|
+
onClick: () => {
|
|
4259
|
+
if (preset.id === "custom") {
|
|
4260
|
+
onChange({ preset: "custom", from, to });
|
|
4261
|
+
} else {
|
|
4262
|
+
onChange({ preset: preset.id });
|
|
4263
|
+
onClose();
|
|
4264
|
+
}
|
|
4265
|
+
},
|
|
4266
|
+
className: cn(
|
|
4267
|
+
"flex w-full items-center justify-between rounded-lg px-2.5 py-1.5 text-left text-sm transition-colors outline-none",
|
|
4268
|
+
isSelected ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
4269
|
+
),
|
|
4270
|
+
children: [
|
|
4271
|
+
/* @__PURE__ */ jsx47("span", { children: preset.label }),
|
|
4272
|
+
preset.hint && /* @__PURE__ */ jsx47("span", { className: "text-xs text-muted-foreground/70", children: preset.hint })
|
|
4273
|
+
]
|
|
4274
|
+
},
|
|
4275
|
+
preset.id
|
|
4276
|
+
);
|
|
4277
|
+
}),
|
|
4278
|
+
value.preset === "custom" && /* @__PURE__ */ jsxs36("div", { className: "mt-2 flex flex-col gap-2 border-t border-border/40 pt-2", children: [
|
|
4279
|
+
/* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2", children: [
|
|
4280
|
+
/* @__PURE__ */ jsx47(
|
|
4281
|
+
"input",
|
|
4282
|
+
{
|
|
4283
|
+
type: "date",
|
|
4284
|
+
value: from,
|
|
4285
|
+
onChange: (e) => setFrom(e.target.value),
|
|
4286
|
+
className: controlClass({ size: "sm" }, "w-full text-xs")
|
|
4287
|
+
}
|
|
4288
|
+
),
|
|
4289
|
+
/* @__PURE__ */ jsx47("span", { className: "text-xs text-muted-foreground", children: "to" }),
|
|
4290
|
+
/* @__PURE__ */ jsx47(
|
|
4291
|
+
"input",
|
|
4292
|
+
{
|
|
4293
|
+
type: "date",
|
|
4294
|
+
value: to,
|
|
4295
|
+
onChange: (e) => setTo(e.target.value),
|
|
4296
|
+
className: controlClass({ size: "sm" }, "w-full text-xs")
|
|
4297
|
+
}
|
|
4298
|
+
)
|
|
4299
|
+
] }),
|
|
4300
|
+
/* @__PURE__ */ jsx47("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx47(
|
|
4301
|
+
Button,
|
|
4302
|
+
{
|
|
4303
|
+
color: "primary",
|
|
4304
|
+
size: "sm",
|
|
4305
|
+
className: "h-8 px-3",
|
|
4306
|
+
onClick: () => {
|
|
4307
|
+
onChange({ preset: "custom", from, to });
|
|
4308
|
+
onClose();
|
|
4309
|
+
},
|
|
4310
|
+
children: "Apply"
|
|
4311
|
+
}
|
|
4312
|
+
) })
|
|
4313
|
+
] })
|
|
4314
|
+
] });
|
|
4315
|
+
}
|
|
4316
|
+
function NumericControl({
|
|
4317
|
+
field,
|
|
4318
|
+
value,
|
|
4319
|
+
onChange,
|
|
4320
|
+
onClose
|
|
4321
|
+
}) {
|
|
4322
|
+
const operators = field.operators ?? DEFAULT_OPERATORS;
|
|
4323
|
+
const [operator, setOperator] = useState5(value.operator);
|
|
4324
|
+
const [draft, setDraft] = useState5(value.value);
|
|
4325
|
+
useEffect3(() => {
|
|
4326
|
+
setOperator(value.operator);
|
|
4327
|
+
setDraft(value.value);
|
|
4328
|
+
}, [value.operator, value.value]);
|
|
4329
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4330
|
+
/* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2", children: [
|
|
4331
|
+
/* @__PURE__ */ jsxs36(Select, { value: operator, onValueChange: (v) => setOperator(v), children: [
|
|
4332
|
+
/* @__PURE__ */ jsx47(SelectTrigger, { size: "sm", className: "shrink-0", children: /* @__PURE__ */ jsx47(SelectValue, {}) }),
|
|
4333
|
+
/* @__PURE__ */ jsx47(SelectContent, { children: operators.map((op) => /* @__PURE__ */ jsx47(SelectItem, { value: op.id, children: op.label }, op.id)) })
|
|
4334
|
+
] }),
|
|
4335
|
+
/* @__PURE__ */ jsx47(
|
|
4336
|
+
"input",
|
|
4337
|
+
{
|
|
4338
|
+
type: "text",
|
|
4339
|
+
inputMode: "decimal",
|
|
4340
|
+
placeholder: field.placeholder ?? "0.00",
|
|
4341
|
+
value: draft,
|
|
4342
|
+
onChange: (e) => setDraft(e.target.value),
|
|
4343
|
+
onKeyDown: (e) => {
|
|
4344
|
+
if (e.key === "Enter") {
|
|
4345
|
+
onChange(draft ? { operator, value: draft } : null);
|
|
4346
|
+
onClose();
|
|
4347
|
+
}
|
|
4348
|
+
},
|
|
4349
|
+
className: controlClass({ size: "sm" }, "min-w-0 flex-1")
|
|
4350
|
+
}
|
|
4351
|
+
)
|
|
4352
|
+
] }),
|
|
4353
|
+
/* @__PURE__ */ jsx47(
|
|
4354
|
+
ApplyClear,
|
|
4355
|
+
{
|
|
4356
|
+
onClear: () => {
|
|
4357
|
+
setDraft("");
|
|
4358
|
+
onChange(null);
|
|
4359
|
+
onClose();
|
|
4360
|
+
},
|
|
4361
|
+
onApply: () => {
|
|
4362
|
+
onChange(draft ? { operator, value: draft } : null);
|
|
4363
|
+
onClose();
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
)
|
|
4367
|
+
] });
|
|
4368
|
+
}
|
|
4369
|
+
function ApplyClear({ onClear, onApply }) {
|
|
4370
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex items-center justify-end gap-2 border-t border-border/40 pt-1", children: [
|
|
4371
|
+
/* @__PURE__ */ jsx47(
|
|
4372
|
+
Button,
|
|
4373
|
+
{
|
|
4374
|
+
variant: "ghost",
|
|
4375
|
+
size: "sm",
|
|
4376
|
+
onClick: onClear,
|
|
4377
|
+
className: "h-8 px-3 text-muted-foreground hover:text-foreground",
|
|
4378
|
+
children: "Clear"
|
|
4379
|
+
}
|
|
4380
|
+
),
|
|
4381
|
+
/* @__PURE__ */ jsx47(Button, { color: "primary", size: "sm", onClick: onApply, className: "h-8 px-3", children: "Apply" })
|
|
4382
|
+
] });
|
|
4268
4383
|
}
|
|
4269
4384
|
|
|
4270
4385
|
// src/app/data/DataTable.tsx
|
|
4271
|
-
import { useEffect as useEffect4, useMemo as useMemo3, useState as
|
|
4386
|
+
import { useEffect as useEffect4, useMemo as useMemo3, useState as useState6 } from "react";
|
|
4272
4387
|
import {
|
|
4273
4388
|
ArrowDownIcon,
|
|
4274
4389
|
ArrowUpDownIcon,
|
|
@@ -4356,7 +4471,7 @@ function DataTable({
|
|
|
4356
4471
|
defaultPageIndex = 0,
|
|
4357
4472
|
onPageChange
|
|
4358
4473
|
}) {
|
|
4359
|
-
const [uncontrolledSort, setUncontrolledSort] =
|
|
4474
|
+
const [uncontrolledSort, setUncontrolledSort] = useState6(
|
|
4360
4475
|
defaultSort
|
|
4361
4476
|
);
|
|
4362
4477
|
const isSortControlled = sortProp !== void 0;
|
|
@@ -4367,7 +4482,7 @@ function DataTable({
|
|
|
4367
4482
|
}
|
|
4368
4483
|
onSortChange?.(next);
|
|
4369
4484
|
};
|
|
4370
|
-
const [uncontrolledSelected, setUncontrolledSelected] =
|
|
4485
|
+
const [uncontrolledSelected, setUncontrolledSelected] = useState6(
|
|
4371
4486
|
defaultSelectedKeys ?? []
|
|
4372
4487
|
);
|
|
4373
4488
|
const isSelectionControlled = selectedKeysProp !== void 0;
|
|
@@ -4379,7 +4494,7 @@ function DataTable({
|
|
|
4379
4494
|
}
|
|
4380
4495
|
onSelectionChange?.(next);
|
|
4381
4496
|
};
|
|
4382
|
-
const [uncontrolledPage, setUncontrolledPage] =
|
|
4497
|
+
const [uncontrolledPage, setUncontrolledPage] = useState6(defaultPageIndex);
|
|
4383
4498
|
const isPageControlled = pageIndexProp !== void 0;
|
|
4384
4499
|
const rawPageIndex = isPageControlled ? pageIndexProp : uncontrolledPage;
|
|
4385
4500
|
const setPage = (next) => {
|
|
@@ -4683,7 +4798,7 @@ var ChartPanel = ({
|
|
|
4683
4798
|
};
|
|
4684
4799
|
|
|
4685
4800
|
// src/app/data/MetricRow.tsx
|
|
4686
|
-
import { useId as useId10, useState as
|
|
4801
|
+
import { useId as useId10, useState as useState7 } from "react";
|
|
4687
4802
|
import { jsx as jsx50, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
4688
4803
|
var MetricRow = ({
|
|
4689
4804
|
title,
|
|
@@ -4701,7 +4816,7 @@ var MetricRow = ({
|
|
|
4701
4816
|
const metricTileClass = useAppDensityClass("metricTile");
|
|
4702
4817
|
const titleId = useId10();
|
|
4703
4818
|
const selectable = onMetricChange != null || activeMetricId != null;
|
|
4704
|
-
const [internalId, setInternalId] =
|
|
4819
|
+
const [internalId, setInternalId] = useState7(
|
|
4705
4820
|
defaultActiveMetricId ?? metrics[0]?.id
|
|
4706
4821
|
);
|
|
4707
4822
|
const activeId = activeMetricId ?? internalId;
|
|
@@ -4775,7 +4890,7 @@ var MetricRow = ({
|
|
|
4775
4890
|
};
|
|
4776
4891
|
|
|
4777
4892
|
// src/app/data/MetricChartCard.tsx
|
|
4778
|
-
import { useId as useId11, useState as
|
|
4893
|
+
import { useId as useId11, useState as useState8 } from "react";
|
|
4779
4894
|
import { jsx as jsx51, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
4780
4895
|
var MetricChartCard = ({
|
|
4781
4896
|
title,
|
|
@@ -4801,7 +4916,7 @@ var MetricChartCard = ({
|
|
|
4801
4916
|
const metricChartRegionClass = useAppDensityClass("metricChartRegion");
|
|
4802
4917
|
const metricTileClass = useAppDensityClass("metricTile");
|
|
4803
4918
|
const titleId = useId11();
|
|
4804
|
-
const [internalId, setInternalId] =
|
|
4919
|
+
const [internalId, setInternalId] = useState8(
|
|
4805
4920
|
defaultActiveMetricId ?? metrics[0]?.id
|
|
4806
4921
|
);
|
|
4807
4922
|
const activeId = activeMetricId ?? internalId;
|
|
@@ -4913,9 +5028,9 @@ var MetricChartCard = ({
|
|
|
4913
5028
|
};
|
|
4914
5029
|
|
|
4915
5030
|
// src/hooks/use-live-query.ts
|
|
4916
|
-
import { useCallback as useCallback2, useEffect as useEffect5, useRef, useState as
|
|
5031
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef2, useState as useState9 } from "react";
|
|
4917
5032
|
function useInterval(callback, delayMs) {
|
|
4918
|
-
const saved =
|
|
5033
|
+
const saved = useRef2(callback);
|
|
4919
5034
|
useEffect5(() => {
|
|
4920
5035
|
saved.current = callback;
|
|
4921
5036
|
}, [callback]);
|
|
@@ -4932,18 +5047,18 @@ function useLiveQuery(fetcher, options = {}) {
|
|
|
4932
5047
|
immediate = true,
|
|
4933
5048
|
refetchOnFocus = true
|
|
4934
5049
|
} = options;
|
|
4935
|
-
const [data, setData] =
|
|
4936
|
-
const [error, setError] =
|
|
4937
|
-
const [loading, setLoading] =
|
|
4938
|
-
const [refreshing, setRefreshing] =
|
|
4939
|
-
const [lastUpdated, setLastUpdated] =
|
|
4940
|
-
const fetcherRef =
|
|
5050
|
+
const [data, setData] = useState9(void 0);
|
|
5051
|
+
const [error, setError] = useState9(void 0);
|
|
5052
|
+
const [loading, setLoading] = useState9(enabled);
|
|
5053
|
+
const [refreshing, setRefreshing] = useState9(false);
|
|
5054
|
+
const [lastUpdated, setLastUpdated] = useState9(null);
|
|
5055
|
+
const fetcherRef = useRef2(fetcher);
|
|
4941
5056
|
useEffect5(() => {
|
|
4942
5057
|
fetcherRef.current = fetcher;
|
|
4943
5058
|
}, [fetcher]);
|
|
4944
|
-
const mounted =
|
|
4945
|
-
const requestId =
|
|
4946
|
-
const hasData =
|
|
5059
|
+
const mounted = useRef2(true);
|
|
5060
|
+
const requestId = useRef2(0);
|
|
5061
|
+
const hasData = useRef2(false);
|
|
4947
5062
|
useEffect5(() => {
|
|
4948
5063
|
mounted.current = true;
|
|
4949
5064
|
return () => {
|