adminforth 2.26.0-test.5 → 2.26.0-test.7
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/commands/createApp/templates/package.json.hbs +1 -1
- package/dist/dataConnectors/qdrant.d.ts +2 -10
- package/dist/dataConnectors/qdrant.d.ts.map +1 -1
- package/dist/dataConnectors/qdrant.js +12 -38
- package/dist/dataConnectors/qdrant.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -31
- package/dist/index.js.map +1 -1
- package/dist/modules/restApi.d.ts +1 -0
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +33 -16
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/utils.d.ts +6 -0
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +13 -0
- package/dist/modules/utils.js.map +1 -1
- package/dist/servers/express.d.ts.map +1 -1
- package/dist/servers/express.js +7 -1
- package/dist/servers/express.js.map +1 -1
- package/dist/spa/package-lock.json +44 -7
- package/dist/spa/package.json +1 -1
- package/dist/spa/pnpm-lock.yaml +301 -299
- package/dist/spa/src/adminforth.ts +17 -29
- package/dist/spa/src/afcl/Input.vue +1 -1
- package/dist/spa/src/afcl/Modal.vue +12 -1
- package/dist/spa/src/afcl/Select.vue +4 -2
- package/dist/spa/src/afcl/Table.vue +27 -13
- package/dist/spa/src/components/AcceptModal.vue +2 -0
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +11 -3
- package/dist/spa/src/components/CustomRangePicker.vue +16 -67
- package/dist/spa/src/components/ListActionsThreeDots.vue +10 -9
- package/dist/spa/src/components/RangePicker.vue +236 -0
- package/dist/spa/src/components/ResourceListTable.vue +283 -136
- package/dist/spa/src/components/Sidebar.vue +1 -1
- package/dist/spa/src/components/ThreeDotsMenu.vue +10 -9
- package/dist/spa/src/i18n.ts +1 -1
- package/dist/spa/src/stores/core.ts +4 -2
- package/dist/spa/src/types/Back.ts +7 -3
- package/dist/spa/src/types/Common.ts +25 -5
- package/dist/spa/src/types/FrontendAPI.ts +6 -1
- package/dist/spa/src/utils/listUtils.ts +8 -2
- package/dist/spa/src/utils/utils.ts +29 -10
- package/dist/spa/src/views/CreateView.vue +8 -8
- package/dist/spa/src/views/EditView.vue +8 -7
- package/dist/spa/src/views/ListView.vue +14 -48
- package/dist/spa/src/views/LoginView.vue +13 -13
- package/dist/spa/src/views/ShowView.vue +10 -10
- package/dist/spa/tsconfig.app.json +0 -1
- package/dist/types/Back.d.ts +4 -4
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +20 -5
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +13 -1
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/package.json +2 -2
- package/dist/spa/src/components/ResourceListTableVirtual.vue +0 -789
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<!-- table -->
|
|
3
|
-
<div
|
|
3
|
+
<div
|
|
4
|
+
class="relative shadow-listTableShadow dark:shadow-darkListTableShadow overflow-auto "
|
|
4
5
|
:class="{'rounded-default': !noRoundings}"
|
|
6
|
+
:style="isVirtualScrollEnabled ? { maxHeight: `${containerHeight}px` } : {}"
|
|
7
|
+
@scroll="handleScroll"
|
|
8
|
+
ref="containerRef"
|
|
5
9
|
>
|
|
6
10
|
<!-- skelet loader -->
|
|
7
|
-
<div
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
<div
|
|
12
|
+
role="status" v-if="!resource || !resource.columns"
|
|
13
|
+
class="max-w p-4 space-y-4 divide-y divide-gray-200 rounded shadow animate-pulse dark:divide-gray-700 md:p-6 dark:border-gray-700"
|
|
14
|
+
>
|
|
15
|
+
<div role="status" class="max-w-sm animate-pulse">
|
|
16
|
+
<div class="h-2 bg-lightListSkeletLoader rounded-full dark:bg-darkListSkeletLoader max-w-[360px]"></div>
|
|
17
|
+
</div>
|
|
12
18
|
</div>
|
|
19
|
+
|
|
13
20
|
<table v-else class="w-full text-sm text-left rtl:text-right text-lightListTableText dark:text-darkListTableText rounded-default">
|
|
14
21
|
|
|
15
22
|
<tbody>
|
|
@@ -27,27 +34,33 @@
|
|
|
27
34
|
|
|
28
35
|
<td v-for="c in columnsListed" ref="headerRefs" scope="col" class="list-table-header-cell px-2 md:px-3 lg:px-6 py-3" :class="{'sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading': c.listSticky}">
|
|
29
36
|
|
|
30
|
-
<div
|
|
31
|
-
|
|
37
|
+
<div
|
|
38
|
+
@click="(evt) => c.sortable && onSortButtonClick(evt, c.name)"
|
|
39
|
+
class="flex items-center " :class="{'cursor-pointer':c.sortable}"
|
|
40
|
+
>
|
|
32
41
|
{{ c.label }}
|
|
33
42
|
|
|
34
43
|
<div v-if="c.sortable">
|
|
35
|
-
<svg
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
<svg
|
|
45
|
+
v-if="ascArr.includes(c.name) || descArr.includes(c.name)" class="w-3 h-3 ms-1.5"
|
|
46
|
+
fill='currentColor'
|
|
47
|
+
:class="{'rotate-180':descArr.includes(c.name)}" viewBox="0 0 24 24"
|
|
48
|
+
>
|
|
49
|
+
<path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 0z"/>
|
|
40
50
|
</svg>
|
|
41
|
-
<svg
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
<svg
|
|
52
|
+
v-else class="w-3 h-3 ms-1.5 opacity-30" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
fill='currentColor'
|
|
54
|
+
viewBox="0 0 24 24"
|
|
55
|
+
>
|
|
56
|
+
<path d="M8.574 11.024h6.852a2.075 2.075 0 0 0 1.847-1.086 1.9 1.9 0 0 0-.11-1.986L13.736 2.9a2.122 2.122 0 0 0-3.472 0L6.837 7.952a1.9 1.9 0 0 0-.11 1.986 2.074 2.074 0 0 0 1.847 1.086Zm6.852 1.952H8.574a2.072 2.072 0 0 0-1.847 1.087 1.9 1.9 0 0 0 .11 1.985l3.426 5.05a2.123 2.123 0 0 0 3.472 0l3.427-5.05a1.9 1.9 0 0 0 .11-1.985 2.074 2.074 0 0 0-1.846-1.087Z"/>
|
|
46
57
|
</svg>
|
|
47
58
|
</div>
|
|
59
|
+
|
|
48
60
|
<span
|
|
49
61
|
class="bg-red-100 text-red-800 text-xs font-medium me-1 px-1 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400"
|
|
50
|
-
v-if="sort.findIndex((s: any) => s.field === c.name) !== -1 && sort?.length > 1"
|
|
62
|
+
v-if="sort.findIndex((s: any) => s.field === c.name) !== -1 && sort?.length > 1"
|
|
63
|
+
>
|
|
51
64
|
{{ sort.findIndex((s: any) => s.field === c.name) + 1 }}
|
|
52
65
|
</span>
|
|
53
66
|
|
|
@@ -72,40 +85,45 @@
|
|
|
72
85
|
|
|
73
86
|
<tr v-else-if="rows.length === 0" class="bg-lightListTable dark:bg-darkListTable dark:border-darkListTableBorder">
|
|
74
87
|
<td :colspan="resource?.columns.length + 2">
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
88
|
+
<div
|
|
89
|
+
id="toast-simple"
|
|
90
|
+
class=" mx-auto my-5 flex items-center w-full max-w-xs p-4 space-x-4 rtl:space-x-reverse text-gray-500 divide-x rtl:divide-x-reverse divide-gray-200 dark:text-gray-400 dark:divide-gray-700 space-x dark:bg-gray-800"
|
|
91
|
+
role="alert"
|
|
92
|
+
>
|
|
79
93
|
<IconInboxOutline class="w-6 h-6 text-gray-500 dark:text-gray-400"/>
|
|
80
94
|
<div class="ps-4 text-sm font-normal">{{ $t('No items here yet') }}</div>
|
|
81
95
|
</div>
|
|
82
|
-
|
|
83
96
|
</td>
|
|
84
97
|
</tr>
|
|
85
98
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
>
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
<!-- Top spacer(virtual scroll) -->
|
|
100
|
+
<tr v-if="isVirtualScrollEnabled && spacerHeight > 0">
|
|
101
|
+
<td :colspan="resource?.columns.length + 2" :style="{ height: `${spacerHeight}px` }"></td>
|
|
102
|
+
</tr>
|
|
103
|
+
|
|
104
|
+
<component
|
|
105
|
+
v-for="(row, rowI) in rowsToRender"
|
|
106
|
+
:is="tableRowReplaceInjection ? getCustomComponent(formatComponent(tableRowReplaceInjection)) : 'tr'"
|
|
107
|
+
:key="`row_${row._primaryKeyValue}`"
|
|
108
|
+
:record="row"
|
|
109
|
+
:resource="resource"
|
|
110
|
+
:adminUser="coreStore.adminUser"
|
|
111
|
+
:meta="tableRowReplaceInjection ? formatComponent(tableRowReplaceInjection).meta : undefined"
|
|
112
|
+
@click="onClick($event, row)"
|
|
113
|
+
ref="rowRefs"
|
|
114
|
+
class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
|
|
115
|
+
:class="{'border-b': rowI !== rowsToRender.length - 1, 'cursor-pointer': row._clickUrl !== null}"
|
|
116
|
+
@mounted="(el: any) => updateRowHeight(`row_${row._primaryKeyValue}`, el.offsetHeight)"
|
|
117
|
+
>
|
|
118
|
+
<td class="w-4 p-4 cursor-default sticky-column bg-lightListTable dark:bg-darkListTable" @click="(e)=>e.stopPropagation()">
|
|
119
|
+
<Checkbox
|
|
120
|
+
:model-value="checkboxesInternal.includes(row._primaryKeyValue)"
|
|
121
|
+
@change="(e: any)=>{addToCheckedValues(row._primaryKeyValue)}"
|
|
122
|
+
@click="(e: any)=>e.stopPropagation()"
|
|
123
|
+
>
|
|
124
|
+
<span class="sr-only">{{ $t('checkbox') }}</span>
|
|
125
|
+
</Checkbox>
|
|
126
|
+
</td>
|
|
109
127
|
|
|
110
128
|
<td v-for="c in columnsListed" class="px-2 md:px-3 lg:px-6 py-4" :class="{'sticky-column bg-lightListTable dark:bg-darkListTable': c.listSticky}">
|
|
111
129
|
<!-- if c.name in listComponentsPerColumn, render it. If not, render ValueRenderer -->
|
|
@@ -130,7 +148,6 @@
|
|
|
130
148
|
primaryKey: row._primaryKeyValue,
|
|
131
149
|
}
|
|
132
150
|
}"
|
|
133
|
-
|
|
134
151
|
>
|
|
135
152
|
<IconEyeSolid class="af-show-icon w-5 h-5 me-2"/>
|
|
136
153
|
</RouterLink>
|
|
@@ -186,17 +203,17 @@
|
|
|
186
203
|
:key="action.id"
|
|
187
204
|
>
|
|
188
205
|
<component
|
|
189
|
-
|
|
190
|
-
:
|
|
206
|
+
v-if="action.customComponent"
|
|
207
|
+
:is="action.customComponent ? getCustomComponent(formatComponent(action.customComponent)) : CallActionWrapper"
|
|
208
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
191
209
|
:row="row"
|
|
192
210
|
:resource="resource"
|
|
193
|
-
:adminUser="adminUser"
|
|
194
|
-
@callAction="(payload? : Object) => startCustomAction(action.id, row, payload)"
|
|
211
|
+
:adminUser="coreStore.adminUser"
|
|
212
|
+
@callAction="(payload? : Object) => startCustomAction(action.id as string | number, row, payload)"
|
|
195
213
|
>
|
|
196
214
|
<button
|
|
197
215
|
type="button"
|
|
198
216
|
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"
|
|
199
|
-
:disabled="rowActionLoadingStates?.[action.id]"
|
|
200
217
|
>
|
|
201
218
|
<component
|
|
202
219
|
v-if="action.icon"
|
|
@@ -219,12 +236,18 @@
|
|
|
219
236
|
:deleteRecord="deleteRecord"
|
|
220
237
|
:resourceId="resource.resourceId"
|
|
221
238
|
:startCustomAction="startCustomAction"
|
|
222
|
-
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems"
|
|
239
|
+
:customActionIconsThreeDotsMenuItems="customActionIconsThreeDotsMenuItems ?? []"
|
|
223
240
|
/>
|
|
224
241
|
</div>
|
|
225
242
|
|
|
226
243
|
</td>
|
|
227
|
-
|
|
244
|
+
</component>
|
|
245
|
+
<!-- Bottom spacer(virtual scroll) -->
|
|
246
|
+
<tr v-if="isVirtualScrollEnabled && totalHeight > 0">
|
|
247
|
+
<td :colspan="resource?.columns.length + 2"
|
|
248
|
+
:style="{ height: `${Math.max(0, totalHeight - (endIndex + 1) * (props.itemHeight || 52.5))}px` }">
|
|
249
|
+
</td>
|
|
250
|
+
</tr>
|
|
228
251
|
</tbody>
|
|
229
252
|
</table>
|
|
230
253
|
</div>
|
|
@@ -236,85 +259,83 @@
|
|
|
236
259
|
<div class="af-pagination-buttons-container inline-flex "
|
|
237
260
|
v-if="(rows || totalRows) && totalRows >= pageSize && totalRows > 0"
|
|
238
261
|
>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
</svg>
|
|
283
|
-
</button>
|
|
262
|
+
<!-- Buttons -->
|
|
263
|
+
<button
|
|
264
|
+
class="af-pagination-prev-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 rounded-s border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-10 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-darkListTablePaginationTextHover dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
265
|
+
@click="page--; pageInput = page.toString();"
|
|
266
|
+
:disabled="page <= 1"
|
|
267
|
+
>
|
|
268
|
+
<svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
|
|
269
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5H1m0 0 4 4M1 5l4-4"/>
|
|
270
|
+
</svg>
|
|
271
|
+
<span class="hidden sm:inline">
|
|
272
|
+
{{ $t('Prev') }}
|
|
273
|
+
</span>
|
|
274
|
+
</button>
|
|
275
|
+
<button
|
|
276
|
+
class="af-pagination-first-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-r-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-10 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-darkListTablePaginationTextHover dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
277
|
+
@click="page = 1;
|
|
278
|
+
pageInput = page.toString();"
|
|
279
|
+
:disabled="page <= 1"
|
|
280
|
+
>
|
|
281
|
+
1
|
|
282
|
+
</button>
|
|
283
|
+
<input
|
|
284
|
+
type="text"
|
|
285
|
+
v-model="pageInput"
|
|
286
|
+
:style="{ width: `${Math.max(1, pageInput.length+4)}ch` }"
|
|
287
|
+
class="af-pagination-input min-w-10 outline-none inline-block py-1.5 px-3 text-sm text-center text-lightListTablePaginationCurrentPageText border border-lightListTablePaginationBorder dark:border-darkListTablePaginationBorder dark:text-darkListTablePaginationCurrentPageText dark:bg-darkListTablePaginationBackgoround z-10"
|
|
288
|
+
@keydown="onPageKeydown($event)"
|
|
289
|
+
@blur="validatePageInput()"
|
|
290
|
+
/>
|
|
291
|
+
|
|
292
|
+
<button
|
|
293
|
+
class="af-pagination-last-page-button flex items-center py-1 px-3 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-l-0 border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-10 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-white dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
294
|
+
@click="page = totalPages; pageInput = page.toString();" :disabled="page >= totalPages">
|
|
295
|
+
{{ totalPages }}
|
|
296
|
+
</button>
|
|
297
|
+
<button
|
|
298
|
+
class="af-pagination-next-button flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightListTablePaginationText focus:outline-none bg-lightListTablePaginationBackgoround border-l-0 rounded-e border border-lightListTablePaginationBorder hover:bg-lightListTablePaginationBackgoroundHover hover:text-lightListTablePaginationTextHover focus:z-10 focus:ring-4 focus:ring-lightListTablePaginationFocusRing dark:focus:ring-darkListTablePaginationFocusRing dark:bg-darkListTablePaginationBackgoround dark:text-darkListTablePaginationText dark:border-darkListTablePaginationBorder dark:hover:text-white dark:hover:bg-darkListTablePaginationBackgoroundHover disabled:opacity-50"
|
|
299
|
+
@click="page++; pageInput = page.toString();" :disabled="page >= totalPages">
|
|
300
|
+
<span class="hidden sm:inline">{{ $t('Next') }}</span>
|
|
301
|
+
<svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 10">
|
|
302
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 5h12m0 0L9 1m4 4L9 9"/>
|
|
303
|
+
</svg>
|
|
304
|
+
</button>
|
|
284
305
|
</div>
|
|
285
306
|
|
|
286
307
|
<!-- Help text -->
|
|
287
308
|
<span class="ml-4 text-sm text-lightListTablePaginationHelpText dark:text-darkListTablePaginationHelpText">
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
309
|
+
<span v-if="((((page || 1) - 1) * pageSize + 1 > totalRows) && totalRows > 0)">{{ $t('Wrong Page') }} </span>
|
|
310
|
+
<template v-else-if="resource && totalRows > 0">
|
|
311
|
+
|
|
312
|
+
<span class="af-pagination-info hidden sm:inline">
|
|
313
|
+
<i18n-t keypath="Showing {from} to {to} of {total} Entries" tag="p" >
|
|
314
|
+
<template v-slot:from>
|
|
315
|
+
<strong>{{ from }}</strong>
|
|
316
|
+
</template>
|
|
317
|
+
<template v-slot:to>
|
|
318
|
+
<strong>{{ to }}</strong>
|
|
319
|
+
</template>
|
|
320
|
+
<template v-slot:total>
|
|
321
|
+
<strong>{{ totalRows }}</strong>
|
|
322
|
+
</template>
|
|
323
|
+
</i18n-t>
|
|
324
|
+
</span>
|
|
325
|
+
<span class="sm:hidden">
|
|
326
|
+
<i18n-t keypath="{from} - {to} of {total}" tag="p" >
|
|
327
|
+
<template v-slot:from>
|
|
328
|
+
<strong>{{ from }}</strong>
|
|
329
|
+
</template>
|
|
330
|
+
<template v-slot:to>
|
|
331
|
+
<strong>{{ to }}</strong>
|
|
332
|
+
</template>
|
|
333
|
+
<template v-slot:total>
|
|
334
|
+
<strong>{{ totalRows }}</strong>
|
|
335
|
+
</template>
|
|
336
|
+
</i18n-t>
|
|
337
|
+
</span>
|
|
338
|
+
</template>
|
|
318
339
|
</span>
|
|
319
340
|
</div>
|
|
320
341
|
</template>
|
|
@@ -326,7 +347,7 @@ import { computed, onMounted, ref, watch, useTemplateRef, nextTick, type Ref } f
|
|
|
326
347
|
import { callAdminForthApi } from '@/utils';
|
|
327
348
|
import { useI18n } from 'vue-i18n';
|
|
328
349
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
329
|
-
import { getCustomComponent } from '@/utils';
|
|
350
|
+
import { getCustomComponent, formatComponent } from '@/utils';
|
|
330
351
|
import { useCoreStore } from '@/stores/core';
|
|
331
352
|
import { showSuccesTost, showErrorTost } from '@/composables/useFrontendApi';
|
|
332
353
|
import SkeleteLoader from '@/components/SkeleteLoader.vue';
|
|
@@ -339,7 +360,7 @@ import {
|
|
|
339
360
|
} from '@iconify-prerendered/vue-flowbite';
|
|
340
361
|
import router from '@/router';
|
|
341
362
|
import { Tooltip } from '@/afcl';
|
|
342
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
363
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclarationFull, AdminForthComponentDeclaration } from '@/types/Common';
|
|
343
364
|
import { useAdminforth } from '@/adminforth';
|
|
344
365
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
345
366
|
import ListActionsThreeDots from '@/components/ListActionsThreeDots.vue';
|
|
@@ -359,10 +380,23 @@ const props = defineProps<{
|
|
|
359
380
|
noRoundings?: boolean,
|
|
360
381
|
customActionsInjection?: any[],
|
|
361
382
|
tableBodyStartInjection?: any[],
|
|
362
|
-
|
|
363
|
-
|
|
383
|
+
containerHeight?: number,
|
|
384
|
+
itemHeight?: number,
|
|
385
|
+
bufferSize?: number,
|
|
386
|
+
customActionIconsThreeDotsMenuItems?: AdminForthComponentDeclaration[]
|
|
387
|
+
tableRowReplaceInjection?: AdminForthComponentDeclaration,
|
|
388
|
+
isVirtualScrollEnabled: boolean
|
|
364
389
|
}>();
|
|
365
390
|
|
|
391
|
+
//select between all rows or rows, that should be rendered in virtual scroll
|
|
392
|
+
const rowsToRender = computed(() => {
|
|
393
|
+
if (!props.isVirtualScrollEnabled) {
|
|
394
|
+
return props.rows || [];
|
|
395
|
+
} else {
|
|
396
|
+
return visibleRows.value;
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
366
400
|
// emits, update page
|
|
367
401
|
const emits = defineEmits([
|
|
368
402
|
'update:page',
|
|
@@ -380,7 +414,7 @@ const sort: Ref<Array<{field: string, direction: string}>> = ref([]);
|
|
|
380
414
|
const showListActionsThreeDots = computed(() => {
|
|
381
415
|
return props.resource?.options?.actions?.some(a => a.showIn?.listThreeDotsMenu) // show if any action is set to show in three dots menu
|
|
382
416
|
|| (props.customActionIconsThreeDotsMenuItems && props.customActionIconsThreeDotsMenuItems.length > 0) // or if there are custom action icons for three dots menu
|
|
383
|
-
|| !props.resource?.options
|
|
417
|
+
|| !props.resource?.options?.baseActionsAsQuickIcons // or if there is no baseActionsAsQuickIcons
|
|
384
418
|
|| (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
|
|
385
419
|
})
|
|
386
420
|
|
|
@@ -575,7 +609,7 @@ async function deleteRecord(row: any) {
|
|
|
575
609
|
|
|
576
610
|
const actionLoadingStates = ref<Record<string | number, boolean>>({});
|
|
577
611
|
|
|
578
|
-
async function startCustomAction(actionId: string, row: any, extraData: Record<string, any> = {}) {
|
|
612
|
+
async function startCustomAction(actionId: string | number, row: any, extraData: Record<string, any> = {}) {
|
|
579
613
|
|
|
580
614
|
actionLoadingStates.value[actionId] = true;
|
|
581
615
|
|
|
@@ -628,6 +662,119 @@ function validatePageInput() {
|
|
|
628
662
|
page.value = validPage;
|
|
629
663
|
pageInput.value = validPage.toString();
|
|
630
664
|
}
|
|
665
|
+
/*
|
|
666
|
+
*___________________________________________________________________
|
|
667
|
+
* |
|
|
668
|
+
* Virtual Scroll Implementation |
|
|
669
|
+
*___________________________________________________________________|
|
|
670
|
+
*/
|
|
671
|
+
// Add throttle utility
|
|
672
|
+
const throttle = (fn: Function, delay: number) => {
|
|
673
|
+
let lastCall = 0;
|
|
674
|
+
return (...args: any[]) => {
|
|
675
|
+
const now = Date.now();
|
|
676
|
+
if (now - lastCall >= delay) {
|
|
677
|
+
lastCall = now;
|
|
678
|
+
fn(...args);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
};
|
|
682
|
+
// Virtual scroll state
|
|
683
|
+
const containerRef = ref<HTMLElement | null>(null);
|
|
684
|
+
const scrollTop = ref(0);
|
|
685
|
+
const visibleRows = ref<any[]>([]);
|
|
686
|
+
const startIndex = ref(0);
|
|
687
|
+
const endIndex = ref(0);
|
|
688
|
+
const totalHeight = ref(0);
|
|
689
|
+
const spacerHeight = ref(0);
|
|
690
|
+
const rowHeightsMap = ref<{[key: string]: number}>({});
|
|
691
|
+
const rowPositions = ref<number[]>([]);
|
|
692
|
+
// Calculate row positions based on heights
|
|
693
|
+
const calculateRowPositions = () => {
|
|
694
|
+
if (!props.rows) return;
|
|
695
|
+
|
|
696
|
+
let currentPosition = 0;
|
|
697
|
+
rowPositions.value = props.rows.map((row) => {
|
|
698
|
+
const height = rowHeightsMap.value[`row_${row._primaryKeyValue}`] || props.itemHeight || 52.5;
|
|
699
|
+
const position = currentPosition;
|
|
700
|
+
currentPosition += height;
|
|
701
|
+
return position;
|
|
702
|
+
});
|
|
703
|
+
totalHeight.value = currentPosition;
|
|
704
|
+
};
|
|
705
|
+
// Calculate visible rows based on scroll position
|
|
706
|
+
const calculateVisibleRows = () => {
|
|
707
|
+
if (!props.rows?.length) {
|
|
708
|
+
visibleRows.value = props.rows || [];
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const buffer = props.bufferSize || 5;
|
|
712
|
+
const containerHeight = props.containerHeight || 900;
|
|
713
|
+
|
|
714
|
+
// For single item or small datasets, show all rows
|
|
715
|
+
if (props.rows.length <= buffer * 2 + 1) {
|
|
716
|
+
startIndex.value = 0;
|
|
717
|
+
endIndex.value = props.rows.length - 1;
|
|
718
|
+
visibleRows.value = props.rows;
|
|
719
|
+
spacerHeight.value = 0;
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Binary search for start index
|
|
724
|
+
let low = 0;
|
|
725
|
+
let high = rowPositions.value.length - 1;
|
|
726
|
+
const targetPosition = scrollTop.value;
|
|
727
|
+
|
|
728
|
+
while (low <= high) {
|
|
729
|
+
const mid = Math.floor((low + high) / 2);
|
|
730
|
+
if (rowPositions.value[mid] <= targetPosition) {
|
|
731
|
+
low = mid + 1;
|
|
732
|
+
} else {
|
|
733
|
+
high = mid - 1;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const newStartIndex = Math.max(0, low - 1 - buffer);
|
|
738
|
+
const newEndIndex = Math.min(
|
|
739
|
+
props.rows.length - 1,
|
|
740
|
+
newStartIndex + Math.ceil(containerHeight / (props.itemHeight || 52.5)) + buffer * 2
|
|
741
|
+
);
|
|
742
|
+
// Ensure at least one row is visible
|
|
743
|
+
if (newEndIndex < newStartIndex) {
|
|
744
|
+
startIndex.value = 0;
|
|
745
|
+
endIndex.value = Math.min(props.rows.length - 1, Math.ceil(containerHeight / (props.itemHeight || 52.5)));
|
|
746
|
+
} else {
|
|
747
|
+
startIndex.value = newStartIndex;
|
|
748
|
+
endIndex.value = newEndIndex;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
visibleRows.value = props.rows.slice(startIndex.value, endIndex.value + 1);
|
|
752
|
+
spacerHeight.value = startIndex.value > 0 ? rowPositions.value[startIndex.value - 1] : 0;
|
|
753
|
+
};
|
|
754
|
+
// Throttled scroll handler
|
|
755
|
+
const handleScroll = throttle((e: Event) => {
|
|
756
|
+
if (!props.isVirtualScrollEnabled) return;
|
|
757
|
+
const target = e.target as HTMLElement;
|
|
758
|
+
scrollTop.value = target.scrollTop;
|
|
759
|
+
calculateVisibleRows();
|
|
760
|
+
}, 16);
|
|
761
|
+
// Update row height when it changes
|
|
762
|
+
const updateRowHeight = (rowId: string, height: number) => {
|
|
763
|
+
if (!props.isVirtualScrollEnabled) return;
|
|
764
|
+
if (rowHeightsMap.value[rowId] !== height) {
|
|
765
|
+
rowHeightsMap.value[rowId] = height;
|
|
766
|
+
calculateRowPositions();
|
|
767
|
+
calculateVisibleRows();
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
// Watch for changes in rows
|
|
771
|
+
watch(() => props.rows, () => {
|
|
772
|
+
if (props.rows) {
|
|
773
|
+
calculateRowPositions();
|
|
774
|
+
calculateVisibleRows();
|
|
775
|
+
}
|
|
776
|
+
}, { immediate: true });
|
|
777
|
+
|
|
631
778
|
|
|
632
779
|
</script>
|
|
633
780
|
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
54
|
|
|
55
|
-
<div v-if="coreStore
|
|
55
|
+
<div v-if="coreStore?.config?.defaultUserExists && !isLocalhost" class="p-4 mb-4 text-white rounded-lg bg-red-700/80 fill-white text-sm">
|
|
56
56
|
<IconExclamationCircleOutline class="inline-block align-text-bottom mr-0,5 w-5 h-5" />
|
|
57
57
|
Default user <strong>"adminforth"</strong> detected. Delete it and create your own account.
|
|
58
58
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template >
|
|
2
|
-
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action:
|
|
2
|
+
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionFront) => action.showInThreeDotsDropdown))">
|
|
3
3
|
<button
|
|
4
4
|
ref="buttonTriggerRef"
|
|
5
5
|
@click="toggleDropdownVisibility"
|
|
@@ -46,11 +46,12 @@
|
|
|
46
46
|
<li v-for="action in customActions" :key="action.id">
|
|
47
47
|
<div class="wrapper">
|
|
48
48
|
<component
|
|
49
|
-
|
|
50
|
-
:
|
|
49
|
+
v-if="action.customComponent"
|
|
50
|
+
:is="(action.customComponent && getCustomComponent(formatComponent(action.customComponent))) || CallActionWrapper"
|
|
51
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
51
52
|
@callAction="(payload? : Object) => handleActionClick(action, payload)"
|
|
52
53
|
>
|
|
53
|
-
<a
|
|
54
|
+
<a @click.prevent class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover">
|
|
54
55
|
<div class="flex items-center gap-2">
|
|
55
56
|
<component
|
|
56
57
|
v-if="action.icon"
|
|
@@ -64,7 +65,7 @@
|
|
|
64
65
|
</div>
|
|
65
66
|
</li>
|
|
66
67
|
<li v-for="action in (bulkActions ?? []).filter(a => a.showInThreeDotsDropdown)" :key="action.id">
|
|
67
|
-
<a
|
|
68
|
+
<a @click.prevent="startBulkAction(action.id)"
|
|
68
69
|
class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover"
|
|
69
70
|
:class="{
|
|
70
71
|
'pointer-events-none': checkboxes && checkboxes.length === 0,
|
|
@@ -88,14 +89,14 @@
|
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
<script setup lang="ts">
|
|
91
|
-
import { getCustomComponent, getIcon } from '@/utils';
|
|
92
|
+
import { getCustomComponent, getIcon, formatComponent } from '@/utils';
|
|
92
93
|
import { useCoreStore } from '@/stores/core';
|
|
93
94
|
import { useAdminforth } from '@/adminforth';
|
|
94
95
|
import { callAdminForthApi } from '@/utils';
|
|
95
96
|
import { useRoute, useRouter } from 'vue-router';
|
|
96
97
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
97
98
|
import { ref, type ComponentPublicInstance, onMounted, onUnmounted } from 'vue';
|
|
98
|
-
import type {
|
|
99
|
+
import type { AdminForthActionFront, AdminForthBulkActionFront, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
99
100
|
import type { AdminForthActionInput } from '@/types/Back';
|
|
100
101
|
|
|
101
102
|
const { list, alert} = useAdminforth();
|
|
@@ -109,8 +110,8 @@ const buttonTriggerRef = ref<HTMLElement | null>(null);
|
|
|
109
110
|
|
|
110
111
|
const props = defineProps({
|
|
111
112
|
threeDotsDropdownItems: Array<AdminForthComponentDeclarationFull>,
|
|
112
|
-
customActions: Array<
|
|
113
|
-
bulkActions: Array<
|
|
113
|
+
customActions: Array<AdminForthActionFront>,
|
|
114
|
+
bulkActions: Array<AdminForthBulkActionFront>,
|
|
114
115
|
checkboxes: Array,
|
|
115
116
|
updateList: {
|
|
116
117
|
type: Function,
|