@svadmin/ui 0.70.0 → 0.73.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 +40 -25
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/actions.js +1 -1
- package/dist/app.css +7804 -5188
- package/dist/app.theme.css +7738 -5173
- 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.svelte.d.ts +33 -19
- package/dist/component-registry.svelte.js +6 -4
- package/dist/components/AdminApp.svelte +55 -53
- package/dist/components/AdminApp.svelte.d.ts +2 -2
- package/dist/components/ApiSettings.svelte +19 -1
- package/dist/components/AuditLogDrawer.svelte +14 -3
- package/dist/components/AutoForm.svelte +162 -50
- package/dist/components/AutoForm.svelte.d.ts +4 -0
- package/dist/components/AutoTable.svelte +599 -259
- package/dist/components/AutoTable.svelte.d.ts +6 -0
- package/dist/components/BoundRecordDetailDrawer.svelte +172 -0
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +11 -0
- package/dist/components/Breadcrumbs.svelte +7 -4
- package/dist/components/CancelTaskButton.svelte +9 -5
- package/dist/components/ColumnHeaderFilter.svelte +4 -3
- package/dist/components/ColumnSettings.svelte +1 -0
- package/dist/components/ComboboxField.svelte +118 -36
- package/dist/components/ComboboxField.svelte.d.ts +5 -4
- package/dist/components/CreatePage.svelte +7 -1
- package/dist/components/CreatePage.svelte.d.ts +2 -0
- package/dist/components/DateRangeInput.svelte +75 -0
- package/dist/components/DateRangeInput.svelte.d.ts +25 -0
- package/dist/components/DateTimeInput.svelte +65 -0
- package/dist/components/DateTimeInput.svelte.d.ts +19 -0
- package/dist/components/DetailDrawer.svelte +4 -0
- package/dist/components/DetailDrawer.svelte.d.ts +1 -0
- package/dist/components/DevTools.svelte +8 -21
- package/dist/components/DraggableGrid.svelte +1 -0
- package/dist/components/DraggableHeader.svelte +5 -9
- package/dist/components/DraggableHeader.svelte.d.ts +43 -18
- package/dist/components/DraggableRowTable.svelte +2 -2
- package/dist/components/DynamicFormList.svelte +17 -17
- package/dist/components/DynamicFormList.svelte.d.ts +2 -1
- package/dist/components/EditPage.svelte +7 -1
- package/dist/components/EditPage.svelte.d.ts +2 -0
- package/dist/components/EditableTable.svelte +4 -0
- package/dist/components/ErrorPage.svelte +3 -1
- package/dist/components/FieldDisplay.svelte +14 -0
- package/dist/components/FieldDisplay.svelte.d.ts +7 -0
- package/dist/components/FieldRenderer.svelte +96 -20
- package/dist/components/FileUpload.svelte +250 -0
- package/dist/components/FileUpload.svelte.d.ts +32 -0
- package/dist/components/FilterBuilder.svelte +236 -268
- package/dist/components/FilterBuilder.svelte.d.ts +3 -0
- package/dist/components/FilterDropdown.svelte.d.ts +1 -1
- package/dist/components/Header.svelte +5 -6
- package/dist/components/ImportWizard.svelte +182 -94
- package/dist/components/InfiniteList.svelte +22 -8
- package/dist/components/InfiniteList.svelte.d.ts +5 -3
- package/dist/components/InlineEdit.svelte +114 -44
- package/dist/components/JsonSchemaForm.svelte +256 -95
- package/dist/components/JsonSchemaForm.svelte.d.ts +12 -1
- package/dist/components/Layout.svelte +20 -17
- package/dist/components/LazyTaskQueueDrawer.svelte +26 -0
- package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +8 -0
- package/dist/components/ListPage.svelte +11 -6
- package/dist/components/ListPage.svelte.d.ts +2 -0
- package/dist/components/LoginPage.svelte +1 -1
- package/dist/components/MasterDetailView.svelte +3 -3
- package/dist/components/MediaLibraryModal.svelte +5 -2
- package/dist/components/MentionsInput.svelte +4 -3
- package/dist/components/MoneyInput.svelte +25 -0
- package/dist/components/MoneyInput.svelte.d.ts +20 -0
- package/dist/components/NavigateToResource.svelte +3 -2
- 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 +4 -3
- package/dist/components/ProfilePage.svelte +2 -2
- package/dist/components/QuickEditDrawer.svelte +42 -0
- package/dist/components/QuickEditDrawer.svelte.d.ts +10 -0
- package/dist/components/RecordDetailDrawer.svelte +16 -108
- package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
- package/dist/components/RecordRowActions.svelte +41 -0
- package/dist/components/RecordRowActions.svelte.d.ts +14 -0
- package/dist/components/ResourceAccessGuard.svelte +7 -5
- package/dist/components/ResourceOperationsPage.svelte +25 -19
- package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
- package/dist/components/RetryTaskButton.svelte +9 -5
- package/dist/components/RowActions.svelte +5 -3
- package/dist/components/SensitiveDataMask.svelte +3 -3
- package/dist/components/SettingsPage.svelte +1 -1
- package/dist/components/ShowInferencer.svelte +4 -2
- package/dist/components/ShowInferencer.svelte.d.ts +2 -0
- package/dist/components/ShowPage.svelte +49 -39
- package/dist/components/ShowPage.svelte.d.ts +2 -0
- package/dist/components/Sidebar.svelte +15 -13
- package/dist/components/SplitPaneLayout.svelte +4 -2
- package/dist/components/SpreadsheetView.svelte +81 -286
- package/dist/components/StepsForm.svelte +166 -49
- package/dist/components/StepsForm.svelte.d.ts +1 -0
- 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/TaskDetails.svelte +20 -7
- package/dist/components/TaskList.svelte +19 -7
- package/dist/components/TaskQueueDrawer.svelte +92 -37
- package/dist/components/Toast.svelte +4 -2
- package/dist/components/TooltipButton.svelte +1 -1
- package/dist/components/TreeSelect.svelte +212 -30
- package/dist/components/TreeSelect.svelte.d.ts +3 -1
- package/dist/components/ValidatedField.svelte +27 -0
- package/dist/components/ValidatedField.svelte.d.ts +35 -0
- package/dist/components/account/ImportMembersPage.svelte +2 -2
- package/dist/components/account/SecurityLogPage.svelte +3 -1
- package/dist/components/account/SettingsEnterprisePage.svelte +7 -5
- package/dist/components/account/TeamMembersPage.svelte +1 -1
- package/dist/components/auto-table-grid.d.ts +26 -0
- package/dist/components/auto-table-grid.js +1 -0
- package/dist/components/buttons/CloneButton.svelte +6 -4
- package/dist/components/buttons/DeleteButton.svelte +95 -29
- package/dist/components/buttons/DeleteButton.svelte.d.ts +2 -0
- package/dist/components/buttons/EditButton.svelte +6 -4
- package/dist/components/buttons/ExportButton.svelte +22 -4
- package/dist/components/buttons/ImportButton.svelte +83 -38
- package/dist/components/buttons/ImportButton.svelte.d.ts +6 -10
- package/dist/components/buttons/RefreshButton.svelte +48 -8
- package/dist/components/buttons/ShowButton.svelte +6 -4
- package/dist/components/content/ApiKeyList.svelte +3 -1
- package/dist/components/content/ContentPageHeader.svelte +12 -9
- package/dist/components/content/ContentPageShell.svelte +8 -9
- package/dist/components/content/FileBrowser.svelte +35 -0
- package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
- package/dist/components/content/FileList.svelte +3 -1
- package/dist/components/content/MemberList.svelte +3 -1
- package/dist/components/content/MetricBlock.svelte +10 -13
- package/dist/components/content/NetworkTable.svelte +6 -4
- package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
- package/dist/components/content/PageToolbar.svelte +6 -10
- package/dist/components/content/SectionHeader.svelte +8 -13
- package/dist/components/content/SecurityEventTable.svelte +3 -1
- 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/StatusTabs.svelte +2 -2
- 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/fieldComponentMap.d.ts +18 -13
- package/dist/components/fieldComponentMap.js +100 -47
- package/dist/components/fields/AvatarField.svelte +1 -1
- package/dist/components/fields/ChoiceField.svelte +16 -0
- package/dist/components/fields/ChoiceField.svelte.d.ts +10 -0
- package/dist/components/fields/CodeField.svelte +11 -12
- package/dist/components/fields/CodeField.svelte.d.ts +1 -1
- package/dist/components/fields/DateField.svelte +4 -3
- package/dist/components/fields/DateRangeField.svelte +2 -4
- package/dist/components/fields/ImageField.svelte +3 -1
- package/dist/components/fields/ImagesField.svelte +23 -0
- package/dist/components/fields/ImagesField.svelte.d.ts +6 -0
- package/dist/components/fields/JsonField.svelte +19 -13
- package/dist/components/fields/PasswordField.svelte +5 -0
- package/dist/components/fields/PasswordField.svelte.d.ts +6 -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/fields/json-value.d.ts +11 -0
- package/dist/components/fields/json-value.js +28 -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/profile/ActivityTimeline.svelte +2 -2
- package/dist/components/record-detail.svelte.d.ts +19 -0
- package/dist/components/record-detail.svelte.js +34 -0
- package/dist/components/saved-list-views.js +29 -29
- 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 +13 -0
- package/dist/components/table-contract.js +42 -0
- package/dist/components/task-utils.js +4 -2
- package/dist/components/ui/alert/alert-variants.d.ts +2 -3
- package/dist/components/ui/alert/alert-variants.js +1 -2
- package/dist/components/ui/alert/alert.svelte +4 -5
- package/dist/components/ui/alert/alert.svelte.d.ts +3 -8
- package/dist/components/ui/alert/index.js +0 -2
- package/dist/components/ui/avatar/avatar-variants.d.ts +2 -3
- package/dist/components/ui/avatar/avatar-variants.js +1 -2
- package/dist/components/ui/avatar/avatar.svelte +5 -6
- package/dist/components/ui/avatar/avatar.svelte.d.ts +3 -8
- package/dist/components/ui/badge/badge-variants.d.ts +2 -3
- package/dist/components/ui/badge/badge-variants.js +3 -2
- package/dist/components/ui/badge/badge.svelte +4 -5
- package/dist/components/ui/badge/badge.svelte.d.ts +3 -8
- package/dist/components/ui/badge/index.js +0 -2
- package/dist/components/ui/button/button-variants.d.ts +2 -3
- package/dist/components/ui/button/button-variants.js +5 -3
- package/dist/components/ui/button/button.svelte +100 -44
- package/dist/components/ui/button/button.svelte.d.ts +8 -12
- package/dist/components/ui/button/index.js +0 -2
- package/dist/components/ui/command/command-dialog.svelte +3 -1
- package/dist/components/ui/command/command-dialog.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-group.svelte +4 -2
- package/dist/components/ui/command/command-item.svelte +4 -2
- package/dist/components/ui/command/command-separator.svelte +3 -1
- package/dist/components/ui/command/index.d.ts +5 -5
- package/dist/components/ui/input/input.svelte +41 -24
- package/dist/components/ui/input/input.svelte.d.ts +5 -4
- package/dist/components/ui/sheet/sheet.svelte +9 -3
- package/dist/components/ui/sheet/sheet.svelte.d.ts +1 -0
- package/dist/components/ui/textarea/textarea.svelte +2 -1
- package/dist/components/ui/tooltip/tooltip.svelte +4 -2
- package/dist/components.css +3079 -0
- package/dist/design-contract.d.ts +13 -0
- package/dist/design-contract.js +11 -0
- package/dist/design-system.d.ts +15 -0
- package/dist/design-system.js +7 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.js +9 -0
- package/dist/inline-value.d.ts +5 -0
- package/dist/inline-value.js +24 -0
- package/dist/numeric-input.d.ts +2 -0
- package/dist/numeric-input.js +9 -0
- package/dist/page-chrome.css +12 -49
- package/dist/recipes.d.ts +4 -0
- package/dist/recipes.js +4 -0
- package/dist/rendering/index.d.ts +87 -0
- package/dist/rendering/index.js +117 -0
- package/dist/styled-system/css/conditions.js +36 -0
- package/dist/styled-system/css/css.d.ts +22 -0
- package/dist/styled-system/css/css.js +45 -0
- package/dist/styled-system/css/cva.d.ts +6 -0
- package/dist/styled-system/css/cva.js +87 -0
- package/dist/styled-system/css/cx.d.ts +5 -0
- package/dist/styled-system/css/cx.js +15 -0
- package/dist/styled-system/css/index.d.ts +5 -0
- package/dist/styled-system/css/index.js +4 -0
- package/dist/styled-system/css/sva.d.ts +4 -0
- package/dist/styled-system/css/sva.js +46 -0
- package/dist/styled-system/helpers.js +316 -0
- package/dist/styled-system/patterns/aspect-ratio.d.ts +20 -0
- package/dist/styled-system/patterns/aspect-ratio.js +38 -0
- package/dist/styled-system/patterns/bleed.d.ts +21 -0
- package/dist/styled-system/patterns/bleed.js +24 -0
- package/dist/styled-system/patterns/box.d.ts +20 -0
- package/dist/styled-system/patterns/box.js +15 -0
- package/dist/styled-system/patterns/center.d.ts +20 -0
- package/dist/styled-system/patterns/center.js +21 -0
- package/dist/styled-system/patterns/circle.d.ts +20 -0
- package/dist/styled-system/patterns/circle.js +25 -0
- package/dist/styled-system/patterns/container.d.ts +20 -0
- package/dist/styled-system/patterns/container.js +21 -0
- package/dist/styled-system/patterns/cq.d.ts +21 -0
- package/dist/styled-system/patterns/cq.js +21 -0
- package/dist/styled-system/patterns/divider.d.ts +22 -0
- package/dist/styled-system/patterns/divider.js +25 -0
- package/dist/styled-system/patterns/flex.d.ts +26 -0
- package/dist/styled-system/patterns/flex.js +26 -0
- package/dist/styled-system/patterns/float.d.ts +23 -0
- package/dist/styled-system/patterns/float.js +52 -0
- package/dist/styled-system/patterns/grid-item.d.ts +25 -0
- package/dist/styled-system/patterns/grid-item.js +25 -0
- package/dist/styled-system/patterns/grid.d.ts +24 -0
- package/dist/styled-system/patterns/grid.js +27 -0
- package/dist/styled-system/patterns/hstack.d.ts +21 -0
- package/dist/styled-system/patterns/hstack.js +24 -0
- package/dist/styled-system/patterns/index.d.ts +21 -0
- package/dist/styled-system/patterns/index.js +20 -0
- package/dist/styled-system/patterns/link-overlay.d.ts +20 -0
- package/dist/styled-system/patterns/link-overlay.js +24 -0
- package/dist/styled-system/patterns/spacer.d.ts +20 -0
- package/dist/styled-system/patterns/spacer.js +25 -0
- package/dist/styled-system/patterns/square.d.ts +20 -0
- package/dist/styled-system/patterns/square.js +24 -0
- package/dist/styled-system/patterns/stack.d.ts +23 -0
- package/dist/styled-system/patterns/stack.js +24 -0
- package/dist/styled-system/patterns/visually-hidden.d.ts +20 -0
- package/dist/styled-system/patterns/visually-hidden.js +18 -0
- package/dist/styled-system/patterns/vstack.d.ts +21 -0
- package/dist/styled-system/patterns/vstack.js +24 -0
- package/dist/styled-system/patterns/wrap.d.ts +24 -0
- package/dist/styled-system/patterns/wrap.js +25 -0
- package/dist/styled-system/recipes/content-header.d.ts +31 -0
- package/dist/styled-system/recipes/content-header.js +65 -0
- package/dist/styled-system/recipes/content-page.d.ts +34 -0
- package/dist/styled-system/recipes/content-page.js +43 -0
- package/dist/styled-system/recipes/create-recipe.js +82 -0
- package/dist/styled-system/recipes/index.d.ts +17 -0
- package/dist/styled-system/recipes/index.js +16 -0
- package/dist/styled-system/recipes/metric-block.d.ts +34 -0
- package/dist/styled-system/recipes/metric-block.js +76 -0
- package/dist/styled-system/recipes/product-list.d.ts +31 -0
- package/dist/styled-system/recipes/product-list.js +73 -0
- package/dist/styled-system/recipes/product-section.d.ts +31 -0
- package/dist/styled-system/recipes/product-section.js +49 -0
- package/dist/styled-system/recipes/product-settings-row.d.ts +34 -0
- package/dist/styled-system/recipes/product-settings-row.js +58 -0
- package/dist/styled-system/recipes/product-settings.d.ts +31 -0
- package/dist/styled-system/recipes/product-settings.js +57 -0
- package/dist/styled-system/recipes/product-status.d.ts +34 -0
- package/dist/styled-system/recipes/product-status.js +45 -0
- package/dist/styled-system/recipes/product-toolbar.d.ts +31 -0
- package/dist/styled-system/recipes/product-toolbar.js +41 -0
- package/dist/styled-system/recipes/product-workspace.d.ts +34 -0
- package/dist/styled-system/recipes/product-workspace.js +58 -0
- package/dist/styled-system/recipes/surface-metric.d.ts +38 -0
- package/dist/styled-system/recipes/surface-metric.js +63 -0
- package/dist/styled-system/recipes/surface-table.d.ts +34 -0
- package/dist/styled-system/recipes/surface-table.js +62 -0
- package/dist/styled-system/recipes/ui-badge.d.ts +31 -0
- package/dist/styled-system/recipes/ui-badge.js +38 -0
- package/dist/styled-system/recipes/ui-button.d.ts +32 -0
- package/dist/styled-system/recipes/ui-button.js +43 -0
- package/dist/styled-system/recipes/ui-input.d.ts +31 -0
- package/dist/styled-system/recipes/ui-input.js +49 -0
- package/dist/styled-system/recipes/ui-textarea.d.ts +31 -0
- package/dist/styled-system/recipes/ui-textarea.js +24 -0
- package/dist/styled-system/styles.css +1257 -0
- package/dist/styled-system/tokens/index.d.ts +9 -0
- package/dist/styled-system/tokens/index.js +268 -0
- package/dist/styled-system/tokens/tokens.d.ts +36 -0
- package/dist/styled-system/types/composition.d.ts +227 -0
- package/dist/styled-system/types/conditions.d.ts +236 -0
- package/dist/styled-system/types/csstype.d.ts +22570 -0
- package/dist/styled-system/types/index.d.ts +6 -0
- package/dist/styled-system/types/parts.d.ts +8 -0
- package/dist/styled-system/types/pattern.d.ts +78 -0
- package/dist/styled-system/types/prop-type.d.ts +223 -0
- package/dist/styled-system/types/recipe.d.ts +181 -0
- package/dist/styled-system/types/selectors.d.ts +59 -0
- package/dist/styled-system/types/static-css.d.ts +56 -0
- package/dist/styled-system/types/style-props.d.ts +8088 -0
- package/dist/styled-system/types/system-types.d.ts +151 -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/styles/recipes.css +1262 -0
- package/dist/types.d.ts +3 -3
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/package.json +38 -14
- package/styles-compatibility.json +12 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { bindResourceRendering, type ResourceRendering } from '../rendering/index.js';
|
|
3
|
+
import type { AutoTableGridState } from './auto-table-grid.js';
|
|
4
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
5
|
+
|
|
6
|
+
import { onDestroy, tick, untrack } from 'svelte';
|
|
3
7
|
import { SvelteMap } from 'svelte/reactivity';
|
|
4
8
|
import { cn } from '../utils.js';
|
|
5
9
|
import {
|
|
@@ -21,6 +25,7 @@
|
|
|
21
25
|
import { createAtom, useSelector } from '@tanstack/svelte-store';
|
|
22
26
|
import {
|
|
23
27
|
column_getCanSort,
|
|
28
|
+
column_getSize,
|
|
24
29
|
column_getIsSorted,
|
|
25
30
|
column_toggleSorting,
|
|
26
31
|
header_getSize,
|
|
@@ -28,13 +33,16 @@
|
|
|
28
33
|
table_getRowModel,
|
|
29
34
|
table_getAllLeafColumns,
|
|
30
35
|
table_getIsAllRowsSelected,
|
|
36
|
+
table_resetRowSelection,
|
|
31
37
|
table_toggleAllRowsSelected,
|
|
32
38
|
row_toggleSelected,
|
|
33
39
|
row_getVisibleCells,
|
|
34
40
|
cell_getValue,
|
|
35
41
|
} from '@tanstack/table-core/static-functions';
|
|
36
42
|
|
|
37
|
-
import { captureAdminContext, DeleteManyPartialError, getAdminOptions,
|
|
43
|
+
import { captureAdminContext, DeleteManyPartialError, getAdminOptions, getContractFormFields, useNavigation, useParsed, useResourceContract, useList, useDeleteMany, downloadData } from '@svadmin/core';
|
|
44
|
+
import { decodeBaseRecord, snapshotPlainData, parseContractRouteId, formatContractRouteId } from '@svadmin/core/schema';
|
|
45
|
+
import { checkedTableRows, copyTableRecord, tableExportRows, tableRowKey, type TableRecord } from './table-contract';
|
|
38
46
|
import type {
|
|
39
47
|
BaseRecord,
|
|
40
48
|
FieldDefinition,
|
|
@@ -84,9 +92,12 @@
|
|
|
84
92
|
} from '@lucide/svelte';
|
|
85
93
|
import ConfirmDialog from './ConfirmDialog.svelte';
|
|
86
94
|
import RecordDetailDrawer from './RecordDetailDrawer.svelte';
|
|
95
|
+
import QuickEditDrawer from './QuickEditDrawer.svelte';
|
|
96
|
+
import RecordRowActions from './RecordRowActions.svelte';
|
|
87
97
|
import CanAccess from './CanAccess.svelte';
|
|
88
98
|
import TooltipButton from './TooltipButton.svelte';
|
|
89
99
|
import InlineEdit from './InlineEdit.svelte';
|
|
100
|
+
import FieldDisplay from './FieldDisplay.svelte';
|
|
90
101
|
import DraggableHeader from './DraggableHeader.svelte';
|
|
91
102
|
import DataState from './content/DataState.svelte';
|
|
92
103
|
import type { Snippet } from 'svelte';
|
|
@@ -96,6 +107,9 @@
|
|
|
96
107
|
// ─── Props with Snippet composability ─────────────────────────
|
|
97
108
|
interface Props {
|
|
98
109
|
resourceName: string;
|
|
110
|
+
rendering?: ResourceRendering | undefined;
|
|
111
|
+
/** 可选主体渲染器:不替换资源、权限、工具栏和持久化逻辑。 */
|
|
112
|
+
gridBody?: Snippet<[AutoTableGridState]>;
|
|
99
113
|
selectable?: boolean;
|
|
100
114
|
density?: 'compact' | 'comfortable';
|
|
101
115
|
showHeader?: boolean;
|
|
@@ -123,10 +137,13 @@
|
|
|
123
137
|
batchActions?: Snippet<[{ selectedIds: (string | number)[] }]>;
|
|
124
138
|
/** Summary row rendered at table footer */
|
|
125
139
|
summary?: Snippet<[{ data: BaseRecord[]; total: number; visibleColumnsCount: number }]>;
|
|
140
|
+
deleteVariables?: unknown;
|
|
126
141
|
}
|
|
127
142
|
|
|
128
143
|
let {
|
|
129
144
|
resourceName,
|
|
145
|
+
rendering,
|
|
146
|
+
gridBody,
|
|
130
147
|
selectable = true,
|
|
131
148
|
density = 'comfortable',
|
|
132
149
|
showHeader = true,
|
|
@@ -144,25 +161,28 @@
|
|
|
144
161
|
summary,
|
|
145
162
|
pagination: externalPagination,
|
|
146
163
|
sorters: externalSorters,
|
|
164
|
+
deleteVariables,
|
|
147
165
|
}: Props = $props();
|
|
148
166
|
|
|
149
167
|
let densityOverride = $state<'compact' | 'comfortable' | undefined>(undefined);
|
|
150
168
|
const currentDensity = $derived(densityOverride ?? density);
|
|
151
169
|
const adminContext = captureAdminContext();
|
|
152
|
-
const adminOptions = getAdminOptions();
|
|
153
|
-
const mutationMode = $derived(adminOptions.mutationMode ?? 'pessimistic');
|
|
154
170
|
const parsed = useParsed();
|
|
155
171
|
const navigation = useNavigation();
|
|
156
172
|
|
|
157
|
-
const
|
|
173
|
+
const binding = useResourceContract(() => resourceName);
|
|
174
|
+
const activeRendering = $derived(bindResourceRendering(rendering, binding.resource));
|
|
175
|
+
const resource = $derived(adminContext.getResource(resourceName));
|
|
158
176
|
const primaryKey = $derived(resource.primaryKey ?? 'id');
|
|
177
|
+
const listPermission = useCan(() => ({ resource: resourceName, action: 'list' }));
|
|
178
|
+
const canRead = $derived(listPermission.allowed);
|
|
159
179
|
const listPreferenceScope = $derived.by(() => {
|
|
160
180
|
const matcher = adminContext.queryKeyMatcher(resourceName);
|
|
161
|
-
return {
|
|
181
|
+
return definedOptions({
|
|
162
182
|
resourceName,
|
|
163
183
|
providerName: matcher.provider ?? 'default',
|
|
164
184
|
tenantIdentity: matcher.tenant,
|
|
165
|
-
};
|
|
185
|
+
});
|
|
166
186
|
});
|
|
167
187
|
|
|
168
188
|
function readLocalPreference(key: string): string | null {
|
|
@@ -189,10 +209,10 @@
|
|
|
189
209
|
// ─── URL state + server-side state ────────────────────────────
|
|
190
210
|
const urlState = readURLState(adminContext);
|
|
191
211
|
|
|
192
|
-
const savedViewColumnIds = new Set([
|
|
193
|
-
...
|
|
212
|
+
const savedViewColumnIds = $derived(new Set([
|
|
213
|
+
...resource.fields.map((field) => field.key),
|
|
194
214
|
'_select', '_expand', '_actions',
|
|
195
|
-
]);
|
|
215
|
+
]));
|
|
196
216
|
const storedSavedViews = untrack(() => readSavedListViews(
|
|
197
217
|
readScopedPreference(
|
|
198
218
|
listPreferenceScope,
|
|
@@ -244,7 +264,7 @@
|
|
|
244
264
|
const parsed: unknown = JSON.parse(stored);
|
|
245
265
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
246
266
|
const visibility: ColumnVisibilityState = {};
|
|
247
|
-
for (const [columnId, visible] of Object.entries(parsed
|
|
267
|
+
for (const [columnId, visible] of Object.entries(decodeBaseRecord(parsed))) {
|
|
248
268
|
if (savedViewColumnIds.has(columnId) && typeof visible === 'boolean') {
|
|
249
269
|
visibility[columnId] = visible;
|
|
250
270
|
}
|
|
@@ -271,7 +291,7 @@
|
|
|
271
291
|
try {
|
|
272
292
|
const parsed: unknown = JSON.parse(stored);
|
|
273
293
|
return Array.isArray(parsed)
|
|
274
|
-
? [...new Set(parsed.filter((columnId): columnId is string => (
|
|
294
|
+
? [...new Set(parsed.filter((columnId: unknown): columnId is string => (
|
|
275
295
|
typeof columnId === 'string' && savedViewColumnIds.has(columnId)
|
|
276
296
|
)))]
|
|
277
297
|
: [];
|
|
@@ -301,19 +321,19 @@
|
|
|
301
321
|
legacyActiveSavedListViewStorageKey(resourceName),
|
|
302
322
|
);
|
|
303
323
|
const activeSavedView = scopedSavedViews.find((view) => view.id === candidate);
|
|
304
|
-
return {
|
|
324
|
+
return definedOptions({
|
|
305
325
|
savedViews: scopedSavedViews,
|
|
306
326
|
activeSavedViewId: activeSavedView?.id,
|
|
307
327
|
activeSavedView,
|
|
308
328
|
columnVisibility: readColumnVisibilityPreference(scope),
|
|
309
329
|
columnOrder: readColumnOrderPreference(scope),
|
|
310
|
-
};
|
|
330
|
+
});
|
|
311
331
|
}
|
|
312
332
|
|
|
313
333
|
// Snapshot resource values for initial state (untrack to avoid reactive tracking)
|
|
314
334
|
const storedPageSize = parseInt(readLocalPreference('svadmin-default-page-size') ?? '', 10);
|
|
315
|
-
const initPageSize =
|
|
316
|
-
const initDefaultSort =
|
|
335
|
+
const initPageSize = $derived(resource.pageSize ?? (Number.isSafeInteger(storedPageSize) && storedPageSize > 0 ? storedPageSize : 10));
|
|
336
|
+
const initDefaultSort = $derived(resource.defaultSort);
|
|
317
337
|
|
|
318
338
|
let pagination = $state<PaginationState>(untrack(() => externalPagination ?? {
|
|
319
339
|
current: urlState.page ?? initialViewState?.pagination.current ?? 1,
|
|
@@ -326,10 +346,10 @@
|
|
|
326
346
|
: initialViewState?.sorters ?? (initDefaultSort ? [initDefaultSort] : [])))
|
|
327
347
|
);
|
|
328
348
|
const initialURLFilters = untrack(() => urlState.filters ?? initialViewState?.filters ?? []);
|
|
329
|
-
const editableFilterKeys = new Set(
|
|
349
|
+
const editableFilterKeys = $derived(new Set(resource.fields.filter((field) => field.filterable).map((field) => field.key)));
|
|
330
350
|
const editableFilterCounts: Record<string, number> = {};
|
|
331
351
|
for (const filter of initialURLFilters) {
|
|
332
|
-
if ('field' in filter && filter.operator === 'contains' && typeof filter.value === 'string' && editableFilterKeys.has(filter.field)) {
|
|
352
|
+
if ('field' in filter && filter.operator === 'contains' && typeof filter.value === 'string' && untrack(() => editableFilterKeys.has(filter.field))) {
|
|
333
353
|
editableFilterCounts[filter.field] = (editableFilterCounts[filter.field] ?? 0) + 1;
|
|
334
354
|
}
|
|
335
355
|
}
|
|
@@ -350,7 +370,8 @@
|
|
|
350
370
|
let searchDebounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
351
371
|
|
|
352
372
|
function scheduleSearch(event: Event) {
|
|
353
|
-
|
|
373
|
+
if (!(event.currentTarget instanceof HTMLInputElement)) return;
|
|
374
|
+
searchText = event.currentTarget.value;
|
|
354
375
|
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
|
355
376
|
searchDebounceTimer = setTimeout(() => {
|
|
356
377
|
appliedSearchText = searchText;
|
|
@@ -395,8 +416,9 @@
|
|
|
395
416
|
const activeFilters = $derived.by(() => {
|
|
396
417
|
const result: Filter[] = [...locationFilters];
|
|
397
418
|
if (appliedSearchText.trim() && searchableFields.length > 0) {
|
|
398
|
-
|
|
399
|
-
|
|
419
|
+
const firstSearchableField = searchableFields[0];
|
|
420
|
+
if (searchableFields.length === 1 && firstSearchableField) {
|
|
421
|
+
result.push({ field: firstSearchableField.key, operator: 'contains', value: appliedSearchText });
|
|
400
422
|
} else {
|
|
401
423
|
const searchFilter: LogicalFilter = {
|
|
402
424
|
operator: 'or',
|
|
@@ -426,6 +448,10 @@
|
|
|
426
448
|
|
|
427
449
|
function clearFilters(): void {
|
|
428
450
|
markSavedViewDirty();
|
|
451
|
+
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
|
452
|
+
searchDebounceTimer = undefined;
|
|
453
|
+
searchText = '';
|
|
454
|
+
appliedSearchText = '';
|
|
429
455
|
filters = [];
|
|
430
456
|
filterValues = {};
|
|
431
457
|
pagination = { ...pagination, current: 1 };
|
|
@@ -447,6 +473,12 @@
|
|
|
447
473
|
pagination = { ...pagination, current: 1 };
|
|
448
474
|
}
|
|
449
475
|
|
|
476
|
+
function setFilterFromEvent(field: string, event: Event): void {
|
|
477
|
+
if (event.currentTarget instanceof HTMLInputElement || event.currentTarget instanceof HTMLSelectElement) {
|
|
478
|
+
setFilterValue(field, event.currentTarget.value);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
450
482
|
function removeActiveFilter(index: number): void {
|
|
451
483
|
markSavedViewDirty();
|
|
452
484
|
const filter = locationFilters[index];
|
|
@@ -460,21 +492,16 @@
|
|
|
460
492
|
&& candidate.operator === 'contains'
|
|
461
493
|
&& typeof candidate.value === 'string'
|
|
462
494
|
));
|
|
463
|
-
|
|
464
|
-
|
|
495
|
+
const remainingFilter = remaining[0];
|
|
496
|
+
filterValues[filter.field] = remaining.length === 1 && remainingFilter && 'field' in remainingFilter
|
|
497
|
+
? String(remainingFilter.value)
|
|
465
498
|
: '';
|
|
466
499
|
}
|
|
467
500
|
pagination = { ...pagination, current: 1 };
|
|
468
501
|
}
|
|
469
502
|
|
|
470
503
|
function clonePlainValue(value: unknown): unknown {
|
|
471
|
-
|
|
472
|
-
if (value && typeof value === 'object') {
|
|
473
|
-
return Object.fromEntries(
|
|
474
|
-
Object.entries(value as Record<string, unknown>).map(([key, entry]) => [key, clonePlainValue(entry)])
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
return value;
|
|
504
|
+
return value === undefined ? undefined : snapshotPlainData(value);
|
|
478
505
|
}
|
|
479
506
|
|
|
480
507
|
function cloneFilter(filter: Filter): Filter {
|
|
@@ -491,11 +518,11 @@
|
|
|
491
518
|
};
|
|
492
519
|
}
|
|
493
520
|
|
|
494
|
-
const queryPagination = $derived<PaginationState>({
|
|
521
|
+
const queryPagination = $derived<PaginationState>(definedOptions({
|
|
495
522
|
current: pagination.current,
|
|
496
523
|
pageSize: pagination.pageSize,
|
|
497
524
|
mode: pagination.mode,
|
|
498
|
-
});
|
|
525
|
+
}));
|
|
499
526
|
const querySorters = $derived<Sort[]>(sorters.map(sorter => ({
|
|
500
527
|
field: sorter.field,
|
|
501
528
|
order: sorter.order,
|
|
@@ -504,38 +531,63 @@
|
|
|
504
531
|
|
|
505
532
|
// ─── Data fetching ────────────────────────────────────────────
|
|
506
533
|
const listResult = useList({
|
|
507
|
-
get resource() { return
|
|
534
|
+
get resource() { void activeRendering; return binding.resource; },
|
|
535
|
+
get dataProviderName() { return binding.dataProviderName; },
|
|
536
|
+
get queryOptions() { return { enabled: canRead }; },
|
|
508
537
|
get pagination() { return queryPagination; },
|
|
509
538
|
get sorters() { return querySorters; },
|
|
510
539
|
get filters() { return queryFilters; },
|
|
511
540
|
});
|
|
512
541
|
const query = listResult;
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
542
|
+
const pageRecords = {
|
|
543
|
+
get ok(): boolean {
|
|
544
|
+
if (!canRead) return true;
|
|
545
|
+
try {
|
|
546
|
+
checkedTableRows(activeRendering ? activeRendering.records(query.data?.data ?? []) : query.data?.data ?? []);
|
|
547
|
+
return true;
|
|
548
|
+
} catch {
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
551
|
+
},
|
|
552
|
+
get data(): TableRecord[] {
|
|
553
|
+
if (!canRead) return [];
|
|
554
|
+
try {
|
|
555
|
+
return checkedTableRows(activeRendering ? activeRendering.records(query.data?.data ?? []) : query.data?.data ?? []);
|
|
556
|
+
} catch {
|
|
557
|
+
return [];
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
};
|
|
561
|
+
let deleteRequest = $state<{ ids: (string | number)[]; batch: boolean } | null>(null);
|
|
562
|
+
const deletePermission = useCan(() => definedOptions({
|
|
563
|
+
resource: resourceName, action: 'delete',
|
|
564
|
+
id: deleteRequest?.batch === false ? deleteRequest.ids[0] : undefined,
|
|
565
|
+
params: deleteRequest?.batch ? { ids: deleteRequest.ids } : undefined,
|
|
566
|
+
queryOptions: { enabled: deleteRequest !== null },
|
|
567
|
+
}));
|
|
568
|
+
const deleteAllowed = $derived(canRead && resource.canDelete !== false && deleteRequest !== null && deletePermission.allowed);
|
|
516
569
|
const deleteManyResult = useDeleteMany({
|
|
517
|
-
get resource() { return
|
|
518
|
-
|
|
519
|
-
message: i18n.t('common.batchDeleteSuccess', { count: batchOperationCount }),
|
|
520
|
-
}),
|
|
521
|
-
errorNotification: (error) => ({
|
|
522
|
-
type: 'error',
|
|
523
|
-
message: error instanceof DeleteManyPartialError
|
|
524
|
-
? i18n.t('common.batchDeletePartialFail', { failed: error.failedIds.length, total: batchOperationCount })
|
|
525
|
-
: i18n.t('common.batchDeleteFailed', { count: batchOperationCount }),
|
|
526
|
-
}),
|
|
570
|
+
get resource() { void activeRendering; return binding.resource; },
|
|
571
|
+
get enabled() { return deleteAllowed; },
|
|
527
572
|
});
|
|
528
573
|
const deleteManyMutation = deleteManyResult.mutation;
|
|
529
574
|
|
|
530
575
|
// ─── Permissions ──────────────────────────────────────────────
|
|
531
576
|
const acEnabled = $derived(!!adminContext.accessControlProvider);
|
|
577
|
+
const deleteResourcePermission = useCan(() => ({
|
|
578
|
+
resource: resourceName, action: 'delete', queryOptions: { enabled: acEnabled },
|
|
579
|
+
}));
|
|
532
580
|
const canCreatePerm = useCan(() => ({ resource: resourceName, action: 'create', queryOptions: { enabled: acEnabled } }));
|
|
533
581
|
const canExportPerm = useCan(() => ({ resource: resourceName, action: 'export', queryOptions: { enabled: acEnabled } }));
|
|
534
582
|
const canCreate = $derived(resource.canCreate !== false && (!acEnabled || canCreatePerm.allowed));
|
|
535
583
|
const canEdit = $derived(resource.canEdit !== false);
|
|
584
|
+
const writableFields = $derived.by(() => {
|
|
585
|
+
try { return new Set(getContractFormFields(binding.resource, 'edit')); }
|
|
586
|
+
catch { return new Set<string>(); }
|
|
587
|
+
});
|
|
536
588
|
const canShow = $derived(resource.canShow !== false);
|
|
537
589
|
const canDelete = $derived(resource.canDelete !== false);
|
|
538
|
-
const canExport = $derived(canExportPerm.allowed);
|
|
590
|
+
const canExport = $derived(!acEnabled || canExportPerm.allowed);
|
|
539
591
|
|
|
540
592
|
// ─── TanStack Table state ────────────────────────────────────
|
|
541
593
|
const initialSorting = untrack<SortingState>(() =>
|
|
@@ -546,8 +598,8 @@
|
|
|
546
598
|
));
|
|
547
599
|
const sortingAtom = createAtom(initialSorting);
|
|
548
600
|
const columnVisibilityAtom = createAtom(initialColumnVisibility);
|
|
549
|
-
|
|
550
|
-
const expandedAtom = createAtom({}
|
|
601
|
+
let rowSelection = $state<RowSelectionState>({});
|
|
602
|
+
const expandedAtom = createAtom<ExpandedState>({});
|
|
551
603
|
const initialColumnOrder = untrack(() => (
|
|
552
604
|
initialViewState?.columnOrder?.length
|
|
553
605
|
? initialViewState.columnOrder
|
|
@@ -565,7 +617,6 @@
|
|
|
565
617
|
|
|
566
618
|
const tableSorting = useSelector(sortingAtom);
|
|
567
619
|
const tableColumnVisibility = useSelector(columnVisibilityAtom);
|
|
568
|
-
const tableRowSelection = useSelector(rowSelectionAtom);
|
|
569
620
|
const tableExpanded = useSelector(expandedAtom);
|
|
570
621
|
const tableColumnOrder = useSelector(columnOrderAtom);
|
|
571
622
|
const selectedIdValueByKey = new SvelteMap<string, string | number>();
|
|
@@ -580,15 +631,15 @@
|
|
|
580
631
|
}
|
|
581
632
|
|
|
582
633
|
function rowIsSelected(rowId: string): boolean {
|
|
583
|
-
return
|
|
634
|
+
return rowSelection[rowId] === true;
|
|
584
635
|
}
|
|
585
636
|
|
|
586
|
-
function rowIdValue(row: Row<TableFeatures,
|
|
587
|
-
|
|
588
|
-
return typeof value === 'string' || typeof value === 'number' ? value : row.id;
|
|
637
|
+
function rowIdValue(row: Row<TableFeatures, TableRecord>): string | number {
|
|
638
|
+
return row.original.id;
|
|
589
639
|
}
|
|
590
640
|
|
|
591
|
-
function toggleRowSelection(row: Row<TableFeatures,
|
|
641
|
+
function toggleRowSelection(row: Row<TableFeatures, TableRecord>): void {
|
|
642
|
+
if (confirmPending || !canRead) return;
|
|
592
643
|
const wasSelected = rowIsSelected(row.id);
|
|
593
644
|
if (!wasSelected) {
|
|
594
645
|
selectedIdValueByKey.set(row.id, rowIdValue(row));
|
|
@@ -696,7 +747,8 @@
|
|
|
696
747
|
columnVisibilityAtom.set(state.columnVisibility);
|
|
697
748
|
columnOrderAtom.set(state.columnOrder);
|
|
698
749
|
persistColumnOrder(state.columnOrder);
|
|
699
|
-
|
|
750
|
+
rowSelection = {};
|
|
751
|
+
selectedIdValueByKey.clear();
|
|
700
752
|
}
|
|
701
753
|
|
|
702
754
|
function resetToDefaultListState(): void {
|
|
@@ -733,7 +785,8 @@
|
|
|
733
785
|
if (!hasExplicitURLState) resetToDefaultListState();
|
|
734
786
|
columnVisibilityAtom.set(preferences.columnVisibility);
|
|
735
787
|
columnOrderAtom.set(preferences.columnOrder);
|
|
736
|
-
|
|
788
|
+
rowSelection = {};
|
|
789
|
+
selectedIdValueByKey.clear();
|
|
737
790
|
}
|
|
738
791
|
|
|
739
792
|
loadedPreferenceScopeId = scopeId;
|
|
@@ -785,7 +838,7 @@
|
|
|
785
838
|
columnOrderAtom.set(ids);
|
|
786
839
|
}
|
|
787
840
|
|
|
788
|
-
function toggleColumnSort(column: Column<TableFeatures,
|
|
841
|
+
function toggleColumnSort(column: Column<TableFeatures, TableRecord, unknown>): void {
|
|
789
842
|
markSavedViewDirty();
|
|
790
843
|
column_toggleSorting(column);
|
|
791
844
|
}
|
|
@@ -808,7 +861,7 @@
|
|
|
808
861
|
resource.fields.filter(f => f.showInList !== false)
|
|
809
862
|
);
|
|
810
863
|
|
|
811
|
-
const columns = $derived<ColumnDef<TableFeatures,
|
|
864
|
+
const columns = $derived<ColumnDef<TableFeatures, TableRecord, unknown>[]>([
|
|
812
865
|
// Selection column
|
|
813
866
|
...(selectable && (canDelete || batchActions) ? [{
|
|
814
867
|
id: '_select',
|
|
@@ -816,7 +869,7 @@
|
|
|
816
869
|
cell: () => '',
|
|
817
870
|
size: 40,
|
|
818
871
|
enableSorting: false,
|
|
819
|
-
} satisfies ColumnDef<TableFeatures,
|
|
872
|
+
} satisfies ColumnDef<TableFeatures, TableRecord, unknown>] : []),
|
|
820
873
|
// Expand column
|
|
821
874
|
...(expandedRowRender ? [{
|
|
822
875
|
id: '_expand',
|
|
@@ -824,14 +877,14 @@
|
|
|
824
877
|
cell: () => '',
|
|
825
878
|
size: 40,
|
|
826
879
|
enableSorting: false,
|
|
827
|
-
} satisfies ColumnDef<TableFeatures,
|
|
880
|
+
} satisfies ColumnDef<TableFeatures, TableRecord, unknown>] : []),
|
|
828
881
|
// Data columns
|
|
829
|
-
...visibleFields.map((field): ColumnDef<TableFeatures,
|
|
882
|
+
...visibleFields.map((field): ColumnDef<TableFeatures, TableRecord, unknown> => (definedOptions({
|
|
830
883
|
id: field.key,
|
|
831
884
|
accessorKey: field.key,
|
|
832
885
|
header: () => field.label,
|
|
833
886
|
size: field.width ? parseInt(String(field.width)) : undefined,
|
|
834
|
-
})),
|
|
887
|
+
}))),
|
|
835
888
|
// Actions column
|
|
836
889
|
{
|
|
837
890
|
id: '_actions',
|
|
@@ -856,22 +909,25 @@
|
|
|
856
909
|
});
|
|
857
910
|
|
|
858
911
|
// ─── Create TanStack Table ────────────────────────────────────
|
|
859
|
-
const tbl = createTable<TableFeatures,
|
|
912
|
+
const tbl = createTable<TableFeatures, TableRecord>(
|
|
860
913
|
{
|
|
861
914
|
features,
|
|
862
|
-
get data() { return
|
|
915
|
+
get data() { return pageRecords.data; },
|
|
863
916
|
get columns() { return orderedColumns; },
|
|
864
917
|
manualSorting: true,
|
|
865
|
-
getRowId: (row:
|
|
918
|
+
getRowId: (row: TableRecord) => tableRowKey(row.id),
|
|
866
919
|
state: {
|
|
867
920
|
get sorting() { return tableSorting.current; },
|
|
868
921
|
get columnVisibility() { return tableColumnVisibility.current; },
|
|
922
|
+
get rowSelection() { return rowSelection; },
|
|
869
923
|
},
|
|
870
924
|
onSortingChange: (updater) => {
|
|
871
925
|
sortingAtom.set(typeof updater === 'function' ? updater(tableSorting.current) : updater);
|
|
872
926
|
},
|
|
927
|
+
onRowSelectionChange: (updater) => {
|
|
928
|
+
rowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
|
929
|
+
},
|
|
873
930
|
atoms: {
|
|
874
|
-
rowSelection: rowSelectionAtom,
|
|
875
931
|
expanded: expandedAtom,
|
|
876
932
|
},
|
|
877
933
|
autoResetExpanded: false,
|
|
@@ -882,6 +938,7 @@
|
|
|
882
938
|
);
|
|
883
939
|
|
|
884
940
|
function toggleAllRowsSelection(): void {
|
|
941
|
+
if (confirmPending || !canRead) return;
|
|
885
942
|
const willSelect = !table_getIsAllRowsSelected(tbl);
|
|
886
943
|
for (const row of tableView.rows) {
|
|
887
944
|
if (willSelect) {
|
|
@@ -894,9 +951,13 @@
|
|
|
894
951
|
}
|
|
895
952
|
|
|
896
953
|
const selectedIds = $derived(
|
|
897
|
-
Object.keys(
|
|
954
|
+
Object.keys(rowSelection).filter(key => rowSelection[key] === true)
|
|
955
|
+
.flatMap(key => {
|
|
956
|
+
const id = selectedIdValueByKey.get(key);
|
|
957
|
+
return id === undefined ? [] : [id];
|
|
958
|
+
})
|
|
898
959
|
);
|
|
899
|
-
const selectedCount = $derived(
|
|
960
|
+
const selectedCount = $derived(Object.keys(rowSelection).filter(key => rowSelection[key] === true).length);
|
|
900
961
|
const batchDeletePerm = useCan(() => ({
|
|
901
962
|
resource: resourceName,
|
|
902
963
|
action: 'delete',
|
|
@@ -904,13 +965,13 @@
|
|
|
904
965
|
queryOptions: { enabled: acEnabled && canDelete && selectedIds.length > 0 },
|
|
905
966
|
}));
|
|
906
967
|
const canBatchDelete = $derived(
|
|
907
|
-
canDelete && (!acEnabled ||
|
|
968
|
+
canDelete && (!acEnabled || batchDeletePerm.isLoading || batchDeletePerm.allowed)
|
|
908
969
|
);
|
|
909
970
|
|
|
910
971
|
function clearSelection(): void {
|
|
911
972
|
if (!deleteManyMutation.isPending) {
|
|
912
973
|
selectedIdValueByKey.clear();
|
|
913
|
-
|
|
974
|
+
table_resetRowSelection(tbl, true);
|
|
914
975
|
}
|
|
915
976
|
}
|
|
916
977
|
|
|
@@ -922,7 +983,7 @@
|
|
|
922
983
|
void tableSorting.current;
|
|
923
984
|
void tableColumnVisibility.current;
|
|
924
985
|
void orderedColumns;
|
|
925
|
-
void
|
|
986
|
+
void rowSelection;
|
|
926
987
|
void tableExpanded.current;
|
|
927
988
|
void tableColumnOrder.current;
|
|
928
989
|
const headerGroups = table_getHeaderGroups(tbl);
|
|
@@ -951,7 +1012,8 @@
|
|
|
951
1012
|
expandedAtom.set({ ...current, [rowId]: true });
|
|
952
1013
|
}
|
|
953
1014
|
|
|
954
|
-
const totalPages = $derived(
|
|
1015
|
+
const totalPages = $derived(canRead && pageRecords.ok && !query.isError
|
|
1016
|
+
? Math.ceil((query.data?.total ?? 0) / (pagination.pageSize ?? 10)) : 0);
|
|
955
1017
|
|
|
956
1018
|
// ─── Pagination helpers ───────────────────────────────────────
|
|
957
1019
|
const currentPage = $derived(pagination.current ?? 1);
|
|
@@ -974,68 +1036,149 @@
|
|
|
974
1036
|
// ─── Confirm dialog ───────────────────────────────────────────
|
|
975
1037
|
let confirmOpen = $state(false);
|
|
976
1038
|
let confirmMessage = $state('');
|
|
977
|
-
let confirmAction = $state<() => void>(() => {});
|
|
978
1039
|
let confirmPending = $state(false);
|
|
1040
|
+
let operationError = $state<string | null>(null);
|
|
1041
|
+
let activeDelete: object | undefined;
|
|
979
1042
|
let detailOpenedInHistory = $state(false);
|
|
980
|
-
|
|
1043
|
+
let quickEditId = $state<string | number | undefined>();
|
|
1044
|
+
const tableScope = $derived({
|
|
1045
|
+
contract: binding.resource, resourceName, provider: adminContext.providers?.[binding.dataProviderName],
|
|
1046
|
+
meta: JSON.stringify(binding.meta), tenant: adminContext.tenantCacheKey?.__svadminTenant,
|
|
1047
|
+
auth: adminContext.authProvider, router: adminContext.routerProvider,
|
|
1048
|
+
permissionProvider: adminContext.accessControlProvider, canRead, canDelete,
|
|
1049
|
+
variables: deleteVariables,
|
|
1050
|
+
});
|
|
1051
|
+
let previousTableScope: typeof tableScope | undefined;
|
|
1052
|
+
const detailState = $derived.by(() => {
|
|
1053
|
+
const route = parsed.params['detail'];
|
|
1054
|
+
if (route === undefined || !canRead) return { id: undefined, invalid: false };
|
|
1055
|
+
try { return { id: parseContractRouteId(binding.resource, route), invalid: false }; }
|
|
1056
|
+
catch { return { id: undefined, invalid: true }; }
|
|
1057
|
+
});
|
|
1058
|
+
const detailRecordId = $derived(detailState.id);
|
|
981
1059
|
const detailOpen = $derived(detailRecordId != null);
|
|
1060
|
+
$effect.pre(() => {
|
|
1061
|
+
const scope = tableScope;
|
|
1062
|
+
if (previousTableScope && (
|
|
1063
|
+
previousTableScope.contract !== scope.contract ||
|
|
1064
|
+
previousTableScope.resourceName !== scope.resourceName ||
|
|
1065
|
+
previousTableScope.provider !== scope.provider ||
|
|
1066
|
+
previousTableScope.meta !== scope.meta ||
|
|
1067
|
+
previousTableScope.tenant !== scope.tenant ||
|
|
1068
|
+
previousTableScope.auth !== scope.auth ||
|
|
1069
|
+
previousTableScope.router !== scope.router ||
|
|
1070
|
+
previousTableScope.permissionProvider !== scope.permissionProvider ||
|
|
1071
|
+
previousTableScope.canRead !== scope.canRead ||
|
|
1072
|
+
previousTableScope.canDelete !== scope.canDelete ||
|
|
1073
|
+
previousTableScope.variables !== scope.variables
|
|
1074
|
+
)) {
|
|
1075
|
+
activeDelete = undefined;
|
|
1076
|
+
confirmOpen = false;
|
|
1077
|
+
confirmPending = false;
|
|
1078
|
+
deleteRequest = null;
|
|
1079
|
+
operationError = null;
|
|
1080
|
+
selectedIdValueByKey.clear();
|
|
1081
|
+
rowSelection = {};
|
|
1082
|
+
expandedAtom.set({});
|
|
1083
|
+
detailOpenedInHistory = false;
|
|
1084
|
+
quickEditId = undefined;
|
|
1085
|
+
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
|
1086
|
+
searchDebounceTimer = undefined;
|
|
1087
|
+
if (previousTableScope.resourceName !== scope.resourceName) resetToDefaultListState();
|
|
1088
|
+
}
|
|
1089
|
+
previousTableScope = scope;
|
|
1090
|
+
});
|
|
1091
|
+
$effect(() => {
|
|
1092
|
+
if (deleteRequest && !deletePermission.isLoading && !deletePermission.allowed) {
|
|
1093
|
+
confirmOpen = false;
|
|
1094
|
+
deleteRequest = null;
|
|
1095
|
+
operationError = i18n.t('common.operationFailed');
|
|
1096
|
+
}
|
|
1097
|
+
});
|
|
1098
|
+
onDestroy(() => { activeDelete = undefined; });
|
|
982
1099
|
|
|
983
1100
|
function confirmDelete(id: string | number) {
|
|
1101
|
+
if (!canRead || !pageRecords.ok || query.isError || !canDelete || confirmPending) return;
|
|
984
1102
|
confirmMessage = i18n.t('common.deleteConfirm');
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
if (mutationMode !== 'pessimistic') confirmOpen = false;
|
|
988
|
-
try {
|
|
989
|
-
await deleteMutation.mutateAsync({
|
|
990
|
-
id,
|
|
991
|
-
resource: resourceName,
|
|
992
|
-
onCancel: () => { confirmOpen = false; },
|
|
993
|
-
});
|
|
994
|
-
confirmOpen = false;
|
|
995
|
-
} catch {
|
|
996
|
-
// useDelete owns error feedback and restores optimistic cache state.
|
|
997
|
-
} finally {
|
|
998
|
-
confirmPending = false;
|
|
999
|
-
}
|
|
1000
|
-
};
|
|
1103
|
+
deleteRequest = { ids: [id], batch: false };
|
|
1104
|
+
operationError = null;
|
|
1001
1105
|
confirmOpen = true;
|
|
1002
1106
|
}
|
|
1003
1107
|
|
|
1004
1108
|
function confirmBatchDelete() {
|
|
1005
1109
|
const ids = [...selectedIds];
|
|
1006
|
-
if (ids.length === 0 ||
|
|
1007
|
-
batchOperationCount = ids.length;
|
|
1110
|
+
if (!canRead || !pageRecords.ok || query.isError || !canBatchDelete || ids.length === 0 || confirmPending) return;
|
|
1008
1111
|
confirmMessage = i18n.t('common.batchDeleteConfirm', { count: ids.length });
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1112
|
+
deleteRequest = { ids, batch: true };
|
|
1113
|
+
operationError = null;
|
|
1114
|
+
confirmOpen = true;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async function confirmAction() {
|
|
1118
|
+
if (!confirmOpen || !pageRecords.ok || query.isError || !deleteAllowed || confirmPending || !deleteRequest) return;
|
|
1119
|
+
const request = deleteRequest;
|
|
1120
|
+
if (request.batch && JSON.stringify(request.ids) !== JSON.stringify(selectedIds)) {
|
|
1121
|
+
confirmOpen = false;
|
|
1122
|
+
deleteRequest = null;
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const scope = tableScope;
|
|
1126
|
+
const token = {};
|
|
1127
|
+
activeDelete = token;
|
|
1128
|
+
confirmPending = true;
|
|
1129
|
+
confirmOpen = false;
|
|
1130
|
+
const clearBatchSelectionBeforeMutation = request.batch && getAdminOptions().mutationMode === 'undoable';
|
|
1131
|
+
const selectionBeforeDelete = clearBatchSelectionBeforeMutation ? new Map(selectedIdValueByKey) : undefined;
|
|
1132
|
+
if (clearBatchSelectionBeforeMutation) {
|
|
1133
|
+
selectedIdValueByKey.clear();
|
|
1134
|
+
table_resetRowSelection(tbl, true);
|
|
1135
|
+
}
|
|
1136
|
+
const current = () => activeDelete === token &&
|
|
1137
|
+
tableScope.contract === scope.contract &&
|
|
1138
|
+
tableScope.resourceName === scope.resourceName &&
|
|
1139
|
+
tableScope.provider === scope.provider &&
|
|
1140
|
+
tableScope.meta === scope.meta &&
|
|
1141
|
+
tableScope.tenant === scope.tenant &&
|
|
1142
|
+
tableScope.auth === scope.auth &&
|
|
1143
|
+
tableScope.router === scope.router &&
|
|
1144
|
+
tableScope.permissionProvider === scope.permissionProvider;
|
|
1145
|
+
try {
|
|
1146
|
+
await deleteManyMutation.mutateAsync({
|
|
1147
|
+
ids: [...request.ids],
|
|
1148
|
+
...definedOptions({ variables: deleteVariables, dataProviderName: binding.dataProviderName }),
|
|
1149
|
+
});
|
|
1150
|
+
if (!current()) return;
|
|
1151
|
+
selectedIdValueByKey.clear();
|
|
1152
|
+
table_resetRowSelection(tbl, true);
|
|
1153
|
+
await tick();
|
|
1154
|
+
confirmOpen = false;
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
if (!current()) return;
|
|
1157
|
+
if (error instanceof DeleteManyPartialError) {
|
|
1158
|
+
rowSelection = Object.fromEntries(error.failedIds.map(id => [tableRowKey(id), true as const]));
|
|
1018
1159
|
selectedIdValueByKey.clear();
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1024
|
-
for (const id of
|
|
1025
|
-
|
|
1026
|
-
rowSelectionAtom.set(failedSelection);
|
|
1160
|
+
for (const id of error.failedIds) selectedIdValueByKey.set(tableRowKey(id), id);
|
|
1161
|
+
operationError = i18n.t('common.batchDeletePartialFail', { failed: error.failedIds.length, total: request.ids.length });
|
|
1162
|
+
} else {
|
|
1163
|
+
if (selectionBeforeDelete) {
|
|
1164
|
+
selectedIdValueByKey.clear();
|
|
1165
|
+
for (const [key, id] of selectionBeforeDelete) selectedIdValueByKey.set(key, id);
|
|
1166
|
+
rowSelection = Object.fromEntries([...selectionBeforeDelete.keys()].map(key => [key, true as const]));
|
|
1027
1167
|
}
|
|
1028
|
-
|
|
1029
|
-
} finally {
|
|
1030
|
-
confirmPending = false;
|
|
1168
|
+
operationError = i18n.t('common.operationFailed');
|
|
1031
1169
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1170
|
+
confirmOpen = false;
|
|
1171
|
+
} finally {
|
|
1172
|
+
if (activeDelete === token) { activeDelete = undefined; confirmPending = false; deleteRequest = null; }
|
|
1173
|
+
await tick();
|
|
1174
|
+
await tick();
|
|
1175
|
+
}
|
|
1034
1176
|
}
|
|
1035
1177
|
|
|
1036
1178
|
function openDetail(id: string | number): void {
|
|
1179
|
+
if (!canRead || !canShow) return;
|
|
1037
1180
|
detailOpenedInHistory = true;
|
|
1038
|
-
writeURLState({ detailId:
|
|
1181
|
+
writeURLState({ detailId: formatContractRouteId(binding.resource, id) }, adminContext, 'push');
|
|
1039
1182
|
}
|
|
1040
1183
|
|
|
1041
1184
|
function closeDetail(): void {
|
|
@@ -1050,33 +1193,257 @@
|
|
|
1050
1193
|
|
|
1051
1194
|
// ─── CSV Export ───────────────────────────────────────────────
|
|
1052
1195
|
function exportCSV() {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
);
|
|
1064
|
-
const csv = [headers.map(h => `"${h.replace(/"/g, '""')}"`).join(','), ...rows.map(r => r.map(v => `"${v.replace(/"/g, '""')}"`).join(','))].join('\n');
|
|
1065
|
-
const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
|
|
1066
|
-
const link = document.createElement('a');
|
|
1067
|
-
link.href = URL.createObjectURL(blob);
|
|
1068
|
-
link.download = `${resourceName}_${new Date().toISOString().slice(0, 10)}.csv`;
|
|
1069
|
-
link.click();
|
|
1070
|
-
setTimeout(() => URL.revokeObjectURL(link.href), 1000);
|
|
1196
|
+
if (!canRead || !canExport || !pageRecords.ok || query.isError || query.isFetching || !pageRecords.data.length) return;
|
|
1197
|
+
try {
|
|
1198
|
+
const exportFields = table_getAllLeafColumns(tbl).flatMap(column => {
|
|
1199
|
+
const field = visibleFields.find(field => field.key === column.id);
|
|
1200
|
+
return field && isColumnVisible(field.key) ? [field] : [];
|
|
1201
|
+
});
|
|
1202
|
+
if (!exportFields.length) return;
|
|
1203
|
+
const rows = tableExportRows(pageRecords.data, exportFields);
|
|
1204
|
+
downloadData(rows, resourceName, 'csv');
|
|
1205
|
+
} catch { operationError = i18n.t('common.operationFailed'); }
|
|
1071
1206
|
}
|
|
1072
1207
|
|
|
1073
1208
|
function goToPage(page: number) {
|
|
1209
|
+
if (!Number.isSafeInteger(page) || page < 1 || page > Math.max(1, totalPages)) return;
|
|
1074
1210
|
markSavedViewDirty();
|
|
1075
1211
|
pagination = { ...pagination, current: page };
|
|
1076
1212
|
}
|
|
1213
|
+
function refreshList() {
|
|
1214
|
+
if (canRead && !query.isFetching) void listResult.refetch();
|
|
1215
|
+
}
|
|
1216
|
+
const gridRowIndex = $derived(new Map(tableView.rows.map(row => [rowIdValue(row), row])));
|
|
1217
|
+
const gridColumns = $derived.by(() => {
|
|
1218
|
+
void tableColumnOrder.current; void tableColumnVisibility.current;
|
|
1219
|
+
return table_getAllLeafColumns(tbl).filter(column => isColumnVisible(column.id)).map(column => ({
|
|
1220
|
+
key: column.id,
|
|
1221
|
+
label: column.id === '_actions' ? i18n.t('common.actions') : column.id === '_select' ? i18n.t('common.selectAll') : column.id === '_expand' ? i18n.t('common.expand') : visibleFields.find(field => field.key === column.id)?.label ?? column.id,
|
|
1222
|
+
width: Math.max(40, Math.min(4096, column_getSize(column))),
|
|
1223
|
+
sortable: column_getCanSort(column),
|
|
1224
|
+
}));
|
|
1225
|
+
});
|
|
1226
|
+
function changeGridSort(next: readonly Sort[]): void {
|
|
1227
|
+
if (!canRead || query.isFetching || deleteManyMutation.isPending) return;
|
|
1228
|
+
markSavedViewDirty();
|
|
1229
|
+
const accepted = next.filter(sort => gridColumns.some(column => column.key === sort.field && column.sortable));
|
|
1230
|
+
sorters = accepted.map(sort => ({ ...sort }));
|
|
1231
|
+
sortingAtom.set(accepted.map(sort => ({ id: sort.field, desc: sort.order === 'desc' })));
|
|
1232
|
+
pagination = { ...pagination, current: 1 };
|
|
1233
|
+
}
|
|
1077
1234
|
</script>
|
|
1078
1235
|
|
|
1079
|
-
|
|
1236
|
+
{#snippet renderGridCell({ id, field }: { id: string | number; field: string })}
|
|
1237
|
+
{@const row = gridRowIndex.get(id)}
|
|
1238
|
+
{#if row}
|
|
1239
|
+
{@const record = copyTableRecord(row.original)}
|
|
1240
|
+
{@const cell = row_getVisibleCells(row).find(item => item.column.id === field)}
|
|
1241
|
+
{#if cell}
|
|
1242
|
+
<ContextMenu.Root>
|
|
1243
|
+
<ContextMenu.Trigger>
|
|
1244
|
+
{#snippet child({ props })}
|
|
1245
|
+
<div {...props}>
|
|
1246
|
+
{#if cell.column.id === '_select'}
|
|
1247
|
+
{#if batchActions}
|
|
1248
|
+
<Checkbox
|
|
1249
|
+
aria-label={i18n.t('common.selectRow', { id })}
|
|
1250
|
+
checked={rowIsSelected(row.id)}
|
|
1251
|
+
onCheckedChange={() => toggleRowSelection(row)}
|
|
1252
|
+
/>
|
|
1253
|
+
{:else}
|
|
1254
|
+
<CanAccess resource={resourceName} action="delete" params={{ id }}>
|
|
1255
|
+
<Checkbox
|
|
1256
|
+
aria-label={i18n.t('common.selectRow', { id })}
|
|
1257
|
+
checked={rowIsSelected(row.id)}
|
|
1258
|
+
onCheckedChange={() => toggleRowSelection(row)}
|
|
1259
|
+
/>
|
|
1260
|
+
</CanAccess>
|
|
1261
|
+
{/if}
|
|
1262
|
+
{:else if cell.column.id === '_expand'}
|
|
1263
|
+
<TooltipButton tooltip={rowIsExpanded(row.id) ? i18n.t('common.collapse') : i18n.t('common.expand')} variant="ghost" size="icon" class="svadmin-u-d0a52b312f7d svadmin-u-cbbf90f9a828" onclick={() => toggleRowExpanded(row.id)}>
|
|
1264
|
+
{#if rowIsExpanded(row.id)}
|
|
1265
|
+
<ChevronUp class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1266
|
+
{:else}
|
|
1267
|
+
<ChevronDown class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1268
|
+
{/if}
|
|
1269
|
+
</TooltipButton>
|
|
1270
|
+
{:else if cell.column.id === '_actions'}
|
|
1271
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77c08e015d14 svadmin-u-44ee8ba0a421">
|
|
1272
|
+
{#if rowActions}
|
|
1273
|
+
{@render rowActions({ record, id })}
|
|
1274
|
+
{:else}
|
|
1275
|
+
{@render defaultRowActions(id)}
|
|
1276
|
+
{/if}
|
|
1277
|
+
</div>
|
|
1278
|
+
{:else}
|
|
1279
|
+
{@const field = visibleFields.find(f => f.key === cell.column.id)}
|
|
1280
|
+
{@const customColumn = field ? customColumns?.[field.key] : undefined}
|
|
1281
|
+
{#if customColumn}
|
|
1282
|
+
{@render customColumn({ value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
|
|
1283
|
+
{:else if defaultCellRenderer && field}
|
|
1284
|
+
{@render defaultCellRenderer({ field, value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
|
|
1285
|
+
{:else if canEdit && field && field.showInEdit !== false && writableFields.has(field.key) && field.key !== primaryKey && field.key !== 'id' && ['text', 'number', 'email', 'url'].includes(field.type)}
|
|
1286
|
+
<CanAccess resource={resourceName} action="edit" params={{ id }}>
|
|
1287
|
+
<InlineEdit
|
|
1288
|
+
{resourceName}
|
|
1289
|
+
recordId={id}
|
|
1290
|
+
{field}
|
|
1291
|
+
value={cell_getValue(cell)}
|
|
1292
|
+
/>
|
|
1293
|
+
{#snippet fallback()}
|
|
1294
|
+
<FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} />
|
|
1295
|
+
{/snippet}
|
|
1296
|
+
</CanAccess>
|
|
1297
|
+
{:else if field?.key === 'id'}
|
|
1298
|
+
<span class="svadmin-u-0214b4b355d1 svadmin-u-f283ea9bea0e svadmin-u-0e65706bcccd svadmin-u-359090c2d529" title={String(cell_getValue(cell) ?? '—')}>{cell_getValue(cell) ?? '—'}</span>
|
|
1299
|
+
{:else if field}
|
|
1300
|
+
<FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} resourceName={field.resource} />
|
|
1301
|
+
{/if}
|
|
1302
|
+
{/if}
|
|
1303
|
+
</div>
|
|
1304
|
+
{/snippet}
|
|
1305
|
+
</ContextMenu.Trigger>
|
|
1306
|
+
<ContextMenu.Content class="svadmin-u-74b2435a1d40">
|
|
1307
|
+
{#if canEdit}
|
|
1308
|
+
<CanAccess resource={resourceName} action="edit" params={{ id }}>
|
|
1309
|
+
<ContextMenu.Item onclick={() => navigation.edit(resourceName, id)} class="svadmin-u-77a2a20e90d4">
|
|
1310
|
+
<Pencil class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.edit')}
|
|
1311
|
+
</ContextMenu.Item>
|
|
1312
|
+
</CanAccess>
|
|
1313
|
+
{/if}
|
|
1314
|
+
{#if canShow}
|
|
1315
|
+
<CanAccess resource={resourceName} action="show" params={{ id }}>
|
|
1316
|
+
<ContextMenu.Item onclick={() => openDetail(id)} class="svadmin-u-77a2a20e90d4">
|
|
1317
|
+
<Eye class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.detail')}
|
|
1318
|
+
</ContextMenu.Item>
|
|
1319
|
+
</CanAccess>
|
|
1320
|
+
{/if}
|
|
1321
|
+
<ContextMenu.Item onclick={() => navigator.clipboard?.writeText(String(id))} class="svadmin-u-77a2a20e90d4">
|
|
1322
|
+
<Copy class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.copyId')}
|
|
1323
|
+
</ContextMenu.Item>
|
|
1324
|
+
{#if canDelete}
|
|
1325
|
+
<CanAccess resource={resourceName} action="delete" params={{ id }}>
|
|
1326
|
+
<ContextMenu.Separator />
|
|
1327
|
+
<ContextMenu.Item onclick={() => confirmDelete(id)} class="svadmin-u-77a2a20e90d4 svadmin-u-811148b13d1e">
|
|
1328
|
+
<Trash2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.delete')}
|
|
1329
|
+
</ContextMenu.Item>
|
|
1330
|
+
</CanAccess>
|
|
1331
|
+
{/if}
|
|
1332
|
+
</ContextMenu.Content>
|
|
1333
|
+
</ContextMenu.Root>
|
|
1334
|
+
{/if}
|
|
1335
|
+
{/if}
|
|
1336
|
+
{/snippet}
|
|
1337
|
+
|
|
1338
|
+
{#snippet renderGridControls()}
|
|
1339
|
+
<Table.Root density={currentDensity}>
|
|
1340
|
+
<Table.Header>
|
|
1341
|
+
{#each tableView.headerGroups as headerGroup, _i (_i)}
|
|
1342
|
+
{@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
|
|
1343
|
+
<DraggableHeader
|
|
1344
|
+
columns={visibleHeaders.map((header) => ({ id: header.column.id, header }))}
|
|
1345
|
+
onReorder={setColumnOrder}
|
|
1346
|
+
>
|
|
1347
|
+
{#snippet header(col, _index, dragProps)}
|
|
1348
|
+
{@const header = col.header}
|
|
1349
|
+
<Table.Head
|
|
1350
|
+
{...dragProps}
|
|
1351
|
+
class={cn('svadmin-u-65fdbade2025 svadmin-u-18049387f0af svadmin-u-b247a17a0d75 svadmin-u-2689f3958069 svadmin-u-d9256981a032 svadmin-u-bfa603190748 svadmin-u-f6e31b39b8e4', dragProps.class)}
|
|
1352
|
+
style={header_getSize(header) != null && header_getSize(header) !== 150 ? `width:${header_getSize(header)}px` : undefined}
|
|
1353
|
+
>
|
|
1354
|
+
{#if header.id === '_select'}
|
|
1355
|
+
{#if batchActions}
|
|
1356
|
+
<Checkbox
|
|
1357
|
+
aria-label={i18n.t('common.selectAll')}
|
|
1358
|
+
checked={table_getIsAllRowsSelected(tbl)}
|
|
1359
|
+
onCheckedChange={toggleAllRowsSelection}
|
|
1360
|
+
/>
|
|
1361
|
+
{:else if !acEnabled || deleteResourcePermission.isLoading || deleteResourcePermission.allowed}
|
|
1362
|
+
<Checkbox
|
|
1363
|
+
aria-label={i18n.t('common.selectAll')}
|
|
1364
|
+
checked={table_getIsAllRowsSelected(tbl)}
|
|
1365
|
+
onCheckedChange={toggleAllRowsSelection}
|
|
1366
|
+
/>
|
|
1367
|
+
{/if}
|
|
1368
|
+
{:else if header.id === '_expand'}
|
|
1369
|
+
<!-- empty -->
|
|
1370
|
+
{:else if header.id === '_actions'}
|
|
1371
|
+
<span class="svadmin-u-308fc069e46e svadmin-u-0214b4b355d1">{i18n.t('common.actions')}</span>
|
|
1372
|
+
{:else if column_getCanSort(header.column)}
|
|
1373
|
+
<Button
|
|
1374
|
+
variant="ghost"
|
|
1375
|
+
size="sm"
|
|
1376
|
+
class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-44ee8ba0a421 svadmin-u-ea7b2e9e070e svadmin-u-8ffe7a29179a svadmin-u-b8f0a08ece1e svadmin-u-660d2effb880 svadmin-u-d5eab218aa34 uppercase svadmin-u-8baf13a3e9d7 svadmin-u-7357df2b2e0c svadmin-u-e83a7042bc91"
|
|
1377
|
+
onclick={() => toggleColumnSort(header.column)}
|
|
1378
|
+
>
|
|
1379
|
+
{visibleFields.find(f => f.key === header.id)?.label ?? header.id}
|
|
1380
|
+
<span class="svadmin-u-359090c2d529 svadmin-u-0b8c506a0596">
|
|
1381
|
+
{#if column_getIsSorted(header.column) === 'asc'}↑
|
|
1382
|
+
{:else if column_getIsSorted(header.column) === 'desc'}↓
|
|
1383
|
+
{:else}⇅
|
|
1384
|
+
{/if}
|
|
1385
|
+
</span>
|
|
1386
|
+
</Button>
|
|
1387
|
+
{:else}
|
|
1388
|
+
{visibleFields.find(f => f.key === header.id)?.label ?? header.id}
|
|
1389
|
+
{/if}
|
|
1390
|
+
</Table.Head>
|
|
1391
|
+
{/snippet}
|
|
1392
|
+
</DraggableHeader>
|
|
1393
|
+
{/each}
|
|
1394
|
+
</Table.Header>
|
|
1395
|
+
</Table.Root>
|
|
1396
|
+
{/snippet}
|
|
1397
|
+
|
|
1398
|
+
{#snippet renderGridAfter()}
|
|
1399
|
+
<Table.Root density={currentDensity}>
|
|
1400
|
+
<Table.Body>
|
|
1401
|
+
{#each tableView.rows as row (row.id)}
|
|
1402
|
+
{#if expandedRowRender && rowIsExpanded(row.id)}
|
|
1403
|
+
<Table.Row><Table.Cell colspan={Math.max(1, gridColumns.length)}>
|
|
1404
|
+
{@render expandedRowRender({ record: copyTableRecord(row.original) })}
|
|
1405
|
+
</Table.Cell></Table.Row>
|
|
1406
|
+
{/if}
|
|
1407
|
+
{/each}
|
|
1408
|
+
</Table.Body>
|
|
1409
|
+
{#if summary}<Table.Footer>
|
|
1410
|
+
{@render summary({ data: pageRecords.data.map(copyTableRecord), total: query.data?.total ?? 0, visibleColumnsCount: gridColumns.length })}
|
|
1411
|
+
</Table.Footer>{/if}
|
|
1412
|
+
</Table.Root>
|
|
1413
|
+
{/snippet}
|
|
1414
|
+
|
|
1415
|
+
{#snippet defaultRowActions(id: string | number)}
|
|
1416
|
+
<RecordRowActions
|
|
1417
|
+
{resourceName} {id} {canShow} {canEdit} {canDelete}
|
|
1418
|
+
onShow={() => openDetail(id)}
|
|
1419
|
+
onEdit={() => navigation.edit(resourceName, id)}
|
|
1420
|
+
onQuickEdit={() => quickEditId = id}
|
|
1421
|
+
onDelete={() => confirmDelete(id)}
|
|
1422
|
+
/>
|
|
1423
|
+
{/snippet}
|
|
1424
|
+
|
|
1425
|
+
{#snippet defaultEmptyState()}
|
|
1426
|
+
{@const hasCriteria = !!(searchText.trim() || appliedSearchText.trim() || activeFilterCount > 0)}
|
|
1427
|
+
<DataState state="empty" description={i18n.t(hasCriteria ? 'empty.description' : 'common.noDataHint')}>
|
|
1428
|
+
{#snippet action()}
|
|
1429
|
+
{#if hasCriteria}
|
|
1430
|
+
<Button variant="outline" size="sm" onclick={clearFilters}>
|
|
1431
|
+
<X class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1432
|
+
{i18n.t('common.clearAllFilters')}
|
|
1433
|
+
</Button>
|
|
1434
|
+
{:else if canCreate}
|
|
1435
|
+
<Button variant="outline" size="sm" onclick={() => navigation.create(resourceName)}>
|
|
1436
|
+
<Plus class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1437
|
+
{i18n.t('common.create')}
|
|
1438
|
+
</Button>
|
|
1439
|
+
{/if}
|
|
1440
|
+
{/snippet}
|
|
1441
|
+
</DataState>
|
|
1442
|
+
{/snippet}
|
|
1443
|
+
|
|
1444
|
+
<div class="svadmin-u-6ed543e2fbbb" data-svadmin-rendering-resource={activeRendering?.resource.name} data-svadmin-rendering-kind={activeRendering ? 'table' : undefined}>
|
|
1445
|
+
{#if operationError}<p role="alert">{operationError}</p>{/if}
|
|
1446
|
+
{#if detailState.invalid}<p role="alert">{i18n.t('common.operationFailed')}</p>{/if}
|
|
1080
1447
|
{#if showHeader}
|
|
1081
1448
|
<!-- Header -->
|
|
1082
1449
|
<div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-77a2a20e90d4">
|
|
@@ -1101,7 +1468,7 @@
|
|
|
1101
1468
|
|
|
1102
1469
|
<!-- Selection Banner (Enterprise Batch Actions) -->
|
|
1103
1470
|
{#if selectedCount > 0}
|
|
1104
|
-
|
|
1471
|
+
<div
|
|
1105
1472
|
class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-1004c0c3954c svadmin-u-ca6bcd4b6f3f svadmin-u-a6afccfc915b svadmin-u-375dc44df6e9 svadmin-u-e0d9cc7f0647 svadmin-u-03b4dd7f172b svadmin-u-5f22e64f2282 svadmin-u-fc7473ca09eb svadmin-u-d4108abe6359 svadmin-u-40137e897961 fade-in svadmin-u-625a4c3fbeb2 svadmin-u-259ce51fc8f3 svadmin-u-020ba687fa12 svadmin-u-9f76a62f4f44 svadmin-u-3b9871a0bf93"
|
|
1106
1473
|
aria-label={i18n.t("common.selectedCount", { count: selectedCount })}
|
|
1107
1474
|
data-svadmin-batch-toolbar
|
|
@@ -1181,7 +1548,7 @@
|
|
|
1181
1548
|
id="filter-{field.key}"
|
|
1182
1549
|
class="svadmin-u-e7a768f922d2 svadmin-u-fc7473ca09eb"
|
|
1183
1550
|
value={filterValues[field.key] ?? ""}
|
|
1184
|
-
onchange={(e) =>
|
|
1551
|
+
onchange={(e: Event) => setFilterFromEvent(field.key, e)}
|
|
1185
1552
|
>
|
|
1186
1553
|
<option value="">{i18n.t("common.all")}</option>
|
|
1187
1554
|
{#each field.options as opt, _i (_i)}
|
|
@@ -1193,7 +1560,7 @@
|
|
|
1193
1560
|
id="filter-{field.key}"
|
|
1194
1561
|
type="text"
|
|
1195
1562
|
value={filterValues[field.key] ?? ""}
|
|
1196
|
-
oninput={(e) =>
|
|
1563
|
+
oninput={(e: Event) => setFilterFromEvent(field.key, e)}
|
|
1197
1564
|
placeholder={field.label}
|
|
1198
1565
|
class="svadmin-u-e7a768f922d2 svadmin-u-fc7473ca09eb"
|
|
1199
1566
|
/>
|
|
@@ -1225,7 +1592,7 @@
|
|
|
1225
1592
|
{#if showDensitySwitcher}
|
|
1226
1593
|
<DropdownMenu.Root>
|
|
1227
1594
|
<DropdownMenu.Trigger>
|
|
1228
|
-
{#snippet child({ props })}
|
|
1595
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
1229
1596
|
<TooltipButton tooltip={i18n.t("common.density")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props}>
|
|
1230
1597
|
<Rows class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" aria-hidden="true" />
|
|
1231
1598
|
</TooltipButton>
|
|
@@ -1245,7 +1612,7 @@
|
|
|
1245
1612
|
<!-- Column Visibility Picker -->
|
|
1246
1613
|
<DropdownMenu.Root>
|
|
1247
1614
|
<DropdownMenu.Trigger>
|
|
1248
|
-
{#snippet child({ props })}
|
|
1615
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
1249
1616
|
<TooltipButton tooltip={i18n.t("common.columns")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props}>
|
|
1250
1617
|
<SlidersHorizontal class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1251
1618
|
</TooltipButton>
|
|
@@ -1255,7 +1622,7 @@
|
|
|
1255
1622
|
{#each table_getAllLeafColumns(tbl).filter((column) => !column.id.startsWith("_")) as column, _i (_i)}
|
|
1256
1623
|
<DropdownMenu.CheckboxItem
|
|
1257
1624
|
checked={tableColumnVisibility.current[column.id] ?? true}
|
|
1258
|
-
onCheckedChange={(v) => setColumnVisibility(column.id,
|
|
1625
|
+
onCheckedChange={(v: boolean) => setColumnVisibility(column.id, v)}
|
|
1259
1626
|
>
|
|
1260
1627
|
{visibleFields.find(f => f.key === column.id)?.label ?? column.id}
|
|
1261
1628
|
</DropdownMenu.CheckboxItem>
|
|
@@ -1284,8 +1651,9 @@
|
|
|
1284
1651
|
id="saved-list-view"
|
|
1285
1652
|
class="svadmin-u-e7a768f922d2 svadmin-u-6da6a3c3f741"
|
|
1286
1653
|
value={activeSavedViewId ?? ""}
|
|
1287
|
-
onchange={(event) => {
|
|
1288
|
-
|
|
1654
|
+
onchange={(event: Event) => {
|
|
1655
|
+
if (!(event.currentTarget instanceof HTMLSelectElement)) return;
|
|
1656
|
+
const id = event.currentTarget.value;
|
|
1289
1657
|
const view = savedViews.find((candidate) => candidate.id === id);
|
|
1290
1658
|
if (view) applySavedView(view);
|
|
1291
1659
|
else {
|
|
@@ -1340,7 +1708,7 @@
|
|
|
1340
1708
|
variant="outline"
|
|
1341
1709
|
size="sm"
|
|
1342
1710
|
class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd"
|
|
1343
|
-
onclick={
|
|
1711
|
+
onclick={refreshList}
|
|
1344
1712
|
>
|
|
1345
1713
|
<RefreshCw class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 {query.isFetching ? "svadmin-u-afbdd13a380e" : ""}" />
|
|
1346
1714
|
</TooltipButton>
|
|
@@ -1398,7 +1766,40 @@
|
|
|
1398
1766
|
|
|
1399
1767
|
<!-- Table (TanStack-powered) -->
|
|
1400
1768
|
<div class="svadmin-u-2cd02d11d1af svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 svadmin-u-438b2237b8d6" role="region" aria-label="{resource.label} {i18n.t('common.list')}" data-table-density={currentDensity}>
|
|
1401
|
-
{#if query.isLoading}
|
|
1769
|
+
{#if listPermission.isLoading || (query.isLoading && !query.isError)}
|
|
1770
|
+
<Table.Root density={currentDensity}>
|
|
1771
|
+
<Table.Header>
|
|
1772
|
+
{#each tableView.headerGroups as headerGroup, _i (_i)}
|
|
1773
|
+
{@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
|
|
1774
|
+
<Table.Row>
|
|
1775
|
+
{#each visibleHeaders as header (header.id)}
|
|
1776
|
+
<Table.Head>
|
|
1777
|
+
{#if header.id === '_actions'}
|
|
1778
|
+
{i18n.t('common.actions')}
|
|
1779
|
+
{:else if column_getCanSort(header.column)}
|
|
1780
|
+
<Button
|
|
1781
|
+
variant="ghost"
|
|
1782
|
+
size="sm"
|
|
1783
|
+
class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-44ee8ba0a421 svadmin-u-ea7b2e9e070e svadmin-u-8ffe7a29179a svadmin-u-b8f0a08ece1e svadmin-u-660d2effb880 svadmin-u-d5eab218aa34 uppercase svadmin-u-8baf13a3e9d7 svadmin-u-7357df2b2e0c svadmin-u-e83a7042bc91"
|
|
1784
|
+
onclick={() => toggleColumnSort(header.column)}
|
|
1785
|
+
>
|
|
1786
|
+
{visibleFields.find(field => field.key === header.id)?.label ?? header.id}
|
|
1787
|
+
<span class="svadmin-u-359090c2d529 svadmin-u-0b8c506a0596">
|
|
1788
|
+
{#if column_getIsSorted(header.column) === 'asc'}↑
|
|
1789
|
+
{:else if column_getIsSorted(header.column) === 'desc'}↓
|
|
1790
|
+
{:else}⇅
|
|
1791
|
+
{/if}
|
|
1792
|
+
</span>
|
|
1793
|
+
</Button>
|
|
1794
|
+
{:else}
|
|
1795
|
+
{visibleFields.find(field => field.key === header.id)?.label ?? header.id}
|
|
1796
|
+
{/if}
|
|
1797
|
+
</Table.Head>
|
|
1798
|
+
{/each}
|
|
1799
|
+
</Table.Row>
|
|
1800
|
+
{/each}
|
|
1801
|
+
</Table.Header>
|
|
1802
|
+
</Table.Root>
|
|
1402
1803
|
<div class="svadmin-u-8e63407b5ceb svadmin-u-6ed543e2fbbb">
|
|
1403
1804
|
<div class="svadmin-u-60fbb7713999 svadmin-u-0c3bc98565dd svadmin-u-a77ed4d908c0">
|
|
1404
1805
|
{#each visibleFields.slice(0, 4) as _, _i (_i)}
|
|
@@ -1413,12 +1814,20 @@
|
|
|
1413
1814
|
</div>
|
|
1414
1815
|
{/each}
|
|
1415
1816
|
</div>
|
|
1416
|
-
{:else if
|
|
1817
|
+
{:else if !canRead}
|
|
1818
|
+
<p role="alert">{i18n.t('common.operationFailed')}</p>
|
|
1819
|
+
{:else if query.error || query.isError || !pageRecords.ok}
|
|
1417
1820
|
<DataState
|
|
1418
1821
|
state="error"
|
|
1419
|
-
description={i18n.t('common.
|
|
1420
|
-
retry={
|
|
1822
|
+
description={i18n.t('common.operationFailed')}
|
|
1823
|
+
retry={refreshList}
|
|
1421
1824
|
/>
|
|
1825
|
+
{:else if gridBody && pageRecords.data.length > 0}
|
|
1826
|
+
{@render gridBody({ records: pageRecords.data.map(copyTableRecord), columns: gridColumns, primaryKey: 'id',
|
|
1827
|
+
sorters: querySorters, density: currentDensity, locale: i18n.locale, label: resource.label,
|
|
1828
|
+
scopeKey: JSON.stringify([resourceName, queryPagination, querySorters, queryFilters, adminContext.tenantCacheKey?.__svadminTenant]),
|
|
1829
|
+
disabled: query.isFetching || deleteManyMutation.isPending,
|
|
1830
|
+
onSortChange: changeGridSort, cell: renderGridCell, controls: renderGridControls, after: renderGridAfter })}
|
|
1422
1831
|
{:else}
|
|
1423
1832
|
<div in:fade={{ duration: 150 }}>
|
|
1424
1833
|
<!-- Desktop Table (hidden on mobile) -->
|
|
@@ -1426,13 +1835,13 @@
|
|
|
1426
1835
|
<Table.Root density={currentDensity}>
|
|
1427
1836
|
<Table.Header>
|
|
1428
1837
|
{#each tableView.headerGroups as headerGroup, _i (_i)}
|
|
1429
|
-
{@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures,
|
|
1838
|
+
{@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
|
|
1430
1839
|
<DraggableHeader
|
|
1431
1840
|
columns={visibleHeaders.map((header) => ({ id: header.column.id, header }))}
|
|
1432
1841
|
onReorder={setColumnOrder}
|
|
1433
1842
|
>
|
|
1434
1843
|
{#snippet header(col, _index, dragProps)}
|
|
1435
|
-
{@const header = col.header
|
|
1844
|
+
{@const header = col.header}
|
|
1436
1845
|
<Table.Head
|
|
1437
1846
|
{...dragProps}
|
|
1438
1847
|
class={cn('svadmin-u-65fdbade2025 svadmin-u-18049387f0af svadmin-u-b247a17a0d75 svadmin-u-2689f3958069 svadmin-u-d9256981a032 svadmin-u-bfa603190748 svadmin-u-f6e31b39b8e4', dragProps.class)}
|
|
@@ -1445,14 +1854,12 @@
|
|
|
1445
1854
|
checked={table_getIsAllRowsSelected(tbl)}
|
|
1446
1855
|
onCheckedChange={toggleAllRowsSelection}
|
|
1447
1856
|
/>
|
|
1448
|
-
{:else}
|
|
1449
|
-
<CanAccess resource={resourceName} action="delete" params={{ ids: tableView.rows.map(rowIdValue) }}>
|
|
1857
|
+
{:else if !acEnabled || deleteResourcePermission.isLoading || deleteResourcePermission.allowed}
|
|
1450
1858
|
<Checkbox
|
|
1451
1859
|
aria-label={i18n.t('common.selectAll')}
|
|
1452
1860
|
checked={table_getIsAllRowsSelected(tbl)}
|
|
1453
1861
|
onCheckedChange={toggleAllRowsSelection}
|
|
1454
1862
|
/>
|
|
1455
|
-
</CanAccess>
|
|
1456
1863
|
{/if}
|
|
1457
1864
|
{:else if header.id === '_expand'}
|
|
1458
1865
|
<!-- empty -->
|
|
@@ -1483,8 +1890,8 @@
|
|
|
1483
1890
|
</Table.Header>
|
|
1484
1891
|
<Table.Body>
|
|
1485
1892
|
{#each tableView.rows as row (row.id)}
|
|
1486
|
-
{@const record = row.original}
|
|
1487
|
-
{@const id =
|
|
1893
|
+
{@const record = copyTableRecord(row.original)}
|
|
1894
|
+
{@const id = rowIdValue(row)}
|
|
1488
1895
|
{@const visibleCells = row_getVisibleCells(row).filter((cell) => isColumnVisible(cell.column.id))}
|
|
1489
1896
|
<ContextMenu.Root>
|
|
1490
1897
|
<ContextMenu.Trigger>
|
|
@@ -1521,65 +1928,32 @@
|
|
|
1521
1928
|
{#if rowActions}
|
|
1522
1929
|
{@render rowActions({ record, id })}
|
|
1523
1930
|
{:else}
|
|
1524
|
-
{
|
|
1525
|
-
<CanAccess resource={resourceName} action="show" params={{ id }}>
|
|
1526
|
-
<TooltipButton tooltip={i18n.t('common.detail')} variant="ghost" size="icon-sm" onclick={() => openDetail(id)}>
|
|
1527
|
-
<Eye class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1528
|
-
</TooltipButton>
|
|
1529
|
-
</CanAccess>
|
|
1530
|
-
{/if}
|
|
1531
|
-
{#if canEdit}
|
|
1532
|
-
<CanAccess resource={resourceName} action="edit" params={{ id }}>
|
|
1533
|
-
<TooltipButton tooltip={i18n.t('common.edit')} variant="ghost" size="icon-sm" onclick={() => navigation.edit(resourceName, id)}>
|
|
1534
|
-
<Pencil class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1535
|
-
</TooltipButton>
|
|
1536
|
-
</CanAccess>
|
|
1537
|
-
{/if}
|
|
1538
|
-
{#if canDelete}
|
|
1539
|
-
<CanAccess resource={resourceName} action="delete" params={{ id }}>
|
|
1540
|
-
<TooltipButton tooltip={i18n.t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="svadmin-u-51e95020d6f2">
|
|
1541
|
-
<Trash2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1542
|
-
</TooltipButton>
|
|
1543
|
-
</CanAccess>
|
|
1544
|
-
{/if}
|
|
1931
|
+
{@render defaultRowActions(id)}
|
|
1545
1932
|
{/if}
|
|
1546
1933
|
</div>
|
|
1547
1934
|
{:else}
|
|
1548
1935
|
{@const field = visibleFields.find(f => f.key === cell.column.id)}
|
|
1549
|
-
{
|
|
1550
|
-
|
|
1936
|
+
{@const customColumn = field ? customColumns?.[field.key] : undefined}
|
|
1937
|
+
{#if customColumn}
|
|
1938
|
+
{@render customColumn({ value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
|
|
1551
1939
|
{:else if defaultCellRenderer && field}
|
|
1552
|
-
{@render defaultCellRenderer({ field, value: cell_getValue(cell), record })}
|
|
1553
|
-
{:else if field
|
|
1554
|
-
<span class="svadmin-u-bb0c4bfc52bd svadmin-u-2f2a842e50fa svadmin-u-940924b6e2d9 svadmin-u-ac204c108886 {cell_getValue(cell) ? 'svadmin-u-3355648fe22b' : 'svadmin-u-2d56f1807e51'}"></span>
|
|
1555
|
-
{:else if field?.type === 'date' && cell_getValue(cell)}
|
|
1556
|
-
{new Date(cell_getValue(cell) as string).toLocaleDateString()}
|
|
1557
|
-
{:else if field?.type === 'tags' && Array.isArray(cell_getValue(cell))}
|
|
1558
|
-
<div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-44ee8ba0a421">
|
|
1559
|
-
{#each (cell_getValue(cell) as string[]).slice(0, 3) as tag, _i (_i)}
|
|
1560
|
-
<Badge variant="secondary">{tag}</Badge>
|
|
1561
|
-
{/each}
|
|
1562
|
-
</div>
|
|
1563
|
-
{:else if field?.type === 'select' && field.options}
|
|
1564
|
-
{@const opt = field.options.find(o => o.value === cell_getValue(cell))}
|
|
1565
|
-
<Badge variant="outline">{opt?.label ?? cell_getValue(cell) ?? '—'}</Badge>
|
|
1566
|
-
{:else if canEdit && field && ['text', 'number', 'email', 'url'].includes(field.type)}
|
|
1940
|
+
{@render defaultCellRenderer({ field, value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
|
|
1941
|
+
{:else if canEdit && field && field.showInEdit !== false && writableFields.has(field.key) && field.key !== primaryKey && field.key !== 'id' && ['text', 'number', 'email', 'url'].includes(field.type)}
|
|
1567
1942
|
<CanAccess resource={resourceName} action="edit" params={{ id }}>
|
|
1568
1943
|
<InlineEdit
|
|
1569
1944
|
{resourceName}
|
|
1570
1945
|
recordId={id}
|
|
1571
1946
|
{field}
|
|
1572
1947
|
value={cell_getValue(cell)}
|
|
1573
|
-
onSave={() => listResult.refetch()}
|
|
1574
1948
|
/>
|
|
1575
1949
|
{#snippet fallback()}
|
|
1576
|
-
<
|
|
1950
|
+
<FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} />
|
|
1577
1951
|
{/snippet}
|
|
1578
1952
|
</CanAccess>
|
|
1579
|
-
{:else if field?.key ===
|
|
1953
|
+
{:else if field?.key === 'id'}
|
|
1580
1954
|
<span class="svadmin-u-0214b4b355d1 svadmin-u-f283ea9bea0e svadmin-u-0e65706bcccd svadmin-u-359090c2d529" title={String(cell_getValue(cell) ?? '—')}>{cell_getValue(cell) ?? '—'}</span>
|
|
1581
|
-
{:else}
|
|
1582
|
-
<
|
|
1955
|
+
{:else if field}
|
|
1956
|
+
<FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} resourceName={field.resource} />
|
|
1583
1957
|
{/if}
|
|
1584
1958
|
{/if}
|
|
1585
1959
|
</Table.Cell>
|
|
@@ -1628,19 +2002,7 @@
|
|
|
1628
2002
|
{#if emptyState}
|
|
1629
2003
|
{@render emptyState()}
|
|
1630
2004
|
{:else}
|
|
1631
|
-
|
|
1632
|
-
<svg class="svadmin-u-acaee62117b1 svadmin-u-baceed3462fd svadmin-u-106b502aac96 svadmin-u-da019856f2cc" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
|
|
1633
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
|
|
1634
|
-
</svg>
|
|
1635
|
-
<p class="svadmin-u-fc7473ca09eb svadmin-u-2689f3958069 svadmin-u-bfa603190748 svadmin-u-65281709dacf">{i18n.t('common.noData')}</p>
|
|
1636
|
-
<p class="svadmin-u-359090c2d529 svadmin-u-7be4d67a6256 svadmin-u-da019856f2cc">{i18n.t('common.noDataHint')}</p>
|
|
1637
|
-
{#if canCreate}
|
|
1638
|
-
<Button variant="outline" size="sm" class="svadmin-u-77a2a20e90d4" onclick={() => navigation.create(resourceName)}>
|
|
1639
|
-
<Plus class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" />
|
|
1640
|
-
{i18n.t('common.create')}
|
|
1641
|
-
</Button>
|
|
1642
|
-
{/if}
|
|
1643
|
-
</div>
|
|
2005
|
+
{@render defaultEmptyState()}
|
|
1644
2006
|
{/if}
|
|
1645
2007
|
</Table.Cell>
|
|
1646
2008
|
</Table.Row>
|
|
@@ -1648,7 +2010,7 @@
|
|
|
1648
2010
|
</Table.Body>
|
|
1649
2011
|
{#if summary}
|
|
1650
2012
|
<Table.Footer class="svadmin-u-2859c861d7de svadmin-u-2689f3958069">
|
|
1651
|
-
{@render summary({ data:
|
|
2013
|
+
{@render summary({ data: pageRecords.data.map(copyTableRecord), total: query.data?.total ?? 0, visibleColumnsCount: columns.filter((c) => c.id != null && isColumnVisible(c.id)).length })}
|
|
1652
2014
|
</Table.Footer>
|
|
1653
2015
|
{/if}
|
|
1654
2016
|
</Table.Root>
|
|
@@ -1657,8 +2019,8 @@
|
|
|
1657
2019
|
<!-- Mobile Card View (visible only on small screens) -->
|
|
1658
2020
|
<div class="svadmin-u-e477a6af4cb6 svadmin-u-6ed543e2fbbb svadmin-u-7660b450905a">
|
|
1659
2021
|
{#each tableView.rows as row, _i (_i)}
|
|
1660
|
-
{@const record = row.original}
|
|
1661
|
-
{@const id =
|
|
2022
|
+
{@const record = copyTableRecord(row.original)}
|
|
2023
|
+
{@const id = rowIdValue(row)}
|
|
1662
2024
|
<div
|
|
1663
2025
|
class="svadmin-u-f5c8cc114f47 svadmin-u-633ef3c47872 svadmin-u-3daca9af0861 svadmin-u-a3158643e114 svadmin-u-cd0ad9a56558 svadmin-u-c07e54fd1439 svadmin-u-0fe7d7d814d0 {rowIsSelected(row.id) ? 'svadmin-u-16b1efa5875e svadmin-u-f42e9fee68a1 svadmin-u-989c466fdbe7' : ''}"
|
|
1664
2026
|
>
|
|
@@ -1688,27 +2050,7 @@
|
|
|
1688
2050
|
{#if rowActions}
|
|
1689
2051
|
{@render rowActions({ record, id })}
|
|
1690
2052
|
{:else}
|
|
1691
|
-
{
|
|
1692
|
-
<CanAccess resource={resourceName} action="edit" params={{ id }}>
|
|
1693
|
-
<TooltipButton tooltip={i18n.t('common.edit')} variant="ghost" size="icon-sm" onclick={() => navigation.edit(resourceName, id)}>
|
|
1694
|
-
<Pencil class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1695
|
-
</TooltipButton>
|
|
1696
|
-
</CanAccess>
|
|
1697
|
-
{/if}
|
|
1698
|
-
{#if canShow}
|
|
1699
|
-
<CanAccess resource={resourceName} action="show" params={{ id }}>
|
|
1700
|
-
<TooltipButton tooltip={i18n.t('common.detail')} variant="ghost" size="icon-sm" onclick={() => openDetail(id)}>
|
|
1701
|
-
<Eye class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1702
|
-
</TooltipButton>
|
|
1703
|
-
</CanAccess>
|
|
1704
|
-
{/if}
|
|
1705
|
-
{#if canDelete}
|
|
1706
|
-
<CanAccess resource={resourceName} action="delete" params={{ id }}>
|
|
1707
|
-
<TooltipButton tooltip={i18n.t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="svadmin-u-51e95020d6f2">
|
|
1708
|
-
<Trash2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1709
|
-
</TooltipButton>
|
|
1710
|
-
</CanAccess>
|
|
1711
|
-
{/if}
|
|
2053
|
+
{@render defaultRowActions(id)}
|
|
1712
2054
|
{/if}
|
|
1713
2055
|
</div>
|
|
1714
2056
|
</div>
|
|
@@ -1716,28 +2058,18 @@
|
|
|
1716
2058
|
<div class="svadmin-u-6f7e013d6499">
|
|
1717
2059
|
{#each visibleFields.filter((field) => isColumnVisible(field.key)).slice(0, 6) as field (field.key)}
|
|
1718
2060
|
{@const value = record[field.key]}
|
|
2061
|
+
{@const customColumn = customColumns?.[field.key]}
|
|
1719
2062
|
<div class="svadmin-u-60fbb7713999 svadmin-u-60541e1e26f8 svadmin-u-8ef2268efbbc svadmin-u-0c3bc98565dd">
|
|
1720
2063
|
<span class="svadmin-u-359090c2d529 svadmin-u-2689f3958069 svadmin-u-bfa603190748 svadmin-u-012fbd121f37">{field.label}</span>
|
|
1721
2064
|
<span class="svadmin-u-fc7473ca09eb svadmin-u-308fc069e46e svadmin-u-f283ea9bea0e svadmin-u-897d497e57b6">
|
|
1722
|
-
{#if
|
|
1723
|
-
{@render
|
|
2065
|
+
{#if customColumn}
|
|
2066
|
+
{@render customColumn({ value: clonePlainValue(value), record: copyTableRecord(record) })}
|
|
1724
2067
|
{:else if defaultCellRenderer && field}
|
|
1725
|
-
{@render defaultCellRenderer({ field, value, record })}
|
|
1726
|
-
{:else if field.
|
|
1727
|
-
|
|
1728
|
-
{:else if field.type === 'date' && value}
|
|
1729
|
-
{new Date(value as string).toLocaleDateString()}
|
|
1730
|
-
{:else if field.type === 'tags' && Array.isArray(value)}
|
|
1731
|
-
<div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-44ee8ba0a421 svadmin-u-77c08e015d14">
|
|
1732
|
-
{#each (value as string[]).slice(0, 2) as tag, _i (_i)}
|
|
1733
|
-
<Badge variant="secondary" class="svadmin-u-1dc571a3609f">{tag}</Badge>
|
|
1734
|
-
{/each}
|
|
1735
|
-
</div>
|
|
1736
|
-
{:else if field.type === 'select' && field.options}
|
|
1737
|
-
{@const opt = field.options.find(o => o.value === value)}
|
|
1738
|
-
<Badge variant="outline" class="svadmin-u-1dc571a3609f">{opt?.label ?? value ?? '—'}</Badge>
|
|
2068
|
+
{@render defaultCellRenderer({ field, value: clonePlainValue(value), record: copyTableRecord(record) })}
|
|
2069
|
+
{:else if field.key === 'id'}
|
|
2070
|
+
{id}
|
|
1739
2071
|
{:else}
|
|
1740
|
-
{value
|
|
2072
|
+
<FieldDisplay type={field.type} {value} options={field.options} resourceName={field.resource} />
|
|
1741
2073
|
{/if}
|
|
1742
2074
|
</span>
|
|
1743
2075
|
</div>
|
|
@@ -1749,7 +2081,7 @@
|
|
|
1749
2081
|
{#if emptyState}
|
|
1750
2082
|
{@render emptyState()}
|
|
1751
2083
|
{:else}
|
|
1752
|
-
|
|
2084
|
+
{@render defaultEmptyState()}
|
|
1753
2085
|
{/if}
|
|
1754
2086
|
</div>
|
|
1755
2087
|
{/each}
|
|
@@ -1767,9 +2099,10 @@
|
|
|
1767
2099
|
aria-label={i18n.t('common.perPage')}
|
|
1768
2100
|
class="svadmin-u-ed8a5df7b2fb svadmin-u-d043cad8e3fa svadmin-u-012fbd121f37 svadmin-u-3032cae0badb"
|
|
1769
2101
|
value={String(pagination.pageSize ?? 10)}
|
|
1770
|
-
onchange={(e) => {
|
|
1771
|
-
|
|
1772
|
-
|
|
2102
|
+
onchange={(e: Event) => {
|
|
2103
|
+
if (!(e.currentTarget instanceof HTMLSelectElement)) return;
|
|
2104
|
+
const size = Number(e.currentTarget.value);
|
|
2105
|
+
if (Number.isSafeInteger(size) && size > 0) {
|
|
1773
2106
|
markSavedViewDirty();
|
|
1774
2107
|
pagination = { ...pagination, pageSize: size, current: 1 };
|
|
1775
2108
|
}
|
|
@@ -1797,7 +2130,7 @@
|
|
|
1797
2130
|
<PaginationUI.Link
|
|
1798
2131
|
class="svadmin-u-3032cae0badb"
|
|
1799
2132
|
isActive={page === currentPage}
|
|
1800
|
-
onclick={() =>
|
|
2133
|
+
onclick={() => { if (typeof page === 'number') goToPage(page); }}
|
|
1801
2134
|
>
|
|
1802
2135
|
{page}
|
|
1803
2136
|
</PaginationUI.Link>
|
|
@@ -1820,16 +2153,23 @@
|
|
|
1820
2153
|
open={confirmOpen}
|
|
1821
2154
|
message={confirmMessage}
|
|
1822
2155
|
confirmText={i18n.t('common.delete')}
|
|
1823
|
-
confirming={confirmPending}
|
|
2156
|
+
confirming={confirmPending || deletePermission.isLoading}
|
|
1824
2157
|
onconfirm={confirmAction}
|
|
1825
|
-
oncancel={() => { confirmOpen = false; }}
|
|
2158
|
+
oncancel={() => { if (!confirmPending) { confirmOpen = false; deleteRequest = null; } }}
|
|
1826
2159
|
/>
|
|
1827
2160
|
|
|
1828
2161
|
{#if detailRecordId != null}
|
|
1829
2162
|
<RecordDetailDrawer
|
|
2163
|
+
rendering={activeRendering}
|
|
1830
2164
|
resourceName={resourceName}
|
|
1831
2165
|
open={detailOpen}
|
|
1832
2166
|
recordId={detailRecordId}
|
|
1833
2167
|
onClose={closeDetail}
|
|
1834
2168
|
/>
|
|
1835
2169
|
{/if}
|
|
2170
|
+
|
|
2171
|
+
{#if quickEditId != null && canRead && canEdit}
|
|
2172
|
+
{#key quickEditId}
|
|
2173
|
+
<QuickEditDrawer rendering={activeRendering} {resourceName} recordId={quickEditId} onClose={() => quickEditId = undefined} />
|
|
2174
|
+
{/key}
|
|
2175
|
+
{/if}
|