@svadmin/ui 0.72.0 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -22
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/app.css +8488 -6297
- package/dist/app.theme.css +8514 -6374
- package/dist/class-properties.generated.d.ts +1 -0
- package/dist/class-properties.generated.js +2 -0
- package/dist/classnames.d.ts +8 -0
- package/dist/classnames.js +26 -0
- package/dist/component-registry.d.ts +14977 -0
- package/dist/component-registry.js +216 -0
- package/dist/components/AboutSettings.svelte +7 -1
- package/dist/components/AboutSettings.svelte.d.ts +4 -1
- package/dist/components/ActivityFeed.svelte +9 -3
- package/dist/components/ActivityFeed.svelte.d.ts +3 -0
- package/dist/components/AdminApp.svelte +19 -10
- package/dist/components/AdminApp.svelte.d.ts +6 -0
- package/dist/components/ApiSettings.svelte +41 -5
- package/dist/components/ApprovalCenter.svelte +283 -0
- package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
- package/dist/components/AuditLogViewer.svelte +3 -1
- package/dist/components/AutoForm.svelte +53 -9
- package/dist/components/AutoForm.svelte.d.ts +3 -0
- package/dist/components/AutoTable.svelte +846 -43
- package/dist/components/AutoTable.svelte.d.ts +22 -1
- package/dist/components/BoundRecordDetailDrawer.svelte +17 -2
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +4 -0
- package/dist/components/Breadcrumbs.svelte +11 -1
- package/dist/components/CancelTaskButton.svelte +12 -42
- package/dist/components/ColorPicker.svelte +73 -0
- package/dist/components/ColorPicker.svelte.d.ts +15 -0
- package/dist/components/ComboboxField.svelte +143 -34
- package/dist/components/ComboboxField.svelte.d.ts +4 -2
- package/dist/components/CreatePage.svelte +4 -0
- package/dist/components/CreatePage.svelte.d.ts +2 -0
- package/dist/components/DashboardView.svelte +99 -0
- package/dist/components/DashboardView.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +10 -0
- package/dist/components/DatePicker.svelte.d.ts +6 -0
- package/dist/components/DateRangeInput.svelte +155 -0
- package/dist/components/DateRangeInput.svelte.d.ts +35 -0
- package/dist/components/DateRangePicker.svelte +10 -0
- package/dist/components/DateRangePicker.svelte.d.ts +6 -0
- package/dist/components/DateTimeInput.svelte +134 -0
- package/dist/components/DateTimeInput.svelte.d.ts +27 -0
- package/dist/components/DateTimePicker.svelte +10 -0
- package/dist/components/DateTimePicker.svelte.d.ts +6 -0
- package/dist/components/DevTools.svelte +36 -28
- package/dist/components/DevframePanel.svelte +123 -0
- package/dist/components/DevframePanel.svelte.d.ts +3 -0
- package/dist/components/EditPage.svelte +4 -0
- package/dist/components/EditPage.svelte.d.ts +2 -0
- package/dist/components/ErrorSummary.svelte +52 -0
- package/dist/components/ErrorSummary.svelte.d.ts +14 -0
- package/dist/components/FieldRenderer.svelte +172 -99
- package/dist/components/FileUpload.svelte +412 -0
- package/dist/components/FileUpload.svelte.d.ts +43 -0
- package/dist/components/FilterBuilder.svelte +244 -265
- package/dist/components/FilterBuilder.svelte.d.ts +3 -0
- package/dist/components/FilterCollectionInput.svelte +80 -0
- package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
- package/dist/components/GanttChart.svelte +66 -14
- package/dist/components/GanttChart.svelte.d.ts +6 -0
- package/dist/components/Header.svelte +18 -2
- package/dist/components/ImportWizard.svelte +509 -100
- package/dist/components/ImportWizard.svelte.d.ts +11 -0
- package/dist/components/JsonSchemaForm.svelte +322 -98
- package/dist/components/JsonSchemaForm.svelte.d.ts +18 -1
- package/dist/components/KanbanBoard.svelte +195 -40
- package/dist/components/KanbanBoard.svelte.d.ts +9 -2
- package/dist/components/Layout.svelte +4 -3
- package/dist/components/LazyAutoForm.svelte +28 -0
- package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
- package/dist/components/LazyAutoTable.svelte +28 -0
- package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
- package/dist/components/LazySettingsPage.svelte +28 -0
- package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
- package/dist/components/LazyShowPage.svelte +28 -0
- package/dist/components/LazyShowPage.svelte.d.ts +8 -0
- package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +1 -1
- package/dist/components/ListPage.svelte +9 -1
- package/dist/components/ListPage.svelte.d.ts +5 -0
- package/dist/components/LoginPage.svelte +2 -2
- package/dist/components/MoneyInput.svelte +25 -0
- package/dist/components/MoneyInput.svelte.d.ts +20 -0
- package/dist/components/MultiTabKeepAlive.svelte +11 -6
- package/dist/components/NotificationsSettings.svelte +69 -2
- package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
- package/dist/components/NumberInput.svelte +25 -0
- package/dist/components/NumberInput.svelte.d.ts +19 -0
- package/dist/components/NumericInput.svelte +110 -0
- package/dist/components/NumericInput.svelte.d.ts +23 -0
- package/dist/components/PercentInput.svelte +25 -0
- package/dist/components/PercentInput.svelte.d.ts +20 -0
- package/dist/components/PermissionMatrix.svelte +20 -1
- package/dist/components/PivotExportButton.svelte +157 -0
- package/dist/components/PivotExportButton.svelte.d.ts +16 -0
- package/dist/components/PivotTable.svelte +229 -74
- package/dist/components/PivotTable.svelte.d.ts +19 -2
- package/dist/components/QuickEditDrawer.svelte +4 -1
- package/dist/components/QuickEditDrawer.svelte.d.ts +2 -0
- package/dist/components/RadioGroup.svelte +121 -0
- package/dist/components/RadioGroup.svelte.d.ts +43 -0
- package/dist/components/RangeSlider.svelte +127 -0
- package/dist/components/RangeSlider.svelte.d.ts +22 -0
- package/dist/components/Rate.svelte +121 -0
- package/dist/components/Rate.svelte.d.ts +16 -0
- package/dist/components/RecordDetailDrawer.svelte +6 -2
- package/dist/components/RecordDetailDrawer.svelte.d.ts +4 -0
- package/dist/components/RelationPicker.svelte +341 -0
- package/dist/components/RelationPicker.svelte.d.ts +26 -0
- package/dist/components/ResourceOperationsPage.svelte +25 -19
- package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
- package/dist/components/RetryTaskButton.svelte +12 -42
- package/dist/components/SecuritySettings.svelte +29 -5
- package/dist/components/SegmentedControl.svelte +145 -0
- package/dist/components/SegmentedControl.svelte.d.ts +38 -0
- package/dist/components/SettingsPage.svelte +3 -1
- package/dist/components/SettingsPage.svelte.d.ts +1 -0
- package/dist/components/ShowInferencer.svelte +4 -2
- package/dist/components/ShowInferencer.svelte.d.ts +2 -0
- package/dist/components/ShowPage.svelte +25 -6
- package/dist/components/ShowPage.svelte.d.ts +5 -0
- package/dist/components/Sidebar.svelte +5 -5
- package/dist/components/Slider.svelte +100 -0
- package/dist/components/Slider.svelte.d.ts +19 -0
- package/dist/components/SpreadsheetView.svelte +1037 -124
- package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
- package/dist/components/StepsForm.svelte +5 -5
- package/dist/components/SvarAutoTable.svelte +39 -0
- package/dist/components/SvarAutoTable.svelte.d.ts +13 -0
- package/dist/components/SvarDataGrid.svelte +362 -0
- package/dist/components/SvarDataGrid.svelte.d.ts +57 -0
- package/dist/components/SvarGridCell.svelte +10 -0
- package/dist/components/SvarGridCell.svelte.d.ts +7 -0
- package/dist/components/SvarResourceRead.svelte +50 -0
- package/dist/components/SvarResourceRead.svelte.d.ts +23 -0
- package/dist/components/SvarResourceTable.svelte +63 -0
- package/dist/components/SvarResourceTable.svelte.d.ts +42 -0
- package/dist/components/SvarResourceTableView.svelte +364 -0
- package/dist/components/SvarResourceTableView.svelte.d.ts +4 -0
- package/dist/components/TagsInput.svelte +178 -0
- package/dist/components/TagsInput.svelte.d.ts +17 -0
- package/dist/components/TaskQueueDrawer.svelte +72 -16
- package/dist/components/TimePicker.svelte +10 -0
- package/dist/components/TimePicker.svelte.d.ts +6 -0
- package/dist/components/TreeSelect.svelte +440 -46
- package/dist/components/TreeSelect.svelte.d.ts +6 -1
- package/dist/components/TreeTable.svelte +484 -82
- package/dist/components/TreeTable.svelte.d.ts +24 -1
- package/dist/components/TwoFactorAuthPage.svelte +85 -48
- package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
- package/dist/components/VirtualTable.svelte +116 -20
- package/dist/components/VirtualTable.svelte.d.ts +5 -0
- package/dist/components/account/CompanyProfilePage.svelte +74 -38
- package/dist/components/account/GetStartedPage.svelte +2 -1
- package/dist/components/account/ImportMembersPage.svelte +22 -167
- package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
- package/dist/components/account/MemberDirectory.svelte +156 -0
- package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
- package/dist/components/account/MembersStarterPage.svelte +9 -55
- package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
- package/dist/components/account/SecurityLogPage.svelte +4 -1
- package/dist/components/account/SettingsPlainPage.svelte +7 -38
- package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
- package/dist/components/account/SettingsSidebarPage.svelte +7 -20
- package/dist/components/account/TeamMembersPage.svelte +4 -20
- package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
- package/dist/components/account/UserProfilePage.svelte +3 -25
- package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
- package/dist/components/auto-table-grid.d.ts +26 -0
- package/dist/components/auto-table-grid.js +1 -0
- package/dist/components/buttons/ExportButton.svelte +56 -12
- package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
- package/dist/components/content/ContentPageHeader.svelte +12 -9
- package/dist/components/content/ContentPageShell.svelte +6 -9
- package/dist/components/content/FileBrowser.svelte +35 -0
- package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
- package/dist/components/content/MetricBlock.svelte +10 -13
- package/dist/components/content/NetworkTable.svelte +3 -3
- package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
- package/dist/components/content/PageToolbar.svelte +6 -10
- package/dist/components/content/ReferenceNotice.svelte +5 -0
- package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
- package/dist/components/content/SectionHeader.svelte +8 -13
- package/dist/components/content/SettingsFieldRow.svelte +9 -23
- package/dist/components/content/SettingsGroup.svelte +11 -27
- package/dist/components/content/StatusBadge.svelte +3 -8
- package/dist/components/content/WorkspaceLayout.svelte +8 -12
- package/dist/components/enterprise/filter-tree.d.ts +48 -0
- package/dist/components/enterprise/filter-tree.js +224 -0
- package/dist/components/enterprise/json-schema-form.d.ts +39 -0
- package/dist/components/enterprise/json-schema-form.js +195 -0
- package/dist/components/enterprise/recursive-schema-form.d.ts +30 -0
- package/dist/components/enterprise/recursive-schema-form.js +196 -0
- package/dist/components/enterprise/spreadsheet-formula.d.ts +12 -0
- package/dist/components/enterprise/spreadsheet-formula.js +245 -0
- package/dist/components/export-reference.d.ts +2 -0
- package/dist/components/export-reference.js +9 -0
- package/dist/components/fieldComponentMap.js +2 -0
- package/dist/components/fields/DateField.svelte +12 -12
- package/dist/components/fields/date-display.d.ts +6 -0
- package/dist/components/fields/date-display.js +24 -0
- package/dist/components/fields/date-field-value.d.ts +2 -0
- package/dist/components/fields/date-field-value.js +13 -0
- package/dist/components/json-schema-form-state.d.ts +37 -0
- package/dist/components/json-schema-form-state.js +168 -0
- package/dist/components/network/TeamCrewTablePage.svelte +6 -1
- package/dist/components/network/UserCardsNFTPage.svelte +2 -0
- package/dist/components/profile/PublicProfilePage.svelte +2 -0
- package/dist/components/record-detail.svelte.d.ts +3 -0
- package/dist/components/record-detail.svelte.js +10 -4
- package/dist/components/saved-list-views.d.ts +74 -1
- package/dist/components/saved-list-views.js +191 -3
- package/dist/components/svar-grid-cells.d.ts +14 -0
- package/dist/components/svar-grid-cells.js +2 -0
- package/dist/components/svar-grid-contract.d.ts +80 -0
- package/dist/components/svar-grid-contract.js +159 -0
- package/dist/components/svar-grid-interactions.d.ts +54 -0
- package/dist/components/svar-grid-interactions.js +174 -0
- package/dist/components/svar-grid-loading.d.ts +40 -0
- package/dist/components/svar-grid-loading.js +180 -0
- package/dist/components/svar-grid-operations.d.ts +37 -0
- package/dist/components/svar-grid-operations.js +186 -0
- package/dist/components/svar-grid-panes.d.ts +34 -0
- package/dist/components/svar-grid-panes.js +66 -0
- package/dist/components/svar-grid-resource-loading.d.ts +18 -0
- package/dist/components/svar-grid-resource-loading.js +68 -0
- package/dist/components/svar-grid-resource-session.svelte.d.ts +29 -0
- package/dist/components/svar-grid-resource-session.svelte.js +61 -0
- package/dist/components/table-contract.d.ts +12 -1
- package/dist/components/task-action.svelte.d.ts +13 -0
- package/dist/components/task-action.svelte.js +73 -0
- package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
- package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
- package/dist/components/ui/badge/badge-variants.d.ts +3 -2
- package/dist/components/ui/badge/badge-variants.js +2 -1
- package/dist/components/ui/badge/badge.svelte +3 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
- package/dist/components/ui/button/button-variants.d.ts +5 -4
- package/dist/components/ui/button/button-variants.js +7 -2
- package/dist/components/ui/checkbox/checkbox.svelte +5 -1
- package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
- package/dist/components/ui/input/input.svelte +8 -6
- package/dist/components/ui/input/input.svelte.d.ts +4 -1
- package/dist/components/ui/select/select.svelte +4 -1
- package/dist/components/ui/select/select.svelte.d.ts +2 -1
- package/dist/components/ui/textarea/textarea.svelte +2 -1
- package/dist/components.css +3110 -0
- package/dist/date-input-policy.d.ts +4 -0
- package/dist/date-input-policy.js +35 -0
- package/dist/design-contract.d.ts +13 -0
- package/dist/design-contract.js +11 -0
- package/dist/design-system-components.d.ts +100 -0
- package/dist/design-system-components.js +101 -0
- package/dist/design-system.d.ts +15 -0
- package/dist/design-system.js +7 -0
- package/dist/devtools-bridge.d.ts +80 -0
- package/dist/devtools-bridge.js +92 -0
- package/dist/index.d.ts +50 -1
- package/dist/index.js +32 -2
- package/dist/page-chrome.css +12 -49
- package/dist/recipes.d.ts +960 -0
- package/dist/recipes.js +264 -0
- package/dist/rendering/index.d.ts +87 -0
- package/dist/rendering/index.js +117 -0
- package/dist/styles/aliases.css +49 -0
- package/dist/styles/compatibility.epilogue.css +384 -0
- package/dist/styles/compatibility.prelude.css +5513 -0
- package/dist/styles/conditional-states.css +28 -0
- package/dist/styles/conditional-utilities.css +22 -0
- package/dist/tree-table-model.d.ts +24 -0
- package/dist/tree-table-model.js +111 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/package.json +40 -10
- package/styles-compatibility.json +12 -0
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
2
3
|
import { Plus, Trash2, RotateCcw, Filter as FilterIcon, Check } from '@lucide/svelte';
|
|
3
4
|
import { cn } from '../utils.js';
|
|
4
|
-
import { numericInputValue } from '../numeric-input.js';
|
|
5
5
|
import { Button } from './ui/button/index.js';
|
|
6
6
|
import { Input } from './ui/input/index.js';
|
|
7
7
|
import { Select } from './ui/select/index.js';
|
|
8
8
|
import { useTranslation } from '@svadmin/core/i18n';
|
|
9
|
-
import type { CrudOperator, FieldDefinition,
|
|
9
|
+
import type { CrudOperator, FieldDefinition, Filter } from '@svadmin/core';
|
|
10
|
+
import { isNumericFilterField as isNumericField } from '@svadmin/core';
|
|
11
|
+
import FilterCollectionInput from './FilterCollectionInput.svelte';
|
|
12
|
+
import { readFilterTree, compileFilterTree, filterOperators, parseFilterInput, isCollectionOperator, isNullOperator,
|
|
13
|
+
FILTER_EDITOR_LIMITS, type FilterGroupNode, type FilterNode, type FilterRuleNode, type FilterEditorIssue, type FilterIssueCode } from './enterprise/filter-tree.js';
|
|
10
14
|
|
|
11
15
|
export interface FilterRuleItem {
|
|
12
16
|
id: string;
|
|
@@ -15,307 +19,282 @@
|
|
|
15
19
|
value: unknown;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
|
-
const i18n = useTranslation();
|
|
19
|
-
|
|
20
22
|
interface Props {
|
|
21
23
|
fields?: FieldDefinition[];
|
|
22
24
|
filters?: Filter[];
|
|
23
25
|
logicalOperator?: 'and' | 'or';
|
|
26
|
+
disabled?: boolean;
|
|
24
27
|
class?: string;
|
|
25
28
|
onApply?: (filters: Filter[]) => void;
|
|
26
29
|
onReset?: () => void;
|
|
30
|
+
onInvalid?: (issues: FilterEditorIssue[]) => void;
|
|
27
31
|
}
|
|
32
|
+
let { fields = [], filters = $bindable([]), logicalOperator = $bindable('and'), disabled = false,
|
|
33
|
+
class: className, onApply, onReset, onInvalid }: Props = $props();
|
|
34
|
+
const uid = $props.id();
|
|
35
|
+
const i18n = useTranslation();
|
|
36
|
+
const chinese = $derived(i18n.locale.startsWith('zh'));
|
|
37
|
+
const availableFields = $derived(fields.filter((field) => field.filterable !== false));
|
|
38
|
+
// 金额、评分等数字字段沿用 core 的输入语义;树编译器统一按 number 验证。
|
|
39
|
+
const editorFields = $derived(fields.map(field => isNumericField(field) ? { ...field, type: 'number' as const } : field));
|
|
40
|
+
let root = $state<FilterGroupNode>({ kind: 'group', id: 'root', operator: 'and', children: [], wrapped: false });
|
|
41
|
+
let loadIssues = $state<FilterEditorIssue[]>([]);
|
|
42
|
+
let attempted = $state(false);
|
|
43
|
+
let nextId = 0;
|
|
44
|
+
let lastInputFilters: Filter[] | undefined;
|
|
45
|
+
let preserved = $state.raw<ReadonlyMap<string, Filter>>(new Map());
|
|
46
|
+
const compilation = $derived(compileFilterTree({ ...root, operator: logicalOperator }, editorFields, preserved));
|
|
47
|
+
const issues = $derived(loadIssues.length ? loadIssues : attempted && !compilation.ok ? compilation.issues : []);
|
|
48
|
+
function countNodes(node: FilterNode): number { return 1 + (node.kind === 'group' ? node.children.reduce((sum, child) => sum + countNodes(child), 0) : 0); }
|
|
49
|
+
const nodeCount = $derived(countNodes(root));
|
|
50
|
+
const full = $derived(nodeCount >= FILTER_EDITOR_LIMITS.nodes);
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class: className,
|
|
34
|
-
onApply,
|
|
35
|
-
onReset,
|
|
36
|
-
}: Props = $props();
|
|
37
|
-
|
|
38
|
-
const operatorOptions: { value: CrudOperator; label: string }[] = [
|
|
39
|
-
{ value: 'eq', label: '等于 (eq)' },
|
|
40
|
-
{ value: 'ne', label: '不等于 (ne)' },
|
|
41
|
-
{ value: 'contains', label: '包含 (contains)' },
|
|
42
|
-
{ value: 'ncontains', label: '不包含 (ncontains)' },
|
|
43
|
-
{ value: 'gt', label: '大于 (>)' },
|
|
44
|
-
{ value: 'gte', label: '大于等于 (>=)' },
|
|
45
|
-
{ value: 'lt', label: '小于 (<)' },
|
|
46
|
-
{ value: 'lte', label: '小于等于 (<=)' },
|
|
47
|
-
{ value: 'null', label: '为空 (null)' },
|
|
48
|
-
{ value: 'nnull', label: '非空 (not null)' },
|
|
49
|
-
];
|
|
52
|
+
function snapshotNode(node: FilterNode): Filter {
|
|
53
|
+
return node.kind === 'group' ? { operator: node.operator, value: node.children.map(snapshotNode) }
|
|
54
|
+
: { field: node.field, operator: node.operator, value: Array.isArray(node.value) ? [...node.value] : node.value };
|
|
55
|
+
}
|
|
50
56
|
|
|
51
|
-
|
|
57
|
+
function cannotEdit(node: FilterRuleNode): boolean {
|
|
58
|
+
return !compileFilterTree({ kind: 'group', id: 'probe', operator: 'and', children: [node] }, editorFields).ok;
|
|
59
|
+
}
|
|
60
|
+
function remember(node: FilterNode, saved: Map<string, Filter>): void {
|
|
61
|
+
saved.set(node.id, snapshotNode(node));
|
|
62
|
+
if (node.kind === 'group') node.children.forEach(child => remember(child, saved));
|
|
63
|
+
else node.readonly = cannotEdit(node);
|
|
64
|
+
}
|
|
52
65
|
|
|
53
|
-
// Initialize rules from filters prop
|
|
54
66
|
$effect(() => {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
} else
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
67
|
+
const input = filters;
|
|
68
|
+
const metadata = editorFields;
|
|
69
|
+
untrack(() => {
|
|
70
|
+
if (input !== lastInputFilters) {
|
|
71
|
+
lastInputFilters = input;
|
|
72
|
+
const parsed = readFilterTree(input);
|
|
73
|
+
const saved = new Map<string, Filter>();
|
|
74
|
+
if (parsed.ok) {
|
|
75
|
+
root = parsed.root;
|
|
76
|
+
remember(root, saved);
|
|
77
|
+
loadIssues = [];
|
|
78
|
+
if (input.length) logicalOperator = root.operator;
|
|
79
|
+
} else {
|
|
80
|
+
root = { kind: 'group', id: 'root', operator: 'and', children: [], wrapped: false };
|
|
81
|
+
loadIssues = parsed.issues;
|
|
82
|
+
}
|
|
83
|
+
preserved = saved;
|
|
84
|
+
attempted = false;
|
|
85
|
+
} else {
|
|
86
|
+
// 元数据加载后只解锁已支持的历史条件;不能把失效草稿改成只读而绕过校验。
|
|
87
|
+
function unlock(node: FilterNode): void {
|
|
88
|
+
if (node.kind === 'group') node.children.forEach(unlock);
|
|
89
|
+
else if (node.readonly && compileFilterTree({ kind: 'group', id: 'probe', operator: 'and', children: [node] }, metadata).ok) node.readonly = false;
|
|
79
90
|
}
|
|
91
|
+
unlock(root);
|
|
80
92
|
}
|
|
81
|
-
|
|
82
|
-
logicalOperator = nextLogical;
|
|
83
|
-
} else {
|
|
84
|
-
rules = [];
|
|
85
|
-
}
|
|
93
|
+
});
|
|
86
94
|
});
|
|
87
95
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
function message(code: FilterIssueCode): string {
|
|
97
|
+
const labels: Record<FilterIssueCode, [string, string]> = {
|
|
98
|
+
'invalid-filter': ['条件结构不受支持,原条件未被修改', 'Unsupported filter structure; the original query is unchanged'],
|
|
99
|
+
limit: ['条件数量或深度超过限制', 'Filter count or depth exceeds the limit'],
|
|
100
|
+
'unknown-field': ['字段不可用于筛选', 'Field is unavailable for filtering'],
|
|
101
|
+
operator: ['操作符不适用于此字段', 'Operator is not supported for this field'],
|
|
102
|
+
value: ['请输入有效的类型化值', 'Enter a valid typed value'],
|
|
103
|
+
'empty-group': ['条件组不能为空', 'A filter group cannot be empty'],
|
|
104
|
+
};
|
|
105
|
+
return labels[code][chinese ? 0 : 1];
|
|
106
|
+
}
|
|
107
|
+
function appendRule(group: FilterGroupNode): void {
|
|
108
|
+
if (disabled || loadIssues.length || full) return;
|
|
109
|
+
const field = availableFields[0];
|
|
110
|
+
if (!field) return;
|
|
111
|
+
group.children = [...group.children, { kind: 'rule', id: `draft-${++nextId}`, field: field.key,
|
|
112
|
+
operator: filterOperators(editorFields.find(item => item.key === field.key)).includes('contains') ? 'contains' : 'eq', value: undefined }];
|
|
113
|
+
}
|
|
96
114
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
id: `rule-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
|
|
101
|
-
field: firstField,
|
|
102
|
-
operator: defaultOp,
|
|
103
|
-
value: '',
|
|
104
|
-
},
|
|
105
|
-
];
|
|
115
|
+
function appendGroup(group: FilterGroupNode): void {
|
|
116
|
+
if (disabled || loadIssues.length || full) return;
|
|
117
|
+
group.children = [...group.children, { kind: 'group', id: `draft-${++nextId}`, operator: 'and', children: [] }];
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
|
|
109
|
-
|
|
120
|
+
function removeNode(group: FilterGroupNode, id: string): void {
|
|
121
|
+
if (disabled || loadIssues.length) return;
|
|
122
|
+
group.children = group.children.filter(child => child.id !== id);
|
|
110
123
|
}
|
|
111
124
|
|
|
112
|
-
export function
|
|
113
|
-
|
|
125
|
+
export function addRule(): void { appendRule(root); }
|
|
126
|
+
export function removeRule(index: number): void { const node = root.children[index]; if (node) removeNode(root, node.id); }
|
|
127
|
+
export function reset(): void {
|
|
128
|
+
if (disabled) return;
|
|
129
|
+
root = { kind: 'group', id: 'root', operator: 'and', children: [], wrapped: false };
|
|
130
|
+
loadIssues = [];
|
|
131
|
+
preserved = new Map();
|
|
114
132
|
filters = [];
|
|
133
|
+
lastInputFilters = filters;
|
|
134
|
+
attempted = false;
|
|
115
135
|
onReset?.();
|
|
116
136
|
}
|
|
117
137
|
|
|
118
|
-
export function apply() {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
138
|
+
export function apply(): void {
|
|
139
|
+
if (disabled) return;
|
|
140
|
+
attempted = true;
|
|
141
|
+
if (loadIssues.length) { onInvalid?.(loadIssues); return; }
|
|
142
|
+
if (!compilation.ok) { onInvalid?.(compilation.issues); return; }
|
|
143
|
+
// 失败时不输出部分条件,避免改变查询含义。
|
|
144
|
+
filters = compilation.filters;
|
|
145
|
+
lastInputFilters = filters;
|
|
146
|
+
onApply?.(compilation.filters);
|
|
147
|
+
}
|
|
126
148
|
|
|
127
|
-
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
value: validFieldFilters,
|
|
134
|
-
};
|
|
135
|
-
compiledFilters = [logical];
|
|
136
|
-
}
|
|
149
|
+
function changeField(rule: FilterRuleNode, key: string): void {
|
|
150
|
+
if (disabled || rule.readonly) return;
|
|
151
|
+
rule.field = key;
|
|
152
|
+
rule.operator = filterOperators(editorFields.find(field => field.key === key)).includes('contains') ? 'contains' : 'eq';
|
|
153
|
+
rule.value = undefined;
|
|
154
|
+
}
|
|
137
155
|
|
|
138
|
-
|
|
139
|
-
|
|
156
|
+
function changeValue(rule: FilterRuleNode, raw: string): void {
|
|
157
|
+
if (disabled || rule.readonly) return;
|
|
158
|
+
try { rule.value = parseFilterInput(raw, editorFields.find(field => field.key === rule.field), rule.operator); }
|
|
159
|
+
catch { rule.value = raw; }
|
|
140
160
|
}
|
|
141
|
-
|
|
161
|
+
function valueText(value: unknown): string { return Array.isArray(value) ? JSON.stringify(value) : value === undefined || value === null ? '' : String(value); }
|
|
142
162
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
'svadmin-u-07389a777c1f svadmin-u-d5eab218aa34 svadmin-u-465609a240a8 svadmin-u-359090c2d529 svadmin-u-ceb69a6b0e5f',
|
|
156
|
-
logicalOperator === 'and' ? 'svadmin-u-e6f9e383a762 svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359 svadmin-u-cef5b893cf23' : 'svadmin-u-bfa603190748 svadmin-u-ea7b2e9e070e'
|
|
157
|
-
)}
|
|
158
|
-
onclick={() => (logicalOperator = 'and')}
|
|
159
|
-
>
|
|
160
|
-
AND (且)
|
|
161
|
-
</button>
|
|
162
|
-
<button
|
|
163
|
-
type="button"
|
|
164
|
-
class={cn(
|
|
165
|
-
'svadmin-u-07389a777c1f svadmin-u-d5eab218aa34 svadmin-u-465609a240a8 svadmin-u-359090c2d529 svadmin-u-ceb69a6b0e5f',
|
|
166
|
-
logicalOperator === 'or' ? 'svadmin-u-e6f9e383a762 svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359 svadmin-u-cef5b893cf23' : 'svadmin-u-bfa603190748 svadmin-u-ea7b2e9e070e'
|
|
167
|
-
)}
|
|
168
|
-
onclick={() => (logicalOperator = 'or')}
|
|
169
|
-
>
|
|
170
|
-
OR (或)
|
|
171
|
-
</button>
|
|
172
|
-
</div>
|
|
173
|
-
{/if}
|
|
174
|
-
</div>
|
|
163
|
+
function changeOperator(rule: FilterRuleNode, operator: CrudOperator): void {
|
|
164
|
+
if (disabled || rule.readonly) return;
|
|
165
|
+
const previous = rule.operator;
|
|
166
|
+
rule.operator = operator;
|
|
167
|
+
if (isNullOperator(operator)) rule.value = null;
|
|
168
|
+
else if (operator === 'between' || operator === 'nbetween') {
|
|
169
|
+
if (previous !== 'between' && previous !== 'nbetween') rule.value = [null, null];
|
|
170
|
+
} else if (isCollectionOperator(operator)) {
|
|
171
|
+
if (previous !== 'in' && previous !== 'nin') rule.value = [null];
|
|
172
|
+
} else if (isCollectionOperator(previous) || isNullOperator(previous)) rule.value = undefined;
|
|
173
|
+
}
|
|
174
|
+
</script>
|
|
175
175
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
onclick={
|
|
193
|
-
|
|
194
|
-
<Check class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
195
|
-
{i18n.t('common.confirm', undefined) ?? '应用'}
|
|
176
|
+
{#snippet groupEditor(group: FilterGroupNode, depth: number, top: boolean)}
|
|
177
|
+
<fieldset class="filter-group" data-testid={top ? 'filter-builder-root' : 'filter-builder-group'} data-filter-group={group.id} disabled={disabled}>
|
|
178
|
+
<legend>{top ? (chinese ? '筛选条件' : 'Filters') : (chinese ? '条件组' : 'Filter group')}</legend>
|
|
179
|
+
<div class="group-actions">
|
|
180
|
+
<label for={`${uid}-${group.id}-logic`}>{chinese ? '逻辑' : 'Logic'}</label>
|
|
181
|
+
<Select id={`${uid}-${group.id}-logic`} aria-label={chinese ? '组合逻辑' : 'Group logic'}
|
|
182
|
+
value={top ? logicalOperator : group.operator}
|
|
183
|
+
onchange={(event: Event) => {
|
|
184
|
+
const target = event.currentTarget;
|
|
185
|
+
if (disabled || !(target instanceof HTMLSelectElement)) return;
|
|
186
|
+
const next = target.value === 'or' ? 'or' : 'and';
|
|
187
|
+
if (top) logicalOperator = next; else group.operator = next;
|
|
188
|
+
}}>
|
|
189
|
+
<option value="and">AND</option><option value="or">OR</option>
|
|
190
|
+
</Select>
|
|
191
|
+
<Button type="button" size="sm" variant="outline" disabled={full || depth >= FILTER_EDITOR_LIMITS.depth || !availableFields.length}
|
|
192
|
+
data-testid={top ? 'filter-builder-add-rule' : 'filter-group-add-rule'} onclick={() => appendRule(group)}>
|
|
193
|
+
<Plus size={14} aria-hidden="true" />{i18n.t('common.addRule')}
|
|
196
194
|
</Button>
|
|
195
|
+
<Button type="button" size="sm" variant="ghost" disabled={full || depth >= FILTER_EDITOR_LIMITS.depth - 1}
|
|
196
|
+
data-testid="filter-builder-add-group" onclick={() => appendGroup(group)}>{chinese ? '添加条件组' : 'Add group'}</Button>
|
|
197
197
|
</div>
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3fd0f778c8d9 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-421ac2be5045 svadmin-u-967d113a1451 svadmin-u-7660b450905a svadmin-u-ca6bcd4b6f3f svadmin-u-6ee2d41e2d2d">
|
|
204
|
-
<!-- Field select -->
|
|
205
|
-
<div class="svadmin-u-df403bbae8fc svadmin-u-012fbd121f37">
|
|
206
|
-
<Select
|
|
207
|
-
class="svadmin-u-ed8a5df7b2fb svadmin-u-359090c2d529"
|
|
208
|
-
value={rule.field}
|
|
209
|
-
onchange={(e: Event) => {
|
|
210
|
-
rule.field = (e.currentTarget as HTMLSelectElement).value;
|
|
211
|
-
}}
|
|
212
|
-
>
|
|
213
|
-
{#each availableFields as f (f.key)}
|
|
214
|
-
<option value={f.key}>{f.label || f.key}</option>
|
|
215
|
-
{/each}
|
|
216
|
-
</Select>
|
|
217
|
-
</div>
|
|
218
|
-
|
|
219
|
-
<!-- Operator select -->
|
|
220
|
-
<div class="svadmin-u-df403bbae8fc svadmin-u-012fbd121f37">
|
|
221
|
-
<Select
|
|
222
|
-
class="svadmin-u-ed8a5df7b2fb svadmin-u-359090c2d529"
|
|
223
|
-
value={rule.operator}
|
|
224
|
-
onchange={(e: Event) => {
|
|
225
|
-
rule.operator = (e.currentTarget as HTMLSelectElement).value as CrudOperator;
|
|
226
|
-
}}
|
|
227
|
-
>
|
|
228
|
-
{#each operatorOptions as op (op.value)}
|
|
229
|
-
<option value={op.value}>{op.label}</option>
|
|
230
|
-
{/each}
|
|
231
|
-
</Select>
|
|
198
|
+
{#each group.children as node (node.id)}
|
|
199
|
+
{#if node.kind === 'group'}
|
|
200
|
+
<div class="nested-group">
|
|
201
|
+
{@render groupEditor(node, depth + 1, false)}
|
|
202
|
+
<Button type="button" size="sm" variant="ghost" aria-label={chinese ? '删除条件组' : 'Remove group'} onclick={() => removeNode(group, node.id)}><Trash2 size={14} aria-hidden="true" /></Button>
|
|
232
203
|
</div>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}}
|
|
247
|
-
>
|
|
248
|
-
<option value="">{i18n.t('field.selectPlaceholder', undefined) ?? '请选择'}</option>
|
|
249
|
-
{#each fieldDef.options as opt (opt.value)}
|
|
250
|
-
<option value={String(opt.value)}>{opt.label}</option>
|
|
251
|
-
{/each}
|
|
204
|
+
{:else}
|
|
205
|
+
{@const field = availableFields.find((item) => item.key === node.field)}
|
|
206
|
+
{@const allowed = filterOperators(editorFields.find(item => item.key === node.field))}
|
|
207
|
+
{@const collection = isCollectionOperator(node.operator)}
|
|
208
|
+
{@const invalid = issues.some((issue) => issue.path === node.id)}
|
|
209
|
+
<div class="filter-rule" data-testid="filter-builder-rule" data-filter-rule={node.id}>
|
|
210
|
+
{#if node.readonly}
|
|
211
|
+
<output aria-label={i18n.t('filter.readonly')}>{node.field} {node.operator} {JSON.stringify(node.value)}</output>
|
|
212
|
+
{:else}
|
|
213
|
+
<div>
|
|
214
|
+
<label for={`${uid}-${node.id}-field`}>{chinese ? '字段' : 'Field'}</label>
|
|
215
|
+
<Select id={`${uid}-${node.id}-field`} value={node.field} onchange={(event: Event) => { if (event.currentTarget instanceof HTMLSelectElement) changeField(node, event.currentTarget.value); }}>
|
|
216
|
+
{#if !field}<option value={node.field} disabled>{node.field}</option>{/if}
|
|
217
|
+
{#each availableFields as option (option.key)}<option value={option.key}>{option.label || option.key}</option>{/each}
|
|
252
218
|
</Select>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
rule.value = val === 'true' ? true : val === 'false' ? false : '';
|
|
260
|
-
}}
|
|
261
|
-
>
|
|
262
|
-
<option value="">{i18n.t('field.selectPlaceholder', undefined) ?? '请选择'}</option>
|
|
263
|
-
<option value="true">{i18n.t('common.yes', undefined) ?? '是 (true)'}</option>
|
|
264
|
-
<option value="false">{i18n.t('common.no', undefined) ?? '否 (false)'}</option>
|
|
219
|
+
</div>
|
|
220
|
+
<div>
|
|
221
|
+
<label for={`${uid}-${node.id}-operator`}>{chinese ? '操作符' : 'Operator'}</label>
|
|
222
|
+
<Select id={`${uid}-${node.id}-operator`} value={node.operator} onchange={(event: Event) => { if (event.currentTarget instanceof HTMLSelectElement) changeOperator(node, event.currentTarget.value as CrudOperator); }}>
|
|
223
|
+
{#if !allowed.includes(node.operator)}<option value={node.operator} disabled>{node.operator}</option>{/if}
|
|
224
|
+
{#each allowed as operator (operator)}<option value={operator}>{operator}</option>{/each}
|
|
265
225
|
</Select>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (!
|
|
275
|
-
|
|
276
|
-
}}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
226
|
+
</div>
|
|
227
|
+
<div>
|
|
228
|
+
<label for={`${uid}-${node.id}-value`}>{collection ? (chinese ? '值(JSON 数组)' : 'Value (JSON array)') : (chinese ? '值' : 'Value')}</label>
|
|
229
|
+
{#if isNullOperator(node.operator)}
|
|
230
|
+
<Input id={`${uid}-${node.id}-value`} value={chinese ? '无需填值' : 'No value required'} disabled />
|
|
231
|
+
{:else if collection && field}
|
|
232
|
+
<FilterCollectionInput {field} value={node.value}
|
|
233
|
+
range={node.operator === 'between' || node.operator === 'nbetween'}
|
|
234
|
+
onchange={(value) => { if (!disabled && !node.readonly) node.value = value; }} />
|
|
235
|
+
{:else if !collection && field?.options?.length}
|
|
236
|
+
<Select id={`${uid}-${node.id}-value`} aria-invalid={invalid}
|
|
237
|
+
value={field.options.findIndex((option) => option.value === node.value) < 0 ? '' : String(field.options.findIndex((option) => option.value === node.value))}
|
|
238
|
+
onchange={(event: Event) => {
|
|
239
|
+
if (disabled || node.readonly || !(event.currentTarget instanceof HTMLSelectElement)) return;
|
|
240
|
+
const raw = event.currentTarget.value;
|
|
241
|
+
if (raw === '') { node.value = undefined; return; }
|
|
242
|
+
if (!/^(0|[1-9][0-9]*)$/u.test(raw)) return;
|
|
243
|
+
const option = field.options?.[Number(raw)];
|
|
244
|
+
if (option && !option.disabled) node.value = option.value;
|
|
245
|
+
}}>
|
|
246
|
+
<option value="">{chinese ? '请选择' : 'Choose'}</option>
|
|
247
|
+
{#each field.options as option, index (index)}<option value={String(index)} disabled={option.disabled}>{option.label}</option>{/each}
|
|
248
|
+
</Select>
|
|
249
|
+
{:else if !collection && field?.type === 'boolean'}
|
|
250
|
+
<Select id={`${uid}-${node.id}-value`} aria-invalid={invalid} value={valueText(node.value)}
|
|
251
|
+
onchange={(event: Event) => { if (event.currentTarget instanceof HTMLSelectElement) changeValue(node, event.currentTarget.value); }}>
|
|
252
|
+
<option value="">{chinese ? '请选择' : 'Choose'}</option><option value="true">true</option><option value="false">false</option>
|
|
253
|
+
</Select>
|
|
254
|
+
{:else if !collection && isNumericField(field)}
|
|
255
|
+
<Input id={`${uid}-${node.id}-value`} type="number" step="any" aria-invalid={invalid} value={typeof node.value === 'number' ? node.value : undefined}
|
|
256
|
+
oninput={(event: Event) => { if (event.currentTarget instanceof HTMLInputElement) changeValue(node, event.currentTarget.value); }} />
|
|
257
|
+
{:else}
|
|
258
|
+
<Input id={`${uid}-${node.id}-value`} type="text" aria-invalid={invalid} value={valueText(node.value)} placeholder={collection ? '[1, 2]' : ''}
|
|
259
|
+
oninput={(event: Event) => { if (event.currentTarget instanceof HTMLInputElement) changeValue(node, event.currentTarget.value); }} />
|
|
260
|
+
{/if}
|
|
261
|
+
</div>
|
|
289
262
|
{/if}
|
|
263
|
+
<Button type="button" size="sm" variant="ghost" aria-label={chinese ? '删除条件' : 'Remove rule'} onclick={() => removeNode(group, node.id)}><Trash2 size={14} aria-hidden="true" /></Button>
|
|
290
264
|
</div>
|
|
291
|
-
|
|
292
|
-
<!-- Remove button -->
|
|
293
|
-
<Button
|
|
294
|
-
type="button"
|
|
295
|
-
variant="ghost"
|
|
296
|
-
size="icon-sm"
|
|
297
|
-
class="svadmin-u-012fbd121f37 svadmin-u-bfa603190748 svadmin-u-51e95020d6f2"
|
|
298
|
-
onclick={() => removeRule(index)}
|
|
299
|
-
>
|
|
300
|
-
<Trash2 class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
301
|
-
</Button>
|
|
302
|
-
</div>
|
|
265
|
+
{/if}
|
|
303
266
|
{:else}
|
|
304
|
-
<
|
|
305
|
-
暂无筛选条件,点击下方按钮添加规则
|
|
306
|
-
</div>
|
|
267
|
+
<p class="empty">{chinese ? '暂无筛选条件,请添加规则。' : 'No filters yet. Add a rule.'}</p>
|
|
307
268
|
{/each}
|
|
308
|
-
</
|
|
269
|
+
</fieldset>
|
|
270
|
+
{/snippet}
|
|
309
271
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
>
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
</Button>
|
|
272
|
+
<div class={cn('svadmin-filter-builder', className)} data-testid="filter-builder">
|
|
273
|
+
<div class="builder-header">
|
|
274
|
+
<span><FilterIcon size={16} aria-hidden="true" /> {i18n.t('common.filterBuilder')}</span>
|
|
275
|
+
<div class="group-actions">
|
|
276
|
+
<Button type="button" variant="ghost" size="sm" disabled={disabled} data-testid="filter-builder-reset" onclick={reset}><RotateCcw size={14} aria-hidden="true" />{i18n.t('common.reset')}</Button>
|
|
277
|
+
<Button type="button" size="sm" disabled={disabled} data-testid="filter-builder-apply" onclick={apply}><Check size={14} aria-hidden="true" />{i18n.t('common.confirm')}</Button>
|
|
278
|
+
</div>
|
|
279
|
+
</div>
|
|
280
|
+
{#if issues.length}<div role="alert" class="errors" data-testid="filter-builder-errors">{#each issues as issue, index (`${issue.path}-${issue.code}-${index}`)}<p>{issue.path}: {message(issue.code)}</p>{/each}</div>{/if}
|
|
281
|
+
{#if !loadIssues.length}{@render groupEditor(root, root.wrapped || logicalOperator === 'or' ? 1 : 0, true)}{/if}
|
|
321
282
|
</div>
|
|
283
|
+
|
|
284
|
+
<style>
|
|
285
|
+
.svadmin-filter-builder { color: var(--foreground); background: var(--card, var(--background)); border: 1px solid var(--border); border-radius: var(--radius, .5rem); padding: .75rem; font-size: .875rem; }
|
|
286
|
+
.builder-header, .group-actions, .nested-group { display: flex; align-items: center; gap: .5rem; }
|
|
287
|
+
.builder-header { justify-content: space-between; flex-wrap: wrap; padding-block-end: .75rem; }
|
|
288
|
+
.builder-header > span { display: inline-flex; align-items: center; gap: .5rem; font-weight: 600; }
|
|
289
|
+
.group-actions { flex-wrap: wrap; }
|
|
290
|
+
.filter-group { min-width: 0; width: 100%; border: 1px solid var(--border); border-radius: var(--radius, .5rem); padding: .75rem; margin: .5rem 0 0; }
|
|
291
|
+
legend { padding-inline: .25rem; color: var(--muted-foreground); }
|
|
292
|
+
.filter-rule { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 2fr) auto; align-items: end; gap: .5rem; margin-block-start: .75rem; }
|
|
293
|
+
.filter-rule > div { min-width: 0; }
|
|
294
|
+
.filter-rule > output { grid-column: 1 / -2; overflow-wrap: anywhere; }
|
|
295
|
+
.filter-rule label { display: block; margin-block-end: .25rem; color: var(--muted-foreground); }
|
|
296
|
+
.empty { padding-block: .75rem; color: var(--muted-foreground); }
|
|
297
|
+
.errors { color: var(--destructive); overflow-wrap: anywhere; }
|
|
298
|
+
.nested-group { align-items: flex-start; }
|
|
299
|
+
@media (max-width: 640px) { .filter-rule { grid-template-columns: minmax(0, 1fr); } .nested-group { gap: .25rem; } }
|
|
300
|
+
</style>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CrudOperator, FieldDefinition, Filter } from '@svadmin/core';
|
|
2
|
+
import { type FilterEditorIssue } from './enterprise/filter-tree.js';
|
|
2
3
|
export interface FilterRuleItem {
|
|
3
4
|
id: string;
|
|
4
5
|
field: string;
|
|
@@ -9,9 +10,11 @@ interface Props {
|
|
|
9
10
|
fields?: FieldDefinition[];
|
|
10
11
|
filters?: Filter[];
|
|
11
12
|
logicalOperator?: 'and' | 'or';
|
|
13
|
+
disabled?: boolean;
|
|
12
14
|
class?: string;
|
|
13
15
|
onApply?: (filters: Filter[]) => void;
|
|
14
16
|
onReset?: () => void;
|
|
17
|
+
onInvalid?: (issues: FilterEditorIssue[]) => void;
|
|
15
18
|
}
|
|
16
19
|
declare const FilterBuilder: import("svelte").Component<Props, {
|
|
17
20
|
addRule: () => void;
|