@timbal-ai/timbal-react 1.5.0 → 1.6.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/CHANGELOG.md +17 -1
- package/README.md +25 -0
- package/dist/app.cjs +855 -640
- 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-C2pZQsaP.d.ts} +81 -29
- package/dist/{chart-artifact-CS3qyGIY.d.cts → chart-artifact-VAqgH-My.d.cts} +81 -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-SZDYIRMB.esm.js → chunk-6SQMTBPL.esm.js} +618 -530
- 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-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/index.cjs +1327 -852
- 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 +1 -1
|
@@ -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";
|
|
@@ -321,7 +325,7 @@ Presentational groups \u2014 import from the package root, not from these paths:
|
|
|
321
325
|
|
|
322
326
|
| Folder | Components |
|
|
323
327
|
|--------|------------|
|
|
324
|
-
| \`data/\` | \`MetricRow\`, \`MetricChartCard\`, \`MetricTile\`, \`DataTable\`, \`FilterBar\`, \`FilterField\`, \`ChartPanel\` |
|
|
328
|
+
| \`data/\` | \`MetricRow\`, \`MetricChartCard\`, \`MetricTile\`, \`DataTable\`, \`FilterBar\`, \`FilterField\`, \`FilterDropdown\`, \`ChartPanel\` |
|
|
325
329
|
| \`integrations/\` | \`IntegrationCard\`, \`ConnectionRow\`, \`ConnectionRowList\`, \`IntegrationsEmptyState\`, \`PlanBadge\` |
|
|
326
330
|
| \`settings/\` | \`SettingsSection\`, \`FieldRow\`, \`DangerZone\`, \`FloatingUnsavedChangesBar\` |
|
|
327
331
|
| \`surfaces/\` | \`StatTile\`, \`InfoCard\`, \`AlertCard\`, \`CatalogCard\`, \`ResourceCard\`, \`DescriptionList\`, \`ExpandableSection\`, \`StatusDot\`, \`StatusBadge\`, \`EmptyState\` |
|
|
@@ -390,6 +394,7 @@ The cause of slop is dropping **below** the curated block layer into raw primiti
|
|
|
390
394
|
| \`StatusBadge\` | Status pill: \`tone\` (\`default\`\\|\`primary\`\\|\`success\`\\|\`warn\`\\|\`danger\`\\|\`muted\`), children. Use \`danger\` for critical/error severity. |
|
|
391
395
|
| \`FilterBar\` | Horizontal filter row \u2014 bottom-aligns controls. Mix \`SearchInput\` with labeled \`FilterField\` + \`Select\` (or \`Field\` + \`Select\`); labels sit above, control baselines match. |
|
|
392
396
|
| \`FilterField\` | Optional label wrapper for a filter control inside \`FilterBar\` (severity, status, \u2026). Omit \`label\` for search-only fields. |
|
|
397
|
+
| \`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
398
|
| \`SearchInput\` | Filter field with consistent app styling. |
|
|
394
399
|
| \`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
400
|
| \`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\`. |
|
|
@@ -448,6 +453,21 @@ Charts run on **recharts** with shadcn \`ChartContainer\` / \`ChartTooltipConten
|
|
|
448
453
|
| \`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
454
|
| \`Timeline\` | Vertical event log: \`items: [{ id, title, description?, meta?, tone?, icon? }]\`. Presentational \u2014 pass already-formatted timestamps in \`meta\`. |
|
|
450
455
|
|
|
456
|
+
#### More \`/ui\` primitives (import from \`/ui\` or the package root)
|
|
457
|
+
|
|
458
|
+
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.
|
|
459
|
+
|
|
460
|
+
| Component | Use for |
|
|
461
|
+
|-----------|---------|
|
|
462
|
+
| \`Stepper\` | Ordered step indicator for wizards / onboarding (horizontal or vertical; complete / active / upcoming states). |
|
|
463
|
+
| \`Rating\` | Star rating \u2014 interactive (keyboard + hover preview) or \`readOnly\`; controlled or uncontrolled. |
|
|
464
|
+
| \`NumberField\` | Numeric input with \u2212/+ steppers on the control surface; clamps to \`min\`/\`max\`, steps by \`step\`. |
|
|
465
|
+
| \`TagInput\` | Chips / token input; commits on Enter/comma, removes on Backspace, optional \`dedupe\`/\`max\`. |
|
|
466
|
+
| \`AvatarGroup\` | Overlapping avatar stack with an optional \`+N\` overflow chip (\`max\`, \`spacing\`). |
|
|
467
|
+
| \`CircularProgress\` | Lightweight SVG progress ring \u2014 determinate (optional center label) or indeterminate. |
|
|
468
|
+
| \`CopyButton\` | Click-to-copy with a transient check confirmation; icon-only or with a label. |
|
|
469
|
+
| \`Snippet\` | Single-line code / command on the elevated surface with a built-in copy button. |
|
|
470
|
+
|
|
451
471
|
Studio chrome (\`StudioSidebar\`, \`ModeToggle\`, \u2026) lives in \`@timbal-ai/timbal-react/studio\` \u2014 optional, not required for every dashboard.
|
|
452
472
|
|
|
453
473
|
### Block recipes \u2014 compose these (don't clone wholesale)
|
|
@@ -514,6 +534,7 @@ import {
|
|
|
514
534
|
DataTable,
|
|
515
535
|
FilterBar,
|
|
516
536
|
FilterField,
|
|
537
|
+
FilterDropdown,
|
|
517
538
|
AlertCard,
|
|
518
539
|
CatalogCard,
|
|
519
540
|
} from "@timbal-ai/timbal-react/app";
|
|
@@ -1648,7 +1669,11 @@ function useAppDensityClass(key, override) {
|
|
|
1648
1669
|
}
|
|
1649
1670
|
|
|
1650
1671
|
// src/charts/sparkline.tsx
|
|
1651
|
-
import {
|
|
1672
|
+
import {
|
|
1673
|
+
useId,
|
|
1674
|
+
useRef,
|
|
1675
|
+
useState
|
|
1676
|
+
} from "react";
|
|
1652
1677
|
import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1653
1678
|
var Sparkline = ({
|
|
1654
1679
|
data,
|
|
@@ -1659,30 +1684,36 @@ var Sparkline = ({
|
|
|
1659
1684
|
height = 28,
|
|
1660
1685
|
strokeWidth = 1.5,
|
|
1661
1686
|
className,
|
|
1662
|
-
ariaLabel = "Trend"
|
|
1687
|
+
ariaLabel = "Trend",
|
|
1688
|
+
interactive = false,
|
|
1689
|
+
labels,
|
|
1690
|
+
formatValue
|
|
1663
1691
|
}) => {
|
|
1664
1692
|
const uid = useId();
|
|
1693
|
+
const containerRef = useRef(null);
|
|
1694
|
+
const [activeIndex, setActiveIndex] = useState(null);
|
|
1665
1695
|
const values = data.map((d) => typeof d === "number" ? d : toNum(d[dataKey]));
|
|
1666
1696
|
if (values.length === 0) {
|
|
1667
1697
|
return /* @__PURE__ */ jsx3("span", { className: cn("inline-block", className), style: { width, height } });
|
|
1668
1698
|
}
|
|
1669
|
-
const
|
|
1699
|
+
const padX = 0;
|
|
1700
|
+
const padY = strokeWidth + 1;
|
|
1670
1701
|
const min = Math.min(...values);
|
|
1671
1702
|
const max = Math.max(...values);
|
|
1672
1703
|
const range = max - min || 1;
|
|
1673
|
-
const innerW = width -
|
|
1674
|
-
const innerH = height -
|
|
1704
|
+
const innerW = width - padX * 2;
|
|
1705
|
+
const innerH = height - padY * 2;
|
|
1675
1706
|
const points = values.map((v, i) => ({
|
|
1676
|
-
x:
|
|
1677
|
-
y:
|
|
1707
|
+
x: padX + (values.length > 1 ? i / (values.length - 1) * innerW : innerW / 2),
|
|
1708
|
+
y: padY + innerH - (v - min) / range * innerH
|
|
1678
1709
|
}));
|
|
1679
|
-
|
|
1710
|
+
const svg = /* @__PURE__ */ jsxs2(
|
|
1680
1711
|
"svg",
|
|
1681
1712
|
{
|
|
1682
1713
|
width,
|
|
1683
1714
|
height,
|
|
1684
1715
|
viewBox: `0 0 ${width} ${height}`,
|
|
1685
|
-
className: cn("block", className),
|
|
1716
|
+
className: cn("block", interactive ? "h-full w-full" : className),
|
|
1686
1717
|
role: "img",
|
|
1687
1718
|
"aria-label": ariaLabel,
|
|
1688
1719
|
preserveAspectRatio: "none",
|
|
@@ -1692,7 +1723,7 @@ var Sparkline = ({
|
|
|
1692
1723
|
/* @__PURE__ */ jsx3("stop", { offset: "0%", style: { stopColor: color, stopOpacity: 0.25 } }),
|
|
1693
1724
|
/* @__PURE__ */ jsx3("stop", { offset: "100%", style: { stopColor: color, stopOpacity: 0 } })
|
|
1694
1725
|
] }) }),
|
|
1695
|
-
/* @__PURE__ */ jsx3("path", { d: monotoneAreaPath(points, height -
|
|
1726
|
+
/* @__PURE__ */ jsx3("path", { d: monotoneAreaPath(points, height - padY), fill: `url(#${uid}-spark)` })
|
|
1696
1727
|
] }),
|
|
1697
1728
|
/* @__PURE__ */ jsx3(
|
|
1698
1729
|
"path",
|
|
@@ -1704,7 +1735,81 @@ var Sparkline = ({
|
|
|
1704
1735
|
strokeLinecap: "round",
|
|
1705
1736
|
strokeLinejoin: "round"
|
|
1706
1737
|
}
|
|
1707
|
-
)
|
|
1738
|
+
),
|
|
1739
|
+
interactive && activeIndex != null && points[activeIndex] ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1740
|
+
/* @__PURE__ */ jsx3(
|
|
1741
|
+
"line",
|
|
1742
|
+
{
|
|
1743
|
+
x1: points[activeIndex].x,
|
|
1744
|
+
x2: points[activeIndex].x,
|
|
1745
|
+
y1: 0,
|
|
1746
|
+
y2: height,
|
|
1747
|
+
stroke: color,
|
|
1748
|
+
strokeWidth: 1,
|
|
1749
|
+
strokeOpacity: 0.3,
|
|
1750
|
+
vectorEffect: "non-scaling-stroke"
|
|
1751
|
+
}
|
|
1752
|
+
),
|
|
1753
|
+
/* @__PURE__ */ jsx3(
|
|
1754
|
+
"circle",
|
|
1755
|
+
{
|
|
1756
|
+
cx: points[activeIndex].x,
|
|
1757
|
+
cy: points[activeIndex].y,
|
|
1758
|
+
r: 2.75,
|
|
1759
|
+
fill: color,
|
|
1760
|
+
stroke: "var(--background, #fff)",
|
|
1761
|
+
strokeWidth: 1.5,
|
|
1762
|
+
vectorEffect: "non-scaling-stroke"
|
|
1763
|
+
}
|
|
1764
|
+
)
|
|
1765
|
+
] }) : null
|
|
1766
|
+
]
|
|
1767
|
+
}
|
|
1768
|
+
);
|
|
1769
|
+
if (!interactive) return svg;
|
|
1770
|
+
const onMove = (e) => {
|
|
1771
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
1772
|
+
if (rect.width === 0) return;
|
|
1773
|
+
const fraction = (e.clientX - rect.left) / rect.width;
|
|
1774
|
+
const index = Math.max(
|
|
1775
|
+
0,
|
|
1776
|
+
Math.min(values.length - 1, Math.round(fraction * (values.length - 1)))
|
|
1777
|
+
);
|
|
1778
|
+
setActiveIndex(index);
|
|
1779
|
+
};
|
|
1780
|
+
const active = activeIndex != null ? points[activeIndex] : null;
|
|
1781
|
+
const formattedValue = activeIndex != null ? formatValue ? formatValue(values[activeIndex], activeIndex) : formatCompact(values[activeIndex]) : null;
|
|
1782
|
+
return /* @__PURE__ */ jsxs2(
|
|
1783
|
+
"span",
|
|
1784
|
+
{
|
|
1785
|
+
ref: containerRef,
|
|
1786
|
+
className: cn("relative block touch-none", className),
|
|
1787
|
+
style: { width: "100%", height: "100%" },
|
|
1788
|
+
onPointerMove: onMove,
|
|
1789
|
+
onPointerLeave: () => setActiveIndex(null),
|
|
1790
|
+
children: [
|
|
1791
|
+
svg,
|
|
1792
|
+
active ? /* @__PURE__ */ jsxs2(
|
|
1793
|
+
"span",
|
|
1794
|
+
{
|
|
1795
|
+
"aria-hidden": true,
|
|
1796
|
+
className: cn(
|
|
1797
|
+
"pointer-events-none absolute z-30 -translate-x-1/2 -translate-y-full whitespace-nowrap",
|
|
1798
|
+
"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)]",
|
|
1799
|
+
"border-white/10 bg-gradient-to-b from-neutral-800 to-neutral-950 text-white",
|
|
1800
|
+
"dark:border-black/10 dark:from-white dark:to-neutral-100 dark:text-neutral-900"
|
|
1801
|
+
),
|
|
1802
|
+
style: {
|
|
1803
|
+
left: `${active.x / width * 100}%`,
|
|
1804
|
+
top: `${active.y / height * 100}%`,
|
|
1805
|
+
marginTop: -8
|
|
1806
|
+
},
|
|
1807
|
+
children: [
|
|
1808
|
+
labels?.[activeIndex] != null ? /* @__PURE__ */ jsx3("span", { className: "mr-1.5 text-neutral-300 dark:text-neutral-500", children: labels[activeIndex] }) : null,
|
|
1809
|
+
/* @__PURE__ */ jsx3("span", { children: formattedValue })
|
|
1810
|
+
]
|
|
1811
|
+
}
|
|
1812
|
+
) : null
|
|
1708
1813
|
]
|
|
1709
1814
|
}
|
|
1710
1815
|
);
|
|
@@ -1770,6 +1875,15 @@ var inlineTrendToneClass = {
|
|
|
1770
1875
|
down: "text-rose-500/90 dark:text-rose-400/95 font-medium",
|
|
1771
1876
|
neutral: "text-muted-foreground/80"
|
|
1772
1877
|
};
|
|
1878
|
+
var sparklineToneColor = {
|
|
1879
|
+
up: "var(--primary, #3b82f6)",
|
|
1880
|
+
down: "var(--destructive, #f43f5e)",
|
|
1881
|
+
neutral: "var(--muted-foreground, #64748b)"
|
|
1882
|
+
};
|
|
1883
|
+
var sparklineBandBleed = {
|
|
1884
|
+
default: "-mx-4 -mb-3 h-10",
|
|
1885
|
+
compact: "-mx-3 -mb-2 h-8"
|
|
1886
|
+
};
|
|
1773
1887
|
var activeToneClass = {
|
|
1774
1888
|
default: "bg-foreground dark:bg-white",
|
|
1775
1889
|
primary: "bg-primary",
|
|
@@ -1799,8 +1913,10 @@ var MetricTile = ({
|
|
|
1799
1913
|
ariaLabel,
|
|
1800
1914
|
className
|
|
1801
1915
|
}) => {
|
|
1916
|
+
const density = useAppDensity();
|
|
1802
1917
|
const metricTileBaseClass = useAppDensityClass("metricTile");
|
|
1803
1918
|
const hasSparkline = Boolean(sparkline || sparklineData);
|
|
1919
|
+
const bandBleed = sparklineBandBleed[density === "compact" ? "compact" : "default"];
|
|
1804
1920
|
const content = /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
1805
1921
|
active ? /* @__PURE__ */ jsx5(
|
|
1806
1922
|
"span",
|
|
@@ -1812,17 +1928,6 @@ var MetricTile = ({
|
|
|
1812
1928
|
)
|
|
1813
1929
|
}
|
|
1814
1930
|
) : 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
1931
|
/* @__PURE__ */ jsxs4("div", { className: "relative z-10 flex flex-col gap-1 w-full text-left", children: [
|
|
1827
1932
|
/* @__PURE__ */ jsx5("span", { className: "text-xs font-semibold text-muted-foreground/80 tracking-tight", children: label }),
|
|
1828
1933
|
/* @__PURE__ */ jsxs4("span", { className: "flex items-center gap-2", children: [
|
|
@@ -1841,7 +1946,28 @@ var MetricTile = ({
|
|
|
1841
1946
|
}
|
|
1842
1947
|
) : null
|
|
1843
1948
|
] })
|
|
1844
|
-
] })
|
|
1949
|
+
] }),
|
|
1950
|
+
hasSparkline ? /* @__PURE__ */ jsx5(
|
|
1951
|
+
"div",
|
|
1952
|
+
{
|
|
1953
|
+
className: cn(
|
|
1954
|
+
"relative z-10 mt-2",
|
|
1955
|
+
bandBleed
|
|
1956
|
+
),
|
|
1957
|
+
children: sparkline ?? /* @__PURE__ */ jsx5(
|
|
1958
|
+
Sparkline,
|
|
1959
|
+
{
|
|
1960
|
+
data: sparklineData,
|
|
1961
|
+
width: 160,
|
|
1962
|
+
height: 40,
|
|
1963
|
+
interactive: true,
|
|
1964
|
+
className: "h-full w-full opacity-90",
|
|
1965
|
+
color: sparklineToneColor[trendTone],
|
|
1966
|
+
...sparklineConfig
|
|
1967
|
+
}
|
|
1968
|
+
)
|
|
1969
|
+
}
|
|
1970
|
+
) : null
|
|
1845
1971
|
] });
|
|
1846
1972
|
const divider = showDivider ? metricCellDividerClass : void 0;
|
|
1847
1973
|
if (onSelect) {
|
|
@@ -1893,7 +2019,7 @@ function useAppShellNav() {
|
|
|
1893
2019
|
|
|
1894
2020
|
// src/app/layout/AppShell.tsx
|
|
1895
2021
|
import { motion, useReducedMotion } from "motion/react";
|
|
1896
|
-
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
2022
|
+
import { useCallback, useEffect, useMemo, useState as useState2 } from "react";
|
|
1897
2023
|
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1898
2024
|
var floatingTriggerClass = cn(
|
|
1899
2025
|
"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 +2043,7 @@ var AppShellBody = ({
|
|
|
1917
2043
|
insetExpanded,
|
|
1918
2044
|
children
|
|
1919
2045
|
}) => {
|
|
1920
|
-
const [isMobile, setIsMobile] =
|
|
2046
|
+
const [isMobile, setIsMobile] = useState2(() => {
|
|
1921
2047
|
if (typeof window === "undefined") return false;
|
|
1922
2048
|
return window.innerWidth < 768;
|
|
1923
2049
|
});
|
|
@@ -1997,7 +2123,7 @@ var AppShell = ({
|
|
|
1997
2123
|
}) => {
|
|
1998
2124
|
const topbarContent = topbar ?? header;
|
|
1999
2125
|
const hasChat = Boolean(chat);
|
|
2000
|
-
const [uncontrolledNavOpen, setUncontrolledNavOpen] =
|
|
2126
|
+
const [uncontrolledNavOpen, setUncontrolledNavOpen] = useState2(defaultNavOpen);
|
|
2001
2127
|
const isNavControlled = navOpenProp !== void 0;
|
|
2002
2128
|
const navOpen = isNavControlled ? navOpenProp : uncontrolledNavOpen;
|
|
2003
2129
|
const setNavOpen = useCallback(
|
|
@@ -2012,7 +2138,7 @@ var AppShell = ({
|
|
|
2012
2138
|
() => ({ open: navOpen, setOpen: setNavOpen, toggle: toggleNav }),
|
|
2013
2139
|
[navOpen, setNavOpen, toggleNav]
|
|
2014
2140
|
);
|
|
2015
|
-
const [uncontrolledOpen, setUncontrolledOpen] =
|
|
2141
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState2(defaultChatOpen);
|
|
2016
2142
|
const isChatControlled = chatOpenProp !== void 0;
|
|
2017
2143
|
const chatOpen = isChatControlled ? chatOpenProp : uncontrolledOpen;
|
|
2018
2144
|
const setChatOpen = useCallback(
|
|
@@ -2027,7 +2153,7 @@ var AppShell = ({
|
|
|
2027
2153
|
const toggleChat = useCallback(() => {
|
|
2028
2154
|
setChatOpen(!chatOpen);
|
|
2029
2155
|
}, [chatOpen, setChatOpen]);
|
|
2030
|
-
const [insetPaddingPx, setInsetPaddingPx] =
|
|
2156
|
+
const [insetPaddingPx, setInsetPaddingPx] = useState2(
|
|
2031
2157
|
sidebar ? SIDEBAR_INSET_PX_EXPANDED : 0
|
|
2032
2158
|
);
|
|
2033
2159
|
const reportShellInset = useCallback((insetPx) => {
|
|
@@ -2540,7 +2666,7 @@ var StatusBadge = ({
|
|
|
2540
2666
|
"span",
|
|
2541
2667
|
{
|
|
2542
2668
|
className: cn(
|
|
2543
|
-
"aui-app-status-badge inline-flex items-center gap-1.5 rounded-full px-2 py-0.5",
|
|
2669
|
+
"aui-app-status-badge inline-flex w-fit shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5",
|
|
2544
2670
|
"text-xs font-medium leading-none ring-1 ring-inset",
|
|
2545
2671
|
statusBadgeToneClass[tone],
|
|
2546
2672
|
className
|
|
@@ -2755,7 +2881,7 @@ var DescriptionList = ({
|
|
|
2755
2881
|
);
|
|
2756
2882
|
|
|
2757
2883
|
// src/app/surfaces/ExpandableSection.tsx
|
|
2758
|
-
import { useId as useId2, useState as
|
|
2884
|
+
import { useId as useId2, useState as useState3 } from "react";
|
|
2759
2885
|
import { AnimatePresence, motion as motion2, useReducedMotion as useReducedMotion2 } from "motion/react";
|
|
2760
2886
|
import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2761
2887
|
var Chevron = ({ open }) => /* @__PURE__ */ jsx24(
|
|
@@ -2787,7 +2913,7 @@ var ExpandableSection = ({
|
|
|
2787
2913
|
}) => {
|
|
2788
2914
|
const reduceMotion = useReducedMotion2();
|
|
2789
2915
|
const panelId = useId2();
|
|
2790
|
-
const [internalOpen, setInternalOpen] =
|
|
2916
|
+
const [internalOpen, setInternalOpen] = useState3(defaultOpen);
|
|
2791
2917
|
const open = openProp ?? internalOpen;
|
|
2792
2918
|
const toggle = () => {
|
|
2793
2919
|
if (openProp == null) setInternalOpen((o) => !o);
|
|
@@ -3147,7 +3273,7 @@ var FieldRow = ({
|
|
|
3147
3273
|
};
|
|
3148
3274
|
|
|
3149
3275
|
// src/app/settings/FloatingUnsavedChangesBar.tsx
|
|
3150
|
-
import { useEffect as useEffect2, useState as
|
|
3276
|
+
import { useEffect as useEffect2, useState as useState4 } from "react";
|
|
3151
3277
|
import { createPortal } from "react-dom";
|
|
3152
3278
|
import { AnimatePresence as AnimatePresence2, motion as motion3, useReducedMotion as useReducedMotion3 } from "motion/react";
|
|
3153
3279
|
import { jsx as jsx30, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
@@ -3164,7 +3290,7 @@ var FloatingUnsavedChangesBar = ({
|
|
|
3164
3290
|
className
|
|
3165
3291
|
}) => {
|
|
3166
3292
|
const reduceMotion = useReducedMotion3();
|
|
3167
|
-
const [mounted, setMounted] =
|
|
3293
|
+
const [mounted, setMounted] = useState4(false);
|
|
3168
3294
|
useEffect2(() => setMounted(true), []);
|
|
3169
3295
|
if (!mounted || typeof document === "undefined") return null;
|
|
3170
3296
|
return createPortal(
|
|
@@ -3770,505 +3896,467 @@ var FilterField = ({
|
|
|
3770
3896
|
};
|
|
3771
3897
|
|
|
3772
3898
|
// 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";
|
|
3899
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useState as useState5 } from "react";
|
|
3900
|
+
import { ChevronRightIcon, ListFilterIcon, XIcon as XIcon2 } from "lucide-react";
|
|
3785
3901
|
import { jsx as jsx47, jsxs as jsxs36 } from "react/jsx-runtime";
|
|
3786
|
-
var
|
|
3787
|
-
{ id: "
|
|
3788
|
-
{ id: "
|
|
3789
|
-
{ id: "
|
|
3902
|
+
var DEFAULT_PRESETS = [
|
|
3903
|
+
{ id: "last_7_days", label: "Last 7 days" },
|
|
3904
|
+
{ id: "last_30_days", label: "Last 30 days" },
|
|
3905
|
+
{ id: "last_90_days", label: "Last 90 days" },
|
|
3906
|
+
{ id: "this_month", label: "This month" },
|
|
3907
|
+
{ id: "this_year", label: "This year" },
|
|
3908
|
+
{ id: "custom", label: "Custom range" }
|
|
3790
3909
|
];
|
|
3791
|
-
var
|
|
3792
|
-
{ id: "
|
|
3793
|
-
{ id: "
|
|
3794
|
-
{ id: "
|
|
3910
|
+
var DEFAULT_OPERATORS = [
|
|
3911
|
+
{ id: "gt", label: "Greater than" },
|
|
3912
|
+
{ id: "lt", label: "Less than" },
|
|
3913
|
+
{ id: "eq", label: "Equals" }
|
|
3795
3914
|
];
|
|
3915
|
+
function asArray(v) {
|
|
3916
|
+
return Array.isArray(v) ? v : [];
|
|
3917
|
+
}
|
|
3918
|
+
function asText(v) {
|
|
3919
|
+
return typeof v === "string" ? v : "";
|
|
3920
|
+
}
|
|
3921
|
+
function asDate(v) {
|
|
3922
|
+
return v && !Array.isArray(v) && typeof v === "object" && "preset" in v ? v : { preset: null };
|
|
3923
|
+
}
|
|
3924
|
+
function asNumeric(v) {
|
|
3925
|
+
return v && !Array.isArray(v) && typeof v === "object" && "operator" in v ? v : { operator: "gt", value: "" };
|
|
3926
|
+
}
|
|
3927
|
+
var OPERATOR_SYMBOL = {
|
|
3928
|
+
gt: ">",
|
|
3929
|
+
lt: "<",
|
|
3930
|
+
eq: "="
|
|
3931
|
+
};
|
|
3796
3932
|
function FilterDropdown({
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3933
|
+
fields,
|
|
3934
|
+
value,
|
|
3935
|
+
defaultValue,
|
|
3936
|
+
onChange,
|
|
3937
|
+
label = "Filter",
|
|
3938
|
+
align = "start",
|
|
3939
|
+
showActiveChips = true,
|
|
3801
3940
|
className
|
|
3802
3941
|
}) {
|
|
3803
|
-
const [isOpen, setIsOpen] =
|
|
3804
|
-
const [
|
|
3805
|
-
const [isMobile, setIsMobile] =
|
|
3942
|
+
const [isOpen, setIsOpen] = useState5(false);
|
|
3943
|
+
const [activeId, setActiveId] = useState5(fields[0]?.id ?? null);
|
|
3944
|
+
const [isMobile, setIsMobile] = useState5(false);
|
|
3945
|
+
const isControlled = value !== void 0;
|
|
3946
|
+
const [internal, setInternal] = useState5(defaultValue ?? {});
|
|
3947
|
+
const values = isControlled ? value : internal;
|
|
3806
3948
|
useEffect3(() => {
|
|
3807
3949
|
const checkMobile = () => setIsMobile(window.innerWidth < 768);
|
|
3808
3950
|
checkMobile();
|
|
3809
3951
|
window.addEventListener("resize", checkMobile);
|
|
3810
3952
|
return () => window.removeEventListener("resize", checkMobile);
|
|
3811
3953
|
}, []);
|
|
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
3954
|
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 ?? "");
|
|
3955
|
+
if (!fields.some((f) => f.id === activeId)) {
|
|
3956
|
+
setActiveId(fields[0]?.id ?? null);
|
|
3848
3957
|
}
|
|
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);
|
|
3958
|
+
}, [fields, activeId]);
|
|
3959
|
+
const commit = (id, next) => {
|
|
3960
|
+
const merged = { ...values, [id]: next };
|
|
3961
|
+
if (!isControlled) setInternal(merged);
|
|
3962
|
+
onChange?.(merged);
|
|
3895
3963
|
};
|
|
3896
|
-
const
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
setIsOpen(false);
|
|
3964
|
+
const clearAll = () => {
|
|
3965
|
+
if (!isControlled) setInternal({});
|
|
3966
|
+
onChange?.({});
|
|
3900
3967
|
};
|
|
3901
|
-
const
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3968
|
+
const activeIdx = fields.findIndex((f) => f.id === activeId);
|
|
3969
|
+
const activeField = activeIdx >= 0 ? fields[activeIdx] : void 0;
|
|
3970
|
+
const chips = [];
|
|
3971
|
+
for (const field of fields) {
|
|
3972
|
+
const v = values[field.id];
|
|
3973
|
+
if (field.type === "multiselect") {
|
|
3974
|
+
const selected = asArray(v);
|
|
3975
|
+
for (const optionValue of selected) {
|
|
3976
|
+
const opt = field.options?.find((o) => o.value === optionValue);
|
|
3977
|
+
chips.push({
|
|
3978
|
+
id: `${field.id}:${optionValue}`,
|
|
3979
|
+
label: `${field.label}: ${opt?.label ?? optionValue}`,
|
|
3980
|
+
remove: () => commit(field.id, selected.filter((x) => x !== optionValue))
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
} else if (field.type === "text") {
|
|
3984
|
+
const text = asText(v);
|
|
3985
|
+
if (text) {
|
|
3986
|
+
chips.push({
|
|
3987
|
+
id: field.id,
|
|
3988
|
+
label: `${field.label}: ${text}`,
|
|
3989
|
+
remove: () => commit(field.id, "")
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3992
|
+
} else if (field.type === "numeric") {
|
|
3993
|
+
const n = asNumeric(v);
|
|
3994
|
+
if (n.value) {
|
|
3995
|
+
chips.push({
|
|
3996
|
+
id: field.id,
|
|
3997
|
+
label: `${field.label} ${OPERATOR_SYMBOL[n.operator]} ${n.value}`,
|
|
3998
|
+
remove: () => commit(field.id, null)
|
|
3999
|
+
});
|
|
4000
|
+
}
|
|
4001
|
+
} else if (field.type === "daterange") {
|
|
4002
|
+
const d = asDate(v);
|
|
4003
|
+
if (d.preset) {
|
|
4004
|
+
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;
|
|
4005
|
+
chips.push({
|
|
4006
|
+
id: field.id,
|
|
4007
|
+
label: `${field.label}: ${presetLabel}`,
|
|
4008
|
+
remove: () => commit(field.id, { preset: null })
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
3912
4011
|
}
|
|
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
|
-
|
|
4012
|
+
}
|
|
4013
|
+
return /* @__PURE__ */ jsxs36("div", { className: cn("flex flex-wrap items-center gap-2", className), children: [
|
|
4014
|
+
/* @__PURE__ */ jsxs36(Popover, { open: isOpen, onOpenChange: setIsOpen, children: [
|
|
4015
|
+
/* @__PURE__ */ jsx47(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx47(
|
|
4016
|
+
Button,
|
|
4017
|
+
{
|
|
4018
|
+
variant: "outline",
|
|
4019
|
+
size: "sm",
|
|
4020
|
+
className: "border-dashed font-medium text-muted-foreground hover:text-foreground",
|
|
4021
|
+
iconLeading: /* @__PURE__ */ jsx47(ListFilterIcon, { className: "size-4" }),
|
|
4022
|
+
children: label
|
|
4023
|
+
}
|
|
4024
|
+
) }),
|
|
4025
|
+
/* @__PURE__ */ jsx47(
|
|
4026
|
+
PopoverContent,
|
|
4027
|
+
{
|
|
4028
|
+
variant: "list",
|
|
4029
|
+
align,
|
|
4030
|
+
className: "overflow-visible border-none bg-transparent p-0 shadow-none max-w-[calc(100vw-32px)] md:max-w-none",
|
|
4031
|
+
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: [
|
|
4032
|
+
/* @__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) => {
|
|
4033
|
+
const isActive = activeId === field.id;
|
|
4034
|
+
return /* @__PURE__ */ jsxs36(
|
|
4035
|
+
"button",
|
|
4036
|
+
{
|
|
4037
|
+
type: "button",
|
|
4038
|
+
className: cn(
|
|
4039
|
+
"flex w-full items-center justify-between rounded-lg px-3 py-2 text-sm text-left transition-colors outline-none",
|
|
4040
|
+
isActive ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
4041
|
+
),
|
|
4042
|
+
onMouseEnter: () => !isMobile && setActiveId(field.id),
|
|
4043
|
+
onClick: () => setActiveId(field.id),
|
|
4044
|
+
children: [
|
|
4045
|
+
/* @__PURE__ */ jsxs36("span", { className: "flex items-center gap-2", children: [
|
|
4046
|
+
field.icon,
|
|
4047
|
+
/* @__PURE__ */ jsx47("span", { children: field.label })
|
|
4048
|
+
] }),
|
|
4049
|
+
/* @__PURE__ */ jsx47(ChevronRightIcon, { className: "size-4 text-muted-foreground/50" })
|
|
4050
|
+
]
|
|
4051
|
+
},
|
|
4052
|
+
field.id
|
|
4053
|
+
);
|
|
4054
|
+
}) }),
|
|
4055
|
+
activeField && /* @__PURE__ */ jsx47(
|
|
4056
|
+
"div",
|
|
4057
|
+
{
|
|
4058
|
+
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",
|
|
4059
|
+
style: isMobile ? {} : { top: `${activeIdx * 36 + 6}px` },
|
|
4060
|
+
children: /* @__PURE__ */ jsx47(
|
|
4061
|
+
FilterFieldControl,
|
|
4062
|
+
{
|
|
4063
|
+
field: activeField,
|
|
4064
|
+
value: values[activeField.id],
|
|
4065
|
+
onChange: (next) => commit(activeField.id, next),
|
|
4066
|
+
onClose: () => setIsOpen(false)
|
|
4067
|
+
}
|
|
4068
|
+
)
|
|
4069
|
+
}
|
|
4070
|
+
)
|
|
4071
|
+
] })
|
|
4072
|
+
}
|
|
4073
|
+
)
|
|
4074
|
+
] }),
|
|
4075
|
+
showActiveChips && chips.map((chip) => /* @__PURE__ */ jsx47(FilterChip, { label: chip.label, onRemove: chip.remove }, chip.id)),
|
|
4076
|
+
showActiveChips && chips.length > 0 && /* @__PURE__ */ jsx47(
|
|
4077
|
+
"button",
|
|
3966
4078
|
{
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
iconLeading: /* @__PURE__ */ jsx47(ListFilterIcon, { className: "size-4" }),
|
|
3972
|
-
children: "Filter"
|
|
4079
|
+
type: "button",
|
|
4080
|
+
onClick: clearAll,
|
|
4081
|
+
className: "rounded-full px-3 py-1 text-sm font-medium text-muted-foreground outline-none transition-colors hover:text-foreground",
|
|
4082
|
+
children: "Clear all"
|
|
3973
4083
|
}
|
|
3974
|
-
)
|
|
4084
|
+
)
|
|
4085
|
+
] });
|
|
4086
|
+
}
|
|
4087
|
+
function FilterChip({ label, onRemove }) {
|
|
4088
|
+
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: [
|
|
4089
|
+
/* @__PURE__ */ jsx47("span", { className: "truncate", children: label }),
|
|
3975
4090
|
/* @__PURE__ */ jsx47(
|
|
3976
|
-
|
|
4091
|
+
"button",
|
|
3977
4092
|
{
|
|
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
|
-
|
|
4093
|
+
type: "button",
|
|
4094
|
+
onClick: onRemove,
|
|
4095
|
+
"aria-label": `Remove ${label}`,
|
|
4096
|
+
className: "flex size-5 items-center justify-center rounded-full text-muted-foreground outline-none transition-colors hover:bg-muted hover:text-foreground",
|
|
4097
|
+
children: /* @__PURE__ */ jsx47(XIcon2, { className: "size-3.5" })
|
|
4098
|
+
}
|
|
4099
|
+
)
|
|
4100
|
+
] });
|
|
4101
|
+
}
|
|
4102
|
+
function FilterFieldControl({
|
|
4103
|
+
field,
|
|
4104
|
+
value,
|
|
4105
|
+
onChange,
|
|
4106
|
+
onClose
|
|
4107
|
+
}) {
|
|
4108
|
+
switch (field.type) {
|
|
4109
|
+
case "multiselect":
|
|
4110
|
+
return /* @__PURE__ */ jsx47(MultiSelectControl, { field, value: asArray(value), onChange });
|
|
4111
|
+
case "text":
|
|
4112
|
+
return /* @__PURE__ */ jsx47(TextControl, { field, value: asText(value), onChange, onClose });
|
|
4113
|
+
case "daterange":
|
|
4114
|
+
return /* @__PURE__ */ jsx47(DateRangeControl, { field, value: asDate(value), onChange, onClose });
|
|
4115
|
+
case "numeric":
|
|
4116
|
+
return /* @__PURE__ */ jsx47(NumericControl, { field, value: asNumeric(value), onChange, onClose });
|
|
4117
|
+
default:
|
|
4118
|
+
return null;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
function MultiSelectControl({
|
|
4122
|
+
field,
|
|
4123
|
+
value,
|
|
4124
|
+
onChange
|
|
4125
|
+
}) {
|
|
4126
|
+
const options = field.options ?? [];
|
|
4127
|
+
const [search, setSearch] = useState5("");
|
|
4128
|
+
const searchable = field.searchable ?? options.length > 8;
|
|
4129
|
+
const filtered = useMemo2(() => {
|
|
4130
|
+
if (!search) return options;
|
|
4131
|
+
const q = search.toLowerCase();
|
|
4132
|
+
return options.filter(
|
|
4133
|
+
(o) => o.label.toLowerCase().includes(q) || o.hint?.toLowerCase().includes(q)
|
|
4134
|
+
);
|
|
4135
|
+
}, [options, search]);
|
|
4136
|
+
const toggle = (optionValue) => {
|
|
4137
|
+
onChange(
|
|
4138
|
+
value.includes(optionValue) ? value.filter((v) => v !== optionValue) : [...value, optionValue]
|
|
4139
|
+
);
|
|
4140
|
+
};
|
|
4141
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4142
|
+
searchable && /* @__PURE__ */ jsx47(
|
|
4143
|
+
SearchInput,
|
|
4144
|
+
{
|
|
4145
|
+
placeholder: field.searchPlaceholder ?? "Search\u2026",
|
|
4146
|
+
value: search,
|
|
4147
|
+
onChange: (e) => setSearch(e.target.value),
|
|
4148
|
+
className: "w-full min-w-0"
|
|
4149
|
+
}
|
|
4150
|
+
),
|
|
4151
|
+
/* @__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(
|
|
4152
|
+
"label",
|
|
4153
|
+
{
|
|
4154
|
+
className: "flex cursor-pointer items-center gap-2.5 rounded-lg px-2 py-1.5 transition-colors hover:bg-muted/50",
|
|
4155
|
+
children: [
|
|
4156
|
+
/* @__PURE__ */ jsx47(
|
|
4157
|
+
Checkbox,
|
|
4007
4158
|
{
|
|
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
|
-
]
|
|
4159
|
+
checked: value.includes(option.value),
|
|
4160
|
+
onCheckedChange: () => toggle(option.value)
|
|
4262
4161
|
}
|
|
4263
|
-
)
|
|
4264
|
-
|
|
4162
|
+
),
|
|
4163
|
+
option.icon,
|
|
4164
|
+
/* @__PURE__ */ jsxs36("span", { className: "flex min-w-0 flex-col", children: [
|
|
4165
|
+
/* @__PURE__ */ jsx47("span", { className: "truncate text-sm font-medium text-foreground", children: option.label }),
|
|
4166
|
+
option.hint && /* @__PURE__ */ jsx47("span", { className: "truncate text-xs text-muted-foreground", children: option.hint })
|
|
4167
|
+
] })
|
|
4168
|
+
]
|
|
4169
|
+
},
|
|
4170
|
+
option.value
|
|
4171
|
+
)) })
|
|
4172
|
+
] });
|
|
4173
|
+
}
|
|
4174
|
+
function TextControl({
|
|
4175
|
+
field,
|
|
4176
|
+
value,
|
|
4177
|
+
onChange,
|
|
4178
|
+
onClose
|
|
4179
|
+
}) {
|
|
4180
|
+
const [draft, setDraft] = useState5(value);
|
|
4181
|
+
useEffect3(() => setDraft(value), [value]);
|
|
4182
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4183
|
+
/* @__PURE__ */ jsx47(
|
|
4184
|
+
"input",
|
|
4185
|
+
{
|
|
4186
|
+
type: "text",
|
|
4187
|
+
placeholder: field.placeholder ?? "Type a value\u2026",
|
|
4188
|
+
value: draft,
|
|
4189
|
+
onChange: (e) => setDraft(e.target.value),
|
|
4190
|
+
onKeyDown: (e) => {
|
|
4191
|
+
if (e.key === "Enter") {
|
|
4192
|
+
onChange(draft);
|
|
4193
|
+
onClose();
|
|
4194
|
+
}
|
|
4195
|
+
},
|
|
4196
|
+
className: controlClass({ size: "sm" }, "w-full")
|
|
4197
|
+
}
|
|
4198
|
+
),
|
|
4199
|
+
/* @__PURE__ */ jsx47(
|
|
4200
|
+
ApplyClear,
|
|
4201
|
+
{
|
|
4202
|
+
onClear: () => {
|
|
4203
|
+
setDraft("");
|
|
4204
|
+
onChange("");
|
|
4205
|
+
onClose();
|
|
4206
|
+
},
|
|
4207
|
+
onApply: () => {
|
|
4208
|
+
onChange(draft);
|
|
4209
|
+
onClose();
|
|
4210
|
+
}
|
|
4265
4211
|
}
|
|
4266
4212
|
)
|
|
4267
|
-
] })
|
|
4213
|
+
] });
|
|
4214
|
+
}
|
|
4215
|
+
function DateRangeControl({
|
|
4216
|
+
field,
|
|
4217
|
+
value,
|
|
4218
|
+
onChange,
|
|
4219
|
+
onClose
|
|
4220
|
+
}) {
|
|
4221
|
+
const presets = field.presets ?? DEFAULT_PRESETS;
|
|
4222
|
+
const [from, setFrom] = useState5(value.from ?? "");
|
|
4223
|
+
const [to, setTo] = useState5(value.to ?? "");
|
|
4224
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-1", children: [
|
|
4225
|
+
presets.map((preset) => {
|
|
4226
|
+
const isSelected = value.preset === preset.id;
|
|
4227
|
+
return /* @__PURE__ */ jsxs36(
|
|
4228
|
+
"button",
|
|
4229
|
+
{
|
|
4230
|
+
type: "button",
|
|
4231
|
+
onClick: () => {
|
|
4232
|
+
if (preset.id === "custom") {
|
|
4233
|
+
onChange({ preset: "custom", from, to });
|
|
4234
|
+
} else {
|
|
4235
|
+
onChange({ preset: preset.id });
|
|
4236
|
+
onClose();
|
|
4237
|
+
}
|
|
4238
|
+
},
|
|
4239
|
+
className: cn(
|
|
4240
|
+
"flex w-full items-center justify-between rounded-lg px-2.5 py-1.5 text-left text-sm transition-colors outline-none",
|
|
4241
|
+
isSelected ? "bg-muted text-foreground" : "text-muted-foreground hover:bg-muted/50 hover:text-foreground"
|
|
4242
|
+
),
|
|
4243
|
+
children: [
|
|
4244
|
+
/* @__PURE__ */ jsx47("span", { children: preset.label }),
|
|
4245
|
+
preset.hint && /* @__PURE__ */ jsx47("span", { className: "text-xs text-muted-foreground/70", children: preset.hint })
|
|
4246
|
+
]
|
|
4247
|
+
},
|
|
4248
|
+
preset.id
|
|
4249
|
+
);
|
|
4250
|
+
}),
|
|
4251
|
+
value.preset === "custom" && /* @__PURE__ */ jsxs36("div", { className: "mt-2 flex flex-col gap-2 border-t border-border/40 pt-2", children: [
|
|
4252
|
+
/* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2", children: [
|
|
4253
|
+
/* @__PURE__ */ jsx47(
|
|
4254
|
+
"input",
|
|
4255
|
+
{
|
|
4256
|
+
type: "date",
|
|
4257
|
+
value: from,
|
|
4258
|
+
onChange: (e) => setFrom(e.target.value),
|
|
4259
|
+
className: controlClass({ size: "sm" }, "w-full text-xs")
|
|
4260
|
+
}
|
|
4261
|
+
),
|
|
4262
|
+
/* @__PURE__ */ jsx47("span", { className: "text-xs text-muted-foreground", children: "to" }),
|
|
4263
|
+
/* @__PURE__ */ jsx47(
|
|
4264
|
+
"input",
|
|
4265
|
+
{
|
|
4266
|
+
type: "date",
|
|
4267
|
+
value: to,
|
|
4268
|
+
onChange: (e) => setTo(e.target.value),
|
|
4269
|
+
className: controlClass({ size: "sm" }, "w-full text-xs")
|
|
4270
|
+
}
|
|
4271
|
+
)
|
|
4272
|
+
] }),
|
|
4273
|
+
/* @__PURE__ */ jsx47("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx47(
|
|
4274
|
+
Button,
|
|
4275
|
+
{
|
|
4276
|
+
color: "primary",
|
|
4277
|
+
size: "sm",
|
|
4278
|
+
className: "h-8 px-3",
|
|
4279
|
+
onClick: () => {
|
|
4280
|
+
onChange({ preset: "custom", from, to });
|
|
4281
|
+
onClose();
|
|
4282
|
+
},
|
|
4283
|
+
children: "Apply"
|
|
4284
|
+
}
|
|
4285
|
+
) })
|
|
4286
|
+
] })
|
|
4287
|
+
] });
|
|
4288
|
+
}
|
|
4289
|
+
function NumericControl({
|
|
4290
|
+
field,
|
|
4291
|
+
value,
|
|
4292
|
+
onChange,
|
|
4293
|
+
onClose
|
|
4294
|
+
}) {
|
|
4295
|
+
const operators = field.operators ?? DEFAULT_OPERATORS;
|
|
4296
|
+
const [operator, setOperator] = useState5(value.operator);
|
|
4297
|
+
const [draft, setDraft] = useState5(value.value);
|
|
4298
|
+
useEffect3(() => {
|
|
4299
|
+
setOperator(value.operator);
|
|
4300
|
+
setDraft(value.value);
|
|
4301
|
+
}, [value.operator, value.value]);
|
|
4302
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-2.5", children: [
|
|
4303
|
+
/* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-2", children: [
|
|
4304
|
+
/* @__PURE__ */ jsxs36(Select, { value: operator, onValueChange: (v) => setOperator(v), children: [
|
|
4305
|
+
/* @__PURE__ */ jsx47(SelectTrigger, { size: "sm", className: "shrink-0", children: /* @__PURE__ */ jsx47(SelectValue, {}) }),
|
|
4306
|
+
/* @__PURE__ */ jsx47(SelectContent, { children: operators.map((op) => /* @__PURE__ */ jsx47(SelectItem, { value: op.id, children: op.label }, op.id)) })
|
|
4307
|
+
] }),
|
|
4308
|
+
/* @__PURE__ */ jsx47(
|
|
4309
|
+
"input",
|
|
4310
|
+
{
|
|
4311
|
+
type: "text",
|
|
4312
|
+
inputMode: "decimal",
|
|
4313
|
+
placeholder: field.placeholder ?? "0.00",
|
|
4314
|
+
value: draft,
|
|
4315
|
+
onChange: (e) => setDraft(e.target.value),
|
|
4316
|
+
onKeyDown: (e) => {
|
|
4317
|
+
if (e.key === "Enter") {
|
|
4318
|
+
onChange(draft ? { operator, value: draft } : null);
|
|
4319
|
+
onClose();
|
|
4320
|
+
}
|
|
4321
|
+
},
|
|
4322
|
+
className: controlClass({ size: "sm" }, "min-w-0 flex-1")
|
|
4323
|
+
}
|
|
4324
|
+
)
|
|
4325
|
+
] }),
|
|
4326
|
+
/* @__PURE__ */ jsx47(
|
|
4327
|
+
ApplyClear,
|
|
4328
|
+
{
|
|
4329
|
+
onClear: () => {
|
|
4330
|
+
setDraft("");
|
|
4331
|
+
onChange(null);
|
|
4332
|
+
onClose();
|
|
4333
|
+
},
|
|
4334
|
+
onApply: () => {
|
|
4335
|
+
onChange(draft ? { operator, value: draft } : null);
|
|
4336
|
+
onClose();
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
)
|
|
4340
|
+
] });
|
|
4341
|
+
}
|
|
4342
|
+
function ApplyClear({ onClear, onApply }) {
|
|
4343
|
+
return /* @__PURE__ */ jsxs36("div", { className: "flex items-center justify-end gap-2 border-t border-border/40 pt-1", children: [
|
|
4344
|
+
/* @__PURE__ */ jsx47(
|
|
4345
|
+
Button,
|
|
4346
|
+
{
|
|
4347
|
+
variant: "ghost",
|
|
4348
|
+
size: "sm",
|
|
4349
|
+
onClick: onClear,
|
|
4350
|
+
className: "h-8 px-3 text-muted-foreground hover:text-foreground",
|
|
4351
|
+
children: "Clear"
|
|
4352
|
+
}
|
|
4353
|
+
),
|
|
4354
|
+
/* @__PURE__ */ jsx47(Button, { color: "primary", size: "sm", onClick: onApply, className: "h-8 px-3", children: "Apply" })
|
|
4355
|
+
] });
|
|
4268
4356
|
}
|
|
4269
4357
|
|
|
4270
4358
|
// src/app/data/DataTable.tsx
|
|
4271
|
-
import { useEffect as useEffect4, useMemo as useMemo3, useState as
|
|
4359
|
+
import { useEffect as useEffect4, useMemo as useMemo3, useState as useState6 } from "react";
|
|
4272
4360
|
import {
|
|
4273
4361
|
ArrowDownIcon,
|
|
4274
4362
|
ArrowUpDownIcon,
|
|
@@ -4356,7 +4444,7 @@ function DataTable({
|
|
|
4356
4444
|
defaultPageIndex = 0,
|
|
4357
4445
|
onPageChange
|
|
4358
4446
|
}) {
|
|
4359
|
-
const [uncontrolledSort, setUncontrolledSort] =
|
|
4447
|
+
const [uncontrolledSort, setUncontrolledSort] = useState6(
|
|
4360
4448
|
defaultSort
|
|
4361
4449
|
);
|
|
4362
4450
|
const isSortControlled = sortProp !== void 0;
|
|
@@ -4367,7 +4455,7 @@ function DataTable({
|
|
|
4367
4455
|
}
|
|
4368
4456
|
onSortChange?.(next);
|
|
4369
4457
|
};
|
|
4370
|
-
const [uncontrolledSelected, setUncontrolledSelected] =
|
|
4458
|
+
const [uncontrolledSelected, setUncontrolledSelected] = useState6(
|
|
4371
4459
|
defaultSelectedKeys ?? []
|
|
4372
4460
|
);
|
|
4373
4461
|
const isSelectionControlled = selectedKeysProp !== void 0;
|
|
@@ -4379,7 +4467,7 @@ function DataTable({
|
|
|
4379
4467
|
}
|
|
4380
4468
|
onSelectionChange?.(next);
|
|
4381
4469
|
};
|
|
4382
|
-
const [uncontrolledPage, setUncontrolledPage] =
|
|
4470
|
+
const [uncontrolledPage, setUncontrolledPage] = useState6(defaultPageIndex);
|
|
4383
4471
|
const isPageControlled = pageIndexProp !== void 0;
|
|
4384
4472
|
const rawPageIndex = isPageControlled ? pageIndexProp : uncontrolledPage;
|
|
4385
4473
|
const setPage = (next) => {
|
|
@@ -4683,7 +4771,7 @@ var ChartPanel = ({
|
|
|
4683
4771
|
};
|
|
4684
4772
|
|
|
4685
4773
|
// src/app/data/MetricRow.tsx
|
|
4686
|
-
import { useId as useId10, useState as
|
|
4774
|
+
import { useId as useId10, useState as useState7 } from "react";
|
|
4687
4775
|
import { jsx as jsx50, jsxs as jsxs39 } from "react/jsx-runtime";
|
|
4688
4776
|
var MetricRow = ({
|
|
4689
4777
|
title,
|
|
@@ -4701,7 +4789,7 @@ var MetricRow = ({
|
|
|
4701
4789
|
const metricTileClass = useAppDensityClass("metricTile");
|
|
4702
4790
|
const titleId = useId10();
|
|
4703
4791
|
const selectable = onMetricChange != null || activeMetricId != null;
|
|
4704
|
-
const [internalId, setInternalId] =
|
|
4792
|
+
const [internalId, setInternalId] = useState7(
|
|
4705
4793
|
defaultActiveMetricId ?? metrics[0]?.id
|
|
4706
4794
|
);
|
|
4707
4795
|
const activeId = activeMetricId ?? internalId;
|
|
@@ -4775,7 +4863,7 @@ var MetricRow = ({
|
|
|
4775
4863
|
};
|
|
4776
4864
|
|
|
4777
4865
|
// src/app/data/MetricChartCard.tsx
|
|
4778
|
-
import { useId as useId11, useState as
|
|
4866
|
+
import { useId as useId11, useState as useState8 } from "react";
|
|
4779
4867
|
import { jsx as jsx51, jsxs as jsxs40 } from "react/jsx-runtime";
|
|
4780
4868
|
var MetricChartCard = ({
|
|
4781
4869
|
title,
|
|
@@ -4801,7 +4889,7 @@ var MetricChartCard = ({
|
|
|
4801
4889
|
const metricChartRegionClass = useAppDensityClass("metricChartRegion");
|
|
4802
4890
|
const metricTileClass = useAppDensityClass("metricTile");
|
|
4803
4891
|
const titleId = useId11();
|
|
4804
|
-
const [internalId, setInternalId] =
|
|
4892
|
+
const [internalId, setInternalId] = useState8(
|
|
4805
4893
|
defaultActiveMetricId ?? metrics[0]?.id
|
|
4806
4894
|
);
|
|
4807
4895
|
const activeId = activeMetricId ?? internalId;
|
|
@@ -4913,9 +5001,9 @@ var MetricChartCard = ({
|
|
|
4913
5001
|
};
|
|
4914
5002
|
|
|
4915
5003
|
// src/hooks/use-live-query.ts
|
|
4916
|
-
import { useCallback as useCallback2, useEffect as useEffect5, useRef, useState as
|
|
5004
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef2, useState as useState9 } from "react";
|
|
4917
5005
|
function useInterval(callback, delayMs) {
|
|
4918
|
-
const saved =
|
|
5006
|
+
const saved = useRef2(callback);
|
|
4919
5007
|
useEffect5(() => {
|
|
4920
5008
|
saved.current = callback;
|
|
4921
5009
|
}, [callback]);
|
|
@@ -4932,18 +5020,18 @@ function useLiveQuery(fetcher, options = {}) {
|
|
|
4932
5020
|
immediate = true,
|
|
4933
5021
|
refetchOnFocus = true
|
|
4934
5022
|
} = options;
|
|
4935
|
-
const [data, setData] =
|
|
4936
|
-
const [error, setError] =
|
|
4937
|
-
const [loading, setLoading] =
|
|
4938
|
-
const [refreshing, setRefreshing] =
|
|
4939
|
-
const [lastUpdated, setLastUpdated] =
|
|
4940
|
-
const fetcherRef =
|
|
5023
|
+
const [data, setData] = useState9(void 0);
|
|
5024
|
+
const [error, setError] = useState9(void 0);
|
|
5025
|
+
const [loading, setLoading] = useState9(enabled);
|
|
5026
|
+
const [refreshing, setRefreshing] = useState9(false);
|
|
5027
|
+
const [lastUpdated, setLastUpdated] = useState9(null);
|
|
5028
|
+
const fetcherRef = useRef2(fetcher);
|
|
4941
5029
|
useEffect5(() => {
|
|
4942
5030
|
fetcherRef.current = fetcher;
|
|
4943
5031
|
}, [fetcher]);
|
|
4944
|
-
const mounted =
|
|
4945
|
-
const requestId =
|
|
4946
|
-
const hasData =
|
|
5032
|
+
const mounted = useRef2(true);
|
|
5033
|
+
const requestId = useRef2(0);
|
|
5034
|
+
const hasData = useRef2(false);
|
|
4947
5035
|
useEffect5(() => {
|
|
4948
5036
|
mounted.current = true;
|
|
4949
5037
|
return () => {
|