adminforth 1.1.70 → 1.1.71
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 +2 -1
- package/dist/auth.js +2 -1
- package/dist/spa/index.html +4 -4
- package/dist/spa/package-lock.json +206 -5
- package/dist/spa/package.json +8 -2
- package/dist/spa/public/assets/favicon.png +0 -0
- package/dist/spa/spa/src/views/LoginView.vue +2 -0
- package/dist/spa/src/App.vue +171 -72
- package/dist/spa/src/assets/logo.svg +19 -1
- package/dist/spa/src/components/AcceptModal.vue +3 -10
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +19 -6
- package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
- package/dist/spa/src/components/CustomRangePicker.vue +148 -0
- package/dist/spa/src/components/Dropdown.vue +19 -5
- package/dist/spa/src/components/Filters.vue +91 -37
- package/dist/spa/src/components/MenuLink.vue +2 -2
- package/dist/spa/src/components/ResourceForm.vue +163 -103
- package/dist/spa/src/components/ResourceListTable.vue +419 -0
- package/dist/spa/src/components/Toast.vue +66 -0
- package/dist/spa/src/components/ValueRenderer.vue +22 -6
- package/dist/spa/src/composables/useFrontendApi.ts +26 -0
- package/dist/spa/src/composables/useStores.ts +113 -0
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +30 -19
- package/dist/spa/src/spa_types/core.ts +51 -0
- package/dist/spa/src/stores/core.ts +65 -59
- package/dist/spa/src/stores/filters.ts +22 -0
- package/dist/spa/src/stores/modal.ts +13 -3
- package/dist/spa/src/stores/toast.ts +15 -0
- package/dist/spa/src/stores/user.ts +54 -0
- package/dist/spa/src/utils.ts +65 -8
- package/dist/spa/src/views/CreateView.vue +76 -16
- package/dist/spa/src/views/EditView.vue +76 -14
- package/dist/spa/src/views/ListView.vue +88 -360
- package/dist/spa/src/views/LoginView.vue +17 -5
- package/dist/spa/src/views/ResourceParent.vue +1 -1
- package/dist/spa/src/views/ShowView.vue +114 -19
- package/dist/spa/tailwind.config.js +5 -2
- package/dist/spa/vite.config.ts +6 -0
- package/package.json +1 -1
- package/spa/src/views/LoginView.vue +2 -0
- package/dist/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/src/views/HomeView.vue +0 -8
- package/dist/spa/src/views/HomeView.vue +0 -8
- package/dist/types.js +0 -30
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div class="rounded-default">
|
|
3
3
|
|
|
4
4
|
<div
|
|
5
|
-
class="relative shadow-
|
|
5
|
+
class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black rounded-default"
|
|
6
6
|
>
|
|
7
7
|
<form autocomplete="off" @submit.prevent>
|
|
8
|
-
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
|
|
9
|
-
<thead class="text-xs text-gray-700 uppercase bg-
|
|
8
|
+
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 ">
|
|
9
|
+
<thead class="text-xs text-gray-700 uppercase bg-form-view-heading dark:bg-gray-700 dark:text-gray-400">
|
|
10
10
|
<tr>
|
|
11
11
|
<th scope="col" class="px-6 py-3">
|
|
12
12
|
Field
|
|
@@ -18,97 +18,114 @@
|
|
|
18
18
|
</thead>
|
|
19
19
|
<tbody>
|
|
20
20
|
<tr v-for="column, i in editableColumns" :key="column.name"
|
|
21
|
-
class="
|
|
21
|
+
class="bg-form-view-bg dark:bg-gray-800 border-b dark:border-gray-700"
|
|
22
22
|
>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
<!-- if column is in customComponentsPerColumn, use this component. If not, use this code -->
|
|
24
|
+
<template v-if="customComponentsPerColumn[column.name]">
|
|
25
|
+
<component
|
|
26
|
+
:is="customComponentsPerColumn[column.name]"
|
|
27
|
+
:column="column"
|
|
28
|
+
:value="currentValues[column.name]"
|
|
29
|
+
@update:value="setCurrentValue(column.name, $event)"
|
|
28
30
|
/>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
31
|
+
</template>
|
|
32
|
+
<template v-else>
|
|
33
|
+
<td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
|
|
34
|
+
{{ column.label }}
|
|
35
|
+
<span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
|
|
36
|
+
<IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
|
|
37
|
+
:class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
|
|
38
|
+
/>
|
|
39
|
+
</span>
|
|
40
|
+
<div :id="`tooltip-show-${i}`"
|
|
41
|
+
role="tooltip" class="absolute z-10 invisible inline-block px-3 py-2 text-sm font-medium text-white transition-opacity duration-300 bg-gray-900 rounded-lg shadow-sm opacity-0 tooltip dark:bg-gray-700">
|
|
42
|
+
Required field
|
|
43
|
+
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
44
|
+
</div>
|
|
45
|
+
</td>
|
|
46
|
+
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
|
|
47
|
+
<Dropdown
|
|
48
|
+
single
|
|
49
|
+
v-if="column.foreignResource"
|
|
50
|
+
:options="columnOptions[column.name] || []"
|
|
51
|
+
:placeholder = "columnOptions[column.name]?.length || 'There are no options available'"
|
|
52
|
+
:modelValue="currentValues[column.name]"
|
|
53
|
+
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
54
|
+
></Dropdown>
|
|
55
|
+
<Dropdown
|
|
56
|
+
single
|
|
57
|
+
v-else-if="column.enum"
|
|
58
|
+
:options="column.enum"
|
|
59
|
+
:modelValue="currentValues[column.name]"
|
|
60
|
+
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
61
|
+
/>
|
|
62
|
+
<Dropdown
|
|
63
|
+
single
|
|
64
|
+
v-else-if="column.type === 'boolean'"
|
|
65
|
+
:options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
|
|
66
|
+
:modelValue="currentValues[column.name]"
|
|
67
|
+
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
68
|
+
/>
|
|
69
|
+
<input
|
|
70
|
+
v-else-if="['integer'].includes(column.type)"
|
|
71
|
+
type="number"
|
|
72
|
+
step="1"
|
|
73
|
+
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-40 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"
|
|
74
|
+
placeholder="0"
|
|
75
|
+
:value="currentValues[column.name]"
|
|
76
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
77
|
+
>
|
|
78
|
+
<CustomDatePicker
|
|
79
|
+
v-else-if="['datetime'].includes(column.type)"
|
|
80
|
+
:column="column"
|
|
81
|
+
:valueStart="currentValues[column.name]"
|
|
82
|
+
auto-hide
|
|
83
|
+
@update:valueStart="setCurrentValue(column.name, $event)"
|
|
84
|
+
/>
|
|
85
|
+
<input
|
|
86
|
+
v-else-if="['decimal', 'float'].includes(column.type)"
|
|
87
|
+
type="number"
|
|
88
|
+
step="0.1"
|
|
89
|
+
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-40 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"
|
|
90
|
+
placeholder="0.0"
|
|
91
|
+
:value="currentValues[column.name]"
|
|
92
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
93
|
+
/>
|
|
94
|
+
<textarea
|
|
95
|
+
v-else-if="['text'].includes(column.type)"
|
|
96
|
+
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-full 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"
|
|
97
|
+
placeholder="Text"
|
|
98
|
+
:value="currentValues[column.name]"
|
|
99
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
100
|
+
>
|
|
101
|
+
</textarea>
|
|
102
|
+
<input
|
|
103
|
+
v-else
|
|
104
|
+
:type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
|
|
105
|
+
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-full 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"
|
|
106
|
+
placeholder="Text"
|
|
107
|
+
:value="currentValues[column.name]"
|
|
108
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
109
|
+
autocomplete="false"
|
|
110
|
+
data-lpignore="true"
|
|
111
|
+
readonly
|
|
112
|
+
onfocus="this.removeAttribute('readonly');"
|
|
113
|
+
>
|
|
114
|
+
|
|
115
|
+
<button
|
|
116
|
+
v-if="column.masked"
|
|
117
|
+
type="button"
|
|
118
|
+
@click="unmasked[column.name] = !unmasked[column.name]"
|
|
119
|
+
class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
|
|
120
|
+
>
|
|
121
|
+
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
|
|
122
|
+
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
123
|
+
</button>
|
|
124
|
+
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
|
|
125
|
+
|
|
126
|
+
<div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
|
|
127
|
+
</td>
|
|
128
|
+
</template>
|
|
112
129
|
</tr>
|
|
113
130
|
|
|
114
131
|
</tbody>
|
|
@@ -121,19 +138,24 @@
|
|
|
121
138
|
|
|
122
139
|
<script setup>
|
|
123
140
|
|
|
124
|
-
import { ref, computed, onMounted, watch } from 'vue';
|
|
125
|
-
import { useCoreStore } from '@/stores/core';
|
|
126
|
-
import Dropdown from '@/components/Dropdown.vue';
|
|
127
|
-
import { IconExclamationCircleSolid } from '@iconify-prerendered/vue-flowbite';
|
|
128
|
-
import { initFlowbite } from 'flowbite'
|
|
129
|
-
import { IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
|
|
130
141
|
import CustomDatePicker from "@/components/CustomDatePicker.vue";
|
|
142
|
+
import Dropdown from '@/components/Dropdown.vue';
|
|
143
|
+
import { useCoreStore } from '@/stores/core';
|
|
144
|
+
import { callAdminForthApi } from '@/utils';
|
|
145
|
+
import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
|
|
146
|
+
import { computedAsync } from '@vueuse/core';
|
|
147
|
+
import { initFlowbite } from 'flowbite';
|
|
148
|
+
import { computed, onMounted, ref, watch } from 'vue';
|
|
149
|
+
import { useRouter } from 'vue-router';
|
|
150
|
+
|
|
151
|
+
const router = useRouter();
|
|
131
152
|
|
|
132
153
|
const props = defineProps({
|
|
133
154
|
loading: Boolean,
|
|
134
|
-
|
|
155
|
+
resource: Object,
|
|
135
156
|
record: Object,
|
|
136
157
|
validating: Boolean,
|
|
158
|
+
customComponentsPerColumn: Object,
|
|
137
159
|
});
|
|
138
160
|
|
|
139
161
|
|
|
@@ -145,6 +167,7 @@ const emit = defineEmits(['update:record', 'update:isValid']);
|
|
|
145
167
|
|
|
146
168
|
const currentValues = ref({});
|
|
147
169
|
|
|
170
|
+
|
|
148
171
|
const columnError = (column) => {
|
|
149
172
|
const val = computed(() => {
|
|
150
173
|
if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
|
|
@@ -166,14 +189,28 @@ const columnError = (column) => {
|
|
|
166
189
|
return `This field must be less than ${column.maxValue}`;
|
|
167
190
|
}
|
|
168
191
|
}
|
|
192
|
+
if ( column.validation && column.validation.length ){
|
|
193
|
+
const validationArray = column.validation;
|
|
194
|
+
for (let i = 0; i < validationArray.length; i++) {
|
|
195
|
+
if (validationArray[i].regExp) {
|
|
196
|
+
const regExp = new RegExp(validationArray[i].regExp);
|
|
197
|
+
let value = currentValues.value[column.name];
|
|
198
|
+
if (value === undefined || value === null) {
|
|
199
|
+
value = '';
|
|
200
|
+
}
|
|
201
|
+
if (!regExp.test(value)) {
|
|
202
|
+
return validationArray[i].message;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
}
|
|
169
208
|
return null;
|
|
170
209
|
});
|
|
171
210
|
return val.value;
|
|
172
211
|
};
|
|
173
212
|
|
|
174
213
|
|
|
175
|
-
|
|
176
|
-
|
|
177
214
|
const setCurrentValue = (key, value) => {
|
|
178
215
|
currentValues.value[key] = value;
|
|
179
216
|
|
|
@@ -185,13 +222,36 @@ onMounted(() => {
|
|
|
185
222
|
currentValues.value[key] = props.record[key];
|
|
186
223
|
});
|
|
187
224
|
initFlowbite();
|
|
225
|
+
|
|
226
|
+
|
|
188
227
|
});
|
|
189
228
|
|
|
229
|
+
const columnOptions = computedAsync(async () => {
|
|
230
|
+
return (await Promise.all(
|
|
231
|
+
Object.values(props.resource.columns).map(async (column) => {
|
|
232
|
+
if (column.foreignResource) {
|
|
233
|
+
const list = await callAdminForthApi({
|
|
234
|
+
method: 'POST',
|
|
235
|
+
path: `/get_resource_foreign_data`,
|
|
236
|
+
body: {
|
|
237
|
+
resourceId: router.currentRoute.value.params.resourceId,
|
|
238
|
+
column: column.name,
|
|
239
|
+
limit: 1000,
|
|
240
|
+
offset: 0,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
return { [column.name]: list.items };
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
)).reduce((acc, val) => Object.assign(acc, val), {})
|
|
247
|
+
|
|
248
|
+
}, {});
|
|
249
|
+
|
|
190
250
|
const coreStore = useCoreStore();
|
|
191
251
|
|
|
192
252
|
const editableColumns = computed(() => {
|
|
193
253
|
const mode = props.record ? 'edit' : 'create';
|
|
194
|
-
return props.
|
|
254
|
+
return props.resource?.columns?.filter(column => column.showIn.includes(mode));
|
|
195
255
|
});
|
|
196
256
|
|
|
197
257
|
const isValid = computed(() => {
|