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.
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/dist/modules/restApi.d.ts +1 -0
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +25 -1
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.js +2 -2
- package/dist/modules/styles.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/App.vue +1 -1
- 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 +9 -8
- package/dist/spa/src/components/RangePicker.vue +236 -0
- package/dist/spa/src/components/ResourceListTable.vue +45 -70
- package/dist/spa/src/components/Sidebar.vue +1 -1
- package/dist/spa/src/components/ThreeDotsMenu.vue +30 -52
- package/dist/spa/src/i18n.ts +1 -1
- package/dist/spa/src/stores/core.ts +4 -2
- package/dist/spa/src/types/Back.ts +11 -4
- package/dist/spa/src/types/Common.ts +26 -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 +187 -10
- package/dist/spa/src/views/CreateView.vue +10 -10
- package/dist/spa/src/views/EditView.vue +10 -9
- package/dist/spa/src/views/ListView.vue +122 -18
- package/dist/spa/src/views/LoginView.vue +13 -13
- package/dist/spa/src/views/ShowView.vue +53 -60
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/types/Back.d.ts +8 -5
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +21 -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 +1 -1
package/dist/spa/src/App.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<nav
|
|
4
4
|
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout"
|
|
5
|
-
class="fixed h-14 top-0 z-30 w-full border-b shadow-sm bg-lightNavbar
|
|
5
|
+
class="fixed h-14 top-0 z-30 w-full border-b drop-shadow-sm bg-lightNavbar dark:bg-darkNavbar dark:border-darkSidebarDevider"
|
|
6
6
|
>
|
|
7
7
|
<div class="af-header px-3 lg:px-5 lg:pl-3 flex items-center justify-between h-full w-full" >
|
|
8
8
|
<div class="flex items-center justify-start rtl:justify-end">
|
|
@@ -19,26 +19,13 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
19
19
|
public modalStore:any
|
|
20
20
|
public filtersStore:any
|
|
21
21
|
public coreStore:any
|
|
22
|
-
private saveInterceptors: Record<string, Array<
|
|
23
|
-
|
|
24
|
-
public list: {
|
|
25
|
-
refresh(): Promise<{ error? : string }>;
|
|
26
|
-
silentRefresh(): Promise<{ error? : string }>;
|
|
27
|
-
silentRefreshRow(pk: any): Promise<{ error? : string }>;
|
|
28
|
-
closeThreeDotsDropdown(): Promise<{ error? : string }>;
|
|
29
|
-
closeUserMenuDropdown: () => void;
|
|
30
|
-
setFilter: (filter: FilterParams) => void;
|
|
31
|
-
updateFilter: (filter: FilterParams) => void;
|
|
32
|
-
clearFilters: () => void;
|
|
33
|
-
}
|
|
22
|
+
private saveInterceptors: Record<string, Array<Parameters<FrontendAPIInterface['registerSaveInterceptor']>[0]>> = {};
|
|
34
23
|
|
|
35
|
-
public
|
|
36
|
-
refreshMenuBadges: () => void;
|
|
37
|
-
}
|
|
24
|
+
public list: FrontendAPIInterface['list'];
|
|
38
25
|
|
|
39
|
-
public
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
public menu: FrontendAPIInterface['menu'];
|
|
27
|
+
|
|
28
|
+
public show: FrontendAPIInterface['show'];
|
|
42
29
|
|
|
43
30
|
closeUserMenuDropdown(): void {
|
|
44
31
|
console.log('closeUserMenuDropdown')
|
|
@@ -70,9 +57,6 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
70
57
|
console.log('closeThreeDotsDropdown')
|
|
71
58
|
return { error: 'Not implemented' }
|
|
72
59
|
},
|
|
73
|
-
closeUserMenuDropdown: () => {
|
|
74
|
-
console.log('closeUserMenuDropdown')
|
|
75
|
-
},
|
|
76
60
|
setFilter: this.setListFilter.bind(this),
|
|
77
61
|
updateFilter: this.updateListFilter.bind(this),
|
|
78
62
|
clearFilters: this.clearListFilters.bind(this),
|
|
@@ -83,11 +67,15 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
83
67
|
console.log('show.refresh')
|
|
84
68
|
}
|
|
85
69
|
}
|
|
70
|
+
|
|
71
|
+
this.closeUserMenuDropdown = () => {
|
|
72
|
+
console.log('closeUserMenuDropdown')
|
|
73
|
+
};
|
|
86
74
|
}
|
|
87
75
|
|
|
88
76
|
registerSaveInterceptor(
|
|
89
|
-
handler:
|
|
90
|
-
):
|
|
77
|
+
handler: Parameters<FrontendAPIInterface['registerSaveInterceptor']>[0]
|
|
78
|
+
): ReturnType<FrontendAPIInterface['registerSaveInterceptor']> {
|
|
91
79
|
const rid = router.currentRoute.value?.params?.resourceId as string;
|
|
92
80
|
if (!rid) {
|
|
93
81
|
return;
|
|
@@ -98,7 +86,7 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
98
86
|
this.saveInterceptors[rid].push(handler);
|
|
99
87
|
}
|
|
100
88
|
|
|
101
|
-
async runSaveInterceptors(params:
|
|
89
|
+
async runSaveInterceptors(params: Parameters<FrontendAPIInterface['runSaveInterceptors']>[0]): ReturnType<FrontendAPIInterface['runSaveInterceptors']> {
|
|
102
90
|
const list = this.saveInterceptors[params.resourceId] || [];
|
|
103
91
|
const aggregatedExtra: Record<string, any> = {};
|
|
104
92
|
for (const fn of list) {
|
|
@@ -120,7 +108,7 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
120
108
|
return { ok: true, extra: aggregatedExtra };
|
|
121
109
|
}
|
|
122
110
|
|
|
123
|
-
clearSaveInterceptors(resourceId?:
|
|
111
|
+
clearSaveInterceptors(resourceId?: Parameters<FrontendAPIInterface['clearSaveInterceptors']>[0]): ReturnType<FrontendAPIInterface['clearSaveInterceptors']> {
|
|
124
112
|
if (resourceId) {
|
|
125
113
|
delete this.saveInterceptors[resourceId];
|
|
126
114
|
} else {
|
|
@@ -128,7 +116,7 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
128
116
|
}
|
|
129
117
|
}
|
|
130
118
|
|
|
131
|
-
confirm(params:
|
|
119
|
+
confirm(params: Parameters<FrontendAPIInterface['confirm']>[0]): ReturnType<FrontendAPIInterface['confirm']> {
|
|
132
120
|
return new Promise((resolve, reject) => {
|
|
133
121
|
this.modalStore.setModalContent({
|
|
134
122
|
content: params.message,
|
|
@@ -142,7 +130,7 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
142
130
|
})
|
|
143
131
|
}
|
|
144
132
|
|
|
145
|
-
alert(params:
|
|
133
|
+
alert(params: Parameters<FrontendAPIInterface['alert']>[0]): ReturnType<FrontendAPIInterface['alert']> {
|
|
146
134
|
const toats = {
|
|
147
135
|
message: params.message,
|
|
148
136
|
messageHtml: params.messageHtml,
|
|
@@ -162,14 +150,14 @@ class FrontendAPI implements FrontendAPIInterface {
|
|
|
162
150
|
}
|
|
163
151
|
}
|
|
164
152
|
|
|
165
|
-
listFilterValidation(filter:
|
|
153
|
+
listFilterValidation(filter: Parameters<FrontendAPIInterface['list']['setFilter']>[0]): boolean {
|
|
166
154
|
if(router.currentRoute.value.meta.type !== 'list'){
|
|
167
155
|
throw new Error(`Cannot use ${this.setListFilter.name} filter on a list page`)
|
|
168
156
|
}
|
|
169
157
|
return true
|
|
170
158
|
}
|
|
171
159
|
|
|
172
|
-
setListFilter(filter:
|
|
160
|
+
setListFilter(filter: Parameters<FrontendAPIInterface['list']['setFilter']>[0]): ReturnType<FrontendAPIInterface['list']['setFilter']> {
|
|
173
161
|
if(this.listFilterValidation(filter)){
|
|
174
162
|
const existingFilterIndex = this.filtersStore.filters.findIndex((f: any) => {
|
|
175
163
|
return f.field === filter.field && f.operator === filter.operator
|
|
@@ -6,7 +6,13 @@
|
|
|
6
6
|
<slot name="trigger"></slot>
|
|
7
7
|
</div>
|
|
8
8
|
<Teleport to="body">
|
|
9
|
-
<div
|
|
9
|
+
<div
|
|
10
|
+
v-show="isModalOpen"
|
|
11
|
+
v-if="!removeFromDom"
|
|
12
|
+
@click="backdropClick"
|
|
13
|
+
class="bg-black/50 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full h-full md:inset-0 h-1rem max-h-full flex"
|
|
14
|
+
:class="props.backgroundCustomClasses"
|
|
15
|
+
>
|
|
10
16
|
<!-- Modal content -->
|
|
11
17
|
<div v-bind="$attrs" class="relative bg-lightDialogBackgorund rounded-lg shadow-sm dark:bg-darkDialogBackgorund">
|
|
12
18
|
|
|
@@ -19,6 +25,7 @@
|
|
|
19
25
|
<div
|
|
20
26
|
v-if="showConfirmationOnClose"
|
|
21
27
|
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-[60]"
|
|
28
|
+
:class="props.modalCustomClasses"
|
|
22
29
|
>
|
|
23
30
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
|
|
24
31
|
<h2 class="text-lg font-semibold mb-4 text-lightDialogHeaderText dark:text-darkDialogHeaderText">Confirm Close</h2>
|
|
@@ -64,6 +71,8 @@ interface DialogProps {
|
|
|
64
71
|
askForCloseConfirmation?: boolean
|
|
65
72
|
closeConfirmationText?: string
|
|
66
73
|
removeFromDomOnClose?: boolean
|
|
74
|
+
backgroundCustomClasses?: string
|
|
75
|
+
modalCustomClasses?: string
|
|
67
76
|
}
|
|
68
77
|
|
|
69
78
|
const props = withDefaults(defineProps<DialogProps>(), {
|
|
@@ -74,6 +83,8 @@ const props = withDefaults(defineProps<DialogProps>(), {
|
|
|
74
83
|
askForCloseConfirmation: false,
|
|
75
84
|
closeConfirmationText: 'Are you sure you want to close this dialog?',
|
|
76
85
|
removeFromDomOnClose: false,
|
|
86
|
+
backgroundCustomClasses: '',
|
|
87
|
+
modalCustomClasses: '',
|
|
77
88
|
})
|
|
78
89
|
|
|
79
90
|
const showConfirmationOnClose = ref(false);
|
|
@@ -119,10 +119,12 @@ import { ref, computed, onMounted, onUnmounted, watch, nextTick,type PropType, t
|
|
|
119
119
|
import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
|
|
120
120
|
import { useElementSize } from '@vueuse/core'
|
|
121
121
|
|
|
122
|
+
type ISingleSelectModelValue = string | number;
|
|
123
|
+
|
|
122
124
|
const props = defineProps({
|
|
123
125
|
options: Array,
|
|
124
126
|
modelValue: {
|
|
125
|
-
type: Array as PropType<(
|
|
127
|
+
type: Array as PropType<(ISingleSelectModelValue)[] | ISingleSelectModelValue>,
|
|
126
128
|
default: () => [],
|
|
127
129
|
},
|
|
128
130
|
multiple: {
|
|
@@ -201,7 +203,7 @@ function updateFromProps() {
|
|
|
201
203
|
selectedItems.value = [];
|
|
202
204
|
}
|
|
203
205
|
} else {
|
|
204
|
-
selectedItems.value = props.options?.filter((item: any) => props.modelValue?.includes(item.value)) || [];
|
|
206
|
+
selectedItems.value = props.options?.filter((item: any) => (props.modelValue as (ISingleSelectModelValue)[])?.includes(item.value)) || [];
|
|
205
207
|
}
|
|
206
208
|
}
|
|
207
209
|
}
|
|
@@ -91,12 +91,12 @@
|
|
|
91
91
|
<template #total><span class="font-semibold text-lightTablePaginationNumeration dark:text-darkTablePaginationNumeration">{{ dataResult.total }}</span></template>
|
|
92
92
|
</i18n-t>
|
|
93
93
|
<div class="af-pagination-container flex flex-row items-center xs:flex-row xs:justify-between xs:items-center gap-3">
|
|
94
|
-
<div class="inline-flex" :class="
|
|
94
|
+
<div class="inline-flex" :class="blockPagination ? 'pointer-events-none select-none opacity-50' : ''">
|
|
95
95
|
<!-- Buttons -->
|
|
96
96
|
<button
|
|
97
97
|
class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightActivePaginationButtonText bg-lightActivePaginationButtonBackground border-r-0 rounded-s hover:opacity-90 dark:bg-darkActivePaginationButtonBackground dark:text-darkActivePaginationButtonText disabled:opacity-50"
|
|
98
98
|
@click="currentPage--; pageInput = currentPage.toString();"
|
|
99
|
-
:disabled="currentPage <= 1 ||
|
|
99
|
+
:disabled="currentPage <= 1 || blockPagination">
|
|
100
100
|
<svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
101
101
|
viewBox="0 0 14 10">
|
|
102
102
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
<button
|
|
107
107
|
class="flex items-center py-1 px-3 text-sm font-medium text-lightUnactivePaginationButtonText focus:outline-none bg-lightUnactivePaginationButtonBackground border-r-0 border border-lightUnactivePaginationButtonBorder hover:bg-lightUnactivePaginationButtonHoverBackground hover:text-lightUnactivePaginationButtonHoverText dark:bg-darkUnactivePaginationButtonBackground dark:text-darkUnactivePaginationButtonText dark:border-darkUnactivePaginationButtonBorder dark:hover:text-darkUnactivePaginationButtonHoverText dark:hover:bg-darkUnactivePaginationButtonHoverBackground disabled:opacity-50"
|
|
108
108
|
@click="switchPage(1); pageInput = currentPage.toString();"
|
|
109
|
-
:disabled="currentPage <= 1 ||
|
|
109
|
+
:disabled="currentPage <= 1 || blockPagination">
|
|
110
110
|
<!-- <IconChevronDoubleLeftOutline class="w-4 h-4" /> -->
|
|
111
111
|
1
|
|
112
112
|
</button>
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
<button
|
|
124
124
|
class="flex items-center py-1 px-3 text-sm font-medium text-lightUnactivePaginationButtonText focus:outline-none bg-lightUnactivePaginationButtonBackground border-l-0 border border-lightUnactivePaginationButtonBorder hover:bg-lightUnactivePaginationButtonHoverBackground hover:text-lightUnactivePaginationButtonHoverText dark:bg-darkUnactivePaginationButtonBackground dark:text-darkUnactivePaginationButtonText dark:border-darkUnactivePaginationButtonBorder dark:hover:text-darkUnactivePaginationButtonHoverText dark:hover:bg-darkUnactivePaginationButtonHoverBackground disabled:opacity-50"
|
|
125
125
|
@click="currentPage = totalPages; pageInput = currentPage.toString();"
|
|
126
|
-
:disabled="currentPage >= totalPages ||
|
|
126
|
+
:disabled="currentPage >= totalPages || blockPagination"
|
|
127
127
|
>
|
|
128
128
|
{{ totalPages }}
|
|
129
129
|
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
<button
|
|
132
132
|
class="flex items-center py-1 px-3 gap-1 text-sm font-medium text-lightActivePaginationButtonText focus:outline-none bg-lightActivePaginationButtonBackground border-l-0 rounded-e hover:opacity-90 dark:bg-darkActivePaginationButtonBackground dark:text-darkActivePaginationButtonText disabled:opacity-50"
|
|
133
133
|
@click="currentPage++; pageInput = currentPage.toString();"
|
|
134
|
-
:disabled="currentPage >= totalPages ||
|
|
134
|
+
:disabled="currentPage >= totalPages || blockPagination"
|
|
135
135
|
>
|
|
136
136
|
<svg class="w-3.5 h-3.5 rtl:rotate-180" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
|
|
137
137
|
viewBox="0 0 14 10">
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
}[],
|
|
164
164
|
data: {
|
|
165
165
|
[key: string]: any,
|
|
166
|
-
}[] | ((params: { offset: number, limit: number, sortField?: string, sortDirection?: 'asc' | 'desc' }) => Promise<{data: {[key: string]: any}[], total: number}>),
|
|
166
|
+
}[] | ((params: { offset: number, limit: number, sortField?: string, sortDirection?: 'asc' | 'desc' }, abortSignal?: AbortSignal) => Promise<{data: {[key: string]: any}[], total: number}>),
|
|
167
167
|
evenHighlights?: boolean,
|
|
168
168
|
pageSize?: number,
|
|
169
169
|
isLoading?: boolean,
|
|
@@ -171,9 +171,11 @@
|
|
|
171
171
|
defaultSortDirection?: 'asc' | 'desc',
|
|
172
172
|
makeHeaderSticky?: boolean,
|
|
173
173
|
makePaginationSticky?: boolean,
|
|
174
|
+
blockPaginationOnLoading?: boolean,
|
|
174
175
|
}>(), {
|
|
175
176
|
evenHighlights: true,
|
|
176
177
|
pageSize: 5,
|
|
178
|
+
blockPaginationOnLoading: true,
|
|
177
179
|
}
|
|
178
180
|
);
|
|
179
181
|
|
|
@@ -188,6 +190,9 @@
|
|
|
188
190
|
const isAtLeastOneLoading = ref<boolean[]>([false]);
|
|
189
191
|
const currentSortField = ref<string | undefined>(props.defaultSortField);
|
|
190
192
|
const currentSortDirection = ref<'asc' | 'desc'>(props.defaultSortDirection ?? 'asc');
|
|
193
|
+
const oldAbortController = ref<AbortController | null>(null);
|
|
194
|
+
|
|
195
|
+
const blockPagination = computed(() => (isLoading.value || props.isLoading) && props.blockPaginationOnLoading);
|
|
191
196
|
|
|
192
197
|
onMounted(() => {
|
|
193
198
|
// If defaultSortField points to a non-sortable column, ignore it
|
|
@@ -277,16 +282,25 @@
|
|
|
277
282
|
isLoading.value = true;
|
|
278
283
|
const currentLoadingIndex = currentPage.value;
|
|
279
284
|
isAtLeastOneLoading.value[currentLoadingIndex] = true;
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
285
|
+
const abortController = new AbortController();
|
|
286
|
+
if (oldAbortController.value) {
|
|
287
|
+
oldAbortController.value.abort();
|
|
288
|
+
}
|
|
289
|
+
oldAbortController.value = abortController;
|
|
290
|
+
const result = await props.data(
|
|
291
|
+
{
|
|
292
|
+
offset: (currentLoadingIndex - 1) * props.pageSize,
|
|
293
|
+
limit: props.pageSize,
|
|
294
|
+
sortField: currentSortField.value,
|
|
295
|
+
...(currentSortField.value ? { sortDirection: currentSortDirection.value } : {}),
|
|
296
|
+
},
|
|
297
|
+
abortController.signal
|
|
298
|
+
);
|
|
286
299
|
isAtLeastOneLoading.value[currentLoadingIndex] = false;
|
|
287
300
|
if (isAtLeastOneLoading.value.every(v => v === false)) {
|
|
288
301
|
isLoading.value = false;
|
|
289
302
|
}
|
|
303
|
+
if(abortController.signal.aborted) return;
|
|
290
304
|
dataResult.value = result;
|
|
291
305
|
} else if (typeof props.data === 'object' && Array.isArray(props.data)) {
|
|
292
306
|
const start = (currentPage.value - 1) * props.pageSize;
|
|
@@ -350,7 +364,7 @@ function sortArrayData(data:any[], sortField?:string, dir:'asc'|'desc'='asc') {
|
|
|
350
364
|
});
|
|
351
365
|
}
|
|
352
366
|
|
|
353
|
-
function tableRowClick(row) {
|
|
367
|
+
function tableRowClick(row: any) {
|
|
354
368
|
emit("clickTableRow", row)
|
|
355
369
|
}
|
|
356
370
|
</script>
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
<Modal
|
|
4
4
|
ref="modalRef"
|
|
5
5
|
:beforeCloseFunction="()=>{modalStore.onAcceptFunction(false);modalStore.isOpened=false}"
|
|
6
|
+
backgroundCustomClasses="z-[998]"
|
|
7
|
+
modalCustomClasses="z-[999]"
|
|
6
8
|
>
|
|
7
9
|
<div class="relative p-4 w-full max-w-md max-h-full" >
|
|
8
10
|
<button type="button" @click="modalStore.togleModal()" class="absolute top-3 end-2.5 text-lightAcceptModalCloseIcon bg-transparent hover:bg-lightAcceptModalCloseIconHoverBackground hover:text-lightAcceptModalCloseIconHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkAcceptModalCloseIcon dark:hover:bg-darkAcceptModalCloseIconHoverBackground dark:hover:text-darkAcceptModalCloseIconHover" >
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
:unmasked="unmasked"
|
|
17
17
|
:deletable="!column.editReadonly"
|
|
18
18
|
@update:modelValue="setCurrentValue(column.name, $event, arrayItemIndex)"
|
|
19
|
-
@update:recordFieldValue="
|
|
19
|
+
@update:recordFieldValue="recordFieldValueUpdate"
|
|
20
20
|
@update:unmasked="$emit('update:unmasked', column.name)"
|
|
21
21
|
@update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
|
|
22
22
|
@update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
|
|
23
|
-
@delete="
|
|
23
|
+
@delete="deleteHandler(arrayItemIndex)"
|
|
24
24
|
/>
|
|
25
25
|
</div>
|
|
26
26
|
<div class="flex items-center">
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
:columnOptions="columnOptions"
|
|
49
49
|
:unmasked="unmasked"
|
|
50
50
|
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
51
|
-
@update:recordFieldValue="
|
|
51
|
+
@update:recordFieldValue="recordFieldValueUpdate"
|
|
52
52
|
@update:unmasked="$emit('update:unmasked', column.name)"
|
|
53
53
|
@update:inValidity="$emit('update:inValidity', { name: column.name, value: $event })"
|
|
54
54
|
@update:emptiness="$emit('update:emptiness', { name: column.name, value: $event })"
|
|
@@ -80,4 +80,12 @@
|
|
|
80
80
|
await nextTick();
|
|
81
81
|
arrayItemRefs.value[arrayItemRefs.value.length - 1].focus();
|
|
82
82
|
}
|
|
83
|
+
|
|
84
|
+
function recordFieldValueUpdate({ fieldName, fieldValue }: { fieldName: string; fieldValue: any }) {
|
|
85
|
+
props.setCurrentValue(fieldName, fieldValue);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function deleteHandler(arrayItemIndex: number | string) {
|
|
89
|
+
props.setCurrentValue(props.column.name, props.currentValues[props.column.name].filter((_: any, index: any) => index !== arrayItemIndex));
|
|
90
|
+
}
|
|
83
91
|
</script>
|
|
@@ -19,8 +19,7 @@
|
|
|
19
19
|
>
|
|
20
20
|
|
|
21
21
|
<div v-if="min && max" class="w-full px-2.5">
|
|
22
|
-
<
|
|
23
|
-
class="custom-slider"
|
|
22
|
+
<RangePicker
|
|
24
23
|
:dot-size="20"
|
|
25
24
|
height="7.99px"
|
|
26
25
|
:min="minFormatted"
|
|
@@ -32,10 +31,9 @@
|
|
|
32
31
|
</div>
|
|
33
32
|
</template>
|
|
34
33
|
<script setup lang="ts">
|
|
35
|
-
import VueSlider from 'vue-slider-component';
|
|
36
|
-
import 'vue-slider-component/theme/antd.css'
|
|
37
34
|
import {computed, onMounted, ref, watch} from "vue";
|
|
38
35
|
import debounce from 'debounce'
|
|
36
|
+
import RangePicker from './RangePicker.vue';
|
|
39
37
|
|
|
40
38
|
const props = defineProps({
|
|
41
39
|
valueStart: {
|
|
@@ -57,7 +55,19 @@ const maxFormatted = computed(() => Math.ceil(<number>props.max));
|
|
|
57
55
|
const start = ref<string | number>(props.valueStart);
|
|
58
56
|
const end = ref<string | number>(props.valueEnd);
|
|
59
57
|
|
|
60
|
-
const sliderValue = ref([minFormatted.value, maxFormatted.value]);
|
|
58
|
+
const sliderValue = ref<[number, number]>([minFormatted.value, maxFormatted.value]);
|
|
59
|
+
|
|
60
|
+
watch([start, end], () => {
|
|
61
|
+
if ( !start.value && end.value ) {
|
|
62
|
+
setSliderValues(minFormatted.value, end.value);
|
|
63
|
+
} else if ( start.value && !end.value ) {
|
|
64
|
+
setSliderValues(start.value, maxFormatted.value);
|
|
65
|
+
} else if ( !start.value && !end.value ) {
|
|
66
|
+
setSliderValues(minFormatted.value, maxFormatted.value);
|
|
67
|
+
} else {
|
|
68
|
+
setSliderValues(start.value, end.value);
|
|
69
|
+
}
|
|
70
|
+
})
|
|
61
71
|
|
|
62
72
|
const updateFromSlider =
|
|
63
73
|
debounce((value: [number, number]) => {
|
|
@@ -111,65 +121,4 @@ watch([minFormatted,maxFormatted], () => {
|
|
|
111
121
|
function setSliderValues(start: any, end: any) {
|
|
112
122
|
sliderValue.value = [start || minFormatted.value, end || maxFormatted.value];
|
|
113
123
|
}
|
|
114
|
-
</script>
|
|
115
|
-
|
|
116
|
-
<style lang="scss" scoped>
|
|
117
|
-
.custom-slider {
|
|
118
|
-
&:deep(.vue-slider-rail) {
|
|
119
|
-
background-color: rgb(229 231 235);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
&:deep(.vue-slider-dot-handle) {
|
|
123
|
-
// apply bg-blue-500 to the handle when active
|
|
124
|
-
@apply bg-lightPrimary;
|
|
125
|
-
border: none;
|
|
126
|
-
box-shadow: none;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
&:deep(.vue-slider-dot-handle:hover) {
|
|
130
|
-
@apply bg-lightPrimary;
|
|
131
|
-
filter: brightness(1.1);
|
|
132
|
-
border: none;
|
|
133
|
-
box-shadow: none;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
&:deep(.vue-slider-process) {
|
|
137
|
-
@apply bg-lightPrimaryOpacity;
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
&:deep(.vue-slider-process:hover) {
|
|
142
|
-
filter: brightness(1.1);
|
|
143
|
-
@apply bg-lightPrimaryOpacity;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.dark .custom-slider {
|
|
148
|
-
&:deep(.vue-slider-rail) {
|
|
149
|
-
background-color: rgb(55 65 81); // gray-700
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
&:deep(.vue-slider-dot-handle) {
|
|
153
|
-
@apply bg-darkPrimary;
|
|
154
|
-
border: none;
|
|
155
|
-
box-shadow: none;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
&:deep(.vue-slider-dot-handle:hover) {
|
|
159
|
-
@apply bg-darkPrimary;
|
|
160
|
-
filter: brightness(1.1);
|
|
161
|
-
border: none;
|
|
162
|
-
box-shadow: none;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
&:deep(.vue-slider-process) {
|
|
166
|
-
@apply bg-darkPrimaryOpacity;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
&:deep(.vue-slider-process:hover) {
|
|
170
|
-
filter: brightness(1.1);
|
|
171
|
-
@apply bg-darkPrimaryOpacity;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
</style>
|
|
124
|
+
</script>
|
|
@@ -57,14 +57,14 @@
|
|
|
57
57
|
{{ $t('Delete item') }}
|
|
58
58
|
</button>
|
|
59
59
|
</template>
|
|
60
|
-
<div v-for="action in (resourceOptions.actions ?? []).filter(a => a.showIn?.listThreeDotsMenu)" :key="action.id" >
|
|
60
|
+
<div v-for="action in (resourceOptions.actions ?? []).filter((a: AdminForthActionInput) => a.showIn?.listThreeDotsMenu)" :key="action.id" >
|
|
61
61
|
<button class="flex text-nowrap p-1 hover:bg-gray-100 dark:hover:bg-gray-800 w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300" @click="() => { startCustomAction(action.id, record); showMenu = false; }">
|
|
62
62
|
<component
|
|
63
63
|
:is="action.customComponent ? getCustomComponent(action.customComponent) : CallActionWrapper"
|
|
64
64
|
:meta="action.customComponent?.meta"
|
|
65
65
|
:row="record"
|
|
66
|
-
:resource="resource"
|
|
67
|
-
:adminUser="adminUser"
|
|
66
|
+
:resource="coreStore.resource"
|
|
67
|
+
:adminUser="coreStore.adminUser"
|
|
68
68
|
@callAction="(payload? : Object) => startCustomAction(action.id, record, payload)"
|
|
69
69
|
>
|
|
70
70
|
<component
|
|
@@ -79,8 +79,8 @@
|
|
|
79
79
|
<template v-if="customActionIconsThreeDotsMenuItems">
|
|
80
80
|
<component
|
|
81
81
|
v-for="c in customActionIconsThreeDotsMenuItems"
|
|
82
|
-
:is="getCustomComponent(c)"
|
|
83
|
-
:meta="c.meta"
|
|
82
|
+
:is="getCustomComponent(formatComponent(c))"
|
|
83
|
+
:meta="formatComponent(c).meta"
|
|
84
84
|
:resource="coreStore.resource"
|
|
85
85
|
:adminUser="coreStore.adminUser"
|
|
86
86
|
:record="record"
|
|
@@ -100,9 +100,10 @@ import {
|
|
|
100
100
|
IconDotsHorizontalOutline
|
|
101
101
|
} from '@iconify-prerendered/vue-flowbite';
|
|
102
102
|
import { onMounted, onBeforeUnmount, ref, nextTick, watch } from 'vue';
|
|
103
|
-
import { getIcon, getCustomComponent } from '@/utils';
|
|
103
|
+
import { getIcon, getCustomComponent, formatComponent } from '@/utils';
|
|
104
104
|
import { useCoreStore } from '@/stores/core';
|
|
105
105
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
106
|
+
import { type AdminForthActionInput, type AdminForthComponentDeclaration, type AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
106
107
|
|
|
107
108
|
const coreStore = useCoreStore();
|
|
108
109
|
const showMenu = ref(false);
|
|
@@ -113,11 +114,11 @@ const menuStyles = ref<Record<string, string>>({});
|
|
|
113
114
|
const props = defineProps<{
|
|
114
115
|
resourceOptions: any;
|
|
115
116
|
record: any;
|
|
116
|
-
customActionIconsThreeDotsMenuItems:
|
|
117
|
+
customActionIconsThreeDotsMenuItems: AdminForthComponentDeclaration[];
|
|
117
118
|
resourceId: string;
|
|
118
119
|
deleteRecord: (record: any) => void;
|
|
119
120
|
updateRecords: () => void;
|
|
120
|
-
startCustomAction: (actionId: string,
|
|
121
|
+
startCustomAction: (actionId: string, row: any, extraData?: Record<string, any>) => void;
|
|
121
122
|
}>();
|
|
122
123
|
|
|
123
124
|
onMounted(() => {
|