@raclettejs/core 0.1.36 → 0.1.38
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/CHANGELOG.md +32 -7
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +2 -2
- package/package.json +2 -2
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/composition/composition.schema.ts +10 -4
- package/services/backend/src/corePlugins/raclette__core/backend/datatypes/interactionLink/interactionLink.schema.ts +4 -1
- package/services/backend/src/shared/schemas/core/CompositionCreate.schema.ts +4 -0
- package/services/backend/src/shared/schemas/core/InteractionLinkCreate.schema.ts +4 -0
- package/services/backend/src/shared/types/core/CompositionCreate.types.ts +1 -0
- package/services/backend/src/shared/types/core/InteractionLinkCreate.types.ts +1 -0
- package/services/frontend/src/core/lib/scheduleTask.ts +35 -0
- package/services/frontend/src/core/store/index.ts +5 -1
- package/services/frontend/src/core/store/reducers/compositionSlots/reducers.ts +1 -5
- package/services/frontend/src/core/store/reducers/data/reducers.ts +101 -65
- package/services/frontend/src/core/store/reducers/metaReducer.ts +23 -0
- package/services/frontend/src/core/store/reducers/notifications/reducers.ts +39 -47
- package/services/frontend/src/core/store/reducers/queries/effects.ts +55 -2
- package/services/frontend/src/core/store/reducers/queries/reducers.ts +60 -62
- package/services/frontend/src/core/store/reducers/queriesCache/effects.ts +105 -0
- package/services/frontend/src/core/store/reducers/queriesCache/index.ts +2 -1
- package/services/frontend/src/core/store/reducers/queriesCache/queryCacheHelper.ts +79 -9
- package/services/frontend/src/core/store/reducers/queriesCache/reducers.ts +95 -100
- package/services/frontend/src/orchestrator/components/CompositionLoadingState.vue +28 -0
- package/services/frontend/src/orchestrator/components/composition/CompositionOverlay.vue +5 -7
- package/services/frontend/src/orchestrator/components/composition/WidgetsLayoutLoader.vue +171 -39
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +336 -56
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableConfirmDeleteBtn.vue +27 -0
- package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableFilterDrawerShell.vue +72 -0
- package/services/frontend/src/orchestrator/components/index.ts +4 -0
- package/services/frontend/src/orchestrator/composables/index.ts +1 -0
- package/services/frontend/src/orchestrator/composables/useBaseDataTableDeleteConfirm.ts +23 -0
- package/services/frontend/src/orchestrator/composables/useVueWriteOperationHelper.ts +16 -4
- package/services/frontend/src/orchestrator/composables/useWidgetLifecycle.ts +53 -23
- package/services/frontend/src/orchestrator/constants/widgetSlotType.ts +4 -0
- package/services/frontend/src/orchestrator/i18n/de-DE.json +7 -1
- package/services/frontend/src/orchestrator/i18n/en-EU.json +7 -1
- package/services/frontend/src/orchestrator/i18n/sk.json +7 -1
- package/services/frontend/src/orchestrator/router/routerHooks/afterEach.ts +15 -9
- package/services/frontend/src/orchestrator/types/DataApi.ts +2 -1
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
class="base-data-table-host tw:relative tw:min-h-0"
|
|
4
|
+
:class="{
|
|
5
|
+
'base-data-table-host--filter-open':
|
|
6
|
+
isFilterDrawerContained && isFilterDrawerOpen,
|
|
7
|
+
}"
|
|
8
|
+
>
|
|
3
9
|
<v-data-table
|
|
4
10
|
v-model="selected"
|
|
5
11
|
:search="showSearch ? search : undefined"
|
|
@@ -43,6 +49,22 @@
|
|
|
43
49
|
class="tw:flex tw:min-w-0 tw:w-full tw:flex-wrap tw:items-center tw:justify-end tw:gap-2"
|
|
44
50
|
>
|
|
45
51
|
<slot name="toolbar-leading" />
|
|
52
|
+
<slot
|
|
53
|
+
name="toolbar-indicators"
|
|
54
|
+
v-bind="toolbarIndicatorsSlotProps"
|
|
55
|
+
>
|
|
56
|
+
<v-chip
|
|
57
|
+
v-for="indicator in toolbarIndicators"
|
|
58
|
+
:key="indicator.id"
|
|
59
|
+
:color="indicator.color"
|
|
60
|
+
:prepend-icon="indicator.icon"
|
|
61
|
+
label
|
|
62
|
+
size="small"
|
|
63
|
+
variant="tonal"
|
|
64
|
+
>
|
|
65
|
+
{{ indicator.label }}
|
|
66
|
+
</v-chip>
|
|
67
|
+
</slot>
|
|
46
68
|
<slot name="toolbar-end" />
|
|
47
69
|
<slot
|
|
48
70
|
name="toolbar-filters-trigger"
|
|
@@ -68,11 +90,30 @@
|
|
|
68
90
|
<template #activator="{ props: menuProps }">
|
|
69
91
|
<v-btn
|
|
70
92
|
v-bind="menuProps"
|
|
71
|
-
icon="mdi-dots-vertical"
|
|
72
93
|
variant="text"
|
|
73
94
|
density="comfortable"
|
|
74
95
|
:aria-label="$t('core.baseDataTable.actionsMenu')"
|
|
75
|
-
|
|
96
|
+
>
|
|
97
|
+
<v-badge
|
|
98
|
+
v-if="hasToolbarIndicators"
|
|
99
|
+
dot
|
|
100
|
+
:color="toolbarIndicators[0]?.color ?? 'primary'"
|
|
101
|
+
:content="
|
|
102
|
+
toolbarIndicators.length > 1
|
|
103
|
+
? toolbarIndicators.length
|
|
104
|
+
: undefined
|
|
105
|
+
"
|
|
106
|
+
>
|
|
107
|
+
<v-icon icon="mdi-dots-vertical" />
|
|
108
|
+
</v-badge>
|
|
109
|
+
<v-icon v-else icon="mdi-dots-vertical" />
|
|
110
|
+
<v-tooltip
|
|
111
|
+
v-if="hasToolbarIndicators"
|
|
112
|
+
activator="parent"
|
|
113
|
+
location="bottom"
|
|
114
|
+
:text="activeModesTooltip"
|
|
115
|
+
/>
|
|
116
|
+
</v-btn>
|
|
76
117
|
</template>
|
|
77
118
|
<v-list density="compact" nav min-width="260">
|
|
78
119
|
<slot
|
|
@@ -142,6 +183,7 @@
|
|
|
142
183
|
:disabled="!selected.length"
|
|
143
184
|
:formats="exportFormats"
|
|
144
185
|
:default-format="exportDefaultFormat"
|
|
186
|
+
:show-data-scope-switch="exportShowDataScopeSwitch"
|
|
145
187
|
:button-label="$t('core.export')"
|
|
146
188
|
/>
|
|
147
189
|
</slot>
|
|
@@ -219,14 +261,14 @@
|
|
|
219
261
|
<!-- Additional dialogs slot -->
|
|
220
262
|
<slot name="dialogs" />
|
|
221
263
|
|
|
222
|
-
<
|
|
264
|
+
<BaseDataTableFilterDrawerShell
|
|
265
|
+
v-if="showFilters"
|
|
223
266
|
v-model="isFilterDrawerOpen"
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
width="360"
|
|
267
|
+
:contained="isFilterDrawerContained"
|
|
268
|
+
:width="filterDrawerWidth"
|
|
227
269
|
:aria-label="$t('core.baseDataTable.filters')"
|
|
228
270
|
>
|
|
229
|
-
<div class="tw:flex tw:flex-col tw:h-full">
|
|
271
|
+
<div class="tw:flex tw:flex-col tw:h-full tw:min-h-0">
|
|
230
272
|
<slot name="filters.summary" v-bind="filterSummarySlotProps" />
|
|
231
273
|
<slot name="filters.drawer.header" v-bind="filterDrawerHeaderSlotProps">
|
|
232
274
|
<div
|
|
@@ -245,35 +287,47 @@
|
|
|
245
287
|
</div>
|
|
246
288
|
</slot>
|
|
247
289
|
|
|
248
|
-
<
|
|
249
|
-
<
|
|
250
|
-
<
|
|
251
|
-
<
|
|
252
|
-
v-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
290
|
+
<div class="tw:flex-1 tw:min-h-0 tw:overflow-y-auto">
|
|
291
|
+
<slot name="filters.drawer.body" v-bind="filterDrawerBodySlotProps">
|
|
292
|
+
<div class="tw:px-4 tw:pb-4 tw:flex tw:flex-col tw:gap-3">
|
|
293
|
+
<template
|
|
294
|
+
v-for="header in filterableHeaders"
|
|
295
|
+
:key="String(header.key)"
|
|
296
|
+
>
|
|
297
|
+
<v-autocomplete
|
|
298
|
+
v-if="columnUsesComboboxFilter(String(header.key))"
|
|
299
|
+
v-model="columnFilters[String(header.key)]"
|
|
300
|
+
@update:model-value="notifyFilterStateChange"
|
|
301
|
+
:items="filterOptionsByKey[String(header.key)] ?? []"
|
|
302
|
+
:label="header.title"
|
|
303
|
+
clearable
|
|
304
|
+
hide-details
|
|
305
|
+
density="compact"
|
|
306
|
+
variant="outlined"
|
|
307
|
+
/>
|
|
308
|
+
<v-text-field
|
|
309
|
+
v-else
|
|
310
|
+
v-model="columnFilters[String(header.key)]"
|
|
311
|
+
@update:model-value="notifyFilterStateChange"
|
|
312
|
+
:label="header.title"
|
|
313
|
+
clearable
|
|
314
|
+
hide-details
|
|
315
|
+
density="compact"
|
|
316
|
+
variant="outlined"
|
|
317
|
+
/>
|
|
318
|
+
</template>
|
|
319
|
+
</div>
|
|
320
|
+
</slot>
|
|
321
|
+
</div>
|
|
275
322
|
|
|
276
|
-
<div
|
|
323
|
+
<div
|
|
324
|
+
class="base-data-table-filter-drawer-footer tw:mt-auto tw:px-4 tw:pt-2 tw:flex tw:gap-2 tw:bg-surface tw:z-10"
|
|
325
|
+
:class="{
|
|
326
|
+
'base-data-table-filter-drawer-footer--contained':
|
|
327
|
+
isFilterDrawerContained,
|
|
328
|
+
}"
|
|
329
|
+
:style="filterDrawerFooterStyle"
|
|
330
|
+
>
|
|
277
331
|
<slot
|
|
278
332
|
name="filters.drawer.footer"
|
|
279
333
|
v-bind="filterDrawerFooterSlotProps"
|
|
@@ -282,6 +336,7 @@
|
|
|
282
336
|
variant="outlined"
|
|
283
337
|
prepend-icon="mdi-filter-remove"
|
|
284
338
|
:text="$t('core.baseDataTable.resetFilters')"
|
|
339
|
+
class="base-data-table-filter-drawer-footer-btn"
|
|
285
340
|
@click="resetFilters"
|
|
286
341
|
/>
|
|
287
342
|
<v-btn
|
|
@@ -289,25 +344,66 @@
|
|
|
289
344
|
variant="flat"
|
|
290
345
|
prepend-icon="mdi-check"
|
|
291
346
|
:text="$t('core.baseDataTable.applyFilters')"
|
|
347
|
+
class="base-data-table-filter-drawer-footer-btn"
|
|
292
348
|
@click="applyFilters"
|
|
293
349
|
/>
|
|
294
350
|
</slot>
|
|
295
351
|
</div>
|
|
296
352
|
</div>
|
|
297
|
-
</
|
|
353
|
+
</BaseDataTableFilterDrawerShell>
|
|
354
|
+
|
|
355
|
+
<v-dialog v-model="deleteDialogOpen" max-width="400">
|
|
356
|
+
<v-card>
|
|
357
|
+
<v-card-title>{{ resolvedDeleteConfirmTitle }}</v-card-title>
|
|
358
|
+
<v-card-text>{{ deleteConfirmMessageText }}</v-card-text>
|
|
359
|
+
<v-card-actions>
|
|
360
|
+
<v-spacer />
|
|
361
|
+
<v-btn variant="text" @click="cancelDelete">
|
|
362
|
+
{{ $t("core.cancel") }}
|
|
363
|
+
</v-btn>
|
|
364
|
+
<v-btn color="error" @click="confirmDeleteAction">
|
|
365
|
+
{{ $t("core.baseDataTable.delete") }}
|
|
366
|
+
</v-btn>
|
|
367
|
+
</v-card-actions>
|
|
368
|
+
</v-card>
|
|
369
|
+
</v-dialog>
|
|
298
370
|
</div>
|
|
299
371
|
</template>
|
|
300
372
|
|
|
301
373
|
<script setup lang="ts" generic="T extends Record<string, any>">
|
|
302
|
-
import {
|
|
374
|
+
import {
|
|
375
|
+
Comment,
|
|
376
|
+
computed,
|
|
377
|
+
inject,
|
|
378
|
+
provide,
|
|
379
|
+
ref,
|
|
380
|
+
useSlots,
|
|
381
|
+
useTemplateRef,
|
|
382
|
+
watch,
|
|
383
|
+
} from "vue"
|
|
384
|
+
import { useI18n } from "vue-i18n"
|
|
303
385
|
import DataExporter from "@racletteOrchestrator/components/dataExport/DataExporter.vue"
|
|
304
386
|
import FileUpload from "@racletteOrchestrator/components/input/FileUpload.vue"
|
|
387
|
+
import BaseDataTableFilterDrawerShell from "@racletteOrchestrator/components/dataTable/BaseDataTableFilterDrawerShell.vue"
|
|
388
|
+
import {
|
|
389
|
+
WIDGET_SLOT_TYPE_KEY,
|
|
390
|
+
type WidgetSlotType,
|
|
391
|
+
} from "@racletteOrchestrator/constants/widgetSlotType"
|
|
392
|
+
import { BASE_DATA_TABLE_REQUEST_DELETE_KEY } from "@racletteOrchestrator/composables/useBaseDataTableDeleteConfirm"
|
|
393
|
+
import configService from "@racletteCore/lib/configService"
|
|
305
394
|
import type {
|
|
306
395
|
ExportPayload,
|
|
307
396
|
FormatEntry,
|
|
308
397
|
} from "@racletteOrchestrator/composables/useExport/types"
|
|
309
398
|
import type { DataTableItem } from "vuetify/lib/components/VDataTable/types"
|
|
310
399
|
|
|
400
|
+
export interface BaseDataTableToolbarIndicator {
|
|
401
|
+
id: string
|
|
402
|
+
label: string
|
|
403
|
+
color?: string
|
|
404
|
+
icon?: string
|
|
405
|
+
}
|
|
406
|
+
|
|
311
407
|
export interface BaseDataTableActionContext<T> {
|
|
312
408
|
item?: T
|
|
313
409
|
filters: Record<string, string>
|
|
@@ -352,10 +448,18 @@ export interface BaseDataTableProps<T> {
|
|
|
352
448
|
showLoading?: boolean
|
|
353
449
|
/** Max distinct values per column before filter falls back to a text field. */
|
|
354
450
|
filterMaxSelectOptions?: number
|
|
451
|
+
/**
|
|
452
|
+
* Where the filter drawer attaches: `auto` uses parent containment when
|
|
453
|
+
* `WidgetsLayoutLoader` slotType is `modal`, otherwise full viewport.
|
|
454
|
+
*/
|
|
455
|
+
filterDrawerAttach?: "auto" | "viewport" | "parent"
|
|
456
|
+
/** Generic toolbar mode indicators (badge on ⋮ menu, optional chips). */
|
|
457
|
+
toolbarIndicators?: BaseDataTableToolbarIndicator[]
|
|
355
458
|
|
|
356
459
|
itemValue?: string
|
|
357
460
|
/** Vuetify group-by key(s), e.g. `"project"` for project-grouped rows. */
|
|
358
461
|
groupBy?: string | readonly string[]
|
|
462
|
+
/** Overrides `core.baseDataTable.actions` for the row-actions column header. */
|
|
359
463
|
actionsHeaderTitle?: string
|
|
360
464
|
|
|
361
465
|
/**
|
|
@@ -364,6 +468,13 @@ export interface BaseDataTableProps<T> {
|
|
|
364
468
|
*/
|
|
365
469
|
exportableItems?: Record<string, unknown>[]
|
|
366
470
|
exportMeta?: Record<string, unknown>
|
|
471
|
+
/**
|
|
472
|
+
* `visible-columns` (default): export only keys present in `headers`.
|
|
473
|
+
* `full-records`: export matched rows as-is (for round-trip import of canonical records).
|
|
474
|
+
*/
|
|
475
|
+
exportRowShape?: "visible-columns" | "full-records"
|
|
476
|
+
/** When false, hides the DataExporter items/meta/combined scope toggle. */
|
|
477
|
+
exportShowDataScopeSwitch?: boolean
|
|
367
478
|
|
|
368
479
|
/** Formats offered in the export menu (workbench list tables default to JSON only). */
|
|
369
480
|
exportFormats?: FormatEntry[]
|
|
@@ -386,6 +497,13 @@ export interface BaseDataTableProps<T> {
|
|
|
386
497
|
payload: Record<string, string>,
|
|
387
498
|
context: BaseDataTableActionContext<T>,
|
|
388
499
|
) => void
|
|
500
|
+
|
|
501
|
+
/** When true (default), row deletes via `requestDelete` show a confirmation dialog. */
|
|
502
|
+
confirmDelete?: boolean
|
|
503
|
+
deleteConfirmTitle?: string
|
|
504
|
+
getDeleteConfirmMessage?: (item: T) => string
|
|
505
|
+
/** Used when `itemsDeleted` is true (hard-delete mode). Falls back to soft-delete message. */
|
|
506
|
+
getHardDeleteConfirmMessage?: (item: T) => string
|
|
389
507
|
}
|
|
390
508
|
|
|
391
509
|
const props = withDefaults(defineProps<BaseDataTableProps<T>>(), {
|
|
@@ -401,11 +519,15 @@ const props = withDefaults(defineProps<BaseDataTableProps<T>>(), {
|
|
|
401
519
|
showSearch: true,
|
|
402
520
|
showLoading: true,
|
|
403
521
|
filterMaxSelectOptions: 300,
|
|
522
|
+
filterDrawerAttach: "auto",
|
|
523
|
+
toolbarIndicators: () => [],
|
|
404
524
|
itemValue: "_id",
|
|
405
525
|
groupBy: undefined,
|
|
406
526
|
actionsHeaderTitle: "",
|
|
407
527
|
exportableItems: undefined,
|
|
408
528
|
exportMeta: undefined,
|
|
529
|
+
exportRowShape: "visible-columns",
|
|
530
|
+
exportShowDataScopeSwitch: true,
|
|
409
531
|
exportFormats: () => ["json"] as FormatEntry[],
|
|
410
532
|
exportDefaultFormat: "json",
|
|
411
533
|
onFileLoaded: undefined,
|
|
@@ -414,6 +536,10 @@ const props = withDefaults(defineProps<BaseDataTableProps<T>>(), {
|
|
|
414
536
|
onFilterStateChange: undefined,
|
|
415
537
|
onFilterApply: undefined,
|
|
416
538
|
onFilterReset: undefined,
|
|
539
|
+
confirmDelete: true,
|
|
540
|
+
deleteConfirmTitle: undefined,
|
|
541
|
+
getDeleteConfirmMessage: undefined,
|
|
542
|
+
getHardDeleteConfirmMessage: undefined,
|
|
417
543
|
})
|
|
418
544
|
|
|
419
545
|
const emit = defineEmits<{
|
|
@@ -421,28 +547,130 @@ const emit = defineEmits<{
|
|
|
421
547
|
"update:currentItems": [items: readonly DataTableItem<T>[]]
|
|
422
548
|
}>()
|
|
423
549
|
|
|
424
|
-
|
|
550
|
+
const { t } = useI18n()
|
|
425
551
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
if (
|
|
430
|
-
|
|
431
|
-
|
|
552
|
+
const widgetSlotType = inject(WIDGET_SLOT_TYPE_KEY, ref<WidgetSlotType>("page"))
|
|
553
|
+
|
|
554
|
+
const resolvedFilterDrawerAttach = computed(() => {
|
|
555
|
+
if (props.filterDrawerAttach !== "auto") {
|
|
556
|
+
return props.filterDrawerAttach
|
|
557
|
+
}
|
|
558
|
+
return widgetSlotType.value === "modal" ? "parent" : "viewport"
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
const isFilterDrawerContained = computed(
|
|
562
|
+
() => resolvedFilterDrawerAttach.value === "parent",
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
const filterDrawerWidth = computed(() =>
|
|
566
|
+
isFilterDrawerContained.value ? 320 : 360,
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
const showRacletteFooterAdvert = computed(() =>
|
|
570
|
+
configService.getPleaseLetUsAdvertiseOurselves(),
|
|
571
|
+
)
|
|
572
|
+
|
|
573
|
+
const filterDrawerFooterStyle = computed(() => {
|
|
574
|
+
const style: Record<string, string> = {
|
|
575
|
+
paddingBottom: "1rem",
|
|
576
|
+
}
|
|
577
|
+
if (!isFilterDrawerContained.value && showRacletteFooterAdvert.value) {
|
|
578
|
+
style.paddingBottom = "4.5rem"
|
|
579
|
+
}
|
|
580
|
+
return style
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
const hasToolbarIndicators = computed(
|
|
584
|
+
() => (props.toolbarIndicators?.length ?? 0) > 0,
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
const activeModesTooltip = computed(() => {
|
|
588
|
+
if (!hasToolbarIndicators.value) {
|
|
589
|
+
return ""
|
|
590
|
+
}
|
|
591
|
+
return t("core.baseDataTable.activeModesTooltip", {
|
|
592
|
+
modes: (props.toolbarIndicators ?? []).map((indicator) => indicator.label).join(", "),
|
|
593
|
+
})
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
const deleteDialogOpen = ref(false)
|
|
597
|
+
const pendingDeleteItem = ref<T | null>(null)
|
|
598
|
+
let pendingDeleteCallback: (() => void | Promise<void>) | null = null
|
|
599
|
+
|
|
600
|
+
const resolvedDeleteConfirmTitle = computed(
|
|
601
|
+
() => props.deleteConfirmTitle ?? t("core.baseDataTable.confirmDeleteTitle"),
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
const deleteConfirmMessageText = computed(() => {
|
|
605
|
+
const item = pendingDeleteItem.value
|
|
606
|
+
if (!item) return ""
|
|
607
|
+
if (props.itemsDeleted && props.getHardDeleteConfirmMessage) {
|
|
608
|
+
return props.getHardDeleteConfirmMessage(item)
|
|
432
609
|
}
|
|
433
|
-
if (
|
|
434
|
-
|
|
435
|
-
return groupBy.map((key) => ({ key, order: "asc" as const }))
|
|
610
|
+
if (props.getDeleteConfirmMessage) {
|
|
611
|
+
return props.getDeleteConfirmMessage(item)
|
|
436
612
|
}
|
|
437
|
-
|
|
613
|
+
const name = String((item as Record<string, unknown>)._id ?? "")
|
|
614
|
+
const key = props.itemsDeleted
|
|
615
|
+
? "core.baseDataTable.confirmHardDeleteMessage"
|
|
616
|
+
: "core.baseDataTable.confirmDeleteMessage"
|
|
617
|
+
return t(key, { name })
|
|
438
618
|
})
|
|
439
619
|
|
|
620
|
+
const requestDelete = <Item,>(
|
|
621
|
+
item: Item,
|
|
622
|
+
onConfirm: () => void | Promise<void>,
|
|
623
|
+
) => {
|
|
624
|
+
if (!props.confirmDelete) {
|
|
625
|
+
void onConfirm()
|
|
626
|
+
return
|
|
627
|
+
}
|
|
628
|
+
pendingDeleteItem.value = item as T
|
|
629
|
+
pendingDeleteCallback = onConfirm
|
|
630
|
+
deleteDialogOpen.value = true
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const cancelDelete = () => {
|
|
634
|
+
deleteDialogOpen.value = false
|
|
635
|
+
pendingDeleteItem.value = null
|
|
636
|
+
pendingDeleteCallback = null
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const confirmDeleteAction = () => {
|
|
640
|
+
const callback = pendingDeleteCallback
|
|
641
|
+
cancelDelete()
|
|
642
|
+
if (callback) {
|
|
643
|
+
void callback()
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
provide(BASE_DATA_TABLE_REQUEST_DELETE_KEY, requestDelete)
|
|
648
|
+
|
|
649
|
+
type VuetifyGroupByItem = { key: string; order?: "asc" | "desc" }
|
|
650
|
+
|
|
651
|
+
/** Vuetify expects `{ key }[]`; accept plain column key(s) from callers. */
|
|
652
|
+
const resolvedGroupBy = computed(
|
|
653
|
+
(): readonly VuetifyGroupByItem[] | undefined => {
|
|
654
|
+
const groupBy = props.groupBy
|
|
655
|
+
if (!groupBy) return undefined
|
|
656
|
+
if (typeof groupBy === "string") {
|
|
657
|
+
return [{ key: groupBy, order: "asc" }]
|
|
658
|
+
}
|
|
659
|
+
if (groupBy.length === 0) return undefined
|
|
660
|
+
if (typeof groupBy[0] === "string") {
|
|
661
|
+
return groupBy.map((key) => ({ key, order: "asc" as const }))
|
|
662
|
+
}
|
|
663
|
+
return groupBy as readonly VuetifyGroupByItem[]
|
|
664
|
+
},
|
|
665
|
+
)
|
|
666
|
+
|
|
440
667
|
const slots = useSlots()
|
|
441
668
|
|
|
442
669
|
/** Slots owned by BaseDataTable chrome (toolbar, filters drawer, row actions, export). */
|
|
443
670
|
const BASE_DATA_TABLE_RESERVED_SLOTS = new Set([
|
|
444
671
|
"toolbar-start",
|
|
445
672
|
"toolbar-leading",
|
|
673
|
+
"toolbar-indicators",
|
|
446
674
|
"toolbar-end",
|
|
447
675
|
"toolbar-filters-trigger",
|
|
448
676
|
"toolbar-actions-trigger",
|
|
@@ -495,7 +723,8 @@ const hasSlotContent = (name: string) => {
|
|
|
495
723
|
if (!slot) return false
|
|
496
724
|
return slot().some((vnode) => {
|
|
497
725
|
if (vnode.type === Comment) return false
|
|
498
|
-
if (typeof vnode.children === "string")
|
|
726
|
+
if (typeof vnode.children === "string")
|
|
727
|
+
return vnode.children.trim().length > 0
|
|
499
728
|
if (Array.isArray(vnode.children)) return vnode.children.length > 0
|
|
500
729
|
return true
|
|
501
730
|
})
|
|
@@ -510,6 +739,8 @@ const showTopToolbar = computed(
|
|
|
510
739
|
Boolean(props.onFileLoaded) ||
|
|
511
740
|
hasSlotContent("toolbar-start") ||
|
|
512
741
|
hasSlotContent("toolbar-leading") ||
|
|
742
|
+
hasSlotContent("toolbar-indicators") ||
|
|
743
|
+
hasToolbarIndicators.value ||
|
|
513
744
|
hasSlotContent("toolbar-end"),
|
|
514
745
|
)
|
|
515
746
|
|
|
@@ -551,8 +782,7 @@ const computedHeaders = computed(() => {
|
|
|
551
782
|
const headers = [...props.headers].map((header) => {
|
|
552
783
|
const key = String(header.key)
|
|
553
784
|
const hasFilter =
|
|
554
|
-
props.showFilters &&
|
|
555
|
-
appliedColumnFilters.value[key]?.trim()?.length > 0
|
|
785
|
+
props.showFilters && appliedColumnFilters.value[key]?.trim()?.length > 0
|
|
556
786
|
return {
|
|
557
787
|
...header,
|
|
558
788
|
title: header.title,
|
|
@@ -568,11 +798,10 @@ const computedHeaders = computed(() => {
|
|
|
568
798
|
: {}),
|
|
569
799
|
}
|
|
570
800
|
})
|
|
571
|
-
|
|
572
801
|
// Add actions column if needed
|
|
573
802
|
if (props.showActionsColumn) {
|
|
574
803
|
headers.push({
|
|
575
|
-
title: props.actionsHeaderTitle || "
|
|
804
|
+
title: props.actionsHeaderTitle || t("core.baseDataTable.actions"),
|
|
576
805
|
key: "actions",
|
|
577
806
|
align: "end" as const,
|
|
578
807
|
sortable: false,
|
|
@@ -676,6 +905,11 @@ const projectRowsToVisibleColumns = <TRow extends Record<string, unknown>>(
|
|
|
676
905
|
})
|
|
677
906
|
}
|
|
678
907
|
|
|
908
|
+
const shapeExportRows = <TRow extends Record<string, unknown>>(rows: TRow[]) =>
|
|
909
|
+
props.exportRowShape === "full-records"
|
|
910
|
+
? rows
|
|
911
|
+
: projectRowsToVisibleColumns(rows)
|
|
912
|
+
|
|
679
913
|
/** Rows passed to DataExporter when something is selected. */
|
|
680
914
|
const exportPayloadData = computed<ExportPayload>(() => {
|
|
681
915
|
const idKey = props.itemValue as keyof T
|
|
@@ -709,7 +943,7 @@ const exportPayloadData = computed<ExportPayload>(() => {
|
|
|
709
943
|
.filter((row): row is Record<string, unknown> => row !== undefined)
|
|
710
944
|
|
|
711
945
|
return {
|
|
712
|
-
items:
|
|
946
|
+
items: shapeExportRows(selectedExportRows),
|
|
713
947
|
...(props.exportMeta ? { meta: props.exportMeta } : {}),
|
|
714
948
|
}
|
|
715
949
|
}
|
|
@@ -728,7 +962,7 @@ const exportPayloadData = computed<ExportPayload>(() => {
|
|
|
728
962
|
}) as Record<string, unknown>[]
|
|
729
963
|
|
|
730
964
|
return {
|
|
731
|
-
items:
|
|
965
|
+
items: shapeExportRows(selectedRows),
|
|
732
966
|
...(props.exportMeta ? { meta: props.exportMeta } : {}),
|
|
733
967
|
}
|
|
734
968
|
})
|
|
@@ -778,11 +1012,25 @@ const isSelectionControlClick = (event: PointerEvent) => {
|
|
|
778
1012
|
)
|
|
779
1013
|
}
|
|
780
1014
|
|
|
1015
|
+
const isInteractiveRowClick = (event: PointerEvent) => {
|
|
1016
|
+
const target = event.target
|
|
1017
|
+
if (!(target instanceof Element)) return false
|
|
1018
|
+
return Boolean(
|
|
1019
|
+
target.closest(
|
|
1020
|
+
"button, a, input, textarea, select, label, [role='button'], .v-btn, .v-btn-group",
|
|
1021
|
+
),
|
|
1022
|
+
)
|
|
1023
|
+
}
|
|
1024
|
+
|
|
781
1025
|
const handleRowClick = (event: PointerEvent, row: { item: T }) => {
|
|
782
1026
|
if (showSelectColumn.value && isSelectionControlClick(event)) {
|
|
783
1027
|
return
|
|
784
1028
|
}
|
|
785
1029
|
|
|
1030
|
+
if (isInteractiveRowClick(event)) {
|
|
1031
|
+
return
|
|
1032
|
+
}
|
|
1033
|
+
|
|
786
1034
|
if (props.rowClickHandler && !isItemRowClickable(row.item)) {
|
|
787
1035
|
return
|
|
788
1036
|
}
|
|
@@ -816,8 +1064,14 @@ const notifyFilterStateChange = () => {
|
|
|
816
1064
|
}
|
|
817
1065
|
}
|
|
818
1066
|
|
|
1067
|
+
/** Selection is tied to visible rows; clear when applied filters change. */
|
|
1068
|
+
const clearExportSelection = () => {
|
|
1069
|
+
selected.value = []
|
|
1070
|
+
}
|
|
1071
|
+
|
|
819
1072
|
const applyFilters = () => {
|
|
820
1073
|
appliedColumnFilters.value = { ...columnFilters.value }
|
|
1074
|
+
clearExportSelection()
|
|
821
1075
|
if (props.onFilterApply) {
|
|
822
1076
|
props.onFilterApply(appliedFilterPayload.value, actionContext())
|
|
823
1077
|
} else {
|
|
@@ -828,6 +1082,7 @@ const applyFilters = () => {
|
|
|
828
1082
|
const resetFilters = () => {
|
|
829
1083
|
columnFilters.value = {}
|
|
830
1084
|
appliedColumnFilters.value = {}
|
|
1085
|
+
clearExportSelection()
|
|
831
1086
|
if (props.onFilterReset) {
|
|
832
1087
|
props.onFilterReset({}, actionContext())
|
|
833
1088
|
} else {
|
|
@@ -876,6 +1131,7 @@ const exporterSlotProps = computed(() => ({
|
|
|
876
1131
|
disabled: !selected.value.length,
|
|
877
1132
|
formats: props.exportFormats,
|
|
878
1133
|
defaultFormat: props.exportDefaultFormat,
|
|
1134
|
+
showDataScopeSwitch: props.exportShowDataScopeSwitch,
|
|
879
1135
|
isExportMode: isExportMode.value,
|
|
880
1136
|
selectedCount: selected.value.length,
|
|
881
1137
|
}))
|
|
@@ -918,6 +1174,10 @@ const filterDrawerFooterSlotProps = computed(() => ({
|
|
|
918
1174
|
filters: draftFilterPayload.value,
|
|
919
1175
|
context: actionContext(),
|
|
920
1176
|
}))
|
|
1177
|
+
|
|
1178
|
+
const toolbarIndicatorsSlotProps = computed(() => ({
|
|
1179
|
+
indicators: props.toolbarIndicators ?? [],
|
|
1180
|
+
}))
|
|
921
1181
|
</script>
|
|
922
1182
|
|
|
923
1183
|
<style lang="css">
|
|
@@ -982,4 +1242,24 @@ const filterDrawerFooterSlotProps = computed(() => ({
|
|
|
982
1242
|
.filtered-column-cell {
|
|
983
1243
|
background-color: rgba(var(--v-theme-info), 0.1);
|
|
984
1244
|
}
|
|
1245
|
+
|
|
1246
|
+
.base-data-table-host--filter-open {
|
|
1247
|
+
overflow: hidden;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
.base-data-table-filter-drawer-footer {
|
|
1251
|
+
position: sticky;
|
|
1252
|
+
bottom: 0;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
.base-data-table-filter-drawer-footer--contained {
|
|
1256
|
+
flex-direction: column;
|
|
1257
|
+
align-items: stretch;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
.base-data-table-filter-drawer-footer--contained
|
|
1261
|
+
:deep(.base-data-table-filter-drawer-footer-btn) {
|
|
1262
|
+
width: 100%;
|
|
1263
|
+
max-width: 100%;
|
|
1264
|
+
}
|
|
985
1265
|
</style>
|
package/services/frontend/src/orchestrator/components/dataTable/BaseDataTableConfirmDeleteBtn.vue
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-btn
|
|
3
|
+
icon="mdi-delete"
|
|
4
|
+
size="small"
|
|
5
|
+
variant="text"
|
|
6
|
+
color="error"
|
|
7
|
+
@click.stop.prevent="handleClick"
|
|
8
|
+
/>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts" generic="T">
|
|
12
|
+
import { useBaseDataTableDeleteConfirm } from "@racletteOrchestrator/composables/useBaseDataTableDeleteConfirm"
|
|
13
|
+
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
item: T
|
|
16
|
+
}>()
|
|
17
|
+
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
delete: [item: T]
|
|
20
|
+
}>()
|
|
21
|
+
|
|
22
|
+
const { requestDelete } = useBaseDataTableDeleteConfirm()
|
|
23
|
+
|
|
24
|
+
const handleClick = () => {
|
|
25
|
+
requestDelete(props.item, () => emit("delete", props.item))
|
|
26
|
+
}
|
|
27
|
+
</script>
|