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
@@ -11,8 +11,8 @@
11
11
 
12
12
  <component
13
13
  v-if="!coreStore.isResourceFetching && !initInProcess"
14
- v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs || []"
15
- :is="getCustomComponent(c)"
14
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeBreadcrumbs as AdminForthComponentDeclaration[] || []"
15
+ :is="getCustomComponent(formatComponent(c))"
16
16
  :meta="(c as AdminForthComponentDeclarationFull).meta"
17
17
  :resource="coreStore.resource"
18
18
  :adminUser="coreStore.adminUser"
@@ -21,8 +21,8 @@
21
21
  <BreadcrumbsWithButtons>
22
22
  <component
23
23
  v-if="!coreStore.isResourceFetching && !initInProcess"
24
- v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeActionButtons || []"
25
- :is="getCustomComponent(c)"
24
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.beforeActionButtons as AdminForthComponentDeclaration[] || []"
25
+ :is="getCustomComponent(formatComponent(c))"
26
26
  :meta="(c as AdminForthComponentDeclarationFull).meta"
27
27
  :resource="coreStore.resource"
28
28
  :adminUser="coreStore.adminUser"
@@ -33,7 +33,12 @@
33
33
  @click="()=>{checkboxes = []}"
34
34
  v-if="checkboxes.length"
35
35
  data-tooltip-target="tooltip-remove-all"
36
- class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover rounded-default"
36
+ class="flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-lightListViewButtonText af-button-shadow
37
+ focus:outline-none bg-lightListViewButtonBackground rounded border border-lightListViewButtonBorder h-[34px]
38
+ hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4
39
+ focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing
40
+ dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder
41
+ dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover rounded-default"
37
42
  >
38
43
  <Tooltip>
39
44
  <IconBanOutline class="w-5 h-5 "/>
@@ -43,7 +48,7 @@
43
48
  </Tooltip>
44
49
  </button>
45
50
 
46
- <div
51
+ <div
47
52
  v-if="checkboxes.length"
48
53
  v-for="(action,i) in coreStore.resource?.options?.bulkActions"
49
54
  >
@@ -51,7 +56,13 @@
51
56
  v-if="!action.showInThreeDotsDropdown"
52
57
  :key="action.id"
53
58
  @click="startBulkActionInner(action.id!)"
54
- class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded-default border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover"
59
+ class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText
60
+ focus:outline-none bg-lightListViewButtonBackground rounded-default border h-[34px]
61
+ border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover
62
+ hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 af-button-shadow
63
+ focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing
64
+ dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder
65
+ dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover"
55
66
  :class="action.buttonCustomCssClass || ''"
56
67
  >
57
68
  <component
@@ -72,23 +83,64 @@
72
83
  <span class="sr-only">Loading...</span>
73
84
  </div>
74
85
  {{ `${action.label} (${checkboxes.length})` }}
75
- <div v-if="action.badge" class="text-white bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800
86
+ <div v-if="action.badge" class="text-white bg-gradient-to-r from-purple-500
87
+ via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none
88
+ focus:ring-purple-300 dark:focus:ring-purple-800
76
89
  font-medium rounded-sm text-xs px-1 ml-1 text-center ">
77
90
  {{ action.badge }}
78
91
  </div>
79
92
  </button>
80
93
  </div>
94
+ <div
95
+ v-if="checkboxes.length"
96
+ v-for="(action,i) in coreStore.resource?.options?.actions?.filter(a => a.showIn?.bulkButton)"
97
+ >
98
+ <button
99
+ :key="action.id"
100
+ @click="startCustomBulkActionInner(action.id!)"
101
+ class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded-default border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover"
102
+ >
103
+ <component
104
+ v-if="action.icon && !customActionLoadingStates[action.id!]"
105
+ :is="getIcon(action.icon)"
106
+ class="w-5 h-5 transition duration-75 group-hover:text-gray-900 dark:group-hover:text-white"></component>
107
+ <div v-if="customActionLoadingStates[action.id!]">
108
+ <svg
109
+ aria-hidden="true"
110
+ class="w-5 h-5 animate-spin text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300"
111
+ viewBox="0 0 100 101"
112
+ fill="none"
113
+ xmlns="http://www.w3.org/2000/svg"
114
+ >
115
+ <path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/>
116
+ <path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
117
+ </svg>
118
+ <span class="sr-only">Loading...</span>
119
+ </div>
120
+ {{ `${action.name} (${checkboxes.length})` }}
121
+ </button>
122
+ </div>
81
123
 
82
124
  <RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
83
125
  :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
84
- class="af-create-button flex items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover rounded-default gap-1"
126
+ class="af-create-button flex items-center py-1 h-[34px] px-3 text-sm af-button-shadow
127
+ font-medium text-lightPrimaryContrast transition-all focus:outline-none
128
+ bg-lightPrimary hover:bg-lightPrimary/80 dark:bg-darkPrimary dark:hover:bg-darkPrimary/80
129
+ rounded border border-lightPrimary/90 focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing
130
+ dark:focus:ring-darkListViewButtonFocusRing dark:text-darkPrimaryContrast dark:border-darkPrimary/80
131
+ dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover rounded-default gap-1"
85
132
  >
86
133
  <IconPlusOutline class="w-4 h-4"/>
87
134
  {{ $t('Create') }}
88
135
  </RouterLink>
89
136
 
90
137
  <button
91
- class="af-filter-button flex gap-1 items-center py-1 px-3 me-2 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover rounded-default"
138
+ class="af-filter-button flex gap-1 items-center py-1 h-[34px] px-3 me-2 af-button-shadow text-sm font-medium
139
+ text-lightListViewButtonText transition-all focus:outline-none bg-lightListViewButtonBackground rounded border
140
+ border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover
141
+ focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing
142
+ dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder
143
+ dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover rounded-default"
92
144
  @click="()=>{filtersShow = !filtersShow}"
93
145
  v-if="coreStore.resource?.options?.allowedActions?.filter"
94
146
  >
@@ -114,8 +166,8 @@
114
166
 
115
167
  <component
116
168
  v-if="!coreStore.isResourceFetching && !initInProcess"
117
- v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs || []"
118
- :is="getCustomComponent(c)"
169
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.afterBreadcrumbs as AdminForthComponentDeclaration[] || []"
170
+ :is="getCustomComponent(formatComponent(c))"
119
171
  :meta="(c as AdminForthComponentDeclarationFull).meta"
120
172
  :resource="coreStore.resource"
121
173
  :adminUser="coreStore.adminUser"
@@ -161,8 +213,8 @@
161
213
  />
162
214
 
163
215
  <component
164
- v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.bottom || []"
165
- :is="getCustomComponent(c)"
216
+ v-for="c in coreStore?.resourceOptions?.pageInjections?.list?.bottom as AdminForthComponentDeclaration[] || []"
217
+ :is="getCustomComponent(formatComponent(c))"
166
218
  :meta="(c as AdminForthComponentDeclarationFull).meta"
167
219
  :resource="coreStore.resource"
168
220
  :adminUser="coreStore.adminUser"
@@ -176,13 +228,13 @@ import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
176
228
  import ResourceListTable from '@/components/ResourceListTable.vue';
177
229
  import { useCoreStore } from '@/stores/core';
178
230
  import { useFiltersStore } from '@/stores/filters';
179
- import { callAdminForthApi, currentQuery, getIcon, setQuery } from '@/utils';
231
+ import { callAdminForthApi, currentQuery, getIcon, setQuery, formatComponent, executeCustomBulkAction } from '@/utils';
180
232
  import { computed, onMounted, onUnmounted, ref, watch, type Ref } from 'vue';
181
233
  import { useRoute } from 'vue-router';
182
234
  import { getCustomComponent, initThreeDotsDropdown, getList, startBulkAction } from '@/utils';
183
235
  import ThreeDotsMenu from '@/components/ThreeDotsMenu.vue';
184
236
  import { Tooltip } from '@/afcl'
185
- import type { AdminForthComponentDeclarationFull } from '@/types/Common';
237
+ import type { AdminForthComponentDeclaration, AdminForthComponentDeclarationFull } from '@/types/Common';
186
238
 
187
239
 
188
240
  import {
@@ -195,7 +247,7 @@ import Filters from '@/components/Filters.vue';
195
247
  import { useAdminforth } from '@/adminforth';
196
248
 
197
249
  const filtersShow = ref(false);
198
- const { list } = useAdminforth();
250
+ const { list, alert } = useAdminforth();
199
251
  const coreStore = useCoreStore();
200
252
  const filtersStore = useFiltersStore();
201
253
 
@@ -214,6 +266,7 @@ const rows: Ref<any[]|null> = ref(null);
214
266
  const totalRows = ref(0);
215
267
  const checkboxes = ref([]);
216
268
  const bulkActionLoadingStates = ref<{[key: string]: boolean}>({});
269
+ const customActionLoadingStates = ref<{[key: string]: boolean}>({});
217
270
 
218
271
  const DEFAULT_PAGE_SIZE = 10;
219
272
 
@@ -283,6 +336,38 @@ async function startBulkActionInner(actionId: string) {
283
336
  await startBulkAction(actionId, coreStore.resource!, checkboxes, bulkActionLoadingStates, getListInner);
284
337
  }
285
338
 
339
+ async function startCustomBulkActionInner(actionId: string | number) {
340
+ const action = coreStore.resource?.options?.actions?.find(a => a.id === actionId);
341
+
342
+ await executeCustomBulkAction({
343
+ actionId,
344
+ resourceId: route.params.resourceId as string,
345
+ recordIds: checkboxes.value,
346
+ confirmMessage: action?.bulkConfirmationMessage,
347
+ setLoadingState: (loading: boolean) => {
348
+ customActionLoadingStates.value[actionId] = loading;
349
+ },
350
+ onSuccess: async (results: any[]) => {
351
+ checkboxes.value = [];
352
+ await getListInner();
353
+
354
+ const successResults = results.filter(r => r?.successMessage);
355
+ if (successResults.length > 0) {
356
+ alert({
357
+ message: action?.bulkSuccessMessage || `${successResults.length} out of ${results.length} items processed successfully`,
358
+ variant: 'success'
359
+ });
360
+ }
361
+ },
362
+ onError: (error: string) => {
363
+ alert({
364
+ message: error,
365
+ variant: 'danger'
366
+ });
367
+ }
368
+ });
369
+ }
370
+
286
371
  async function getListInner() {
287
372
  rows.value = null; // to show loading state
288
373
  const result = await getList(coreStore.resource!, isPageLoaded.value, page.value, pageSize.value, sort.value, checkboxes, filtersStore.filters);
@@ -445,4 +530,23 @@ watch([sort], async () => {
445
530
  setQuery({ sort: SortQuerySerializer.serialize(sort.value) });
446
531
  });
447
532
 
448
- </script>
533
+ </script>
534
+
535
+ <style lang="scss">
536
+
537
+
538
+ .af-button-shadow {
539
+ position: relative;
540
+ &::after {
541
+ content: '';
542
+ position: absolute;
543
+ left: 0;
544
+ right: 0;
545
+ height: 100%;
546
+ box-shadow: -0px 6px 6px rgb(0, 0, 0, 0.1);
547
+ border-radius: inherit;
548
+ }
549
+ }
550
+
551
+
552
+ </style>
@@ -31,12 +31,12 @@
31
31
  <!-- Modal header -->
32
32
  <div class="af-login-modal-header flex items-center justify-between flex-col p-4 md:p-5 border-b rounded-t dark:border-gray-600">
33
33
 
34
- <template v-if="coreStore?.config?.loginPageInjections?.panelHeader.length > 0">
34
+ <template v-if="coreStore?.config?.loginPageInjections?.panelHeader.length && coreStore?.config?.loginPageInjections?.panelHeader.length > 0">
35
35
  <component
36
36
  v-for="(c, index) in coreStore?.config?.loginPageInjections?.panelHeader || []"
37
37
  :key="index"
38
- :is="getCustomComponent(c)"
39
- :meta="c.meta"
38
+ :is="getCustomComponent(formatComponent(c))"
39
+ :meta="formatComponent(c).meta"
40
40
  />
41
41
  </template>
42
42
  <h3 v-else class="text-xl font-semibold text-lightLoginViewText dark:text-darkLoginViewTextColor">
@@ -55,7 +55,7 @@
55
55
  name="username"
56
56
  id="username"
57
57
  ref="usernameInput"
58
- @keydown.enter="passwordInput.focus()"
58
+ @keydown.enter="passwordInput?.focus()"
59
59
  class="w-full"
60
60
  placeholder="name@company.com" required />
61
61
  </div>
@@ -76,7 +76,7 @@
76
76
  </Input>
77
77
  </div>
78
78
 
79
- <div v-if="coreStore.config.rememberMeDuration"
79
+ <div v-if="coreStore?.config?.rememberMeDuration"
80
80
  class="flex items-start mb-5"
81
81
  :title="$t(`Stay logged in for {days}`, {days: coreStore.config.rememberMeDuration})"
82
82
  >
@@ -88,8 +88,8 @@
88
88
 
89
89
  <component
90
90
  v-for="c in coreStore?.config?.loginPageInjections?.underInputs || []"
91
- :is="getCustomComponent(c)"
92
- :meta="c.meta"
91
+ :is="getCustomComponent(formatComponent(c))"
92
+ :meta="formatComponent(c).meta"
93
93
  @update:disableLoginButton="setDisableLoginButton($event)"
94
94
  />
95
95
 
@@ -107,8 +107,8 @@
107
107
  </Button>
108
108
  <component
109
109
  v-for="c in coreStore?.config?.loginPageInjections?.underLoginButton || []"
110
- :is="getCustomComponent(c)"
111
- :meta="c.meta"
110
+ :is="getCustomComponent(formatComponent(c))"
111
+ :meta="formatComponent(c).meta"
112
112
  @update:disableLoginButton="setDisableLoginButton($event)"
113
113
  />
114
114
  </form>
@@ -124,7 +124,7 @@
124
124
 
125
125
  <script setup lang="ts">
126
126
 
127
- import { getCustomComponent } from '@/utils';
127
+ import { getCustomComponent, formatComponent } from '@/utils';
128
128
  import { onBeforeMount, onMounted, ref, computed } from 'vue';
129
129
  import { useCoreStore } from '@/stores/core';
130
130
  import { useUserStore } from '@/stores/user';
@@ -137,8 +137,8 @@ import ErrorMessage from '@/components/ErrorMessage.vue';
137
137
 
138
138
  const { t } = useI18n();
139
139
 
140
- const passwordInput = ref(null);
141
- const usernameInput = ref(null);
140
+ const passwordInput = ref<InstanceType<typeof Input> | null>(null);
141
+ const usernameInput = ref<InstanceType<typeof Input> | null>(null);
142
142
  const rememberMeValue= ref(false);
143
143
  const username = ref('');
144
144
  const password = ref('');
@@ -179,7 +179,7 @@ onMounted(async () => {
179
179
  username.value = demoUsername;
180
180
  password.value = demoPassword;
181
181
  }
182
- usernameInput.value.focus();
182
+ usernameInput.value?.focus();
183
183
  });
184
184
 
185
185
 
@@ -3,8 +3,8 @@
3
3
  <component
4
4
  v-if="!loading"
5
5
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.beforeBreadcrumbs || []"
6
- :is="getCustomComponent(c)"
7
- :meta="(c as AdminForthComponentDeclarationFull).meta"
6
+ :is="getCustomComponent(formatComponent(c as AdminForthComponentDeclarationFull))"
7
+ :meta="formatComponent(c as AdminForthComponentDeclarationFull).meta"
8
8
  :record="coreStore.record"
9
9
  :resource="coreStore.resource"
10
10
  :adminUser="coreStore.adminUser"
@@ -14,15 +14,15 @@
14
14
 
15
15
  <template v-for="action in coreStore.resource.options.actions.filter(a => a.showIn?.showButton)" :key="action.id">
16
16
  <component
17
- :is="action?.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
18
- :meta="action.customComponent?.meta"
19
- @callAction="(payload?) => startCustomAction(action.id, payload)"
17
+ :is="action?.customComponent ? getCustomComponent(formatComponent(action.customComponent)) : CallActionWrapper"
18
+ :meta="action.customComponent ? formatComponent(action.customComponent).meta : undefined"
19
+ @callAction="(payload?: any) => startCustomAction(action.id, payload)"
20
20
  :disabled="actionLoadingStates[action.id]"
21
21
  >
22
22
  <button
23
23
  :key="action.id"
24
24
  :disabled="actionLoadingStates[action.id!]"
25
- class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
25
+ class="flex items-center af-button-shadow h-[34px] py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-default border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700"
26
26
  >
27
27
  <component
28
28
  v-if="action.icon"
@@ -36,21 +36,21 @@
36
36
  </template>
37
37
  <RouterLink v-if="coreStore.resource?.options?.allowedActions?.create"
38
38
  :to="{ name: 'resource-create', params: { resourceId: $route.params.resourceId } }"
39
- class="af-add-new-button flex items-center py-1 px-3 text-sm font-medium text-lightShowViewButtonText focus:outline-none bg-lightShowViewButtonBackground rounded border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-lightShowViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-darkShowViewButtonText dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover rounded-default gap-1"
39
+ class="af-add-new-button af-button-shadow h-[34px] flex items-center py-1 px-3 text-sm font-medium text-lightShowViewButtonText focus:outline-none bg-lightShowViewButtonBackground rounded border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-lightShowViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-darkShowViewButtonText dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover rounded-default gap-1"
40
40
  >
41
41
  <IconPlusOutline class="w-4 h-4"/>
42
42
  {{ $t('Add new') }}
43
43
  </RouterLink>
44
44
 
45
45
  <RouterLink v-if="coreStore?.resourceOptions?.allowedActions?.edit" :to="{ name: 'resource-edit', params: { resourceId: $route.params.resourceId, primaryKey: $route.params.primaryKey } }"
46
- class="flex items-center af-edit-button py-1 px-3 text-sm font-medium text-lightShowViewButtonText focus:outline-none bg-lightShowViewButtonBackground rounded-default border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-lightShowViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-darkShowViewButtonText dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover gap-1"
46
+ class="flex items-center h-[34px] af-button-shadow af-edit-button py-1 px-3 text-sm font-medium text-lightShowViewButtonText focus:outline-none bg-lightShowViewButtonBackground rounded-default border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-lightShowViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-darkShowViewButtonText dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover gap-1"
47
47
  >
48
48
  <IconPenSolid class="w-4 h-4" />
49
49
  {{ $t('Edit') }}
50
50
  </RouterLink>
51
51
 
52
52
  <button v-if="coreStore?.resourceOptions?.allowedActions?.delete" @click="deleteRecord"
53
- class="flex items-center af-delete-button py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-lightShowViewButtonBackground border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-red-500 dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover gap-1"
53
+ class="flex items-center h-[34px] af-button-shadow af-delete-button py-1 px-3 text-sm font-medium rounded-default text-red-600 focus:outline-none bg-lightShowViewButtonBackground border border-lightShowViewButtonBorder hover:bg-lightShowViewButtonBackgroundHover hover:text-red-700 focus:z-10 focus:ring-4 focus:ring-lightShowViewButtonFocusRing dark:focus:ring-darkShowViewButtonFocusRing dark:bg-darkShowViewButtonBackground dark:text-red-500 dark:border-darkShowViewButtonBorder dark:hover:text-darkShowViewButtonTextHover dark:hover:bg-darkShowViewButtonBackgroundHover gap-1"
54
54
  >
55
55
  <IconTrashBinSolid class="w-4 h-4" />
56
56
  {{ $t('Delete') }}
@@ -65,8 +65,8 @@
65
65
  <component
66
66
  v-if="!loading"
67
67
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.afterBreadcrumbs || []"
68
- :is="getCustomComponent(c)"
69
- :meta="(c as AdminForthComponentDeclarationFull).meta"
68
+ :is="getCustomComponent(formatComponent(c as AdminForthComponentDeclarationFull))"
69
+ :meta="formatComponent(c as AdminForthComponentDeclarationFull).meta"
70
70
  :record="coreStore.record"
71
71
  :resource="coreStore.resource"
72
72
  :adminUser="coreStore.adminUser"
@@ -95,7 +95,7 @@
95
95
  <template v-else>
96
96
  <template v-for="group in groups" :key="group.groupName">
97
97
  <ShowTable
98
- :columns="group.columns"
98
+ :columns="group.columns as any"
99
99
  :groupName="group.groupName"
100
100
  :noTitle="group.noTitle"
101
101
  :resource="coreStore.resource"
@@ -120,8 +120,8 @@
120
120
  <component
121
121
  v-if="!loading"
122
122
  v-for="c in coreStore?.resourceOptions?.pageInjections?.show?.bottom || []"
123
- :is="getCustomComponent(c)"
124
- :meta="(c as AdminForthComponentDeclarationFull).meta"
123
+ :is="getCustomComponent(formatComponent(c as AdminForthComponentDeclarationFull))"
124
+ :meta="formatComponent(c as AdminForthComponentDeclarationFull).meta"
125
125
  :record="coreStore.record"
126
126
  :resource="coreStore.resource"
127
127
  :adminUser="coreStore.adminUser"
@@ -137,7 +137,7 @@
137
137
  import BreadcrumbsWithButtons from '@/components/BreadcrumbsWithButtons.vue';
138
138
 
139
139
  import { useCoreStore } from '@/stores/core';
140
- import { getCustomComponent, checkAcessByAllowedActions, initThreeDotsDropdown } from '@/utils';
140
+ import { getCustomComponent, checkAcessByAllowedActions, initThreeDotsDropdown, formatComponent, executeCustomAction } from '@/utils';
141
141
  import { IconPenSolid, IconTrashBinSolid, IconPlusOutline } from '@iconify-prerendered/vue-flowbite';
142
142
  import { onMounted, ref, computed } from 'vue';
143
143
  import { useRoute,useRouter } from 'vue-router';
@@ -245,55 +245,48 @@ async function deleteRecord() {
245
245
 
246
246
  }
247
247
 
248
- async function startCustomAction(actionId: string, extra: any) {
249
- actionLoadingStates.value[actionId] = true;
250
-
251
- const data = await callAdminForthApi({
252
- path: '/start_custom_action',
253
- method: 'POST',
254
- body: {
255
- resourceId: route.params.resourceId,
256
- actionId: actionId,
257
- recordId: route.params.primaryKey,
258
- extra: extra,
259
- }
260
- });
261
-
262
- actionLoadingStates.value[actionId] = false;
263
-
264
- if (data?.redirectUrl) {
265
- // Check if the URL should open in a new tab
266
- if (data.redirectUrl.includes('target=_blank')) {
267
- window.open(data.redirectUrl.replace('&target=_blank', '').replace('?target=_blank', ''), '_blank');
268
- } else {
269
- // Navigate within the app
270
- if (data.redirectUrl.startsWith('http')) {
271
- window.location.href = data.redirectUrl;
272
- } else {
273
- router.push(data.redirectUrl);
248
+ async function startCustomAction(actionId: string, extra?: any) {
249
+ await executeCustomAction({
250
+ actionId,
251
+ resourceId: route.params.resourceId as string,
252
+ recordId: route.params.primaryKey as string,
253
+ extra,
254
+ setLoadingState: (loading: boolean) => {
255
+ actionLoadingStates.value[actionId] = loading;
256
+ },
257
+ onSuccess: async (data: any) => {
258
+ if (data?.redirectUrl) {
259
+ // Check if the URL should open in a new tab
260
+ if (data.redirectUrl.includes('target=_blank')) {
261
+ window.open(data.redirectUrl.replace('&target=_blank', '').replace('?target=_blank', ''), '_blank');
262
+ } else {
263
+ // Navigate within the app
264
+ if (data.redirectUrl.startsWith('http')) {
265
+ window.location.href = data.redirectUrl;
266
+ } else {
267
+ router.push(data.redirectUrl);
268
+ }
269
+ }
270
+ return;
274
271
  }
275
- }
276
- return;
277
- }
278
-
279
- if (data?.ok) {
280
- await coreStore.fetchRecord({
281
- resourceId: route.params.resourceId as string,
282
- primaryKey: route.params.primaryKey as string,
283
- source: 'show',
284
- });
285
272
 
286
- if (data.successMessage) {
287
- alert({
288
- message: data.successMessage,
289
- variant: 'success'
273
+ await coreStore.fetchRecord({
274
+ resourceId: route.params.resourceId as string,
275
+ primaryKey: route.params.primaryKey as string,
276
+ source: 'show',
290
277
  });
278
+
279
+ if (data.successMessage) {
280
+ alert({
281
+ message: data.successMessage,
282
+ variant: 'success'
283
+ });
284
+ }
285
+ },
286
+ onError: (error: string) => {
287
+ showErrorTost(error);
291
288
  }
292
- }
293
-
294
- if (data?.error) {
295
- showErrorTost(data.error);
296
- }
289
+ });
297
290
  }
298
291
 
299
292
  show.refresh = () => {
@@ -5,7 +5,7 @@
5
5
  "compilerOptions": {
6
6
  "composite": true,
7
7
  "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
8
-
8
+ "ignoreDeprecations": "6.0",
9
9
  "baseUrl": ".",
10
10
  "paths": {
11
11
  "@/*": ["./src/*"]
@@ -1,4 +1,4 @@
1
- import type { Express, Request } from 'express';
1
+ import type { Express, Request, Response } from 'express';
2
2
  import type { Writable } from 'stream';
3
3
  import { ActionCheckSource, AdminForthFilterOperators, AdminForthSortDirections, AllowedActionsEnum, AdminForthResourcePages, type AdminForthComponentDeclaration, type AdminUser, type AllowedActionsResolved, type AdminForthBulkActionCommon, type AdminForthForeignResourceCommon, type AdminForthResourceColumnCommon, type AdminForthResourceInputCommon, type AdminForthComponentDeclarationFull, type AdminForthConfigMenuItem, type AnnouncementBadgeResponse, type AdminForthResourceColumnInputCommon } from './Common.js';
4
4
  export interface ICodeInjector {
@@ -44,7 +44,7 @@ export interface IHttpServer {
44
44
  [key: string]: string;
45
45
  }, cookies: {
46
46
  [key: string]: string;
47
- }, response: IAdminForthHttpResponse) => void;
47
+ }, response: IAdminForthHttpResponse, requestUrl: string, abortSignal: AbortSignal, _raw_express_req: Request, _raw_express_res: Response) => void;
48
48
  }): void;
49
49
  }
50
50
  export interface IExpressHttpServer extends IHttpServer {
@@ -1191,11 +1191,14 @@ interface AdminForthInputConfigCustomization {
1191
1191
  }
1192
1192
  export interface AdminForthActionInput {
1193
1193
  name: string;
1194
+ bulkConfirmationMessage?: string;
1195
+ bulkSuccessMessage?: string;
1194
1196
  showIn?: {
1195
1197
  list?: boolean;
1196
1198
  listThreeDotsMenu?: boolean;
1197
1199
  showButton?: boolean;
1198
1200
  showThreeDotsMenu?: boolean;
1201
+ bulkButton?: boolean;
1199
1202
  };
1200
1203
  allowed?: (params: {
1201
1204
  adminUser: AdminUser;
@@ -1216,7 +1219,7 @@ export interface AdminForthActionInput {
1216
1219
  message?: string;
1217
1220
  }>;
1218
1221
  icon?: string;
1219
- id?: string;
1222
+ id: string;
1220
1223
  customComponent?: AdminForthComponentDeclaration;
1221
1224
  }
1222
1225
  export interface AdminForthResourceInput extends Omit<NonNullable<AdminForthResourceInputCommon>, 'columns' | 'hooks' | 'options'> {
@@ -1494,7 +1497,7 @@ export interface AdminForthConfigCustomization extends Omit<AdminForthInputConfi
1494
1497
  customPages: Array<AdminForthPageDeclaration>;
1495
1498
  loginPageInjections: {
1496
1499
  underInputs: Array<AdminForthComponentDeclarationFull>;
1497
- underLoginButton?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>;
1500
+ underLoginButton: Array<AdminForthComponentDeclarationFull>;
1498
1501
  panelHeader: Array<AdminForthComponentDeclarationFull>;
1499
1502
  };
1500
1503
  globalInjections: {
@@ -1590,7 +1593,7 @@ export type AllowedActions = {
1590
1593
  /**
1591
1594
  * General options for resource.
1592
1595
  */
1593
- export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
1596
+ export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions' | 'actions'> {
1594
1597
  /**
1595
1598
  * Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
1596
1599
  * using checkboxes.