@caffeinebounce/ui 0.62.2
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/LICENSE +21 -0
- package/README.md +39 -0
- package/dist/FinancialStatementTable-BNUrdTU4.d.cts +249 -0
- package/dist/FinancialStatementTable-BNUrdTU4.d.ts +249 -0
- package/dist/NewsletterSignup-BPreFi5W.d.cts +37 -0
- package/dist/NewsletterSignup-BPreFi5W.d.ts +37 -0
- package/dist/TestimonialsSection-DtPu2OeR.d.ts +736 -0
- package/dist/TestimonialsSection-z-yotRfv.d.cts +736 -0
- package/dist/UnifiedTableControls-CG299y9h.d.ts +1233 -0
- package/dist/UnifiedTableControls-CIjRpNhy.d.cts +1233 -0
- package/dist/blog.d.cts +99 -0
- package/dist/blog.d.ts +99 -0
- package/dist/blog.js +292 -0
- package/dist/blog.mjs +279 -0
- package/dist/button-BHnoRUkF.d.cts +34 -0
- package/dist/button-BHnoRUkF.d.ts +34 -0
- package/dist/charts.d.cts +80 -0
- package/dist/charts.d.ts +80 -0
- package/dist/charts.js +19 -0
- package/dist/charts.mjs +2 -0
- package/dist/chunk-33JR5SYE.js +44 -0
- package/dist/chunk-3NDB4YPC.js +247 -0
- package/dist/chunk-5RW4PYZ6.js +1504 -0
- package/dist/chunk-5THF4YP6.mjs +155 -0
- package/dist/chunk-6BUBNZPG.mjs +164 -0
- package/dist/chunk-6DVWMJ3H.mjs +7029 -0
- package/dist/chunk-6TGV2DNP.js +120 -0
- package/dist/chunk-72JGQ7IJ.mjs +1498 -0
- package/dist/chunk-7DHKQODW.mjs +2932 -0
- package/dist/chunk-7IKHZKAC.js +160 -0
- package/dist/chunk-ADIDI7AJ.js +11 -0
- package/dist/chunk-BFWX5GJZ.mjs +940 -0
- package/dist/chunk-BMFQG6RA.mjs +5537 -0
- package/dist/chunk-C4UT5V3P.js +65 -0
- package/dist/chunk-CC4KUIKC.js +2004 -0
- package/dist/chunk-D3QXPL47.mjs +1814 -0
- package/dist/chunk-DQWHYBV2.js +36 -0
- package/dist/chunk-E42B4GSB.js +993 -0
- package/dist/chunk-ESZ7CBA2.js +1007 -0
- package/dist/chunk-EYJJQVV4.js +111 -0
- package/dist/chunk-FRRS2YZH.mjs +129 -0
- package/dist/chunk-G5TQWWQA.mjs +37 -0
- package/dist/chunk-GAIQJCAZ.js +5612 -0
- package/dist/chunk-GMBGWOCR.mjs +48 -0
- package/dist/chunk-GUSTCU2W.mjs +105 -0
- package/dist/chunk-HBLKVQIQ.mjs +23 -0
- package/dist/chunk-HMAMMC72.js +26 -0
- package/dist/chunk-IMKLN273.mjs +9 -0
- package/dist/chunk-J6OLBSZR.js +250 -0
- package/dist/chunk-JAE45W2T.js +28 -0
- package/dist/chunk-JORVSG52.js +606 -0
- package/dist/chunk-KB7UXIQZ.mjs +984 -0
- package/dist/chunk-KGGQ5NZV.js +186 -0
- package/dist/chunk-KHCC5UKW.mjs +22 -0
- package/dist/chunk-KRRIRS6A.mjs +1945 -0
- package/dist/chunk-LQCVRWVF.js +48 -0
- package/dist/chunk-MHZXUOGN.mjs +168 -0
- package/dist/chunk-MPZFPU4X.js +349 -0
- package/dist/chunk-MVYRNJA6.js +74 -0
- package/dist/chunk-N6AFNFZR.mjs +565 -0
- package/dist/chunk-OFQLWSUR.mjs +214 -0
- package/dist/chunk-OLAAXLXY.mjs +219 -0
- package/dist/chunk-OZZWDIOY.js +1859 -0
- package/dist/chunk-PZRI43EZ.js +111 -0
- package/dist/chunk-Q256HRYD.js +176 -0
- package/dist/chunk-Q52ZEL62.mjs +223 -0
- package/dist/chunk-R26Y7LGJ.js +142 -0
- package/dist/chunk-RBUYW6QQ.js +39 -0
- package/dist/chunk-RVEFPVZF.mjs +43 -0
- package/dist/chunk-SORWLKOK.js +96 -0
- package/dist/chunk-STEP6C4C.mjs +33 -0
- package/dist/chunk-T5R6HUDG.mjs +22 -0
- package/dist/chunk-T6ZBA6VJ.js +7109 -0
- package/dist/chunk-UM7WMXIU.js +2995 -0
- package/dist/chunk-VBD4Q5LL.mjs +117 -0
- package/dist/chunk-WOITB4JL.js +195 -0
- package/dist/chunk-YKZEKJMZ.mjs +71 -0
- package/dist/chunk-YL7RZRHM.mjs +103 -0
- package/dist/chunk-YSLD2HLP.mjs +346 -0
- package/dist/chunk-ZFLZA7UP.mjs +140 -0
- package/dist/chunk-ZKUIUE7W.mjs +26 -0
- package/dist/chunk-ZOA7B32D.js +248 -0
- package/dist/data-table.d.cts +46 -0
- package/dist/data-table.d.ts +46 -0
- package/dist/data-table.js +270 -0
- package/dist/data-table.mjs +13 -0
- package/dist/editor.d.cts +89 -0
- package/dist/editor.d.ts +89 -0
- package/dist/editor.js +10 -0
- package/dist/editor.mjs +1 -0
- package/dist/forms.d.cts +142 -0
- package/dist/forms.d.ts +142 -0
- package/dist/forms.js +24 -0
- package/dist/forms.mjs +11 -0
- package/dist/hooks-8ORd7T1A.d.cts +751 -0
- package/dist/hooks-B2jU1GVT.d.ts +751 -0
- package/dist/hooks.d.cts +6 -0
- package/dist/hooks.d.ts +6 -0
- package/dist/hooks.js +99 -0
- package/dist/hooks.mjs +10 -0
- package/dist/index.d.cts +3141 -0
- package/dist/index.d.ts +3141 -0
- package/dist/index.js +7332 -0
- package/dist/index.mjs +5876 -0
- package/dist/layouts-DPfDDF53.d.cts +1001 -0
- package/dist/layouts-DPfDDF53.d.ts +1001 -0
- package/dist/layouts.d.cts +4 -0
- package/dist/layouts.d.ts +4 -0
- package/dist/layouts.js +100 -0
- package/dist/layouts.mjs +11 -0
- package/dist/marketing-3d.d.cts +51 -0
- package/dist/marketing-3d.d.ts +51 -0
- package/dist/marketing-3d.js +553 -0
- package/dist/marketing-3d.mjs +526 -0
- package/dist/marketing.d.cts +583 -0
- package/dist/marketing.d.ts +583 -0
- package/dist/marketing.js +299 -0
- package/dist/marketing.mjs +10 -0
- package/dist/media.d.cts +29 -0
- package/dist/media.d.ts +29 -0
- package/dist/media.js +189 -0
- package/dist/media.mjs +182 -0
- package/dist/navigation.d.cts +591 -0
- package/dist/navigation.d.ts +591 -0
- package/dist/navigation.js +108 -0
- package/dist/navigation.mjs +15 -0
- package/dist/portal.d.cts +58 -0
- package/dist/portal.d.ts +58 -0
- package/dist/portal.js +403 -0
- package/dist/portal.mjs +395 -0
- package/dist/primitives.d.cts +508 -0
- package/dist/primitives.d.ts +508 -0
- package/dist/primitives.js +295 -0
- package/dist/primitives.mjs +14 -0
- package/dist/settings.d.cts +216 -0
- package/dist/settings.d.ts +216 -0
- package/dist/settings.js +34 -0
- package/dist/settings.mjs +9 -0
- package/dist/shared-Dqn36QHZ.d.cts +4 -0
- package/dist/shared-Dqn36QHZ.d.ts +4 -0
- package/dist/styles.css +1213 -0
- package/package.json +189 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,2932 @@
|
|
|
1
|
+
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from './chunk-6BUBNZPG.mjs';
|
|
2
|
+
import { Button } from './chunk-VBD4Q5LL.mjs';
|
|
3
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent } from './chunk-OFQLWSUR.mjs';
|
|
4
|
+
import { TooltipProvider, Tooltip, TooltipTrigger, TooltipContent } from './chunk-YKZEKJMZ.mjs';
|
|
5
|
+
import { Input } from './chunk-STEP6C4C.mjs';
|
|
6
|
+
import { cn } from './chunk-IMKLN273.mjs';
|
|
7
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
8
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
|
+
import { MoreHorizontal, Trash2, Check, X, Plus, GripVertical, ChevronDown, ChevronRight, Filter, ArrowDown, ArrowUp, ListOrdered, WrapText, EyeOff, ChevronsDownUp, ChevronsUpDown, Text, List, CheckSquare, Calendar, Hash, BookOpen, Info, DollarSign, TrendingUp, CheckCircle2, AlertTriangle } from 'lucide-react';
|
|
10
|
+
import * as React3 from 'react';
|
|
11
|
+
import { flexRender, useReactTable, getExpandedRowModel, getCoreRowModel } from '@tanstack/react-table';
|
|
12
|
+
|
|
13
|
+
var Popover = PopoverPrimitive.Root;
|
|
14
|
+
var PopoverTrigger = PopoverPrimitive.Trigger;
|
|
15
|
+
var PopoverAnchor = PopoverPrimitive.Anchor;
|
|
16
|
+
function PopoverContent({
|
|
17
|
+
className,
|
|
18
|
+
align = "center",
|
|
19
|
+
sideOffset = 4,
|
|
20
|
+
...props
|
|
21
|
+
}) {
|
|
22
|
+
return /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
23
|
+
PopoverPrimitive.Content,
|
|
24
|
+
{
|
|
25
|
+
align,
|
|
26
|
+
sideOffset,
|
|
27
|
+
className: cn(
|
|
28
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-box border p-4 shadow-md outline-none",
|
|
29
|
+
className
|
|
30
|
+
),
|
|
31
|
+
...props
|
|
32
|
+
}
|
|
33
|
+
) });
|
|
34
|
+
}
|
|
35
|
+
function Table({ className, ...props }) {
|
|
36
|
+
return /* @__PURE__ */ jsx("div", { className: "relative w-full overflow-auto", children: /* @__PURE__ */ jsx(
|
|
37
|
+
"table",
|
|
38
|
+
{
|
|
39
|
+
"data-slot": "table",
|
|
40
|
+
className: cn("w-full caption-bottom text-sm", className),
|
|
41
|
+
...props
|
|
42
|
+
}
|
|
43
|
+
) });
|
|
44
|
+
}
|
|
45
|
+
function TableHeader({ className, ...props }) {
|
|
46
|
+
return /* @__PURE__ */ jsx(
|
|
47
|
+
"thead",
|
|
48
|
+
{
|
|
49
|
+
"data-slot": "table-header",
|
|
50
|
+
className: cn("[&_tr]:border-b", className),
|
|
51
|
+
...props
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
function TableBody({ className, ...props }) {
|
|
56
|
+
return /* @__PURE__ */ jsx(
|
|
57
|
+
"tbody",
|
|
58
|
+
{
|
|
59
|
+
"data-slot": "table-body",
|
|
60
|
+
className: cn("[&_tr:last-child]:border-0", className),
|
|
61
|
+
...props
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
function TableFooter({ className, ...props }) {
|
|
66
|
+
return /* @__PURE__ */ jsx(
|
|
67
|
+
"tfoot",
|
|
68
|
+
{
|
|
69
|
+
"data-slot": "table-footer",
|
|
70
|
+
className: cn(
|
|
71
|
+
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
|
|
72
|
+
className
|
|
73
|
+
),
|
|
74
|
+
...props
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
function TableRow({ className, ...props }) {
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
"tr",
|
|
81
|
+
{
|
|
82
|
+
"data-slot": "table-row",
|
|
83
|
+
className: cn(
|
|
84
|
+
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
85
|
+
className
|
|
86
|
+
),
|
|
87
|
+
...props
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
function TableHead({ className, ...props }) {
|
|
92
|
+
return /* @__PURE__ */ jsx(
|
|
93
|
+
"th",
|
|
94
|
+
{
|
|
95
|
+
"data-slot": "table-head",
|
|
96
|
+
className: cn(
|
|
97
|
+
"text-muted-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
98
|
+
className
|
|
99
|
+
),
|
|
100
|
+
...props
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
function TableCell({ className, ...props }) {
|
|
105
|
+
return /* @__PURE__ */ jsx(
|
|
106
|
+
"td",
|
|
107
|
+
{
|
|
108
|
+
"data-slot": "table-cell",
|
|
109
|
+
className: cn(
|
|
110
|
+
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
111
|
+
className
|
|
112
|
+
),
|
|
113
|
+
...props
|
|
114
|
+
}
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
function TableCaption({
|
|
118
|
+
className,
|
|
119
|
+
...props
|
|
120
|
+
}) {
|
|
121
|
+
return /* @__PURE__ */ jsx(
|
|
122
|
+
"caption",
|
|
123
|
+
{
|
|
124
|
+
"data-slot": "table-caption",
|
|
125
|
+
className: cn("text-muted-foreground mt-4 text-sm", className),
|
|
126
|
+
...props
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
var colorMap = {
|
|
131
|
+
green: "#22c55e",
|
|
132
|
+
red: "#ef4444",
|
|
133
|
+
blue: "#3b82f6",
|
|
134
|
+
yellow: "#eab308",
|
|
135
|
+
orange: "#f97316",
|
|
136
|
+
purple: "#a855f7",
|
|
137
|
+
pink: "#ec4899",
|
|
138
|
+
gray: "#6b7280",
|
|
139
|
+
slate: "#64748b"
|
|
140
|
+
};
|
|
141
|
+
function getColorValue(color) {
|
|
142
|
+
if (!color) return void 0;
|
|
143
|
+
return colorMap[color.toLowerCase()] ?? color;
|
|
144
|
+
}
|
|
145
|
+
function TextLikeFilterBody({
|
|
146
|
+
type,
|
|
147
|
+
placeholder,
|
|
148
|
+
value,
|
|
149
|
+
onValueChange,
|
|
150
|
+
onApply,
|
|
151
|
+
onClear
|
|
152
|
+
}) {
|
|
153
|
+
return /* @__PURE__ */ jsxs("div", { className: "px-2 py-2", children: [
|
|
154
|
+
/* @__PURE__ */ jsx(
|
|
155
|
+
Input,
|
|
156
|
+
{
|
|
157
|
+
type: type === "number" ? "number" : void 0,
|
|
158
|
+
placeholder,
|
|
159
|
+
value,
|
|
160
|
+
onChange: (e) => onValueChange(e.target.value),
|
|
161
|
+
onKeyDown: (e) => {
|
|
162
|
+
if (e.key === "Enter") {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
onApply();
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
className: "h-8 text-xs",
|
|
168
|
+
autoFocus: true
|
|
169
|
+
}
|
|
170
|
+
),
|
|
171
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mt-2 gap-2", children: [
|
|
172
|
+
value && /* @__PURE__ */ jsx(
|
|
173
|
+
Button,
|
|
174
|
+
{
|
|
175
|
+
variant: "ghost",
|
|
176
|
+
size: "sm",
|
|
177
|
+
className: "h-7 text-xs px-2 text-muted-foreground hover:text-foreground",
|
|
178
|
+
onClick: onClear,
|
|
179
|
+
children: "Clear"
|
|
180
|
+
}
|
|
181
|
+
),
|
|
182
|
+
/* @__PURE__ */ jsx(
|
|
183
|
+
Button,
|
|
184
|
+
{
|
|
185
|
+
size: "sm",
|
|
186
|
+
className: "h-7 text-xs px-3 ml-auto",
|
|
187
|
+
onClick: onApply,
|
|
188
|
+
children: "Apply"
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
] })
|
|
192
|
+
] });
|
|
193
|
+
}
|
|
194
|
+
function DataTableColumnFilterContent({
|
|
195
|
+
title,
|
|
196
|
+
value,
|
|
197
|
+
onChange,
|
|
198
|
+
onClear,
|
|
199
|
+
onDelete,
|
|
200
|
+
filterType = "text",
|
|
201
|
+
filterOptions,
|
|
202
|
+
placeholder,
|
|
203
|
+
showDeleteMenu = true
|
|
204
|
+
}) {
|
|
205
|
+
const [localValue, setLocalValue] = React3.useState(value ?? "");
|
|
206
|
+
const [operator, setOperator] = React3.useState("is");
|
|
207
|
+
React3.useEffect(() => setLocalValue(value ?? ""), [value]);
|
|
208
|
+
const handleApply = () => onChange(localValue || void 0);
|
|
209
|
+
const handleClear = () => {
|
|
210
|
+
setLocalValue("");
|
|
211
|
+
onClear?.();
|
|
212
|
+
onChange(void 0);
|
|
213
|
+
};
|
|
214
|
+
const selectedValues = React3.useMemo(() => {
|
|
215
|
+
if (!localValue) return /* @__PURE__ */ new Set();
|
|
216
|
+
return new Set(localValue.split(",").filter(Boolean));
|
|
217
|
+
}, [localValue]);
|
|
218
|
+
const toggleOption = (optionValue) => {
|
|
219
|
+
const newSelected = new Set(selectedValues);
|
|
220
|
+
if (newSelected.has(optionValue)) {
|
|
221
|
+
newSelected.delete(optionValue);
|
|
222
|
+
} else {
|
|
223
|
+
newSelected.add(optionValue);
|
|
224
|
+
}
|
|
225
|
+
const newValue = Array.from(newSelected).join(",");
|
|
226
|
+
setLocalValue(newValue);
|
|
227
|
+
onChange(newValue || void 0);
|
|
228
|
+
};
|
|
229
|
+
const hasValue = Boolean(localValue);
|
|
230
|
+
return /* @__PURE__ */ jsxs("div", { className: "w-52", children: [
|
|
231
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-3 py-2 border-b border-border", children: [
|
|
232
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-foreground", children: title }),
|
|
233
|
+
/* @__PURE__ */ jsxs(
|
|
234
|
+
Select,
|
|
235
|
+
{
|
|
236
|
+
value: operator,
|
|
237
|
+
onValueChange: (v) => setOperator(v),
|
|
238
|
+
children: [
|
|
239
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "!h-auto !min-h-0 !py-0.5 w-auto gap-1 border-0 shadow-none bg-muted/50 px-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted rounded focus:ring-0 focus:ring-offset-0 focus-visible:ring-0", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
240
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
241
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "is", className: "text-xs", children: "is" }),
|
|
242
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "is_not", className: "text-xs", children: "is not" }),
|
|
243
|
+
filterType === "text" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
244
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "contains", className: "text-xs", children: "contains" }),
|
|
245
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "does_not_contain", className: "text-xs", children: "does not contain" })
|
|
246
|
+
] }),
|
|
247
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "is_empty", className: "text-xs", children: "is empty" }),
|
|
248
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "is_not_empty", className: "text-xs", children: "is not empty" })
|
|
249
|
+
] })
|
|
250
|
+
]
|
|
251
|
+
}
|
|
252
|
+
),
|
|
253
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
254
|
+
showDeleteMenu && hasValue && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
255
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "ghost-icon", size: "icon-sm", className: "size-5", children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "size-3.5" }) }) }),
|
|
256
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { align: "start", side: "right", sideOffset: 4, children: /* @__PURE__ */ jsxs(
|
|
257
|
+
DropdownMenuItem,
|
|
258
|
+
{
|
|
259
|
+
onClick: onDelete ?? handleClear,
|
|
260
|
+
className: "text-xs gap-2",
|
|
261
|
+
children: [
|
|
262
|
+
/* @__PURE__ */ jsx(Trash2, { className: "size-3.5" }),
|
|
263
|
+
"Delete filter"
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
) })
|
|
267
|
+
] })
|
|
268
|
+
] }),
|
|
269
|
+
/* @__PURE__ */ jsxs("div", { className: "p-1", children: [
|
|
270
|
+
filterType === "text" && /* @__PURE__ */ jsx(
|
|
271
|
+
TextLikeFilterBody,
|
|
272
|
+
{
|
|
273
|
+
placeholder: placeholder ?? `Search ${title.toLowerCase()}...`,
|
|
274
|
+
value: localValue,
|
|
275
|
+
onValueChange: setLocalValue,
|
|
276
|
+
onApply: handleApply,
|
|
277
|
+
onClear: handleClear
|
|
278
|
+
}
|
|
279
|
+
),
|
|
280
|
+
filterType === "number" && /* @__PURE__ */ jsx(
|
|
281
|
+
TextLikeFilterBody,
|
|
282
|
+
{
|
|
283
|
+
type: "number",
|
|
284
|
+
placeholder: placeholder ?? `Enter ${title.toLowerCase()}...`,
|
|
285
|
+
value: localValue,
|
|
286
|
+
onValueChange: setLocalValue,
|
|
287
|
+
onApply: handleApply,
|
|
288
|
+
onClear: handleClear
|
|
289
|
+
}
|
|
290
|
+
),
|
|
291
|
+
filterType === "select" && filterOptions && /* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto px-1", children: filterOptions.map((option) => {
|
|
292
|
+
const isSelected = selectedValues.has(option.value);
|
|
293
|
+
const colorValue = getColorValue(option.color);
|
|
294
|
+
return /* @__PURE__ */ jsxs(
|
|
295
|
+
"button",
|
|
296
|
+
{
|
|
297
|
+
type: "button",
|
|
298
|
+
className: cn(
|
|
299
|
+
"flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-xs transition-colors",
|
|
300
|
+
"hover:bg-muted",
|
|
301
|
+
isSelected && "bg-muted/50"
|
|
302
|
+
),
|
|
303
|
+
onClick: () => toggleOption(option.value),
|
|
304
|
+
children: [
|
|
305
|
+
/* @__PURE__ */ jsx(
|
|
306
|
+
"div",
|
|
307
|
+
{
|
|
308
|
+
className: cn(
|
|
309
|
+
"flex size-3.5 shrink-0 items-center justify-center rounded-sm border self-center",
|
|
310
|
+
isSelected ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/30"
|
|
311
|
+
),
|
|
312
|
+
children: isSelected && /* @__PURE__ */ jsx(Check, { className: "size-2.5" })
|
|
313
|
+
}
|
|
314
|
+
),
|
|
315
|
+
option.badge ? /* @__PURE__ */ jsx("span", { className: "shrink-0 flex items-center", children: option.badge }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
316
|
+
(colorValue || option.icon) && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: option.icon ? option.icon : /* @__PURE__ */ jsx(
|
|
317
|
+
"span",
|
|
318
|
+
{
|
|
319
|
+
className: "inline-block size-2 rounded-full",
|
|
320
|
+
style: { backgroundColor: colorValue }
|
|
321
|
+
}
|
|
322
|
+
) }),
|
|
323
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: option.label })
|
|
324
|
+
] })
|
|
325
|
+
]
|
|
326
|
+
},
|
|
327
|
+
option.value
|
|
328
|
+
);
|
|
329
|
+
}) }),
|
|
330
|
+
filterType === "boolean" && /* @__PURE__ */ jsx("div", { className: "px-1", children: (filterOptions ?? [
|
|
331
|
+
{ label: "Yes", value: "true", color: "green" },
|
|
332
|
+
{ label: "No", value: "false", color: "red" }
|
|
333
|
+
]).map((opt) => {
|
|
334
|
+
const isSelected = selectedValues.has(opt.value);
|
|
335
|
+
const colorValue = opt.color ? getColorValue(opt.color) : void 0;
|
|
336
|
+
let iconElement = null;
|
|
337
|
+
if (opt.icon) {
|
|
338
|
+
if (React3.isValidElement(opt.icon)) {
|
|
339
|
+
iconElement = opt.icon;
|
|
340
|
+
} else {
|
|
341
|
+
const Icon = opt.icon;
|
|
342
|
+
iconElement = /* @__PURE__ */ jsx(Icon, { className: "size-3" });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return /* @__PURE__ */ jsxs(
|
|
346
|
+
"button",
|
|
347
|
+
{
|
|
348
|
+
type: "button",
|
|
349
|
+
className: cn(
|
|
350
|
+
"flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-xs transition-colors",
|
|
351
|
+
"hover:bg-muted",
|
|
352
|
+
isSelected && "bg-muted/50"
|
|
353
|
+
),
|
|
354
|
+
onClick: () => toggleOption(opt.value),
|
|
355
|
+
children: [
|
|
356
|
+
/* @__PURE__ */ jsx(
|
|
357
|
+
"div",
|
|
358
|
+
{
|
|
359
|
+
className: cn(
|
|
360
|
+
"flex size-3.5 shrink-0 items-center justify-center rounded-sm border self-center",
|
|
361
|
+
isSelected ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/30"
|
|
362
|
+
),
|
|
363
|
+
children: isSelected && /* @__PURE__ */ jsx(Check, { className: "size-2.5" })
|
|
364
|
+
}
|
|
365
|
+
),
|
|
366
|
+
opt.badge ? /* @__PURE__ */ jsx("span", { className: "flex items-center", children: opt.badge }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
367
|
+
colorValue && /* @__PURE__ */ jsx(
|
|
368
|
+
"span",
|
|
369
|
+
{
|
|
370
|
+
className: "inline-block size-2 rounded-full",
|
|
371
|
+
style: { backgroundColor: colorValue }
|
|
372
|
+
}
|
|
373
|
+
),
|
|
374
|
+
iconElement && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground [&>svg]:size-3", children: iconElement }),
|
|
375
|
+
/* @__PURE__ */ jsx("span", { children: opt.label })
|
|
376
|
+
] })
|
|
377
|
+
]
|
|
378
|
+
},
|
|
379
|
+
opt.value
|
|
380
|
+
);
|
|
381
|
+
}) })
|
|
382
|
+
] }),
|
|
383
|
+
(filterType === "select" || filterType === "boolean") && selectedValues.size > 0 && /* @__PURE__ */ jsx("div", { className: "border-t border-border px-1 py-1", children: /* @__PURE__ */ jsx(
|
|
384
|
+
"button",
|
|
385
|
+
{
|
|
386
|
+
type: "button",
|
|
387
|
+
className: "flex w-full items-center rounded-md px-2 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
388
|
+
onClick: handleClear,
|
|
389
|
+
children: "Clear filter"
|
|
390
|
+
}
|
|
391
|
+
) })
|
|
392
|
+
] });
|
|
393
|
+
}
|
|
394
|
+
function DataTableColumnFilter({
|
|
395
|
+
column,
|
|
396
|
+
title,
|
|
397
|
+
filterType = "text",
|
|
398
|
+
filterOptions,
|
|
399
|
+
placeholder,
|
|
400
|
+
children,
|
|
401
|
+
onFilterChange
|
|
402
|
+
}) {
|
|
403
|
+
const [open, setOpen] = React3.useState(false);
|
|
404
|
+
const current = column.getFilterValue();
|
|
405
|
+
return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
406
|
+
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children }),
|
|
407
|
+
/* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-56 p-0", sideOffset: 4, children: /* @__PURE__ */ jsx(
|
|
408
|
+
DataTableColumnFilterContent,
|
|
409
|
+
{
|
|
410
|
+
title,
|
|
411
|
+
value: current,
|
|
412
|
+
onChange: (v) => {
|
|
413
|
+
column.setFilterValue(v);
|
|
414
|
+
onFilterChange?.(column.id, v);
|
|
415
|
+
},
|
|
416
|
+
onClear: () => column.setFilterValue(void 0),
|
|
417
|
+
filterType,
|
|
418
|
+
filterOptions,
|
|
419
|
+
placeholder,
|
|
420
|
+
showDeleteMenu: false
|
|
421
|
+
}
|
|
422
|
+
) })
|
|
423
|
+
] });
|
|
424
|
+
}
|
|
425
|
+
function DataTableFilterBadges({
|
|
426
|
+
filters,
|
|
427
|
+
onRemoveFilter,
|
|
428
|
+
onChangeFilter,
|
|
429
|
+
onClearAll,
|
|
430
|
+
onAddFilter,
|
|
431
|
+
className,
|
|
432
|
+
...props
|
|
433
|
+
}) {
|
|
434
|
+
if (filters.length === 0 && !onAddFilter) {
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
return /* @__PURE__ */ jsxs(
|
|
438
|
+
"div",
|
|
439
|
+
{
|
|
440
|
+
className: cn("flex flex-wrap items-center gap-1.5", className),
|
|
441
|
+
...props,
|
|
442
|
+
children: [
|
|
443
|
+
filters.map((filter) => /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
444
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
445
|
+
"button",
|
|
446
|
+
{
|
|
447
|
+
type: "button",
|
|
448
|
+
className: cn(
|
|
449
|
+
"inline-flex items-center gap-1 rounded border border-sky-200 dark:border-sky-800 bg-sky-50 dark:bg-sky-950/30 px-1.5 py-0.5",
|
|
450
|
+
"text-xs hover:bg-sky-100 dark:hover:bg-sky-900/40 transition-colors cursor-pointer"
|
|
451
|
+
),
|
|
452
|
+
children: [
|
|
453
|
+
filter.icon && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground shrink-0 [&>svg]:size-3", children: React3.isValidElement(filter.icon) ? filter.icon : (() => {
|
|
454
|
+
const Icon = filter.icon;
|
|
455
|
+
return /* @__PURE__ */ jsx(Icon, { className: "size-3" });
|
|
456
|
+
})() }),
|
|
457
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: filter.label }),
|
|
458
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground/50", children: "\xB7" }),
|
|
459
|
+
filter.valueBadge ? /* @__PURE__ */ jsx("span", { className: "inline-flex items-center", children: filter.valueBadge }) : /* @__PURE__ */ jsx("span", { className: "font-medium truncate max-w-28", children: filter.value }),
|
|
460
|
+
/* @__PURE__ */ jsx(
|
|
461
|
+
"button",
|
|
462
|
+
{
|
|
463
|
+
type: "button",
|
|
464
|
+
onPointerDown: (e) => {
|
|
465
|
+
e.stopPropagation();
|
|
466
|
+
e.preventDefault();
|
|
467
|
+
},
|
|
468
|
+
onClick: (e) => {
|
|
469
|
+
e.stopPropagation();
|
|
470
|
+
e.preventDefault();
|
|
471
|
+
onRemoveFilter(filter.id);
|
|
472
|
+
},
|
|
473
|
+
className: "ml-0.5 rounded-sm p-0.5 hover:bg-sky-200/50 dark:hover:bg-sky-800/50 -mr-0.5",
|
|
474
|
+
"aria-label": `Remove ${filter.label} filter`,
|
|
475
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3 text-muted-foreground" })
|
|
476
|
+
}
|
|
477
|
+
)
|
|
478
|
+
]
|
|
479
|
+
}
|
|
480
|
+
) }),
|
|
481
|
+
/* @__PURE__ */ jsx(DropdownMenuContent, { sideOffset: 4, align: "start", className: "p-0", children: /* @__PURE__ */ jsx(
|
|
482
|
+
DataTableColumnFilterContent,
|
|
483
|
+
{
|
|
484
|
+
title: filter.label,
|
|
485
|
+
value: filter.rawValue ?? filter.value,
|
|
486
|
+
onChange: (v) => onChangeFilter?.(filter.id, v),
|
|
487
|
+
onClear: () => onRemoveFilter(filter.id),
|
|
488
|
+
onDelete: () => onRemoveFilter(filter.id),
|
|
489
|
+
filterType: filter.filterType,
|
|
490
|
+
filterOptions: filter.filterOptions,
|
|
491
|
+
placeholder: filter.filterPlaceholder,
|
|
492
|
+
showDeleteMenu: true
|
|
493
|
+
}
|
|
494
|
+
) })
|
|
495
|
+
] }, filter.id)),
|
|
496
|
+
onAddFilter && /* @__PURE__ */ jsxs(
|
|
497
|
+
"button",
|
|
498
|
+
{
|
|
499
|
+
type: "button",
|
|
500
|
+
onClick: onAddFilter,
|
|
501
|
+
className: cn(
|
|
502
|
+
"inline-flex items-center gap-1 rounded px-1.5 py-0.5",
|
|
503
|
+
"text-xs text-muted-foreground hover:text-foreground hover:bg-accent/50 transition-colors"
|
|
504
|
+
),
|
|
505
|
+
children: [
|
|
506
|
+
/* @__PURE__ */ jsx(Plus, { className: "size-3" }),
|
|
507
|
+
/* @__PURE__ */ jsx("span", { children: "Filter" })
|
|
508
|
+
]
|
|
509
|
+
}
|
|
510
|
+
),
|
|
511
|
+
onClearAll && filters.length > 0 && /* @__PURE__ */ jsx(
|
|
512
|
+
"button",
|
|
513
|
+
{
|
|
514
|
+
type: "button",
|
|
515
|
+
onClick: onClearAll,
|
|
516
|
+
className: "text-xs text-muted-foreground hover:text-foreground ml-0.5",
|
|
517
|
+
children: "Clear"
|
|
518
|
+
}
|
|
519
|
+
)
|
|
520
|
+
]
|
|
521
|
+
}
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
var DataTableContext = React3.createContext(null);
|
|
525
|
+
function useDataTableContext() {
|
|
526
|
+
return React3.useContext(DataTableContext);
|
|
527
|
+
}
|
|
528
|
+
function TreeExpandCycleButton({
|
|
529
|
+
table,
|
|
530
|
+
density
|
|
531
|
+
}) {
|
|
532
|
+
const depthRef = React3.useRef(-1);
|
|
533
|
+
const maxDepth = React3.useMemo(() => {
|
|
534
|
+
let max = 0;
|
|
535
|
+
const walk = (rows) => {
|
|
536
|
+
for (const row of rows) {
|
|
537
|
+
if (row.depth > max) max = row.depth;
|
|
538
|
+
if (row.subRows?.length) walk(row.subRows);
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
walk(
|
|
542
|
+
table.getPreExpandedRowModel?.()?.rows ?? table.getCoreRowModel().rows
|
|
543
|
+
);
|
|
544
|
+
return max;
|
|
545
|
+
}, [table]);
|
|
546
|
+
const handleClick = React3.useCallback(() => {
|
|
547
|
+
const nextDepth = depthRef.current + 1;
|
|
548
|
+
if (nextDepth > maxDepth) {
|
|
549
|
+
table.toggleAllRowsExpanded(false);
|
|
550
|
+
depthRef.current = -1;
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const newExpanded = {};
|
|
554
|
+
const allRows = table.getPreExpandedRowModel?.()?.rows ?? table.getCoreRowModel().rows;
|
|
555
|
+
const walk = (rows) => {
|
|
556
|
+
for (const row of rows) {
|
|
557
|
+
if (row.depth <= nextDepth && row.getCanExpand()) {
|
|
558
|
+
newExpanded[row.id] = true;
|
|
559
|
+
}
|
|
560
|
+
if (row.subRows?.length) walk(row.subRows);
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
walk(allRows);
|
|
564
|
+
table.setExpanded(newExpanded);
|
|
565
|
+
depthRef.current = nextDepth;
|
|
566
|
+
}, [table, maxDepth]);
|
|
567
|
+
const isAllExpanded = table.getIsAllRowsExpanded();
|
|
568
|
+
const isSomeExpanded = table.getIsSomeRowsExpanded();
|
|
569
|
+
const iconSize = density === "compact" ? "size-3.5" : "size-4";
|
|
570
|
+
return /* @__PURE__ */ jsx(
|
|
571
|
+
"button",
|
|
572
|
+
{
|
|
573
|
+
type: "button",
|
|
574
|
+
onClick: handleClick,
|
|
575
|
+
className: cn(
|
|
576
|
+
"inline-flex items-center justify-center shrink-0 text-muted-foreground hover:text-foreground rounded hover:bg-accent transition-colors",
|
|
577
|
+
density === "compact" ? "size-5 ml-0.5" : "size-6 ml-1"
|
|
578
|
+
),
|
|
579
|
+
"aria-label": isAllExpanded ? "Collapse all rows" : "Expand next level",
|
|
580
|
+
title: isAllExpanded ? "Collapse all" : isSomeExpanded ? "Expand next level" : "Expand all",
|
|
581
|
+
children: isAllExpanded ? /* @__PURE__ */ jsx(ChevronsDownUp, { className: iconSize }) : isSomeExpanded ? /* @__PURE__ */ jsx(ChevronsUpDown, { className: iconSize }) : /* @__PURE__ */ jsx(ChevronsUpDown, { className: iconSize })
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
function DataTable({
|
|
586
|
+
table,
|
|
587
|
+
columns,
|
|
588
|
+
actionBar,
|
|
589
|
+
floatingBar,
|
|
590
|
+
filters,
|
|
591
|
+
onRemoveFilter,
|
|
592
|
+
onChangeFilter,
|
|
593
|
+
onClearAllFilters,
|
|
594
|
+
onAddFilter,
|
|
595
|
+
density = "comfy",
|
|
596
|
+
fontSize = "sm",
|
|
597
|
+
enableColumnResizing = false,
|
|
598
|
+
rowSelectionStyle = "hover",
|
|
599
|
+
enableRowDrag = true,
|
|
600
|
+
onRowDragEnd,
|
|
601
|
+
enableColumnDrag = false,
|
|
602
|
+
columnOrder: externalColumnOrder,
|
|
603
|
+
onColumnOrderChange,
|
|
604
|
+
columnWrapping: externalColumnWrapping,
|
|
605
|
+
onColumnWrappingChange,
|
|
606
|
+
onRowClick,
|
|
607
|
+
summary,
|
|
608
|
+
enableTreeView = false,
|
|
609
|
+
treeIndentPx = 20,
|
|
610
|
+
getRowClassName: getRowClassName2,
|
|
611
|
+
stickyColumns = 0,
|
|
612
|
+
stickyColumnIds: explicitStickyColumnIds,
|
|
613
|
+
className,
|
|
614
|
+
children,
|
|
615
|
+
...props
|
|
616
|
+
}) {
|
|
617
|
+
const cellPadding = density === "compact" ? "px-1.5 py-0.5" : "px-2 py-1";
|
|
618
|
+
const headerHeight = density === "compact" ? "h-7" : "h-8";
|
|
619
|
+
const fontSizeClass = {
|
|
620
|
+
xs: "text-[10px]",
|
|
621
|
+
sm: "text-xs",
|
|
622
|
+
md: "text-sm",
|
|
623
|
+
lg: "text-base"
|
|
624
|
+
}[fontSize];
|
|
625
|
+
const checkboxSize = {
|
|
626
|
+
xs: "size-3.5",
|
|
627
|
+
sm: "size-4",
|
|
628
|
+
md: "size-4.5",
|
|
629
|
+
lg: "size-5"
|
|
630
|
+
}[fontSize];
|
|
631
|
+
const checkmarkSize = {
|
|
632
|
+
xs: "size-2",
|
|
633
|
+
sm: "size-2.5",
|
|
634
|
+
md: "size-3",
|
|
635
|
+
lg: "size-3.5"
|
|
636
|
+
}[fontSize];
|
|
637
|
+
const [columnSizing, setColumnSizing] = React3.useState({});
|
|
638
|
+
const [resizingColumn, setResizingColumn] = React3.useState(
|
|
639
|
+
null
|
|
640
|
+
);
|
|
641
|
+
const [hoveredRowId, setHoveredRowId] = React3.useState(null);
|
|
642
|
+
const defaultColumnOrder = React3.useMemo(
|
|
643
|
+
() => columns.map(
|
|
644
|
+
(col) => col.id ? col.id : "accessorKey" in col ? String(col.accessorKey) : ""
|
|
645
|
+
),
|
|
646
|
+
[columns]
|
|
647
|
+
);
|
|
648
|
+
const [internalColumnOrder, setInternalColumnOrder] = React3.useState(defaultColumnOrder);
|
|
649
|
+
const columnOrder = externalColumnOrder ?? internalColumnOrder;
|
|
650
|
+
const setColumnOrder = onColumnOrderChange ?? setInternalColumnOrder;
|
|
651
|
+
const renderedLeafColumns = React3.useMemo(() => {
|
|
652
|
+
let leafColumns = table.getVisibleLeafColumns();
|
|
653
|
+
if (rowSelectionStyle === "hover" || enableRowDrag) {
|
|
654
|
+
leafColumns = leafColumns.filter((column) => column.id !== "select");
|
|
655
|
+
}
|
|
656
|
+
if (enableColumnDrag) {
|
|
657
|
+
leafColumns = [...leafColumns].sort((a, b) => {
|
|
658
|
+
const aIndex = columnOrder.indexOf(a.id);
|
|
659
|
+
const bIndex = columnOrder.indexOf(b.id);
|
|
660
|
+
if (aIndex === -1) return 1;
|
|
661
|
+
if (bIndex === -1) return -1;
|
|
662
|
+
return aIndex - bIndex;
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return leafColumns;
|
|
666
|
+
}, [table, rowSelectionStyle, enableRowDrag, enableColumnDrag, columnOrder]);
|
|
667
|
+
const renderedLeafIndexById = React3.useMemo(() => {
|
|
668
|
+
const indexById = /* @__PURE__ */ new Map();
|
|
669
|
+
renderedLeafColumns.forEach((column, index) => {
|
|
670
|
+
indexById.set(column.id, index);
|
|
671
|
+
});
|
|
672
|
+
return indexById;
|
|
673
|
+
}, [renderedLeafColumns]);
|
|
674
|
+
const stickyLeafColumns = React3.useMemo(() => {
|
|
675
|
+
if (explicitStickyColumnIds && explicitStickyColumnIds.length > 0) {
|
|
676
|
+
const stickyIdSet = new Set(explicitStickyColumnIds);
|
|
677
|
+
return renderedLeafColumns.filter((column) => stickyIdSet.has(column.id));
|
|
678
|
+
}
|
|
679
|
+
if (!stickyColumns || stickyColumns <= 0) return [];
|
|
680
|
+
return renderedLeafColumns.slice(0, stickyColumns);
|
|
681
|
+
}, [renderedLeafColumns, stickyColumns, explicitStickyColumnIds]);
|
|
682
|
+
const stickyColumnIds = React3.useMemo(
|
|
683
|
+
() => new Set(stickyLeafColumns.map((column) => column.id)),
|
|
684
|
+
[stickyLeafColumns]
|
|
685
|
+
);
|
|
686
|
+
const stickyColumnLefts = React3.useMemo(() => {
|
|
687
|
+
const lefts = /* @__PURE__ */ new Map();
|
|
688
|
+
let runningLeft = 0;
|
|
689
|
+
for (const column of stickyLeafColumns) {
|
|
690
|
+
lefts.set(column.id, runningLeft);
|
|
691
|
+
runningLeft += column.getSize();
|
|
692
|
+
}
|
|
693
|
+
return lefts;
|
|
694
|
+
}, [stickyLeafColumns]);
|
|
695
|
+
const renderedTableWidth = React3.useMemo(() => {
|
|
696
|
+
return renderedLeafColumns.reduce((total, column) => {
|
|
697
|
+
const resizedWidth = columnSizing[column.id];
|
|
698
|
+
const baseWidth = resizedWidth ?? column.getSize();
|
|
699
|
+
const minWidth = column.columnDef.minSize ?? 0;
|
|
700
|
+
const maxWidth = column.columnDef.maxSize;
|
|
701
|
+
const clamped = Math.max(
|
|
702
|
+
minWidth,
|
|
703
|
+
maxWidth !== void 0 ? Math.min(baseWidth, maxWidth) : baseWidth
|
|
704
|
+
);
|
|
705
|
+
return total + clamped;
|
|
706
|
+
}, 0);
|
|
707
|
+
}, [renderedLeafColumns, columnSizing]);
|
|
708
|
+
const [internalColumnWrapping, setInternalColumnWrapping] = React3.useState({});
|
|
709
|
+
const columnWrapping = externalColumnWrapping ?? internalColumnWrapping;
|
|
710
|
+
const setColumnWrapping = onColumnWrappingChange ?? setInternalColumnWrapping;
|
|
711
|
+
const [draggedColumnId, setDraggedColumnId] = React3.useState(
|
|
712
|
+
null
|
|
713
|
+
);
|
|
714
|
+
const [dragOverColumnId, setDragOverColumnId] = React3.useState(
|
|
715
|
+
null
|
|
716
|
+
);
|
|
717
|
+
const [dragOverColumnPosition, setDragOverColumnPosition] = React3.useState(null);
|
|
718
|
+
const handleColumnDragStart = React3.useCallback(
|
|
719
|
+
(e, columnId) => {
|
|
720
|
+
setDraggedColumnId(columnId);
|
|
721
|
+
e.dataTransfer.effectAllowed = "move";
|
|
722
|
+
e.dataTransfer.setData("text/plain", columnId);
|
|
723
|
+
requestAnimationFrame(() => {
|
|
724
|
+
e.target.closest("th")?.classList.add("opacity-50");
|
|
725
|
+
});
|
|
726
|
+
},
|
|
727
|
+
[]
|
|
728
|
+
);
|
|
729
|
+
const handleColumnDragOver = React3.useCallback(
|
|
730
|
+
(e, columnId) => {
|
|
731
|
+
e.preventDefault();
|
|
732
|
+
e.dataTransfer.dropEffect = "move";
|
|
733
|
+
if (draggedColumnId !== null && draggedColumnId !== columnId) {
|
|
734
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
735
|
+
const midX = rect.left + rect.width / 2;
|
|
736
|
+
const position = e.clientX < midX ? "left" : "right";
|
|
737
|
+
setDragOverColumnId(columnId);
|
|
738
|
+
setDragOverColumnPosition(position);
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
[draggedColumnId]
|
|
742
|
+
);
|
|
743
|
+
const handleColumnDragLeave = React3.useCallback(() => {
|
|
744
|
+
setDragOverColumnId(null);
|
|
745
|
+
setDragOverColumnPosition(null);
|
|
746
|
+
}, []);
|
|
747
|
+
const handleColumnDrop = React3.useCallback(
|
|
748
|
+
(e, targetColumnId) => {
|
|
749
|
+
e.preventDefault();
|
|
750
|
+
if (draggedColumnId && draggedColumnId !== targetColumnId) {
|
|
751
|
+
const currentOrder = [...columnOrder];
|
|
752
|
+
const fromIndex = currentOrder.indexOf(draggedColumnId);
|
|
753
|
+
let toIndex = currentOrder.indexOf(targetColumnId);
|
|
754
|
+
if (fromIndex !== -1 && toIndex !== -1) {
|
|
755
|
+
currentOrder.splice(fromIndex, 1);
|
|
756
|
+
toIndex = currentOrder.indexOf(targetColumnId);
|
|
757
|
+
if (dragOverColumnPosition === "right") {
|
|
758
|
+
toIndex += 1;
|
|
759
|
+
}
|
|
760
|
+
currentOrder.splice(toIndex, 0, draggedColumnId);
|
|
761
|
+
setColumnOrder(currentOrder);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
setDraggedColumnId(null);
|
|
765
|
+
setDragOverColumnId(null);
|
|
766
|
+
setDragOverColumnPosition(null);
|
|
767
|
+
},
|
|
768
|
+
[draggedColumnId, columnOrder, dragOverColumnPosition, setColumnOrder]
|
|
769
|
+
);
|
|
770
|
+
const handleColumnDragEnd = React3.useCallback((e) => {
|
|
771
|
+
e.target.closest("th")?.classList.remove("opacity-50");
|
|
772
|
+
setDraggedColumnId(null);
|
|
773
|
+
setDragOverColumnId(null);
|
|
774
|
+
setDragOverColumnPosition(null);
|
|
775
|
+
}, []);
|
|
776
|
+
const toggleColumnWrapping = React3.useCallback(
|
|
777
|
+
(columnId) => {
|
|
778
|
+
setColumnWrapping({
|
|
779
|
+
...columnWrapping,
|
|
780
|
+
[columnId]: !columnWrapping[columnId]
|
|
781
|
+
});
|
|
782
|
+
},
|
|
783
|
+
[columnWrapping, setColumnWrapping]
|
|
784
|
+
);
|
|
785
|
+
const [draggedRowIndex, setDraggedRowIndex] = React3.useState(
|
|
786
|
+
null
|
|
787
|
+
);
|
|
788
|
+
const [dragOverRowIndex, setDragOverRowIndex] = React3.useState(
|
|
789
|
+
null
|
|
790
|
+
);
|
|
791
|
+
const [dragOverPosition, setDragOverPosition] = React3.useState(null);
|
|
792
|
+
const handleRowDragStart = React3.useCallback(
|
|
793
|
+
(e, rowIndex) => {
|
|
794
|
+
setDraggedRowIndex(rowIndex);
|
|
795
|
+
e.dataTransfer.effectAllowed = "move";
|
|
796
|
+
e.dataTransfer.setData("text/plain", String(rowIndex));
|
|
797
|
+
requestAnimationFrame(() => {
|
|
798
|
+
e.target.closest("tr")?.classList.add("opacity-50");
|
|
799
|
+
});
|
|
800
|
+
},
|
|
801
|
+
[]
|
|
802
|
+
);
|
|
803
|
+
const handleRowDragOver = React3.useCallback(
|
|
804
|
+
(e, rowIndex) => {
|
|
805
|
+
e.preventDefault();
|
|
806
|
+
e.dataTransfer.dropEffect = "move";
|
|
807
|
+
if (draggedRowIndex !== null && draggedRowIndex !== rowIndex) {
|
|
808
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
809
|
+
const midY = rect.top + rect.height / 2;
|
|
810
|
+
const position = e.clientY < midY ? "above" : "below";
|
|
811
|
+
setDragOverRowIndex(rowIndex);
|
|
812
|
+
setDragOverPosition(position);
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
[draggedRowIndex]
|
|
816
|
+
);
|
|
817
|
+
const handleRowDragLeave = React3.useCallback(() => {
|
|
818
|
+
setDragOverRowIndex(null);
|
|
819
|
+
setDragOverPosition(null);
|
|
820
|
+
}, []);
|
|
821
|
+
const handleRowDrop = React3.useCallback(
|
|
822
|
+
(e, toIndex) => {
|
|
823
|
+
e.preventDefault();
|
|
824
|
+
const fromIndex = draggedRowIndex;
|
|
825
|
+
if (fromIndex !== null && fromIndex !== toIndex && onRowDragEnd) {
|
|
826
|
+
let targetIndex = toIndex;
|
|
827
|
+
if (dragOverPosition === "below") {
|
|
828
|
+
targetIndex = toIndex + 1;
|
|
829
|
+
}
|
|
830
|
+
if (fromIndex < targetIndex) {
|
|
831
|
+
targetIndex -= 1;
|
|
832
|
+
}
|
|
833
|
+
if (fromIndex !== targetIndex) {
|
|
834
|
+
onRowDragEnd(fromIndex, targetIndex);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
setDraggedRowIndex(null);
|
|
838
|
+
setDragOverRowIndex(null);
|
|
839
|
+
setDragOverPosition(null);
|
|
840
|
+
},
|
|
841
|
+
[draggedRowIndex, dragOverPosition, onRowDragEnd]
|
|
842
|
+
);
|
|
843
|
+
const handleRowDragEnd = React3.useCallback((e) => {
|
|
844
|
+
e.target.closest("tr")?.classList.remove("opacity-50");
|
|
845
|
+
setDraggedRowIndex(null);
|
|
846
|
+
setDragOverRowIndex(null);
|
|
847
|
+
setDragOverPosition(null);
|
|
848
|
+
}, []);
|
|
849
|
+
const handleResizeStart = React3.useCallback(
|
|
850
|
+
(columnId, startX, startWidth) => {
|
|
851
|
+
setResizingColumn(columnId);
|
|
852
|
+
const handleMouseMove = (e) => {
|
|
853
|
+
const delta = e.clientX - startX;
|
|
854
|
+
const newWidth = Math.max(20, startWidth + delta);
|
|
855
|
+
setColumnSizing((prev) => ({ ...prev, [columnId]: newWidth }));
|
|
856
|
+
};
|
|
857
|
+
const handleMouseUp = () => {
|
|
858
|
+
setResizingColumn(null);
|
|
859
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
860
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
861
|
+
document.body.style.cursor = "";
|
|
862
|
+
document.body.style.userSelect = "";
|
|
863
|
+
};
|
|
864
|
+
document.body.style.cursor = "col-resize";
|
|
865
|
+
document.body.style.userSelect = "none";
|
|
866
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
867
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
868
|
+
},
|
|
869
|
+
[]
|
|
870
|
+
);
|
|
871
|
+
const contextValue = React3.useMemo(
|
|
872
|
+
() => ({
|
|
873
|
+
columnWrapping,
|
|
874
|
+
toggleColumnWrapping,
|
|
875
|
+
enableColumnDrag: enableColumnDrag ?? false,
|
|
876
|
+
onColumnDragStart: handleColumnDragStart,
|
|
877
|
+
onColumnDragEnd: handleColumnDragEnd,
|
|
878
|
+
fontSize,
|
|
879
|
+
fontSizeClass,
|
|
880
|
+
density
|
|
881
|
+
}),
|
|
882
|
+
[
|
|
883
|
+
columnWrapping,
|
|
884
|
+
toggleColumnWrapping,
|
|
885
|
+
enableColumnDrag,
|
|
886
|
+
handleColumnDragStart,
|
|
887
|
+
handleColumnDragEnd,
|
|
888
|
+
fontSize,
|
|
889
|
+
fontSizeClass,
|
|
890
|
+
density
|
|
891
|
+
]
|
|
892
|
+
);
|
|
893
|
+
return /* @__PURE__ */ jsx(DataTableContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
|
|
894
|
+
"div",
|
|
895
|
+
{
|
|
896
|
+
className: cn(
|
|
897
|
+
"flex w-full max-w-full min-w-0 flex-col gap-2.5",
|
|
898
|
+
className
|
|
899
|
+
),
|
|
900
|
+
...props,
|
|
901
|
+
children: [
|
|
902
|
+
children,
|
|
903
|
+
filters && filters.length > 0 || onAddFilter ? /* @__PURE__ */ jsx("div", { className: "px-1", children: /* @__PURE__ */ jsx(
|
|
904
|
+
DataTableFilterBadges,
|
|
905
|
+
{
|
|
906
|
+
filters: filters ?? [],
|
|
907
|
+
onRemoveFilter: (id) => onRemoveFilter?.(id),
|
|
908
|
+
onChangeFilter: (id, value) => onChangeFilter?.(id, value),
|
|
909
|
+
onClearAll: onClearAllFilters,
|
|
910
|
+
onAddFilter
|
|
911
|
+
}
|
|
912
|
+
) }) : null,
|
|
913
|
+
/* @__PURE__ */ jsx("div", { className: cn("mx-1", fontSizeClass), children: /* @__PURE__ */ jsx(
|
|
914
|
+
"div",
|
|
915
|
+
{
|
|
916
|
+
className: cn(
|
|
917
|
+
"overflow-x-auto rounded-t-md [&::-webkit-scrollbar-track]:bg-transparent",
|
|
918
|
+
summary ? "border-t" : "border rounded-b-md"
|
|
919
|
+
),
|
|
920
|
+
children: /* @__PURE__ */ jsxs(
|
|
921
|
+
"table",
|
|
922
|
+
{
|
|
923
|
+
className: cn(
|
|
924
|
+
"caption-bottom border-separate border-spacing-0",
|
|
925
|
+
fontSizeClass
|
|
926
|
+
),
|
|
927
|
+
style: {
|
|
928
|
+
width: `max(100%, ${renderedTableWidth}px)`,
|
|
929
|
+
tableLayout: "fixed"
|
|
930
|
+
},
|
|
931
|
+
children: [
|
|
932
|
+
/* @__PURE__ */ jsx(TableHeader, { children: table.getHeaderGroups().map((headerGroup) => {
|
|
933
|
+
const renderableHeaders = headerGroup.headers.filter(
|
|
934
|
+
(h) => !((rowSelectionStyle === "hover" || enableRowDrag) && h.id === "select")
|
|
935
|
+
);
|
|
936
|
+
const orderedHeaders = enableColumnDrag ? [...renderableHeaders].sort((a, b) => {
|
|
937
|
+
const aIndex = columnOrder.indexOf(a.id);
|
|
938
|
+
const bIndex = columnOrder.indexOf(b.id);
|
|
939
|
+
if (aIndex === -1) return 1;
|
|
940
|
+
if (bIndex === -1) return -1;
|
|
941
|
+
return aIndex - bIndex;
|
|
942
|
+
}) : renderableHeaders;
|
|
943
|
+
return /* @__PURE__ */ jsxs(
|
|
944
|
+
TableRow,
|
|
945
|
+
{
|
|
946
|
+
className: "hover:bg-transparent",
|
|
947
|
+
children: [
|
|
948
|
+
(rowSelectionStyle === "hover" || enableRowDrag) && /* @__PURE__ */ jsx(
|
|
949
|
+
TableHead,
|
|
950
|
+
{
|
|
951
|
+
className: cn(
|
|
952
|
+
"p-0 border-r-0 whitespace-nowrap border-l border-b border-border group/header-gutter",
|
|
953
|
+
headerHeight
|
|
954
|
+
),
|
|
955
|
+
style: { width: "1%" },
|
|
956
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-0.5 px-1 h-full", children: [
|
|
957
|
+
enableRowDrag && /* @__PURE__ */ jsx(
|
|
958
|
+
"div",
|
|
959
|
+
{
|
|
960
|
+
className: cn(
|
|
961
|
+
"shrink-0",
|
|
962
|
+
density === "compact" ? "size-5" : "size-6"
|
|
963
|
+
),
|
|
964
|
+
"aria-hidden": "true"
|
|
965
|
+
}
|
|
966
|
+
),
|
|
967
|
+
/* @__PURE__ */ jsxs(
|
|
968
|
+
"button",
|
|
969
|
+
{
|
|
970
|
+
type: "button",
|
|
971
|
+
onClick: () => table.toggleAllPageRowsSelected(
|
|
972
|
+
!table.getIsAllPageRowsSelected()
|
|
973
|
+
),
|
|
974
|
+
className: cn(
|
|
975
|
+
"flex items-center justify-center rounded border transition-all",
|
|
976
|
+
checkboxSize,
|
|
977
|
+
table.getIsAllPageRowsSelected() ? "bg-primary border-primary text-primary-foreground opacity-100" : table.getIsSomePageRowsSelected() ? "bg-primary/50 border-primary text-primary-foreground opacity-100" : "border-border hover:border-primary/50 hover:bg-accent opacity-0 group-hover/header-gutter:opacity-100"
|
|
978
|
+
),
|
|
979
|
+
"aria-label": table.getIsAllPageRowsSelected() ? "Deselect all rows" : "Select all rows",
|
|
980
|
+
children: [
|
|
981
|
+
table.getIsAllPageRowsSelected() && /* @__PURE__ */ jsx(
|
|
982
|
+
"svg",
|
|
983
|
+
{
|
|
984
|
+
className: checkmarkSize,
|
|
985
|
+
viewBox: "0 0 12 12",
|
|
986
|
+
fill: "none",
|
|
987
|
+
"aria-hidden": "true",
|
|
988
|
+
children: /* @__PURE__ */ jsx(
|
|
989
|
+
"path",
|
|
990
|
+
{
|
|
991
|
+
d: "M2.5 6L5 8.5L9.5 3.5",
|
|
992
|
+
stroke: "currentColor",
|
|
993
|
+
strokeWidth: "2",
|
|
994
|
+
strokeLinecap: "round",
|
|
995
|
+
strokeLinejoin: "round"
|
|
996
|
+
}
|
|
997
|
+
)
|
|
998
|
+
}
|
|
999
|
+
),
|
|
1000
|
+
table.getIsSomePageRowsSelected() && !table.getIsAllPageRowsSelected() && /* @__PURE__ */ jsx(
|
|
1001
|
+
"svg",
|
|
1002
|
+
{
|
|
1003
|
+
className: checkmarkSize,
|
|
1004
|
+
viewBox: "0 0 12 12",
|
|
1005
|
+
fill: "none",
|
|
1006
|
+
"aria-hidden": "true",
|
|
1007
|
+
children: /* @__PURE__ */ jsx(
|
|
1008
|
+
"path",
|
|
1009
|
+
{
|
|
1010
|
+
d: "M2.5 6H9.5",
|
|
1011
|
+
stroke: "currentColor",
|
|
1012
|
+
strokeWidth: "2",
|
|
1013
|
+
strokeLinecap: "round"
|
|
1014
|
+
}
|
|
1015
|
+
)
|
|
1016
|
+
}
|
|
1017
|
+
)
|
|
1018
|
+
]
|
|
1019
|
+
}
|
|
1020
|
+
)
|
|
1021
|
+
] })
|
|
1022
|
+
}
|
|
1023
|
+
),
|
|
1024
|
+
orderedHeaders.map((header, headerIndex) => {
|
|
1025
|
+
const isLastColumn = headerIndex === orderedHeaders.length - 1;
|
|
1026
|
+
const hasGutter = rowSelectionStyle === "hover" || enableRowDrag;
|
|
1027
|
+
const isFirstColumn = headerIndex === 0 && !hasGutter;
|
|
1028
|
+
const headerLeafColumnIds = header.getLeafHeaders().map((leafHeader) => leafHeader.column.id).filter(
|
|
1029
|
+
(columnId) => renderedLeafIndexById.has(columnId)
|
|
1030
|
+
);
|
|
1031
|
+
const stickyHeaderLeafIds = headerLeafColumnIds.filter(
|
|
1032
|
+
(columnId) => stickyColumnIds.has(columnId)
|
|
1033
|
+
);
|
|
1034
|
+
const isStickyColumn = headerLeafColumnIds.length > 0 && stickyHeaderLeafIds.length === headerLeafColumnIds.length;
|
|
1035
|
+
const stickyHeaderColumnId = isStickyColumn ? stickyHeaderLeafIds.sort(
|
|
1036
|
+
(a, b) => (renderedLeafIndexById.get(a) ?? 0) - (renderedLeafIndexById.get(b) ?? 0)
|
|
1037
|
+
)[0] : void 0;
|
|
1038
|
+
const stickyLeft = stickyHeaderColumnId ? stickyColumnLefts.get(stickyHeaderColumnId) ?? 0 : 0;
|
|
1039
|
+
const headerDef = header.column.columnDef.header;
|
|
1040
|
+
const isSimpleHeader = typeof headerDef === "string";
|
|
1041
|
+
const colDef = header.column.columnDef;
|
|
1042
|
+
const isFixedWidth = colDef.size && colDef.minSize === colDef.size && colDef.maxSize === colDef.size;
|
|
1043
|
+
const isActionsColumn = header.id === "actions" || header.id === "select";
|
|
1044
|
+
const resizedWidth = isFixedWidth ? void 0 : columnSizing[header.id];
|
|
1045
|
+
const canDragColumn = enableColumnDrag && !isFixedWidth && !isActionsColumn;
|
|
1046
|
+
const isDragOverLeft = dragOverColumnId === header.id && dragOverColumnPosition === "left";
|
|
1047
|
+
const isDragOverRight = dragOverColumnId === header.id && dragOverColumnPosition === "right";
|
|
1048
|
+
const firstLeafIndex = Math.min(
|
|
1049
|
+
...headerLeafColumnIds.map(
|
|
1050
|
+
(columnId) => renderedLeafIndexById.get(columnId) ?? 0
|
|
1051
|
+
)
|
|
1052
|
+
);
|
|
1053
|
+
const isFirstNonStickyAfterSticky = !isStickyColumn && firstLeafIndex > 0 && stickyColumnIds.has(
|
|
1054
|
+
renderedLeafColumns[firstLeafIndex - 1]?.id ?? ""
|
|
1055
|
+
);
|
|
1056
|
+
return /* @__PURE__ */ jsxs(
|
|
1057
|
+
TableHead,
|
|
1058
|
+
{
|
|
1059
|
+
"data-sticky-column": isStickyColumn ? "true" : void 0,
|
|
1060
|
+
colSpan: header.colSpan,
|
|
1061
|
+
onDragOver: canDragColumn ? (e) => handleColumnDragOver(e, header.id) : void 0,
|
|
1062
|
+
onDragLeave: canDragColumn ? handleColumnDragLeave : void 0,
|
|
1063
|
+
onDrop: canDragColumn ? (e) => handleColumnDrop(e, header.id) : void 0,
|
|
1064
|
+
className: cn(
|
|
1065
|
+
isSimpleHeader ? "px-2" : "p-0",
|
|
1066
|
+
// Add padding for simple headers, let component headers handle their own
|
|
1067
|
+
headerHeight,
|
|
1068
|
+
"relative",
|
|
1069
|
+
"overflow-hidden text-ellipsis border-b border-border",
|
|
1070
|
+
!columnWrapping[header.id] && "whitespace-nowrap",
|
|
1071
|
+
columnWrapping[header.id] && "whitespace-normal",
|
|
1072
|
+
isFirstColumn && "border-l border-border",
|
|
1073
|
+
!isStickyColumn && "border-r border-border",
|
|
1074
|
+
isStickyColumn && "border-r border-border",
|
|
1075
|
+
isFirstNonStickyAfterSticky && "border-l border-border",
|
|
1076
|
+
isDragOverLeft && "border-l-2 border-l-primary",
|
|
1077
|
+
isDragOverRight && "border-r-2 border-r-primary"
|
|
1078
|
+
),
|
|
1079
|
+
style: {
|
|
1080
|
+
...isActionsColumn ? { width: "1%" } : {
|
|
1081
|
+
width: resizedWidth ?? header.getSize(),
|
|
1082
|
+
minWidth: header.column.columnDef.minSize,
|
|
1083
|
+
maxWidth: header.column.columnDef.maxSize
|
|
1084
|
+
},
|
|
1085
|
+
...isStickyColumn ? {
|
|
1086
|
+
position: "sticky",
|
|
1087
|
+
left: stickyLeft,
|
|
1088
|
+
zIndex: 60,
|
|
1089
|
+
background: "hsl(var(--background) / 1)",
|
|
1090
|
+
backgroundColor: "hsl(var(--background) / 1)",
|
|
1091
|
+
opacity: 1,
|
|
1092
|
+
backgroundClip: "padding-box",
|
|
1093
|
+
boxShadow: "inset -1px 0 0 hsl(var(--border)), inset 0 -1px 0 hsl(var(--border))",
|
|
1094
|
+
isolation: "isolate",
|
|
1095
|
+
contain: "paint",
|
|
1096
|
+
backfaceVisibility: "hidden",
|
|
1097
|
+
mixBlendMode: "normal",
|
|
1098
|
+
backdropFilter: "none",
|
|
1099
|
+
WebkitBackdropFilter: "none",
|
|
1100
|
+
transform: "translateZ(0)",
|
|
1101
|
+
WebkitTransform: "translateZ(0)"
|
|
1102
|
+
} : {}
|
|
1103
|
+
},
|
|
1104
|
+
children: [
|
|
1105
|
+
isStickyColumn && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 top-0 bottom-px z-0 bg-background" }),
|
|
1106
|
+
header.isPlaceholder ? null : /* @__PURE__ */ jsxs(
|
|
1107
|
+
"div",
|
|
1108
|
+
{
|
|
1109
|
+
className: cn(
|
|
1110
|
+
"relative z-10",
|
|
1111
|
+
enableTreeView && headerIndex === 0 && "flex items-center"
|
|
1112
|
+
),
|
|
1113
|
+
children: [
|
|
1114
|
+
enableTreeView && headerIndex === 0 && /* @__PURE__ */ jsx(
|
|
1115
|
+
TreeExpandCycleButton,
|
|
1116
|
+
{
|
|
1117
|
+
table,
|
|
1118
|
+
density
|
|
1119
|
+
}
|
|
1120
|
+
),
|
|
1121
|
+
isSimpleHeader ? /* @__PURE__ */ jsx(
|
|
1122
|
+
"span",
|
|
1123
|
+
{
|
|
1124
|
+
className: cn("font-medium", fontSizeClass),
|
|
1125
|
+
children: headerDef
|
|
1126
|
+
}
|
|
1127
|
+
) : flexRender(
|
|
1128
|
+
header.column.columnDef.header,
|
|
1129
|
+
header.getContext()
|
|
1130
|
+
)
|
|
1131
|
+
]
|
|
1132
|
+
}
|
|
1133
|
+
),
|
|
1134
|
+
enableColumnResizing && !isLastColumn && !isFixedWidth && // biome-ignore lint/a11y/noStaticElementInteractions: Column resize uses mouse drag interaction only
|
|
1135
|
+
/* @__PURE__ */ jsx(
|
|
1136
|
+
"div",
|
|
1137
|
+
{
|
|
1138
|
+
onMouseDown: (e) => {
|
|
1139
|
+
e.preventDefault();
|
|
1140
|
+
e.stopPropagation();
|
|
1141
|
+
const th = e.currentTarget.parentElement;
|
|
1142
|
+
if (th) {
|
|
1143
|
+
handleResizeStart(
|
|
1144
|
+
header.id,
|
|
1145
|
+
e.clientX,
|
|
1146
|
+
th.offsetWidth
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
className: cn(
|
|
1151
|
+
"absolute -right-1 top-0 z-10 h-full w-3 cursor-col-resize select-none",
|
|
1152
|
+
"before:absolute before:right-1 before:top-0 before:h-full before:w-px before:bg-transparent before:transition-colors",
|
|
1153
|
+
"hover:before:bg-primary/50",
|
|
1154
|
+
resizingColumn === header.id && "before:bg-primary"
|
|
1155
|
+
)
|
|
1156
|
+
}
|
|
1157
|
+
)
|
|
1158
|
+
]
|
|
1159
|
+
},
|
|
1160
|
+
header.id
|
|
1161
|
+
);
|
|
1162
|
+
})
|
|
1163
|
+
]
|
|
1164
|
+
},
|
|
1165
|
+
headerGroup.id
|
|
1166
|
+
);
|
|
1167
|
+
}) }),
|
|
1168
|
+
/* @__PURE__ */ jsx(
|
|
1169
|
+
TableBody,
|
|
1170
|
+
{
|
|
1171
|
+
className: summary ? "[&_tr:last-child]:border-b" : void 0,
|
|
1172
|
+
children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row, rowIndex) => {
|
|
1173
|
+
const isHovered = hoveredRowId === row.id;
|
|
1174
|
+
const isSelected = row.getIsSelected();
|
|
1175
|
+
const showRowControls = isHovered || isSelected;
|
|
1176
|
+
const isDragOverAbove = dragOverRowIndex === rowIndex && dragOverPosition === "above";
|
|
1177
|
+
const isDragOverBelow = dragOverRowIndex === rowIndex && dragOverPosition === "below";
|
|
1178
|
+
return /* @__PURE__ */ jsxs(
|
|
1179
|
+
TableRow,
|
|
1180
|
+
{
|
|
1181
|
+
"data-state": isSelected && "selected",
|
|
1182
|
+
"data-hovered": isHovered,
|
|
1183
|
+
onMouseEnter: () => setHoveredRowId(row.id),
|
|
1184
|
+
onMouseLeave: () => setHoveredRowId(null),
|
|
1185
|
+
onClick: () => onRowClick?.(row),
|
|
1186
|
+
onDragOver: enableRowDrag ? (e) => handleRowDragOver(e, rowIndex) : void 0,
|
|
1187
|
+
onDragLeave: enableRowDrag ? handleRowDragLeave : void 0,
|
|
1188
|
+
onDrop: enableRowDrag ? (e) => handleRowDrop(e, rowIndex) : void 0,
|
|
1189
|
+
className: cn(
|
|
1190
|
+
"group/row",
|
|
1191
|
+
onRowClick && "cursor-pointer hover:bg-muted/50",
|
|
1192
|
+
getRowClassName2?.(row),
|
|
1193
|
+
// Add bottom border to last row when summary exists
|
|
1194
|
+
summary && rowIndex === table.getRowModel().rows.length - 1 && "border-b"
|
|
1195
|
+
),
|
|
1196
|
+
children: [
|
|
1197
|
+
(rowSelectionStyle === "hover" || enableRowDrag) && /* @__PURE__ */ jsx(
|
|
1198
|
+
TableCell,
|
|
1199
|
+
{
|
|
1200
|
+
className: cn(
|
|
1201
|
+
"p-0 border-r-0 whitespace-nowrap border-l border-border",
|
|
1202
|
+
// All rows get bottom border except last row without summary
|
|
1203
|
+
rowIndex !== table.getRowModel().rows.length - 1 && "border-b border-border",
|
|
1204
|
+
// Last row only gets border-b when summary exists, plus rounding
|
|
1205
|
+
summary && rowIndex === table.getRowModel().rows.length - 1 && "border-b border-border rounded-bl-md overflow-hidden",
|
|
1206
|
+
// Drag-over indicator
|
|
1207
|
+
isDragOverAbove && "border-t-2 border-t-primary",
|
|
1208
|
+
isDragOverBelow && "border-b-2 border-b-primary"
|
|
1209
|
+
),
|
|
1210
|
+
style: { width: "1%" },
|
|
1211
|
+
children: /* @__PURE__ */ jsxs(
|
|
1212
|
+
"div",
|
|
1213
|
+
{
|
|
1214
|
+
className: cn(
|
|
1215
|
+
"flex items-center justify-end gap-0.5 px-1 h-full transition-opacity",
|
|
1216
|
+
showRowControls ? "opacity-100" : "opacity-0"
|
|
1217
|
+
),
|
|
1218
|
+
children: [
|
|
1219
|
+
enableRowDrag && /* @__PURE__ */ jsx(
|
|
1220
|
+
"div",
|
|
1221
|
+
{
|
|
1222
|
+
draggable: true,
|
|
1223
|
+
onDragStart: (e) => handleRowDragStart(e, rowIndex),
|
|
1224
|
+
onDragEnd: handleRowDragEnd,
|
|
1225
|
+
className: cn(
|
|
1226
|
+
"flex items-center justify-center text-muted-foreground/50 hover:text-muted-foreground cursor-grab active:cursor-grabbing rounded hover:bg-accent",
|
|
1227
|
+
density === "compact" ? "size-5" : "size-6"
|
|
1228
|
+
),
|
|
1229
|
+
"aria-hidden": "true",
|
|
1230
|
+
children: /* @__PURE__ */ jsx(
|
|
1231
|
+
GripVertical,
|
|
1232
|
+
{
|
|
1233
|
+
className: density === "compact" ? "size-3" : "size-4"
|
|
1234
|
+
}
|
|
1235
|
+
)
|
|
1236
|
+
}
|
|
1237
|
+
),
|
|
1238
|
+
/* @__PURE__ */ jsx(
|
|
1239
|
+
"button",
|
|
1240
|
+
{
|
|
1241
|
+
type: "button",
|
|
1242
|
+
onClick: () => row.toggleSelected(!row.getIsSelected()),
|
|
1243
|
+
className: cn(
|
|
1244
|
+
"flex items-center justify-center rounded border transition-colors",
|
|
1245
|
+
checkboxSize,
|
|
1246
|
+
isSelected ? "bg-primary border-primary text-primary-foreground" : "border-border hover:border-primary/50 hover:bg-accent"
|
|
1247
|
+
),
|
|
1248
|
+
"aria-label": isSelected ? "Deselect row" : "Select row",
|
|
1249
|
+
children: isSelected && /* @__PURE__ */ jsx(
|
|
1250
|
+
"svg",
|
|
1251
|
+
{
|
|
1252
|
+
className: checkmarkSize,
|
|
1253
|
+
viewBox: "0 0 12 12",
|
|
1254
|
+
fill: "none",
|
|
1255
|
+
"aria-hidden": "true",
|
|
1256
|
+
children: /* @__PURE__ */ jsx(
|
|
1257
|
+
"path",
|
|
1258
|
+
{
|
|
1259
|
+
d: "M2.5 6L5 8.5L9.5 3.5",
|
|
1260
|
+
stroke: "currentColor",
|
|
1261
|
+
strokeWidth: "2",
|
|
1262
|
+
strokeLinecap: "round",
|
|
1263
|
+
strokeLinejoin: "round"
|
|
1264
|
+
}
|
|
1265
|
+
)
|
|
1266
|
+
}
|
|
1267
|
+
)
|
|
1268
|
+
}
|
|
1269
|
+
)
|
|
1270
|
+
]
|
|
1271
|
+
}
|
|
1272
|
+
)
|
|
1273
|
+
}
|
|
1274
|
+
),
|
|
1275
|
+
row.getVisibleCells().map((cell) => {
|
|
1276
|
+
if ((rowSelectionStyle === "hover" || enableRowDrag) && cell.column.id === "select") {
|
|
1277
|
+
return null;
|
|
1278
|
+
}
|
|
1279
|
+
return cell;
|
|
1280
|
+
}).filter(
|
|
1281
|
+
(cell) => cell !== null
|
|
1282
|
+
).sort((a, b) => {
|
|
1283
|
+
if (!enableColumnDrag) return 0;
|
|
1284
|
+
const aIndex = columnOrder.indexOf(a.column.id);
|
|
1285
|
+
const bIndex = columnOrder.indexOf(b.column.id);
|
|
1286
|
+
if (aIndex === -1) return 1;
|
|
1287
|
+
if (bIndex === -1) return -1;
|
|
1288
|
+
return aIndex - bIndex;
|
|
1289
|
+
}).map((cell, cellIndex, sortedCells) => {
|
|
1290
|
+
const isLastColumn = cellIndex === sortedCells.length - 1;
|
|
1291
|
+
const hasGutter = rowSelectionStyle === "hover" || enableRowDrag;
|
|
1292
|
+
const isFirstColumn = cellIndex === 0 && !hasGutter;
|
|
1293
|
+
const isStickyCell = stickyColumnIds.has(
|
|
1294
|
+
cell.column.id
|
|
1295
|
+
);
|
|
1296
|
+
const stickyLeft = stickyColumnLefts.get(cell.column.id) ?? 0;
|
|
1297
|
+
const isFirstNonStickyAfterSticky = !isStickyCell && cellIndex > 0 && stickyColumnIds.has(
|
|
1298
|
+
sortedCells[cellIndex - 1]?.column.id ?? ""
|
|
1299
|
+
);
|
|
1300
|
+
const colDef = cell.column.columnDef;
|
|
1301
|
+
const isFixedWidth = colDef.size && colDef.minSize === colDef.size && colDef.maxSize === colDef.size;
|
|
1302
|
+
const isActionsColumn = cell.column.id === "actions" || cell.column.id === "select";
|
|
1303
|
+
const resizedWidth = isFixedWidth ? void 0 : columnSizing[cell.column.id];
|
|
1304
|
+
const isWrapped = columnWrapping[cell.column.id];
|
|
1305
|
+
const colMeta = cell.column.columnDef.meta;
|
|
1306
|
+
const colAlign = colMeta?.align ?? "left";
|
|
1307
|
+
const allowHorizontalScroll = colMeta?.allowHorizontalScroll === true;
|
|
1308
|
+
const alignClass = colAlign === "right" ? "text-right" : colAlign === "center" ? "text-center" : "";
|
|
1309
|
+
const isFirstDataCell = cellIndex === 0;
|
|
1310
|
+
const showTreeUI = enableTreeView && isFirstDataCell;
|
|
1311
|
+
const treeIndent = showTreeUI ? row.depth * treeIndentPx : 0;
|
|
1312
|
+
const canExpand = showTreeUI && row.getCanExpand();
|
|
1313
|
+
return /* @__PURE__ */ jsxs(
|
|
1314
|
+
TableCell,
|
|
1315
|
+
{
|
|
1316
|
+
"data-sticky-column": isStickyCell ? "true" : void 0,
|
|
1317
|
+
className: cn(
|
|
1318
|
+
cellPadding,
|
|
1319
|
+
isStickyCell && "relative",
|
|
1320
|
+
!isWrapped && "overflow-hidden text-ellipsis whitespace-nowrap",
|
|
1321
|
+
isWrapped && "whitespace-normal break-words",
|
|
1322
|
+
!isLastColumn && !isStickyCell && "border-r border-border",
|
|
1323
|
+
isStickyCell && "border-r border-border",
|
|
1324
|
+
isFirstColumn && "border-l border-border",
|
|
1325
|
+
isFirstNonStickyAfterSticky && "border-l border-border",
|
|
1326
|
+
isLastColumn && "border-r border-border",
|
|
1327
|
+
isActionsColumn && "whitespace-nowrap",
|
|
1328
|
+
// All rows get bottom border except last row without summary
|
|
1329
|
+
rowIndex !== table.getRowModel().rows.length - 1 && "border-b border-border",
|
|
1330
|
+
// Last row only gets border-b when summary exists, plus rounding on corners
|
|
1331
|
+
summary && rowIndex === table.getRowModel().rows.length - 1 && "border-b border-border",
|
|
1332
|
+
summary && rowIndex === table.getRowModel().rows.length - 1 && isLastColumn && "rounded-br-md overflow-hidden",
|
|
1333
|
+
summary && rowIndex === table.getRowModel().rows.length - 1 && isFirstColumn && "rounded-bl-md overflow-hidden"
|
|
1334
|
+
),
|
|
1335
|
+
style: {
|
|
1336
|
+
width: resizedWidth || cell.column.getSize(),
|
|
1337
|
+
minWidth: cell.column.columnDef.minSize,
|
|
1338
|
+
maxWidth: cell.column.columnDef.maxSize,
|
|
1339
|
+
...isStickyCell ? {
|
|
1340
|
+
position: "sticky",
|
|
1341
|
+
left: stickyLeft,
|
|
1342
|
+
zIndex: 50,
|
|
1343
|
+
background: "hsl(var(--background) / 1)",
|
|
1344
|
+
backgroundColor: "hsl(var(--background) / 1)",
|
|
1345
|
+
opacity: 1,
|
|
1346
|
+
backgroundClip: "padding-box",
|
|
1347
|
+
boxShadow: "inset 0 0 0 9999px hsl(var(--background)), inset -1px 0 0 hsl(var(--border))",
|
|
1348
|
+
borderBottom: "1px solid hsl(var(--border))",
|
|
1349
|
+
isolation: "isolate",
|
|
1350
|
+
contain: "paint",
|
|
1351
|
+
backfaceVisibility: "hidden",
|
|
1352
|
+
mixBlendMode: "normal",
|
|
1353
|
+
backdropFilter: "none",
|
|
1354
|
+
WebkitBackdropFilter: "none",
|
|
1355
|
+
transform: "translateZ(0)",
|
|
1356
|
+
WebkitTransform: "translateZ(0)"
|
|
1357
|
+
} : {}
|
|
1358
|
+
},
|
|
1359
|
+
children: [
|
|
1360
|
+
isStickyCell && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-0 z-0 border-b border-border bg-background" }),
|
|
1361
|
+
showTreeUI ? /* @__PURE__ */ jsxs(
|
|
1362
|
+
"div",
|
|
1363
|
+
{
|
|
1364
|
+
className: "relative z-10 flex items-center gap-1",
|
|
1365
|
+
style: { paddingLeft: `${treeIndent}px` },
|
|
1366
|
+
children: [
|
|
1367
|
+
canExpand ? /* @__PURE__ */ jsx(
|
|
1368
|
+
"button",
|
|
1369
|
+
{
|
|
1370
|
+
type: "button",
|
|
1371
|
+
onClick: (e) => {
|
|
1372
|
+
e.stopPropagation();
|
|
1373
|
+
row.toggleExpanded();
|
|
1374
|
+
},
|
|
1375
|
+
className: "shrink-0 text-muted-foreground hover:text-foreground",
|
|
1376
|
+
"aria-label": row.getIsExpanded() ? "Collapse" : "Expand",
|
|
1377
|
+
children: row.getIsExpanded() ? /* @__PURE__ */ jsx(ChevronDown, { className: "size-4" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "size-4" })
|
|
1378
|
+
}
|
|
1379
|
+
) : /* @__PURE__ */ jsx(
|
|
1380
|
+
"span",
|
|
1381
|
+
{
|
|
1382
|
+
className: "inline-block shrink-0 size-4",
|
|
1383
|
+
"aria-hidden": "true"
|
|
1384
|
+
}
|
|
1385
|
+
),
|
|
1386
|
+
/* @__PURE__ */ jsx(
|
|
1387
|
+
"div",
|
|
1388
|
+
{
|
|
1389
|
+
className: cn(
|
|
1390
|
+
"min-w-0",
|
|
1391
|
+
!isWrapped && !allowHorizontalScroll && "truncate",
|
|
1392
|
+
allowHorizontalScroll && "overflow-x-auto whitespace-nowrap [&::-webkit-scrollbar]:hidden"
|
|
1393
|
+
),
|
|
1394
|
+
style: allowHorizontalScroll ? {
|
|
1395
|
+
scrollbarWidth: "none",
|
|
1396
|
+
msOverflowStyle: "none"
|
|
1397
|
+
} : void 0,
|
|
1398
|
+
children: flexRender(
|
|
1399
|
+
cell.column.columnDef.cell,
|
|
1400
|
+
cell.getContext()
|
|
1401
|
+
)
|
|
1402
|
+
}
|
|
1403
|
+
)
|
|
1404
|
+
]
|
|
1405
|
+
}
|
|
1406
|
+
) : /* @__PURE__ */ jsx(
|
|
1407
|
+
"div",
|
|
1408
|
+
{
|
|
1409
|
+
className: cn(
|
|
1410
|
+
"relative z-10",
|
|
1411
|
+
!isWrapped && !allowHorizontalScroll && "truncate",
|
|
1412
|
+
allowHorizontalScroll && "overflow-x-auto whitespace-nowrap [&::-webkit-scrollbar]:hidden",
|
|
1413
|
+
alignClass
|
|
1414
|
+
),
|
|
1415
|
+
style: allowHorizontalScroll ? {
|
|
1416
|
+
scrollbarWidth: "none",
|
|
1417
|
+
msOverflowStyle: "none"
|
|
1418
|
+
} : void 0,
|
|
1419
|
+
children: flexRender(
|
|
1420
|
+
cell.column.columnDef.cell,
|
|
1421
|
+
cell.getContext()
|
|
1422
|
+
)
|
|
1423
|
+
}
|
|
1424
|
+
)
|
|
1425
|
+
]
|
|
1426
|
+
},
|
|
1427
|
+
cell.id
|
|
1428
|
+
);
|
|
1429
|
+
})
|
|
1430
|
+
]
|
|
1431
|
+
},
|
|
1432
|
+
row.id
|
|
1433
|
+
);
|
|
1434
|
+
}) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
|
|
1435
|
+
TableCell,
|
|
1436
|
+
{
|
|
1437
|
+
colSpan: columns.length + (rowSelectionStyle === "hover" || enableRowDrag ? 1 : 0),
|
|
1438
|
+
className: "h-24",
|
|
1439
|
+
children: /* @__PURE__ */ jsx("div", { className: "sticky left-0 w-fit px-4 text-muted-foreground", children: "No results." })
|
|
1440
|
+
}
|
|
1441
|
+
) })
|
|
1442
|
+
}
|
|
1443
|
+
),
|
|
1444
|
+
summary && /* @__PURE__ */ jsx("tfoot", { children: summary })
|
|
1445
|
+
]
|
|
1446
|
+
}
|
|
1447
|
+
)
|
|
1448
|
+
}
|
|
1449
|
+
) }),
|
|
1450
|
+
actionBar && table.getFilteredSelectedRowModel().rows.length > 0 && actionBar,
|
|
1451
|
+
floatingBar && table.getFilteredSelectedRowModel().rows.length > 0 && floatingBar
|
|
1452
|
+
]
|
|
1453
|
+
}
|
|
1454
|
+
) });
|
|
1455
|
+
}
|
|
1456
|
+
var defaultColumnIcons = {
|
|
1457
|
+
text: Text,
|
|
1458
|
+
number: Hash,
|
|
1459
|
+
date: Calendar,
|
|
1460
|
+
boolean: CheckSquare,
|
|
1461
|
+
enum: List,
|
|
1462
|
+
custom: Text
|
|
1463
|
+
};
|
|
1464
|
+
function getColumnIcon(column, propIcon, propColumnType) {
|
|
1465
|
+
const meta = column.columnDef.meta;
|
|
1466
|
+
if (propIcon) return propIcon;
|
|
1467
|
+
if (meta?.icon) return meta.icon;
|
|
1468
|
+
const columnType = propColumnType ?? meta?.columnType;
|
|
1469
|
+
if (columnType) return defaultColumnIcons[columnType];
|
|
1470
|
+
return void 0;
|
|
1471
|
+
}
|
|
1472
|
+
function DataTableColumnHeader({
|
|
1473
|
+
column,
|
|
1474
|
+
title,
|
|
1475
|
+
className,
|
|
1476
|
+
columnType,
|
|
1477
|
+
icon,
|
|
1478
|
+
filterable,
|
|
1479
|
+
filterType,
|
|
1480
|
+
filterOptions,
|
|
1481
|
+
onFilterChange,
|
|
1482
|
+
isWrapped: isWrappedProp,
|
|
1483
|
+
onToggleWrap: onToggleWrapProp,
|
|
1484
|
+
customMenuItems
|
|
1485
|
+
}) {
|
|
1486
|
+
const meta = column.columnDef.meta;
|
|
1487
|
+
const dataTableContext = useDataTableContext();
|
|
1488
|
+
const isWrapped = isWrappedProp ?? dataTableContext?.columnWrapping[column.id] ?? false;
|
|
1489
|
+
const onToggleWrap = onToggleWrapProp ?? (() => dataTableContext?.toggleColumnWrapping(column.id));
|
|
1490
|
+
const canSort = column.getCanSort();
|
|
1491
|
+
const canHide = column.getCanHide();
|
|
1492
|
+
const isSorted = column.getIsSorted();
|
|
1493
|
+
const isFiltered = column.getIsFiltered();
|
|
1494
|
+
const Icon = getColumnIcon(column, icon, columnType);
|
|
1495
|
+
const showFilter = filterable ?? meta?.filterable ?? false;
|
|
1496
|
+
const resolvedFilterType = filterType ?? meta?.filterType ?? "text";
|
|
1497
|
+
const resolvedFilterOptions = filterOptions ?? meta?.filterOptions;
|
|
1498
|
+
const filterPlaceholder = meta?.filterPlaceholder;
|
|
1499
|
+
const [isOpen, setIsOpen] = React3.useState(false);
|
|
1500
|
+
const mouseDownPosRef = React3.useRef(null);
|
|
1501
|
+
const didDragRef = React3.useRef(false);
|
|
1502
|
+
const canDrag = dataTableContext?.enableColumnDrag ?? false;
|
|
1503
|
+
const handleMouseDown = (e) => {
|
|
1504
|
+
mouseDownPosRef.current = { x: e.clientX, y: e.clientY };
|
|
1505
|
+
didDragRef.current = false;
|
|
1506
|
+
};
|
|
1507
|
+
const handleDragStart = (e) => {
|
|
1508
|
+
didDragRef.current = true;
|
|
1509
|
+
mouseDownPosRef.current = null;
|
|
1510
|
+
dataTableContext?.onColumnDragStart(e, column.id);
|
|
1511
|
+
};
|
|
1512
|
+
const handleDragEnd = (e) => {
|
|
1513
|
+
dataTableContext?.onColumnDragEnd(e);
|
|
1514
|
+
};
|
|
1515
|
+
const handleClick = (e) => {
|
|
1516
|
+
if (!didDragRef.current && mouseDownPosRef.current) {
|
|
1517
|
+
const dx = Math.abs(e.clientX - mouseDownPosRef.current.x);
|
|
1518
|
+
const dy = Math.abs(e.clientY - mouseDownPosRef.current.y);
|
|
1519
|
+
if (dx < 5 && dy < 5) {
|
|
1520
|
+
setIsOpen(true);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
mouseDownPosRef.current = null;
|
|
1524
|
+
didDragRef.current = false;
|
|
1525
|
+
};
|
|
1526
|
+
const colAlign = meta?.align ?? "left";
|
|
1527
|
+
const justifyClass = colAlign === "right" ? "justify-end" : colAlign === "center" ? "justify-center" : "";
|
|
1528
|
+
if (!canSort && !canHide && !showFilter) {
|
|
1529
|
+
return /* @__PURE__ */ jsxs(
|
|
1530
|
+
"div",
|
|
1531
|
+
{
|
|
1532
|
+
className: cn(
|
|
1533
|
+
"flex items-center gap-1.5 px-2 h-full",
|
|
1534
|
+
justifyClass,
|
|
1535
|
+
canDrag && "cursor-grab",
|
|
1536
|
+
className
|
|
1537
|
+
),
|
|
1538
|
+
draggable: canDrag,
|
|
1539
|
+
onDragStart: canDrag ? handleDragStart : void 0,
|
|
1540
|
+
onDragEnd: canDrag ? handleDragEnd : void 0,
|
|
1541
|
+
"aria-hidden": canDrag ? "true" : void 0,
|
|
1542
|
+
children: [
|
|
1543
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "size-4 text-muted-foreground" }),
|
|
1544
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: title })
|
|
1545
|
+
]
|
|
1546
|
+
}
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
return /* @__PURE__ */ jsx("div", { className: cn("flex h-full items-center w-full", className), children: /* @__PURE__ */ jsxs(DropdownMenu, { open: isOpen, onOpenChange: setIsOpen, modal: false, children: [
|
|
1550
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx("div", { className: "w-full h-full", style: { pointerEvents: "none" }, children: /* @__PURE__ */ jsxs(
|
|
1551
|
+
"button",
|
|
1552
|
+
{
|
|
1553
|
+
type: "button",
|
|
1554
|
+
draggable: canDrag,
|
|
1555
|
+
onMouseDown: handleMouseDown,
|
|
1556
|
+
onDragStart: canDrag ? handleDragStart : void 0,
|
|
1557
|
+
onDragEnd: canDrag ? handleDragEnd : void 0,
|
|
1558
|
+
onClick: handleClick,
|
|
1559
|
+
style: { pointerEvents: "auto" },
|
|
1560
|
+
className: cn(
|
|
1561
|
+
"relative flex items-center gap-1.5 w-full h-full px-2",
|
|
1562
|
+
justifyClass || "text-left",
|
|
1563
|
+
"before:absolute before:-inset-[99px] before:-z-10 before:pointer-events-none before:transition-colors hover:before:bg-accent/50",
|
|
1564
|
+
isOpen && "before:bg-accent",
|
|
1565
|
+
"focus:outline-none focus-visible:ring-1 focus-visible:ring-ring",
|
|
1566
|
+
canDrag ? "cursor-grab" : "cursor-pointer"
|
|
1567
|
+
),
|
|
1568
|
+
children: [
|
|
1569
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "size-3.5 text-muted-foreground shrink-0" }),
|
|
1570
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: title }),
|
|
1571
|
+
isFiltered && /* @__PURE__ */ jsx(Filter, { className: "size-3 text-primary shrink-0" }),
|
|
1572
|
+
isSorted === "desc" && /* @__PURE__ */ jsx(ArrowDown, { className: "size-3.5 text-muted-foreground shrink-0" }),
|
|
1573
|
+
isSorted === "asc" && /* @__PURE__ */ jsx(ArrowUp, { className: "size-3.5 text-muted-foreground shrink-0" })
|
|
1574
|
+
]
|
|
1575
|
+
}
|
|
1576
|
+
) }) }),
|
|
1577
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", className: "w-36", children: [
|
|
1578
|
+
customMenuItems && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1579
|
+
customMenuItems,
|
|
1580
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {})
|
|
1581
|
+
] }),
|
|
1582
|
+
showFilter && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1583
|
+
/* @__PURE__ */ jsxs(DropdownMenuSub, { children: [
|
|
1584
|
+
/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { className: "text-xs", children: [
|
|
1585
|
+
/* @__PURE__ */ jsx(Filter, { className: "mr-2 size-3.5 text-muted-foreground" }),
|
|
1586
|
+
"Filter",
|
|
1587
|
+
isFiltered && /* @__PURE__ */ jsx("span", { className: "ml-auto text-[10px] text-muted-foreground", children: "Active" })
|
|
1588
|
+
] }),
|
|
1589
|
+
/* @__PURE__ */ jsx(DropdownMenuSubContent, { alignOffset: -5, sideOffset: 5, children: /* @__PURE__ */ jsx(
|
|
1590
|
+
DataTableColumnFilterContent,
|
|
1591
|
+
{
|
|
1592
|
+
title,
|
|
1593
|
+
value: column.getFilterValue(),
|
|
1594
|
+
onChange: (v) => {
|
|
1595
|
+
column.setFilterValue(v);
|
|
1596
|
+
onFilterChange?.(column.id, v);
|
|
1597
|
+
},
|
|
1598
|
+
onClear: () => column.setFilterValue(void 0),
|
|
1599
|
+
filterType: resolvedFilterType,
|
|
1600
|
+
filterOptions: resolvedFilterOptions,
|
|
1601
|
+
placeholder: filterPlaceholder,
|
|
1602
|
+
showDeleteMenu: false
|
|
1603
|
+
}
|
|
1604
|
+
) })
|
|
1605
|
+
] }),
|
|
1606
|
+
isFiltered && /* @__PURE__ */ jsx(
|
|
1607
|
+
DropdownMenuItem,
|
|
1608
|
+
{
|
|
1609
|
+
onClick: () => column.setFilterValue(void 0),
|
|
1610
|
+
className: "text-xs",
|
|
1611
|
+
children: "Clear filter"
|
|
1612
|
+
}
|
|
1613
|
+
),
|
|
1614
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {})
|
|
1615
|
+
] }),
|
|
1616
|
+
canSort && /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [
|
|
1617
|
+
/* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { className: "text-xs", children: [
|
|
1618
|
+
/* @__PURE__ */ jsx(ListOrdered, { className: "mr-2 size-3.5 text-muted-foreground" }),
|
|
1619
|
+
"Sort"
|
|
1620
|
+
] }),
|
|
1621
|
+
/* @__PURE__ */ jsxs(DropdownMenuSubContent, { alignOffset: -5, sideOffset: 5, children: [
|
|
1622
|
+
/* @__PURE__ */ jsxs(
|
|
1623
|
+
DropdownMenuItem,
|
|
1624
|
+
{
|
|
1625
|
+
onClick: () => column.toggleSorting(false),
|
|
1626
|
+
className: cn("text-xs", isSorted === "asc" && "bg-accent"),
|
|
1627
|
+
children: [
|
|
1628
|
+
/* @__PURE__ */ jsx(ArrowUp, { className: "mr-2 size-3.5 text-muted-foreground" }),
|
|
1629
|
+
"Ascending"
|
|
1630
|
+
]
|
|
1631
|
+
}
|
|
1632
|
+
),
|
|
1633
|
+
/* @__PURE__ */ jsxs(
|
|
1634
|
+
DropdownMenuItem,
|
|
1635
|
+
{
|
|
1636
|
+
onClick: () => column.toggleSorting(true),
|
|
1637
|
+
className: cn("text-xs", isSorted === "desc" && "bg-accent"),
|
|
1638
|
+
children: [
|
|
1639
|
+
/* @__PURE__ */ jsx(ArrowDown, { className: "mr-2 size-3.5 text-muted-foreground" }),
|
|
1640
|
+
"Descending"
|
|
1641
|
+
]
|
|
1642
|
+
}
|
|
1643
|
+
),
|
|
1644
|
+
isSorted && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1645
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1646
|
+
/* @__PURE__ */ jsx(
|
|
1647
|
+
DropdownMenuItem,
|
|
1648
|
+
{
|
|
1649
|
+
onClick: () => column.clearSorting(),
|
|
1650
|
+
className: "text-xs",
|
|
1651
|
+
children: "Clear sort"
|
|
1652
|
+
}
|
|
1653
|
+
)
|
|
1654
|
+
] })
|
|
1655
|
+
] })
|
|
1656
|
+
] }),
|
|
1657
|
+
(dataTableContext || onToggleWrapProp) && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1658
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1659
|
+
/* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: onToggleWrap, className: "text-xs", children: [
|
|
1660
|
+
/* @__PURE__ */ jsx(WrapText, { className: "mr-2 size-3.5 text-muted-foreground" }),
|
|
1661
|
+
isWrapped ? "Disable wrap" : "Wrap text"
|
|
1662
|
+
] })
|
|
1663
|
+
] }),
|
|
1664
|
+
canHide && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1665
|
+
(canSort || dataTableContext || onToggleWrapProp) && /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
1666
|
+
/* @__PURE__ */ jsxs(
|
|
1667
|
+
DropdownMenuItem,
|
|
1668
|
+
{
|
|
1669
|
+
onClick: () => column.toggleVisibility(false),
|
|
1670
|
+
className: "text-xs",
|
|
1671
|
+
children: [
|
|
1672
|
+
/* @__PURE__ */ jsx(EyeOff, { className: "mr-2 size-3.5 text-muted-foreground" }),
|
|
1673
|
+
"Hide"
|
|
1674
|
+
]
|
|
1675
|
+
}
|
|
1676
|
+
)
|
|
1677
|
+
] })
|
|
1678
|
+
] })
|
|
1679
|
+
] }) });
|
|
1680
|
+
}
|
|
1681
|
+
var FinanceDisplayContext = React3.createContext({
|
|
1682
|
+
decimals: 0,
|
|
1683
|
+
displayUnits: "actuals",
|
|
1684
|
+
currencySignDisplay: "all"
|
|
1685
|
+
});
|
|
1686
|
+
var FinanceDecimalsContext = React3.createContext(0);
|
|
1687
|
+
function FinanceDisplayProvider({
|
|
1688
|
+
decimals,
|
|
1689
|
+
displayUnits = "actuals",
|
|
1690
|
+
currencySignDisplay = "all",
|
|
1691
|
+
children
|
|
1692
|
+
}) {
|
|
1693
|
+
const value = React3.useMemo(
|
|
1694
|
+
() => ({ decimals, displayUnits, currencySignDisplay }),
|
|
1695
|
+
[decimals, displayUnits, currencySignDisplay]
|
|
1696
|
+
);
|
|
1697
|
+
return /* @__PURE__ */ jsx(FinanceDisplayContext.Provider, { value, children: /* @__PURE__ */ jsx(FinanceDecimalsContext.Provider, { value: decimals, children }) });
|
|
1698
|
+
}
|
|
1699
|
+
function FinanceDecimalsProvider({
|
|
1700
|
+
decimals,
|
|
1701
|
+
children
|
|
1702
|
+
}) {
|
|
1703
|
+
return /* @__PURE__ */ jsx(FinanceDisplayProvider, { decimals, children });
|
|
1704
|
+
}
|
|
1705
|
+
function useFinanceDecimals() {
|
|
1706
|
+
return React3.useContext(FinanceDecimalsContext);
|
|
1707
|
+
}
|
|
1708
|
+
function useFinanceDisplay() {
|
|
1709
|
+
return React3.useContext(FinanceDisplayContext);
|
|
1710
|
+
}
|
|
1711
|
+
function getUnitDivisor(units) {
|
|
1712
|
+
switch (units) {
|
|
1713
|
+
case "thousands":
|
|
1714
|
+
return 1e3;
|
|
1715
|
+
case "millions":
|
|
1716
|
+
return 1e6;
|
|
1717
|
+
default:
|
|
1718
|
+
return 1;
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
function getUnitLabel(units) {
|
|
1722
|
+
switch (units) {
|
|
1723
|
+
case "thousands":
|
|
1724
|
+
return "in thousands";
|
|
1725
|
+
case "millions":
|
|
1726
|
+
return "in millions";
|
|
1727
|
+
default:
|
|
1728
|
+
return null;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
function getUnitSuffix(units) {
|
|
1732
|
+
switch (units) {
|
|
1733
|
+
case "thousands":
|
|
1734
|
+
return "(000s)";
|
|
1735
|
+
case "millions":
|
|
1736
|
+
return "(M)";
|
|
1737
|
+
default:
|
|
1738
|
+
return null;
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
function useFinanceDecimalsSetting(storageKey = "finance-decimal-places", defaultValue = 0) {
|
|
1742
|
+
const [decimals, setDecimalsState] = React3.useState(() => {
|
|
1743
|
+
if (typeof window === "undefined") return defaultValue;
|
|
1744
|
+
const stored = localStorage.getItem(storageKey);
|
|
1745
|
+
return stored !== null ? Number(stored) : defaultValue;
|
|
1746
|
+
});
|
|
1747
|
+
const setDecimals = React3.useCallback(
|
|
1748
|
+
(v) => {
|
|
1749
|
+
setDecimalsState(v);
|
|
1750
|
+
if (typeof window !== "undefined") {
|
|
1751
|
+
localStorage.setItem(storageKey, String(v));
|
|
1752
|
+
}
|
|
1753
|
+
},
|
|
1754
|
+
[storageKey]
|
|
1755
|
+
);
|
|
1756
|
+
return [decimals, setDecimals];
|
|
1757
|
+
}
|
|
1758
|
+
var formatterCache = /* @__PURE__ */ new Map();
|
|
1759
|
+
function getFormatter(locale, currencyCode, decimals) {
|
|
1760
|
+
const key = `${locale}:${currencyCode}:${decimals}`;
|
|
1761
|
+
let fmt = formatterCache.get(key);
|
|
1762
|
+
if (!fmt) {
|
|
1763
|
+
fmt = new Intl.NumberFormat(locale, {
|
|
1764
|
+
style: "currency",
|
|
1765
|
+
currency: currencyCode,
|
|
1766
|
+
minimumFractionDigits: decimals,
|
|
1767
|
+
maximumFractionDigits: decimals
|
|
1768
|
+
});
|
|
1769
|
+
formatterCache.set(key, fmt);
|
|
1770
|
+
}
|
|
1771
|
+
return fmt;
|
|
1772
|
+
}
|
|
1773
|
+
function formatCurrencyValue(value, options) {
|
|
1774
|
+
const raw = value ?? 0;
|
|
1775
|
+
const divisor = getUnitDivisor(options?.displayUnits ?? "actuals");
|
|
1776
|
+
const v = divisor === 1 ? raw : raw / divisor;
|
|
1777
|
+
const isZero = raw === 0;
|
|
1778
|
+
const isNegative = v < 0;
|
|
1779
|
+
const curr = options?.currency ?? "USD";
|
|
1780
|
+
const locale = options?.locale ?? "en-US";
|
|
1781
|
+
const dashZero = options?.dashZero ?? false;
|
|
1782
|
+
const decimals = options?.decimals ?? 2;
|
|
1783
|
+
const showSign = options?.showCurrencySign ?? true;
|
|
1784
|
+
if (isZero && dashZero) {
|
|
1785
|
+
return { text: "\u2014", isNegative: false, isZero: true };
|
|
1786
|
+
}
|
|
1787
|
+
const absV = Math.abs(v);
|
|
1788
|
+
let formatted;
|
|
1789
|
+
if (showSign) {
|
|
1790
|
+
const fmt = getFormatter(locale, curr, decimals);
|
|
1791
|
+
formatted = fmt.format(absV);
|
|
1792
|
+
} else {
|
|
1793
|
+
formatted = absV.toLocaleString(locale, {
|
|
1794
|
+
minimumFractionDigits: decimals,
|
|
1795
|
+
maximumFractionDigits: decimals
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
if (isNegative) {
|
|
1799
|
+
return { text: `(${formatted})`, isNegative: true, isZero: false };
|
|
1800
|
+
}
|
|
1801
|
+
return { text: formatted, isNegative: false, isZero };
|
|
1802
|
+
}
|
|
1803
|
+
function DataTableCurrencyCell({
|
|
1804
|
+
value,
|
|
1805
|
+
currency: currencyCode = "USD",
|
|
1806
|
+
locale = "en-US",
|
|
1807
|
+
dashZero = false,
|
|
1808
|
+
decimals,
|
|
1809
|
+
className,
|
|
1810
|
+
/** Whether this cell is in a summary/total row */
|
|
1811
|
+
isSummaryRow
|
|
1812
|
+
}) {
|
|
1813
|
+
const ctx = useFinanceDisplay();
|
|
1814
|
+
const resolvedDecimals = decimals ?? ctx.decimals;
|
|
1815
|
+
const showCurrencySign = ctx.currencySignDisplay === "all" || ctx.currencySignDisplay === "summary" && isSummaryRow;
|
|
1816
|
+
const { text, isNegative, isZero } = formatCurrencyValue(value, {
|
|
1817
|
+
currency: currencyCode,
|
|
1818
|
+
locale,
|
|
1819
|
+
dashZero,
|
|
1820
|
+
decimals: resolvedDecimals,
|
|
1821
|
+
displayUnits: ctx.displayUnits,
|
|
1822
|
+
showCurrencySign
|
|
1823
|
+
});
|
|
1824
|
+
return /* @__PURE__ */ jsx(
|
|
1825
|
+
"span",
|
|
1826
|
+
{
|
|
1827
|
+
className: cn(
|
|
1828
|
+
"font-mono tabular-nums",
|
|
1829
|
+
isNegative && "text-red-600 dark:text-red-400",
|
|
1830
|
+
isZero && dashZero && "text-muted-foreground",
|
|
1831
|
+
className
|
|
1832
|
+
),
|
|
1833
|
+
children: text
|
|
1834
|
+
}
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// src/blocks/data-table/financialStatementRowImportance.ts
|
|
1839
|
+
var DEFAULT_ROW_IMPORTANCE_CONFIG = {
|
|
1840
|
+
"income-statement": {
|
|
1841
|
+
emphasis: ["Total Revenue", "Total Expenses", "Operating Income"],
|
|
1842
|
+
keySummary: ["Net Income"]
|
|
1843
|
+
},
|
|
1844
|
+
"balance-sheet": {
|
|
1845
|
+
emphasis: ["Total Liabilities", "Total Equity"],
|
|
1846
|
+
keySummary: ["Total Assets", "Total Liabilities & Equity"]
|
|
1847
|
+
},
|
|
1848
|
+
"cash-flow": {
|
|
1849
|
+
emphasis: [
|
|
1850
|
+
"Total Operating Activities",
|
|
1851
|
+
"Total Investing Activities",
|
|
1852
|
+
"Total Financing Activities",
|
|
1853
|
+
"Net Cash from Operating Activities",
|
|
1854
|
+
"Net Cash from Investing Activities",
|
|
1855
|
+
"Net Cash from Financing Activities"
|
|
1856
|
+
],
|
|
1857
|
+
keySummary: [
|
|
1858
|
+
"Net Change in Cash",
|
|
1859
|
+
"Ending Cash",
|
|
1860
|
+
"Total Cash & Cash Equivalents"
|
|
1861
|
+
]
|
|
1862
|
+
},
|
|
1863
|
+
"working-capital": {
|
|
1864
|
+
emphasis: ["Total Current Assets", "Total Current Liabilities"],
|
|
1865
|
+
keySummary: ["NWC", "Net Working Capital"]
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
function normalizeLabel(value) {
|
|
1869
|
+
return value.trim().toLowerCase().replace(/\s+/g, " ");
|
|
1870
|
+
}
|
|
1871
|
+
function createFinancialStatementRowImportanceResolver(kind, overrides) {
|
|
1872
|
+
const base = DEFAULT_ROW_IMPORTANCE_CONFIG[kind];
|
|
1873
|
+
const emphasis = new Set(
|
|
1874
|
+
(overrides?.emphasis ?? base?.emphasis ?? []).map(normalizeLabel)
|
|
1875
|
+
);
|
|
1876
|
+
const keySummary = new Set(
|
|
1877
|
+
(overrides?.keySummary ?? base?.keySummary ?? []).map(normalizeLabel)
|
|
1878
|
+
);
|
|
1879
|
+
return (row) => {
|
|
1880
|
+
const label = normalizeLabel(row.name);
|
|
1881
|
+
if (keySummary.has(label)) return "key-summary";
|
|
1882
|
+
if (emphasis.has(label)) return "emphasis";
|
|
1883
|
+
return "normal";
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
function getFinancialStatementRowImportanceClassName(importance) {
|
|
1887
|
+
switch (importance) {
|
|
1888
|
+
case "emphasis":
|
|
1889
|
+
return "bg-muted/25 border-t border-border/50 font-medium";
|
|
1890
|
+
case "key-summary":
|
|
1891
|
+
return "bg-muted/45 border-t-2 border-border/70 font-semibold";
|
|
1892
|
+
default:
|
|
1893
|
+
return void 0;
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
var TOLERANCE = 0.01;
|
|
1897
|
+
function RecCheck({ computed, raw }) {
|
|
1898
|
+
const diff = Math.abs(computed - raw);
|
|
1899
|
+
const matches = diff <= TOLERANCE;
|
|
1900
|
+
const icon = matches ? /* @__PURE__ */ jsx(CheckCircle2, { className: "size-3.5 text-white fill-emerald-500" }) : /* @__PURE__ */ jsx(AlertTriangle, { className: "size-3.5 text-white fill-amber-500" });
|
|
1901
|
+
return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1902
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx("span", { children: icon }) }),
|
|
1903
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "left", className: "text-[11px] max-w-none p-2", children: matches ? /* @__PURE__ */ jsx("span", { className: "text-emerald-400", children: "\u2713 Balanced \u2014 computed total matches source data" }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
1904
|
+
/* @__PURE__ */ jsx("div", { className: "text-amber-400 font-medium", children: "\u26A0 Variance detected" }),
|
|
1905
|
+
/* @__PURE__ */ jsx("table", { className: "text-[11px]", children: /* @__PURE__ */ jsxs("tbody", { children: [
|
|
1906
|
+
/* @__PURE__ */ jsxs("tr", { children: [
|
|
1907
|
+
/* @__PURE__ */ jsx("td", { className: "pr-3 text-muted-foreground", children: "Computed:" }),
|
|
1908
|
+
/* @__PURE__ */ jsx("td", { className: "font-mono tabular-nums text-right", children: formatCurrencyValue(computed, { decimals: 2 }).text })
|
|
1909
|
+
] }),
|
|
1910
|
+
/* @__PURE__ */ jsxs("tr", { children: [
|
|
1911
|
+
/* @__PURE__ */ jsx("td", { className: "pr-3 text-muted-foreground", children: "Source:" }),
|
|
1912
|
+
/* @__PURE__ */ jsx("td", { className: "font-mono tabular-nums text-right", children: formatCurrencyValue(raw, { decimals: 2 }).text })
|
|
1913
|
+
] }),
|
|
1914
|
+
/* @__PURE__ */ jsxs("tr", { className: "border-t border-border/40", children: [
|
|
1915
|
+
/* @__PURE__ */ jsx("td", { className: "pr-3 text-muted-foreground pt-0.5", children: "Variance:" }),
|
|
1916
|
+
/* @__PURE__ */ jsx("td", { className: "font-mono tabular-nums text-right pt-0.5 text-amber-400", children: formatCurrencyValue(computed - raw, { decimals: 2 }).text })
|
|
1917
|
+
] })
|
|
1918
|
+
] }) })
|
|
1919
|
+
] }) })
|
|
1920
|
+
] }) });
|
|
1921
|
+
}
|
|
1922
|
+
function splitMetricFormula(formula) {
|
|
1923
|
+
const lines = formula.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1924
|
+
return {
|
|
1925
|
+
equationLine: lines[0] ?? formula,
|
|
1926
|
+
detailLines: lines.slice(1)
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
function MetricEquationTooltip({
|
|
1930
|
+
title,
|
|
1931
|
+
formula
|
|
1932
|
+
}) {
|
|
1933
|
+
const { equationLine } = splitMetricFormula(formula);
|
|
1934
|
+
const [lhs, rhs] = equationLine.split("=").map((part) => part.trim());
|
|
1935
|
+
if (!rhs) {
|
|
1936
|
+
return /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed", children: formula });
|
|
1937
|
+
}
|
|
1938
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
1939
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] font-medium text-muted-foreground/90", children: title }),
|
|
1940
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-md border border-slate-500/15 bg-gradient-to-r from-slate-500/[0.05] via-sky-500/[0.04] to-emerald-500/[0.05] px-2.5 py-1.5", children: /* @__PURE__ */ jsxs("p", { className: "text-xs font-mono tracking-tight leading-relaxed text-foreground/85", children: [
|
|
1941
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold text-sky-700/85 dark:text-sky-200/85", children: lhs }),
|
|
1942
|
+
/* @__PURE__ */ jsx("span", { className: "px-1 text-muted-foreground/80", children: "=" }),
|
|
1943
|
+
/* @__PURE__ */ jsx("span", { className: "text-emerald-700/85 dark:text-emerald-200/85", children: rhs })
|
|
1944
|
+
] }) })
|
|
1945
|
+
] });
|
|
1946
|
+
}
|
|
1947
|
+
function toPeriodKey(date, unit) {
|
|
1948
|
+
const [y, m] = date.split("-").map(Number);
|
|
1949
|
+
if (unit === "year") return String(y);
|
|
1950
|
+
if (unit === "quarter") {
|
|
1951
|
+
const q = Math.floor((m - 1) / 3) + 1;
|
|
1952
|
+
return `${y}-Q${q}`;
|
|
1953
|
+
}
|
|
1954
|
+
return `${y}-${String(m).padStart(2, "0")}`;
|
|
1955
|
+
}
|
|
1956
|
+
function resolvePeriodColumns(periods, timeUnit, periodOrder, periodColumnOrder = "oldest-first") {
|
|
1957
|
+
const basePeriods = periodColumnOrder === "newest-first" ? [...periods].reverse() : periods;
|
|
1958
|
+
if (!periodOrder || periodOrder.length === 0) return basePeriods;
|
|
1959
|
+
const normalizedOrder = periodOrder.map((pk) => toPeriodKey(pk, timeUnit));
|
|
1960
|
+
const periodSet = new Set(basePeriods);
|
|
1961
|
+
const prioritized = Array.from(new Set(normalizedOrder)).filter(
|
|
1962
|
+
(pk) => periodSet.has(pk)
|
|
1963
|
+
);
|
|
1964
|
+
const remainder = basePeriods.filter((pk) => !prioritized.includes(pk));
|
|
1965
|
+
return [...prioritized, ...remainder];
|
|
1966
|
+
}
|
|
1967
|
+
function calculateDesktopFinancialColumnSizing({
|
|
1968
|
+
periods,
|
|
1969
|
+
rows,
|
|
1970
|
+
showRowTotals,
|
|
1971
|
+
showVariance
|
|
1972
|
+
}) {
|
|
1973
|
+
const MIN_ACCOUNT = 260;
|
|
1974
|
+
const MIN_DATA = 88;
|
|
1975
|
+
const MIN_TOTAL = 96;
|
|
1976
|
+
const VARIANCE_COL = showVariance ? 80 : 0;
|
|
1977
|
+
const TARGET_TABLE_WIDTH = 1180;
|
|
1978
|
+
let maxNumericChars = 1;
|
|
1979
|
+
for (const row of rows) {
|
|
1980
|
+
for (const pk of periods) {
|
|
1981
|
+
maxNumericChars = Math.max(
|
|
1982
|
+
maxNumericChars,
|
|
1983
|
+
Math.round(Math.abs(row.periodAmounts[pk] ?? 0)).toLocaleString().length
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
maxNumericChars = Math.max(
|
|
1987
|
+
maxNumericChars,
|
|
1988
|
+
Math.round(Math.abs(row.total ?? 0)).toLocaleString().length
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
let dataSize = Math.min(156, Math.max(104, maxNumericChars * 9 + 26));
|
|
1992
|
+
let totalSize = Math.max(MIN_TOTAL, dataSize + 8);
|
|
1993
|
+
let accountSize = Math.max(340, 620 - periods.length * 26);
|
|
1994
|
+
let tableWidth = accountSize + periods.length * dataSize + (showRowTotals ? totalSize : 0) + VARIANCE_COL;
|
|
1995
|
+
if (tableWidth > TARGET_TABLE_WIDTH) {
|
|
1996
|
+
const reducibleAccount = accountSize - MIN_ACCOUNT;
|
|
1997
|
+
const needed = tableWidth - TARGET_TABLE_WIDTH;
|
|
1998
|
+
const reduceAccount = Math.min(reducibleAccount, needed);
|
|
1999
|
+
accountSize -= reduceAccount;
|
|
2000
|
+
tableWidth -= reduceAccount;
|
|
2001
|
+
}
|
|
2002
|
+
if (tableWidth > TARGET_TABLE_WIDTH && periods.length > 0) {
|
|
2003
|
+
const reducibleDataPerCol = Math.max(0, dataSize - MIN_DATA);
|
|
2004
|
+
const totalReducible = reducibleDataPerCol * periods.length;
|
|
2005
|
+
const needed = tableWidth - TARGET_TABLE_WIDTH;
|
|
2006
|
+
const reduceTotal = Math.min(totalReducible, needed);
|
|
2007
|
+
const perCol = Math.floor(reduceTotal / periods.length);
|
|
2008
|
+
dataSize = Math.max(MIN_DATA, dataSize - perCol);
|
|
2009
|
+
if (showRowTotals) {
|
|
2010
|
+
totalSize = Math.max(MIN_TOTAL, totalSize - Math.ceil(perCol * 0.9));
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
return {
|
|
2014
|
+
accountSize,
|
|
2015
|
+
accountMinSize: MIN_ACCOUNT,
|
|
2016
|
+
dataSize,
|
|
2017
|
+
dataMinSize: MIN_DATA,
|
|
2018
|
+
totalSize
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
function toPeriodLabel(key, unit) {
|
|
2022
|
+
if (unit === "year") return key;
|
|
2023
|
+
if (unit === "quarter") return key.replace("-", " ");
|
|
2024
|
+
const [year, month] = key.split("-");
|
|
2025
|
+
const date = new Date(Number(year), Number(month) - 1);
|
|
2026
|
+
return date.toLocaleDateString("en-US", { month: "short", year: "2-digit" });
|
|
2027
|
+
}
|
|
2028
|
+
function sumChildPeriods(children) {
|
|
2029
|
+
const periodAmounts = {};
|
|
2030
|
+
let total = 0;
|
|
2031
|
+
for (const child of children) {
|
|
2032
|
+
total += child.total;
|
|
2033
|
+
for (const [pk, amt] of Object.entries(child.periodAmounts)) {
|
|
2034
|
+
periodAmounts[pk] = (periodAmounts[pk] || 0) + amt;
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
return { periodAmounts, total };
|
|
2038
|
+
}
|
|
2039
|
+
function buildAccountTree(accounts, sign) {
|
|
2040
|
+
const byNumber = /* @__PURE__ */ new Map();
|
|
2041
|
+
for (const a of accounts) {
|
|
2042
|
+
if (a.number) byNumber.set(a.number, a);
|
|
2043
|
+
}
|
|
2044
|
+
const nameToNumber = /* @__PURE__ */ new Map();
|
|
2045
|
+
for (const a of accounts) {
|
|
2046
|
+
if (a.number && a.name) nameToNumber.set(a.name, a.number);
|
|
2047
|
+
}
|
|
2048
|
+
const synthetics = [];
|
|
2049
|
+
for (const a of accounts) {
|
|
2050
|
+
if (a.isSubAccount && a.parentNumber && a.parentNumber !== a.number && !byNumber.has(a.parentNumber)) {
|
|
2051
|
+
const parentName = a.parentName ?? a.name;
|
|
2052
|
+
const synthetic = {
|
|
2053
|
+
id: `synthetic-${a.parentNumber}`,
|
|
2054
|
+
name: parentName,
|
|
2055
|
+
secondaryLabel: null,
|
|
2056
|
+
number: a.parentNumber,
|
|
2057
|
+
type: a.type,
|
|
2058
|
+
subType: a.subType,
|
|
2059
|
+
isSubAccount: false,
|
|
2060
|
+
parentNumber: null,
|
|
2061
|
+
parentName: null,
|
|
2062
|
+
periods: {},
|
|
2063
|
+
total: 0
|
|
2064
|
+
};
|
|
2065
|
+
byNumber.set(a.parentNumber, synthetic);
|
|
2066
|
+
nameToNumber.set(parentName, a.parentNumber);
|
|
2067
|
+
synthetics.push(synthetic);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
for (const s of synthetics) {
|
|
2071
|
+
const lastColon = s.name.lastIndexOf(":");
|
|
2072
|
+
if (lastColon > 0) {
|
|
2073
|
+
const grandparentPath = s.name.substring(0, lastColon);
|
|
2074
|
+
const gpNumber = nameToNumber.get(grandparentPath);
|
|
2075
|
+
if (gpNumber && gpNumber !== s.number) {
|
|
2076
|
+
s.isSubAccount = true;
|
|
2077
|
+
s.parentNumber = gpNumber;
|
|
2078
|
+
s.parentName = grandparentPath;
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
for (const s of synthetics) {
|
|
2083
|
+
accounts.push(s);
|
|
2084
|
+
}
|
|
2085
|
+
const roots = [];
|
|
2086
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
2087
|
+
for (const a of accounts) {
|
|
2088
|
+
if (a.isSubAccount && a.parentNumber && a.parentNumber !== a.number && byNumber.has(a.parentNumber)) {
|
|
2089
|
+
const existing = childrenOf.get(a.parentNumber) || [];
|
|
2090
|
+
existing.push(a);
|
|
2091
|
+
childrenOf.set(a.parentNumber, existing);
|
|
2092
|
+
} else {
|
|
2093
|
+
roots.push(a);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
function buildRow(acct, depth) {
|
|
2097
|
+
const kids = childrenOf.get(acct.number ?? "") || [];
|
|
2098
|
+
const childRows = kids.sort((a, b) => (a.number ?? "").localeCompare(b.number ?? "")).map((k) => buildRow(k, depth + 1));
|
|
2099
|
+
const periodAmounts = {};
|
|
2100
|
+
let total = 0;
|
|
2101
|
+
if (childRows.length > 0) {
|
|
2102
|
+
const childSums = sumChildPeriods(childRows);
|
|
2103
|
+
total = acct.total * sign + childSums.total;
|
|
2104
|
+
for (const pk of Object.keys({
|
|
2105
|
+
...acct.periods,
|
|
2106
|
+
...childSums.periodAmounts
|
|
2107
|
+
})) {
|
|
2108
|
+
periodAmounts[pk] = (acct.periods[pk] || 0) * sign + (childSums.periodAmounts[pk] || 0);
|
|
2109
|
+
}
|
|
2110
|
+
} else {
|
|
2111
|
+
total = acct.total * sign;
|
|
2112
|
+
for (const [pk, amt] of Object.entries(acct.periods)) {
|
|
2113
|
+
periodAmounts[pk] = amt * sign;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
_type: childRows.length > 0 ? "account-group" : "account",
|
|
2118
|
+
id: acct.id,
|
|
2119
|
+
name: acct.name.includes(":") ? acct.name.substring(acct.name.lastIndexOf(":") + 1) : acct.name,
|
|
2120
|
+
accountNumber: acct.number,
|
|
2121
|
+
secondaryLabel: acct.secondaryLabel,
|
|
2122
|
+
accountType: acct.type,
|
|
2123
|
+
accountSubType: acct.subType,
|
|
2124
|
+
periodAmounts,
|
|
2125
|
+
total,
|
|
2126
|
+
children: childRows,
|
|
2127
|
+
depth
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
return roots.sort((a, b) => (a.number ?? "").localeCompare(b.number ?? "")).map((r) => buildRow(r, 0));
|
|
2131
|
+
}
|
|
2132
|
+
function applySubtotalRules(accountTree, rules, sectionId) {
|
|
2133
|
+
const sortedRules = [...rules].sort((a, b) => a.order - b.order);
|
|
2134
|
+
const compiled = sortedRules.map((r) => ({
|
|
2135
|
+
rule: r,
|
|
2136
|
+
regex: new RegExp(r.pattern)
|
|
2137
|
+
}));
|
|
2138
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2139
|
+
const unmatched = [];
|
|
2140
|
+
for (const r of sortedRules) {
|
|
2141
|
+
groups.set(r.label, []);
|
|
2142
|
+
}
|
|
2143
|
+
function matchRow(row) {
|
|
2144
|
+
for (const { rule, regex } of compiled) {
|
|
2145
|
+
if (row.accountNumber && regex.test(row.accountNumber)) {
|
|
2146
|
+
return rule.label;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
return null;
|
|
2150
|
+
}
|
|
2151
|
+
let anyMatched = false;
|
|
2152
|
+
for (const row of accountTree) {
|
|
2153
|
+
const groupLabel = matchRow(row);
|
|
2154
|
+
if (groupLabel) {
|
|
2155
|
+
groups.get(groupLabel).push(row);
|
|
2156
|
+
anyMatched = true;
|
|
2157
|
+
} else {
|
|
2158
|
+
unmatched.push(row);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
if (!anyMatched) {
|
|
2162
|
+
return accountTree;
|
|
2163
|
+
}
|
|
2164
|
+
const result = [];
|
|
2165
|
+
for (const rule of sortedRules) {
|
|
2166
|
+
const groupRows = groups.get(rule.label) ?? [];
|
|
2167
|
+
if (groupRows.length === 0) continue;
|
|
2168
|
+
const sums = sumChildPeriods(groupRows);
|
|
2169
|
+
result.push({
|
|
2170
|
+
_type: "subtotal-group",
|
|
2171
|
+
id: `subtotal-${sectionId}-${rule.label.toLowerCase().replace(/\s+/g, "-")}`,
|
|
2172
|
+
name: rule.label,
|
|
2173
|
+
accountNumber: null,
|
|
2174
|
+
periodAmounts: sums.periodAmounts,
|
|
2175
|
+
total: sums.total,
|
|
2176
|
+
children: groupRows,
|
|
2177
|
+
depth: 0
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
if (unmatched.length > 0) {
|
|
2181
|
+
const sums = sumChildPeriods(unmatched);
|
|
2182
|
+
result.push({
|
|
2183
|
+
_type: "subtotal-group",
|
|
2184
|
+
id: `subtotal-${sectionId}-other`,
|
|
2185
|
+
name: "Other",
|
|
2186
|
+
accountNumber: null,
|
|
2187
|
+
periodAmounts: sums.periodAmounts,
|
|
2188
|
+
total: sums.total,
|
|
2189
|
+
children: unmatched,
|
|
2190
|
+
depth: 0
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
return result;
|
|
2194
|
+
}
|
|
2195
|
+
function filterZeroRows(rows) {
|
|
2196
|
+
return rows.reduce((acc, row) => {
|
|
2197
|
+
if (row._type === "section-header" || row._type === "section-total" || row._type === "grand-total") {
|
|
2198
|
+
if (row.children.length > 0) {
|
|
2199
|
+
acc.push({ ...row, children: filterZeroRows(row.children) });
|
|
2200
|
+
} else {
|
|
2201
|
+
acc.push(row);
|
|
2202
|
+
}
|
|
2203
|
+
return acc;
|
|
2204
|
+
}
|
|
2205
|
+
if (row.children.length > 0) {
|
|
2206
|
+
const filteredChildren = filterZeroRows(row.children);
|
|
2207
|
+
if (filteredChildren.length === 0) {
|
|
2208
|
+
const parentIsZero = row.total === 0 && Object.values(row.periodAmounts).every((v) => v === 0);
|
|
2209
|
+
if (parentIsZero) return acc;
|
|
2210
|
+
acc.push({ ...row, children: [] });
|
|
2211
|
+
return acc;
|
|
2212
|
+
}
|
|
2213
|
+
if (row._type === "subtotal-group") {
|
|
2214
|
+
const sums = sumChildPeriods(filteredChildren);
|
|
2215
|
+
acc.push({
|
|
2216
|
+
...row,
|
|
2217
|
+
children: filteredChildren,
|
|
2218
|
+
periodAmounts: sums.periodAmounts,
|
|
2219
|
+
total: sums.total
|
|
2220
|
+
});
|
|
2221
|
+
} else {
|
|
2222
|
+
acc.push({ ...row, children: filteredChildren });
|
|
2223
|
+
}
|
|
2224
|
+
return acc;
|
|
2225
|
+
}
|
|
2226
|
+
const isZero = row.total === 0 && Object.values(row.periodAmounts).every((v) => v === 0);
|
|
2227
|
+
if (!isZero) {
|
|
2228
|
+
acc.push(row);
|
|
2229
|
+
}
|
|
2230
|
+
return acc;
|
|
2231
|
+
}, []);
|
|
2232
|
+
}
|
|
2233
|
+
function buildFinancialStatementData(data, config, timeUnit, subtotalRules, options) {
|
|
2234
|
+
const periodSet = /* @__PURE__ */ new Set();
|
|
2235
|
+
for (const entry of data) {
|
|
2236
|
+
periodSet.add(toPeriodKey(entry.period_first_day, timeUnit));
|
|
2237
|
+
}
|
|
2238
|
+
const periods = [...periodSet].sort();
|
|
2239
|
+
const sectionTotals = /* @__PURE__ */ new Map();
|
|
2240
|
+
const sectionRawTotals = /* @__PURE__ */ new Map();
|
|
2241
|
+
const rows = [];
|
|
2242
|
+
for (const section of config.sections) {
|
|
2243
|
+
const classSet = new Set(section.accountClasses);
|
|
2244
|
+
const sign = section.sign ?? 1;
|
|
2245
|
+
const accountMap = /* @__PURE__ */ new Map();
|
|
2246
|
+
for (const entry of data) {
|
|
2247
|
+
if (!classSet.has(entry.account_class)) continue;
|
|
2248
|
+
const pk = toPeriodKey(entry.period_first_day, timeUnit);
|
|
2249
|
+
if (!accountMap.has(entry.account_id)) {
|
|
2250
|
+
accountMap.set(entry.account_id, {
|
|
2251
|
+
id: entry.account_id,
|
|
2252
|
+
name: entry.account_name,
|
|
2253
|
+
secondaryLabel: entry.account_secondary_label ?? null,
|
|
2254
|
+
number: entry.account_number,
|
|
2255
|
+
type: entry.account_type ?? "",
|
|
2256
|
+
subType: entry.account_sub_type ?? null,
|
|
2257
|
+
isSubAccount: entry.is_sub_account ?? false,
|
|
2258
|
+
parentNumber: entry.parent_account_number ?? null,
|
|
2259
|
+
parentName: entry.parent_account_name ?? null,
|
|
2260
|
+
periods: {},
|
|
2261
|
+
total: 0
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
const acct = accountMap.get(entry.account_id);
|
|
2265
|
+
acct.periods[pk] = (acct.periods[pk] || 0) + entry.amount;
|
|
2266
|
+
acct.total += entry.amount;
|
|
2267
|
+
}
|
|
2268
|
+
const rawPeriods = {};
|
|
2269
|
+
let rawTotal = 0;
|
|
2270
|
+
for (const entry of data) {
|
|
2271
|
+
if (!classSet.has(entry.account_class)) continue;
|
|
2272
|
+
const pk = toPeriodKey(entry.period_first_day, timeUnit);
|
|
2273
|
+
const val = entry.amount * sign;
|
|
2274
|
+
rawPeriods[pk] = (rawPeriods[pk] || 0) + val;
|
|
2275
|
+
rawTotal += val;
|
|
2276
|
+
}
|
|
2277
|
+
sectionRawTotals.set(section.id, {
|
|
2278
|
+
periodAmounts: rawPeriods,
|
|
2279
|
+
total: rawTotal
|
|
2280
|
+
});
|
|
2281
|
+
const accountList = [...accountMap.values()];
|
|
2282
|
+
const accountTree = buildAccountTree(accountList, sign);
|
|
2283
|
+
if (accountTree.length === 0 && !options?.preserveEmptySections) continue;
|
|
2284
|
+
const useSubtotals = subtotalRules?.enabled && subtotalRules.rules.length > 0;
|
|
2285
|
+
const sectionChildren = useSubtotals ? applySubtotalRules(accountTree, subtotalRules.rules, section.id) : accountTree;
|
|
2286
|
+
const sectionSums = sumChildPeriods(accountTree);
|
|
2287
|
+
sectionTotals.set(section.id, sectionSums);
|
|
2288
|
+
if (section.standalone) {
|
|
2289
|
+
const normalizeDepth = (items) => items.map((item) => ({
|
|
2290
|
+
...item,
|
|
2291
|
+
depth: Math.max(0, item.depth - 1),
|
|
2292
|
+
children: normalizeDepth(item.children)
|
|
2293
|
+
}));
|
|
2294
|
+
rows.push(...normalizeDepth(sectionChildren));
|
|
2295
|
+
continue;
|
|
2296
|
+
}
|
|
2297
|
+
const sectionHeader = {
|
|
2298
|
+
_type: "section-header",
|
|
2299
|
+
id: `section-${section.id}`,
|
|
2300
|
+
name: section.label,
|
|
2301
|
+
accountNumber: null,
|
|
2302
|
+
periodAmounts: {},
|
|
2303
|
+
total: 0,
|
|
2304
|
+
children: sectionChildren,
|
|
2305
|
+
depth: 0
|
|
2306
|
+
};
|
|
2307
|
+
rows.push(sectionHeader);
|
|
2308
|
+
const rawSums = sectionRawTotals.get(section.id);
|
|
2309
|
+
rows.push({
|
|
2310
|
+
_type: "section-total",
|
|
2311
|
+
id: `total-${section.id}`,
|
|
2312
|
+
name: `Total ${section.label}`,
|
|
2313
|
+
accountNumber: null,
|
|
2314
|
+
periodAmounts: sectionSums.periodAmounts,
|
|
2315
|
+
total: sectionSums.total,
|
|
2316
|
+
children: [],
|
|
2317
|
+
depth: 0,
|
|
2318
|
+
_rawPeriodAmounts: rawSums?.periodAmounts,
|
|
2319
|
+
_rawTotal: rawSums?.total
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
for (const total of config.totals) {
|
|
2323
|
+
const totalPeriods = {};
|
|
2324
|
+
let totalAmount = 0;
|
|
2325
|
+
const rawTotalPeriods = {};
|
|
2326
|
+
let rawTotalAmount = 0;
|
|
2327
|
+
for (const ref of total.formula) {
|
|
2328
|
+
const subtract = ref.startsWith("-");
|
|
2329
|
+
const sectionId = subtract ? ref.slice(1) : ref;
|
|
2330
|
+
const sums = sectionTotals.get(sectionId);
|
|
2331
|
+
const rawSums = sectionRawTotals.get(sectionId);
|
|
2332
|
+
if (!sums) continue;
|
|
2333
|
+
const mult = subtract ? -1 : 1;
|
|
2334
|
+
totalAmount += sums.total * mult;
|
|
2335
|
+
rawTotalAmount += (rawSums?.total ?? 0) * mult;
|
|
2336
|
+
for (const pk of periods) {
|
|
2337
|
+
totalPeriods[pk] = (totalPeriods[pk] || 0) + (sums.periodAmounts[pk] || 0) * mult;
|
|
2338
|
+
rawTotalPeriods[pk] = (rawTotalPeriods[pk] || 0) + (rawSums?.periodAmounts[pk] || 0) * mult;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
rows.push({
|
|
2342
|
+
_type: "grand-total",
|
|
2343
|
+
id: `grand-total-${total.label.toLowerCase().replace(/\s+/g, "-")}`,
|
|
2344
|
+
name: total.label,
|
|
2345
|
+
accountNumber: null,
|
|
2346
|
+
periodAmounts: totalPeriods,
|
|
2347
|
+
total: totalAmount,
|
|
2348
|
+
children: [],
|
|
2349
|
+
depth: 0,
|
|
2350
|
+
_rawPeriodAmounts: rawTotalPeriods,
|
|
2351
|
+
_rawTotal: rawTotalAmount
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
return { rows, periods };
|
|
2355
|
+
}
|
|
2356
|
+
function buildColumns(periods, unit, rows, showAccountNumbers, renderAmountCell, showRowTotals = true, showVariance, currentPeriodCount, isMobile = false, mobileCompareEnabled = false, periodLabelsOverride, accountColumnLabel = "Account", accountColumnMenuItems) {
|
|
2357
|
+
const desktopSizing = calculateDesktopFinancialColumnSizing({
|
|
2358
|
+
periods,
|
|
2359
|
+
rows,
|
|
2360
|
+
showRowTotals,
|
|
2361
|
+
showVariance: Boolean(showVariance)
|
|
2362
|
+
});
|
|
2363
|
+
const compactMobileAccount = isMobile && periods.length <= 1;
|
|
2364
|
+
const cols = [
|
|
2365
|
+
{
|
|
2366
|
+
id: "account",
|
|
2367
|
+
accessorKey: "name",
|
|
2368
|
+
header: ({ column }) => /* @__PURE__ */ jsx(
|
|
2369
|
+
DataTableColumnHeader,
|
|
2370
|
+
{
|
|
2371
|
+
column,
|
|
2372
|
+
title: accountColumnLabel,
|
|
2373
|
+
customMenuItems: accountColumnMenuItems
|
|
2374
|
+
}
|
|
2375
|
+
),
|
|
2376
|
+
meta: {
|
|
2377
|
+
displayName: accountColumnLabel,
|
|
2378
|
+
icon: BookOpen,
|
|
2379
|
+
allowHorizontalScroll: isMobile && mobileCompareEnabled
|
|
2380
|
+
},
|
|
2381
|
+
cell: ({ row }) => {
|
|
2382
|
+
const r = row.original;
|
|
2383
|
+
if (r._type === "section-header") {
|
|
2384
|
+
return /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: r.name });
|
|
2385
|
+
}
|
|
2386
|
+
if (r._type === "subtotal-group") {
|
|
2387
|
+
return /* @__PURE__ */ jsx("span", { className: "font-semibold", children: r.name });
|
|
2388
|
+
}
|
|
2389
|
+
if (r._type === "section-total" || r._type === "subtotal-total") {
|
|
2390
|
+
return /* @__PURE__ */ jsx("span", { className: "font-semibold", children: r.name });
|
|
2391
|
+
}
|
|
2392
|
+
if (r._type === "grand-total") {
|
|
2393
|
+
return /* @__PURE__ */ jsx("span", { className: "font-bold", children: r.name });
|
|
2394
|
+
}
|
|
2395
|
+
if (r._type === "account-group") {
|
|
2396
|
+
return /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
2397
|
+
showAccountNumbers && r.accountNumber && /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground mr-1.5 text-xs", children: r.accountNumber }),
|
|
2398
|
+
r.name,
|
|
2399
|
+
!isMobile && r.secondaryLabel && /* @__PURE__ */ jsx("span", { className: "ml-1.5 text-[11px] font-normal text-muted-foreground", children: r.secondaryLabel })
|
|
2400
|
+
] });
|
|
2401
|
+
}
|
|
2402
|
+
const isPlaceholder = r.accountSubType === "placeholder";
|
|
2403
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5", children: [
|
|
2404
|
+
/* @__PURE__ */ jsxs(
|
|
2405
|
+
"span",
|
|
2406
|
+
{
|
|
2407
|
+
className: isPlaceholder ? "italic text-muted-foreground" : "",
|
|
2408
|
+
children: [
|
|
2409
|
+
showAccountNumbers && r.accountNumber && /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground mr-1.5 text-xs", children: r.accountNumber }),
|
|
2410
|
+
r.name,
|
|
2411
|
+
!isMobile && r.secondaryLabel && /* @__PURE__ */ jsx("span", { className: "ml-1.5 text-[11px] font-normal text-muted-foreground", children: r.secondaryLabel })
|
|
2412
|
+
]
|
|
2413
|
+
}
|
|
2414
|
+
),
|
|
2415
|
+
r.accountType === "metric" && r.accountSubType && /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 0, children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2416
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
2417
|
+
"button",
|
|
2418
|
+
{
|
|
2419
|
+
type: "button",
|
|
2420
|
+
className: "text-muted-foreground/70 hover:text-foreground transition-colors",
|
|
2421
|
+
"aria-label": `${r.name} metric details`,
|
|
2422
|
+
children: /* @__PURE__ */ jsx(Info, { className: "h-3.5 w-3.5" })
|
|
2423
|
+
}
|
|
2424
|
+
) }),
|
|
2425
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: "top", className: "max-w-[340px] text-xs", children: /* @__PURE__ */ jsx(
|
|
2426
|
+
MetricEquationTooltip,
|
|
2427
|
+
{
|
|
2428
|
+
title: r.name,
|
|
2429
|
+
formula: r.accountSubType
|
|
2430
|
+
}
|
|
2431
|
+
) })
|
|
2432
|
+
] }) })
|
|
2433
|
+
] });
|
|
2434
|
+
},
|
|
2435
|
+
enableSorting: false,
|
|
2436
|
+
// Mobile: shrink account column based on how many period columns need to fit.
|
|
2437
|
+
// 2 periods → moderate shrink, 3+ periods → aggressive shrink
|
|
2438
|
+
size: isMobile ? mobileCompareEnabled || compactMobileAccount ? 180 : periods.length >= 3 ? 120 : periods.length >= 2 ? 160 : 260 : desktopSizing.accountSize,
|
|
2439
|
+
minSize: isMobile ? mobileCompareEnabled || compactMobileAccount ? 120 : periods.length >= 2 ? 80 : 200 : desktopSizing.accountMinSize,
|
|
2440
|
+
maxSize: isMobile && !(mobileCompareEnabled || compactMobileAccount) ? periods.length >= 3 ? 160 : periods.length >= 2 ? 200 : 320 : void 0
|
|
2441
|
+
}
|
|
2442
|
+
];
|
|
2443
|
+
for (const pk of periods) {
|
|
2444
|
+
const label = periodLabelsOverride?.[pk] ?? toPeriodLabel(pk, unit);
|
|
2445
|
+
cols.push({
|
|
2446
|
+
id: `period-${pk}`,
|
|
2447
|
+
accessorFn: (row) => row.periodAmounts[pk] ?? 0,
|
|
2448
|
+
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, title: label }),
|
|
2449
|
+
meta: {
|
|
2450
|
+
displayName: label,
|
|
2451
|
+
icon: Calendar,
|
|
2452
|
+
align: "right"
|
|
2453
|
+
},
|
|
2454
|
+
cell: ({ row }) => {
|
|
2455
|
+
const r = row.original;
|
|
2456
|
+
const val = r.periodAmounts[pk] ?? 0;
|
|
2457
|
+
if (r._type === "section-header") return null;
|
|
2458
|
+
const isTotalRow = r._type === "section-total" || r._type === "grand-total";
|
|
2459
|
+
const bold = r._type === "grand-total" ? "font-bold" : r._type === "section-total" || r._type === "subtotal-total" || r._type === "account-group" || r._type === "subtotal-group" ? "font-semibold" : "";
|
|
2460
|
+
if (renderAmountCell) {
|
|
2461
|
+
const cellNode = renderAmountCell(r, pk, val);
|
|
2462
|
+
return /* @__PURE__ */ jsx(
|
|
2463
|
+
"span",
|
|
2464
|
+
{
|
|
2465
|
+
className: cn(
|
|
2466
|
+
bold,
|
|
2467
|
+
"inline-flex cursor-help border-b border-dotted border-muted-foreground/35 leading-none hover:border-muted-foreground/60"
|
|
2468
|
+
),
|
|
2469
|
+
children: cellNode
|
|
2470
|
+
}
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
const rawVal = isTotalRow ? r._rawPeriodAmounts?.[pk] : void 0;
|
|
2474
|
+
if (isTotalRow && rawVal !== void 0) {
|
|
2475
|
+
return /* @__PURE__ */ jsxs(
|
|
2476
|
+
"span",
|
|
2477
|
+
{
|
|
2478
|
+
className: cn(
|
|
2479
|
+
bold,
|
|
2480
|
+
"flex items-center justify-end gap-1.5 group/rec w-full h-full"
|
|
2481
|
+
),
|
|
2482
|
+
children: [
|
|
2483
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-0 group-hover/rec:opacity-100 transition-opacity flex-shrink-0 leading-[0]", children: /* @__PURE__ */ jsx(RecCheck, { computed: val, raw: rawVal }) }),
|
|
2484
|
+
/* @__PURE__ */ jsx("span", { className: "leading-none", children: /* @__PURE__ */ jsx(
|
|
2485
|
+
DataTableCurrencyCell,
|
|
2486
|
+
{
|
|
2487
|
+
value: val,
|
|
2488
|
+
dashZero: true,
|
|
2489
|
+
isSummaryRow: r._type === "section-total" || r._type === "grand-total" || r._type === "subtotal-total"
|
|
2490
|
+
}
|
|
2491
|
+
) })
|
|
2492
|
+
]
|
|
2493
|
+
}
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
return /* @__PURE__ */ jsx("span", { className: bold, children: /* @__PURE__ */ jsx(
|
|
2497
|
+
DataTableCurrencyCell,
|
|
2498
|
+
{
|
|
2499
|
+
value: val,
|
|
2500
|
+
dashZero: true,
|
|
2501
|
+
isSummaryRow: r._type === "section-total" || r._type === "grand-total" || r._type === "subtotal-total"
|
|
2502
|
+
}
|
|
2503
|
+
) });
|
|
2504
|
+
},
|
|
2505
|
+
enableSorting: false,
|
|
2506
|
+
// Mobile compare mode: data columns stay readable but can shrink/grow dynamically.
|
|
2507
|
+
size: isMobile ? mobileCompareEnabled ? 96 : 110 : desktopSizing.dataSize,
|
|
2508
|
+
minSize: isMobile ? mobileCompareEnabled ? 82 : 80 : desktopSizing.dataMinSize,
|
|
2509
|
+
maxSize: isMobile && mobileCompareEnabled ? 112 : void 0
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2512
|
+
if (showRowTotals) {
|
|
2513
|
+
cols.push({
|
|
2514
|
+
id: "total",
|
|
2515
|
+
accessorFn: (row) => row.total,
|
|
2516
|
+
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, title: "Total" }),
|
|
2517
|
+
meta: {
|
|
2518
|
+
displayName: "Total",
|
|
2519
|
+
icon: DollarSign,
|
|
2520
|
+
align: "right"
|
|
2521
|
+
},
|
|
2522
|
+
cell: ({ row }) => {
|
|
2523
|
+
const r = row.original;
|
|
2524
|
+
if (r._type === "section-header") return null;
|
|
2525
|
+
const isTotalRow = r._type === "section-total" || r._type === "grand-total";
|
|
2526
|
+
const bold = r._type === "grand-total" ? "font-bold" : r._type === "section-total" || r._type === "subtotal-total" || r._type === "account-group" || r._type === "subtotal-group" ? "font-semibold" : "";
|
|
2527
|
+
if (renderAmountCell) {
|
|
2528
|
+
const cellNode = renderAmountCell(r, null, r.total);
|
|
2529
|
+
return /* @__PURE__ */ jsx(
|
|
2530
|
+
"span",
|
|
2531
|
+
{
|
|
2532
|
+
className: cn(
|
|
2533
|
+
bold,
|
|
2534
|
+
"inline-flex cursor-help border-b border-dotted border-muted-foreground/35 leading-none hover:border-muted-foreground/60"
|
|
2535
|
+
),
|
|
2536
|
+
children: cellNode
|
|
2537
|
+
}
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
if (isTotalRow && r._rawTotal !== void 0) {
|
|
2541
|
+
return /* @__PURE__ */ jsxs(
|
|
2542
|
+
"span",
|
|
2543
|
+
{
|
|
2544
|
+
className: cn(
|
|
2545
|
+
bold,
|
|
2546
|
+
"flex items-center justify-end gap-1.5 group/rec w-full h-full"
|
|
2547
|
+
),
|
|
2548
|
+
children: [
|
|
2549
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-0 group-hover/rec:opacity-100 transition-opacity flex-shrink-0 leading-[0]", children: /* @__PURE__ */ jsx(RecCheck, { computed: r.total, raw: r._rawTotal }) }),
|
|
2550
|
+
/* @__PURE__ */ jsx("span", { className: "leading-none", children: /* @__PURE__ */ jsx(
|
|
2551
|
+
DataTableCurrencyCell,
|
|
2552
|
+
{
|
|
2553
|
+
value: r.total,
|
|
2554
|
+
dashZero: true,
|
|
2555
|
+
isSummaryRow: r._type === "section-total" || r._type === "grand-total" || r._type === "subtotal-total"
|
|
2556
|
+
}
|
|
2557
|
+
) })
|
|
2558
|
+
]
|
|
2559
|
+
}
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
return /* @__PURE__ */ jsx("span", { className: bold, children: /* @__PURE__ */ jsx(
|
|
2563
|
+
DataTableCurrencyCell,
|
|
2564
|
+
{
|
|
2565
|
+
value: r.total,
|
|
2566
|
+
dashZero: true,
|
|
2567
|
+
isSummaryRow: r._type === "section-total" || r._type === "grand-total" || r._type === "subtotal-total"
|
|
2568
|
+
}
|
|
2569
|
+
) });
|
|
2570
|
+
},
|
|
2571
|
+
enableSorting: false,
|
|
2572
|
+
size: isMobile ? 130 : desktopSizing.totalSize,
|
|
2573
|
+
minSize: isMobile ? 100 : 96
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
if (showVariance && currentPeriodCount && currentPeriodCount > 0) {
|
|
2577
|
+
const priorPeriods = periods.slice(0, periods.length - currentPeriodCount);
|
|
2578
|
+
const currentPeriods = periods.slice(periods.length - currentPeriodCount);
|
|
2579
|
+
cols.push({
|
|
2580
|
+
id: "variance",
|
|
2581
|
+
accessorFn: (row) => {
|
|
2582
|
+
const currentSum = currentPeriods.reduce(
|
|
2583
|
+
(s, pk) => s + (row.periodAmounts[pk] ?? 0),
|
|
2584
|
+
0
|
|
2585
|
+
);
|
|
2586
|
+
const priorSum = priorPeriods.reduce(
|
|
2587
|
+
(s, pk) => s + (row.periodAmounts[pk] ?? 0),
|
|
2588
|
+
0
|
|
2589
|
+
);
|
|
2590
|
+
if (priorSum === 0) return currentSum === 0 ? 0 : Infinity;
|
|
2591
|
+
return (currentSum - priorSum) / Math.abs(priorSum) * 100;
|
|
2592
|
+
},
|
|
2593
|
+
header: ({ column }) => /* @__PURE__ */ jsx(DataTableColumnHeader, { column, title: "\u0394%" }),
|
|
2594
|
+
meta: {
|
|
2595
|
+
displayName: "Variance %",
|
|
2596
|
+
icon: TrendingUp,
|
|
2597
|
+
align: "right"
|
|
2598
|
+
},
|
|
2599
|
+
cell: ({ row }) => {
|
|
2600
|
+
const r = row.original;
|
|
2601
|
+
if (r._type === "section-header") return null;
|
|
2602
|
+
const currentSum = currentPeriods.reduce(
|
|
2603
|
+
(s, pk) => s + (r.periodAmounts[pk] ?? 0),
|
|
2604
|
+
0
|
|
2605
|
+
);
|
|
2606
|
+
const priorSum = priorPeriods.reduce(
|
|
2607
|
+
(s, pk) => s + (r.periodAmounts[pk] ?? 0),
|
|
2608
|
+
0
|
|
2609
|
+
);
|
|
2610
|
+
if (priorSum === 0 && currentSum === 0)
|
|
2611
|
+
return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2013" });
|
|
2612
|
+
if (priorSum === 0)
|
|
2613
|
+
return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "n/a" });
|
|
2614
|
+
const pct = (currentSum - priorSum) / Math.abs(priorSum) * 100;
|
|
2615
|
+
const isPositive = pct > 0;
|
|
2616
|
+
const isNegative = pct < 0;
|
|
2617
|
+
const bold = r._type === "grand-total" ? "font-bold" : r._type === "section-total" || r._type === "subtotal-total" ? "font-semibold" : "";
|
|
2618
|
+
return /* @__PURE__ */ jsxs(
|
|
2619
|
+
"span",
|
|
2620
|
+
{
|
|
2621
|
+
className: cn(
|
|
2622
|
+
bold,
|
|
2623
|
+
"tabular-nums text-xs",
|
|
2624
|
+
isPositive && "text-emerald-500",
|
|
2625
|
+
isNegative && "text-red-500",
|
|
2626
|
+
!isPositive && !isNegative && "text-muted-foreground"
|
|
2627
|
+
),
|
|
2628
|
+
children: [
|
|
2629
|
+
isPositive ? "+" : "",
|
|
2630
|
+
pct.toFixed(1),
|
|
2631
|
+
"%"
|
|
2632
|
+
]
|
|
2633
|
+
}
|
|
2634
|
+
);
|
|
2635
|
+
},
|
|
2636
|
+
enableSorting: false,
|
|
2637
|
+
size: 80,
|
|
2638
|
+
minSize: 60
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
return cols;
|
|
2642
|
+
}
|
|
2643
|
+
function getRowClassName(row, rowImportanceResolver) {
|
|
2644
|
+
const structuralClass = (() => {
|
|
2645
|
+
switch (row.original._type) {
|
|
2646
|
+
case "section-header":
|
|
2647
|
+
return "bg-muted/30 border-b-0";
|
|
2648
|
+
case "subtotal-group":
|
|
2649
|
+
return "bg-muted/20";
|
|
2650
|
+
case "section-total":
|
|
2651
|
+
case "subtotal-total":
|
|
2652
|
+
return "bg-muted/40 border-t border-border font-semibold";
|
|
2653
|
+
case "grand-total":
|
|
2654
|
+
return "bg-muted/60 border-t-2 border-double border-border";
|
|
2655
|
+
default:
|
|
2656
|
+
return void 0;
|
|
2657
|
+
}
|
|
2658
|
+
})();
|
|
2659
|
+
const importanceClass = rowImportanceResolver ? getFinancialStatementRowImportanceClassName(
|
|
2660
|
+
rowImportanceResolver(row.original)
|
|
2661
|
+
) : void 0;
|
|
2662
|
+
return cn(structuralClass, importanceClass);
|
|
2663
|
+
}
|
|
2664
|
+
function FinancialStatementTable({
|
|
2665
|
+
data,
|
|
2666
|
+
timeUnit,
|
|
2667
|
+
config,
|
|
2668
|
+
showAccountNumbers = true,
|
|
2669
|
+
treeIndentPx = 20,
|
|
2670
|
+
subtotalRules,
|
|
2671
|
+
hideZeroRows = false,
|
|
2672
|
+
preserveEmptySections = false,
|
|
2673
|
+
showRowTotals = true,
|
|
2674
|
+
showVariance = false,
|
|
2675
|
+
currentPeriodCount,
|
|
2676
|
+
toolbar,
|
|
2677
|
+
renderAmountCell,
|
|
2678
|
+
isMobile = false,
|
|
2679
|
+
mobileCompareEnabled = false,
|
|
2680
|
+
periodOrder,
|
|
2681
|
+
periodColumnOrder = "oldest-first",
|
|
2682
|
+
periodLabels,
|
|
2683
|
+
accountColumnLabel,
|
|
2684
|
+
accountColumnMenuItems,
|
|
2685
|
+
className,
|
|
2686
|
+
rowImportanceResolver
|
|
2687
|
+
}) {
|
|
2688
|
+
const [expanded, setExpanded] = React3.useState({});
|
|
2689
|
+
const [columnVisibility, setColumnVisibility] = React3.useState({});
|
|
2690
|
+
const [viewportLayout, setViewportLayout] = React3.useState("unknown");
|
|
2691
|
+
const { rows: rawRows, periods } = React3.useMemo(
|
|
2692
|
+
() => buildFinancialStatementData(data, config, timeUnit, subtotalRules, {
|
|
2693
|
+
preserveEmptySections
|
|
2694
|
+
}),
|
|
2695
|
+
[data, config, timeUnit, subtotalRules, preserveEmptySections]
|
|
2696
|
+
);
|
|
2697
|
+
const rows = React3.useMemo(
|
|
2698
|
+
() => hideZeroRows ? filterZeroRows(rawRows) : rawRows,
|
|
2699
|
+
[rawRows, hideZeroRows]
|
|
2700
|
+
);
|
|
2701
|
+
const orderedPeriods = React3.useMemo(
|
|
2702
|
+
() => resolvePeriodColumns(periods, timeUnit, periodOrder, periodColumnOrder),
|
|
2703
|
+
[periods, timeUnit, periodOrder, periodColumnOrder]
|
|
2704
|
+
);
|
|
2705
|
+
const effectiveShowRowTotals = isMobile && mobileCompareEnabled ? false : showRowTotals;
|
|
2706
|
+
const columns = React3.useMemo(
|
|
2707
|
+
() => buildColumns(
|
|
2708
|
+
orderedPeriods,
|
|
2709
|
+
timeUnit,
|
|
2710
|
+
rows,
|
|
2711
|
+
showAccountNumbers,
|
|
2712
|
+
renderAmountCell,
|
|
2713
|
+
effectiveShowRowTotals,
|
|
2714
|
+
showVariance,
|
|
2715
|
+
currentPeriodCount,
|
|
2716
|
+
isMobile,
|
|
2717
|
+
mobileCompareEnabled,
|
|
2718
|
+
periodLabels,
|
|
2719
|
+
accountColumnLabel,
|
|
2720
|
+
accountColumnMenuItems
|
|
2721
|
+
),
|
|
2722
|
+
[
|
|
2723
|
+
orderedPeriods,
|
|
2724
|
+
timeUnit,
|
|
2725
|
+
rows,
|
|
2726
|
+
showAccountNumbers,
|
|
2727
|
+
renderAmountCell,
|
|
2728
|
+
effectiveShowRowTotals,
|
|
2729
|
+
showVariance,
|
|
2730
|
+
currentPeriodCount,
|
|
2731
|
+
isMobile,
|
|
2732
|
+
mobileCompareEnabled,
|
|
2733
|
+
periodLabels,
|
|
2734
|
+
accountColumnLabel,
|
|
2735
|
+
accountColumnMenuItems
|
|
2736
|
+
]
|
|
2737
|
+
);
|
|
2738
|
+
React3.useEffect(() => {
|
|
2739
|
+
if (typeof window === "undefined") return;
|
|
2740
|
+
const updateViewportLayout = () => {
|
|
2741
|
+
setViewportLayout(
|
|
2742
|
+
window.innerWidth > window.innerHeight ? "landscape" : "portrait"
|
|
2743
|
+
);
|
|
2744
|
+
};
|
|
2745
|
+
updateViewportLayout();
|
|
2746
|
+
window.addEventListener("resize", updateViewportLayout);
|
|
2747
|
+
window.addEventListener("orientationchange", updateViewportLayout);
|
|
2748
|
+
return () => {
|
|
2749
|
+
window.removeEventListener("resize", updateViewportLayout);
|
|
2750
|
+
window.removeEventListener("orientationchange", updateViewportLayout);
|
|
2751
|
+
};
|
|
2752
|
+
}, []);
|
|
2753
|
+
React3.useEffect(() => {
|
|
2754
|
+
if (rows.length === 0) return;
|
|
2755
|
+
const toExpand = {};
|
|
2756
|
+
for (const row of rows) {
|
|
2757
|
+
if (row._type === "section-header") {
|
|
2758
|
+
toExpand[row.id] = true;
|
|
2759
|
+
for (const child of row.children) {
|
|
2760
|
+
if (child._type === "account-group" || child._type === "subtotal-group") {
|
|
2761
|
+
toExpand[child.id] = true;
|
|
2762
|
+
if (child._type === "subtotal-group") {
|
|
2763
|
+
for (const grandchild of child.children) {
|
|
2764
|
+
if (grandchild._type === "account-group") {
|
|
2765
|
+
toExpand[grandchild.id] = true;
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
setExpanded(toExpand);
|
|
2774
|
+
}, [rows]);
|
|
2775
|
+
const table = useReactTable({
|
|
2776
|
+
data: rows,
|
|
2777
|
+
columns,
|
|
2778
|
+
getRowId: (row) => row.id,
|
|
2779
|
+
getCoreRowModel: getCoreRowModel(),
|
|
2780
|
+
getSubRows: (row) => row.children.length > 0 ? row.children : void 0,
|
|
2781
|
+
getExpandedRowModel: getExpandedRowModel(),
|
|
2782
|
+
state: { expanded, columnVisibility },
|
|
2783
|
+
onExpandedChange: setExpanded,
|
|
2784
|
+
onColumnVisibilityChange: setColumnVisibility
|
|
2785
|
+
});
|
|
2786
|
+
return /* @__PURE__ */ jsxs("div", { className, children: [
|
|
2787
|
+
toolbar && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-end gap-1 pb-1", children: toolbar }),
|
|
2788
|
+
/* @__PURE__ */ jsx(
|
|
2789
|
+
DataTable,
|
|
2790
|
+
{
|
|
2791
|
+
table,
|
|
2792
|
+
columns,
|
|
2793
|
+
enableTreeView: true,
|
|
2794
|
+
treeIndentPx,
|
|
2795
|
+
getRowClassName: ((row) => getRowClassName(row, rowImportanceResolver)),
|
|
2796
|
+
enableColumnResizing: true,
|
|
2797
|
+
enableRowDrag: false,
|
|
2798
|
+
rowSelectionStyle: "none",
|
|
2799
|
+
density: "compact",
|
|
2800
|
+
fontSize: "sm",
|
|
2801
|
+
stickyColumnIds: ["account"]
|
|
2802
|
+
},
|
|
2803
|
+
`fs-${isMobile ? "m" : "d"}-${mobileCompareEnabled ? "cmp" : "std"}-${viewportLayout}`
|
|
2804
|
+
)
|
|
2805
|
+
] });
|
|
2806
|
+
}
|
|
2807
|
+
function flattenStatementForExport(data, config, timeUnit, subtotalRules, hideZeroRows) {
|
|
2808
|
+
let { rows: tree, periods } = buildFinancialStatementData(
|
|
2809
|
+
data,
|
|
2810
|
+
config,
|
|
2811
|
+
timeUnit,
|
|
2812
|
+
subtotalRules
|
|
2813
|
+
);
|
|
2814
|
+
if (hideZeroRows) tree = filterZeroRows(tree);
|
|
2815
|
+
const flat = [];
|
|
2816
|
+
function walk(rows, indent) {
|
|
2817
|
+
for (const row of rows) {
|
|
2818
|
+
const prefix = " ".repeat(indent);
|
|
2819
|
+
const exportRow = {
|
|
2820
|
+
type: row._type,
|
|
2821
|
+
account: `${prefix}${row.accountNumber ? `${row.accountNumber} ` : ""}${row.name}`,
|
|
2822
|
+
accountNumber: row.accountNumber ?? ""
|
|
2823
|
+
};
|
|
2824
|
+
for (const pk of periods) {
|
|
2825
|
+
exportRow[pk] = row.periodAmounts[pk] ?? 0;
|
|
2826
|
+
}
|
|
2827
|
+
exportRow.total = row.total;
|
|
2828
|
+
if (row._type !== "section-header" || row.children.length === 0) {
|
|
2829
|
+
flat.push(exportRow);
|
|
2830
|
+
} else {
|
|
2831
|
+
flat.push(exportRow);
|
|
2832
|
+
}
|
|
2833
|
+
if (row.children.length > 0) {
|
|
2834
|
+
walk(row.children, indent + 1);
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
walk(tree, 0);
|
|
2839
|
+
return { rows: flat, periodKeys: periods };
|
|
2840
|
+
}
|
|
2841
|
+
var INCOME_STATEMENT_CONFIG = {
|
|
2842
|
+
sections: [
|
|
2843
|
+
{ id: "revenue", label: "Revenue", accountClasses: ["Revenue"] },
|
|
2844
|
+
{ id: "expenses", label: "Expenses", accountClasses: ["Expense"] }
|
|
2845
|
+
],
|
|
2846
|
+
totals: [{ label: "Net Income", formula: ["revenue", "-expenses"] }]
|
|
2847
|
+
};
|
|
2848
|
+
var BALANCE_SHEET_CONFIG = {
|
|
2849
|
+
sections: [
|
|
2850
|
+
{ id: "assets", label: "Assets", accountClasses: ["Asset"] },
|
|
2851
|
+
{ id: "liabilities", label: "Liabilities", accountClasses: ["Liability"] },
|
|
2852
|
+
{ id: "equity", label: "Equity", accountClasses: ["Equity"] }
|
|
2853
|
+
],
|
|
2854
|
+
totals: [
|
|
2855
|
+
{
|
|
2856
|
+
label: "Total Liabilities & Equity",
|
|
2857
|
+
formula: ["liabilities", "equity"]
|
|
2858
|
+
}
|
|
2859
|
+
]
|
|
2860
|
+
};
|
|
2861
|
+
|
|
2862
|
+
// src/hooks/spreadsheetWorkbook.ts
|
|
2863
|
+
function escapeXml(value) {
|
|
2864
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2865
|
+
}
|
|
2866
|
+
function styleId(style) {
|
|
2867
|
+
if (!style) return void 0;
|
|
2868
|
+
const map = {
|
|
2869
|
+
header: "Header",
|
|
2870
|
+
text: "Text",
|
|
2871
|
+
number: "Number",
|
|
2872
|
+
currency: "Currency",
|
|
2873
|
+
percent: "Percent",
|
|
2874
|
+
totalLabel: "TotalLabel",
|
|
2875
|
+
totalNumber: "TotalNumber"
|
|
2876
|
+
};
|
|
2877
|
+
return map[style];
|
|
2878
|
+
}
|
|
2879
|
+
function excelColumnName(columnIndex) {
|
|
2880
|
+
let n = columnIndex + 1;
|
|
2881
|
+
let result = "";
|
|
2882
|
+
while (n > 0) {
|
|
2883
|
+
const rem = (n - 1) % 26;
|
|
2884
|
+
result = String.fromCharCode(65 + rem) + result;
|
|
2885
|
+
n = Math.floor((n - 1) / 26);
|
|
2886
|
+
}
|
|
2887
|
+
return result;
|
|
2888
|
+
}
|
|
2889
|
+
function buildSpreadsheetWorkbookXml({
|
|
2890
|
+
worksheetName,
|
|
2891
|
+
rows
|
|
2892
|
+
}) {
|
|
2893
|
+
const xmlRows = rows.map(
|
|
2894
|
+
(row) => `<Row>${row.map((cell) => {
|
|
2895
|
+
const inferredType = cell.type ?? (typeof cell.value === "number" || cell.formula ? "Number" : "String");
|
|
2896
|
+
const rawValue = cell.value ?? "";
|
|
2897
|
+
const value = inferredType === "Number" ? Number(rawValue || 0) : escapeXml(String(rawValue));
|
|
2898
|
+
const style = styleId(cell.style);
|
|
2899
|
+
const styleAttr = style ? ` ss:StyleID="${style}"` : "";
|
|
2900
|
+
const formulaAttr = cell.formula ? ` ss:Formula="=${escapeXml(cell.formula)}"` : "";
|
|
2901
|
+
return `<Cell${styleAttr}${formulaAttr}><Data ss:Type="${inferredType}">${value}</Data></Cell>`;
|
|
2902
|
+
}).join("")}</Row>`
|
|
2903
|
+
).join("\n");
|
|
2904
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
2905
|
+
<?mso-application progid="Excel.Sheet"?>
|
|
2906
|
+
<Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
|
|
2907
|
+
<Styles>
|
|
2908
|
+
<Style ss:ID="Header"><Font ss:Bold="1"/><Interior ss:Color="#F3F4F6" ss:Pattern="Solid"/></Style>
|
|
2909
|
+
<Style ss:ID="Text"><NumberFormat ss:Format="@"/></Style>
|
|
2910
|
+
<Style ss:ID="Number"><NumberFormat ss:Format="#,##0.00"/></Style>
|
|
2911
|
+
<Style ss:ID="Currency"><NumberFormat ss:Format="$#,##0.00;[Red]($#,##0.00)"/></Style>
|
|
2912
|
+
<Style ss:ID="Percent"><NumberFormat ss:Format="0.00%"/></Style>
|
|
2913
|
+
<Style ss:ID="TotalLabel"><Font ss:Bold="1"/><Interior ss:Color="#F9FAFB" ss:Pattern="Solid"/></Style>
|
|
2914
|
+
<Style ss:ID="TotalNumber"><Font ss:Bold="1"/><NumberFormat ss:Format="$#,##0.00;[Red]($#,##0.00)"/><Interior ss:Color="#F9FAFB" ss:Pattern="Solid"/></Style>
|
|
2915
|
+
</Styles>
|
|
2916
|
+
<Worksheet ss:Name="${escapeXml(worksheetName)}"><Table>
|
|
2917
|
+
${xmlRows}
|
|
2918
|
+
</Table></Worksheet></Workbook>`;
|
|
2919
|
+
}
|
|
2920
|
+
function downloadExcelXml(xml, filename) {
|
|
2921
|
+
const blob = new Blob([xml], {
|
|
2922
|
+
type: "application/vnd.ms-excel;charset=utf-8;"
|
|
2923
|
+
});
|
|
2924
|
+
const url = URL.createObjectURL(blob);
|
|
2925
|
+
const link = document.createElement("a");
|
|
2926
|
+
link.href = url;
|
|
2927
|
+
link.download = `${filename}.xls`;
|
|
2928
|
+
link.click();
|
|
2929
|
+
URL.revokeObjectURL(url);
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
export { BALANCE_SHEET_CONFIG, DataTable, DataTableColumnFilter, DataTableColumnFilterContent, DataTableColumnHeader, DataTableContext, DataTableCurrencyCell, DataTableFilterBadges, FinanceDecimalsProvider, FinanceDisplayProvider, FinancialStatementTable, INCOME_STATEMENT_CONFIG, Popover, PopoverAnchor, PopoverContent, PopoverTrigger, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, buildFinancialStatementData, buildSpreadsheetWorkbookXml, calculateDesktopFinancialColumnSizing, createFinancialStatementRowImportanceResolver, downloadExcelXml, excelColumnName, flattenStatementForExport, formatCurrencyValue, getFinancialStatementRowImportanceClassName, getUnitDivisor, getUnitLabel, getUnitSuffix, resolvePeriodColumns, useDataTableContext, useFinanceDecimals, useFinanceDecimalsSetting, useFinanceDisplay };
|