adminforth 1.2.56 → 1.2.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +215 -1
- package/dist/spa/package.json +2 -0
- package/dist/spa/spa/src/types/AdminForthConfig.ts +1477 -0
- package/dist/spa/spa/src/types/FrontendAPI.ts +121 -0
- package/dist/spa/src/App.vue +176 -133
- package/dist/spa/src/assets/base.css +2 -0
- package/dist/spa/src/components/Breadcrumbs.vue +3 -2
- package/dist/spa/src/components/CustomDatePicker.vue +6 -4
- package/dist/spa/src/components/CustomRangePicker.vue +11 -7
- package/dist/spa/src/components/Filters.vue +16 -5
- package/dist/spa/src/components/MenuLink.vue +3 -3
- package/dist/spa/src/components/ResourceForm.vue +140 -111
- package/dist/spa/src/components/ResourceListTable.vue +76 -101
- package/dist/spa/src/components/SkeleteLoader.vue +23 -0
- package/dist/spa/src/components/Toast.vue +18 -7
- package/dist/spa/src/components/ValueRenderer.vue +10 -4
- package/dist/spa/src/composables/useStores.ts +25 -11
- package/dist/spa/src/index.scss +2 -1
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +0 -1
- package/dist/spa/src/stores/user.ts +21 -3
- package/dist/spa/src/types/AdminForthConfig.ts +1477 -0
- package/dist/spa/src/types/FrontendAPI.ts +121 -0
- package/dist/spa/src/utils.ts +6 -4
- package/dist/spa/src/views/CreateView.vue +2 -9
- package/dist/spa/src/views/EditView.vue +1 -9
- package/dist/spa/src/views/ListView.vue +17 -16
- package/dist/spa/src/views/LoginView.vue +57 -31
- package/dist/spa/src/views/ResourceParent.vue +1 -2
- package/dist/spa/src/views/ShowView.vue +17 -21
- package/dist/spa/tailwind.config.js +3 -1
- package/dist/spa/vite.config.ts +7 -0
- package/package.json +3 -4
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<!-- drawer component -->
|
|
3
3
|
<div id="drawer-navigation"
|
|
4
4
|
|
|
5
|
-
class="fixed right-0 z-50 p-4 overflow-y-auto transition-transform translate-x-full bg-white w-80 dark:bg-gray-800 shadow-xl dark:shadow-
|
|
5
|
+
class="fixed right-0 z-50 p-4 overflow-y-auto transition-transform translate-x-full bg-white w-80 dark:bg-gray-800 shadow-xl dark:shadow-gray-900"
|
|
6
6
|
|
|
7
7
|
:class="show ? 'top-0 transform-none' : ''"
|
|
8
8
|
tabindex="-1" aria-labelledby="drawer-navigation-label"
|
|
@@ -70,8 +70,8 @@
|
|
|
70
70
|
|
|
71
71
|
<CustomRangePicker
|
|
72
72
|
v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
|
|
73
|
-
:min="c.
|
|
74
|
-
:max="c.
|
|
73
|
+
:min="getFilterMinValue(c.name)"
|
|
74
|
+
:max="getFilterMaxValue(c.name)"
|
|
75
75
|
:valueStart="getFilterItem({ column: c, operator: 'gte' })"
|
|
76
76
|
@update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
|
|
77
77
|
:valueEnd="getFilterItem({ column: c, operator: 'lte' })"
|
|
@@ -189,8 +189,6 @@ function setFilterItem({ column, operator, value }) {
|
|
|
189
189
|
if (index !== -1) {
|
|
190
190
|
filtersStore.filters.splice(index, 1);
|
|
191
191
|
}
|
|
192
|
-
emits('update:filters', [...filtersStore.filters]);
|
|
193
|
-
return;
|
|
194
192
|
} else {
|
|
195
193
|
if (index === -1) {
|
|
196
194
|
filtersStore.setFilter({ field: column.name, value, operator });
|
|
@@ -207,5 +205,18 @@ function getFilterItem({ column, operator }) {
|
|
|
207
205
|
|
|
208
206
|
async function clear() {
|
|
209
207
|
filtersStore.clearFilters();
|
|
208
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getFilterMinValue(columnName) {
|
|
212
|
+
if(props.columnsMinMax && props.columnsMinMax[columnName]) {
|
|
213
|
+
return props.columnsMinMax[columnName]?.min
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getFilterMaxValue(columnName) {
|
|
218
|
+
if(props.columnsMinMax && props.columnsMinMax[columnName]) {
|
|
219
|
+
return props.columnsMinMax[columnName]?.max
|
|
220
|
+
}
|
|
210
221
|
}
|
|
211
222
|
</script>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<RouterLink
|
|
3
3
|
:to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
|
|
4
|
-
class="flex items-center py-2 text-
|
|
4
|
+
class="flex group items-center py-2 text-lightSidebarText dark:text-darkSidebarText rounded-default hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover active:bg-lightSidebarActive dark:active:bg-darkSidebarHover" role="menuitem"
|
|
5
5
|
:class="{
|
|
6
6
|
'px-4': isChild,
|
|
7
7
|
'px-2': !isChild,
|
|
8
|
-
'bg-
|
|
8
|
+
'bg-lightSidebarItemActive dark:bg-darkSidebarItemActive': item.resourceId ?
|
|
9
9
|
($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
|
|
10
10
|
($route.name === item.path)
|
|
11
11
|
}"
|
|
12
12
|
>
|
|
13
|
-
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-
|
|
13
|
+
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons dark:text-darkSidebarIcons transition duration-75 group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover" ></component>
|
|
14
14
|
<span class="ms-3">{{ item.label }}</span>
|
|
15
15
|
</RouterLink>
|
|
16
16
|
</template>
|
|
@@ -1,131 +1,138 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="rounded-default">
|
|
3
|
-
|
|
4
3
|
<div
|
|
5
|
-
class="relative shadow-
|
|
4
|
+
class="relative shadow-resourseFormShadow dark:shadow-darkResourseFormShadow sm:rounded-lg dark:shadow-2xl rounded-default"
|
|
6
5
|
>
|
|
7
6
|
<form autocomplete="off" @submit.prevent>
|
|
8
7
|
<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
|
+
<thead class="text-xs text-gray-700 uppercase bg-lightFormHeading dark:bg-gray-700 dark:text-gray-400 block md:table-row-group">
|
|
10
9
|
<tr>
|
|
11
|
-
<th scope="col" class="px-6 py-3">
|
|
10
|
+
<th scope="col" class="px-6 py-3 hidden md:table-cell">
|
|
12
11
|
Field
|
|
13
12
|
</th>
|
|
14
|
-
<th scope="col" class="px-6 py-3 w-
|
|
13
|
+
<th scope="col" class="px-6 py-3 w-5/6 hidden md:table-cell">
|
|
15
14
|
Value
|
|
16
15
|
</th>
|
|
16
|
+
|
|
17
|
+
|
|
17
18
|
</tr>
|
|
18
19
|
</thead>
|
|
19
20
|
<tbody>
|
|
20
21
|
<tr v-for="column, i in editableColumns" :key="column.name"
|
|
21
|
-
|
|
22
|
+
v-if="currentValues !== null"
|
|
23
|
+
class="bg-ligftForm dark:bg-gray-800 border-b dark:border-gray-700 block md:table-row"
|
|
22
24
|
>
|
|
23
|
-
|
|
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)"
|
|
30
|
-
/>
|
|
31
|
-
</template>
|
|
32
|
-
<template v-else>
|
|
33
|
-
<td class="px-6 py-4 whitespace-nowrap "> <!--align-top-->
|
|
25
|
+
<td class="px-6 py-4 sm:pb-0 whitespace-nowrap flex items-center block md:table-cell"> <!--align-top-->
|
|
34
26
|
{{ column.label }}
|
|
35
|
-
<span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
|
|
27
|
+
<span :data-tooltip-target="`tooltip-show-${i}`" class="ml-1 relative inline-block">
|
|
36
28
|
<IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
|
|
37
29
|
:class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
|
|
38
30
|
/>
|
|
39
31
|
</span>
|
|
40
32
|
<div :id="`tooltip-show-${i}`"
|
|
41
|
-
role="tooltip"
|
|
33
|
+
role="tooltip"
|
|
34
|
+
class="ml-1 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
35
|
Required field
|
|
43
36
|
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
44
37
|
</div>
|
|
45
38
|
</td>
|
|
46
|
-
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
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)"
|
|
39
|
+
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative block md:table-cell">
|
|
40
|
+
<template v-if="column?.components?.[props.source]?.file">
|
|
41
|
+
<component
|
|
42
|
+
:is="getCustomComponent(column.components[props.source])"
|
|
80
43
|
: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
44
|
:value="currentValues[column.name]"
|
|
108
|
-
@
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
45
|
+
@update:value="setCurrentValue(column.name, $event)"
|
|
46
|
+
:meta="column.components[props.source].meta"
|
|
47
|
+
:record="currentValues"
|
|
48
|
+
@update:inValidity="customComponentsInValidity[column.name] = $event"
|
|
49
|
+
@update:emptiness="customComponentsEmptiness[column.name] = $event"
|
|
50
|
+
/>
|
|
51
|
+
</template>
|
|
52
|
+
<template v-else>
|
|
53
|
+
<Dropdown
|
|
54
|
+
single
|
|
55
|
+
v-if="column.foreignResource"
|
|
56
|
+
:options="columnOptions[column.name] || []"
|
|
57
|
+
:placeholder = "columnOptions[column.name]?.length ?'Select...': 'There are no options available'"
|
|
58
|
+
:modelValue="currentValues[column.name]"
|
|
59
|
+
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
60
|
+
></Dropdown>
|
|
61
|
+
<Dropdown
|
|
62
|
+
single
|
|
63
|
+
v-else-if="column.enum"
|
|
64
|
+
:options="column.enum"
|
|
65
|
+
:modelValue="currentValues[column.name]"
|
|
66
|
+
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
67
|
+
/>
|
|
68
|
+
<Dropdown
|
|
69
|
+
single
|
|
70
|
+
v-else-if="column.type === 'boolean'"
|
|
71
|
+
:options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
|
|
72
|
+
:modelValue="currentValues[column.name]"
|
|
73
|
+
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
74
|
+
/>
|
|
75
|
+
<input
|
|
76
|
+
v-else-if="['integer'].includes(column.type)"
|
|
77
|
+
type="number"
|
|
78
|
+
step="1"
|
|
79
|
+
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"
|
|
80
|
+
placeholder="0"
|
|
81
|
+
:value="currentValues[column.name]"
|
|
82
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
83
|
+
>
|
|
84
|
+
<CustomDatePicker
|
|
85
|
+
v-else-if="['datetime'].includes(column.type)"
|
|
86
|
+
:column="column"
|
|
87
|
+
:valueStart="currentValues[column.name]"
|
|
88
|
+
auto-hide
|
|
89
|
+
@update:valueStart="setCurrentValue(column.name, $event)"
|
|
90
|
+
/>
|
|
91
|
+
<input
|
|
92
|
+
v-else-if="['decimal', 'float'].includes(column.type)"
|
|
93
|
+
type="number"
|
|
94
|
+
step="0.1"
|
|
95
|
+
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"
|
|
96
|
+
placeholder="0.0"
|
|
97
|
+
:value="currentValues[column.name]"
|
|
98
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
99
|
+
/>
|
|
100
|
+
<textarea
|
|
101
|
+
v-else-if="['text', 'richtext'].includes(column.type)"
|
|
102
|
+
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"
|
|
103
|
+
placeholder="Text"
|
|
104
|
+
:value="currentValues[column.name]"
|
|
105
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
106
|
+
>
|
|
107
|
+
</textarea>
|
|
108
|
+
<input
|
|
109
|
+
v-else
|
|
110
|
+
:type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
|
|
111
|
+
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"
|
|
112
|
+
placeholder="Text"
|
|
113
|
+
:value="currentValues[column.name]"
|
|
114
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
115
|
+
autocomplete="false"
|
|
116
|
+
data-lpignore="true"
|
|
117
|
+
readonly
|
|
118
|
+
onfocus="this.removeAttribute('readonly');"
|
|
119
|
+
>
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
<button
|
|
122
|
+
v-if="column.masked"
|
|
123
|
+
type="button"
|
|
124
|
+
@click="unmasked[column.name] = !unmasked[column.name]"
|
|
125
|
+
class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
|
|
126
|
+
>
|
|
127
|
+
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
|
|
128
|
+
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
129
|
+
</button>
|
|
130
|
+
</template>
|
|
124
131
|
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
|
|
125
132
|
|
|
126
133
|
<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
|
-
|
|
128
|
-
</
|
|
134
|
+
|
|
135
|
+
</td>
|
|
129
136
|
</tr>
|
|
130
137
|
|
|
131
138
|
</tbody>
|
|
@@ -141,7 +148,7 @@
|
|
|
141
148
|
import CustomDatePicker from "@/components/CustomDatePicker.vue";
|
|
142
149
|
import Dropdown from '@/components/Dropdown.vue';
|
|
143
150
|
import { useCoreStore } from '@/stores/core';
|
|
144
|
-
import { callAdminForthApi } from '@/utils';
|
|
151
|
+
import { callAdminForthApi, getCustomComponent } from '@/utils';
|
|
145
152
|
import { IconExclamationCircleSolid, IconEyeSlashSolid, IconEyeSolid } from '@iconify-prerendered/vue-flowbite';
|
|
146
153
|
import { computedAsync } from '@vueuse/core';
|
|
147
154
|
import { initFlowbite } from 'flowbite';
|
|
@@ -155,22 +162,38 @@ const props = defineProps({
|
|
|
155
162
|
resource: Object,
|
|
156
163
|
record: Object,
|
|
157
164
|
validating: Boolean,
|
|
158
|
-
|
|
165
|
+
source: String,
|
|
159
166
|
});
|
|
160
167
|
|
|
161
|
-
|
|
162
168
|
const unmasked = ref({});
|
|
163
169
|
|
|
164
170
|
const mode = computed(() => props.record && Object.keys(props.record).length ? 'edit' : 'create');
|
|
165
171
|
|
|
166
172
|
const emit = defineEmits(['update:record', 'update:isValid']);
|
|
167
173
|
|
|
168
|
-
const currentValues = ref(
|
|
174
|
+
const currentValues = ref(null);
|
|
175
|
+
|
|
176
|
+
const customComponentsInValidity = ref({});
|
|
177
|
+
const customComponentsEmptiness = ref({});
|
|
169
178
|
|
|
170
179
|
|
|
171
180
|
const columnError = (column) => {
|
|
172
181
|
const val = computed(() => {
|
|
173
|
-
if (
|
|
182
|
+
if (!currentValues.value) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
if (customComponentsInValidity.value[column.name]) {
|
|
186
|
+
return customComponentsInValidity.value[column.name];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (
|
|
190
|
+
column.required[mode.value] &&
|
|
191
|
+
(currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') &&
|
|
192
|
+
// if component is custum it might tell other criteria for emptiness by emitting 'update:emptiness'
|
|
193
|
+
// components which do not emit 'update:emptiness' will have undefined value in customComponentsEmptiness
|
|
194
|
+
(customComponentsEmptiness.value[column.name] !== false)
|
|
195
|
+
|
|
196
|
+
) {
|
|
174
197
|
return 'This field is required';
|
|
175
198
|
}
|
|
176
199
|
if ( column.type === 'string' || column.type === 'text' ) {
|
|
@@ -182,14 +205,17 @@ const columnError = (column) => {
|
|
|
182
205
|
}
|
|
183
206
|
}
|
|
184
207
|
if ( ['integer', 'decimal', 'float'].includes(column.type) ) {
|
|
185
|
-
if ( column.minValue !== undefined
|
|
208
|
+
if ( column.minValue !== undefined
|
|
209
|
+
&& currentValues.value[column.name] !== null
|
|
210
|
+
&& currentValues.value[column.name] < column.minValue
|
|
211
|
+
) {
|
|
186
212
|
return `This field must be greater than ${column.minValue}`;
|
|
187
213
|
}
|
|
188
214
|
if ( column.maxValue !== undefined && currentValues.value[column.name] > column.maxValue ) {
|
|
189
215
|
return `This field must be less than ${column.maxValue}`;
|
|
190
216
|
}
|
|
191
217
|
}
|
|
192
|
-
if ( column.validation && column.validation.length ){
|
|
218
|
+
if ( column.validation && column.validation.length ) {
|
|
193
219
|
const validationArray = column.validation;
|
|
194
220
|
for (let i = 0; i < validationArray.length; i++) {
|
|
195
221
|
if (validationArray[i].regExp) {
|
|
@@ -212,18 +238,22 @@ const columnError = (column) => {
|
|
|
212
238
|
|
|
213
239
|
|
|
214
240
|
const setCurrentValue = (key, value) => {
|
|
215
|
-
|
|
216
|
-
|
|
241
|
+
const col = props.resource.columns.find((column) => column.name === key);
|
|
242
|
+
if (['integer', 'float'].includes(col.type) && (value || value === 0)) {
|
|
243
|
+
currentValues.value[key] = +value;
|
|
244
|
+
} else {
|
|
245
|
+
currentValues.value[key] = value;
|
|
246
|
+
}
|
|
247
|
+
currentValues.value = { ...currentValues.value };
|
|
248
|
+
console.log('3️⃣ setCurrentValue', key, value);
|
|
217
249
|
emit('update:record', currentValues.value);
|
|
218
250
|
};
|
|
219
251
|
|
|
220
252
|
onMounted(() => {
|
|
221
|
-
Object.
|
|
222
|
-
|
|
223
|
-
});
|
|
253
|
+
currentValues.value = Object.assign({}, props.record);
|
|
254
|
+
console.log('2️⃣ currentValues', JSON.stringify(currentValues.value));
|
|
224
255
|
initFlowbite();
|
|
225
|
-
|
|
226
|
-
|
|
256
|
+
emit('update:isValid', isValid.value);
|
|
227
257
|
});
|
|
228
258
|
|
|
229
259
|
const columnOptions = computedAsync(async () => {
|
|
@@ -247,7 +277,6 @@ const columnOptions = computedAsync(async () => {
|
|
|
247
277
|
|
|
248
278
|
}, {});
|
|
249
279
|
|
|
250
|
-
const coreStore = useCoreStore();
|
|
251
280
|
|
|
252
281
|
const editableColumns = computed(() => {
|
|
253
282
|
const mode = props.record ? 'edit' : 'create';
|