adminforth 2.26.0-next.3 → 2.26.0-next.31

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 (88) hide show
  1. package/commands/createApp/templates/package.json.hbs +1 -1
  2. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  3. package/dist/dataConnectors/baseConnector.js +3 -0
  4. package/dist/dataConnectors/baseConnector.js.map +1 -1
  5. package/dist/dataConnectors/clickhouse.d.ts +4 -0
  6. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  7. package/dist/dataConnectors/clickhouse.js +14 -0
  8. package/dist/dataConnectors/clickhouse.js.map +1 -1
  9. package/dist/dataConnectors/mongo.d.ts +4 -0
  10. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  11. package/dist/dataConnectors/mongo.js +9 -0
  12. package/dist/dataConnectors/mongo.js.map +1 -1
  13. package/dist/dataConnectors/mysql.d.ts +4 -0
  14. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  15. package/dist/dataConnectors/mysql.js +11 -0
  16. package/dist/dataConnectors/mysql.js.map +1 -1
  17. package/dist/dataConnectors/postgres.d.ts +4 -0
  18. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  19. package/dist/dataConnectors/postgres.js +11 -0
  20. package/dist/dataConnectors/postgres.js.map +1 -1
  21. package/dist/dataConnectors/qdrant.d.ts +57 -0
  22. package/dist/dataConnectors/qdrant.d.ts.map +1 -0
  23. package/dist/dataConnectors/qdrant.js +469 -0
  24. package/dist/dataConnectors/qdrant.js.map +1 -0
  25. package/dist/dataConnectors/sqlite.d.ts +4 -0
  26. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  27. package/dist/dataConnectors/sqlite.js +11 -0
  28. package/dist/dataConnectors/sqlite.js.map +1 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +22 -33
  31. package/dist/index.js.map +1 -1
  32. package/dist/modules/codeInjector.d.ts.map +1 -1
  33. package/dist/modules/codeInjector.js +59 -50
  34. package/dist/modules/codeInjector.js.map +1 -1
  35. package/dist/modules/configValidator.d.ts.map +1 -1
  36. package/dist/modules/configValidator.js +3 -2
  37. package/dist/modules/configValidator.js.map +1 -1
  38. package/dist/modules/restApi.d.ts +1 -0
  39. package/dist/modules/restApi.d.ts.map +1 -1
  40. package/dist/modules/restApi.js +33 -16
  41. package/dist/modules/restApi.js.map +1 -1
  42. package/dist/modules/utils.d.ts +6 -0
  43. package/dist/modules/utils.d.ts.map +1 -1
  44. package/dist/modules/utils.js +13 -0
  45. package/dist/modules/utils.js.map +1 -1
  46. package/dist/servers/express.d.ts.map +1 -1
  47. package/dist/servers/express.js +7 -1
  48. package/dist/servers/express.js.map +1 -1
  49. package/dist/spa/package-lock.json +57 -0
  50. package/dist/spa/package.json +2 -0
  51. package/dist/spa/pnpm-lock.yaml +32 -0
  52. package/dist/spa/src/adminforth.ts +17 -29
  53. package/dist/spa/src/afcl/Input.vue +1 -1
  54. package/dist/spa/src/afcl/Modal.vue +18 -3
  55. package/dist/spa/src/afcl/Select.vue +4 -2
  56. package/dist/spa/src/afcl/Table.vue +27 -13
  57. package/dist/spa/src/components/AcceptModal.vue +33 -53
  58. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +4 -5
  59. package/dist/spa/src/components/ColumnValueInputWrapper.vue +11 -3
  60. package/dist/spa/src/components/ListActionsThreeDots.vue +10 -9
  61. package/dist/spa/src/components/ResourceListTable.vue +291 -144
  62. package/dist/spa/src/components/Sidebar.vue +6 -2
  63. package/dist/spa/src/components/ThreeDotsMenu.vue +10 -9
  64. package/dist/spa/src/i18n.ts +1 -1
  65. package/dist/spa/src/renderers/CountryFlag.vue +2 -2
  66. package/dist/spa/src/stores/core.ts +4 -2
  67. package/dist/spa/src/types/Back.ts +24 -5
  68. package/dist/spa/src/types/Common.ts +45 -5
  69. package/dist/spa/src/types/FrontendAPI.ts +6 -1
  70. package/dist/spa/src/utils/listUtils.ts +8 -2
  71. package/dist/spa/src/utils/utils.ts +29 -10
  72. package/dist/spa/src/views/CreateView.vue +8 -8
  73. package/dist/spa/src/views/EditView.vue +8 -7
  74. package/dist/spa/src/views/ListView.vue +14 -48
  75. package/dist/spa/src/views/LoginView.vue +13 -13
  76. package/dist/spa/src/views/PageNotFound.vue +5 -1
  77. package/dist/spa/src/views/ShowView.vue +6 -6
  78. package/dist/types/Back.d.ts +23 -6
  79. package/dist/types/Back.d.ts.map +1 -1
  80. package/dist/types/Back.js.map +1 -1
  81. package/dist/types/Common.d.ts +25 -5
  82. package/dist/types/Common.d.ts.map +1 -1
  83. package/dist/types/Common.js.map +1 -1
  84. package/dist/types/FrontendAPI.d.ts +13 -1
  85. package/dist/types/FrontendAPI.d.ts.map +1 -1
  86. package/dist/types/FrontendAPI.js.map +1 -1
  87. package/package.json +2 -1
  88. package/dist/spa/src/components/ResourceListTableVirtual.vue +0 -794
@@ -14,6 +14,9 @@ importers:
14
14
  '@iconify-prerendered/vue-flowbite':
15
15
  specifier: ^0.28.1754899090
16
16
  version: 0.28.1754899090(vue@3.5.29(typescript@5.4.5))
17
+ '@iconify-prerendered/vue-heroicons':
18
+ specifier: ^0.28.1721921294
19
+ version: 0.28.1721921294(vue@3.5.29(typescript@5.4.5))
17
20
  '@iconify-prerendered/vue-humbleicons':
18
21
  specifier: ^0.28.1754108846
19
22
  version: 0.28.1754108846(vue@3.5.29(typescript@5.4.5))
@@ -78,6 +81,9 @@ importers:
78
81
  '@types/node':
79
82
  specifier: ^20.12.5
80
83
  version: 20.19.37
84
+ '@types/sanitize-html':
85
+ specifier: ^2.16.1
86
+ version: 2.16.1
81
87
  '@vitejs/plugin-vue':
82
88
  specifier: ^5.0.4
83
89
  version: 5.2.4(vite@5.4.21(@types/node@20.19.37)(sass@1.97.3))(vue@3.5.29(typescript@5.4.5))
@@ -335,6 +341,11 @@ packages:
335
341
  peerDependencies:
336
342
  vue: ^3.0.0
337
343
 
344
+ '@iconify-prerendered/vue-heroicons@0.28.1721921294':
345
+ resolution: {integrity: sha512-L/Xe9HeunSvUA2+KtlaRehFpKjdZPpzPWsynX1To4QmCPAF3YD0lwusV9fZrtkdi0Y5NYS8WZF1SDypkL65+dQ==}
346
+ peerDependencies:
347
+ vue: ^3.0.0
348
+
338
349
  '@iconify-prerendered/vue-humbleicons@0.28.1754108846':
339
350
  resolution: {integrity: sha512-O7X7bHh4QhR9ui098FK8Sh/fPA7JRg2rVrLxUpv8Xh0LpahI9u01YKy5flhkzcwwQ+A6zN2uuKI16rytrVVcaA==}
340
351
  peerDependencies:
@@ -669,6 +680,9 @@ packages:
669
680
  '@types/resolve@1.20.2':
670
681
  resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
671
682
 
683
+ '@types/sanitize-html@2.16.1':
684
+ resolution: {integrity: sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==}
685
+
672
686
  '@types/web-bluetooth@0.0.14':
673
687
  resolution: {integrity: sha512-5d2RhCard1nQUC3aHcq/gHzWYO6K0WJmAbjO7mQJgCQKtZpgXxv1rOM6O/dBDhDYYVutk1sciOgNSe+5YyfM8A==}
674
688
 
@@ -1265,6 +1279,9 @@ packages:
1265
1279
  hookable@5.5.3:
1266
1280
  resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
1267
1281
 
1282
+ htmlparser2@10.1.0:
1283
+ resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
1284
+
1268
1285
  htmlparser2@8.0.2:
1269
1286
  resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
1270
1287
 
@@ -2019,6 +2036,10 @@ snapshots:
2019
2036
  dependencies:
2020
2037
  vue: 3.5.29(typescript@5.4.5)
2021
2038
 
2039
+ '@iconify-prerendered/vue-heroicons@0.28.1721921294(vue@3.5.29(typescript@5.4.5))':
2040
+ dependencies:
2041
+ vue: 3.5.29(typescript@5.4.5)
2042
+
2022
2043
  '@iconify-prerendered/vue-humbleicons@0.28.1754108846(vue@3.5.29(typescript@5.4.5))':
2023
2044
  dependencies:
2024
2045
  vue: 3.5.29(typescript@5.4.5)
@@ -2253,6 +2274,10 @@ snapshots:
2253
2274
 
2254
2275
  '@types/resolve@1.20.2': {}
2255
2276
 
2277
+ '@types/sanitize-html@2.16.1':
2278
+ dependencies:
2279
+ htmlparser2: 10.1.0
2280
+
2256
2281
  '@types/web-bluetooth@0.0.14': {}
2257
2282
 
2258
2283
  '@types/web-bluetooth@0.0.20': {}
@@ -2960,6 +2985,13 @@ snapshots:
2960
2985
 
2961
2986
  hookable@5.5.3: {}
2962
2987
 
2988
+ htmlparser2@10.1.0:
2989
+ dependencies:
2990
+ domelementtype: 2.3.0
2991
+ domhandler: 5.0.3
2992
+ domutils: 3.2.2
2993
+ entities: 7.0.1
2994
+
2963
2995
  htmlparser2@8.0.2:
2964
2996
  dependencies:
2965
2997
  domelementtype: 2.3.0
@@ -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,18 +83,24 @@ 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);
80
91
 
81
92
 
82
93
  async function open() {
83
- await props.beforeOpenFunction?.();
94
+ if (props.beforeOpenFunction) {
95
+ await props.beforeOpenFunction?.();
96
+ }
84
97
  isModalOpen.value = true;
85
98
  }
86
99
 
87
100
  async function close() {
88
- await props.beforeCloseFunction?.();
101
+ if (props.beforeCloseFunction) {
102
+ await props.beforeCloseFunction?.();
103
+ }
89
104
  isModalOpen.value = false;
90
105
  }
91
106
 
@@ -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>
@@ -1,41 +1,42 @@
1
1
  <template>
2
2
  <Teleport to="body">
3
- <div ref="modalEl" tabindex="-1" aria-hidden="true" class="hidden fixed inset-0 z-[110] w-full h-full overflow-y-auto overflow-x-hidden flex items-center justify-center">
4
- <div class="relative p-4 w-full max-w-md max-h-full" >
5
- <div class="afcl-confirmation-container relative bg-lightAcceptModalBackground rounded-lg shadow dark:bg-darkAcceptModalBackground dark:shadow-black">
6
- <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" >
7
- <svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
8
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
9
- </svg>
10
- <span class="sr-only">{{ $t('Close modal') }}</span>
11
- </button>
12
- <div class="p-4 md:p-5 text-center">
13
- <svg class="mx-auto mb-4 text-lightAcceptModalWarningIcon w-12 h-12 dark:text-darkAcceptModalWarningIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
14
- <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
15
- </svg>
16
- <h3 class="afcl-confirmation-title mb-5 text-lg font-normal text-lightAcceptModalText dark:text-darkAcceptModalText">{{ modalStore?.modalContent?.content }}</h3>
17
- <h3 class=" afcl-confirmation-title mb-5 text-lg font-normal text-lightAcceptModalText dark:text-darkAcceptModalText" v-html="modalStore?.modalContent?.contentHTML"></h3>
18
-
19
- <button @click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}" type="button" class="afcl-confirmation-accept-button text-lightAcceptModalConfirmButtonText bg-lightAcceptModalConfirmButtonBackground hover:bg-lightAcceptModalConfirmButtonBackgroundHover focus:ring-4 focus:outline-none focus:ring-lightAcceptModalConfirmButtonFocus font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center dark:text-darkAcceptModalConfirmButtonText dark:bg-darkAcceptModalConfirmButtonBackground dark:hover:bg-darkAcceptModalConfirmButtonBackgroundHover dark:focus:ring-darkAcceptModalConfirmButtonFocus">
20
- {{ modalStore?.modalContent?.acceptText }}
21
- </button>
22
- <button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="afcl-confirmation-cancel-button py-2.5 px-5 ms-3 text-sm font-medium text-lightAcceptModalCancelButtonText focus:outline-none bg-lightAcceptModalCancelButtonBackground rounded-lg border border-lightAcceptModalCancelButtonBorder hover:bg-lightAcceptModalCancelButtonBackgroundHover hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-lightAcceptModalCancelButtonFocus dark:focus:ring-darkAcceptModalCancelButtonFocus dark:bg-darkAcceptModalCancelButtonBackground dark:text-darkAcceptModalCancelButtonText dark:border-darkAcceptModalCancelButtonBorder dark:hover:text-darkAcceptModalCancelButtonTextHover dark:hover:bg-darkAcceptModalCancelButtonBackgroundHover">{{ modalStore?.modalContent?.cancelText }}</button>
23
- </div>
24
- </div>
3
+ <Modal
4
+ ref="modalRef"
5
+ :beforeCloseFunction="()=>{modalStore.onAcceptFunction(false);modalStore.isOpened=false}"
6
+ backgroundCustomClasses="z-[998]"
7
+ modalCustomClasses="z-[999]"
8
+ >
9
+ <div class="relative p-4 w-full max-w-md max-h-full" >
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" >
11
+ <svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
12
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
13
+ </svg>
14
+ <span class="sr-only">{{ $t('Close modal') }}</span>
15
+ </button>
16
+ <div class="p-4 md:p-5 text-center">
17
+ <svg class="mx-auto mb-4 text-lightAcceptModalWarningIcon w-12 h-12 dark:text-darkAcceptModalWarningIcon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
18
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
19
+ </svg>
20
+ <h3 class="afcl-confirmation-title mb-5 text-lg font-normal text-lightAcceptModalText dark:text-darkAcceptModalText">{{ modalStore?.modalContent?.content }}</h3>
21
+ <h3 class=" afcl-confirmation-title mb-5 text-lg font-normal text-lightAcceptModalText dark:text-darkAcceptModalText" v-html="modalStore?.modalContent?.contentHTML"></h3>
22
+
23
+ <button @click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}" type="button" class="afcl-confirmation-accept-button text-lightAcceptModalConfirmButtonText bg-lightAcceptModalConfirmButtonBackground hover:bg-lightAcceptModalConfirmButtonBackgroundHover focus:ring-4 focus:outline-none focus:ring-lightAcceptModalConfirmButtonFocus font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center dark:text-darkAcceptModalConfirmButtonText dark:bg-darkAcceptModalConfirmButtonBackground dark:hover:bg-darkAcceptModalConfirmButtonBackgroundHover dark:focus:ring-darkAcceptModalConfirmButtonFocus">
24
+ {{ modalStore?.modalContent?.acceptText }}
25
+ </button>
26
+ <button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="afcl-confirmation-cancel-button py-2.5 px-5 ms-3 text-sm font-medium text-lightAcceptModalCancelButtonText focus:outline-none bg-lightAcceptModalCancelButtonBackground rounded-lg border border-lightAcceptModalCancelButtonBorder hover:bg-lightAcceptModalCancelButtonBackgroundHover hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-lightAcceptModalCancelButtonFocus dark:focus:ring-darkAcceptModalCancelButtonFocus dark:bg-darkAcceptModalCancelButtonBackground dark:text-darkAcceptModalCancelButtonText dark:border-darkAcceptModalCancelButtonBorder dark:hover:text-darkAcceptModalCancelButtonTextHover dark:hover:bg-darkAcceptModalCancelButtonBackgroundHover">{{ modalStore?.modalContent?.cancelText }}</button>
25
27
  </div>
26
- </div>
27
-
28
- </Teleport>
28
+ </div>
29
+ </Modal>
30
+ </Teleport>
29
31
  </template>
30
32
 
31
33
  <script setup lang="ts">
32
34
  import { watch, onMounted, nextTick, ref } from 'vue';
33
35
  import { useModalStore } from '@/stores/modal';
34
- import { Modal } from 'flowbite';
36
+ import { Modal } from '@/afcl';
35
37
 
38
+ const modalRef = ref();
36
39
  const modalStore = useModalStore();
37
- const modalEl = ref(null);
38
- const modal = ref(null);
39
40
 
40
41
  watch( () => modalStore.isOpened, (newVal) => {
41
42
  if (newVal) {
@@ -46,35 +47,14 @@ watch( () => modalStore.isOpened, (newVal) => {
46
47
  }
47
48
  );
48
49
 
49
- onMounted(async () => {
50
- await nextTick();
51
- modal.value = new Modal(
52
- modalEl.value,
53
- {
54
- closable: true,
55
- backdrop: 'static',
56
- backdropClasses: "bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-[100]"
57
- }
58
- );
59
- })
50
+
60
51
 
61
52
  function open() {
62
- modal.value?.show();
53
+ modalRef.value.open();
63
54
  }
64
55
 
65
56
  function close() {
66
- modal.value?.hide();
57
+ modalRef.value.close();
67
58
  }
68
59
 
69
- </script>
70
-
71
- <style scoped>
72
- .modal {
73
- position: fixed;
74
- z-index: 999;
75
- top: 20%;
76
- left: 50%;
77
- width: 300px;
78
- margin-left: -150px;
79
- }
80
- </style>
60
+ </script>
@@ -6,17 +6,16 @@
6
6
  <slot></slot>
7
7
  </div>
8
8
  </div>
9
- <div class="flex items-center justify-between mb-3 flex-wrap gap-y-2 gap-2" v-if="coreStore.resourceColumnsError">
10
- <div class="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400" role="alert">
11
- <span class="font-medium">{{ $t('Error!')}}</span> {{ coreStore.resourceColumnsError }}
12
- </div>
9
+ <div v-if="coreStore.resourceColumnsError" >
10
+ <PageNotFound :errorMessage="coreStore.resourceColumnsError" />
13
11
  </div>
14
12
  </div>
15
13
 
16
14
  </template>
17
15
 
18
16
  <script setup>
19
- import Breadcrumbs from '@/components/Breadcrumbs.vue';
17
+ import Breadcrumbs from '@/components/Breadcrumbs.vue';
18
+ import PageNotFound from '@/views/PageNotFound.vue';
20
19
 
21
20
  import { useCoreStore } from '@/stores/core';
22
21
 
@@ -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>
@@ -11,7 +11,7 @@
11
11
  <div
12
12
  v-if="showMenu"
13
13
  ref="menuRef"
14
- class="z-50 bg-white dark:bg-gray-900 rounded-md shadow-lg border dark:border-gray-700 py-1"
14
+ class="z-40 bg-white dark:bg-gray-900 rounded-md shadow-lg border dark:border-gray-700 py-1"
15
15
  :style="menuStyles"
16
16
  >
17
17
  <template v-if="!resourceOptions?.baseActionsAsQuickIcons || (resourceOptions?.baseActionsAsQuickIcons && !resourceOptions?.baseActionsAsQuickIcons.includes('show'))">
@@ -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(() => {