adminforth 2.13.0-next.6 → 2.13.0-next.61

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 (47) hide show
  1. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  2. package/dist/dataConnectors/postgres.js +2 -0
  3. package/dist/dataConnectors/postgres.js.map +1 -1
  4. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  5. package/dist/dataConnectors/sqlite.js +15 -1
  6. package/dist/dataConnectors/sqlite.js.map +1 -1
  7. package/dist/modules/configValidator.d.ts.map +1 -1
  8. package/dist/modules/configValidator.js +23 -3
  9. package/dist/modules/configValidator.js.map +1 -1
  10. package/dist/modules/restApi.d.ts.map +1 -1
  11. package/dist/modules/restApi.js +15 -7
  12. package/dist/modules/restApi.js.map +1 -1
  13. package/dist/modules/utils.d.ts +2 -0
  14. package/dist/modules/utils.d.ts.map +1 -1
  15. package/dist/modules/utils.js +51 -0
  16. package/dist/modules/utils.js.map +1 -1
  17. package/dist/spa/package-lock.json +768 -1412
  18. package/dist/spa/package.json +33 -32
  19. package/dist/spa/src/App.vue +45 -91
  20. package/dist/spa/src/afcl/Dialog.vue +65 -5
  21. package/dist/spa/src/afcl/Input.vue +1 -1
  22. package/dist/spa/src/afcl/Select.vue +3 -2
  23. package/dist/spa/src/components/CallActionWrapper.vue +1 -1
  24. package/dist/spa/src/components/ResourceListTable.vue +18 -9
  25. package/dist/spa/src/components/ResourceListTableVirtual.vue +19 -10
  26. package/dist/spa/src/components/Sidebar.vue +15 -3
  27. package/dist/spa/src/components/ThreeDotsMenu.vue +34 -6
  28. package/dist/spa/src/components/ValueRenderer.vue +1 -1
  29. package/dist/spa/src/stores/core.ts +8 -1
  30. package/dist/spa/src/stores/filters.ts +5 -0
  31. package/dist/spa/src/types/Back.ts +6 -0
  32. package/dist/spa/src/types/Common.ts +1 -0
  33. package/dist/spa/src/types/adapters/OAuth2Adapter.ts +1 -1
  34. package/dist/spa/src/utils.ts +31 -11
  35. package/dist/spa/src/views/ListView.vue +6 -0
  36. package/dist/spa/src/views/LoginView.vue +8 -2
  37. package/dist/spa/src/views/PageNotFound.vue +2 -2
  38. package/dist/spa/tsconfig.json +1 -1
  39. package/dist/types/Back.d.ts +4 -0
  40. package/dist/types/Back.d.ts.map +1 -1
  41. package/dist/types/Back.js.map +1 -1
  42. package/dist/types/Common.d.ts +1 -0
  43. package/dist/types/Common.d.ts.map +1 -1
  44. package/dist/types/Common.js.map +1 -1
  45. package/dist/types/adapters/OAuth2Adapter.d.ts +2 -0
  46. package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -1
  47. package/package.json +1 -1
@@ -13,47 +13,48 @@
13
13
  "i18n:extract": "echo {} > i18n-empty.json && vue-i18n-extract report --vueFiles \"./src/**/*.{js,vue,ts}\" --output ./i18n-messages.json --languageFiles \"i18n-empty.json\" --add"
14
14
  },
15
15
  "dependencies": {
16
- "@iconify-prerendered/vue-flag": "^0.28.1754899047",
16
+ "@iconify-prerendered/vue-flag": "^0.28.1748584105",
17
17
  "@iconify-prerendered/vue-flowbite": "^0.28.1754899090",
18
- "@unhead/vue": "^1.11.20",
19
- "@vueuse/core": "^10.11.1",
18
+ "@iconify-prerendered/vue-humbleicons": "^0.28.1754108846",
19
+ "@unhead/vue": "^1.9.12",
20
+ "@vueuse/core": "^10.10.0",
20
21
  "apexcharts": "^4.7.0",
21
- "dayjs": "^1.11.19",
22
- "debounce": "^2.2.0",
23
- "flowbite-datepicker": "^1.3.2",
24
- "javascript-time-ago": "^2.5.12",
25
- "pinia": "^2.3.1",
26
- "sanitize-html": "^2.17.0",
27
- "unhead": "^1.11.20",
22
+ "dayjs": "^1.11.11",
23
+ "debounce": "^2.1.0",
24
+ "flowbite-datepicker": "^1.2.6",
25
+ "javascript-time-ago": "^2.5.11",
26
+ "pinia": "^2.1.7",
27
+ "sanitize-html": "^2.13.0",
28
+ "unhead": "^1.9.12",
28
29
  "uuid": "^10.0.0",
29
- "vue": "^3.5.22",
30
+ "vue": "^3.5.12",
30
31
  "vue-diff": "^1.2.4",
31
- "vue-i18n": "^10.0.8",
32
- "vue-router": "^4.6.3",
32
+ "vue-i18n": "^10.0.5",
33
+ "vue-router": "^4.3.0",
33
34
  "vue-slider-component": "^4.1.0-beta.7"
34
35
  },
35
36
  "devDependencies": {
36
- "@rushstack/eslint-patch": "^1.14.1",
37
- "@tsconfig/node20": "^20.1.6",
38
- "@types/node": "^20.19.24",
39
- "@vitejs/plugin-vue": "^6.0.2",
37
+ "@rushstack/eslint-patch": "^1.8.0",
38
+ "@tsconfig/node20": "^20.1.4",
39
+ "@types/node": "^20.12.5",
40
+ "@vitejs/plugin-vue": "^5.0.4",
40
41
  "@vue/eslint-config-typescript": "^13.0.0",
41
- "@vue/tsconfig": "^0.8.1",
42
- "autoprefixer": "^10.4.21",
43
- "eslint": "^8.57.1",
44
- "eslint-plugin-vue": "^9.33.0",
45
- "flag-icons": "^7.5.0",
42
+ "@vue/tsconfig": "^0.5.1",
43
+ "autoprefixer": "^10.4.19",
44
+ "eslint": "^8.57.0",
45
+ "eslint-plugin-vue": "^9.23.0",
46
+ "flag-icons": "^7.2.3",
46
47
  "flowbite": "^3.1.2",
47
- "i18n-iso-countries": "^7.14.0",
48
- "npm-run-all2": "^6.2.6",
49
- "portfinder": "^1.0.38",
50
- "postcss": "^8.5.6",
51
- "sass": "^1.93.3",
52
- "tailwindcss": "^3.4.18",
53
- "typescript": "~5.9.3",
54
- "vite": "^7.2.4",
48
+ "i18n-iso-countries": "^7.12.0",
49
+ "npm-run-all2": "^6.1.2",
50
+ "portfinder": "^1.0.32",
51
+ "postcss": "^8.4.38",
52
+ "sass": "^1.77.2",
53
+ "tailwindcss": "^3.4.17",
54
+ "typescript": "~5.4.0",
55
+ "vite": "^5.2.13",
55
56
  "vue-i18n-extract": "^2.0.7",
56
- "vue-tsc": "^2.2.12",
57
- "vue3-json-viewer": "^2.4.1"
57
+ "vue-tsc": "^2.0.11",
58
+ "vue3-json-viewer": "^2.2.2"
58
59
  }
59
60
  }
@@ -24,6 +24,12 @@
24
24
  />
25
25
 
26
26
  <div class="flex items-center ms-3 ">
27
+ <Tooltip>
28
+ <IconWifiOff v-if="coreStore.isInternetError" class="blinking-icon w-8 h-8 text-red-500" />
29
+ <template #tooltip>
30
+ {{$t('Internet connection lost')}}
31
+ </template>
32
+ </Tooltip>
27
33
  <span
28
34
  v-if="!coreStore.config?.singleTheme"
29
35
  @click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black dark:text-darkSidebarTextHover dark:hover:text-darkSidebarTextActive" role="menuitem">
@@ -35,12 +41,17 @@
35
41
  ref="dropdownUserButton"
36
42
  type="button" class="flex text-sm bg- rounded-full focus:ring-4 focus:ring-lightSidebarDevider dark:focus:ring-darkSidebarDevider dark:bg-" aria-expanded="false" data-dropdown-toggle="dropdown-user">
37
43
  <span class="sr-only">{{ $t('Open user menu') }}</span>
38
- <svg class="w-8 h-8 text-lightNavbarIcons dark:text-darkNavbarIcons" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
44
+ <img
45
+ v-if="coreStore.userAvatarUrl"
46
+ class="w-8 h-8 rounded-full object-cover"
47
+ :src="coreStore.userAvatarUrl"
48
+ alt="user photo"
49
+ />
50
+ <svg v-else class="w-8 h-8 text-lightNavbarIcons dark:text-darkNavbarIcons" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
39
51
  <path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
40
52
  </svg>
41
53
  </button>
42
54
  </div>
43
-
44
55
  <div class="z-50 hidden my-4 text-base list-none bg-lightUserMenuBackground divide-y divide-lightUserMenuBorder text-lightUserMenuText rounded shadow dark:shadow-black dark:bg-darkUserMenuBackground dark:divide-darkUserMenuBorder text-darkUserMenuText dark:shadow-black" id="dropdown-user">
45
56
  <div class="px-4 py-3" role="none">
46
57
  <p class="text-sm text-gray-900 dark:text-darkNavbarText" role="none" v-if="coreStore.userFullname">
@@ -73,7 +84,7 @@
73
84
  </nav>
74
85
 
75
86
  <Sidebar
76
- v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout && !headerOnlyLayout"
87
+ v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout && !headerOnlyLayout && coreStore.menu.length > 0"
77
88
  :sideBarOpen="sideBarOpen"
78
89
  :forceIconOnly="route.meta?.sidebarAndHeader === 'preferIconOnly'"
79
90
  @hideSidebar="hideSidebar"
@@ -129,6 +140,19 @@
129
140
 
130
141
  <style lang="scss" scoped>
131
142
 
143
+ @keyframes blink {
144
+ 0%, 100% {
145
+ opacity: 1;
146
+ }
147
+ 50% {
148
+ opacity: 0.2;
149
+ }
150
+ }
151
+
152
+ .blinking-icon {
153
+ animation: blink 2s ease-in-out infinite;
154
+ }
155
+
132
156
  .fade-leave-active {
133
157
  @apply transition-opacity duration-500;
134
158
  }
@@ -163,6 +187,7 @@ import './index.scss'
163
187
  import { useCoreStore } from '@/stores/core';
164
188
  import { useUserStore } from '@/stores/user';
165
189
  import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite';
190
+ import { IconWifiOff } from '@iconify-prerendered/vue-humbleicons';
166
191
  import AcceptModal from './components/AcceptModal.vue';
167
192
  import Sidebar from './components/Sidebar.vue';
168
193
  import { useRoute, useRouter } from 'vue-router';
@@ -173,6 +198,7 @@ import {useToastStore} from '@/stores/toast';
173
198
  import { initFrontedAPI } from '@/adminforth';
174
199
  import adminforth from '@/adminforth';
175
200
  import UserMenuSettingsButton from './components/UserMenuSettingsButton.vue';
201
+ import { Tooltip } from '@/afcl'
176
202
 
177
203
  const coreStore = useCoreStore();
178
204
  const toastStore = useToastStore();
@@ -182,8 +208,6 @@ initFrontedAPI()
182
208
 
183
209
  createHead()
184
210
  const sideBarOpen = ref(false);
185
- const defaultLayout = ref(true);
186
- const headerOnlyLayout = ref(false);
187
211
  const route = useRoute();
188
212
  const router = useRouter();
189
213
  const publicConfigLoaded = ref(false);
@@ -197,82 +221,22 @@ const isSidebarIconOnly = ref(localStorage.getItem('afIconOnlySidebar') === 'tru
197
221
 
198
222
  const loggedIn = computed(() => !!coreStore?.adminUser);
199
223
 
224
+ const defaultLayout = computed(() => {
225
+ return route.meta?.sidebarAndHeader !== 'none';
226
+ });
227
+
228
+ const headerOnlyLayout = computed(() => {
229
+ return route.meta?.sidebarAndHeader === 'headerOnly';
230
+ });
231
+
200
232
  const expandedWidth = computed(() => coreStore.config?.iconOnlySidebar?.expandedSidebarWidth || '16.5rem');
201
233
 
202
234
  const theme = ref('light');
203
235
 
204
236
  const userMenuComponents = computed(() => {
205
- console.log('🪲🆕 userMenuComponents recomputed', JSON.parse(JSON.stringify(coreStore?.config?.globalInjections?.userMenu)));
206
237
  return coreStore?.config?.globalInjections?.userMenu || [];
207
238
  })
208
239
 
209
- watch(
210
- () => coreStore.config?.globalInjections?.userMenu,
211
- (newVal, oldVal) => {
212
- // Only log when it becomes undefined (you can relax this if needed)
213
- if (newVal === undefined) {
214
- const err = new Error('🔍 userMenu changed to undefined');
215
- console.groupCollapsed(
216
- '%c[TRACE] userMenu changed to undefined',
217
- 'color: red; font-weight: bold;'
218
- );
219
- console.log('old value:', oldVal);
220
- console.log('new value:', newVal);
221
- console.log('coreStore.config.globalInjections:', coreStore.config?.globalInjections);
222
- console.log('Stack trace:');
223
- console.log(err.stack);
224
- console.groupEnd();
225
- } else {
226
- // Optional: log ALL changes for debugging
227
- console.groupCollapsed(
228
- '%c[DEBUG] userMenu changed',
229
- 'color: orange; font-weight: bold;'
230
- );
231
- console.log('old value:', oldVal);
232
- console.log('new value:', newVal);
233
- console.log('coreStore.config.globalInjections:', coreStore.config?.globalInjections);
234
- console.groupEnd();
235
- }
236
- },
237
- {
238
- deep: false,
239
- immediate: false,
240
- }
241
- );
242
-
243
- watch(() => coreStore.config?.globalInjections, (v) => {
244
- console.log("🔧 globalInjections replaced:", v);
245
- }, { deep: false });
246
-
247
- watch(
248
- () => coreStore.config?.globalInjections?.userMenu,
249
- (newVal, oldVal) => {
250
- if (newVal === undefined) {
251
- const err = new Error('🔍 userMenu changed to undefined');
252
- console.groupCollapsed(
253
- '%c[TRACE] userMenu changed to undefined',
254
- 'color: red; font-weight: bold;'
255
- );
256
- console.log('old value:', oldVal);
257
- console.log('new value:', newVal);
258
- console.log('coreStore.config.globalInjections:', coreStore.config?.globalInjections);
259
- console.log('Stack trace:');
260
- console.log(err.stack);
261
- console.groupEnd();
262
- } else {
263
- console.groupCollapsed(
264
- '%c[DEBUG] userMenu changed',
265
- 'color: orange; font-weight: bold;'
266
- );
267
- console.log('old value:', oldVal);
268
- console.log('new value:', newVal);
269
- console.log('coreStore.config.globalInjections:', coreStore.config?.globalInjections);
270
- console.groupEnd();
271
- }
272
- },
273
- { deep: false, immediate: false }
274
- );
275
-
276
240
  function hideSidebar(): void {
277
241
  sideBarOpen.value = false;
278
242
  }
@@ -308,19 +272,6 @@ async function loadMenu() {
308
272
  loginRedirectCheckIsReady.value = true;
309
273
  }
310
274
 
311
- function handleCustomLayout() {
312
- if (route.meta?.sidebarAndHeader === 'none') {
313
- defaultLayout.value = false;
314
- } else if (route.meta?.sidebarAndHeader === 'preferIconOnly') {
315
- defaultLayout.value = true;
316
- isSidebarIconOnly.value = true;
317
- } else if (route.meta?.sidebarAndHeader === 'headerOnly') {
318
- headerOnlyLayout.value = true;
319
- } else {
320
- defaultLayout.value = true;
321
- }
322
- }
323
-
324
275
  function humanizeSnake(str: string): string {
325
276
  if (!str) {
326
277
  return '';
@@ -345,10 +296,11 @@ watch(title, (title) => {
345
296
  document.title = title;
346
297
  })
347
298
 
348
- watch([route, () => coreStore.resourceById, () => coreStore.config], async () => {
349
- handleCustomLayout()
350
- await new Promise((resolve) => setTimeout(resolve, 0));
351
-
299
+ watch(route, () => {
300
+ // Handle preferIconOnly layout
301
+ if (route.meta?.sidebarAndHeader === 'preferIconOnly') {
302
+ isSidebarIconOnly.value = true;
303
+ }
352
304
  });
353
305
 
354
306
 
@@ -376,11 +328,13 @@ onMounted(async () => {
376
328
  loadPublicConfig(); // and this
377
329
  // before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
378
330
  await initRouter();
379
- handleCustomLayout();
380
331
 
381
332
  adminforth.menu.refreshMenuBadges = async () => {
382
333
  await coreStore.fetchMenuBadges();
383
334
  }
335
+
336
+ window.addEventListener('online', () => coreStore.isInternetError = false);
337
+ window.addEventListener('offline', () => coreStore.isInternetError = true);
384
338
  })
385
339
 
386
340
  onBeforeMount(()=>{
@@ -22,7 +22,7 @@
22
22
  v-if="headerCloseButton"
23
23
  type="button"
24
24
  class="text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
25
- @click="modal?.hide()"
25
+ @click="tryToHideModal"
26
26
  >
27
27
  <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
28
28
  <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"/>
@@ -51,13 +51,41 @@
51
51
  </div>
52
52
  </div>
53
53
  </div>
54
+ <div>
55
+ <!-- Confirmation Modal -->
56
+ <div
57
+ v-if="showConfirmationOnClose"
58
+ class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-60"
59
+ >
60
+ <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
61
+ <h2 class="text-lg font-semibold mb-4 text-lightDialogHeaderText dark:text-darkDialogHeaderText">Confirm Close</h2>
62
+ <p class="mb-6 text-lightDialogBodyText dark:text-darkDialogBodyText">{{ props.closeConfirmationText }}</p>
63
+ <div class="flex justify-end">
64
+ <Button
65
+ class="me-3"
66
+ @click="showConfirmationOnClose = false"
67
+ >
68
+ Cancel
69
+ </Button>
70
+ <Button
71
+ @click="
72
+ showConfirmationOnClose = false;
73
+ modal?.hide();
74
+ "
75
+ >
76
+ Confirm
77
+ </Button>
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </div>
54
82
  </div>
55
83
  </Teleport>
56
84
  </template>
57
85
 
58
86
  <script setup lang="ts">
59
87
  import Button from "./Button.vue";
60
- import { ref, onMounted, nextTick, onUnmounted, type Ref } from 'vue';
88
+ import { ref, onMounted, nextTick, onUnmounted, computed, type Ref } from 'vue';
61
89
  import { Modal } from 'flowbite';
62
90
 
63
91
  const modalEl = ref(null);
@@ -77,20 +105,42 @@ interface DialogProps {
77
105
  beforeCloseFunction?: (() => void | Promise<void>) | null
78
106
  beforeOpenFunction?: (() => void | Promise<void>) | null
79
107
  closable?: boolean
108
+ askForCloseConfirmation?: boolean
109
+ closeConfirmationText?: string
80
110
  }
81
111
 
82
112
  const props = withDefaults(defineProps<DialogProps>(), {
83
113
  header: '',
84
114
  headerCloseButton: true,
85
- buttons: () => [
86
- { label: 'Close', onclick: (dialog: any) => dialog.hide(), type: '' },
87
- ],
115
+ buttons: () => [],
88
116
  clickToCloseOutside: true,
89
117
  beforeCloseFunction: null,
90
118
  beforeOpenFunction: null,
91
119
  closable: true,
120
+ askForCloseConfirmation: false,
121
+ closeConfirmationText: 'Are you sure you want to close this dialog?',
92
122
  })
93
123
 
124
+ const buttons = computed<DialogButton[]>(() => {
125
+ if (props.buttons && props.buttons.length > 0) {
126
+ return props.buttons;
127
+ }
128
+ return [
129
+ {
130
+ label: 'Close',
131
+ onclick: (dialog: any) => {
132
+ if (!props.askForCloseConfirmation) {
133
+ dialog.hide();
134
+ } else {
135
+ showConfirmationOnClose.value = true;
136
+ }
137
+ },
138
+ options: {}
139
+ }
140
+ ];
141
+ });
142
+
143
+ const showConfirmationOnClose = ref(false);
94
144
  onMounted(async () => {
95
145
  //await one tick when all is mounted
96
146
  await nextTick();
@@ -129,6 +179,16 @@ function close() {
129
179
  defineExpose({
130
180
  open: open,
131
181
  close: close,
182
+ tryToHideModal: tryToHideModal,
132
183
  })
133
184
 
185
+ function tryToHideModal() {
186
+ if (!props.askForCloseConfirmation ) {
187
+ modal.value?.hide();
188
+ } else {
189
+ showConfirmationOnClose.value = true;
190
+ }
191
+ }
192
+
193
+
134
194
  </script>
@@ -15,7 +15,7 @@
15
15
  @input="$emit('update:modelValue', type === 'number' ? Number(($event.target as HTMLInputElement)?.value) : ($event.target as HTMLInputElement)?.value)"
16
16
  :value="modelValue"
17
17
  aria-describedby="helper-text-explanation"
18
- class="afcl-input inline-flex bg-lightInputBackground border border-lightInputBorder rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
18
+ class="afcl-input inline-flex bg-lightInputBackground text-lightInputText dark:text-darkInputText border border-lightInputBorder rounded-0 focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary
19
19
  blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-darkInputBackground dark:border-darkInputBorder placeholder-lightInputPlaceholderText dark:placeholder-darkInputPlaceholderText dark:text-darkInputText translate-y-0"
20
20
  :class="{'rounded-l-md': !$slots.prefix && !prefix, 'rounded-r-md': !$slots.suffix && !suffix, 'w-full': fullWidth, 'text-base': isIos, 'text-sm': !isIos }"
21
21
  :disabled="readonly"
@@ -13,6 +13,7 @@
13
13
  class="block w-full pl-3 pr-10 py-2.5 border border-lightDropownButtonsBorder rounded-md leading-5 bg-lightDropdownButtonsBackground
14
14
  placeholder-lightDropdownButtonsPlaceholderText text-lightDropdownButtonsText sm:text-sm transition duration-150 ease-in-out dark:bg-darkDropdownButtonsBackground dark:border-darkDropdownButtonsBorder dark:placeholder-darkDropdownButtonsPlaceholderText
15
15
  dark:text-darkDropdownButtonsText focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
16
+ :class="{'cursor-pointer': searchDisabled}"
16
17
  autocomplete="off" data-custom="no-autofill"
17
18
  :placeholder="
18
19
  selectedItems.length && !multiple ? '' : (showDropdown ? $t('Search') : placeholder || $t('Select...'))
@@ -265,11 +266,11 @@ onMounted(() => {
265
266
 
266
267
  watch(() => props.modelValue, (value) => {
267
268
  updateFromProps();
268
- });
269
+ }, {deep: true});
269
270
 
270
271
  watch(() => props.options, () => {
271
272
  updateFromProps();
272
- });
273
+ }, { deep: true });
273
274
 
274
275
  addClickListener();
275
276
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div @click="onClick">
2
+ <div @click="onClick" class="flex items-center justify-center">
3
3
  <slot />
4
4
  </div>
5
5
  </template>
@@ -83,13 +83,20 @@
83
83
  </td>
84
84
  </tr>
85
85
 
86
- <tr @click="onClick($event,row)"
87
- v-else v-for="(row, rowI) in rows" :key="`row_${row._primaryKeyValue}`"
88
- ref="rowRefs"
89
- class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
90
-
91
- :class="{'border-b': rowI !== rows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
92
- >
86
+ <component
87
+ v-else
88
+ v-for="(row, rowI) in rows"
89
+ :is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
90
+ :key="`row_${row._primaryKeyValue}`"
91
+ :record="row"
92
+ :resource="resource"
93
+ :adminUser="coreStore.adminUser"
94
+ :meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
95
+ @click="onClick($event, row)"
96
+ ref="rowRefs"
97
+ class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
98
+ :class="{'border-b': rowI !== rows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
99
+ >
93
100
  <td class="w-4 p-4 cursor-default sticky-column bg-lightListTable dark:bg-darkListTable" @click="(e)=>e.stopPropagation()">
94
101
  <Checkbox
95
102
  :model-value="checkboxesInternal.includes(row._primaryKeyValue)"
@@ -210,7 +217,7 @@
210
217
  </div>
211
218
 
212
219
  </td>
213
- </tr>
220
+ </component>
214
221
  </tbody>
215
222
  </table>
216
223
  </div>
@@ -328,9 +335,10 @@ import {
328
335
  } from '@iconify-prerendered/vue-flowbite';
329
336
  import router from '@/router';
330
337
  import { Tooltip } from '@/afcl';
331
- import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon } from '@/types/Common';
338
+ import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
332
339
  import adminforth from '@/adminforth';
333
340
  import Checkbox from '@/afcl/Checkbox.vue';
341
+ import CallActionWrapper from '@/components/CallActionWrapper.vue'
334
342
 
335
343
  const coreStore = useCoreStore();
336
344
  const { t } = useI18n();
@@ -345,6 +353,7 @@ const props = defineProps<{
345
353
  noRoundings?: boolean,
346
354
  customActionsInjection?: any[],
347
355
  tableBodyStartInjection?: any[],
356
+ tableRowReplaceInjection?: AdminForthComponentDeclaration,
348
357
  }>();
349
358
 
350
359
  // emits, update page
@@ -93,14 +93,21 @@
93
93
  </tr>
94
94
 
95
95
  <!-- Visible rows -->
96
- <tr @click="onClick($event,row)"
97
- v-for="(row, rowI) in visibleRows"
98
- :key="`row_${row._primaryKeyValue}`"
99
- ref="rowRefs"
100
- class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
101
- :class="{'border-b': rowI !== visibleRows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
102
- @mounted="(el: any) => updateRowHeight(`row_${row._primaryKeyValue}`, el.offsetHeight)"
103
- >
96
+ <component
97
+ v-else
98
+ v-for="(row, rowI) in visibleRows"
99
+ :is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
100
+ :key="`row_${row._primaryKeyValue}`"
101
+ :record="row"
102
+ :resource="resource"
103
+ :adminUser="coreStore.adminUser"
104
+ :meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
105
+ @click="onClick($event, row)"
106
+ ref="rowRefs"
107
+ class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
108
+ :class="{'border-b': rowI !== visibleRows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
109
+ @mounted="(el: any) => updateRowHeight(`row_${row._primaryKeyValue}`, el.offsetHeight)"
110
+ >
104
111
  <td class="w-4 p-4 cursor-default sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading" @click="(e)=>e.stopPropagation()">
105
112
  <Checkbox
106
113
  :model-value="checkboxesInternal.includes(row._primaryKeyValue)"
@@ -224,7 +231,7 @@
224
231
  </template>
225
232
  </div>
226
233
  </td>
227
- </tr>
234
+ </component>
228
235
 
229
236
  <!-- Bottom spacer -->
230
237
  <tr v-if="totalHeight > 0">
@@ -350,9 +357,10 @@ import {
350
357
  } from '@iconify-prerendered/vue-flowbite';
351
358
  import router from '@/router';
352
359
  import { Tooltip } from '@/afcl';
353
- import type { AdminForthResourceCommon, AdminForthResourceColumnCommon } from '@/types/Common';
360
+ import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
354
361
  import adminforth from '@/adminforth';
355
362
  import Checkbox from '@/afcl/Checkbox.vue';
363
+ import CallActionWrapper from '@/components/CallActionWrapper.vue'
356
364
 
357
365
  const coreStore = useCoreStore();
358
366
  const { t } = useI18n();
@@ -370,6 +378,7 @@ const props = defineProps<{
370
378
  containerHeight?: number,
371
379
  itemHeight?: number,
372
380
  bufferSize?: number,
381
+ tableRowReplaceInjection?: AdminForthComponentDeclaration
373
382
  }>();
374
383
 
375
384
  // emits, update page
@@ -14,9 +14,21 @@
14
14
  aria-label="Sidebar"
15
15
  >
16
16
  <div class="h-full px-3 pb-20 md:pb-4 bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder dark:border-darkSidebarBorder pt-4" :class="{'sidebar-scroll':!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)}">
17
- <div class="af-logo-title-wrapper flex relative transition-all duration-300 ease-in-out h-8 items-center" :class="{'mb-4': isSidebarIconOnly && !isSidebarHovering, 'mx-4 mb-4': !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)}">
18
- <img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-logo h-8 me-3" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))) }" />
19
- <img :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8 me-3" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
17
+ <div
18
+ class="af-logo-title-wrapper flex relative transition-all duration-300 ease-in-out h-8 items-center"
19
+ :class="{
20
+ 'mb-4': isSidebarIconOnly && !isSidebarHovering, 'mx-4 mb-4': !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering),
21
+ 'justify-center': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)))
22
+ }"
23
+ >
24
+ <img
25
+ :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')"
26
+ :alt="`${ coreStore.config?.brandName } Logo`"
27
+ class="af-logo h-8 me-3"
28
+ :class="{
29
+ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))) }"
30
+ />
31
+ <img :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
20
32
  <span
21
33
  v-if="coreStore.config?.showBrandNameInSidebar && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))"
22
34
  class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"