@svadmin/ui 0.69.1 → 0.72.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/dist/actions.js +1 -1
- package/dist/app.css +102 -63
- package/dist/app.theme.css +102 -63
- 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 +146 -48
- package/dist/components/AutoForm.svelte.d.ts +2 -0
- package/dist/components/AutoTable.svelte +358 -258
- package/dist/components/AutoTable.svelte.d.ts +1 -0
- package/dist/components/BoundRecordDetailDrawer.svelte +166 -0
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +9 -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 +90 -30
- package/dist/components/ComboboxField.svelte.d.ts +2 -2
- package/dist/components/CreatePage.svelte +3 -1
- package/dist/components/DetailDrawer.svelte +4 -0
- package/dist/components/DetailDrawer.svelte.d.ts +1 -0
- package/dist/components/DevTools.svelte +1 -1
- 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 +3 -1
- 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 +9 -5
- package/dist/components/FilterBuilder.svelte +19 -8
- package/dist/components/FilterDropdown.svelte.d.ts +1 -1
- package/dist/components/Header.svelte +5 -6
- package/dist/components/ImportWizard.svelte +3 -2
- 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 +19 -14
- package/dist/components/Layout.svelte +19 -15
- package/dist/components/LazyTaskQueueDrawer.svelte +26 -0
- package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +8 -0
- package/dist/components/ListPage.svelte +7 -6
- package/dist/components/MasterDetailView.svelte +3 -3
- package/dist/components/MediaLibraryModal.svelte +5 -2
- package/dist/components/MentionsInput.svelte +4 -3
- package/dist/components/NavigateToResource.svelte +3 -2
- package/dist/components/PermissionMatrix.svelte +4 -3
- package/dist/components/ProfilePage.svelte +2 -2
- package/dist/components/QuickEditDrawer.svelte +39 -0
- package/dist/components/QuickEditDrawer.svelte.d.ts +8 -0
- package/dist/components/RecordDetailDrawer.svelte +14 -108
- 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/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/ShowPage.svelte +44 -38
- package/dist/components/Sidebar.svelte +10 -8
- package/dist/components/SplitPaneLayout.svelte +4 -2
- package/dist/components/StepsForm.svelte +161 -44
- package/dist/components/StepsForm.svelte.d.ts +1 -0
- package/dist/components/TaskDetails.svelte +20 -7
- package/dist/components/TaskList.svelte +19 -7
- package/dist/components/TaskQueueDrawer.svelte +21 -22
- package/dist/components/Toast.svelte +4 -2
- package/dist/components/TooltipButton.svelte +1 -1
- package/dist/components/TreeSelect.svelte +4 -2
- 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/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/ContentPageShell.svelte +3 -1
- package/dist/components/content/FileList.svelte +3 -1
- package/dist/components/content/MemberList.svelte +3 -1
- package/dist/components/content/NetworkTable.svelte +3 -1
- package/dist/components/content/SecurityEventTable.svelte +3 -1
- package/dist/components/content/StatusTabs.svelte +2 -2
- package/dist/components/fieldComponentMap.d.ts +18 -13
- package/dist/components/fieldComponentMap.js +95 -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/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/json-value.d.ts +11 -0
- package/dist/components/fields/json-value.js +28 -0
- package/dist/components/profile/ActivityTimeline.svelte +2 -2
- package/dist/components/record-detail.svelte.d.ts +16 -0
- package/dist/components/record-detail.svelte.js +28 -0
- package/dist/components/saved-list-views.js +29 -29
- 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 +1 -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 +1 -2
- 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 +40 -20
- 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/tooltip/tooltip.svelte +4 -2
- package/dist/index.d.ts +1 -1
- 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/types.d.ts +3 -3
- package/package.json +11 -5
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { definedOptions } from '@svadmin/core/options';
|
|
3
|
+
|
|
4
|
+
import { onDestroy, tick, untrack } from 'svelte';
|
|
3
5
|
import { SvelteMap } from 'svelte/reactivity';
|
|
4
6
|
import { cn } from '../utils.js';
|
|
5
7
|
import {
|
|
@@ -28,13 +30,16 @@
|
|
|
28
30
|
table_getRowModel,
|
|
29
31
|
table_getAllLeafColumns,
|
|
30
32
|
table_getIsAllRowsSelected,
|
|
33
|
+
table_resetRowSelection,
|
|
31
34
|
table_toggleAllRowsSelected,
|
|
32
35
|
row_toggleSelected,
|
|
33
36
|
row_getVisibleCells,
|
|
34
37
|
cell_getValue,
|
|
35
38
|
} from '@tanstack/table-core/static-functions';
|
|
36
39
|
|
|
37
|
-
import { captureAdminContext, DeleteManyPartialError, getAdminOptions,
|
|
40
|
+
import { captureAdminContext, DeleteManyPartialError, getAdminOptions, getContractFormFields, useNavigation, useParsed, useResourceContract, useList, useDeleteMany, downloadData } from '@svadmin/core';
|
|
41
|
+
import { decodeBaseRecord, snapshotPlainData, parseContractRouteId, formatContractRouteId } from '@svadmin/core/schema';
|
|
42
|
+
import { checkedTableRows, copyTableRecord, tableExportRows, tableRowKey, type TableRecord } from './table-contract';
|
|
38
43
|
import type {
|
|
39
44
|
BaseRecord,
|
|
40
45
|
FieldDefinition,
|
|
@@ -84,9 +89,12 @@
|
|
|
84
89
|
} from '@lucide/svelte';
|
|
85
90
|
import ConfirmDialog from './ConfirmDialog.svelte';
|
|
86
91
|
import RecordDetailDrawer from './RecordDetailDrawer.svelte';
|
|
92
|
+
import QuickEditDrawer from './QuickEditDrawer.svelte';
|
|
93
|
+
import RecordRowActions from './RecordRowActions.svelte';
|
|
87
94
|
import CanAccess from './CanAccess.svelte';
|
|
88
95
|
import TooltipButton from './TooltipButton.svelte';
|
|
89
96
|
import InlineEdit from './InlineEdit.svelte';
|
|
97
|
+
import FieldDisplay from './FieldDisplay.svelte';
|
|
90
98
|
import DraggableHeader from './DraggableHeader.svelte';
|
|
91
99
|
import DataState from './content/DataState.svelte';
|
|
92
100
|
import type { Snippet } from 'svelte';
|
|
@@ -123,6 +131,7 @@
|
|
|
123
131
|
batchActions?: Snippet<[{ selectedIds: (string | number)[] }]>;
|
|
124
132
|
/** Summary row rendered at table footer */
|
|
125
133
|
summary?: Snippet<[{ data: BaseRecord[]; total: number; visibleColumnsCount: number }]>;
|
|
134
|
+
deleteVariables?: unknown;
|
|
126
135
|
}
|
|
127
136
|
|
|
128
137
|
let {
|
|
@@ -144,25 +153,27 @@
|
|
|
144
153
|
summary,
|
|
145
154
|
pagination: externalPagination,
|
|
146
155
|
sorters: externalSorters,
|
|
156
|
+
deleteVariables,
|
|
147
157
|
}: Props = $props();
|
|
148
158
|
|
|
149
159
|
let densityOverride = $state<'compact' | 'comfortable' | undefined>(undefined);
|
|
150
160
|
const currentDensity = $derived(densityOverride ?? density);
|
|
151
161
|
const adminContext = captureAdminContext();
|
|
152
|
-
const adminOptions = getAdminOptions();
|
|
153
|
-
const mutationMode = $derived(adminOptions.mutationMode ?? 'pessimistic');
|
|
154
162
|
const parsed = useParsed();
|
|
155
163
|
const navigation = useNavigation();
|
|
156
164
|
|
|
157
|
-
const
|
|
165
|
+
const binding = useResourceContract(() => resourceName);
|
|
166
|
+
const resource = $derived(adminContext.getResource(resourceName));
|
|
158
167
|
const primaryKey = $derived(resource.primaryKey ?? 'id');
|
|
168
|
+
const listPermission = useCan(() => ({ resource: resourceName, action: 'list' }));
|
|
169
|
+
const canRead = $derived(listPermission.allowed);
|
|
159
170
|
const listPreferenceScope = $derived.by(() => {
|
|
160
171
|
const matcher = adminContext.queryKeyMatcher(resourceName);
|
|
161
|
-
return {
|
|
172
|
+
return definedOptions({
|
|
162
173
|
resourceName,
|
|
163
174
|
providerName: matcher.provider ?? 'default',
|
|
164
175
|
tenantIdentity: matcher.tenant,
|
|
165
|
-
};
|
|
176
|
+
});
|
|
166
177
|
});
|
|
167
178
|
|
|
168
179
|
function readLocalPreference(key: string): string | null {
|
|
@@ -189,10 +200,10 @@
|
|
|
189
200
|
// ─── URL state + server-side state ────────────────────────────
|
|
190
201
|
const urlState = readURLState(adminContext);
|
|
191
202
|
|
|
192
|
-
const savedViewColumnIds = new Set([
|
|
193
|
-
...
|
|
203
|
+
const savedViewColumnIds = $derived(new Set([
|
|
204
|
+
...resource.fields.map((field) => field.key),
|
|
194
205
|
'_select', '_expand', '_actions',
|
|
195
|
-
]);
|
|
206
|
+
]));
|
|
196
207
|
const storedSavedViews = untrack(() => readSavedListViews(
|
|
197
208
|
readScopedPreference(
|
|
198
209
|
listPreferenceScope,
|
|
@@ -244,7 +255,7 @@
|
|
|
244
255
|
const parsed: unknown = JSON.parse(stored);
|
|
245
256
|
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
246
257
|
const visibility: ColumnVisibilityState = {};
|
|
247
|
-
for (const [columnId, visible] of Object.entries(parsed
|
|
258
|
+
for (const [columnId, visible] of Object.entries(decodeBaseRecord(parsed))) {
|
|
248
259
|
if (savedViewColumnIds.has(columnId) && typeof visible === 'boolean') {
|
|
249
260
|
visibility[columnId] = visible;
|
|
250
261
|
}
|
|
@@ -271,7 +282,7 @@
|
|
|
271
282
|
try {
|
|
272
283
|
const parsed: unknown = JSON.parse(stored);
|
|
273
284
|
return Array.isArray(parsed)
|
|
274
|
-
? [...new Set(parsed.filter((columnId): columnId is string => (
|
|
285
|
+
? [...new Set(parsed.filter((columnId: unknown): columnId is string => (
|
|
275
286
|
typeof columnId === 'string' && savedViewColumnIds.has(columnId)
|
|
276
287
|
)))]
|
|
277
288
|
: [];
|
|
@@ -301,19 +312,19 @@
|
|
|
301
312
|
legacyActiveSavedListViewStorageKey(resourceName),
|
|
302
313
|
);
|
|
303
314
|
const activeSavedView = scopedSavedViews.find((view) => view.id === candidate);
|
|
304
|
-
return {
|
|
315
|
+
return definedOptions({
|
|
305
316
|
savedViews: scopedSavedViews,
|
|
306
317
|
activeSavedViewId: activeSavedView?.id,
|
|
307
318
|
activeSavedView,
|
|
308
319
|
columnVisibility: readColumnVisibilityPreference(scope),
|
|
309
320
|
columnOrder: readColumnOrderPreference(scope),
|
|
310
|
-
};
|
|
321
|
+
});
|
|
311
322
|
}
|
|
312
323
|
|
|
313
324
|
// Snapshot resource values for initial state (untrack to avoid reactive tracking)
|
|
314
325
|
const storedPageSize = parseInt(readLocalPreference('svadmin-default-page-size') ?? '', 10);
|
|
315
|
-
const initPageSize =
|
|
316
|
-
const initDefaultSort =
|
|
326
|
+
const initPageSize = $derived(resource.pageSize ?? (Number.isSafeInteger(storedPageSize) && storedPageSize > 0 ? storedPageSize : 10));
|
|
327
|
+
const initDefaultSort = $derived(resource.defaultSort);
|
|
317
328
|
|
|
318
329
|
let pagination = $state<PaginationState>(untrack(() => externalPagination ?? {
|
|
319
330
|
current: urlState.page ?? initialViewState?.pagination.current ?? 1,
|
|
@@ -326,10 +337,10 @@
|
|
|
326
337
|
: initialViewState?.sorters ?? (initDefaultSort ? [initDefaultSort] : [])))
|
|
327
338
|
);
|
|
328
339
|
const initialURLFilters = untrack(() => urlState.filters ?? initialViewState?.filters ?? []);
|
|
329
|
-
const editableFilterKeys = new Set(
|
|
340
|
+
const editableFilterKeys = $derived(new Set(resource.fields.filter((field) => field.filterable).map((field) => field.key)));
|
|
330
341
|
const editableFilterCounts: Record<string, number> = {};
|
|
331
342
|
for (const filter of initialURLFilters) {
|
|
332
|
-
if ('field' in filter && filter.operator === 'contains' && typeof filter.value === 'string' && editableFilterKeys.has(filter.field)) {
|
|
343
|
+
if ('field' in filter && filter.operator === 'contains' && typeof filter.value === 'string' && untrack(() => editableFilterKeys.has(filter.field))) {
|
|
333
344
|
editableFilterCounts[filter.field] = (editableFilterCounts[filter.field] ?? 0) + 1;
|
|
334
345
|
}
|
|
335
346
|
}
|
|
@@ -350,7 +361,8 @@
|
|
|
350
361
|
let searchDebounceTimer: ReturnType<typeof setTimeout> | undefined;
|
|
351
362
|
|
|
352
363
|
function scheduleSearch(event: Event) {
|
|
353
|
-
|
|
364
|
+
if (!(event.currentTarget instanceof HTMLInputElement)) return;
|
|
365
|
+
searchText = event.currentTarget.value;
|
|
354
366
|
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
|
355
367
|
searchDebounceTimer = setTimeout(() => {
|
|
356
368
|
appliedSearchText = searchText;
|
|
@@ -395,8 +407,9 @@
|
|
|
395
407
|
const activeFilters = $derived.by(() => {
|
|
396
408
|
const result: Filter[] = [...locationFilters];
|
|
397
409
|
if (appliedSearchText.trim() && searchableFields.length > 0) {
|
|
398
|
-
|
|
399
|
-
|
|
410
|
+
const firstSearchableField = searchableFields[0];
|
|
411
|
+
if (searchableFields.length === 1 && firstSearchableField) {
|
|
412
|
+
result.push({ field: firstSearchableField.key, operator: 'contains', value: appliedSearchText });
|
|
400
413
|
} else {
|
|
401
414
|
const searchFilter: LogicalFilter = {
|
|
402
415
|
operator: 'or',
|
|
@@ -426,6 +439,10 @@
|
|
|
426
439
|
|
|
427
440
|
function clearFilters(): void {
|
|
428
441
|
markSavedViewDirty();
|
|
442
|
+
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
|
443
|
+
searchDebounceTimer = undefined;
|
|
444
|
+
searchText = '';
|
|
445
|
+
appliedSearchText = '';
|
|
429
446
|
filters = [];
|
|
430
447
|
filterValues = {};
|
|
431
448
|
pagination = { ...pagination, current: 1 };
|
|
@@ -447,6 +464,12 @@
|
|
|
447
464
|
pagination = { ...pagination, current: 1 };
|
|
448
465
|
}
|
|
449
466
|
|
|
467
|
+
function setFilterFromEvent(field: string, event: Event): void {
|
|
468
|
+
if (event.currentTarget instanceof HTMLInputElement || event.currentTarget instanceof HTMLSelectElement) {
|
|
469
|
+
setFilterValue(field, event.currentTarget.value);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
450
473
|
function removeActiveFilter(index: number): void {
|
|
451
474
|
markSavedViewDirty();
|
|
452
475
|
const filter = locationFilters[index];
|
|
@@ -460,21 +483,16 @@
|
|
|
460
483
|
&& candidate.operator === 'contains'
|
|
461
484
|
&& typeof candidate.value === 'string'
|
|
462
485
|
));
|
|
463
|
-
|
|
464
|
-
|
|
486
|
+
const remainingFilter = remaining[0];
|
|
487
|
+
filterValues[filter.field] = remaining.length === 1 && remainingFilter && 'field' in remainingFilter
|
|
488
|
+
? String(remainingFilter.value)
|
|
465
489
|
: '';
|
|
466
490
|
}
|
|
467
491
|
pagination = { ...pagination, current: 1 };
|
|
468
492
|
}
|
|
469
493
|
|
|
470
494
|
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;
|
|
495
|
+
return value === undefined ? undefined : snapshotPlainData(value);
|
|
478
496
|
}
|
|
479
497
|
|
|
480
498
|
function cloneFilter(filter: Filter): Filter {
|
|
@@ -491,11 +509,11 @@
|
|
|
491
509
|
};
|
|
492
510
|
}
|
|
493
511
|
|
|
494
|
-
const queryPagination = $derived<PaginationState>({
|
|
512
|
+
const queryPagination = $derived<PaginationState>(definedOptions({
|
|
495
513
|
current: pagination.current,
|
|
496
514
|
pageSize: pagination.pageSize,
|
|
497
515
|
mode: pagination.mode,
|
|
498
|
-
});
|
|
516
|
+
}));
|
|
499
517
|
const querySorters = $derived<Sort[]>(sorters.map(sorter => ({
|
|
500
518
|
field: sorter.field,
|
|
501
519
|
order: sorter.order,
|
|
@@ -504,38 +522,48 @@
|
|
|
504
522
|
|
|
505
523
|
// ─── Data fetching ────────────────────────────────────────────
|
|
506
524
|
const listResult = useList({
|
|
507
|
-
get resource() { return
|
|
525
|
+
get resource() { return binding.resource; },
|
|
526
|
+
get dataProviderName() { return binding.dataProviderName; },
|
|
527
|
+
get queryOptions() { return { enabled: canRead }; },
|
|
508
528
|
get pagination() { return queryPagination; },
|
|
509
529
|
get sorters() { return querySorters; },
|
|
510
530
|
get filters() { return queryFilters; },
|
|
511
531
|
});
|
|
512
532
|
const query = listResult;
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
|
|
533
|
+
const pageRecords = $derived.by(() => {
|
|
534
|
+
try { return { ok: true as const, data: canRead ? checkedTableRows(query.data?.data ?? []) : [] }; }
|
|
535
|
+
catch { return { ok: false as const, data: [] }; }
|
|
536
|
+
});
|
|
537
|
+
let deleteRequest = $state<{ ids: (string | number)[]; batch: boolean } | null>(null);
|
|
538
|
+
const deletePermission = useCan(() => definedOptions({
|
|
539
|
+
resource: resourceName, action: 'delete',
|
|
540
|
+
id: deleteRequest?.batch === false ? deleteRequest.ids[0] : undefined,
|
|
541
|
+
params: deleteRequest?.batch ? { ids: deleteRequest.ids } : undefined,
|
|
542
|
+
queryOptions: { enabled: deleteRequest !== null },
|
|
543
|
+
}));
|
|
544
|
+
const deleteAllowed = $derived(canRead && resource.canDelete !== false && deleteRequest !== null && deletePermission.allowed);
|
|
516
545
|
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
|
-
}),
|
|
546
|
+
get resource() { return binding.resource; },
|
|
547
|
+
get enabled() { return deleteAllowed; },
|
|
527
548
|
});
|
|
528
549
|
const deleteManyMutation = deleteManyResult.mutation;
|
|
529
550
|
|
|
530
551
|
// ─── Permissions ──────────────────────────────────────────────
|
|
531
552
|
const acEnabled = $derived(!!adminContext.accessControlProvider);
|
|
553
|
+
const deleteResourcePermission = useCan(() => ({
|
|
554
|
+
resource: resourceName, action: 'delete', queryOptions: { enabled: acEnabled },
|
|
555
|
+
}));
|
|
532
556
|
const canCreatePerm = useCan(() => ({ resource: resourceName, action: 'create', queryOptions: { enabled: acEnabled } }));
|
|
533
557
|
const canExportPerm = useCan(() => ({ resource: resourceName, action: 'export', queryOptions: { enabled: acEnabled } }));
|
|
534
558
|
const canCreate = $derived(resource.canCreate !== false && (!acEnabled || canCreatePerm.allowed));
|
|
535
559
|
const canEdit = $derived(resource.canEdit !== false);
|
|
560
|
+
const writableFields = $derived.by(() => {
|
|
561
|
+
try { return new Set(getContractFormFields(binding.resource, 'edit')); }
|
|
562
|
+
catch { return new Set<string>(); }
|
|
563
|
+
});
|
|
536
564
|
const canShow = $derived(resource.canShow !== false);
|
|
537
565
|
const canDelete = $derived(resource.canDelete !== false);
|
|
538
|
-
const canExport = $derived(canExportPerm.allowed);
|
|
566
|
+
const canExport = $derived(!acEnabled || canExportPerm.allowed);
|
|
539
567
|
|
|
540
568
|
// ─── TanStack Table state ────────────────────────────────────
|
|
541
569
|
const initialSorting = untrack<SortingState>(() =>
|
|
@@ -546,8 +574,8 @@
|
|
|
546
574
|
));
|
|
547
575
|
const sortingAtom = createAtom(initialSorting);
|
|
548
576
|
const columnVisibilityAtom = createAtom(initialColumnVisibility);
|
|
549
|
-
|
|
550
|
-
const expandedAtom = createAtom({}
|
|
577
|
+
let rowSelection = $state<RowSelectionState>({});
|
|
578
|
+
const expandedAtom = createAtom<ExpandedState>({});
|
|
551
579
|
const initialColumnOrder = untrack(() => (
|
|
552
580
|
initialViewState?.columnOrder?.length
|
|
553
581
|
? initialViewState.columnOrder
|
|
@@ -565,7 +593,6 @@
|
|
|
565
593
|
|
|
566
594
|
const tableSorting = useSelector(sortingAtom);
|
|
567
595
|
const tableColumnVisibility = useSelector(columnVisibilityAtom);
|
|
568
|
-
const tableRowSelection = useSelector(rowSelectionAtom);
|
|
569
596
|
const tableExpanded = useSelector(expandedAtom);
|
|
570
597
|
const tableColumnOrder = useSelector(columnOrderAtom);
|
|
571
598
|
const selectedIdValueByKey = new SvelteMap<string, string | number>();
|
|
@@ -580,15 +607,15 @@
|
|
|
580
607
|
}
|
|
581
608
|
|
|
582
609
|
function rowIsSelected(rowId: string): boolean {
|
|
583
|
-
return
|
|
610
|
+
return rowSelection[rowId] === true;
|
|
584
611
|
}
|
|
585
612
|
|
|
586
|
-
function rowIdValue(row: Row<TableFeatures,
|
|
587
|
-
|
|
588
|
-
return typeof value === 'string' || typeof value === 'number' ? value : row.id;
|
|
613
|
+
function rowIdValue(row: Row<TableFeatures, TableRecord>): string | number {
|
|
614
|
+
return row.original.id;
|
|
589
615
|
}
|
|
590
616
|
|
|
591
|
-
function toggleRowSelection(row: Row<TableFeatures,
|
|
617
|
+
function toggleRowSelection(row: Row<TableFeatures, TableRecord>): void {
|
|
618
|
+
if (confirmPending || !canRead) return;
|
|
592
619
|
const wasSelected = rowIsSelected(row.id);
|
|
593
620
|
if (!wasSelected) {
|
|
594
621
|
selectedIdValueByKey.set(row.id, rowIdValue(row));
|
|
@@ -696,7 +723,8 @@
|
|
|
696
723
|
columnVisibilityAtom.set(state.columnVisibility);
|
|
697
724
|
columnOrderAtom.set(state.columnOrder);
|
|
698
725
|
persistColumnOrder(state.columnOrder);
|
|
699
|
-
|
|
726
|
+
rowSelection = {};
|
|
727
|
+
selectedIdValueByKey.clear();
|
|
700
728
|
}
|
|
701
729
|
|
|
702
730
|
function resetToDefaultListState(): void {
|
|
@@ -733,7 +761,8 @@
|
|
|
733
761
|
if (!hasExplicitURLState) resetToDefaultListState();
|
|
734
762
|
columnVisibilityAtom.set(preferences.columnVisibility);
|
|
735
763
|
columnOrderAtom.set(preferences.columnOrder);
|
|
736
|
-
|
|
764
|
+
rowSelection = {};
|
|
765
|
+
selectedIdValueByKey.clear();
|
|
737
766
|
}
|
|
738
767
|
|
|
739
768
|
loadedPreferenceScopeId = scopeId;
|
|
@@ -785,7 +814,7 @@
|
|
|
785
814
|
columnOrderAtom.set(ids);
|
|
786
815
|
}
|
|
787
816
|
|
|
788
|
-
function toggleColumnSort(column: Column<TableFeatures,
|
|
817
|
+
function toggleColumnSort(column: Column<TableFeatures, TableRecord, unknown>): void {
|
|
789
818
|
markSavedViewDirty();
|
|
790
819
|
column_toggleSorting(column);
|
|
791
820
|
}
|
|
@@ -808,7 +837,7 @@
|
|
|
808
837
|
resource.fields.filter(f => f.showInList !== false)
|
|
809
838
|
);
|
|
810
839
|
|
|
811
|
-
const columns = $derived<ColumnDef<TableFeatures,
|
|
840
|
+
const columns = $derived<ColumnDef<TableFeatures, TableRecord, unknown>[]>([
|
|
812
841
|
// Selection column
|
|
813
842
|
...(selectable && (canDelete || batchActions) ? [{
|
|
814
843
|
id: '_select',
|
|
@@ -816,7 +845,7 @@
|
|
|
816
845
|
cell: () => '',
|
|
817
846
|
size: 40,
|
|
818
847
|
enableSorting: false,
|
|
819
|
-
} satisfies ColumnDef<TableFeatures,
|
|
848
|
+
} satisfies ColumnDef<TableFeatures, TableRecord, unknown>] : []),
|
|
820
849
|
// Expand column
|
|
821
850
|
...(expandedRowRender ? [{
|
|
822
851
|
id: '_expand',
|
|
@@ -824,14 +853,14 @@
|
|
|
824
853
|
cell: () => '',
|
|
825
854
|
size: 40,
|
|
826
855
|
enableSorting: false,
|
|
827
|
-
} satisfies ColumnDef<TableFeatures,
|
|
856
|
+
} satisfies ColumnDef<TableFeatures, TableRecord, unknown>] : []),
|
|
828
857
|
// Data columns
|
|
829
|
-
...visibleFields.map((field): ColumnDef<TableFeatures,
|
|
858
|
+
...visibleFields.map((field): ColumnDef<TableFeatures, TableRecord, unknown> => (definedOptions({
|
|
830
859
|
id: field.key,
|
|
831
860
|
accessorKey: field.key,
|
|
832
861
|
header: () => field.label,
|
|
833
862
|
size: field.width ? parseInt(String(field.width)) : undefined,
|
|
834
|
-
})),
|
|
863
|
+
}))),
|
|
835
864
|
// Actions column
|
|
836
865
|
{
|
|
837
866
|
id: '_actions',
|
|
@@ -856,22 +885,25 @@
|
|
|
856
885
|
});
|
|
857
886
|
|
|
858
887
|
// ─── Create TanStack Table ────────────────────────────────────
|
|
859
|
-
const tbl = createTable<TableFeatures,
|
|
888
|
+
const tbl = createTable<TableFeatures, TableRecord>(
|
|
860
889
|
{
|
|
861
890
|
features,
|
|
862
|
-
get data() { return
|
|
891
|
+
get data() { return pageRecords.data; },
|
|
863
892
|
get columns() { return orderedColumns; },
|
|
864
893
|
manualSorting: true,
|
|
865
|
-
getRowId: (row:
|
|
894
|
+
getRowId: (row: TableRecord) => tableRowKey(row.id),
|
|
866
895
|
state: {
|
|
867
896
|
get sorting() { return tableSorting.current; },
|
|
868
897
|
get columnVisibility() { return tableColumnVisibility.current; },
|
|
898
|
+
get rowSelection() { return rowSelection; },
|
|
869
899
|
},
|
|
870
900
|
onSortingChange: (updater) => {
|
|
871
901
|
sortingAtom.set(typeof updater === 'function' ? updater(tableSorting.current) : updater);
|
|
872
902
|
},
|
|
903
|
+
onRowSelectionChange: (updater) => {
|
|
904
|
+
rowSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
|
905
|
+
},
|
|
873
906
|
atoms: {
|
|
874
|
-
rowSelection: rowSelectionAtom,
|
|
875
907
|
expanded: expandedAtom,
|
|
876
908
|
},
|
|
877
909
|
autoResetExpanded: false,
|
|
@@ -882,6 +914,7 @@
|
|
|
882
914
|
);
|
|
883
915
|
|
|
884
916
|
function toggleAllRowsSelection(): void {
|
|
917
|
+
if (confirmPending || !canRead) return;
|
|
885
918
|
const willSelect = !table_getIsAllRowsSelected(tbl);
|
|
886
919
|
for (const row of tableView.rows) {
|
|
887
920
|
if (willSelect) {
|
|
@@ -894,9 +927,13 @@
|
|
|
894
927
|
}
|
|
895
928
|
|
|
896
929
|
const selectedIds = $derived(
|
|
897
|
-
Object.keys(
|
|
930
|
+
Object.keys(rowSelection).filter(key => rowSelection[key] === true)
|
|
931
|
+
.flatMap(key => {
|
|
932
|
+
const id = selectedIdValueByKey.get(key);
|
|
933
|
+
return id === undefined ? [] : [id];
|
|
934
|
+
})
|
|
898
935
|
);
|
|
899
|
-
const selectedCount = $derived(
|
|
936
|
+
const selectedCount = $derived(Object.keys(rowSelection).filter(key => rowSelection[key] === true).length);
|
|
900
937
|
const batchDeletePerm = useCan(() => ({
|
|
901
938
|
resource: resourceName,
|
|
902
939
|
action: 'delete',
|
|
@@ -904,13 +941,13 @@
|
|
|
904
941
|
queryOptions: { enabled: acEnabled && canDelete && selectedIds.length > 0 },
|
|
905
942
|
}));
|
|
906
943
|
const canBatchDelete = $derived(
|
|
907
|
-
canDelete && (!acEnabled ||
|
|
944
|
+
canDelete && (!acEnabled || batchDeletePerm.isLoading || batchDeletePerm.allowed)
|
|
908
945
|
);
|
|
909
946
|
|
|
910
947
|
function clearSelection(): void {
|
|
911
948
|
if (!deleteManyMutation.isPending) {
|
|
912
949
|
selectedIdValueByKey.clear();
|
|
913
|
-
|
|
950
|
+
table_resetRowSelection(tbl, true);
|
|
914
951
|
}
|
|
915
952
|
}
|
|
916
953
|
|
|
@@ -922,7 +959,7 @@
|
|
|
922
959
|
void tableSorting.current;
|
|
923
960
|
void tableColumnVisibility.current;
|
|
924
961
|
void orderedColumns;
|
|
925
|
-
void
|
|
962
|
+
void rowSelection;
|
|
926
963
|
void tableExpanded.current;
|
|
927
964
|
void tableColumnOrder.current;
|
|
928
965
|
const headerGroups = table_getHeaderGroups(tbl);
|
|
@@ -951,7 +988,8 @@
|
|
|
951
988
|
expandedAtom.set({ ...current, [rowId]: true });
|
|
952
989
|
}
|
|
953
990
|
|
|
954
|
-
const totalPages = $derived(
|
|
991
|
+
const totalPages = $derived(canRead && pageRecords.ok && !query.isError
|
|
992
|
+
? Math.ceil((query.data?.total ?? 0) / (pagination.pageSize ?? 10)) : 0);
|
|
955
993
|
|
|
956
994
|
// ─── Pagination helpers ───────────────────────────────────────
|
|
957
995
|
const currentPage = $derived(pagination.current ?? 1);
|
|
@@ -974,68 +1012,137 @@
|
|
|
974
1012
|
// ─── Confirm dialog ───────────────────────────────────────────
|
|
975
1013
|
let confirmOpen = $state(false);
|
|
976
1014
|
let confirmMessage = $state('');
|
|
977
|
-
let confirmAction = $state<() => void>(() => {});
|
|
978
1015
|
let confirmPending = $state(false);
|
|
1016
|
+
let operationError = $state<string | null>(null);
|
|
1017
|
+
let activeDelete: object | undefined;
|
|
979
1018
|
let detailOpenedInHistory = $state(false);
|
|
980
|
-
|
|
1019
|
+
let quickEditId = $state<string | number | undefined>();
|
|
1020
|
+
const tableScope = $derived({
|
|
1021
|
+
contract: binding.resource, resourceName, provider: adminContext.providers?.[binding.dataProviderName],
|
|
1022
|
+
meta: JSON.stringify(binding.meta), tenant: adminContext.tenantCacheKey?.__svadminTenant,
|
|
1023
|
+
auth: adminContext.authProvider, router: adminContext.routerProvider,
|
|
1024
|
+
permissionProvider: adminContext.accessControlProvider, canRead, canDelete,
|
|
1025
|
+
variables: deleteVariables,
|
|
1026
|
+
});
|
|
1027
|
+
let previousTableScope: typeof tableScope | undefined;
|
|
1028
|
+
const detailState = $derived.by(() => {
|
|
1029
|
+
const route = parsed.params['detail'];
|
|
1030
|
+
if (route === undefined || !canRead) return { id: undefined, invalid: false };
|
|
1031
|
+
try { return { id: parseContractRouteId(binding.resource, route), invalid: false }; }
|
|
1032
|
+
catch { return { id: undefined, invalid: true }; }
|
|
1033
|
+
});
|
|
1034
|
+
const detailRecordId = $derived(detailState.id);
|
|
981
1035
|
const detailOpen = $derived(detailRecordId != null);
|
|
1036
|
+
$effect.pre(() => {
|
|
1037
|
+
const scope = tableScope;
|
|
1038
|
+
if (previousTableScope && previousTableScope !== scope) {
|
|
1039
|
+
activeDelete = undefined;
|
|
1040
|
+
confirmOpen = false;
|
|
1041
|
+
confirmPending = false;
|
|
1042
|
+
deleteRequest = null;
|
|
1043
|
+
operationError = null;
|
|
1044
|
+
selectedIdValueByKey.clear();
|
|
1045
|
+
rowSelection = {};
|
|
1046
|
+
expandedAtom.set({});
|
|
1047
|
+
detailOpenedInHistory = false;
|
|
1048
|
+
quickEditId = undefined;
|
|
1049
|
+
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
|
1050
|
+
searchDebounceTimer = undefined;
|
|
1051
|
+
if (previousTableScope.resourceName !== scope.resourceName) resetToDefaultListState();
|
|
1052
|
+
}
|
|
1053
|
+
previousTableScope = scope;
|
|
1054
|
+
});
|
|
1055
|
+
$effect(() => {
|
|
1056
|
+
if (deleteRequest && !deletePermission.isLoading && !deletePermission.allowed) {
|
|
1057
|
+
confirmOpen = false;
|
|
1058
|
+
deleteRequest = null;
|
|
1059
|
+
operationError = i18n.t('common.operationFailed');
|
|
1060
|
+
}
|
|
1061
|
+
});
|
|
1062
|
+
onDestroy(() => { activeDelete = undefined; });
|
|
982
1063
|
|
|
983
1064
|
function confirmDelete(id: string | number) {
|
|
1065
|
+
if (!canRead || !pageRecords.ok || query.isError || !canDelete || confirmPending) return;
|
|
984
1066
|
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
|
-
};
|
|
1067
|
+
deleteRequest = { ids: [id], batch: false };
|
|
1068
|
+
operationError = null;
|
|
1001
1069
|
confirmOpen = true;
|
|
1002
1070
|
}
|
|
1003
1071
|
|
|
1004
1072
|
function confirmBatchDelete() {
|
|
1005
1073
|
const ids = [...selectedIds];
|
|
1006
|
-
if (ids.length === 0 ||
|
|
1007
|
-
batchOperationCount = ids.length;
|
|
1074
|
+
if (!canRead || !pageRecords.ok || query.isError || !canBatchDelete || ids.length === 0 || confirmPending) return;
|
|
1008
1075
|
confirmMessage = i18n.t('common.batchDeleteConfirm', { count: ids.length });
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1076
|
+
deleteRequest = { ids, batch: true };
|
|
1077
|
+
operationError = null;
|
|
1078
|
+
confirmOpen = true;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
async function confirmAction() {
|
|
1082
|
+
if (!confirmOpen || !pageRecords.ok || query.isError || !deleteAllowed || confirmPending || !deleteRequest) return;
|
|
1083
|
+
const request = deleteRequest;
|
|
1084
|
+
if (request.batch && JSON.stringify(request.ids) !== JSON.stringify(selectedIds)) {
|
|
1085
|
+
confirmOpen = false;
|
|
1086
|
+
deleteRequest = null;
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
const scope = tableScope;
|
|
1090
|
+
const token = {};
|
|
1091
|
+
activeDelete = token;
|
|
1092
|
+
confirmPending = true;
|
|
1093
|
+
confirmOpen = false;
|
|
1094
|
+
const clearBatchSelectionBeforeMutation = request.batch && getAdminOptions().mutationMode === 'undoable';
|
|
1095
|
+
const selectionBeforeDelete = clearBatchSelectionBeforeMutation ? new Map(selectedIdValueByKey) : undefined;
|
|
1096
|
+
if (clearBatchSelectionBeforeMutation) {
|
|
1097
|
+
selectedIdValueByKey.clear();
|
|
1098
|
+
table_resetRowSelection(tbl, true);
|
|
1099
|
+
}
|
|
1100
|
+
const current = () => activeDelete === token &&
|
|
1101
|
+
tableScope.contract === scope.contract &&
|
|
1102
|
+
tableScope.resourceName === scope.resourceName &&
|
|
1103
|
+
tableScope.provider === scope.provider &&
|
|
1104
|
+
tableScope.meta === scope.meta &&
|
|
1105
|
+
tableScope.tenant === scope.tenant &&
|
|
1106
|
+
tableScope.auth === scope.auth &&
|
|
1107
|
+
tableScope.router === scope.router &&
|
|
1108
|
+
tableScope.permissionProvider === scope.permissionProvider;
|
|
1109
|
+
try {
|
|
1110
|
+
await deleteManyMutation.mutateAsync({
|
|
1111
|
+
ids: [...request.ids],
|
|
1112
|
+
...definedOptions({ variables: deleteVariables, dataProviderName: binding.dataProviderName }),
|
|
1113
|
+
});
|
|
1114
|
+
if (!current()) return;
|
|
1115
|
+
selectedIdValueByKey.clear();
|
|
1116
|
+
table_resetRowSelection(tbl, true);
|
|
1117
|
+
await tick();
|
|
1118
|
+
confirmOpen = false;
|
|
1119
|
+
} catch (error) {
|
|
1120
|
+
if (!current()) return;
|
|
1121
|
+
if (error instanceof DeleteManyPartialError) {
|
|
1122
|
+
rowSelection = Object.fromEntries(error.failedIds.map(id => [tableRowKey(id), true as const]));
|
|
1018
1123
|
selectedIdValueByKey.clear();
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}
|
|
1022
|
-
if (
|
|
1023
|
-
|
|
1024
|
-
for (const id of
|
|
1025
|
-
|
|
1026
|
-
rowSelectionAtom.set(failedSelection);
|
|
1124
|
+
for (const id of error.failedIds) selectedIdValueByKey.set(tableRowKey(id), id);
|
|
1125
|
+
operationError = i18n.t('common.batchDeletePartialFail', { failed: error.failedIds.length, total: request.ids.length });
|
|
1126
|
+
} else {
|
|
1127
|
+
if (selectionBeforeDelete) {
|
|
1128
|
+
selectedIdValueByKey.clear();
|
|
1129
|
+
for (const [key, id] of selectionBeforeDelete) selectedIdValueByKey.set(key, id);
|
|
1130
|
+
rowSelection = Object.fromEntries([...selectionBeforeDelete.keys()].map(key => [key, true as const]));
|
|
1027
1131
|
}
|
|
1028
|
-
|
|
1029
|
-
} finally {
|
|
1030
|
-
confirmPending = false;
|
|
1132
|
+
operationError = i18n.t('common.operationFailed');
|
|
1031
1133
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1134
|
+
confirmOpen = false;
|
|
1135
|
+
} finally {
|
|
1136
|
+
if (activeDelete === token) { activeDelete = undefined; confirmPending = false; deleteRequest = null; }
|
|
1137
|
+
await tick();
|
|
1138
|
+
await tick();
|
|
1139
|
+
}
|
|
1034
1140
|
}
|
|
1035
1141
|
|
|
1036
1142
|
function openDetail(id: string | number): void {
|
|
1143
|
+
if (!canRead || !canShow) return;
|
|
1037
1144
|
detailOpenedInHistory = true;
|
|
1038
|
-
writeURLState({ detailId:
|
|
1145
|
+
writeURLState({ detailId: formatContractRouteId(binding.resource, id) }, adminContext, 'push');
|
|
1039
1146
|
}
|
|
1040
1147
|
|
|
1041
1148
|
function closeDetail(): void {
|
|
@@ -1050,33 +1157,60 @@
|
|
|
1050
1157
|
|
|
1051
1158
|
// ─── CSV Export ───────────────────────────────────────────────
|
|
1052
1159
|
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);
|
|
1160
|
+
if (!canRead || !canExport || !pageRecords.ok || query.isError || query.isFetching || !pageRecords.data.length) return;
|
|
1161
|
+
try {
|
|
1162
|
+
const exportFields = table_getAllLeafColumns(tbl).flatMap(column => {
|
|
1163
|
+
const field = visibleFields.find(field => field.key === column.id);
|
|
1164
|
+
return field && isColumnVisible(field.key) ? [field] : [];
|
|
1165
|
+
});
|
|
1166
|
+
if (!exportFields.length) return;
|
|
1167
|
+
const rows = tableExportRows(pageRecords.data, exportFields);
|
|
1168
|
+
downloadData(rows, resourceName, 'csv');
|
|
1169
|
+
} catch { operationError = i18n.t('common.operationFailed'); }
|
|
1071
1170
|
}
|
|
1072
1171
|
|
|
1073
1172
|
function goToPage(page: number) {
|
|
1173
|
+
if (!Number.isSafeInteger(page) || page < 1 || page > Math.max(1, totalPages)) return;
|
|
1074
1174
|
markSavedViewDirty();
|
|
1075
1175
|
pagination = { ...pagination, current: page };
|
|
1076
1176
|
}
|
|
1177
|
+
function refreshList() {
|
|
1178
|
+
if (canRead && !query.isFetching) void listResult.refetch();
|
|
1179
|
+
}
|
|
1077
1180
|
</script>
|
|
1078
1181
|
|
|
1182
|
+
{#snippet defaultRowActions(id: string | number)}
|
|
1183
|
+
<RecordRowActions
|
|
1184
|
+
{resourceName} {id} {canShow} {canEdit} {canDelete}
|
|
1185
|
+
onShow={() => openDetail(id)}
|
|
1186
|
+
onEdit={() => navigation.edit(resourceName, id)}
|
|
1187
|
+
onQuickEdit={() => quickEditId = id}
|
|
1188
|
+
onDelete={() => confirmDelete(id)}
|
|
1189
|
+
/>
|
|
1190
|
+
{/snippet}
|
|
1191
|
+
|
|
1192
|
+
{#snippet defaultEmptyState()}
|
|
1193
|
+
{@const hasCriteria = !!(searchText.trim() || appliedSearchText.trim() || activeFilterCount > 0)}
|
|
1194
|
+
<DataState state="empty" description={i18n.t(hasCriteria ? 'empty.description' : 'common.noDataHint')}>
|
|
1195
|
+
{#snippet action()}
|
|
1196
|
+
{#if hasCriteria}
|
|
1197
|
+
<Button variant="outline" size="sm" onclick={clearFilters}>
|
|
1198
|
+
<X class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1199
|
+
{i18n.t('common.clearAllFilters')}
|
|
1200
|
+
</Button>
|
|
1201
|
+
{:else if canCreate}
|
|
1202
|
+
<Button variant="outline" size="sm" onclick={() => navigation.create(resourceName)}>
|
|
1203
|
+
<Plus class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1204
|
+
{i18n.t('common.create')}
|
|
1205
|
+
</Button>
|
|
1206
|
+
{/if}
|
|
1207
|
+
{/snippet}
|
|
1208
|
+
</DataState>
|
|
1209
|
+
{/snippet}
|
|
1210
|
+
|
|
1079
1211
|
<div class="svadmin-u-6ed543e2fbbb">
|
|
1212
|
+
{#if operationError}<p role="alert">{operationError}</p>{/if}
|
|
1213
|
+
{#if detailState.invalid}<p role="alert">{i18n.t('common.operationFailed')}</p>{/if}
|
|
1080
1214
|
{#if showHeader}
|
|
1081
1215
|
<!-- Header -->
|
|
1082
1216
|
<div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-77a2a20e90d4">
|
|
@@ -1101,7 +1235,7 @@
|
|
|
1101
1235
|
|
|
1102
1236
|
<!-- Selection Banner (Enterprise Batch Actions) -->
|
|
1103
1237
|
{#if selectedCount > 0}
|
|
1104
|
-
|
|
1238
|
+
<div
|
|
1105
1239
|
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
1240
|
aria-label={i18n.t("common.selectedCount", { count: selectedCount })}
|
|
1107
1241
|
data-svadmin-batch-toolbar
|
|
@@ -1181,7 +1315,7 @@
|
|
|
1181
1315
|
id="filter-{field.key}"
|
|
1182
1316
|
class="svadmin-u-e7a768f922d2 svadmin-u-fc7473ca09eb"
|
|
1183
1317
|
value={filterValues[field.key] ?? ""}
|
|
1184
|
-
onchange={(e) =>
|
|
1318
|
+
onchange={(e: Event) => setFilterFromEvent(field.key, e)}
|
|
1185
1319
|
>
|
|
1186
1320
|
<option value="">{i18n.t("common.all")}</option>
|
|
1187
1321
|
{#each field.options as opt, _i (_i)}
|
|
@@ -1193,7 +1327,7 @@
|
|
|
1193
1327
|
id="filter-{field.key}"
|
|
1194
1328
|
type="text"
|
|
1195
1329
|
value={filterValues[field.key] ?? ""}
|
|
1196
|
-
oninput={(e) =>
|
|
1330
|
+
oninput={(e: Event) => setFilterFromEvent(field.key, e)}
|
|
1197
1331
|
placeholder={field.label}
|
|
1198
1332
|
class="svadmin-u-e7a768f922d2 svadmin-u-fc7473ca09eb"
|
|
1199
1333
|
/>
|
|
@@ -1225,7 +1359,7 @@
|
|
|
1225
1359
|
{#if showDensitySwitcher}
|
|
1226
1360
|
<DropdownMenu.Root>
|
|
1227
1361
|
<DropdownMenu.Trigger>
|
|
1228
|
-
{#snippet child({ props })}
|
|
1362
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
1229
1363
|
<TooltipButton tooltip={i18n.t("common.density")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props}>
|
|
1230
1364
|
<Rows class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" aria-hidden="true" />
|
|
1231
1365
|
</TooltipButton>
|
|
@@ -1245,7 +1379,7 @@
|
|
|
1245
1379
|
<!-- Column Visibility Picker -->
|
|
1246
1380
|
<DropdownMenu.Root>
|
|
1247
1381
|
<DropdownMenu.Trigger>
|
|
1248
|
-
{#snippet child({ props })}
|
|
1382
|
+
{#snippet child({ props }: { props: Record<string, unknown> })}
|
|
1249
1383
|
<TooltipButton tooltip={i18n.t("common.columns")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props}>
|
|
1250
1384
|
<SlidersHorizontal class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1251
1385
|
</TooltipButton>
|
|
@@ -1255,7 +1389,7 @@
|
|
|
1255
1389
|
{#each table_getAllLeafColumns(tbl).filter((column) => !column.id.startsWith("_")) as column, _i (_i)}
|
|
1256
1390
|
<DropdownMenu.CheckboxItem
|
|
1257
1391
|
checked={tableColumnVisibility.current[column.id] ?? true}
|
|
1258
|
-
onCheckedChange={(v) => setColumnVisibility(column.id,
|
|
1392
|
+
onCheckedChange={(v: boolean) => setColumnVisibility(column.id, v)}
|
|
1259
1393
|
>
|
|
1260
1394
|
{visibleFields.find(f => f.key === column.id)?.label ?? column.id}
|
|
1261
1395
|
</DropdownMenu.CheckboxItem>
|
|
@@ -1284,8 +1418,9 @@
|
|
|
1284
1418
|
id="saved-list-view"
|
|
1285
1419
|
class="svadmin-u-e7a768f922d2 svadmin-u-6da6a3c3f741"
|
|
1286
1420
|
value={activeSavedViewId ?? ""}
|
|
1287
|
-
onchange={(event) => {
|
|
1288
|
-
|
|
1421
|
+
onchange={(event: Event) => {
|
|
1422
|
+
if (!(event.currentTarget instanceof HTMLSelectElement)) return;
|
|
1423
|
+
const id = event.currentTarget.value;
|
|
1289
1424
|
const view = savedViews.find((candidate) => candidate.id === id);
|
|
1290
1425
|
if (view) applySavedView(view);
|
|
1291
1426
|
else {
|
|
@@ -1340,7 +1475,7 @@
|
|
|
1340
1475
|
variant="outline"
|
|
1341
1476
|
size="sm"
|
|
1342
1477
|
class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd"
|
|
1343
|
-
onclick={
|
|
1478
|
+
onclick={refreshList}
|
|
1344
1479
|
>
|
|
1345
1480
|
<RefreshCw class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3 {query.isFetching ? "svadmin-u-afbdd13a380e" : ""}" />
|
|
1346
1481
|
</TooltipButton>
|
|
@@ -1398,7 +1533,40 @@
|
|
|
1398
1533
|
|
|
1399
1534
|
<!-- Table (TanStack-powered) -->
|
|
1400
1535
|
<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}
|
|
1536
|
+
{#if listPermission.isLoading || (query.isLoading && !query.isError)}
|
|
1537
|
+
<Table.Root density={currentDensity}>
|
|
1538
|
+
<Table.Header>
|
|
1539
|
+
{#each tableView.headerGroups as headerGroup, _i (_i)}
|
|
1540
|
+
{@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
|
|
1541
|
+
<Table.Row>
|
|
1542
|
+
{#each visibleHeaders as header (header.id)}
|
|
1543
|
+
<Table.Head>
|
|
1544
|
+
{#if header.id === '_actions'}
|
|
1545
|
+
{i18n.t('common.actions')}
|
|
1546
|
+
{:else if column_getCanSort(header.column)}
|
|
1547
|
+
<Button
|
|
1548
|
+
variant="ghost"
|
|
1549
|
+
size="sm"
|
|
1550
|
+
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"
|
|
1551
|
+
onclick={() => toggleColumnSort(header.column)}
|
|
1552
|
+
>
|
|
1553
|
+
{visibleFields.find(field => field.key === header.id)?.label ?? header.id}
|
|
1554
|
+
<span class="svadmin-u-359090c2d529 svadmin-u-0b8c506a0596">
|
|
1555
|
+
{#if column_getIsSorted(header.column) === 'asc'}↑
|
|
1556
|
+
{:else if column_getIsSorted(header.column) === 'desc'}↓
|
|
1557
|
+
{:else}⇅
|
|
1558
|
+
{/if}
|
|
1559
|
+
</span>
|
|
1560
|
+
</Button>
|
|
1561
|
+
{:else}
|
|
1562
|
+
{visibleFields.find(field => field.key === header.id)?.label ?? header.id}
|
|
1563
|
+
{/if}
|
|
1564
|
+
</Table.Head>
|
|
1565
|
+
{/each}
|
|
1566
|
+
</Table.Row>
|
|
1567
|
+
{/each}
|
|
1568
|
+
</Table.Header>
|
|
1569
|
+
</Table.Root>
|
|
1402
1570
|
<div class="svadmin-u-8e63407b5ceb svadmin-u-6ed543e2fbbb">
|
|
1403
1571
|
<div class="svadmin-u-60fbb7713999 svadmin-u-0c3bc98565dd svadmin-u-a77ed4d908c0">
|
|
1404
1572
|
{#each visibleFields.slice(0, 4) as _, _i (_i)}
|
|
@@ -1413,11 +1581,13 @@
|
|
|
1413
1581
|
</div>
|
|
1414
1582
|
{/each}
|
|
1415
1583
|
</div>
|
|
1416
|
-
{:else if
|
|
1584
|
+
{:else if !canRead}
|
|
1585
|
+
<p role="alert">{i18n.t('common.operationFailed')}</p>
|
|
1586
|
+
{:else if query.error || query.isError || !pageRecords.ok}
|
|
1417
1587
|
<DataState
|
|
1418
1588
|
state="error"
|
|
1419
|
-
description={i18n.t('common.
|
|
1420
|
-
retry={
|
|
1589
|
+
description={i18n.t('common.operationFailed')}
|
|
1590
|
+
retry={refreshList}
|
|
1421
1591
|
/>
|
|
1422
1592
|
{:else}
|
|
1423
1593
|
<div in:fade={{ duration: 150 }}>
|
|
@@ -1426,13 +1596,13 @@
|
|
|
1426
1596
|
<Table.Root density={currentDensity}>
|
|
1427
1597
|
<Table.Header>
|
|
1428
1598
|
{#each tableView.headerGroups as headerGroup, _i (_i)}
|
|
1429
|
-
{@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures,
|
|
1599
|
+
{@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
|
|
1430
1600
|
<DraggableHeader
|
|
1431
1601
|
columns={visibleHeaders.map((header) => ({ id: header.column.id, header }))}
|
|
1432
1602
|
onReorder={setColumnOrder}
|
|
1433
1603
|
>
|
|
1434
1604
|
{#snippet header(col, _index, dragProps)}
|
|
1435
|
-
{@const header = col.header
|
|
1605
|
+
{@const header = col.header}
|
|
1436
1606
|
<Table.Head
|
|
1437
1607
|
{...dragProps}
|
|
1438
1608
|
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 +1615,12 @@
|
|
|
1445
1615
|
checked={table_getIsAllRowsSelected(tbl)}
|
|
1446
1616
|
onCheckedChange={toggleAllRowsSelection}
|
|
1447
1617
|
/>
|
|
1448
|
-
{:else}
|
|
1449
|
-
<CanAccess resource={resourceName} action="delete" params={{ ids: tableView.rows.map(rowIdValue) }}>
|
|
1618
|
+
{:else if !acEnabled || deleteResourcePermission.isLoading || deleteResourcePermission.allowed}
|
|
1450
1619
|
<Checkbox
|
|
1451
1620
|
aria-label={i18n.t('common.selectAll')}
|
|
1452
1621
|
checked={table_getIsAllRowsSelected(tbl)}
|
|
1453
1622
|
onCheckedChange={toggleAllRowsSelection}
|
|
1454
1623
|
/>
|
|
1455
|
-
</CanAccess>
|
|
1456
1624
|
{/if}
|
|
1457
1625
|
{:else if header.id === '_expand'}
|
|
1458
1626
|
<!-- empty -->
|
|
@@ -1483,8 +1651,8 @@
|
|
|
1483
1651
|
</Table.Header>
|
|
1484
1652
|
<Table.Body>
|
|
1485
1653
|
{#each tableView.rows as row (row.id)}
|
|
1486
|
-
{@const record = row.original}
|
|
1487
|
-
{@const id =
|
|
1654
|
+
{@const record = copyTableRecord(row.original)}
|
|
1655
|
+
{@const id = rowIdValue(row)}
|
|
1488
1656
|
{@const visibleCells = row_getVisibleCells(row).filter((cell) => isColumnVisible(cell.column.id))}
|
|
1489
1657
|
<ContextMenu.Root>
|
|
1490
1658
|
<ContextMenu.Trigger>
|
|
@@ -1521,65 +1689,32 @@
|
|
|
1521
1689
|
{#if rowActions}
|
|
1522
1690
|
{@render rowActions({ record, id })}
|
|
1523
1691
|
{: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}
|
|
1692
|
+
{@render defaultRowActions(id)}
|
|
1545
1693
|
{/if}
|
|
1546
1694
|
</div>
|
|
1547
1695
|
{:else}
|
|
1548
1696
|
{@const field = visibleFields.find(f => f.key === cell.column.id)}
|
|
1549
|
-
{
|
|
1550
|
-
|
|
1697
|
+
{@const customColumn = field ? customColumns?.[field.key] : undefined}
|
|
1698
|
+
{#if customColumn}
|
|
1699
|
+
{@render customColumn({ value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
|
|
1551
1700
|
{: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)}
|
|
1701
|
+
{@render defaultCellRenderer({ field, value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
|
|
1702
|
+
{: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
1703
|
<CanAccess resource={resourceName} action="edit" params={{ id }}>
|
|
1568
1704
|
<InlineEdit
|
|
1569
1705
|
{resourceName}
|
|
1570
1706
|
recordId={id}
|
|
1571
1707
|
{field}
|
|
1572
1708
|
value={cell_getValue(cell)}
|
|
1573
|
-
onSave={() => listResult.refetch()}
|
|
1574
1709
|
/>
|
|
1575
1710
|
{#snippet fallback()}
|
|
1576
|
-
<
|
|
1711
|
+
<FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} />
|
|
1577
1712
|
{/snippet}
|
|
1578
1713
|
</CanAccess>
|
|
1579
|
-
{:else if field?.key ===
|
|
1714
|
+
{:else if field?.key === 'id'}
|
|
1580
1715
|
<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
|
-
<
|
|
1716
|
+
{:else if field}
|
|
1717
|
+
<FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} resourceName={field.resource} />
|
|
1583
1718
|
{/if}
|
|
1584
1719
|
{/if}
|
|
1585
1720
|
</Table.Cell>
|
|
@@ -1628,19 +1763,7 @@
|
|
|
1628
1763
|
{#if emptyState}
|
|
1629
1764
|
{@render emptyState()}
|
|
1630
1765
|
{: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>
|
|
1766
|
+
{@render defaultEmptyState()}
|
|
1644
1767
|
{/if}
|
|
1645
1768
|
</Table.Cell>
|
|
1646
1769
|
</Table.Row>
|
|
@@ -1648,7 +1771,7 @@
|
|
|
1648
1771
|
</Table.Body>
|
|
1649
1772
|
{#if summary}
|
|
1650
1773
|
<Table.Footer class="svadmin-u-2859c861d7de svadmin-u-2689f3958069">
|
|
1651
|
-
{@render summary({ data:
|
|
1774
|
+
{@render summary({ data: pageRecords.data.map(copyTableRecord), total: query.data?.total ?? 0, visibleColumnsCount: columns.filter((c) => c.id != null && isColumnVisible(c.id)).length })}
|
|
1652
1775
|
</Table.Footer>
|
|
1653
1776
|
{/if}
|
|
1654
1777
|
</Table.Root>
|
|
@@ -1657,8 +1780,8 @@
|
|
|
1657
1780
|
<!-- Mobile Card View (visible only on small screens) -->
|
|
1658
1781
|
<div class="svadmin-u-e477a6af4cb6 svadmin-u-6ed543e2fbbb svadmin-u-7660b450905a">
|
|
1659
1782
|
{#each tableView.rows as row, _i (_i)}
|
|
1660
|
-
{@const record = row.original}
|
|
1661
|
-
{@const id =
|
|
1783
|
+
{@const record = copyTableRecord(row.original)}
|
|
1784
|
+
{@const id = rowIdValue(row)}
|
|
1662
1785
|
<div
|
|
1663
1786
|
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
1787
|
>
|
|
@@ -1688,27 +1811,7 @@
|
|
|
1688
1811
|
{#if rowActions}
|
|
1689
1812
|
{@render rowActions({ record, id })}
|
|
1690
1813
|
{: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}
|
|
1814
|
+
{@render defaultRowActions(id)}
|
|
1712
1815
|
{/if}
|
|
1713
1816
|
</div>
|
|
1714
1817
|
</div>
|
|
@@ -1716,28 +1819,18 @@
|
|
|
1716
1819
|
<div class="svadmin-u-6f7e013d6499">
|
|
1717
1820
|
{#each visibleFields.filter((field) => isColumnVisible(field.key)).slice(0, 6) as field (field.key)}
|
|
1718
1821
|
{@const value = record[field.key]}
|
|
1822
|
+
{@const customColumn = customColumns?.[field.key]}
|
|
1719
1823
|
<div class="svadmin-u-60fbb7713999 svadmin-u-60541e1e26f8 svadmin-u-8ef2268efbbc svadmin-u-0c3bc98565dd">
|
|
1720
1824
|
<span class="svadmin-u-359090c2d529 svadmin-u-2689f3958069 svadmin-u-bfa603190748 svadmin-u-012fbd121f37">{field.label}</span>
|
|
1721
1825
|
<span class="svadmin-u-fc7473ca09eb svadmin-u-308fc069e46e svadmin-u-f283ea9bea0e svadmin-u-897d497e57b6">
|
|
1722
|
-
{#if
|
|
1723
|
-
{@render
|
|
1826
|
+
{#if customColumn}
|
|
1827
|
+
{@render customColumn({ value: clonePlainValue(value), record: copyTableRecord(record) })}
|
|
1724
1828
|
{: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>
|
|
1829
|
+
{@render defaultCellRenderer({ field, value: clonePlainValue(value), record: copyTableRecord(record) })}
|
|
1830
|
+
{:else if field.key === 'id'}
|
|
1831
|
+
{id}
|
|
1739
1832
|
{:else}
|
|
1740
|
-
{value
|
|
1833
|
+
<FieldDisplay type={field.type} {value} options={field.options} resourceName={field.resource} />
|
|
1741
1834
|
{/if}
|
|
1742
1835
|
</span>
|
|
1743
1836
|
</div>
|
|
@@ -1749,7 +1842,7 @@
|
|
|
1749
1842
|
{#if emptyState}
|
|
1750
1843
|
{@render emptyState()}
|
|
1751
1844
|
{:else}
|
|
1752
|
-
|
|
1845
|
+
{@render defaultEmptyState()}
|
|
1753
1846
|
{/if}
|
|
1754
1847
|
</div>
|
|
1755
1848
|
{/each}
|
|
@@ -1767,9 +1860,10 @@
|
|
|
1767
1860
|
aria-label={i18n.t('common.perPage')}
|
|
1768
1861
|
class="svadmin-u-ed8a5df7b2fb svadmin-u-d043cad8e3fa svadmin-u-012fbd121f37 svadmin-u-3032cae0badb"
|
|
1769
1862
|
value={String(pagination.pageSize ?? 10)}
|
|
1770
|
-
onchange={(e) => {
|
|
1771
|
-
|
|
1772
|
-
|
|
1863
|
+
onchange={(e: Event) => {
|
|
1864
|
+
if (!(e.currentTarget instanceof HTMLSelectElement)) return;
|
|
1865
|
+
const size = Number(e.currentTarget.value);
|
|
1866
|
+
if (Number.isSafeInteger(size) && size > 0) {
|
|
1773
1867
|
markSavedViewDirty();
|
|
1774
1868
|
pagination = { ...pagination, pageSize: size, current: 1 };
|
|
1775
1869
|
}
|
|
@@ -1797,7 +1891,7 @@
|
|
|
1797
1891
|
<PaginationUI.Link
|
|
1798
1892
|
class="svadmin-u-3032cae0badb"
|
|
1799
1893
|
isActive={page === currentPage}
|
|
1800
|
-
onclick={() =>
|
|
1894
|
+
onclick={() => { if (typeof page === 'number') goToPage(page); }}
|
|
1801
1895
|
>
|
|
1802
1896
|
{page}
|
|
1803
1897
|
</PaginationUI.Link>
|
|
@@ -1820,9 +1914,9 @@
|
|
|
1820
1914
|
open={confirmOpen}
|
|
1821
1915
|
message={confirmMessage}
|
|
1822
1916
|
confirmText={i18n.t('common.delete')}
|
|
1823
|
-
confirming={confirmPending}
|
|
1917
|
+
confirming={confirmPending || deletePermission.isLoading}
|
|
1824
1918
|
onconfirm={confirmAction}
|
|
1825
|
-
oncancel={() => { confirmOpen = false; }}
|
|
1919
|
+
oncancel={() => { if (!confirmPending) { confirmOpen = false; deleteRequest = null; } }}
|
|
1826
1920
|
/>
|
|
1827
1921
|
|
|
1828
1922
|
{#if detailRecordId != null}
|
|
@@ -1833,3 +1927,9 @@
|
|
|
1833
1927
|
onClose={closeDetail}
|
|
1834
1928
|
/>
|
|
1835
1929
|
{/if}
|
|
1930
|
+
|
|
1931
|
+
{#if quickEditId != null && canRead && canEdit}
|
|
1932
|
+
{#key quickEditId}
|
|
1933
|
+
<QuickEditDrawer {resourceName} recordId={quickEditId} onClose={() => quickEditId = undefined} />
|
|
1934
|
+
{/key}
|
|
1935
|
+
{/if}
|