adminforth 1.0.69 → 1.0.70
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/auth.ts +4 -2
- package/dist/auth.js +5 -2
- package/dist/index.js +3 -0
- package/dist/spa/spa/src/App.vue +24 -29
- package/dist/spa/spa/src/components/CustomDatePicker.vue +16 -3
- package/dist/spa/spa/src/components/CustomDateRangePicker.vue +11 -2
- package/dist/spa/spa/src/components/Filters.vue +18 -1
- package/dist/spa/spa/src/components/ResourceForm.vue +1 -0
- package/dist/spa/spa/src/stores/core.ts +0 -2
- package/dist/spa/spa/src/utils.ts +19 -1
- package/dist/spa/spa/src/views/LoginView.vue +7 -2
- package/index.ts +3 -0
- package/package.json +1 -1
- package/spa/src/App.vue +24 -29
- package/spa/src/components/CustomDatePicker.vue +16 -3
- package/spa/src/components/CustomDateRangePicker.vue +11 -2
- package/spa/src/components/Filters.vue +18 -1
- package/spa/src/components/ResourceForm.vue +1 -0
- package/spa/src/stores/core.ts +0 -2
- package/spa/src/utils.ts +19 -1
- package/spa/src/views/LoginView.vue +7 -2
package/auth.ts
CHANGED
|
@@ -43,8 +43,10 @@ class AdminForthAuth {
|
|
|
43
43
|
const decoded = jwt.verify(jwtToken, secret);
|
|
44
44
|
return decoded;
|
|
45
45
|
} catch (err) {
|
|
46
|
-
if (err.name === '
|
|
47
|
-
console.error('Token expired', err.message);
|
|
46
|
+
if (err.name === 'TokenExpiredError') {
|
|
47
|
+
console.error('Token expired:', err.message);
|
|
48
|
+
} else if (err.name === 'JsonWebTokenError') {
|
|
49
|
+
console.error('Token error:', err.message);
|
|
48
50
|
} else {
|
|
49
51
|
console.error('Failed to verify JWT token', err);
|
|
50
52
|
}
|
package/dist/auth.js
CHANGED
|
@@ -46,8 +46,11 @@ class AdminForthAuth {
|
|
|
46
46
|
return decoded;
|
|
47
47
|
}
|
|
48
48
|
catch (err) {
|
|
49
|
-
if (err.name === '
|
|
50
|
-
console.error('Token expired', err.message);
|
|
49
|
+
if (err.name === 'TokenExpiredError') {
|
|
50
|
+
console.error('Token expired:', err.message);
|
|
51
|
+
}
|
|
52
|
+
else if (err.name === 'JsonWebTokenError') {
|
|
53
|
+
console.error('Token error:', err.message);
|
|
51
54
|
}
|
|
52
55
|
else {
|
|
53
56
|
console.error('Failed to verify JWT token', err);
|
package/dist/index.js
CHANGED
|
@@ -788,6 +788,9 @@ class AdminForth {
|
|
|
788
788
|
for (const recordField in record) {
|
|
789
789
|
if (record[recordField] !== oldRecord[recordField]) {
|
|
790
790
|
const column = resource.columns.find((col) => col.name === recordField);
|
|
791
|
+
if (column.virtual) {
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
791
794
|
if (column) {
|
|
792
795
|
newValues[recordField] = connector.setFieldValue(column, record[recordField]);
|
|
793
796
|
}
|
package/dist/spa/spa/src/App.vue
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
</svg>
|
|
91
91
|
</button>
|
|
92
92
|
|
|
93
|
-
<ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !
|
|
93
|
+
<ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
|
|
94
94
|
<template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
|
|
95
95
|
<li>
|
|
96
96
|
<MenuLink :item="child" isChild="true" />
|
|
@@ -147,6 +147,8 @@ import { useRoute, useRouter } from 'vue-router';
|
|
|
147
147
|
import { getIcon } from '@/utils';
|
|
148
148
|
import { useHead } from 'unhead'
|
|
149
149
|
import { createHead } from 'unhead'
|
|
150
|
+
import { loadFile } from './utils';
|
|
151
|
+
|
|
150
152
|
// import { link } from 'fs';
|
|
151
153
|
const coreStore = useCoreStore();
|
|
152
154
|
|
|
@@ -156,20 +158,6 @@ const splitAtLast = (str: string, separator: string) => {
|
|
|
156
158
|
return [str.slice(0, index), str.slice(index + 1)];
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
const loadFile = (file: string) => {
|
|
160
|
-
let path;
|
|
161
|
-
let baseUrl = '';
|
|
162
|
-
if (file.startsWith('@/')) {
|
|
163
|
-
path = file.replace('@/', '');
|
|
164
|
-
baseUrl = new URL(`./${path}`, import.meta.url).href;
|
|
165
|
-
} else if (file.startsWith('@@/')) {
|
|
166
|
-
path = file.replace('@@/', '');
|
|
167
|
-
baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
|
|
168
|
-
} else {
|
|
169
|
-
baseUrl = new URL(`./${file}`, import.meta.url).href;
|
|
170
|
-
}
|
|
171
|
-
return baseUrl;
|
|
172
|
-
}
|
|
173
161
|
|
|
174
162
|
|
|
175
163
|
createHead()
|
|
@@ -183,8 +171,8 @@ const route = useRoute();
|
|
|
183
171
|
watch(route, () => {
|
|
184
172
|
title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
|
|
185
173
|
useHead({
|
|
186
|
-
|
|
187
|
-
})
|
|
174
|
+
title: title.value,
|
|
175
|
+
})
|
|
188
176
|
});
|
|
189
177
|
|
|
190
178
|
const router = useRouter();
|
|
@@ -227,20 +215,27 @@ async function initRouter() {
|
|
|
227
215
|
routerIsReady.value = true;
|
|
228
216
|
}
|
|
229
217
|
|
|
218
|
+
async function loadMenu() {
|
|
219
|
+
await coreStore.fetchMenuAndResource()
|
|
220
|
+
loginRedirectCheckIsReady.value = true;
|
|
221
|
+
|
|
222
|
+
coreStore.menu.forEach((item, i) => {
|
|
223
|
+
if (item.open) {
|
|
224
|
+
opened.value.push(i);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
230
229
|
// initialize components based on data attribute selectors
|
|
231
230
|
onMounted(async () => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (item.open) {
|
|
241
|
-
opened.value.push(i);
|
|
242
|
-
}
|
|
243
|
-
});
|
|
231
|
+
loadMenu(); // run this in async mode
|
|
232
|
+
// before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
|
|
233
|
+
await initRouter()
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
initFlowbite();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
|
|
244
239
|
})
|
|
245
240
|
|
|
246
241
|
</script>
|
|
@@ -67,6 +67,9 @@ const props = defineProps({
|
|
|
67
67
|
},
|
|
68
68
|
label: {
|
|
69
69
|
type: String,
|
|
70
|
+
},
|
|
71
|
+
autoHide: {
|
|
72
|
+
type: Boolean,
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
|
|
@@ -100,8 +103,7 @@ function updateFromProps() {
|
|
|
100
103
|
if (!props.valueStart) {
|
|
101
104
|
datepickerStartEl.value.value = '';
|
|
102
105
|
startTime.value = '';
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
106
|
+
} else {
|
|
105
107
|
datepickerObject.value.setDate(dayjs(props.valueStart).format('DD MMM YYYY'));
|
|
106
108
|
startTime.value = dayjs(props.valueStart).format('HH:mm:ss')
|
|
107
109
|
}
|
|
@@ -121,7 +123,13 @@ watch(start, () => {
|
|
|
121
123
|
})
|
|
122
124
|
|
|
123
125
|
function initDatepickers() {
|
|
124
|
-
|
|
126
|
+
const options = {format: 'dd M yyyy'};
|
|
127
|
+
|
|
128
|
+
if (props.autoHide) {
|
|
129
|
+
options.autohide = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
datepickerObject.value = new Datepicker(datepickerStartEl.value, options);
|
|
125
133
|
|
|
126
134
|
addChangeDateListener();
|
|
127
135
|
}
|
|
@@ -134,6 +142,10 @@ function removeChangeDateListener() {
|
|
|
134
142
|
datepickerStartEl.value.removeEventListener('changeDate', setStartDate);
|
|
135
143
|
}
|
|
136
144
|
|
|
145
|
+
function destroyDatepickerElement() {
|
|
146
|
+
datepickerObject.value.destroy();
|
|
147
|
+
}
|
|
148
|
+
|
|
137
149
|
function setStartDate(event) {
|
|
138
150
|
startDate.value = event.detail.date
|
|
139
151
|
}
|
|
@@ -157,5 +169,6 @@ onMounted(() => {
|
|
|
157
169
|
|
|
158
170
|
onBeforeUnmount(() => {
|
|
159
171
|
removeChangeDateListener();
|
|
172
|
+
destroyDatepickerElement();
|
|
160
173
|
});
|
|
161
174
|
</script>
|
|
@@ -101,6 +101,9 @@ const endDate = ref('');
|
|
|
101
101
|
const startTime = ref('');
|
|
102
102
|
const endTime = ref('');
|
|
103
103
|
|
|
104
|
+
const datepickerStartObject = ref('')
|
|
105
|
+
const datepickerEndObject = ref('')
|
|
106
|
+
|
|
104
107
|
const start = computed(() => {
|
|
105
108
|
if (!startDate.value) {
|
|
106
109
|
return;
|
|
@@ -162,9 +165,9 @@ watch(end, () => {
|
|
|
162
165
|
|
|
163
166
|
function initDatepickers() {
|
|
164
167
|
|
|
165
|
-
new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
|
|
168
|
+
datepickerStartObject.value = new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
|
|
166
169
|
|
|
167
|
-
new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
|
|
170
|
+
datepickerEndObject.value = new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
|
|
168
171
|
addChangeDateListener();
|
|
169
172
|
}
|
|
170
173
|
|
|
@@ -178,6 +181,11 @@ function removeChangeDateListener() {
|
|
|
178
181
|
datepickerEndEl.value.removeEventListener('changeDate', setEndDate);
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
function destroyDatepickerElement() {
|
|
185
|
+
datepickerStartObject.value.destroy();
|
|
186
|
+
datepickerEndObject.value.destroy();
|
|
187
|
+
}
|
|
188
|
+
|
|
181
189
|
function setStartDate(event) {
|
|
182
190
|
startDate.value = event.detail.date
|
|
183
191
|
}
|
|
@@ -205,5 +213,6 @@ onMounted(() => {
|
|
|
205
213
|
|
|
206
214
|
onBeforeUnmount(() => {
|
|
207
215
|
removeChangeDateListener();
|
|
216
|
+
destroyDatepickerElement();
|
|
208
217
|
})
|
|
209
218
|
</script>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
>
|
|
70
70
|
|
|
71
71
|
<CustomRangePicker
|
|
72
|
-
v-else-if="['integer', 'decimal', 'float'].includes(c.type)"
|
|
72
|
+
v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
|
|
73
73
|
:min="c.min"
|
|
74
74
|
:max="c.max"
|
|
75
75
|
:valueStart="getFilterItem({ column: c, operator: 'gte' })"
|
|
@@ -77,6 +77,23 @@
|
|
|
77
77
|
:valueEnd="getFilterItem({ column: c, operator: 'lte' })"
|
|
78
78
|
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
|
|
79
79
|
/>
|
|
80
|
+
|
|
81
|
+
<div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
|
|
82
|
+
<input
|
|
83
|
+
type="number" aria-describedby="helper-text-explanation"
|
|
84
|
+
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
85
|
+
placeholder="From"
|
|
86
|
+
@input="setFilterItem({ column: c, operator: 'gte', value: $event.target.value || undefined })"
|
|
87
|
+
:value="getFilterItem({ column: c, operator: 'gte' })"
|
|
88
|
+
>
|
|
89
|
+
<input
|
|
90
|
+
type="number" aria-describedby="helper-text-explanation"
|
|
91
|
+
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
92
|
+
placeholder="To"
|
|
93
|
+
@input="setFilterItem({ column: c, operator: 'lte', value: $event.target.value || undefined})"
|
|
94
|
+
:value="getFilterItem({ column: c, operator: 'lte' })"
|
|
95
|
+
>
|
|
96
|
+
</div>
|
|
80
97
|
|
|
81
98
|
</li>
|
|
82
99
|
</ul>
|
|
@@ -12,7 +12,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
12
12
|
const resourceColumnsError = ref('');
|
|
13
13
|
const resourceColumnsId = ref(null);
|
|
14
14
|
const user = ref(null);
|
|
15
|
-
const flowBitIsInitialised = ref(false);
|
|
16
15
|
|
|
17
16
|
async function fetchMenuAndResource() {
|
|
18
17
|
const resp = await callAdminForthApi({
|
|
@@ -129,7 +128,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
129
128
|
resourceColumns,
|
|
130
129
|
fetchResourceFull,
|
|
131
130
|
resourceColumnsError,
|
|
132
|
-
flowBitIsInitialised,
|
|
133
131
|
resourceOptions,
|
|
134
132
|
logout
|
|
135
133
|
}
|
|
@@ -43,4 +43,22 @@ export function getIcon(icon: string) {
|
|
|
43
43
|
const [iconSet, iconName] = icon.split(':');
|
|
44
44
|
const compName = 'Icon' + iconName.split('-').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
45
45
|
return resolveComponent(compName);
|
|
46
|
-
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const loadFile = (file: string) => {
|
|
49
|
+
if (file.startsWith('http')) {
|
|
50
|
+
return file;
|
|
51
|
+
}
|
|
52
|
+
let path;
|
|
53
|
+
let baseUrl = '';
|
|
54
|
+
if (file.startsWith('@/')) {
|
|
55
|
+
path = file.replace('@/', '');
|
|
56
|
+
baseUrl = new URL(`./${path}`, import.meta.url).href;
|
|
57
|
+
} else if (file.startsWith('@@/')) {
|
|
58
|
+
path = file.replace('@@/', '');
|
|
59
|
+
baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
|
|
60
|
+
} else {
|
|
61
|
+
baseUrl = new URL(`./${file}`, import.meta.url).href;
|
|
62
|
+
}
|
|
63
|
+
return baseUrl;
|
|
64
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
|
|
3
3
|
:style="coreStore.config?.loginBackgroundImage ? {
|
|
4
|
-
'background-image': 'url(' + coreStore.config?.loginBackgroundImage + ')',
|
|
4
|
+
'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
|
|
5
5
|
'background-size': 'cover',
|
|
6
6
|
'background-position': 'center',
|
|
7
7
|
'background-blend-mode': 'darken'
|
|
@@ -81,8 +81,9 @@
|
|
|
81
81
|
import { onMounted, ref } from 'vue';
|
|
82
82
|
import { useCoreStore } from '@/stores/core';
|
|
83
83
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
84
|
-
import { callAdminForthApi } from '@/utils';
|
|
84
|
+
import { callAdminForthApi, loadFile } from '@/utils';
|
|
85
85
|
import { useRouter } from 'vue-router';
|
|
86
|
+
import { initFlowbite } from 'flowbite'
|
|
86
87
|
|
|
87
88
|
const router = useRouter();
|
|
88
89
|
const inProgress = ref(false);
|
|
@@ -114,6 +115,10 @@ async function login() {
|
|
|
114
115
|
error.value = null;
|
|
115
116
|
router.push('/');
|
|
116
117
|
await coreStore.fetchMenuAndResource();
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
initFlowbite();
|
|
120
|
+
});
|
|
121
|
+
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
}
|
package/index.ts
CHANGED
|
@@ -892,6 +892,9 @@ class AdminForth {
|
|
|
892
892
|
for (const recordField in record) {
|
|
893
893
|
if (record[recordField] !== oldRecord[recordField]) {
|
|
894
894
|
const column = resource.columns.find((col) => col.name === recordField);
|
|
895
|
+
if (column.virtual) {
|
|
896
|
+
continue
|
|
897
|
+
}
|
|
895
898
|
if (column) {
|
|
896
899
|
newValues[recordField] = connector.setFieldValue(column, record[recordField]);
|
|
897
900
|
} else {
|
package/package.json
CHANGED
package/spa/src/App.vue
CHANGED
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
</svg>
|
|
91
91
|
</button>
|
|
92
92
|
|
|
93
|
-
<ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !
|
|
93
|
+
<ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
|
|
94
94
|
<template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
|
|
95
95
|
<li>
|
|
96
96
|
<MenuLink :item="child" isChild="true" />
|
|
@@ -147,6 +147,8 @@ import { useRoute, useRouter } from 'vue-router';
|
|
|
147
147
|
import { getIcon } from '@/utils';
|
|
148
148
|
import { useHead } from 'unhead'
|
|
149
149
|
import { createHead } from 'unhead'
|
|
150
|
+
import { loadFile } from './utils';
|
|
151
|
+
|
|
150
152
|
// import { link } from 'fs';
|
|
151
153
|
const coreStore = useCoreStore();
|
|
152
154
|
|
|
@@ -156,20 +158,6 @@ const splitAtLast = (str: string, separator: string) => {
|
|
|
156
158
|
return [str.slice(0, index), str.slice(index + 1)];
|
|
157
159
|
}
|
|
158
160
|
|
|
159
|
-
const loadFile = (file: string) => {
|
|
160
|
-
let path;
|
|
161
|
-
let baseUrl = '';
|
|
162
|
-
if (file.startsWith('@/')) {
|
|
163
|
-
path = file.replace('@/', '');
|
|
164
|
-
baseUrl = new URL(`./${path}`, import.meta.url).href;
|
|
165
|
-
} else if (file.startsWith('@@/')) {
|
|
166
|
-
path = file.replace('@@/', '');
|
|
167
|
-
baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
|
|
168
|
-
} else {
|
|
169
|
-
baseUrl = new URL(`./${file}`, import.meta.url).href;
|
|
170
|
-
}
|
|
171
|
-
return baseUrl;
|
|
172
|
-
}
|
|
173
161
|
|
|
174
162
|
|
|
175
163
|
createHead()
|
|
@@ -183,8 +171,8 @@ const route = useRoute();
|
|
|
183
171
|
watch(route, () => {
|
|
184
172
|
title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
|
|
185
173
|
useHead({
|
|
186
|
-
|
|
187
|
-
})
|
|
174
|
+
title: title.value,
|
|
175
|
+
})
|
|
188
176
|
});
|
|
189
177
|
|
|
190
178
|
const router = useRouter();
|
|
@@ -227,20 +215,27 @@ async function initRouter() {
|
|
|
227
215
|
routerIsReady.value = true;
|
|
228
216
|
}
|
|
229
217
|
|
|
218
|
+
async function loadMenu() {
|
|
219
|
+
await coreStore.fetchMenuAndResource()
|
|
220
|
+
loginRedirectCheckIsReady.value = true;
|
|
221
|
+
|
|
222
|
+
coreStore.menu.forEach((item, i) => {
|
|
223
|
+
if (item.open) {
|
|
224
|
+
opened.value.push(i);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
230
229
|
// initialize components based on data attribute selectors
|
|
231
230
|
onMounted(async () => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (item.open) {
|
|
241
|
-
opened.value.push(i);
|
|
242
|
-
}
|
|
243
|
-
});
|
|
231
|
+
loadMenu(); // run this in async mode
|
|
232
|
+
// before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
|
|
233
|
+
await initRouter()
|
|
234
|
+
setTimeout(() => {
|
|
235
|
+
initFlowbite();
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
|
|
244
239
|
})
|
|
245
240
|
|
|
246
241
|
</script>
|
|
@@ -67,6 +67,9 @@ const props = defineProps({
|
|
|
67
67
|
},
|
|
68
68
|
label: {
|
|
69
69
|
type: String,
|
|
70
|
+
},
|
|
71
|
+
autoHide: {
|
|
72
|
+
type: Boolean,
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
|
|
@@ -100,8 +103,7 @@ function updateFromProps() {
|
|
|
100
103
|
if (!props.valueStart) {
|
|
101
104
|
datepickerStartEl.value.value = '';
|
|
102
105
|
startTime.value = '';
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
106
|
+
} else {
|
|
105
107
|
datepickerObject.value.setDate(dayjs(props.valueStart).format('DD MMM YYYY'));
|
|
106
108
|
startTime.value = dayjs(props.valueStart).format('HH:mm:ss')
|
|
107
109
|
}
|
|
@@ -121,7 +123,13 @@ watch(start, () => {
|
|
|
121
123
|
})
|
|
122
124
|
|
|
123
125
|
function initDatepickers() {
|
|
124
|
-
|
|
126
|
+
const options = {format: 'dd M yyyy'};
|
|
127
|
+
|
|
128
|
+
if (props.autoHide) {
|
|
129
|
+
options.autohide = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
datepickerObject.value = new Datepicker(datepickerStartEl.value, options);
|
|
125
133
|
|
|
126
134
|
addChangeDateListener();
|
|
127
135
|
}
|
|
@@ -134,6 +142,10 @@ function removeChangeDateListener() {
|
|
|
134
142
|
datepickerStartEl.value.removeEventListener('changeDate', setStartDate);
|
|
135
143
|
}
|
|
136
144
|
|
|
145
|
+
function destroyDatepickerElement() {
|
|
146
|
+
datepickerObject.value.destroy();
|
|
147
|
+
}
|
|
148
|
+
|
|
137
149
|
function setStartDate(event) {
|
|
138
150
|
startDate.value = event.detail.date
|
|
139
151
|
}
|
|
@@ -157,5 +169,6 @@ onMounted(() => {
|
|
|
157
169
|
|
|
158
170
|
onBeforeUnmount(() => {
|
|
159
171
|
removeChangeDateListener();
|
|
172
|
+
destroyDatepickerElement();
|
|
160
173
|
});
|
|
161
174
|
</script>
|
|
@@ -101,6 +101,9 @@ const endDate = ref('');
|
|
|
101
101
|
const startTime = ref('');
|
|
102
102
|
const endTime = ref('');
|
|
103
103
|
|
|
104
|
+
const datepickerStartObject = ref('')
|
|
105
|
+
const datepickerEndObject = ref('')
|
|
106
|
+
|
|
104
107
|
const start = computed(() => {
|
|
105
108
|
if (!startDate.value) {
|
|
106
109
|
return;
|
|
@@ -162,9 +165,9 @@ watch(end, () => {
|
|
|
162
165
|
|
|
163
166
|
function initDatepickers() {
|
|
164
167
|
|
|
165
|
-
new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
|
|
168
|
+
datepickerStartObject.value = new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
|
|
166
169
|
|
|
167
|
-
new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
|
|
170
|
+
datepickerEndObject.value = new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
|
|
168
171
|
addChangeDateListener();
|
|
169
172
|
}
|
|
170
173
|
|
|
@@ -178,6 +181,11 @@ function removeChangeDateListener() {
|
|
|
178
181
|
datepickerEndEl.value.removeEventListener('changeDate', setEndDate);
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
function destroyDatepickerElement() {
|
|
185
|
+
datepickerStartObject.value.destroy();
|
|
186
|
+
datepickerEndObject.value.destroy();
|
|
187
|
+
}
|
|
188
|
+
|
|
181
189
|
function setStartDate(event) {
|
|
182
190
|
startDate.value = event.detail.date
|
|
183
191
|
}
|
|
@@ -205,5 +213,6 @@ onMounted(() => {
|
|
|
205
213
|
|
|
206
214
|
onBeforeUnmount(() => {
|
|
207
215
|
removeChangeDateListener();
|
|
216
|
+
destroyDatepickerElement();
|
|
208
217
|
})
|
|
209
218
|
</script>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
>
|
|
70
70
|
|
|
71
71
|
<CustomRangePicker
|
|
72
|
-
v-else-if="['integer', 'decimal', 'float'].includes(c.type)"
|
|
72
|
+
v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
|
|
73
73
|
:min="c.min"
|
|
74
74
|
:max="c.max"
|
|
75
75
|
:valueStart="getFilterItem({ column: c, operator: 'gte' })"
|
|
@@ -77,6 +77,23 @@
|
|
|
77
77
|
:valueEnd="getFilterItem({ column: c, operator: 'lte' })"
|
|
78
78
|
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
|
|
79
79
|
/>
|
|
80
|
+
|
|
81
|
+
<div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
|
|
82
|
+
<input
|
|
83
|
+
type="number" aria-describedby="helper-text-explanation"
|
|
84
|
+
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
85
|
+
placeholder="From"
|
|
86
|
+
@input="setFilterItem({ column: c, operator: 'gte', value: $event.target.value || undefined })"
|
|
87
|
+
:value="getFilterItem({ column: c, operator: 'gte' })"
|
|
88
|
+
>
|
|
89
|
+
<input
|
|
90
|
+
type="number" aria-describedby="helper-text-explanation"
|
|
91
|
+
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-20 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
|
92
|
+
placeholder="To"
|
|
93
|
+
@input="setFilterItem({ column: c, operator: 'lte', value: $event.target.value || undefined})"
|
|
94
|
+
:value="getFilterItem({ column: c, operator: 'lte' })"
|
|
95
|
+
>
|
|
96
|
+
</div>
|
|
80
97
|
|
|
81
98
|
</li>
|
|
82
99
|
</ul>
|
package/spa/src/stores/core.ts
CHANGED
|
@@ -12,7 +12,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
12
12
|
const resourceColumnsError = ref('');
|
|
13
13
|
const resourceColumnsId = ref(null);
|
|
14
14
|
const user = ref(null);
|
|
15
|
-
const flowBitIsInitialised = ref(false);
|
|
16
15
|
|
|
17
16
|
async function fetchMenuAndResource() {
|
|
18
17
|
const resp = await callAdminForthApi({
|
|
@@ -129,7 +128,6 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
129
128
|
resourceColumns,
|
|
130
129
|
fetchResourceFull,
|
|
131
130
|
resourceColumnsError,
|
|
132
|
-
flowBitIsInitialised,
|
|
133
131
|
resourceOptions,
|
|
134
132
|
logout
|
|
135
133
|
}
|
package/spa/src/utils.ts
CHANGED
|
@@ -43,4 +43,22 @@ export function getIcon(icon: string) {
|
|
|
43
43
|
const [iconSet, iconName] = icon.split(':');
|
|
44
44
|
const compName = 'Icon' + iconName.split('-').map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('');
|
|
45
45
|
return resolveComponent(compName);
|
|
46
|
-
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const loadFile = (file: string) => {
|
|
49
|
+
if (file.startsWith('http')) {
|
|
50
|
+
return file;
|
|
51
|
+
}
|
|
52
|
+
let path;
|
|
53
|
+
let baseUrl = '';
|
|
54
|
+
if (file.startsWith('@/')) {
|
|
55
|
+
path = file.replace('@/', '');
|
|
56
|
+
baseUrl = new URL(`./${path}`, import.meta.url).href;
|
|
57
|
+
} else if (file.startsWith('@@/')) {
|
|
58
|
+
path = file.replace('@@/', '');
|
|
59
|
+
baseUrl = new URL(`./custom/${path}`, import.meta.url).href;
|
|
60
|
+
} else {
|
|
61
|
+
baseUrl = new URL(`./${file}`, import.meta.url).href;
|
|
62
|
+
}
|
|
63
|
+
return baseUrl;
|
|
64
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="relative flex items-center justify-center min-h-screen bg-gray-100 dark:bg-gray-800"
|
|
3
3
|
:style="coreStore.config?.loginBackgroundImage ? {
|
|
4
|
-
'background-image': 'url(' + coreStore.config?.loginBackgroundImage + ')',
|
|
4
|
+
'background-image': 'url(' + loadFile(coreStore.config?.loginBackgroundImage) + ')',
|
|
5
5
|
'background-size': 'cover',
|
|
6
6
|
'background-position': 'center',
|
|
7
7
|
'background-blend-mode': 'darken'
|
|
@@ -81,8 +81,9 @@
|
|
|
81
81
|
import { onMounted, ref } from 'vue';
|
|
82
82
|
import { useCoreStore } from '@/stores/core';
|
|
83
83
|
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
84
|
-
import { callAdminForthApi } from '@/utils';
|
|
84
|
+
import { callAdminForthApi, loadFile } from '@/utils';
|
|
85
85
|
import { useRouter } from 'vue-router';
|
|
86
|
+
import { initFlowbite } from 'flowbite'
|
|
86
87
|
|
|
87
88
|
const router = useRouter();
|
|
88
89
|
const inProgress = ref(false);
|
|
@@ -114,6 +115,10 @@ async function login() {
|
|
|
114
115
|
error.value = null;
|
|
115
116
|
router.push('/');
|
|
116
117
|
await coreStore.fetchMenuAndResource();
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
initFlowbite();
|
|
120
|
+
});
|
|
121
|
+
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
}
|