@raclettejs/core 0.1.35 → 0.1.36-canary.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.
Files changed (101) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/dist/cli.js +86 -82
  3. package/dist/cli.js.map +3 -3
  4. package/package.json +5 -5
  5. package/services/.dockerignore +2 -0
  6. package/services/backend/.dockerignore +3 -1
  7. package/services/backend/src/core/payload/payloadTypes.ts +1 -0
  8. package/services/backend/src/core/pluginSystem/configGenerator/index.ts +12 -11
  9. package/services/backend/src/core/pluginSystem/pluginFastifyInstance.ts +28 -3
  10. package/services/backend/src/core/pluginSystem/pluginLoader.ts +11 -2
  11. package/services/backend/src/core/pluginSystem/pluginTypes.ts +39 -1
  12. package/services/backend/src/core/sockets/createSystemEventHandlers.ts +4 -4
  13. package/services/backend/src/core/sockets/socketManager.ts +80 -32
  14. package/services/backend/src/core/sockets/socketTypes.ts +6 -0
  15. package/services/backend/src/core/validation/schemaGenerator.ts +10 -3
  16. package/services/backend/src/corePlugins/raclette__core/backend/datatypes/project/project.service.ts +1 -1
  17. package/services/backend/src/corePlugins/raclette__core/backend/index.ts +0 -2
  18. package/services/backend/src/modules/authentication/index.ts +15 -23
  19. package/services/backend/src/shared/helper/adminTag.ts +34 -0
  20. package/services/backend/src/shared/types/plugins/index.ts +1 -9
  21. package/services/backend/src/utils/request.utils.ts +1 -1
  22. package/services/frontend/.dockerignore +3 -1
  23. package/services/frontend/src/core/lib/data/dataApi.ts +27 -16
  24. package/services/frontend/src/core/lib/data/queryApi.ts +17 -8
  25. package/services/frontend/src/core/lib/data/responseTypeHandler.ts +17 -8
  26. package/services/frontend/src/core/lib/data/writeDataHandler.ts +3 -1
  27. package/services/frontend/src/core/lib/dataHelper.ts +51 -12
  28. package/services/frontend/src/core/lib/eventWhitelist.ts +2 -0
  29. package/services/frontend/src/core/lib/httpClient.ts +16 -2
  30. package/services/frontend/src/core/lib/userNotifier.ts +20 -0
  31. package/services/frontend/src/core/lib/workSessionUrl.ts +16 -0
  32. package/services/frontend/src/core/main.ts +82 -15
  33. package/services/frontend/src/core/setup/socketBootstrap.ts +83 -0
  34. package/services/frontend/src/core/setup/socketClient.ts +39 -39
  35. package/services/frontend/src/core/setup/socketEvents.ts +152 -158
  36. package/services/frontend/src/core/setup/socketRegistry.ts +19 -0
  37. package/services/frontend/src/core/types/DataApi.ts +10 -2
  38. package/services/frontend/src/core/types/PluginRegistrar.ts +1 -1
  39. package/services/frontend/src/orchestrator/ProductOrchestrator.vue +14 -0
  40. package/services/frontend/src/orchestrator/assets/styles/layers.css +4 -1
  41. package/services/frontend/src/orchestrator/components/dataTable/BaseDataTable.vue +267 -35
  42. package/services/frontend/src/orchestrator/components/menu/ServerStatus.vue +11 -2
  43. package/services/frontend/src/orchestrator/composables/useVueQueryObservableHelper.ts +12 -7
  44. package/services/frontend/src/orchestrator/composables/useVueWriteOperationHelper.ts +12 -6
  45. package/services/frontend/src/orchestrator/i18n/de-DE.json +1 -0
  46. package/services/frontend/src/orchestrator/i18n/en-EU.json +1 -0
  47. package/services/frontend/src/orchestrator/i18n/sk.json +1 -0
  48. package/services/frontend/src/orchestrator/i18n/tl-TL.json +1 -0
  49. package/services/frontend/src/orchestrator/index.ts +7 -24
  50. package/services/frontend/src/orchestrator/lib/queryParamsSource.ts +48 -0
  51. package/services/frontend/src/orchestrator/main.ts +2 -2
  52. package/services/frontend/src/orchestrator/router/index.ts +0 -19
  53. package/services/frontend/src/orchestrator/router/registerEventHandlers.ts +29 -0
  54. package/services/frontend/src/orchestrator/router/routeStore.ts +92 -17
  55. package/services/frontend/src/orchestrator/router/routes.ts +5 -0
  56. package/services/frontend/src/orchestrator/setup/application.ts +11 -2
  57. package/services/frontend/src/orchestrator/types/DataApi.ts +19 -0
  58. package/services/frontend/src/orchestrator/types/PluginApi.ts +4 -7
  59. package/services/frontend/src/orchestrator/types/Widgets.ts +2 -0
  60. package/services/frontend/src/orchestrator/types/index.ts +1 -0
  61. package/templates/fs.yml +1 -1
  62. package/package/dist/index.js +0 -452
  63. package/services/backend/.yarn/install-state.gz +0 -0
  64. package/services/backend/dist/core/eventBus/index.js +0 -7
  65. package/services/backend/dist/core/pluginSystem/configGenerator/index.js +0 -346
  66. package/services/backend/dist/core/sockets/index.js +0 -95
  67. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/events/index.js +0 -31
  68. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/index.js +0 -48
  69. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/account/routes/index.js +0 -15
  70. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/events/index.js +0 -22
  71. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/index.js +0 -51
  72. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/composition/routes/index.js +0 -19
  73. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/events/index.js +0 -22
  74. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/index.js +0 -48
  75. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/interactionLink/routes/index.js +0 -19
  76. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/events/index.js +0 -31
  77. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/index.js +0 -49
  78. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/project/routes/index.js +0 -16
  79. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/events/index.js +0 -39
  80. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/index.js +0 -50
  81. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/tag/routes/index.js +0 -19
  82. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/events/index.js +0 -31
  83. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/index.js +0 -51
  84. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/user/routes/index.js +0 -21
  85. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/events/index.js +0 -31
  86. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/index.js +0 -48
  87. package/services/backend/dist/corePlugins/raclette__core/backend/datatypes/workSession/routes/index.js +0 -15
  88. package/services/backend/dist/corePlugins/raclette__core/backend/index.js +0 -34
  89. package/services/backend/dist/domains/index.js +0 -11
  90. package/services/backend/dist/domains/system/index.js +0 -11
  91. package/services/backend/dist/domains/system/routes/index.js +0 -17
  92. package/services/backend/dist/helpers/index.js +0 -14
  93. package/services/backend/dist/index.js +0 -3
  94. package/services/backend/dist/modules/authentication/index.js +0 -253
  95. package/services/backend/dist/shared/types/core/index.js +0 -8
  96. package/services/backend/dist/shared/types/dataTypes/index.js +0 -6
  97. package/services/backend/dist/shared/types/index.js +0 -8
  98. package/services/backend/dist/shared/types/plugins/index.js +0 -8
  99. package/services/backend/dist/types/index.js +0 -12
  100. package/services/backend/dist/utils/index.js +0 -2
  101. package/services/frontend/.yarn/install-state.gz +0 -0
@@ -2,22 +2,26 @@
2
2
  <div>
3
3
  <v-data-table
4
4
  v-model="selected"
5
- v-model:search="search"
5
+ :search="showSearch ? search : undefined"
6
6
  density="compact"
7
7
  :items-per-page="itemsPerPage"
8
8
  sticky
9
9
  fixed-header
10
10
  :headers="computedHeaders"
11
11
  :items="filteredItems"
12
- :loading="loading"
12
+ :loading="showLoading ? loading : false"
13
+ :row-props="resolveRowProps"
13
14
  @click:row="handleRowClick"
14
15
  @update:current-items="(items) => emit('update:currentItems', items)"
15
16
  :class="{ itemsDeleted }"
16
- :show-select="showExport && isExportMode"
17
+ :show-select="showSelectColumn"
17
18
  :item-value
19
+ :group-by="resolvedGroupBy"
20
+ :sort-by="sortBy"
18
21
  >
19
22
  <template #top>
20
23
  <v-toolbar
24
+ v-if="showTopToolbar"
21
25
  flat
22
26
  density="compact"
23
27
  class="base-data-table-toolbar tw:pr-2"
@@ -105,7 +109,7 @@
105
109
  </template>
106
110
  </v-toolbar>
107
111
 
108
- <slot name="toolbar-search">
112
+ <slot v-if="showSearch" name="toolbar-search">
109
113
  <v-text-field
110
114
  v-model="search"
111
115
  :label="
@@ -192,8 +196,25 @@
192
196
  />
193
197
  </template>
194
198
 
195
- <!-- Additional table slots (this caused the table to be empty) -->
196
- <!-- <slot name="table-slots" /> -->
199
+ <!-- Always bridge tbody summary slots so v-data-table receives a stable slot (dynamic v-for is not reliable here). -->
200
+ <template #body.prepend="slotProps">
201
+ <slot name="body.prepend" v-bind="slotProps ?? {}" />
202
+ </template>
203
+ <template #body.append="slotProps">
204
+ <slot name="body.append" v-bind="slotProps ?? {}" />
205
+ </template>
206
+ <template #tfoot="slotProps">
207
+ <slot name="tfoot" v-bind="slotProps ?? {}" />
208
+ </template>
209
+
210
+ <!-- Forward remaining v-data-table slots (e.g. bottom, thead). -->
211
+ <template
212
+ v-for="slotName in vDataTablePassthroughSlots"
213
+ :key="slotName"
214
+ #[slotName]="slotProps"
215
+ >
216
+ <slot :name="slotName" v-bind="slotProps ?? {}" />
217
+ </template>
197
218
  </v-data-table>
198
219
  <!-- Additional dialogs slot -->
199
220
  <slot name="dialogs" />
@@ -226,17 +247,29 @@
226
247
 
227
248
  <slot name="filters.drawer.body" v-bind="filterDrawerBodySlotProps">
228
249
  <div class="tw:px-4 tw:pb-4 tw:flex tw:flex-col tw:gap-3">
229
- <v-text-field
230
- v-for="header in filterableHeaders"
231
- :key="String(header.key)"
232
- v-model="columnFilters[String(header.key)]"
233
- @update:model-value="notifyFilterStateChange"
234
- :label="header.title"
235
- clearable
236
- hide-details
237
- density="compact"
238
- variant="outlined"
239
- />
250
+ <template v-for="header in filterableHeaders" :key="String(header.key)">
251
+ <v-autocomplete
252
+ v-if="columnUsesComboboxFilter(String(header.key))"
253
+ v-model="columnFilters[String(header.key)]"
254
+ @update:model-value="notifyFilterStateChange"
255
+ :items="filterOptionsByKey[String(header.key)] ?? []"
256
+ :label="header.title"
257
+ clearable
258
+ hide-details
259
+ density="compact"
260
+ variant="outlined"
261
+ />
262
+ <v-text-field
263
+ v-else
264
+ v-model="columnFilters[String(header.key)]"
265
+ @update:model-value="notifyFilterStateChange"
266
+ :label="header.title"
267
+ clearable
268
+ hide-details
269
+ density="compact"
270
+ variant="outlined"
271
+ />
272
+ </template>
240
273
  </div>
241
274
  </slot>
242
275
 
@@ -266,7 +299,7 @@
266
299
  </template>
267
300
 
268
301
  <script setup lang="ts" generic="T extends Record<string, any>">
269
- import { computed, ref, useSlots, useTemplateRef } from "vue"
302
+ import { Comment, computed, ref, useSlots, useTemplateRef, watch } from "vue"
270
303
  import DataExporter from "@racletteOrchestrator/components/dataExport/DataExporter.vue"
271
304
  import FileUpload from "@racletteOrchestrator/components/input/FileUpload.vue"
272
305
  import type {
@@ -296,19 +329,33 @@ export interface BaseDataTableProps<T> {
296
329
  width?: string | number
297
330
  }>
298
331
  itemsPerPage?: number
332
+ /** Initial / default column sort (Vuetify `{ key, order }[]`). */
333
+ sortBy?: ReadonlyArray<{ key: string; order: "asc" | "desc" }>
299
334
 
300
335
  // Row actions
301
336
  showActionsColumn?: boolean
302
337
 
303
338
  // Item processing
304
339
  rowClickHandler?: (item: T, context?: BaseDataTableActionContext<T>) => void
340
+ /** When set with `rowClickHandler`, only matching rows are clickable and show a pointer cursor. */
341
+ isRowClickable?: (item: T) => boolean
305
342
 
306
343
  dataName: string
307
344
 
308
345
  showExport?: boolean
346
+ /** Checkbox column for row selection (independent of export mode). */
347
+ showSelect?: boolean
309
348
  showFilters?: boolean
349
+ /** Show the search field above the table (default true). */
350
+ showSearch?: boolean
351
+ /** Pass loading state through to v-data-table (default true). */
352
+ showLoading?: boolean
353
+ /** Max distinct values per column before filter falls back to a text field. */
354
+ filterMaxSelectOptions?: number
310
355
 
311
356
  itemValue?: string
357
+ /** Vuetify group-by key(s), e.g. `"project"` for project-grouped rows. */
358
+ groupBy?: string | readonly string[]
312
359
  actionsHeaderTitle?: string
313
360
 
314
361
  /**
@@ -345,11 +392,17 @@ const props = withDefaults(defineProps<BaseDataTableProps<T>>(), {
345
392
  uuid: undefined,
346
393
  items: () => [],
347
394
  itemsDeleted: false,
348
- itemsPerPage: 50,
395
+ itemsPerPage: 10,
396
+ sortBy: undefined,
349
397
  showActionsColumn: true,
350
398
  showExport: true,
399
+ showSelect: false,
351
400
  showFilters: false,
401
+ showSearch: true,
402
+ showLoading: true,
403
+ filterMaxSelectOptions: 300,
352
404
  itemValue: "_id",
405
+ groupBy: undefined,
353
406
  actionsHeaderTitle: "",
354
407
  exportableItems: undefined,
355
408
  exportMeta: undefined,
@@ -368,8 +421,65 @@ const emit = defineEmits<{
368
421
  "update:currentItems": [items: readonly DataTableItem<T>[]]
369
422
  }>()
370
423
 
424
+ type VuetifyGroupByItem = { key: string; order?: "asc" | "desc" }
425
+
426
+ /** Vuetify expects `{ key }[]`; accept plain column key(s) from callers. */
427
+ const resolvedGroupBy = computed((): readonly VuetifyGroupByItem[] | undefined => {
428
+ const groupBy = props.groupBy
429
+ if (!groupBy) return undefined
430
+ if (typeof groupBy === "string") {
431
+ return [{ key: groupBy, order: "asc" }]
432
+ }
433
+ if (groupBy.length === 0) return undefined
434
+ if (typeof groupBy[0] === "string") {
435
+ return groupBy.map((key) => ({ key, order: "asc" as const }))
436
+ }
437
+ return groupBy as readonly VuetifyGroupByItem[]
438
+ })
439
+
371
440
  const slots = useSlots()
372
441
 
442
+ /** Slots owned by BaseDataTable chrome (toolbar, filters drawer, row actions, export). */
443
+ const BASE_DATA_TABLE_RESERVED_SLOTS = new Set([
444
+ "toolbar-start",
445
+ "toolbar-leading",
446
+ "toolbar-end",
447
+ "toolbar-filters-trigger",
448
+ "toolbar-actions-trigger",
449
+ "action.export-mode",
450
+ "action.import",
451
+ "toolbar-actions-menu",
452
+ "toolbar-search",
453
+ "importer",
454
+ "exporter",
455
+ "prepend-row-actions",
456
+ "row-actions",
457
+ "append-row-actions",
458
+ "dialogs",
459
+ "filters.summary",
460
+ "filters.drawer.header",
461
+ "filters.drawer.body",
462
+ "filters.drawer.footer",
463
+ ])
464
+
465
+ const V_DATA_TABLE_BRIDGED_SLOTS = new Set([
466
+ "body.prepend",
467
+ "body.append",
468
+ "tfoot",
469
+ ])
470
+
471
+ /** v-data-table slots supplied by the parent (e.g. bottom), excluding built-in handling. */
472
+ const vDataTablePassthroughSlots = computed(() =>
473
+ Object.keys(slots).filter(
474
+ (name) =>
475
+ !BASE_DATA_TABLE_RESERVED_SLOTS.has(name) &&
476
+ !V_DATA_TABLE_BRIDGED_SLOTS.has(name) &&
477
+ !name.startsWith("item.") &&
478
+ name !== "loader" &&
479
+ name !== "top",
480
+ ),
481
+ )
482
+
373
483
  /** Hide ⋮ when the menu would be empty (no export toggle, import, or slot content). */
374
484
  const showActionsOverflowMenu = computed(
375
485
  () =>
@@ -380,17 +490,58 @@ const showActionsOverflowMenu = computed(
380
490
  Boolean(slots["action.import"]),
381
491
  )
382
492
 
493
+ const hasSlotContent = (name: string) => {
494
+ const slot = slots[name]
495
+ if (!slot) return false
496
+ return slot().some((vnode) => {
497
+ if (vnode.type === Comment) return false
498
+ if (typeof vnode.children === "string") return vnode.children.trim().length > 0
499
+ if (Array.isArray(vnode.children)) return vnode.children.length > 0
500
+ return true
501
+ })
502
+ }
503
+
504
+ /** Toolbar hidden when no title, filters, export menu, import, or custom slot content. */
505
+ const showTopToolbar = computed(
506
+ () =>
507
+ Boolean(props.dataName?.length) ||
508
+ Boolean(props.showFilters) ||
509
+ showActionsOverflowMenu.value ||
510
+ Boolean(props.onFileLoaded) ||
511
+ hasSlotContent("toolbar-start") ||
512
+ hasSlotContent("toolbar-leading") ||
513
+ hasSlotContent("toolbar-end"),
514
+ )
515
+
383
516
  const importFileUploadRef = useTemplateRef<{
384
517
  openPicker: () => void
385
518
  }>("importFileUploadRef")
386
519
 
387
520
  // Reactive data
388
521
  const search = ref("")
389
- const selected = ref<Array<string | number>>([])
522
+ const selected = defineModel<Array<string | number>>("selected", {
523
+ default: () => [],
524
+ })
390
525
  const isExportMode = ref(false)
526
+ const showSelectColumn = computed(
527
+ () => props.showSelect || (props.showExport && isExportMode.value),
528
+ )
391
529
  const isActionsMenuOpen = ref(false)
392
530
  const isFilterDrawerOpen = ref(false)
531
+ /** Draft values edited in the filter drawer (applied only on Apply). */
393
532
  const columnFilters = ref<Record<string, string>>({})
533
+ /** Values currently filtering the table. */
534
+ const appliedColumnFilters = ref<Record<string, string>>({})
535
+
536
+ const syncDraftFiltersFromApplied = () => {
537
+ columnFilters.value = { ...appliedColumnFilters.value }
538
+ }
539
+
540
+ watch(isFilterDrawerOpen, (open) => {
541
+ if (open) {
542
+ syncDraftFiltersFromApplied()
543
+ }
544
+ })
394
545
 
395
546
  const computedHeaders = computed(() => {
396
547
  if (!props.headers || !props.headers?.length) {
@@ -400,7 +551,8 @@ const computedHeaders = computed(() => {
400
551
  const headers = [...props.headers].map((header) => {
401
552
  const key = String(header.key)
402
553
  const hasFilter =
403
- props.showFilters && columnFilters.value[key]?.trim()?.length > 0
554
+ props.showFilters &&
555
+ appliedColumnFilters.value[key]?.trim()?.length > 0
404
556
  return {
405
557
  ...header,
406
558
  title: header.title,
@@ -449,13 +601,42 @@ const filterableHeaders = computed(() =>
449
601
  }),
450
602
  )
451
603
 
604
+ const filterOptionsByKey = computed(() => {
605
+ const rows = props.items ?? []
606
+ const options: Record<string, string[]> = {}
607
+
608
+ for (const header of filterableHeaders.value) {
609
+ const key = String(header.key)
610
+ const values = new Set<string>()
611
+ for (const row of rows) {
612
+ const raw = row[key as keyof T]
613
+ if (raw === undefined || raw === null) {
614
+ continue
615
+ }
616
+ const text = String(raw)
617
+ if (text.length === 0) {
618
+ continue
619
+ }
620
+ values.add(text)
621
+ }
622
+ options[key] = [...values].sort((a, b) => a.localeCompare(b))
623
+ }
624
+
625
+ return options
626
+ })
627
+
628
+ const columnUsesComboboxFilter = (key: string) => {
629
+ const count = filterOptionsByKey.value[key]?.length ?? 0
630
+ return count <= props.filterMaxSelectOptions
631
+ }
632
+
452
633
  const filteredItems = computed(() => {
453
634
  const rows = props.items ?? []
454
635
  if (!props.showFilters) {
455
636
  return rows
456
637
  }
457
638
 
458
- const activeFilters = Object.entries(columnFilters.value).filter(
639
+ const activeFilters = Object.entries(appliedColumnFilters.value).filter(
459
640
  ([, value]) => value?.trim().length > 0,
460
641
  )
461
642
  if (!activeFilters.length) {
@@ -468,7 +649,11 @@ const filteredItems = computed(() => {
468
649
  if (raw === undefined || raw === null) {
469
650
  return false
470
651
  }
471
- return String(raw).toLowerCase().includes(filterValue.toLowerCase())
652
+ const cell = String(raw)
653
+ if (columnUsesComboboxFilter(key)) {
654
+ return cell.toLowerCase() === filterValue.toLowerCase()
655
+ }
656
+ return cell.toLowerCase().includes(filterValue.toLowerCase())
472
657
  }),
473
658
  )
474
659
  })
@@ -551,20 +736,57 @@ const exportPayloadData = computed<ExportPayload>(() => {
551
736
  // Methods
552
737
  const toggleExportMode = () => {
553
738
  isExportMode.value = !isExportMode.value
554
- if (!isExportMode.value) {
739
+ if (!isExportMode.value && !props.showSelect) {
555
740
  selected.value = []
556
741
  }
557
742
  }
558
743
 
559
744
  const toggleFilterDrawer = () => {
745
+ if (!isFilterDrawerOpen.value) {
746
+ syncDraftFiltersFromApplied()
747
+ }
560
748
  isFilterDrawerOpen.value = !isFilterDrawerOpen.value
561
749
  }
562
750
 
563
751
  const openFilters = () => {
752
+ syncDraftFiltersFromApplied()
564
753
  isFilterDrawerOpen.value = true
565
754
  }
566
755
 
567
- const handleRowClick = (_: PointerEvent, row: { item: T }) => {
756
+ const isItemRowClickable = (item: T) =>
757
+ props.isRowClickable ? props.isRowClickable(item) : true
758
+
759
+ const resolveRowProps = (data: { item: T }) => {
760
+ if (!props.rowClickHandler) {
761
+ return {}
762
+ }
763
+
764
+ return {
765
+ class: isItemRowClickable(data.item)
766
+ ? "tw:cursor-pointer"
767
+ : "tw:cursor-default",
768
+ }
769
+ }
770
+
771
+ const isSelectionControlClick = (event: PointerEvent) => {
772
+ const target = event.target
773
+ if (!(target instanceof Element)) return false
774
+ return Boolean(
775
+ target.closest(
776
+ ".v-selection-control, .v-data-table__checkbox, .v-data-table-column--select",
777
+ ),
778
+ )
779
+ }
780
+
781
+ const handleRowClick = (event: PointerEvent, row: { item: T }) => {
782
+ if (showSelectColumn.value && isSelectionControlClick(event)) {
783
+ return
784
+ }
785
+
786
+ if (props.rowClickHandler && !isItemRowClickable(row.item)) {
787
+ return
788
+ }
789
+
568
790
  const context = actionContext(row.item)
569
791
  if (props.rowClickHandler) {
570
792
  props.rowClickHandler(row.item, context)
@@ -573,31 +795,39 @@ const handleRowClick = (_: PointerEvent, row: { item: T }) => {
573
795
  emit("rowClick", row.item)
574
796
  }
575
797
 
576
- const filterPayload = computed(() => ({ ...columnFilters.value }))
798
+ const draftFilterPayload = computed(() => ({ ...columnFilters.value }))
799
+ const appliedFilterPayload = computed(() => ({
800
+ ...appliedColumnFilters.value,
801
+ }))
577
802
  const actionContext = (item?: T): BaseDataTableActionContext<T> => ({
578
803
  item,
579
- filters: filterPayload.value,
804
+ filters: appliedFilterPayload.value,
580
805
  selectedIds: [...selected.value],
581
806
  })
582
807
 
583
808
  const notifyFilterStateChange = () => {
584
809
  if (props.onFilterStateChange) {
585
- props.onFilterStateChange(filterPayload.value, actionContext())
810
+ props.onFilterStateChange(draftFilterPayload.value, {
811
+ ...actionContext(),
812
+ filters: draftFilterPayload.value,
813
+ })
586
814
  } else {
587
- console.log("[BaseDataTable] filter-state-change", filterPayload.value)
815
+ console.log("[BaseDataTable] filter-state-change", draftFilterPayload.value)
588
816
  }
589
817
  }
590
818
 
591
819
  const applyFilters = () => {
820
+ appliedColumnFilters.value = { ...columnFilters.value }
592
821
  if (props.onFilterApply) {
593
- props.onFilterApply(filterPayload.value, actionContext())
822
+ props.onFilterApply(appliedFilterPayload.value, actionContext())
594
823
  } else {
595
- console.log("[BaseDataTable] filter-apply", filterPayload.value)
824
+ console.log("[BaseDataTable] filter-apply", appliedFilterPayload.value)
596
825
  }
597
826
  }
598
827
 
599
828
  const resetFilters = () => {
600
829
  columnFilters.value = {}
830
+ appliedColumnFilters.value = {}
601
831
  if (props.onFilterReset) {
602
832
  props.onFilterReset({}, actionContext())
603
833
  } else {
@@ -658,8 +888,8 @@ const importerSlotProps = computed(() => ({
658
888
  }))
659
889
 
660
890
  const filterSummarySlotProps = computed(() => ({
661
- filters: filterPayload.value,
662
- activeCount: Object.values(columnFilters.value).filter(
891
+ filters: appliedFilterPayload.value,
892
+ activeCount: Object.values(appliedColumnFilters.value).filter(
663
893
  (value) => value?.trim().length > 0,
664
894
  ).length,
665
895
  }))
@@ -668,12 +898,14 @@ const filterDrawerHeaderSlotProps = computed(() => ({
668
898
  close: () => {
669
899
  isFilterDrawerOpen.value = false
670
900
  },
671
- filters: filterPayload.value,
901
+ filters: draftFilterPayload.value,
672
902
  }))
673
903
 
674
904
  const filterDrawerBodySlotProps = computed(() => ({
675
905
  filters: columnFilters.value,
676
906
  headers: filterableHeaders.value,
907
+ filterOptionsByKey: filterOptionsByKey.value,
908
+ columnUsesComboboxFilter,
677
909
  setFilter: (key: string, value: string) => {
678
910
  columnFilters.value[key] = value
679
911
  notifyFilterStateChange()
@@ -683,7 +915,7 @@ const filterDrawerBodySlotProps = computed(() => ({
683
915
  const filterDrawerFooterSlotProps = computed(() => ({
684
916
  applyFilters,
685
917
  resetFilters,
686
- filters: filterPayload.value,
918
+ filters: draftFilterPayload.value,
687
919
  context: actionContext(),
688
920
  }))
689
921
  </script>
@@ -10,6 +10,7 @@
10
10
  v-bind="props"
11
11
  :icon="currentState.icon"
12
12
  :color="currentState.color"
13
+ :aria-label="serverStatusLabel"
13
14
  />
14
15
  </template>
15
16
 
@@ -64,13 +65,21 @@ const theme = computed(() =>
64
65
  vuetifyTheme.global.name.value === "light" ? "dark" : "light",
65
66
  )
66
67
 
68
+ const socketStatus = computed(
69
+ () => backend.value?.socket?.status ?? 2,
70
+ )
71
+
72
+ const serverStatusLabel = computed(() =>
73
+ i18n.t(`core.socket_status_${socketStatus.value || 0}`),
74
+ )
75
+
67
76
  const currentState = computed(() => {
68
- const status = backend.value?.socket?.status ?? 2 // default to "some fucked"
77
+ const status = socketStatus.value
69
78
 
70
79
  const stateMapping = [
71
80
  { icon: "mdi-alert-circle", color: "error" }, // status 0
72
81
  { icon: "mdi-checkbox-marked-circle", color: "success" }, // status 1
73
- { icon: "mdi-alert-circle", color: "warning" }, // status 2 or anything else
82
+ { icon: "mdi-alert-circle", color: "warning" }, // status 2 connecting
74
83
  ]
75
84
 
76
85
  return stateMapping[status] ?? stateMapping[2]
@@ -1,11 +1,12 @@
1
1
  import { computed, ref, watch, onUnmounted } from "vue"
2
2
  import { useObservable } from "@vueuse/rxjs"
3
3
  import { $queryApi } from "@racletteCore/lib/data/dataApi"
4
+ import type { QueryApiQuery, QueryMethods } from "@racletteCore/types"
4
5
  import type {
5
- CustomStoreActionsPayload,
6
- QueryApiQuery,
7
- QueryMethods,
8
- } from "@racletteCore/types"
6
+ VueQueryParamsInput,
7
+ VueStoreActionsPayload,
8
+ } from "../types/DataApi"
9
+ import { toQueryParamsSource } from "../lib/queryParamsSource"
9
10
  import { difference } from "ramda"
10
11
  import getStateObservable from "@racletteCore/lib/observableHelpers/getStateObservable"
11
12
  import type { Observable } from "rxjs"
@@ -16,8 +17,9 @@ const subscriberCounter = {}
16
17
  export const useVueQueryObservableHelper = (
17
18
  instanceActionId,
18
19
  target,
19
- { params = {}, options = {} }: CustomStoreActionsPayload = {},
20
+ { params = {}, options = {} }: VueStoreActionsPayload = {},
20
21
  ): QueryMethods => {
22
+ const paramsSource = toQueryParamsSource(params)
21
23
  const dataObservable$ = ref<Observable<unknown>>()
22
24
 
23
25
  const data = computed(() => {
@@ -36,7 +38,7 @@ export const useVueQueryObservableHelper = (
36
38
  coreQuery = subscriber$[instanceActionId]
37
39
  } else {
38
40
  subscriberCounter[instanceActionId] = 1
39
- coreQuery = $queryApi(instanceActionId, target, { params, options })
41
+ coreQuery = $queryApi(instanceActionId, target, { params: paramsSource, options })
40
42
  }
41
43
  subscriber$[instanceActionId] = coreQuery
42
44
  const queryRef = useObservable<QueryApiQuery>(coreQuery.query$)
@@ -77,7 +79,10 @@ export const useVueQueryObservableHelper = (
77
79
  return data.value ? Object.values(data.value) : []
78
80
  }),
79
81
  response: responseRef,
80
- execute: coreQuery.execute,
82
+ execute: (executeParams?: VueQueryParamsInput) =>
83
+ executeParams === undefined
84
+ ? coreQuery.execute()
85
+ : coreQuery.execute(toQueryParamsSource(executeParams)),
81
86
  uuids: coreQuery.uuids$,
82
87
  remove: coreQuery.remove,
83
88
  clear: coreQuery.clear,
@@ -1,10 +1,12 @@
1
1
  import { computed, ref, watch, onUnmounted } from "vue"
2
2
  import { useObservable } from "@vueuse/rxjs"
3
3
  import { $writeDataApi } from "@racletteCore/lib/data/dataApi"
4
+ import type { DataApiMethods } from "@racletteCore/types"
4
5
  import type {
5
- CustomStoreActionsPayload,
6
- DataApiMethods,
7
- } from "@racletteCore/types"
6
+ VueQueryParamsInput,
7
+ VueStoreActionsPayload,
8
+ } from "../types/DataApi"
9
+ import { toQueryParamsSource } from "../lib/queryParamsSource"
8
10
  import getStateObservable from "@racletteCore/lib/observableHelpers/getStateObservable"
9
11
  import type { Observable } from "rxjs"
10
12
 
@@ -14,8 +16,9 @@ const subscriberCounter = {}
14
16
  export const useVueWriteOperationHelper = (
15
17
  instanceActionId,
16
18
  operationDefinition,
17
- { params = {}, options = {} }: CustomStoreActionsPayload = {},
19
+ { params = {}, options = {} }: VueStoreActionsPayload = {},
18
20
  ): DataApiMethods => {
21
+ const paramsSource = toQueryParamsSource(params)
19
22
  let writeOperation
20
23
  const dataStoreObservable$ = ref<Observable<any>>()
21
24
 
@@ -34,7 +37,7 @@ export const useVueWriteOperationHelper = (
34
37
  } else {
35
38
  subscriberCounter[instanceActionId] = 1
36
39
  writeOperation = $writeDataApi(instanceActionId, operationDefinition, {
37
- params,
40
+ params: paramsSource,
38
41
  options,
39
42
  })
40
43
  }
@@ -84,7 +87,10 @@ export const useVueWriteOperationHelper = (
84
87
  return data.value ? Object.values(data.value) : []
85
88
  }),
86
89
  response: responseRef,
87
- execute: writeOperation.execute,
90
+ execute: (executeParams?: VueQueryParamsInput) =>
91
+ executeParams === undefined
92
+ ? writeOperation.execute()
93
+ : writeOperation.execute(toQueryParamsSource(executeParams)),
88
94
  remove: writeOperation.remove,
89
95
  clear: writeOperation.clear,
90
96
  error: computed(() => {
@@ -140,6 +140,7 @@
140
140
  "socket_status_0": "Server nicht verbunden",
141
141
  "socket_status_1": "Server verbunden",
142
142
  "socket_status_2": "Verbindung zum Server wird hergestellt...",
143
+ "app_loading": "Anwendungsinhalt wird geladen",
143
144
  "dataSynced$displayName$type": "{type} \"{displayName}\" wurde synchronisiert",
144
145
  "dataCreate$displayName$type": "{type} \"{displayName}\" wurde erstellt",
145
146
  "dataUpdate$displayName$type": "{type} \"{displayName}\" wurde aktualisiert",
@@ -140,6 +140,7 @@
140
140
  "socket_status_0": "Server not connected",
141
141
  "socket_status_1": "Server connected",
142
142
  "socket_status_2": "Connecting to server...",
143
+ "app_loading": "Loading application content",
143
144
  "dataSynced$displayName$type": "{type} \"{displayName}\" has been synchronized",
144
145
  "dataCreate$displayName$type": "{type} \"{displayName}\" has been created",
145
146
  "dataUpdate$displayName$type": "{type} \"{displayName}\" has been updated",
@@ -140,6 +140,7 @@
140
140
  "socket_status_0": "Server nie je pripojený",
141
141
  "socket_status_1": "Server je pripojený",
142
142
  "socket_status_2": "Pripájanie k serveru...",
143
+ "app_loading": "Načítava sa obsah aplikácie",
143
144
  "dataSynced$displayName$type": "{type} \"{displayName}\" bol synchronizovaný",
144
145
  "dataCreate$displayName$type": "{type} \"{displayName}\" bol vytvorený",
145
146
  "dataUpdate$displayName$type": "{type} \"{displayName}\" bol aktualizovaný",
@@ -110,6 +110,7 @@
110
110
  "socket_status_0": "Qun Qapbogh",
111
111
  "socket_status_1": "Qun QaptaH",
112
112
  "socket_status_2": "QunDaq QaptaH…",
113
+ "app_loading": "App SeHlaw Loading",
113
114
  "dataSynced$displayName$type": "{type} “{displayName}” QaptaH",
114
115
  "dataCreate$displayName$type": "{type} “{displayName}” botlhutlh",
115
116
  "dataUpdate$displayName$type": "{type} “{displayName}” Qapla’",