@rakun-kit/manager-react 1.4.5 → 1.4.6
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/README.md +196 -196
- package/dist/cjs/components/editor/themes/editor-theme.css +91 -91
- package/dist/cjs/components/ui/sortable.js +4 -4
- package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.js +346 -76
- package/dist/cjs/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.js.map +1 -1
- package/dist/cjs/state/theme.js +12 -12
- package/dist/esm/components/editor/themes/editor-theme.css +91 -91
- package/dist/esm/components/ui/sortable.js +4 -4
- package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.d.ts +20 -4
- package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.d.ts.map +1 -1
- package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.js +343 -75
- package/dist/esm/router/dashboard/[contentType]/[edit]/_fields/DynamicDataControl.js.map +1 -1
- package/dist/esm/state/theme.js +12 -12
- package/dist/styles.css +18 -14
- package/dist/styles.css.d.ts +3 -3
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +192 -192
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { getListField, isDynamicDataSourceContentTypeAllowed, isTranslatableObject, } from '@rakun-kit/core/client';
|
|
4
|
-
import { Cable, ChevronRight, HelpCircle, Link2, ListFilter, X } from 'lucide-react';
|
|
3
|
+
import { DYNAMIC_QUERY_CURRENT_VALUE_KEY, getListField, isDynamicDataSourceContentTypeAllowed, isTranslatableObject, } from '@rakun-kit/core/client';
|
|
4
|
+
import { Cable, ChevronRight, HelpCircle, Link2, ListFilter, Plus, Trash2, X, } from 'lucide-react';
|
|
5
5
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
6
6
|
import { Badge } from '../../../../../components/ui/badge.js';
|
|
7
7
|
import { Button } from '../../../../../components/ui/button.js';
|
|
@@ -130,12 +130,24 @@ const nestedSourceFieldOptions = ({ contentType, prefix = '', targetField, depth
|
|
|
130
130
|
return [];
|
|
131
131
|
if (field.config.type === 'Relation' && depth < 3) {
|
|
132
132
|
const relationContentType = field.contentType;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
const idOption = isCompatibleSourceKind('string', targetField)
|
|
134
|
+
? [
|
|
135
|
+
{
|
|
136
|
+
label: fieldLabel(`${path}._id`),
|
|
137
|
+
value: `${path}._id`,
|
|
138
|
+
kind: 'string',
|
|
139
|
+
},
|
|
140
|
+
]
|
|
141
|
+
: [];
|
|
142
|
+
return [
|
|
143
|
+
...idOption,
|
|
144
|
+
...nestedSourceFieldOptions({
|
|
145
|
+
contentType: relationContentType,
|
|
146
|
+
prefix: path,
|
|
147
|
+
targetField,
|
|
148
|
+
depth: depth + 1,
|
|
149
|
+
}),
|
|
150
|
+
];
|
|
139
151
|
}
|
|
140
152
|
if (field.config.type === 'File') {
|
|
141
153
|
const fieldOption = areFieldKindsCompatible(field, targetField)
|
|
@@ -200,17 +212,55 @@ const sourceContentTypeLabel = (source, documentContentTypeName) => source.conte
|
|
|
200
212
|
? 'Current document'
|
|
201
213
|
: source.contentType;
|
|
202
214
|
const getSourceContentTypes = (contentTypes) => contentTypes.filter((sourceContentType) => isDynamicDataSourceContentTypeAllowed(sourceContentType));
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
215
|
+
const operatorByMongoOperator = {
|
|
216
|
+
$eq: 'equals',
|
|
217
|
+
$ne: 'notEquals',
|
|
218
|
+
$contains: 'contains',
|
|
219
|
+
$in: 'in',
|
|
220
|
+
$nin: 'notIn',
|
|
221
|
+
$gt: 'greaterThan',
|
|
222
|
+
$gte: 'greaterThanOrEqual',
|
|
223
|
+
$lt: 'lessThan',
|
|
224
|
+
$lte: 'lessThanOrEqual',
|
|
225
|
+
};
|
|
226
|
+
const readFilterOperand = (value) => {
|
|
227
|
+
if (isRecord(value) &&
|
|
228
|
+
typeof value[DYNAMIC_QUERY_CURRENT_VALUE_KEY] === 'string') {
|
|
229
|
+
return {
|
|
230
|
+
value: value[DYNAMIC_QUERY_CURRENT_VALUE_KEY],
|
|
231
|
+
valueSource: 'current',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
value: Array.isArray(value) ? value.join(', ') : String(value ?? ''),
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
const filterConditionFromEntry = (field, value) => {
|
|
208
239
|
if (value === true)
|
|
209
240
|
return { field, operator: 'true', value: '' };
|
|
210
241
|
if (value === false)
|
|
211
242
|
return { field, operator: 'false', value: '' };
|
|
212
|
-
|
|
213
|
-
|
|
243
|
+
const directOperand = readFilterOperand(value);
|
|
244
|
+
if (directOperand.valueSource === 'current') {
|
|
245
|
+
return { field, operator: 'equals', ...directOperand };
|
|
246
|
+
}
|
|
247
|
+
if (isRecord(value)) {
|
|
248
|
+
if (typeof value.$exists === 'boolean') {
|
|
249
|
+
return {
|
|
250
|
+
field,
|
|
251
|
+
operator: value.$exists ? 'exists' : 'notExists',
|
|
252
|
+
value: '',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const operatorEntry = Object.entries(value).find(([operator]) => operator in operatorByMongoOperator);
|
|
256
|
+
if (operatorEntry) {
|
|
257
|
+
const [operator, operatorValue] = operatorEntry;
|
|
258
|
+
return {
|
|
259
|
+
field,
|
|
260
|
+
operator: operatorByMongoOperator[operator],
|
|
261
|
+
...readFilterOperand(operatorValue),
|
|
262
|
+
};
|
|
263
|
+
}
|
|
214
264
|
}
|
|
215
265
|
return {
|
|
216
266
|
field,
|
|
@@ -218,31 +268,122 @@ const readFilterState = (filter) => {
|
|
|
218
268
|
value: typeof value === 'string' ? value : String(value ?? ''),
|
|
219
269
|
};
|
|
220
270
|
};
|
|
221
|
-
const
|
|
222
|
-
if (!
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
271
|
+
export const readFilterState = (filter) => {
|
|
272
|
+
if (!filter)
|
|
273
|
+
return { combinator: 'and', conditions: [] };
|
|
274
|
+
const logicalEntry = ['$and', '$or'].find((key) => Array.isArray(filter[key]));
|
|
275
|
+
if (logicalEntry) {
|
|
276
|
+
const conditions = filter[logicalEntry].flatMap((item) => isRecord(item)
|
|
277
|
+
? Object.entries(item)
|
|
278
|
+
.filter(([field]) => !field.startsWith('$'))
|
|
279
|
+
.map(([field, value]) => filterConditionFromEntry(field, value))
|
|
280
|
+
: []);
|
|
281
|
+
return {
|
|
282
|
+
combinator: logicalEntry === '$or' ? 'or' : 'and',
|
|
283
|
+
conditions,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
combinator: 'and',
|
|
288
|
+
conditions: Object.entries(filter)
|
|
289
|
+
.filter(([field]) => !field.startsWith('$'))
|
|
290
|
+
.map(([field, value]) => filterConditionFromEntry(field, value)),
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
const operatorNeedsValue = (operator) => !['true', 'false', 'exists', 'notExists'].includes(operator);
|
|
294
|
+
const parseFilterValue = (condition, fieldOptions) => {
|
|
295
|
+
const kind = fieldOptions.find((option) => option.value === condition.field)?.kind;
|
|
296
|
+
const parseValue = (value) => kind === 'number' && value.trim() !== '' && Number.isFinite(Number(value))
|
|
297
|
+
? Number(value)
|
|
298
|
+
: value.trim();
|
|
299
|
+
if (condition.operator === 'in' || condition.operator === 'notIn') {
|
|
300
|
+
return condition.value
|
|
301
|
+
.split(',')
|
|
302
|
+
.map((value) => value.trim())
|
|
303
|
+
.filter(Boolean)
|
|
304
|
+
.map(parseValue);
|
|
305
|
+
}
|
|
306
|
+
return parseValue(condition.value);
|
|
307
|
+
};
|
|
308
|
+
const buildFilterCondition = (condition, fieldOptions) => {
|
|
309
|
+
if (!condition.field)
|
|
229
310
|
return undefined;
|
|
230
|
-
if (
|
|
231
|
-
return { [
|
|
311
|
+
if (condition.operator === 'true')
|
|
312
|
+
return { [condition.field]: true };
|
|
313
|
+
if (condition.operator === 'false')
|
|
314
|
+
return { [condition.field]: false };
|
|
315
|
+
if (condition.operator === 'exists') {
|
|
316
|
+
return { [condition.field]: { $exists: true } };
|
|
317
|
+
}
|
|
318
|
+
if (condition.operator === 'notExists') {
|
|
319
|
+
return { [condition.field]: { $exists: false } };
|
|
232
320
|
}
|
|
233
|
-
|
|
321
|
+
if (!condition.value.trim())
|
|
322
|
+
return undefined;
|
|
323
|
+
const value = condition.valueSource === 'current'
|
|
324
|
+
? { [DYNAMIC_QUERY_CURRENT_VALUE_KEY]: condition.value }
|
|
325
|
+
: parseFilterValue(condition, fieldOptions);
|
|
326
|
+
const mongoOperator = {
|
|
327
|
+
notEquals: '$ne',
|
|
328
|
+
contains: '$contains',
|
|
329
|
+
in: '$in',
|
|
330
|
+
notIn: '$nin',
|
|
331
|
+
greaterThan: '$gt',
|
|
332
|
+
greaterThanOrEqual: '$gte',
|
|
333
|
+
lessThan: '$lt',
|
|
334
|
+
lessThanOrEqual: '$lte',
|
|
335
|
+
};
|
|
336
|
+
const operator = mongoOperator[condition.operator];
|
|
337
|
+
return operator
|
|
338
|
+
? { [condition.field]: { [operator]: value } }
|
|
339
|
+
: { [condition.field]: value };
|
|
340
|
+
};
|
|
341
|
+
export const buildFilter = (state, fieldOptions = []) => {
|
|
342
|
+
const conditions = state.conditions.flatMap((condition) => {
|
|
343
|
+
const builtCondition = buildFilterCondition(condition, fieldOptions);
|
|
344
|
+
return builtCondition ? [builtCondition] : [];
|
|
345
|
+
});
|
|
346
|
+
if (conditions.length === 0)
|
|
347
|
+
return undefined;
|
|
348
|
+
if (conditions.length === 1)
|
|
349
|
+
return conditions[0];
|
|
350
|
+
return { [state.combinator === 'or' ? '$or' : '$and']: conditions };
|
|
234
351
|
};
|
|
235
352
|
const filterSummary = (filter) => {
|
|
236
353
|
const state = readFilterState(filter);
|
|
237
|
-
if (
|
|
354
|
+
if (state.conditions.length === 0)
|
|
238
355
|
return '';
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
356
|
+
const first = state.conditions[0];
|
|
357
|
+
const operatorLabels = {
|
|
358
|
+
equals: '=',
|
|
359
|
+
notEquals: '!=',
|
|
360
|
+
contains: 'contains',
|
|
361
|
+
in: 'in',
|
|
362
|
+
notIn: 'not in',
|
|
363
|
+
greaterThan: '>',
|
|
364
|
+
greaterThanOrEqual: '>=',
|
|
365
|
+
lessThan: '<',
|
|
366
|
+
lessThanOrEqual: '<=',
|
|
367
|
+
true: '= true',
|
|
368
|
+
false: '= false',
|
|
369
|
+
exists: 'is set',
|
|
370
|
+
notExists: 'is not set',
|
|
371
|
+
};
|
|
372
|
+
const firstSummary = [
|
|
373
|
+
first.field,
|
|
374
|
+
operatorLabels[first.operator],
|
|
375
|
+
operatorNeedsValue(first.operator)
|
|
376
|
+
? first.valueSource === 'current'
|
|
377
|
+
? `Current document.${first.value}`
|
|
378
|
+
: first.value
|
|
379
|
+
: '',
|
|
380
|
+
]
|
|
381
|
+
.filter(Boolean)
|
|
382
|
+
.join(' ');
|
|
383
|
+
const remaining = state.conditions.length - 1;
|
|
384
|
+
return remaining > 0
|
|
385
|
+
? `${firstSummary} + ${remaining} ${remaining === 1 ? 'condition' : 'conditions'}`
|
|
386
|
+
: firstSummary;
|
|
246
387
|
};
|
|
247
388
|
const bindingSummary = ({ list, fieldBinding, listBinding, documentContentTypeName, }) => {
|
|
248
389
|
if (list) {
|
|
@@ -377,6 +518,57 @@ const getSortFieldOptions = (contentType) => Object.entries(contentType.fields).
|
|
|
377
518
|
isSingleSelect;
|
|
378
519
|
return isSortable ? [{ label: fieldLabel(name), value: name, kind }] : [];
|
|
379
520
|
});
|
|
521
|
+
const filterOperatorOptions = {
|
|
522
|
+
equals: { label: 'Equals', value: 'equals' },
|
|
523
|
+
notEquals: { label: 'Does not equal', value: 'notEquals' },
|
|
524
|
+
contains: { label: 'Contains', value: 'contains' },
|
|
525
|
+
in: { label: 'Is one of', value: 'in' },
|
|
526
|
+
notIn: { label: 'Is not one of', value: 'notIn' },
|
|
527
|
+
greaterThan: { label: 'Greater than', value: 'greaterThan' },
|
|
528
|
+
greaterThanOrEqual: {
|
|
529
|
+
label: 'Greater than or equal',
|
|
530
|
+
value: 'greaterThanOrEqual',
|
|
531
|
+
},
|
|
532
|
+
lessThan: { label: 'Less than', value: 'lessThan' },
|
|
533
|
+
lessThanOrEqual: {
|
|
534
|
+
label: 'Less than or equal',
|
|
535
|
+
value: 'lessThanOrEqual',
|
|
536
|
+
},
|
|
537
|
+
true: { label: 'Is true', value: 'true' },
|
|
538
|
+
false: { label: 'Is false', value: 'false' },
|
|
539
|
+
exists: { label: 'Is set', value: 'exists' },
|
|
540
|
+
notExists: { label: 'Is not set', value: 'notExists' },
|
|
541
|
+
};
|
|
542
|
+
const getFilterOperatorOptions = (kind) => {
|
|
543
|
+
const operators = kind === 'boolean'
|
|
544
|
+
? ['true', 'false', 'exists', 'notExists']
|
|
545
|
+
: kind === 'number' || kind === 'date'
|
|
546
|
+
? [
|
|
547
|
+
'equals',
|
|
548
|
+
'notEquals',
|
|
549
|
+
'greaterThan',
|
|
550
|
+
'greaterThanOrEqual',
|
|
551
|
+
'lessThan',
|
|
552
|
+
'lessThanOrEqual',
|
|
553
|
+
'in',
|
|
554
|
+
'notIn',
|
|
555
|
+
'exists',
|
|
556
|
+
'notExists',
|
|
557
|
+
]
|
|
558
|
+
: kind === 'string' || kind === 'richText'
|
|
559
|
+
? [
|
|
560
|
+
'equals',
|
|
561
|
+
'notEquals',
|
|
562
|
+
'contains',
|
|
563
|
+
'in',
|
|
564
|
+
'notIn',
|
|
565
|
+
'exists',
|
|
566
|
+
'notExists',
|
|
567
|
+
]
|
|
568
|
+
: ['equals', 'notEquals', 'exists', 'notExists'];
|
|
569
|
+
return operators.map((operator) => filterOperatorOptions[operator]);
|
|
570
|
+
};
|
|
571
|
+
const defaultFilterOperator = (kind) => (kind === 'boolean' ? 'true' : 'equals');
|
|
380
572
|
const createRelatedCollectionSource = ({ contentType, currentSource, targetField, }) => {
|
|
381
573
|
const relation = getRelatedRelationOptions(contentType, currentSource)[0];
|
|
382
574
|
const path = getRelatedPathOptions(contentType, targetField)[0]?.value;
|
|
@@ -457,7 +649,7 @@ const MappingSourceEditor = ({ contentTypes, currentSource, targetField, source,
|
|
|
457
649
|
})
|
|
458
650
|
: undefined, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: 'asc', children: "Ascending" }), _jsx(SelectItem, { value: 'desc', children: "Descending" })] })] })] })] })] }));
|
|
459
651
|
};
|
|
460
|
-
const ListBindingEditor = ({ contentTypes, field, binding, onChange, }) => {
|
|
652
|
+
const ListBindingEditor = ({ contentTypes, documentContentType, field, binding, onChange, }) => {
|
|
461
653
|
const [sourceType, setSourceType] = useState(binding?.contentType || '');
|
|
462
654
|
const [itemName, setItemName] = useState(binding?.itemName || field.fields[0]?.name || '');
|
|
463
655
|
const [filterState, setFilterState] = useState(readFilterState(binding?.query?.filter));
|
|
@@ -473,6 +665,15 @@ const ListBindingEditor = ({ contentTypes, field, binding, onChange, }) => {
|
|
|
473
665
|
const filterFieldOptions = selectedSource
|
|
474
666
|
? sourceFieldOptions(selectedSource).filter((item) => item.value !== '$href')
|
|
475
667
|
: [];
|
|
668
|
+
const currentDocumentFieldOptions = [
|
|
669
|
+
{ label: '_id', value: '_id', kind: 'string' },
|
|
670
|
+
...sourceFieldOptions(documentContentType).filter((item) => item.value !== '$href' &&
|
|
671
|
+
item.kind !== 'object' &&
|
|
672
|
+
item.kind !== 'array'),
|
|
673
|
+
];
|
|
674
|
+
const sortEntry = Object.entries(binding?.query?.options?.sort ?? {})[0];
|
|
675
|
+
const sortField = sortEntry?.[0] ?? '';
|
|
676
|
+
const sortDirection = sortEntry?.[1] ?? 'desc';
|
|
476
677
|
const emit = (patch) => {
|
|
477
678
|
const next = {
|
|
478
679
|
contentType: sourceType,
|
|
@@ -492,57 +693,124 @@ const ListBindingEditor = ({ contentTypes, field, binding, onChange, }) => {
|
|
|
492
693
|
emit({
|
|
493
694
|
query: {
|
|
494
695
|
...binding?.query,
|
|
495
|
-
filter: buildFilter(nextFilterState),
|
|
696
|
+
filter: buildFilter(nextFilterState, filterFieldOptions),
|
|
496
697
|
},
|
|
497
698
|
});
|
|
498
699
|
};
|
|
700
|
+
const updateFilterCondition = (index, nextCondition) => {
|
|
701
|
+
const conditions = [...filterState.conditions];
|
|
702
|
+
conditions[index] = nextCondition;
|
|
703
|
+
updateFilter({ ...filterState, conditions });
|
|
704
|
+
};
|
|
499
705
|
return (_jsxs("div", { className: 'grid gap-3', children: [_jsx(PanelSection, { title: 'Collection', children: _jsxs("div", { className: 'grid gap-3 md:grid-cols-2', children: [_jsxs(Label, { className: 'grid gap-1.5', children: ["Source", _jsxs(Select, { disabled: contentTypes.length === 0, value: sourceType, onValueChange: (value) => {
|
|
500
706
|
setSourceType(value);
|
|
501
|
-
|
|
707
|
+
const nextFilterState = {
|
|
708
|
+
combinator: 'and',
|
|
709
|
+
conditions: [],
|
|
710
|
+
};
|
|
711
|
+
setFilterState(nextFilterState);
|
|
712
|
+
emit({
|
|
713
|
+
contentType: value,
|
|
714
|
+
map: {},
|
|
715
|
+
query: {
|
|
716
|
+
options: {
|
|
717
|
+
limit: binding?.query?.options?.limit ?? 10,
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
});
|
|
502
721
|
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: contentTypes.length > 0 ? 'Content type' : 'No content types' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: contentTypes.map((ct) => (_jsx(SelectItem, { value: ct.name, children: ct.name }, ct.name))) }) })] })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Item", _jsxs(Select, { disabled: !sourceType || field.fields.length === 0, value: itemName, onValueChange: (value) => {
|
|
503
722
|
setItemName(value);
|
|
504
723
|
emit({ itemName: value, map: {} });
|
|
505
|
-
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: field.fields.length > 0 ? 'Item type' : 'No item types' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: field.fields.map((item) => (_jsx(SelectItem, { value: item.name, children: item.name }, item.name))) }) })] })] })] }) }),
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
724
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: field.fields.length > 0 ? 'Item type' : 'No item types' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: field.fields.map((item) => (_jsx(SelectItem, { value: item.name, children: item.name }, item.name))) }) })] })] })] }) }), _jsxs(PanelSection, { title: 'Query', children: [_jsxs("div", { className: 'grid grid-cols-[minmax(5rem,0.4fr)_minmax(0,1.2fr)_minmax(8rem,0.6fr)] items-end gap-3', children: [_jsxs(Label, { className: 'grid gap-1.5', children: ["Limit", _jsx(Input, { type: 'number', min: 1, max: 100, value: String(binding?.query?.options?.limit ?? 10), onChange: (event) => emit({
|
|
725
|
+
query: {
|
|
726
|
+
...binding?.query,
|
|
727
|
+
options: {
|
|
728
|
+
...binding?.query?.options,
|
|
729
|
+
limit: Math.min(100, Math.max(1, Number(event.target.value || 10))),
|
|
730
|
+
},
|
|
511
731
|
},
|
|
512
|
-
},
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
732
|
+
}) })] }), _jsxs(Label, { className: 'grid min-w-0 gap-1.5', children: ["Sort by", _jsxs(Select, { disabled: sortFieldOptions.length === 0, value: sortField || '__none__', onValueChange: (value) => emit({
|
|
733
|
+
query: {
|
|
734
|
+
...binding?.query,
|
|
735
|
+
options: {
|
|
736
|
+
...binding?.query?.options,
|
|
737
|
+
sort: value === '__none__'
|
|
738
|
+
? undefined
|
|
739
|
+
: { [value]: sortDirection },
|
|
740
|
+
},
|
|
519
741
|
},
|
|
520
|
-
},
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
742
|
+
}), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: sortFieldOptions.length > 0
|
|
743
|
+
? 'Sort by field'
|
|
744
|
+
: 'No sortable fields' }) }), _jsx(SelectContent, { children: _jsxs(SelectGroup, { children: [_jsx(SelectItem, { value: '__none__', children: "No sort" }), sortFieldOptions.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value)))] }) })] })] }), _jsxs(Label, { className: 'grid gap-1.5', children: ["Direction", _jsxs(Select, { disabled: !sortField, value: sortDirection, onValueChange: (direction) => sortField
|
|
745
|
+
? emit({
|
|
746
|
+
query: {
|
|
747
|
+
...binding?.query,
|
|
748
|
+
options: {
|
|
749
|
+
...binding?.query?.options,
|
|
750
|
+
sort: {
|
|
751
|
+
[sortField]: direction,
|
|
752
|
+
},
|
|
753
|
+
},
|
|
754
|
+
},
|
|
755
|
+
})
|
|
756
|
+
: undefined, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: 'asc', children: "Ascending" }), _jsx(SelectItem, { value: 'desc', children: "Descending" })] })] })] })] }), _jsxs("div", { className: 'grid gap-3 rounded-md border border-border bg-background/60 p-3', children: [_jsxs("div", { className: 'flex flex-wrap items-center justify-between gap-2', children: [_jsxs("div", { children: [_jsx("div", { className: 'text-sm font-medium', children: "Conditions" }), _jsx("div", { className: 'text-xs text-muted-foreground', children: "Filter the records used to build this list." })] }), _jsxs("div", { className: 'flex items-center gap-2', children: [filterState.conditions.length > 1 ? (_jsxs(Select, { value: filterState.combinator, onValueChange: (combinator) => updateFilter({
|
|
757
|
+
...filterState,
|
|
758
|
+
combinator: combinator,
|
|
759
|
+
}), children: [_jsx(SelectTrigger, { className: 'h-8 w-36', children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: 'and', children: "Match all" }), _jsx(SelectItem, { value: 'or', children: "Match any" })] })] })) : null, _jsxs(Button, { type: 'button', size: 'sm', variant: 'outline', disabled: filterFieldOptions.length === 0 ||
|
|
760
|
+
filterState.conditions.length >= 25, onClick: () => updateFilter({
|
|
533
761
|
...filterState,
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
})
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
762
|
+
conditions: [
|
|
763
|
+
...filterState.conditions,
|
|
764
|
+
{ field: '', operator: 'equals', value: '' },
|
|
765
|
+
],
|
|
766
|
+
}), children: [_jsx(Plus, { className: 'h-4 w-4' }), "Add condition"] })] })] }), filterState.conditions.length === 0 ? (_jsx("div", { className: 'rounded-md border border-dashed border-border px-3 py-5 text-center text-sm text-muted-foreground', children: "No conditions. All records from this collection will match." })) : (_jsx("div", { className: 'grid gap-2', children: filterState.conditions.map((condition, index) => {
|
|
767
|
+
const fieldOption = filterFieldOptions.find((option) => option.value === condition.field);
|
|
768
|
+
const operatorOptions = getFilterOperatorOptions(fieldOption?.kind);
|
|
769
|
+
const needsValue = operatorNeedsValue(condition.operator);
|
|
770
|
+
const currentValueOptions = currentDocumentFieldOptions.filter((option) => !fieldOption || option.kind === fieldOption.kind);
|
|
771
|
+
const allowsCurrentValue = condition.operator !== 'in' &&
|
|
772
|
+
condition.operator !== 'notIn' &&
|
|
773
|
+
currentValueOptions.length > 0;
|
|
774
|
+
return (_jsxs("div", { className: 'grid items-center gap-2 rounded-md border border-border bg-background p-2 sm:grid-cols-[1.2fr_1fr_1.8fr_auto]', children: [_jsxs(Select, { disabled: filterFieldOptions.length === 0, value: condition.field, onValueChange: (value) => {
|
|
775
|
+
const kind = filterFieldOptions.find((option) => option.value === value)?.kind;
|
|
776
|
+
updateFilterCondition(index, {
|
|
777
|
+
field: value,
|
|
778
|
+
operator: defaultFilterOperator(kind),
|
|
779
|
+
value: '',
|
|
780
|
+
});
|
|
781
|
+
}, children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'Field' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: filterFieldOptions.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value))) }) })] }), _jsxs(Select, { disabled: !condition.field, value: condition.operator, onValueChange: (operator) => updateFilterCondition(index, {
|
|
782
|
+
...condition,
|
|
783
|
+
operator: operator,
|
|
784
|
+
valueSource: operator === 'in' || operator === 'notIn'
|
|
785
|
+
? 'literal'
|
|
786
|
+
: condition.valueSource,
|
|
787
|
+
value: operatorNeedsValue(operator)
|
|
788
|
+
? operator === 'in' || operator === 'notIn'
|
|
789
|
+
? ''
|
|
790
|
+
: condition.value
|
|
791
|
+
: '',
|
|
792
|
+
}), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'Operator' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: operatorOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) }) })] }), needsValue ? (_jsxs("div", { className: 'grid grid-cols-[minmax(7rem,0.7fr)_minmax(0,1fr)] gap-2', children: [_jsxs(Select, { value: condition.valueSource ?? 'literal', onValueChange: (valueSource) => updateFilterCondition(index, {
|
|
793
|
+
...condition,
|
|
794
|
+
valueSource: valueSource,
|
|
795
|
+
value: '',
|
|
796
|
+
}), children: [_jsx(SelectTrigger, { className: 'w-full', "aria-label": 'Value source', children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: 'literal', children: "Fixed value" }), allowsCurrentValue ? (_jsx(SelectItem, { value: 'current', children: "Current document" })) : null] })] }), condition.valueSource === 'current' ? (_jsxs(Select, { disabled: !condition.field, value: condition.value, onValueChange: (value) => updateFilterCondition(index, {
|
|
797
|
+
...condition,
|
|
798
|
+
value,
|
|
799
|
+
}), children: [_jsx(SelectTrigger, { className: 'w-full', children: _jsx(SelectValue, { placeholder: 'Current field' }) }), _jsx(SelectContent, { children: _jsx(SelectGroup, { children: currentValueOptions.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) }) })] })) : (_jsx(Input, { type: fieldOption?.kind === 'number' &&
|
|
800
|
+
condition.operator !== 'in' &&
|
|
801
|
+
condition.operator !== 'notIn'
|
|
802
|
+
? 'number'
|
|
803
|
+
: 'text', value: condition.value, disabled: !condition.field, placeholder: condition.operator === 'in' ||
|
|
804
|
+
condition.operator === 'notIn'
|
|
805
|
+
? 'Comma-separated values'
|
|
806
|
+
: 'Value', onChange: (event) => updateFilterCondition(index, {
|
|
807
|
+
...condition,
|
|
808
|
+
value: event.target.value,
|
|
809
|
+
}) }))] })) : (_jsx("div", { className: 'h-9 rounded-md border border-dashed border-border bg-muted/30' })), _jsxs(Button, { type: 'button', size: 'icon', variant: 'ghost', className: 'size-9 text-muted-foreground hover:text-destructive', onClick: () => updateFilter({
|
|
542
810
|
...filterState,
|
|
543
|
-
|
|
544
|
-
})
|
|
545
|
-
|
|
811
|
+
conditions: filterState.conditions.filter((_, conditionIndex) => conditionIndex !== index),
|
|
812
|
+
}), children: [_jsx(Trash2, { className: 'h-4 w-4' }), _jsx("span", { className: 'sr-only', children: "Remove condition" })] })] }, `${index}-${condition.field}`));
|
|
813
|
+
}) }))] })] }), _jsx(PanelSection, { title: 'Mapping', children: _jsx("div", { className: 'grid gap-2', children: targetFields.map(([targetField, targetFieldConfig]) => {
|
|
546
814
|
const source = binding?.map?.[targetField];
|
|
547
815
|
const summary = mappingSourceSummary(source, selectedSource?.name ?? 'Source');
|
|
548
816
|
const isOpen = openMappingField === targetField;
|
|
@@ -641,7 +909,7 @@ export const DynamicDataControl = ({ contentType, documentContentType, fieldName
|
|
|
641
909
|
setOpen(false);
|
|
642
910
|
};
|
|
643
911
|
const trigger = (_jsxs("div", { className: 'flex min-w-0 items-center gap-1.5', children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: 'button', className: 'min-w-0 cursor-pointer rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring', onClick: () => setOpen((value) => !value), children: bound && summary ? (_jsxs(Badge, { variant: 'secondary', className: 'max-w-80 min-w-0 gap-1.5 rounded-md px-2 py-1 hover:bg-secondary/80', children: [list ? (_jsx(ListFilter, { className: 'h-3.5 w-3.5 shrink-0' })) : (_jsx(Link2, { className: 'h-3.5 w-3.5 shrink-0' })), _jsx("span", { className: 'truncate', children: summary })] })) : (_jsxs(Badge, { variant: 'outline', className: 'gap-1.5 rounded-md px-2 py-1 text-muted-foreground', children: [_jsx(Cable, { className: 'h-3.5 w-3.5' }), "Not linked"] })) }) }), _jsx(TooltipContent, { side: 'top', sideOffset: 6, children: triggerLabel })] }), bound ? (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs(Button, { type: 'button', size: 'icon', variant: 'ghost', className: 'size-6', onClick: clearBinding, children: [_jsx(X, { className: 'h-4 w-4' }), _jsx("span", { className: 'sr-only', children: "Clear dynamic data" })] }) }), _jsx(TooltipContent, { side: 'top', sideOffset: 6, children: "Clear dynamic data link" })] })) : null] }));
|
|
644
|
-
const editor = list ? (_jsx(ListBindingEditor, { contentTypes: sourceContentTypes, field: field, binding: listBinding, onChange: updateListBinding })) : (_jsx(FieldBindingEditor, { contentTypes: fieldSourceContentTypes, documentContentType: currentDocumentContentType, currentDocumentSourceEnabled: currentDocumentSourceEnabled, targetField: field, binding: fieldBinding, onChange: updateFieldBinding }));
|
|
912
|
+
const editor = list ? (_jsx(ListBindingEditor, { contentTypes: sourceContentTypes, documentContentType: currentDocumentContentType, field: field, binding: listBinding, onChange: updateListBinding })) : (_jsx(FieldBindingEditor, { contentTypes: fieldSourceContentTypes, documentContentType: currentDocumentContentType, currentDocumentSourceEnabled: currentDocumentSourceEnabled, targetField: field, binding: fieldBinding, onChange: updateFieldBinding }));
|
|
645
913
|
const panel = isOpen ? (_jsx("div", { className: 'rounded-md border border-border bg-background p-4 shadow-sm', children: editor })) : null;
|
|
646
914
|
const dialog = (_jsx(Dialog, { open: isOpen, onOpenChange: (value) => {
|
|
647
915
|
if (value) {
|