@wealthx/shadcn 1.5.1 → 1.5.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/.turbo/turbo-build.log +118 -118
- package/CHANGELOG.md +6 -0
- package/dist/chunk-G2EWIP2N.mjs +960 -0
- package/dist/{chunk-MHHA7QGO.mjs → chunk-ODO6BUOF.mjs} +1 -1
- package/dist/chunk-PX4M67XQ.mjs +301 -0
- package/dist/{chunk-FYUSF5KO.mjs → chunk-QRVEI6J3.mjs} +1 -1
- package/dist/{chunk-42NEC57Y.mjs → chunk-RAKBWNQH.mjs} +272 -3
- package/dist/components/ui/{contact-alert-dialog.js → contact-alert-dialog/index.js} +1029 -593
- package/dist/components/ui/contact-alert-dialog/index.mjs +31 -0
- package/dist/components/ui/file-preview-dialog.js +407 -100
- package/dist/components/ui/file-preview-dialog.mjs +3 -1
- package/dist/components/ui/kanban-column.js +408 -113
- package/dist/components/ui/kanban-column.mjs +3 -2
- package/dist/components/ui/opportunity-card.js +383 -88
- package/dist/components/ui/opportunity-card.mjs +2 -1
- package/dist/components/ui/pipeline-board.js +424 -129
- package/dist/components/ui/pipeline-board.mjs +4 -3
- package/dist/index.js +3081 -2282
- package/dist/index.mjs +39 -35
- package/dist/styles.css +1 -1
- package/package.json +5 -4
- package/src/components/index.tsx +3 -2
- package/src/components/ui/contact-alert-dialog/builder-ui.tsx +556 -0
- package/src/components/ui/contact-alert-dialog/config.ts +262 -0
- package/src/components/ui/contact-alert-dialog/contact-alert-dialog.tsx +214 -0
- package/src/components/ui/contact-alert-dialog/index.tsx +15 -0
- package/src/components/ui/contact-alert-dialog/types.ts +61 -0
- package/src/components/ui/contact-alert-dialog/utils.ts +93 -0
- package/src/components/ui/file-preview-dialog.tsx +299 -99
- package/src/components/ui/opportunity-card.tsx +328 -1
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +1 -1
- package/dist/chunk-5WMFKQZ6.mjs +0 -180
- package/dist/chunk-Y24TXIFJ.mjs +0 -518
- package/dist/components/ui/contact-alert-dialog.mjs +0 -27
- package/src/components/ui/contact-alert-dialog.tsx +0 -710
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wealthx/shadcn",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"@base-ui/react": "^1.3.0",
|
|
18
18
|
"@fontsource-variable/figtree": "^5.2.10",
|
|
19
|
+
"@react-awesome-query-builder/ui": "6.7.0-alpha.0",
|
|
19
20
|
"@tanstack/react-table": "8.21.3",
|
|
20
21
|
"chart.js": "^4.5.1",
|
|
21
22
|
"chartjs-plugin-datalabels": "^2.2.0",
|
|
@@ -503,9 +504,9 @@
|
|
|
503
504
|
"require": "./dist/components/ui/review-alerts-dialog.js"
|
|
504
505
|
},
|
|
505
506
|
"./contact-alert-dialog": {
|
|
506
|
-
"types": "./src/components/ui/contact-alert-dialog.tsx",
|
|
507
|
-
"import": "./dist/components/ui/contact-alert-dialog.mjs",
|
|
508
|
-
"require": "./dist/components/ui/contact-alert-dialog.js"
|
|
507
|
+
"types": "./src/components/ui/contact-alert-dialog/index.tsx",
|
|
508
|
+
"import": "./dist/components/ui/contact-alert-dialog/index.mjs",
|
|
509
|
+
"require": "./dist/components/ui/contact-alert-dialog/index.js"
|
|
509
510
|
},
|
|
510
511
|
"./share-details-dialog": {
|
|
511
512
|
"types": "./src/components/ui/share-details-dialog.tsx",
|
package/src/components/index.tsx
CHANGED
|
@@ -461,6 +461,8 @@ export {
|
|
|
461
461
|
ContactAlertQueryBuilder,
|
|
462
462
|
ContactAlertDialog,
|
|
463
463
|
ALERT_QUERY_FIELDS,
|
|
464
|
+
AlertSharingType,
|
|
465
|
+
createAlertTree,
|
|
464
466
|
} from "./ui/contact-alert-dialog";
|
|
465
467
|
export type {
|
|
466
468
|
AlertQueryCombinator,
|
|
@@ -468,11 +470,10 @@ export type {
|
|
|
468
470
|
AlertQueryFieldType,
|
|
469
471
|
ContactAlertSeverity,
|
|
470
472
|
AlertQueryField,
|
|
471
|
-
AlertQueryRule,
|
|
472
|
-
AlertQueryGroup,
|
|
473
473
|
ContactAlertQueryBuilderProps,
|
|
474
474
|
ContactAlertDialogProps,
|
|
475
475
|
} from "./ui/contact-alert-dialog";
|
|
476
|
+
export type { ImmutableTree, JsonGroup } from "@react-awesome-query-builder/ui";
|
|
476
477
|
|
|
477
478
|
export {
|
|
478
479
|
ShareDetailsDialog,
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ChevronDownIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
|
3
|
+
import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion";
|
|
4
|
+
import {
|
|
5
|
+
Utils as QbUtils,
|
|
6
|
+
type Actions,
|
|
7
|
+
type ImmutableTree,
|
|
8
|
+
type JsonItem,
|
|
9
|
+
} from "@react-awesome-query-builder/ui";
|
|
10
|
+
import { Button } from "@/components/ui/button";
|
|
11
|
+
import {
|
|
12
|
+
InputGroup,
|
|
13
|
+
InputGroupAddon,
|
|
14
|
+
InputGroupInput,
|
|
15
|
+
InputGroupText,
|
|
16
|
+
} from "@/components/ui/input-group";
|
|
17
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
18
|
+
import { Label } from "@/components/ui/label";
|
|
19
|
+
import {
|
|
20
|
+
Select,
|
|
21
|
+
SelectContent,
|
|
22
|
+
SelectItem,
|
|
23
|
+
SelectTrigger,
|
|
24
|
+
SelectValue,
|
|
25
|
+
} from "@/components/ui/select";
|
|
26
|
+
import { cn } from "@/lib/utils";
|
|
27
|
+
import type { AlertQueryField, AlertQueryOperator } from "./types";
|
|
28
|
+
import {
|
|
29
|
+
ALL_NUMERIC_OPERATORS,
|
|
30
|
+
BOOLEAN_OPERATORS,
|
|
31
|
+
OPERATOR_LABELS,
|
|
32
|
+
} from "./config";
|
|
33
|
+
import {
|
|
34
|
+
allOperatorLabels,
|
|
35
|
+
formatWithCommas,
|
|
36
|
+
longestOf,
|
|
37
|
+
parseCommas,
|
|
38
|
+
ruleSummary,
|
|
39
|
+
} from "./utils";
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// SelectAutoWidth
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
function SelectAutoWidth({
|
|
46
|
+
longestLabel,
|
|
47
|
+
children,
|
|
48
|
+
}: {
|
|
49
|
+
longestLabel: string;
|
|
50
|
+
children: React.ReactNode;
|
|
51
|
+
}) {
|
|
52
|
+
return (
|
|
53
|
+
<div className="relative inline-block shrink-0">
|
|
54
|
+
<span
|
|
55
|
+
aria-hidden
|
|
56
|
+
className="invisible block h-8 whitespace-nowrap pl-3 pr-10 text-body-medium"
|
|
57
|
+
>
|
|
58
|
+
{longestLabel}
|
|
59
|
+
</span>
|
|
60
|
+
<div className="absolute inset-0">{children}</div>
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// ValueInput
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
function ValueInput({
|
|
70
|
+
value,
|
|
71
|
+
onChange,
|
|
72
|
+
unit,
|
|
73
|
+
placeholder = "0",
|
|
74
|
+
}: {
|
|
75
|
+
value: string;
|
|
76
|
+
onChange: (v: string) => void;
|
|
77
|
+
unit?: "dollar" | "percent";
|
|
78
|
+
placeholder?: string;
|
|
79
|
+
}) {
|
|
80
|
+
return (
|
|
81
|
+
<InputGroup className="w-36">
|
|
82
|
+
{unit === "dollar" && (
|
|
83
|
+
<InputGroupAddon align="inline-start">
|
|
84
|
+
<InputGroupText>$</InputGroupText>
|
|
85
|
+
</InputGroupAddon>
|
|
86
|
+
)}
|
|
87
|
+
<InputGroupInput
|
|
88
|
+
type="text"
|
|
89
|
+
inputMode="numeric"
|
|
90
|
+
value={formatWithCommas(value)}
|
|
91
|
+
onChange={(e) => onChange(parseCommas(e.target.value))}
|
|
92
|
+
placeholder={placeholder}
|
|
93
|
+
/>
|
|
94
|
+
{unit === "percent" && (
|
|
95
|
+
<InputGroupAddon align="inline-end">
|
|
96
|
+
<InputGroupText>%</InputGroupText>
|
|
97
|
+
</InputGroupAddon>
|
|
98
|
+
)}
|
|
99
|
+
</InputGroup>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// RuleEditorFields
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function RuleEditorFields({
|
|
108
|
+
ruleProps,
|
|
109
|
+
path,
|
|
110
|
+
actions,
|
|
111
|
+
fields,
|
|
112
|
+
longestFieldLabel,
|
|
113
|
+
longestOperatorLabel,
|
|
114
|
+
}: {
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
+
ruleProps: any;
|
|
117
|
+
path: string[];
|
|
118
|
+
actions: Actions;
|
|
119
|
+
fields: AlertQueryField[];
|
|
120
|
+
longestFieldLabel: string;
|
|
121
|
+
longestOperatorLabel: string;
|
|
122
|
+
}) {
|
|
123
|
+
const field: string = ruleProps?.field ?? fields[0]?.key ?? "";
|
|
124
|
+
const operator: string = ruleProps?.operator ?? "greater_or_equal";
|
|
125
|
+
const value0 = ruleProps?.value?.[0];
|
|
126
|
+
const value1 = ruleProps?.value?.[1];
|
|
127
|
+
|
|
128
|
+
const fieldDef = fields.find((f) => f.key === field) ?? fields[0];
|
|
129
|
+
const isBooleanField = fieldDef?.type === "boolean";
|
|
130
|
+
const availableOperators: AlertQueryOperator[] = isBooleanField
|
|
131
|
+
? BOOLEAN_OPERATORS
|
|
132
|
+
: (fieldDef?.operators ?? ALL_NUMERIC_OPERATORS);
|
|
133
|
+
|
|
134
|
+
function handleFieldChange(key: string) {
|
|
135
|
+
actions.setField(path, key);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleOperatorChange(op: string) {
|
|
139
|
+
actions.setOperator(path, op);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function handleValueChange(idx: number, raw: string) {
|
|
143
|
+
const n = parseFloat(parseCommas(raw));
|
|
144
|
+
if (!isNaN(n)) {
|
|
145
|
+
actions.setValue(path, idx, n, "number");
|
|
146
|
+
} else if (!raw) {
|
|
147
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
148
|
+
actions.setValue(path, idx, null as any, "number");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function handleBoolChange(v: string) {
|
|
153
|
+
actions.setValue(path, 0, v === "true", "boolean");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const valStr = (v: unknown): string => {
|
|
157
|
+
if (v === null || v === undefined) return "";
|
|
158
|
+
return String(v);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<div className="flex flex-wrap items-center gap-1.5">
|
|
163
|
+
{/* Field */}
|
|
164
|
+
<SelectAutoWidth longestLabel={longestFieldLabel}>
|
|
165
|
+
<Select
|
|
166
|
+
value={field}
|
|
167
|
+
onValueChange={(v) => handleFieldChange(v as string)}
|
|
168
|
+
>
|
|
169
|
+
<SelectTrigger size="sm" className="w-full">
|
|
170
|
+
<SelectValue>
|
|
171
|
+
{fields.find((f) => f.key === field)?.label ?? field}
|
|
172
|
+
</SelectValue>
|
|
173
|
+
</SelectTrigger>
|
|
174
|
+
<SelectContent>
|
|
175
|
+
{fields.map((f) => (
|
|
176
|
+
<SelectItem key={f.key} value={f.key}>
|
|
177
|
+
{f.label}
|
|
178
|
+
</SelectItem>
|
|
179
|
+
))}
|
|
180
|
+
</SelectContent>
|
|
181
|
+
</Select>
|
|
182
|
+
</SelectAutoWidth>
|
|
183
|
+
|
|
184
|
+
{/* Operator */}
|
|
185
|
+
<SelectAutoWidth longestLabel={longestOperatorLabel}>
|
|
186
|
+
<Select
|
|
187
|
+
value={operator}
|
|
188
|
+
onValueChange={(v) => handleOperatorChange(v as string)}
|
|
189
|
+
>
|
|
190
|
+
<SelectTrigger size="sm" className="w-full">
|
|
191
|
+
<SelectValue>
|
|
192
|
+
{OPERATOR_LABELS[operator as AlertQueryOperator] ?? operator}
|
|
193
|
+
</SelectValue>
|
|
194
|
+
</SelectTrigger>
|
|
195
|
+
<SelectContent>
|
|
196
|
+
{availableOperators.map((op) => (
|
|
197
|
+
<SelectItem key={op} value={op}>
|
|
198
|
+
{OPERATOR_LABELS[op]}
|
|
199
|
+
</SelectItem>
|
|
200
|
+
))}
|
|
201
|
+
</SelectContent>
|
|
202
|
+
</Select>
|
|
203
|
+
</SelectAutoWidth>
|
|
204
|
+
|
|
205
|
+
{/* Value */}
|
|
206
|
+
{isBooleanField ? (
|
|
207
|
+
<Select
|
|
208
|
+
value={String(value0 ?? true)}
|
|
209
|
+
onValueChange={(v) => handleBoolChange(v as string)}
|
|
210
|
+
>
|
|
211
|
+
<SelectTrigger size="sm" className="w-36">
|
|
212
|
+
<SelectValue>{(value0 ?? true) ? "Yes" : "No"}</SelectValue>
|
|
213
|
+
</SelectTrigger>
|
|
214
|
+
<SelectContent>
|
|
215
|
+
<SelectItem value="true">Yes</SelectItem>
|
|
216
|
+
<SelectItem value="false">No</SelectItem>
|
|
217
|
+
</SelectContent>
|
|
218
|
+
</Select>
|
|
219
|
+
) : (
|
|
220
|
+
<>
|
|
221
|
+
<ValueInput
|
|
222
|
+
value={valStr(value0)}
|
|
223
|
+
onChange={(v) => handleValueChange(0, v)}
|
|
224
|
+
unit={fieldDef?.unit}
|
|
225
|
+
/>
|
|
226
|
+
{operator === "between" && (
|
|
227
|
+
<>
|
|
228
|
+
<span className="text-xs text-muted-foreground">and</span>
|
|
229
|
+
<ValueInput
|
|
230
|
+
value={valStr(value1)}
|
|
231
|
+
onChange={(v) => handleValueChange(1, v)}
|
|
232
|
+
unit={fieldDef?.unit}
|
|
233
|
+
/>
|
|
234
|
+
</>
|
|
235
|
+
)}
|
|
236
|
+
</>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// RuleAccordionItem
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
function RuleAccordionItem({
|
|
247
|
+
ruleId,
|
|
248
|
+
ruleProps,
|
|
249
|
+
path,
|
|
250
|
+
actions,
|
|
251
|
+
fields,
|
|
252
|
+
longestFieldLabel,
|
|
253
|
+
longestOperatorLabel,
|
|
254
|
+
removable,
|
|
255
|
+
}: {
|
|
256
|
+
ruleId: string;
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
258
|
+
ruleProps: any;
|
|
259
|
+
path: string[];
|
|
260
|
+
actions: Actions;
|
|
261
|
+
fields: AlertQueryField[];
|
|
262
|
+
longestFieldLabel: string;
|
|
263
|
+
longestOperatorLabel: string;
|
|
264
|
+
removable: boolean;
|
|
265
|
+
}) {
|
|
266
|
+
const summary = ruleSummary(ruleProps, fields);
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<AccordionPrimitive.Item value={ruleId} className="border-b">
|
|
270
|
+
<AccordionPrimitive.Header className="flex items-center">
|
|
271
|
+
<AccordionPrimitive.Trigger
|
|
272
|
+
className={cn(
|
|
273
|
+
"flex flex-1 items-center gap-3 py-3 text-left text-sm outline-none",
|
|
274
|
+
"rounded-none hover:underline",
|
|
275
|
+
"focus-visible:ring-2 focus-visible:ring-foreground/30",
|
|
276
|
+
"[&[data-panel-open]>svg:last-child]:rotate-180",
|
|
277
|
+
)}
|
|
278
|
+
>
|
|
279
|
+
<span className="flex-1 text-sm text-foreground">{summary}</span>
|
|
280
|
+
<ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
|
281
|
+
</AccordionPrimitive.Trigger>
|
|
282
|
+
<Button
|
|
283
|
+
type="button"
|
|
284
|
+
variant="ghost"
|
|
285
|
+
size="sm"
|
|
286
|
+
className="shrink-0 px-2"
|
|
287
|
+
onClick={() => actions.removeRule(path)}
|
|
288
|
+
disabled={!removable}
|
|
289
|
+
aria-label="Remove rule"
|
|
290
|
+
>
|
|
291
|
+
<Trash2Icon className="size-4" />
|
|
292
|
+
</Button>
|
|
293
|
+
</AccordionPrimitive.Header>
|
|
294
|
+
<AccordionPrimitive.Panel className="overflow-hidden h-(--accordion-panel-height) transition-[height] duration-200 ease-out data-starting-style:h-0 data-ending-style:h-0">
|
|
295
|
+
<div className="pb-3">
|
|
296
|
+
<RuleEditorFields
|
|
297
|
+
ruleProps={ruleProps}
|
|
298
|
+
path={path}
|
|
299
|
+
actions={actions}
|
|
300
|
+
fields={fields}
|
|
301
|
+
longestFieldLabel={longestFieldLabel}
|
|
302
|
+
longestOperatorLabel={longestOperatorLabel}
|
|
303
|
+
/>
|
|
304
|
+
</div>
|
|
305
|
+
</AccordionPrimitive.Panel>
|
|
306
|
+
</AccordionPrimitive.Item>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
// SubGroupAccordionItem
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
function SubGroupAccordionItem({
|
|
315
|
+
groupId,
|
|
316
|
+
group,
|
|
317
|
+
path,
|
|
318
|
+
actions,
|
|
319
|
+
fields,
|
|
320
|
+
longestFieldLabel,
|
|
321
|
+
longestOperatorLabel,
|
|
322
|
+
}: {
|
|
323
|
+
groupId: string;
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
325
|
+
group: any;
|
|
326
|
+
path: string[];
|
|
327
|
+
actions: Actions;
|
|
328
|
+
fields: AlertQueryField[];
|
|
329
|
+
longestFieldLabel: string;
|
|
330
|
+
longestOperatorLabel: string;
|
|
331
|
+
}) {
|
|
332
|
+
const conjunction: string = group.properties?.conjunction ?? "AND";
|
|
333
|
+
const nestedChildren: JsonItem[] = group.children1 ?? [];
|
|
334
|
+
const count = nestedChildren.length;
|
|
335
|
+
const triggerLabel = `Group (${count} ${count === 1 ? "rule" : "rules"})`;
|
|
336
|
+
|
|
337
|
+
return (
|
|
338
|
+
<AccordionPrimitive.Item value={groupId} className="border-b">
|
|
339
|
+
<AccordionPrimitive.Header className="flex items-center">
|
|
340
|
+
<AccordionPrimitive.Trigger
|
|
341
|
+
className={cn(
|
|
342
|
+
"flex flex-1 items-center gap-3 py-3 text-left text-sm outline-none",
|
|
343
|
+
"rounded-none hover:underline",
|
|
344
|
+
"focus-visible:ring-2 focus-visible:ring-foreground/30",
|
|
345
|
+
"[&[data-panel-open]>svg:last-child]:rotate-180",
|
|
346
|
+
)}
|
|
347
|
+
>
|
|
348
|
+
<span className="flex-1 text-sm font-medium text-foreground">
|
|
349
|
+
{triggerLabel}
|
|
350
|
+
</span>
|
|
351
|
+
<ChevronDownIcon className="size-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
|
352
|
+
</AccordionPrimitive.Trigger>
|
|
353
|
+
<Button
|
|
354
|
+
type="button"
|
|
355
|
+
variant="ghost"
|
|
356
|
+
size="sm"
|
|
357
|
+
className="shrink-0 px-2"
|
|
358
|
+
onClick={() => actions.removeGroup(path)}
|
|
359
|
+
aria-label="Remove group"
|
|
360
|
+
>
|
|
361
|
+
<Trash2Icon className="size-4" />
|
|
362
|
+
</Button>
|
|
363
|
+
</AccordionPrimitive.Header>
|
|
364
|
+
<AccordionPrimitive.Panel className="overflow-hidden h-(--accordion-panel-height) transition-[height] duration-200 ease-out data-starting-style:h-0 data-ending-style:h-0">
|
|
365
|
+
<div className="pb-3 pl-4 border-l ml-2 flex flex-col gap-3">
|
|
366
|
+
{/* Nested combinator */}
|
|
367
|
+
<div className="flex items-center gap-2">
|
|
368
|
+
<Checkbox
|
|
369
|
+
id={`subgroup-${groupId}-combinator`}
|
|
370
|
+
checked={conjunction === "AND"}
|
|
371
|
+
onCheckedChange={(checked) =>
|
|
372
|
+
actions.setConjunction(path, checked ? "AND" : "OR")
|
|
373
|
+
}
|
|
374
|
+
/>
|
|
375
|
+
<Label
|
|
376
|
+
htmlFor={`subgroup-${groupId}-combinator`}
|
|
377
|
+
className="cursor-pointer font-normal text-sm"
|
|
378
|
+
>
|
|
379
|
+
Match all conditions
|
|
380
|
+
</Label>
|
|
381
|
+
<span className="text-xs text-muted-foreground">
|
|
382
|
+
({conjunction})
|
|
383
|
+
</span>
|
|
384
|
+
</div>
|
|
385
|
+
|
|
386
|
+
{/* Nested rules — flat rows */}
|
|
387
|
+
{nestedChildren.map((child) => {
|
|
388
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
389
|
+
const c = child as any;
|
|
390
|
+
const ruleId: string = c.id ?? "";
|
|
391
|
+
const rulePath = [...path, ruleId];
|
|
392
|
+
return (
|
|
393
|
+
<div key={ruleId} className="flex items-start gap-1.5">
|
|
394
|
+
<div className="flex-1">
|
|
395
|
+
<RuleEditorFields
|
|
396
|
+
ruleProps={c.properties}
|
|
397
|
+
path={rulePath}
|
|
398
|
+
actions={actions}
|
|
399
|
+
fields={fields}
|
|
400
|
+
longestFieldLabel={longestFieldLabel}
|
|
401
|
+
longestOperatorLabel={longestOperatorLabel}
|
|
402
|
+
/>
|
|
403
|
+
</div>
|
|
404
|
+
<Button
|
|
405
|
+
type="button"
|
|
406
|
+
variant="ghost"
|
|
407
|
+
size="sm"
|
|
408
|
+
className="shrink-0 px-2"
|
|
409
|
+
onClick={() => actions.removeRule(rulePath)}
|
|
410
|
+
disabled={nestedChildren.length <= 1}
|
|
411
|
+
aria-label="Remove rule"
|
|
412
|
+
>
|
|
413
|
+
<Trash2Icon className="size-4" />
|
|
414
|
+
</Button>
|
|
415
|
+
</div>
|
|
416
|
+
);
|
|
417
|
+
})}
|
|
418
|
+
|
|
419
|
+
{/* Add rule inside group */}
|
|
420
|
+
<Button
|
|
421
|
+
type="button"
|
|
422
|
+
variant="ghost"
|
|
423
|
+
size="sm"
|
|
424
|
+
onClick={() => actions.addRule(path)}
|
|
425
|
+
className="w-fit"
|
|
426
|
+
>
|
|
427
|
+
<PlusIcon className="size-4" />
|
|
428
|
+
Add rule
|
|
429
|
+
</Button>
|
|
430
|
+
</div>
|
|
431
|
+
</AccordionPrimitive.Panel>
|
|
432
|
+
</AccordionPrimitive.Item>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
// CustomBuilderUI
|
|
438
|
+
// ---------------------------------------------------------------------------
|
|
439
|
+
|
|
440
|
+
export interface CustomBuilderUIProps {
|
|
441
|
+
tree: ImmutableTree;
|
|
442
|
+
actions: Actions;
|
|
443
|
+
fields: AlertQueryField[];
|
|
444
|
+
defaultOpenItems: string[];
|
|
445
|
+
className?: string;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export function CustomBuilderUI({
|
|
449
|
+
tree,
|
|
450
|
+
actions,
|
|
451
|
+
fields,
|
|
452
|
+
defaultOpenItems,
|
|
453
|
+
className,
|
|
454
|
+
}: CustomBuilderUIProps) {
|
|
455
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
456
|
+
const treeJson = QbUtils.getTree(tree) as any;
|
|
457
|
+
const rootId: string = treeJson?.id ?? "root";
|
|
458
|
+
const conjunction: string = treeJson?.properties?.conjunction ?? "AND";
|
|
459
|
+
const children: JsonItem[] = treeJson?.children1 ?? [];
|
|
460
|
+
|
|
461
|
+
const longestFieldLabel = React.useMemo(
|
|
462
|
+
() => longestOf(fields.map((f) => f.label)),
|
|
463
|
+
[fields],
|
|
464
|
+
);
|
|
465
|
+
const longestOperatorLabel = React.useMemo(
|
|
466
|
+
() => longestOf(allOperatorLabels(fields)),
|
|
467
|
+
[fields],
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
<div className={cn("flex flex-col gap-3", className)}>
|
|
472
|
+
{/* Root combinator */}
|
|
473
|
+
<div className="flex items-center gap-2">
|
|
474
|
+
<Checkbox
|
|
475
|
+
id="qb-root-combinator"
|
|
476
|
+
checked={conjunction === "AND"}
|
|
477
|
+
onCheckedChange={(checked) =>
|
|
478
|
+
actions.setConjunction([rootId], checked ? "AND" : "OR")
|
|
479
|
+
}
|
|
480
|
+
/>
|
|
481
|
+
<Label
|
|
482
|
+
htmlFor="qb-root-combinator"
|
|
483
|
+
className="cursor-pointer font-normal text-sm"
|
|
484
|
+
>
|
|
485
|
+
Match all conditions
|
|
486
|
+
</Label>
|
|
487
|
+
<span className="text-xs text-muted-foreground">({conjunction})</span>
|
|
488
|
+
</div>
|
|
489
|
+
|
|
490
|
+
{/* Rules and groups */}
|
|
491
|
+
<AccordionPrimitive.Root
|
|
492
|
+
multiple
|
|
493
|
+
defaultValue={defaultOpenItems}
|
|
494
|
+
className="border-t"
|
|
495
|
+
>
|
|
496
|
+
{children.map((child) => {
|
|
497
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
498
|
+
const c = child as any;
|
|
499
|
+
const childId: string = c.id ?? "";
|
|
500
|
+
const childPath = [rootId, childId];
|
|
501
|
+
|
|
502
|
+
if (c.type === "group") {
|
|
503
|
+
return (
|
|
504
|
+
<SubGroupAccordionItem
|
|
505
|
+
key={childId}
|
|
506
|
+
groupId={childId}
|
|
507
|
+
group={c}
|
|
508
|
+
path={childPath}
|
|
509
|
+
actions={actions}
|
|
510
|
+
fields={fields}
|
|
511
|
+
longestFieldLabel={longestFieldLabel}
|
|
512
|
+
longestOperatorLabel={longestOperatorLabel}
|
|
513
|
+
/>
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return (
|
|
518
|
+
<RuleAccordionItem
|
|
519
|
+
key={childId}
|
|
520
|
+
ruleId={childId}
|
|
521
|
+
ruleProps={c.properties}
|
|
522
|
+
path={childPath}
|
|
523
|
+
actions={actions}
|
|
524
|
+
fields={fields}
|
|
525
|
+
longestFieldLabel={longestFieldLabel}
|
|
526
|
+
longestOperatorLabel={longestOperatorLabel}
|
|
527
|
+
removable={children.length > 1}
|
|
528
|
+
/>
|
|
529
|
+
);
|
|
530
|
+
})}
|
|
531
|
+
</AccordionPrimitive.Root>
|
|
532
|
+
|
|
533
|
+
{/* Action buttons */}
|
|
534
|
+
<div className="flex gap-2">
|
|
535
|
+
<Button
|
|
536
|
+
type="button"
|
|
537
|
+
variant="outline"
|
|
538
|
+
size="sm"
|
|
539
|
+
onClick={() => actions.addRule([rootId])}
|
|
540
|
+
>
|
|
541
|
+
<PlusIcon className="size-4" />
|
|
542
|
+
Add rule
|
|
543
|
+
</Button>
|
|
544
|
+
<Button
|
|
545
|
+
type="button"
|
|
546
|
+
variant="outline"
|
|
547
|
+
size="sm"
|
|
548
|
+
onClick={() => actions.addGroup([rootId])}
|
|
549
|
+
>
|
|
550
|
+
<PlusIcon className="size-4" />
|
|
551
|
+
Add group
|
|
552
|
+
</Button>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
);
|
|
556
|
+
}
|