adminforth 2.26.2 → 2.27.0-next.2

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 (50) hide show
  1. package/commands/createApp/templates/package.json.hbs +1 -1
  2. package/dist/modules/restApi.d.ts +1 -0
  3. package/dist/modules/restApi.d.ts.map +1 -1
  4. package/dist/modules/restApi.js +25 -1
  5. package/dist/modules/restApi.js.map +1 -1
  6. package/dist/modules/styles.js +2 -2
  7. package/dist/modules/styles.js.map +1 -1
  8. package/dist/servers/express.d.ts.map +1 -1
  9. package/dist/servers/express.js +7 -1
  10. package/dist/servers/express.js.map +1 -1
  11. package/dist/spa/package-lock.json +44 -7
  12. package/dist/spa/package.json +1 -1
  13. package/dist/spa/pnpm-lock.yaml +301 -299
  14. package/dist/spa/src/App.vue +1 -1
  15. package/dist/spa/src/adminforth.ts +17 -29
  16. package/dist/spa/src/afcl/Input.vue +1 -1
  17. package/dist/spa/src/afcl/Modal.vue +12 -1
  18. package/dist/spa/src/afcl/Select.vue +4 -2
  19. package/dist/spa/src/afcl/Table.vue +27 -13
  20. package/dist/spa/src/components/AcceptModal.vue +2 -0
  21. package/dist/spa/src/components/ColumnValueInputWrapper.vue +11 -3
  22. package/dist/spa/src/components/CustomRangePicker.vue +16 -67
  23. package/dist/spa/src/components/ListActionsThreeDots.vue +9 -8
  24. package/dist/spa/src/components/RangePicker.vue +236 -0
  25. package/dist/spa/src/components/ResourceListTable.vue +45 -70
  26. package/dist/spa/src/components/Sidebar.vue +1 -1
  27. package/dist/spa/src/components/ThreeDotsMenu.vue +30 -52
  28. package/dist/spa/src/i18n.ts +1 -1
  29. package/dist/spa/src/stores/core.ts +4 -2
  30. package/dist/spa/src/types/Back.ts +11 -4
  31. package/dist/spa/src/types/Common.ts +26 -5
  32. package/dist/spa/src/types/FrontendAPI.ts +6 -1
  33. package/dist/spa/src/utils/listUtils.ts +8 -2
  34. package/dist/spa/src/utils/utils.ts +187 -10
  35. package/dist/spa/src/views/CreateView.vue +10 -10
  36. package/dist/spa/src/views/EditView.vue +10 -9
  37. package/dist/spa/src/views/ListView.vue +122 -18
  38. package/dist/spa/src/views/LoginView.vue +13 -13
  39. package/dist/spa/src/views/ShowView.vue +53 -60
  40. package/dist/spa/tsconfig.app.json +1 -1
  41. package/dist/types/Back.d.ts +8 -5
  42. package/dist/types/Back.d.ts.map +1 -1
  43. package/dist/types/Back.js.map +1 -1
  44. package/dist/types/Common.d.ts +21 -5
  45. package/dist/types/Common.d.ts.map +1 -1
  46. package/dist/types/Common.js.map +1 -1
  47. package/dist/types/FrontendAPI.d.ts +13 -1
  48. package/dist/types/FrontendAPI.d.ts.map +1 -1
  49. package/dist/types/FrontendAPI.js.map +1 -1
  50. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  <div>
3
3
  <nav
4
4
  v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout"
5
- class="fixed h-14 top-0 z-30 w-full border-b shadow-sm bg-lightNavbar shadow-headerShadow dark:bg-darkNavbar dark:border-darkSidebarDevider"
5
+ class="fixed h-14 top-0 z-30 w-full border-b drop-shadow-sm bg-lightNavbar dark:bg-darkNavbar dark:border-darkSidebarDevider"
6
6
  >
7
7
  <div class="af-header px-3 lg:px-5 lg:pl-3 flex items-center justify-between h-full w-full" >
8
8
  <div class="flex items-center justify-start rtl:justify-end">
@@ -19,26 +19,13 @@ class FrontendAPI implements FrontendAPIInterface {
19
19
  public modalStore:any
20
20
  public filtersStore:any
21
21
  public coreStore:any
22
- private saveInterceptors: Record<string, Array<(ctx: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }) => Promise<{ ok: boolean; error?: string | null; extra?: object; }>>> = {};
23
-
24
- public list: {
25
- refresh(): Promise<{ error? : string }>;
26
- silentRefresh(): Promise<{ error? : string }>;
27
- silentRefreshRow(pk: any): Promise<{ error? : string }>;
28
- closeThreeDotsDropdown(): Promise<{ error? : string }>;
29
- closeUserMenuDropdown: () => void;
30
- setFilter: (filter: FilterParams) => void;
31
- updateFilter: (filter: FilterParams) => void;
32
- clearFilters: () => void;
33
- }
22
+ private saveInterceptors: Record<string, Array<Parameters<FrontendAPIInterface['registerSaveInterceptor']>[0]>> = {};
34
23
 
35
- public menu: {
36
- refreshMenuBadges: () => void;
37
- }
24
+ public list: FrontendAPIInterface['list'];
38
25
 
39
- public show: {
40
- refresh(): void;
41
- }
26
+ public menu: FrontendAPIInterface['menu'];
27
+
28
+ public show: FrontendAPIInterface['show'];
42
29
 
43
30
  closeUserMenuDropdown(): void {
44
31
  console.log('closeUserMenuDropdown')
@@ -70,9 +57,6 @@ class FrontendAPI implements FrontendAPIInterface {
70
57
  console.log('closeThreeDotsDropdown')
71
58
  return { error: 'Not implemented' }
72
59
  },
73
- closeUserMenuDropdown: () => {
74
- console.log('closeUserMenuDropdown')
75
- },
76
60
  setFilter: this.setListFilter.bind(this),
77
61
  updateFilter: this.updateListFilter.bind(this),
78
62
  clearFilters: this.clearListFilters.bind(this),
@@ -83,11 +67,15 @@ class FrontendAPI implements FrontendAPIInterface {
83
67
  console.log('show.refresh')
84
68
  }
85
69
  }
70
+
71
+ this.closeUserMenuDropdown = () => {
72
+ console.log('closeUserMenuDropdown')
73
+ };
86
74
  }
87
75
 
88
76
  registerSaveInterceptor(
89
- handler: (ctx: { action: 'create'|'edit'; values: any; resource: any; }) => Promise<{ ok: boolean; error?: string | null; extra?: object; }>,
90
- ): void {
77
+ handler: Parameters<FrontendAPIInterface['registerSaveInterceptor']>[0]
78
+ ): ReturnType<FrontendAPIInterface['registerSaveInterceptor']> {
91
79
  const rid = router.currentRoute.value?.params?.resourceId as string;
92
80
  if (!rid) {
93
81
  return;
@@ -98,7 +86,7 @@ class FrontendAPI implements FrontendAPIInterface {
98
86
  this.saveInterceptors[rid].push(handler);
99
87
  }
100
88
 
101
- async runSaveInterceptors(params: { action: 'create'|'edit'; values: any; resource: any; resourceId: string; }): Promise<{ ok: boolean; error?: string | null; extra?: object; }> {
89
+ async runSaveInterceptors(params: Parameters<FrontendAPIInterface['runSaveInterceptors']>[0]): ReturnType<FrontendAPIInterface['runSaveInterceptors']> {
102
90
  const list = this.saveInterceptors[params.resourceId] || [];
103
91
  const aggregatedExtra: Record<string, any> = {};
104
92
  for (const fn of list) {
@@ -120,7 +108,7 @@ class FrontendAPI implements FrontendAPIInterface {
120
108
  return { ok: true, extra: aggregatedExtra };
121
109
  }
122
110
 
123
- clearSaveInterceptors(resourceId?: string): void {
111
+ clearSaveInterceptors(resourceId?: Parameters<FrontendAPIInterface['clearSaveInterceptors']>[0]): ReturnType<FrontendAPIInterface['clearSaveInterceptors']> {
124
112
  if (resourceId) {
125
113
  delete this.saveInterceptors[resourceId];
126
114
  } else {
@@ -128,7 +116,7 @@ class FrontendAPI implements FrontendAPIInterface {
128
116
  }
129
117
  }
130
118
 
131
- confirm(params: ConfirmParams): Promise<boolean> {
119
+ confirm(params: Parameters<FrontendAPIInterface['confirm']>[0]): ReturnType<FrontendAPIInterface['confirm']> {
132
120
  return new Promise((resolve, reject) => {
133
121
  this.modalStore.setModalContent({
134
122
  content: params.message,
@@ -142,7 +130,7 @@ class FrontendAPI implements FrontendAPIInterface {
142
130
  })
143
131
  }
144
132
 
145
- alert(params: AlertParams): void | Promise<string> | string {
133
+ alert(params: Parameters<FrontendAPIInterface['alert']>[0]): ReturnType<FrontendAPIInterface['alert']> {
146
134
  const toats = {
147
135
  message: params.message,
148
136
  messageHtml: params.messageHtml,
@@ -162,14 +150,14 @@ class FrontendAPI implements FrontendAPIInterface {
162
150
  }
163
151
  }
164
152
 
165
- listFilterValidation(filter: FilterParams): boolean {
153
+ listFilterValidation(filter: Parameters<FrontendAPIInterface['list']['setFilter']>[0]): boolean {
166
154
  if(router.currentRoute.value.meta.type !== 'list'){
167
155
  throw new Error(`Cannot use ${this.setListFilter.name} filter on a list page`)
168
156
  }
169
157
  return true
170
158
  }
171
159
 
172
- setListFilter(filter: FilterParams): void {
160
+ setListFilter(filter: Parameters<FrontendAPIInterface['list']['setFilter']>[0]): ReturnType<FrontendAPIInterface['list']['setFilter']> {
173
161
  if(this.listFilterValidation(filter)){
174
162
  const existingFilterIndex = this.filtersStore.filters.findIndex((f: any) => {
175
163
  return f.field === filter.field && f.operator === filter.operator
@@ -43,7 +43,7 @@ const isIos = coreStore.isIos;
43
43
 
44
44
  const props = defineProps<{
45
45
  type: string,
46
- fullWidth: boolean,
46
+ fullWidth?: boolean,
47
47
  modelValue: string,
48
48
  suffix?: string,
49
49
  prefix?: string,
@@ -6,7 +6,13 @@
6
6
  <slot name="trigger"></slot>
7
7
  </div>
8
8
  <Teleport to="body">
9
- <div v-show="isModalOpen" v-if="!removeFromDom" @click="backdropClick" class="bg-black/50 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full h-full md:inset-0 h-1rem max-h-full flex" >
9
+ <div
10
+ v-show="isModalOpen"
11
+ v-if="!removeFromDom"
12
+ @click="backdropClick"
13
+ class="bg-black/50 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full h-full md:inset-0 h-1rem max-h-full flex"
14
+ :class="props.backgroundCustomClasses"
15
+ >
10
16
  <!-- Modal content -->
11
17
  <div v-bind="$attrs" class="relative bg-lightDialogBackgorund rounded-lg shadow-sm dark:bg-darkDialogBackgorund">
12
18
 
@@ -19,6 +25,7 @@
19
25
  <div
20
26
  v-if="showConfirmationOnClose"
21
27
  class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-[60]"
28
+ :class="props.modalCustomClasses"
22
29
  >
23
30
  <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
24
31
  <h2 class="text-lg font-semibold mb-4 text-lightDialogHeaderText dark:text-darkDialogHeaderText">Confirm Close</h2>
@@ -64,6 +71,8 @@ interface DialogProps {
64
71
  askForCloseConfirmation?: boolean
65
72
  closeConfirmationText?: string
66
73
  removeFromDomOnClose?: boolean
74
+ backgroundCustomClasses?: string
75
+ modalCustomClasses?: string
67
76
  }
68
77
 
69
78
  const props = withDefaults(defineProps<DialogProps>(), {
@@ -74,6 +83,8 @@ const props = withDefaults(defineProps<DialogProps>(), {
74
83
  askForCloseConfirmation: false,
75
84
  closeConfirmationText: 'Are you sure you want to close this dialog?',
76
85
  removeFromDomOnClose: false,
86
+ backgroundCustomClasses: '',
87
+ modalCustomClasses: '',
77
88
  })
78
89
 
79
90
  const showConfirmationOnClose = ref(false);
@@ -119,10 +119,12 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick,type PropType, t
119
119
  import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
120
120
  import { useElementSize } from '@vueuse/core'
121
121
 
122
+ type ISingleSelectModelValue = string | number;
123
+
122
124
  const props = defineProps({
123
125
  options: Array,
124
126
  modelValue: {
125
- type: Array as PropType<(string | number)[] | (string | number)>,
127
+ type: Array as PropType<(ISingleSelectModelValue)[] | ISingleSelectModelValue>,
126
128
  default: () => [],
127
129
  },
128
130
  multiple: {
@@ -201,7 +203,7 @@ function updateFromProps() {
201
203
  selectedItems.value = [];
202
204
  }
203
205
  } else {
204
- selectedItems.value = props.options?.filter((item: any) => props.modelValue?.includes(item.value)) || [];
206
+ selectedItems.value = props.options?.filter((item: any) => (props.modelValue as (ISingleSelectModelValue)[])?.includes(item.value)) || [];
205
207
  }
206
208
  }
207
209
  }
@@ -91,12 +91,12 @@
91
91
  <template #total><span class="font-semibold text-lightTablePaginationNumeration dark:text-darkTablePaginationNumeration">{{ dataResult.total }}</span></template>
92
92
  </i18n-t>
93
93
  <div class="af-pagination-container flex flex-row items-center xs:flex-row xs:justify-between xs:items-center gap-3">
94
- <div class="inline-flex" :class="isLoading || props.isLoading ? 'pointer-events-none select-none opacity-50' : ''">
94
+ <div class="inline-flex" :class="blockPagination ? 'pointer-events-none select-none opacity-50' : ''">
95
95
  <!-- Buttons -->
96
96
  <button
97
97
  class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightActivePaginationButtonText bg-lightActivePaginationButtonBackground border-r-0 rounded-s hover:opacity-90 dark:bg-darkActivePaginationButtonBackground dark:text-darkActivePaginationButtonText disabled:opacity-50"
98
98
  @click="currentPage--; pageInput = currentPage.toString();"
99
- :disabled="currentPage <= 1 || isLoading || props.isLoading">
99
+ :disabled="currentPage <= 1 || blockPagination">
100
100
  <svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
101
101
  viewBox="0 0 14 10">
102
102
  <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
@@ -106,7 +106,7 @@
106
106
  <button
107
107
  class="flex items-center py-1 px-3 text-sm font-medium text-lightUnactivePaginationButtonText focus:outline-none bg-lightUnactivePaginationButtonBackground border-r-0 border border-lightUnactivePaginationButtonBorder hover:bg-lightUnactivePaginationButtonHoverBackground hover:text-lightUnactivePaginationButtonHoverText dark:bg-darkUnactivePaginationButtonBackground dark:text-darkUnactivePaginationButtonText dark:border-darkUnactivePaginationButtonBorder dark:hover:text-darkUnactivePaginationButtonHoverText dark:hover:bg-darkUnactivePaginationButtonHoverBackground disabled:opacity-50"
108
108
  @click="switchPage(1); pageInput = currentPage.toString();"
109
- :disabled="currentPage <= 1 || isLoading || props.isLoading">
109
+ :disabled="currentPage <= 1 || blockPagination">
110
110
  <!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
111
111
  1
112
112
  </button>
@@ -123,7 +123,7 @@
123
123
  <button
124
124
  class="flex items-center py-1 px-3 text-sm font-medium text-lightUnactivePaginationButtonText focus:outline-none bg-lightUnactivePaginationButtonBackground border-l-0 border border-lightUnactivePaginationButtonBorder hover:bg-lightUnactivePaginationButtonHoverBackground hover:text-lightUnactivePaginationButtonHoverText dark:bg-darkUnactivePaginationButtonBackground dark:text-darkUnactivePaginationButtonText dark:border-darkUnactivePaginationButtonBorder dark:hover:text-darkUnactivePaginationButtonHoverText dark:hover:bg-darkUnactivePaginationButtonHoverBackground disabled:opacity-50"
125
125
  @click="currentPage = totalPages; pageInput = currentPage.toString();"
126
- :disabled="currentPage >= totalPages || isLoading || props.isLoading"
126
+ :disabled="currentPage >= totalPages || blockPagination"
127
127
  >
128
128
  {{ totalPages }}
129
129
 
@@ -131,7 +131,7 @@
131
131
  <button
132
132
  class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightActivePaginationButtonText focus:outline-none bg-lightActivePaginationButtonBackground border-l-0 rounded-e hover:opacity-90 dark:bg-darkActivePaginationButtonBackground dark:text-darkActivePaginationButtonText disabled:opacity-50"
133
133
  @click="currentPage++; pageInput = currentPage.toString();"
134
- :disabled="currentPage >= totalPages || isLoading || props.isLoading"
134
+ :disabled="currentPage >= totalPages || blockPagination"
135
135
  >
136
136
  <svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
137
137
  viewBox="0 0 14 10">
@@ -163,7 +163,7 @@
163
163
  }[],
164
164
  data: {
165
165
  [key: string]: any,
166
- }[] | ((params: { offset: number, limit: number, sortField?: string, sortDirection?: 'asc' | 'desc' }) => Promise<{data: {[key: string]: any}[], total: number}>),
166
+ }[] | ((params: { offset: number, limit: number, sortField?: string, sortDirection?: 'asc' | 'desc' }, abortSignal?: AbortSignal) => Promise<{data: {[key: string]: any}[], total: number}>),
167
167
  evenHighlights?: boolean,
168
168
  pageSize?: number,
169
169
  isLoading?: boolean,
@@ -171,9 +171,11 @@
171
171
  defaultSortDirection?: 'asc' | 'desc',
172
172
  makeHeaderSticky?: boolean,
173
173
  makePaginationSticky?: boolean,
174
+ blockPaginationOnLoading?: boolean,
174
175
  }>(), {
175
176
  evenHighlights: true,
176
177
  pageSize: 5,
178
+ blockPaginationOnLoading: true,
177
179
  }
178
180
  );
179
181
 
@@ -188,6 +190,9 @@
188
190
  const isAtLeastOneLoading = ref<boolean[]>([false]);
189
191
  const currentSortField = ref<string | undefined>(props.defaultSortField);
190
192
  const currentSortDirection = ref<'asc' | 'desc'>(props.defaultSortDirection ?? 'asc');
193
+ const oldAbortController = ref<AbortController | null>(null);
194
+
195
+ const blockPagination = computed(() => (isLoading.value || props.isLoading) && props.blockPaginationOnLoading);
191
196
 
192
197
  onMounted(() => {
193
198
  // If defaultSortField points to a non-sortable column, ignore it
@@ -277,16 +282,25 @@
277
282
  isLoading.value = true;
278
283
  const currentLoadingIndex = currentPage.value;
279
284
  isAtLeastOneLoading.value[currentLoadingIndex] = true;
280
- const result = await props.data({
281
- offset: (currentLoadingIndex - 1) * props.pageSize,
282
- limit: props.pageSize,
283
- sortField: currentSortField.value,
284
- ...(currentSortField.value ? { sortDirection: currentSortDirection.value } : {}),
285
- });
285
+ const abortController = new AbortController();
286
+ if (oldAbortController.value) {
287
+ oldAbortController.value.abort();
288
+ }
289
+ oldAbortController.value = abortController;
290
+ const result = await props.data(
291
+ {
292
+ offset: (currentLoadingIndex - 1) * props.pageSize,
293
+ limit: props.pageSize,
294
+ sortField: currentSortField.value,
295
+ ...(currentSortField.value ? { sortDirection: currentSortDirection.value } : {}),
296
+ },
297
+ abortController.signal
298
+ );
286
299
  isAtLeastOneLoading.value[currentLoadingIndex] = false;
287
300
  if (isAtLeastOneLoading.value.every(v => v === false)) {
288
301
  isLoading.value = false;
289
302
  }
303
+ if(abortController.signal.aborted) return;
290
304
  dataResult.value = result;
291
305
  } else if (typeof props.data === 'object' && Array.isArray(props.data)) {
292
306
  const start = (currentPage.value - 1) * props.pageSize;
@@ -350,7 +364,7 @@ function sortArrayData(data:any[], sortField?:string, dir:'asc'|'desc'='asc') {
350
364
  });
351
365
  }
352
366
 
353
- function tableRowClick(row) {
367
+ function tableRowClick(row: any) {
354
368
  emit("clickTableRow", row)
355
369
  }
356
370
  </script>
@@ -3,6 +3,8 @@
3
3
  <Modal
4
4
  ref="modalRef"
5
5
  :beforeCloseFunction="()=>{modalStore.onAcceptFunction(false);modalStore.isOpened=false}"
6
+ backgroundCustomClasses="z-[998]"
7
+ modalCustomClasses="z-[999]"
6
8
  >
7
9
  <div class="relative p-4 w-full max-w-md max-h-full" >
8
10
  <button type="button" @click="modalStore.togleModal()" class="absolute top-3 end-2.5 text-lightAcceptModalCloseIcon bg-transparent hover:bg-lightAcceptModalCloseIconHoverBackground hover:text-lightAcceptModalCloseIconHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkAcceptModalCloseIcon dark:hover:bg-darkAcceptModalCloseIconHoverBackground dark:hover:text-darkAcceptModalCloseIconHover" >
@@ -16,11 +16,11 @@
16
16
  :unmasked="unmasked"
17
17
  :deletable="!column.editReadonly"
18
18
  @update:modelValue="setCurrentValue(column.name, $event, arrayItemIndex)"
19
- @update:recordFieldValue="({ fieldName, fieldValue }: { fieldName: string; fieldValue: any }) => setCurrentValue(fieldName, fieldValue)"
19
+ @update:recordFieldValue="recordFieldValueUpdate"
20
20
  @update:unmasked="$emit('update:unmasked', column.name)"
21
21
  @update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
22
22
  @update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
23
- @delete="setCurrentValue(column.name, currentValues[column.name].filter((_: any, index: any) => index !== arrayItemIndex))"
23
+ @delete="deleteHandler(arrayItemIndex)"
24
24
  />
25
25
  </div>
26
26
  <div class="flex items-center">
@@ -48,7 +48,7 @@
48
48
  :columnOptions="columnOptions"
49
49
  :unmasked="unmasked"
50
50
  @update:modelValue="setCurrentValue(column.name, $event)"
51
- @update:recordFieldValue="({ fieldName, fieldValue }: { fieldName: string; fieldValue: any }) => setCurrentValue(fieldName, fieldValue)"
51
+ @update:recordFieldValue="recordFieldValueUpdate"
52
52
  @update:unmasked="$emit('update:unmasked', column.name)"
53
53
  @update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
54
54
  @update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
@@ -80,4 +80,12 @@
80
80
  await nextTick();
81
81
  arrayItemRefs.value[arrayItemRefs.value.length - 1].focus();
82
82
  }
83
+
84
+ function recordFieldValueUpdate({ fieldName, fieldValue }: { fieldName: string; fieldValue: any }) {
85
+ props.setCurrentValue(fieldName, fieldValue);
86
+ }
87
+
88
+ function deleteHandler(arrayItemIndex: number | string) {
89
+ props.setCurrentValue(props.column.name, props.currentValues[props.column.name].filter((_: any, index: any) => index !== arrayItemIndex));
90
+ }
83
91
  </script>
@@ -19,8 +19,7 @@
19
19
  >
20
20
 
21
21
  <div v-if="min && max" class="w-full px-2.5">
22
- <vue-slider
23
- class="custom-slider"
22
+ <RangePicker
24
23
  :dot-size="20"
25
24
  height="7.99px"
26
25
  :min="minFormatted"
@@ -32,10 +31,9 @@
32
31
  </div>
33
32
  </template>
34
33
  <script setup lang="ts">
35
- import VueSlider from 'vue-slider-component';
36
- import 'vue-slider-component/theme/antd.css'
37
34
  import {computed, onMounted, ref, watch} from "vue";
38
35
  import debounce from 'debounce'
36
+ import RangePicker from './RangePicker.vue';
39
37
 
40
38
  const props = defineProps({
41
39
  valueStart: {
@@ -57,7 +55,19 @@ const maxFormatted = computed(() => Math.ceil(<number>props.max));
57
55
  const start = ref<string | number>(props.valueStart);
58
56
  const end = ref<string | number>(props.valueEnd);
59
57
 
60
- const sliderValue = ref([minFormatted.value, maxFormatted.value]);
58
+ const sliderValue = ref<[number, number]>([minFormatted.value, maxFormatted.value]);
59
+
60
+ watch([start, end], () => {
61
+ if ( !start.value && end.value ) {
62
+ setSliderValues(minFormatted.value, end.value);
63
+ } else if ( start.value && !end.value ) {
64
+ setSliderValues(start.value, maxFormatted.value);
65
+ } else if ( !start.value && !end.value ) {
66
+ setSliderValues(minFormatted.value, maxFormatted.value);
67
+ } else {
68
+ setSliderValues(start.value, end.value);
69
+ }
70
+ })
61
71
 
62
72
  const updateFromSlider =
63
73
  debounce((value: [number, number]) => {
@@ -111,65 +121,4 @@ watch([minFormatted,maxFormatted], () => {
111
121
  function setSliderValues(start: any, end: any) {
112
122
  sliderValue.value = [start || minFormatted.value, end || maxFormatted.value];
113
123
  }
114
- </script>
115
-
116
- <style lang="scss" scoped>
117
- .custom-slider {
118
- &:deep(.vue-slider-rail) {
119
- background-color: rgb(229 231 235);
120
- }
121
-
122
- &:deep(.vue-slider-dot-handle) {
123
- // apply bg-blue-500 to the handle when active
124
- @apply bg-lightPrimary;
125
- border: none;
126
- box-shadow: none;
127
- }
128
-
129
- &:deep(.vue-slider-dot-handle:hover) {
130
- @apply bg-lightPrimary;
131
- filter: brightness(1.1);
132
- border: none;
133
- box-shadow: none;
134
- }
135
-
136
- &:deep(.vue-slider-process) {
137
- @apply bg-lightPrimaryOpacity;
138
-
139
- }
140
-
141
- &:deep(.vue-slider-process:hover) {
142
- filter: brightness(1.1);
143
- @apply bg-lightPrimaryOpacity;
144
- }
145
- }
146
-
147
- .dark .custom-slider {
148
- &:deep(.vue-slider-rail) {
149
- background-color: rgb(55 65 81); // gray-700
150
- }
151
-
152
- &:deep(.vue-slider-dot-handle) {
153
- @apply bg-darkPrimary;
154
- border: none;
155
- box-shadow: none;
156
- }
157
-
158
- &:deep(.vue-slider-dot-handle:hover) {
159
- @apply bg-darkPrimary;
160
- filter: brightness(1.1);
161
- border: none;
162
- box-shadow: none;
163
- }
164
-
165
- &:deep(.vue-slider-process) {
166
- @apply bg-darkPrimaryOpacity;
167
- }
168
-
169
- &:deep(.vue-slider-process:hover) {
170
- filter: brightness(1.1);
171
- @apply bg-darkPrimaryOpacity;
172
- }
173
- }
174
-
175
- </style>
124
+ </script>
@@ -57,14 +57,14 @@
57
57
  {{ $t('Delete item') }}
58
58
  </button>
59
59
  </template>
60
- <div v-for="action in (resourceOptions.actions ?? []).filter(a => a.showIn?.listThreeDotsMenu)" :key="action.id" >
60
+ <div v-for="action in (resourceOptions.actions ?? []).filter((a: AdminForthActionInput) => a.showIn?.listThreeDotsMenu)" :key="action.id" >
61
61
  <button class="flex text-nowrap p-1 hover:bg-gray-100 dark:hover:bg-gray-800 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300" @click="() => { startCustomAction(action.id, record); showMenu = false; }">
62
62
  <component
63
63
  :is="action.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
64
64
  :meta="action.customComponent?.meta"
65
65
  :row="record"
66
- :resource="resource"
67
- :adminUser="adminUser"
66
+ :resource="coreStore.resource"
67
+ :adminUser="coreStore.adminUser"
68
68
  @callAction="(payload? : Object) => startCustomAction(action.id, record, payload)"
69
69
  >
70
70
  <component
@@ -79,8 +79,8 @@
79
79
  <template v-if="customActionIconsThreeDotsMenuItems">
80
80
  <component
81
81
  v-for="c in customActionIconsThreeDotsMenuItems"
82
- :is="getCustomComponent(c)"
83
- :meta="c.meta"
82
+ :is="getCustomComponent(formatComponent(c))"
83
+ :meta="formatComponent(c).meta"
84
84
  :resource="coreStore.resource"
85
85
  :adminUser="coreStore.adminUser"
86
86
  :record="record"
@@ -100,9 +100,10 @@ import {
100
100
  IconDotsHorizontalOutline
101
101
  } from '@iconify-prerendered/vue-flowbite';
102
102
  import { onMounted, onBeforeUnmount, ref, nextTick, watch } from 'vue';
103
- import { getIcon, getCustomComponent } from '@/utils';
103
+ import { getIcon, getCustomComponent, formatComponent } from '@/utils';
104
104
  import { useCoreStore } from '@/stores/core';
105
105
  import CallActionWrapper from '@/components/CallActionWrapper.vue'
106
+ import { type AdminForthActionInput, type AdminForthComponentDeclaration, type AdminForthComponentDeclarationFull } from '@/types/Common';
106
107
 
107
108
  const coreStore = useCoreStore();
108
109
  const showMenu = ref(false);
@@ -113,11 +114,11 @@ const menuStyles = ref<Record<string, string>>({});
113
114
  const props = defineProps<{
114
115
  resourceOptions: any;
115
116
  record: any;
116
- customActionIconsThreeDotsMenuItems: any[];
117
+ customActionIconsThreeDotsMenuItems: AdminForthComponentDeclaration[];
117
118
  resourceId: string;
118
119
  deleteRecord: (record: any) => void;
119
120
  updateRecords: () => void;
120
- startCustomAction: (actionId: string, record: any) => void;
121
+ startCustomAction: (actionId: string, row: any, extraData?: Record<string, any>) => void;
121
122
  }>();
122
123
 
123
124
  onMounted(() => {