adminforth 2.17.0-next.10 → 2.17.0-next.12
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.
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +7 -5
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.js +3 -3
- package/dist/modules/restApi.js.map +1 -1
- package/dist/spa/src/components/ListActionsThreeDots.vue +235 -0
- package/dist/spa/src/components/ResourceListTable.vue +23 -11
- package/dist/spa/src/components/ResourceListTableVirtual.vue +27 -13
- package/dist/spa/src/types/Back.ts +3 -0
- package/dist/spa/src/types/Common.ts +1 -0
- package/dist/spa/src/views/ListView.vue +12 -2
- package/dist/types/Back.d.ts +3 -0
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +1 -0
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative inline-block">
|
|
3
|
+
<div
|
|
4
|
+
ref="triggerRef"
|
|
5
|
+
class="border border-gray-300 dark:border-gray-700 dark:border-opacity-0 border-opacity-0 hover:border-opacity-100 dark:hover:border-opacity-100 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
|
|
6
|
+
@click="toggleMenu"
|
|
7
|
+
>
|
|
8
|
+
<IconDotsHorizontalOutline class="w-6 h-6 text-lightPrimary dark:text-darkPrimary" />
|
|
9
|
+
</div>
|
|
10
|
+
<teleport to="body">
|
|
11
|
+
<div
|
|
12
|
+
v-if="showMenu"
|
|
13
|
+
ref="menuRef"
|
|
14
|
+
class="z-50 bg-white dark:bg-gray-900 rounded-md shadow-lg border dark:border-gray-700 py-1"
|
|
15
|
+
:style="menuStyles"
|
|
16
|
+
>
|
|
17
|
+
<template v-if="!resourceOptions?.baseActionsAsQuickIcons || (resourceOptions?.baseActionsAsQuickIcons && !resourceOptions?.baseActionsAsQuickIcons.includes('show'))">
|
|
18
|
+
<RouterLink
|
|
19
|
+
v-if="resourceOptions?.allowedActions?.show"
|
|
20
|
+
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"
|
|
21
|
+
:to="{
|
|
22
|
+
name: 'resource-show',
|
|
23
|
+
params: {
|
|
24
|
+
resourceId: props.resourceId,
|
|
25
|
+
primaryKey: record._primaryKeyValue,
|
|
26
|
+
}
|
|
27
|
+
}"
|
|
28
|
+
|
|
29
|
+
>
|
|
30
|
+
<IconEyeSolid class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"/>
|
|
31
|
+
{{ $t('Show item') }}
|
|
32
|
+
</RouterLink>
|
|
33
|
+
</template>
|
|
34
|
+
<template v-if="!resourceOptions?.baseActionsAsQuickIcons || (resourceOptions?.baseActionsAsQuickIcons && !resourceOptions?.baseActionsAsQuickIcons.includes('edit'))">
|
|
35
|
+
<RouterLink
|
|
36
|
+
v-if="resourceOptions?.allowedActions?.edit"
|
|
37
|
+
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"
|
|
38
|
+
:to="{
|
|
39
|
+
name: 'resource-edit',
|
|
40
|
+
params: {
|
|
41
|
+
resourceId: props.resourceId,
|
|
42
|
+
primaryKey: record._primaryKeyValue,
|
|
43
|
+
}
|
|
44
|
+
}"
|
|
45
|
+
>
|
|
46
|
+
<IconPenSolid class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"/>
|
|
47
|
+
{{ $t('Edit item') }}
|
|
48
|
+
</RouterLink>
|
|
49
|
+
</template>
|
|
50
|
+
<template v-if="!resourceOptions?.baseActionsAsQuickIcons || (resourceOptions?.baseActionsAsQuickIcons && !resourceOptions?.baseActionsAsQuickIcons.includes('delete'))">
|
|
51
|
+
<button
|
|
52
|
+
v-if="resourceOptions?.allowedActions?.delete"
|
|
53
|
+
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"
|
|
54
|
+
@click="deleteRecord(record)"
|
|
55
|
+
>
|
|
56
|
+
<IconTrashBinSolid class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"/>
|
|
57
|
+
{{ $t('Delete item') }}
|
|
58
|
+
</button>
|
|
59
|
+
</template>
|
|
60
|
+
<div v-for="action in (resourceOptions.actions ?? []).filter(a => a.showIn?.listThreeDotsMenu)" :key="action.id" >
|
|
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
|
+
<component
|
|
63
|
+
:is="action.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
|
|
64
|
+
:meta="action.customComponent?.meta"
|
|
65
|
+
:row="record"
|
|
66
|
+
:resource="resource"
|
|
67
|
+
:adminUser="adminUser"
|
|
68
|
+
@callAction="(payload? : Object) => startCustomAction(action.id, record, payload)"
|
|
69
|
+
>
|
|
70
|
+
<component
|
|
71
|
+
v-if="action.icon"
|
|
72
|
+
:is="getIcon(action.icon)"
|
|
73
|
+
class="w-5 h-5 mr-2 text-lightPrimary dark:text-darkPrimary"
|
|
74
|
+
/>
|
|
75
|
+
{{ $t(action.name) }}
|
|
76
|
+
</component>
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
<template v-if="customActionIconsThreeDotsMenuItems">
|
|
80
|
+
<component
|
|
81
|
+
v-for="c in customActionIconsThreeDotsMenuItems"
|
|
82
|
+
:is="getCustomComponent(c)"
|
|
83
|
+
:meta="c.meta"
|
|
84
|
+
:resource="coreStore.resource"
|
|
85
|
+
:adminUser="coreStore.adminUser"
|
|
86
|
+
:record="record"
|
|
87
|
+
:updateRecords="props.updateRecords"
|
|
88
|
+
/>
|
|
89
|
+
</template>
|
|
90
|
+
</div>
|
|
91
|
+
</teleport>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
94
|
+
|
|
95
|
+
<script lang="ts" setup>
|
|
96
|
+
import {
|
|
97
|
+
IconEyeSolid,
|
|
98
|
+
IconPenSolid,
|
|
99
|
+
IconTrashBinSolid,
|
|
100
|
+
IconDotsHorizontalOutline
|
|
101
|
+
} from '@iconify-prerendered/vue-flowbite';
|
|
102
|
+
import { onMounted, onBeforeUnmount, ref, nextTick, watch } from 'vue';
|
|
103
|
+
import { getIcon, getCustomComponent } from '@/utils';
|
|
104
|
+
import { useCoreStore } from '@/stores/core';
|
|
105
|
+
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
106
|
+
|
|
107
|
+
const coreStore = useCoreStore();
|
|
108
|
+
const showMenu = ref(false);
|
|
109
|
+
const triggerRef = ref<HTMLElement | null>(null);
|
|
110
|
+
const menuRef = ref<HTMLElement | null>(null);
|
|
111
|
+
const menuStyles = ref<Record<string, string>>({});
|
|
112
|
+
|
|
113
|
+
const props = defineProps<{
|
|
114
|
+
resourceOptions: any;
|
|
115
|
+
record: any;
|
|
116
|
+
customActionIconsThreeDotsMenuItems: any[];
|
|
117
|
+
resourceId: string;
|
|
118
|
+
deleteRecord: (record: any) => void;
|
|
119
|
+
updateRecords: () => void;
|
|
120
|
+
startCustomAction: (actionId: string, record: any) => void;
|
|
121
|
+
}>();
|
|
122
|
+
|
|
123
|
+
onMounted(() => {
|
|
124
|
+
window.addEventListener('scroll', handleScrollOrResize, true);
|
|
125
|
+
window.addEventListener('resize', handleScrollOrResize);
|
|
126
|
+
document.addEventListener('click', handleOutsideClick, true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
onBeforeUnmount(() => {
|
|
130
|
+
window.removeEventListener('scroll', handleScrollOrResize, true);
|
|
131
|
+
window.removeEventListener('resize', handleScrollOrResize);
|
|
132
|
+
document.removeEventListener('click', handleOutsideClick, true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
watch(showMenu, async (isOpen) => {
|
|
136
|
+
if (isOpen) {
|
|
137
|
+
await nextTick();
|
|
138
|
+
// First pass: after DOM mount
|
|
139
|
+
updateMenuPosition();
|
|
140
|
+
// Second pass: after layout/paint to catch width changes (fonts/icons)
|
|
141
|
+
requestAnimationFrame(() => {
|
|
142
|
+
updateMenuPosition();
|
|
143
|
+
// Final safety: one micro-delay retry if width was still 0
|
|
144
|
+
setTimeout(() => updateMenuPosition(), 0);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
function toggleMenu() {
|
|
150
|
+
if (!showMenu.value) {
|
|
151
|
+
// Provisional position to avoid flashing at left:0 on first open
|
|
152
|
+
const el = triggerRef.value;
|
|
153
|
+
if (el) {
|
|
154
|
+
const rect = el.getBoundingClientRect();
|
|
155
|
+
const gap = 8;
|
|
156
|
+
menuStyles.value = {
|
|
157
|
+
position: 'fixed',
|
|
158
|
+
top: `${Math.round(rect.bottom)}px`,
|
|
159
|
+
left: `${Math.round(rect.left)}px`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
showMenu.value = !showMenu.value;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function updateMenuPosition() {
|
|
167
|
+
const el = triggerRef.value;
|
|
168
|
+
if (!el) return;
|
|
169
|
+
const rect = el.getBoundingClientRect();
|
|
170
|
+
const margin = 8; // gap around the trigger/menu
|
|
171
|
+
const menuEl = menuRef.value;
|
|
172
|
+
// Measure current menu size to align and decide flipping
|
|
173
|
+
let menuWidth = rect.width; // fallback to trigger width
|
|
174
|
+
let menuHeight = 0;
|
|
175
|
+
if (menuEl) {
|
|
176
|
+
const menuRect = menuEl.getBoundingClientRect();
|
|
177
|
+
// Prefer bounding rect; fallback to offset/scroll width if needed
|
|
178
|
+
const measuredW = menuRect.width || menuEl.offsetWidth || menuEl.scrollWidth;
|
|
179
|
+
if (measuredW > 0) menuWidth = measuredW;
|
|
180
|
+
const measuredH = menuRect.height || menuEl.offsetHeight || menuEl.scrollHeight;
|
|
181
|
+
if (measuredH > 0) menuHeight = measuredH;
|
|
182
|
+
}
|
|
183
|
+
// Right-align: right edge of menu == right edge of trigger
|
|
184
|
+
let left = rect.right - menuWidth;
|
|
185
|
+
// Clamp within viewport with small margin so it doesn't render off-screen
|
|
186
|
+
const minLeft = margin;
|
|
187
|
+
const maxLeft = Math.max(minLeft, window.innerWidth - margin - menuWidth);
|
|
188
|
+
left = Math.min(Math.max(left, minLeft), maxLeft);
|
|
189
|
+
|
|
190
|
+
// Determine whether to place above or below based on available space
|
|
191
|
+
const spaceBelow = window.innerHeight - rect.bottom - margin;
|
|
192
|
+
const spaceAbove = rect.top - margin;
|
|
193
|
+
const maxMenuHeight = Math.max(0, window.innerHeight - 2 * margin);
|
|
194
|
+
|
|
195
|
+
let top: number;
|
|
196
|
+
if (menuHeight === 0) {
|
|
197
|
+
// Unknown height yet (first pass). Prefer placing below; a subsequent pass will correct if needed.
|
|
198
|
+
top = rect.bottom + margin;
|
|
199
|
+
} else if (menuHeight <= spaceBelow) {
|
|
200
|
+
// Enough space below
|
|
201
|
+
top = rect.bottom + margin;
|
|
202
|
+
} else if (menuHeight <= spaceAbove) {
|
|
203
|
+
// Not enough below but enough above -> flip
|
|
204
|
+
top = rect.top - margin - menuHeight;
|
|
205
|
+
} else {
|
|
206
|
+
// Not enough space on either side: pick the side with more room and clamp within viewport
|
|
207
|
+
if (spaceBelow >= spaceAbove) {
|
|
208
|
+
top = Math.min(rect.bottom + margin, window.innerHeight - margin - menuHeight);
|
|
209
|
+
} else {
|
|
210
|
+
top = Math.max(margin, rect.top - margin - menuHeight);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
menuStyles.value = {
|
|
215
|
+
position: 'fixed',
|
|
216
|
+
top: `${Math.round(top)}px`,
|
|
217
|
+
left: `${Math.round(left)}px`,
|
|
218
|
+
maxHeight: `${Math.round(maxMenuHeight)}px`,
|
|
219
|
+
overflowY: 'auto',
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function handleScrollOrResize() {
|
|
224
|
+
showMenu.value = false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function handleOutsideClick(e: MouseEvent) {
|
|
228
|
+
const target = e.target as Node | null;
|
|
229
|
+
if (!target) return;
|
|
230
|
+
if (menuRef.value?.contains(target)) return;
|
|
231
|
+
if (triggerRef.value?.contains(target)) return;
|
|
232
|
+
showMenu.value = false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
</script>
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
</td>
|
|
121
121
|
<td class=" items-center px-2 md:px-3 lg:px-6 py-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
|
|
122
122
|
<div class="flex text-lightPrimary dark:text-darkPrimary items-center">
|
|
123
|
-
<Tooltip>
|
|
123
|
+
<Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('show')">
|
|
124
124
|
<RouterLink
|
|
125
125
|
v-if="resource.options?.allowedActions?.show"
|
|
126
126
|
:to="{
|
|
@@ -139,8 +139,7 @@
|
|
|
139
139
|
{{ $t('Show item') }}
|
|
140
140
|
</template>
|
|
141
141
|
</Tooltip>
|
|
142
|
-
|
|
143
|
-
<Tooltip>
|
|
142
|
+
<Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('edit')" >
|
|
144
143
|
<RouterLink
|
|
145
144
|
v-if="resource.options?.allowedActions?.edit"
|
|
146
145
|
:to="{
|
|
@@ -157,8 +156,7 @@
|
|
|
157
156
|
{{ $t('Edit item') }}
|
|
158
157
|
</template>
|
|
159
158
|
</Tooltip>
|
|
160
|
-
|
|
161
|
-
<Tooltip>
|
|
159
|
+
<Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('delete')">
|
|
162
160
|
<button
|
|
163
161
|
v-if="resource.options?.allowedActions?.delete"
|
|
164
162
|
@click="deleteRecord(row)"
|
|
@@ -170,7 +168,6 @@
|
|
|
170
168
|
{{ $t('Delete item') }}
|
|
171
169
|
</template>
|
|
172
170
|
</Tooltip>
|
|
173
|
-
|
|
174
171
|
<template v-if="customActionsInjection">
|
|
175
172
|
<component
|
|
176
173
|
v-for="c in customActionsInjection"
|
|
@@ -185,7 +182,7 @@
|
|
|
185
182
|
|
|
186
183
|
<template v-if="resource.options?.actions">
|
|
187
184
|
<Tooltip
|
|
188
|
-
v-for="action in resource.options.actions.filter(a => a.showIn?.list)"
|
|
185
|
+
v-for="action in resource.options.actions.filter(a => a.showIn?.list || a.showIn?.listQuickIcon)"
|
|
189
186
|
:key="action.id"
|
|
190
187
|
>
|
|
191
188
|
<component
|
|
@@ -213,6 +210,16 @@
|
|
|
213
210
|
</template>
|
|
214
211
|
</Tooltip>
|
|
215
212
|
</template>
|
|
213
|
+
<ListActionsThreeDots
|
|
214
|
+
v-if="showListActionsThreeDots"
|
|
215
|
+
:resourceOptions="resource?.options"
|
|
216
|
+
:record="row"
|
|
217
|
+
:updateRecords="()=>emits('update:records', true)"
|
|
218
|
+
:deleteRecord="deleteRecord"
|
|
219
|
+
:resourceId="resource.resourceId"
|
|
220
|
+
:startCustomAction="startCustomAction"
|
|
221
|
+
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
|
|
222
|
+
/>
|
|
216
223
|
</div>
|
|
217
224
|
|
|
218
225
|
</td>
|
|
@@ -323,20 +330,18 @@ import { useCoreStore } from '@/stores/core';
|
|
|
323
330
|
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
324
331
|
import SkeleteLoader from '@/components/SkeleteLoader.vue';
|
|
325
332
|
import { getIcon } from '@/utils';
|
|
326
|
-
import {
|
|
327
|
-
IconInboxOutline,
|
|
328
|
-
} from '@iconify-prerendered/vue-flowbite';
|
|
329
|
-
|
|
330
333
|
import {
|
|
331
334
|
IconEyeSolid,
|
|
332
335
|
IconPenSolid,
|
|
333
336
|
IconTrashBinSolid,
|
|
337
|
+
IconInboxOutline
|
|
334
338
|
} from '@iconify-prerendered/vue-flowbite';
|
|
335
339
|
import router from '@/router';
|
|
336
340
|
import { Tooltip } from '@/afcl';
|
|
337
341
|
import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
|
|
338
342
|
import adminforth from '@/adminforth';
|
|
339
343
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
344
|
+
import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
|
|
340
345
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
341
346
|
|
|
342
347
|
const coreStore = useCoreStore();
|
|
@@ -352,6 +357,7 @@ const props = defineProps<{
|
|
|
352
357
|
noRoundings?: boolean,
|
|
353
358
|
customActionsInjection?: any[],
|
|
354
359
|
tableBodyStartInjection?: any[],
|
|
360
|
+
customActionIconsThreeDotsMenuItems?: any[]
|
|
355
361
|
tableRowReplaceInjection?: AdminForthComponentDeclaration,
|
|
356
362
|
}>();
|
|
357
363
|
|
|
@@ -369,6 +375,12 @@ const pageInput = ref('1');
|
|
|
369
375
|
const page = ref(1);
|
|
370
376
|
const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
|
|
371
377
|
|
|
378
|
+
const showListActionsThreeDots = computed(() => {
|
|
379
|
+
return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
|
|
380
|
+
|| (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
|
|
381
|
+
|| !props.resource?.options.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
|
|
382
|
+
|| (props.resource?.options.baseActionsAsQuickIcons && props.resource?.options.baseActionsAsQuickIcons.length < 3) // if there all 3 base actions are shown as quick icons - hide three dots icon
|
|
383
|
+
})
|
|
372
384
|
|
|
373
385
|
const from = computed(() => ((page.value || 1) - 1) * props.pageSize + 1);
|
|
374
386
|
const to = computed(() => Math.min((page.value || 1) * props.pageSize, props.totalRows));
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
</td>
|
|
131
131
|
<td class=" items-center px-2 md:px-3 lg:px-6 py-4 cursor-default" @click="(e)=>{e.stopPropagation()}">
|
|
132
132
|
<div class="flex text-lightPrimary dark:text-darkPrimary items-center">
|
|
133
|
-
<Tooltip>
|
|
133
|
+
<Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('show')">
|
|
134
134
|
<RouterLink
|
|
135
135
|
v-if="resource.options?.allowedActions?.show"
|
|
136
136
|
:to="{
|
|
@@ -142,15 +142,14 @@
|
|
|
142
142
|
}"
|
|
143
143
|
|
|
144
144
|
>
|
|
145
|
-
<IconEyeSolid class="w-5 h-5 me-2"/>
|
|
145
|
+
<IconEyeSolid class="af-show-icon w-5 h-5 me-2"/>
|
|
146
146
|
</RouterLink>
|
|
147
147
|
|
|
148
148
|
<template v-slot:tooltip>
|
|
149
149
|
{{ $t('Show item') }}
|
|
150
150
|
</template>
|
|
151
151
|
</Tooltip>
|
|
152
|
-
|
|
153
|
-
<Tooltip>
|
|
152
|
+
<Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('edit')" >
|
|
154
153
|
<RouterLink
|
|
155
154
|
v-if="resource.options?.allowedActions?.edit"
|
|
156
155
|
:to="{
|
|
@@ -161,26 +160,24 @@
|
|
|
161
160
|
}
|
|
162
161
|
}"
|
|
163
162
|
>
|
|
164
|
-
<IconPenSolid class="w-5 h-5 me-2"/>
|
|
163
|
+
<IconPenSolid class="af-edit-icon w-5 h-5 me-2"/>
|
|
165
164
|
</RouterLink>
|
|
166
165
|
<template v-slot:tooltip>
|
|
167
166
|
{{ $t('Edit item') }}
|
|
168
167
|
</template>
|
|
169
168
|
</Tooltip>
|
|
170
|
-
|
|
171
|
-
<Tooltip>
|
|
169
|
+
<Tooltip v-if="resource.options?.baseActionsAsQuickIcons && resource.options?.baseActionsAsQuickIcons.includes('delete')">
|
|
172
170
|
<button
|
|
173
171
|
v-if="resource.options?.allowedActions?.delete"
|
|
174
172
|
@click="deleteRecord(row)"
|
|
175
173
|
>
|
|
176
|
-
<IconTrashBinSolid class="w-5 h-5 me-2"/>
|
|
174
|
+
<IconTrashBinSolid class="af-delete-icon w-5 h-5 me-2"/>
|
|
177
175
|
</button>
|
|
178
176
|
|
|
179
177
|
<template v-slot:tooltip>
|
|
180
178
|
{{ $t('Delete item') }}
|
|
181
179
|
</template>
|
|
182
|
-
</Tooltip>
|
|
183
|
-
|
|
180
|
+
</Tooltip>
|
|
184
181
|
<template v-if="customActionsInjection">
|
|
185
182
|
<component
|
|
186
183
|
v-for="c in customActionsInjection"
|
|
@@ -192,10 +189,9 @@
|
|
|
192
189
|
:updateRecords="()=>emits('update:records', true)"
|
|
193
190
|
/>
|
|
194
191
|
</template>
|
|
195
|
-
|
|
196
|
-
<template v-if="resource.options?.actions">
|
|
192
|
+
<template v-if="resource.options?.actions">
|
|
197
193
|
<Tooltip
|
|
198
|
-
v-for="action in resource.options.actions.filter(a => a.showIn?.list)"
|
|
194
|
+
v-for="action in resource.options.actions.filter(a => a.showIn?.list || a.showIn?.listQuickIcon)"
|
|
199
195
|
:key="action.id"
|
|
200
196
|
>
|
|
201
197
|
<CallActionWrapper
|
|
@@ -228,6 +224,16 @@
|
|
|
228
224
|
</template>
|
|
229
225
|
</Tooltip>
|
|
230
226
|
</template>
|
|
227
|
+
<ListActionsThreeDots
|
|
228
|
+
v-if="showListActionsThreeDots"
|
|
229
|
+
:resourceOptions="resource?.options"
|
|
230
|
+
:record="row"
|
|
231
|
+
:updateRecords="()=>emits('update:records', true)"
|
|
232
|
+
:deleteRecord="deleteRecord"
|
|
233
|
+
:resourceId="resource.resourceId"
|
|
234
|
+
:startCustomAction="startCustomAction"
|
|
235
|
+
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
|
|
236
|
+
/>
|
|
231
237
|
</div>
|
|
232
238
|
</td>
|
|
233
239
|
</component>
|
|
@@ -359,6 +365,7 @@ import { Tooltip } from '@/afcl';
|
|
|
359
365
|
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
|
|
360
366
|
import adminforth from '@/adminforth';
|
|
361
367
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
368
|
+
import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
|
|
362
369
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
363
370
|
|
|
364
371
|
const coreStore = useCoreStore();
|
|
@@ -377,6 +384,7 @@ const props = defineProps<{
|
|
|
377
384
|
containerHeight?: number,
|
|
378
385
|
itemHeight?: number,
|
|
379
386
|
bufferSize?: number,
|
|
387
|
+
customActionIconsThreeDotsMenuItems?: any[]
|
|
380
388
|
tableRowReplaceInjection?: AdminForthComponentDeclaration
|
|
381
389
|
}>();
|
|
382
390
|
|
|
@@ -394,6 +402,12 @@ const pageInput = ref('1');
|
|
|
394
402
|
const page = ref(1);
|
|
395
403
|
const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
|
|
396
404
|
|
|
405
|
+
const showListActionsThreeDots = computed(() => {
|
|
406
|
+
return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
|
|
407
|
+
|| (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
|
|
408
|
+
|| !props.resource?.options.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
|
|
409
|
+
|| (props.resource?.options.baseActionsAsQuickIcons && props.resource?.options.baseActionsAsQuickIcons.length < 3) // if there all 3 base actions are shown as quick icons - hide three dots icon
|
|
410
|
+
})
|
|
397
411
|
|
|
398
412
|
const from = computed(() => ((page.value || 1) - 1) * props.pageSize + 1);
|
|
399
413
|
const to = computed(() => Math.min((page.value || 1) * props.pageSize, props.totalRows));
|
|
@@ -848,6 +848,7 @@ export interface AdminForthActionInput {
|
|
|
848
848
|
name: string;
|
|
849
849
|
showIn?: {
|
|
850
850
|
list?: boolean,
|
|
851
|
+
listThreeDotsMenu?: boolean,
|
|
851
852
|
showButton?: boolean,
|
|
852
853
|
showThreeDotsMenu?: boolean,
|
|
853
854
|
};
|
|
@@ -861,6 +862,7 @@ export interface AdminForthActionInput {
|
|
|
861
862
|
resource: AdminForthResource;
|
|
862
863
|
recordId: string;
|
|
863
864
|
adminUser: AdminUser;
|
|
865
|
+
response: IAdminForthHttpResponse;
|
|
864
866
|
extra?: HttpExtra;
|
|
865
867
|
tr: Function;
|
|
866
868
|
}) => Promise<{
|
|
@@ -1358,6 +1360,7 @@ export type AllowedActions = {
|
|
|
1358
1360
|
*/
|
|
1359
1361
|
export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
|
|
1360
1362
|
|
|
1363
|
+
baseActionsAsQuickIcons?: ('show' | 'edit' | 'delete')[],
|
|
1361
1364
|
/**
|
|
1362
1365
|
* Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
|
|
1363
1366
|
* using checkboxes.
|
|
@@ -503,6 +503,7 @@ export interface AdminForthResourceInputCommon {
|
|
|
503
503
|
bottom?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
504
504
|
threeDotsDropdownItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
505
505
|
customActionIcons?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
506
|
+
customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
506
507
|
tableBodyStart?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
507
508
|
tableRowReplace?: AdminForthComponentDeclaration | Array<AdminForthComponentDeclaration>,
|
|
508
509
|
},
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
:adminUser="coreStore.adminUser"
|
|
120
120
|
/>
|
|
121
121
|
<ResourceListTableVirtual
|
|
122
|
-
v-if="isVirtualScrollEnabled"
|
|
122
|
+
v-if="isVirtualScrollEnabled && !coreStore.isResourceFetching"
|
|
123
123
|
:resource="coreStore.resource"
|
|
124
124
|
:rows="rows"
|
|
125
125
|
:page="page"
|
|
@@ -143,6 +143,11 @@
|
|
|
143
143
|
? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
|
|
144
144
|
: []
|
|
145
145
|
"
|
|
146
|
+
:customActionIconsThreeDotsMenuItems="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems)
|
|
147
|
+
? coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems
|
|
148
|
+
: coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems
|
|
149
|
+
? [coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems]
|
|
150
|
+
: []"
|
|
146
151
|
:tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
|
|
147
152
|
? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
|
|
148
153
|
: coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
|
|
@@ -152,7 +157,7 @@
|
|
|
152
157
|
/>
|
|
153
158
|
|
|
154
159
|
<ResourceListTable
|
|
155
|
-
v-else
|
|
160
|
+
v-else-if="!coreStore.isResourceFetching"
|
|
156
161
|
:resource="coreStore.resource"
|
|
157
162
|
:rows="rows"
|
|
158
163
|
:page="page"
|
|
@@ -176,6 +181,11 @@
|
|
|
176
181
|
? [coreStore.resourceOptions.pageInjections.list.tableBodyStart]
|
|
177
182
|
: []
|
|
178
183
|
"
|
|
184
|
+
:customActionIconsThreeDotsMenuItems="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems)
|
|
185
|
+
? coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems
|
|
186
|
+
: coreStore.resourceOptions?.pageInjections?.list?.customActionIconsThreeDotsMenuItems
|
|
187
|
+
? [coreStore.resourceOptions.pageInjections.list.customActionIconsThreeDotsMenuItems]
|
|
188
|
+
: []"
|
|
179
189
|
:tableRowReplaceInjection="Array.isArray(coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace)
|
|
180
190
|
? coreStore.resourceOptions.pageInjections.list.tableRowReplace[0]
|
|
181
191
|
: coreStore.resourceOptions?.pageInjections?.list?.tableRowReplace || undefined"
|
package/dist/types/Back.d.ts
CHANGED
|
@@ -830,6 +830,7 @@ export interface AdminForthActionInput {
|
|
|
830
830
|
name: string;
|
|
831
831
|
showIn?: {
|
|
832
832
|
list?: boolean;
|
|
833
|
+
listThreeDotsMenu?: boolean;
|
|
833
834
|
showButton?: boolean;
|
|
834
835
|
showThreeDotsMenu?: boolean;
|
|
835
836
|
};
|
|
@@ -843,6 +844,7 @@ export interface AdminForthActionInput {
|
|
|
843
844
|
resource: AdminForthResource;
|
|
844
845
|
recordId: string;
|
|
845
846
|
adminUser: AdminUser;
|
|
847
|
+
response: IAdminForthHttpResponse;
|
|
846
848
|
extra?: HttpExtra;
|
|
847
849
|
tr: Function;
|
|
848
850
|
}) => Promise<{
|
|
@@ -1208,6 +1210,7 @@ export type AllowedActions = {
|
|
|
1208
1210
|
* General options for resource.
|
|
1209
1211
|
*/
|
|
1210
1212
|
export interface ResourceOptionsInput extends Omit<NonNullable<AdminForthResourceInputCommon['options']>, 'allowedActions' | 'bulkActions'> {
|
|
1213
|
+
baseActionsAsQuickIcons?: ('show' | 'edit' | 'delete')[];
|
|
1211
1214
|
/**
|
|
1212
1215
|
* Custom bulk actions list. Bulk actions available in list view when user selects multiple records by
|
|
1213
1216
|
* using checkboxes.
|