adminforth 1.2.87 → 1.2.88
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/modules/restApi.js +9 -0
- package/dist/spa/.vscode/extensions.json +6 -0
- package/dist/spa/index.html +4 -4
- package/dist/spa/package-lock.json +6 -421
- package/dist/spa/package.json +2 -10
- package/dist/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/.eslintrc.cjs +14 -0
- package/dist/spa/spa/.vscode/extensions.json +6 -0
- package/dist/spa/spa/README.md +39 -0
- package/dist/spa/spa/env.d.ts +1 -0
- package/dist/spa/spa/index.html +23 -0
- package/dist/spa/spa/package-lock.json +4573 -0
- package/dist/spa/spa/package.json +49 -0
- package/dist/spa/spa/postcss.config.js +6 -0
- package/dist/spa/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/src/App.vue +314 -0
- package/dist/spa/spa/src/assets/base.css +0 -0
- package/dist/spa/spa/src/assets/logo.svg +19 -0
- package/dist/spa/spa/src/components/AcceptModal.vue +45 -0
- package/dist/spa/spa/src/components/Breadcrumbs.vue +40 -0
- package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +26 -0
- package/dist/spa/spa/src/components/CustomDatePicker.vue +174 -0
- package/dist/spa/spa/src/components/CustomDateRangePicker.vue +218 -0
- package/dist/spa/spa/src/components/Dropdown.vue +168 -0
- package/dist/spa/spa/src/components/Filters.vue +222 -0
- package/dist/spa/spa/src/components/HelloWorld.vue +17 -0
- package/dist/spa/spa/src/components/MenuLink.vue +24 -0
- package/dist/spa/spa/src/components/ResourceForm.vue +289 -0
- package/dist/spa/{src → spa/src}/components/ResourceListTable.vue +3 -6
- package/dist/spa/spa/src/components/SingleSkeletLoader.vue +13 -0
- package/dist/spa/{src → spa/src}/components/Toast.vue +3 -9
- package/dist/spa/spa/src/components/ValueRenderer.vue +66 -0
- package/dist/spa/spa/src/components/icons/IconCalendar.vue +5 -0
- package/dist/spa/spa/src/components/icons/IconCommunity.vue +7 -0
- package/dist/spa/spa/src/components/icons/IconDocumentation.vue +7 -0
- package/dist/spa/spa/src/components/icons/IconEcosystem.vue +7 -0
- package/dist/spa/spa/src/components/icons/IconSupport.vue +7 -0
- package/dist/spa/spa/src/components/icons/IconTime.vue +5 -0
- package/dist/spa/spa/src/components/icons/IconTooling.vue +19 -0
- package/dist/spa/spa/src/index.scss +27 -0
- package/dist/spa/spa/src/main.ts +18 -0
- package/dist/spa/spa/src/router/index.ts +63 -0
- package/dist/spa/spa/src/stores/core.ts +144 -0
- package/dist/spa/spa/src/stores/modal.ts +48 -0
- package/dist/spa/spa/src/utils.ts +103 -0
- package/dist/spa/spa/src/views/CreateView.vue +156 -0
- package/dist/spa/spa/src/views/EditView.vue +157 -0
- package/dist/spa/spa/src/views/HomeView.vue +8 -0
- package/dist/spa/spa/src/views/ListView.vue +258 -0
- package/dist/spa/spa/src/views/LoginView.vue +143 -0
- package/dist/spa/spa/src/views/ResourceParent.vue +17 -0
- package/dist/spa/spa/src/views/ShowView.vue +184 -0
- package/dist/spa/spa/tailwind.config.js +17 -0
- package/dist/spa/spa/tsconfig.app.json +14 -0
- package/dist/spa/spa/tsconfig.json +11 -0
- package/dist/spa/spa/tsconfig.node.json +19 -0
- package/dist/spa/spa/vite.config.ts +56 -0
- package/dist/spa/src/App.vue +94 -236
- package/dist/spa/src/assets/base.css +0 -2
- package/dist/spa/src/assets/logo.svg +1 -19
- package/dist/spa/src/components/AcceptModal.vue +10 -3
- package/dist/spa/src/components/Breadcrumbs.vue +2 -3
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +10 -25
- package/dist/spa/src/components/CustomDateRangePicker.vue +5 -14
- package/dist/spa/src/components/Dropdown.vue +5 -19
- package/dist/spa/src/components/Filters.vue +38 -103
- package/dist/spa/src/components/MenuLink.vue +3 -3
- package/dist/spa/src/components/ResourceForm.vue +116 -205
- package/dist/spa/src/components/ValueRenderer.vue +8 -30
- package/dist/spa/src/index.scss +1 -2
- package/dist/spa/src/main.ts +2 -2
- package/dist/spa/src/router/index.ts +19 -29
- package/dist/spa/src/stores/core.ts +59 -65
- package/dist/spa/src/stores/modal.ts +3 -13
- package/dist/spa/src/utils.ts +10 -69
- package/dist/spa/src/views/CreateView.vue +16 -69
- package/dist/spa/src/views/EditView.vue +14 -68
- package/dist/spa/src/views/HomeView.vue +8 -0
- package/dist/spa/src/views/ListView.vue +371 -100
- package/dist/spa/src/views/LoginView.vue +29 -67
- package/dist/spa/src/views/ResourceParent.vue +2 -1
- package/dist/spa/src/views/ShowView.vue +25 -116
- package/dist/spa/tailwind.config.js +3 -8
- package/dist/spa/vite.config.ts +0 -13
- package/dist/types.js +30 -0
- package/modules/restApi.ts +14 -1
- package/package.json +1 -1
- package/spa/src/components/MenuLink.vue +3 -0
- package/types/AdminForthConfig.ts +6 -0
- package/dist/spa/src/types/AdminForthConfig.ts +0 -1477
- package/dist/spa/src/types/FrontendAPI.ts +0 -121
- /package/dist/spa/{public → spa/public}/assets/favicon.png +0 -0
- /package/dist/spa/{src → spa/src}/components/CustomRangePicker.vue +0 -0
- /package/dist/spa/{src → spa/src}/components/SkeleteLoader.vue +0 -0
- /package/dist/spa/{src → spa/src}/composables/useFrontendApi.ts +0 -0
- /package/dist/spa/{src → spa/src}/composables/useStores.ts +0 -0
- /package/dist/spa/{src → spa/src}/spa_types/core.ts +0 -0
- /package/dist/spa/{src → spa/src}/stores/filters.ts +0 -0
- /package/dist/spa/{src → spa/src}/stores/toast.ts +0 -0
- /package/dist/spa/{src → spa/src}/stores/user.ts +0 -0
|
@@ -1,137 +1,113 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div>
|
|
3
|
+
|
|
3
4
|
<div
|
|
4
|
-
class="relative shadow-
|
|
5
|
+
class="relative shadow-md sm:rounded-lg dark:shadow-2xl"
|
|
5
6
|
>
|
|
6
7
|
<form autocomplete="off" @submit.prevent>
|
|
7
|
-
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400
|
|
8
|
-
<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-gray-50 dark:bg-gray-700 dark:text-gray-400">
|
|
9
10
|
<tr>
|
|
10
|
-
<th scope="col" class="px-6 py-3
|
|
11
|
+
<th scope="col" class="px-6 py-3">
|
|
11
12
|
Field
|
|
12
13
|
</th>
|
|
13
|
-
<th scope="col" class="px-6 py-3 w-
|
|
14
|
+
<th scope="col" class="px-6 py-3 w-4/6">
|
|
14
15
|
Value
|
|
15
16
|
</th>
|
|
16
|
-
|
|
17
|
-
|
|
18
17
|
</tr>
|
|
19
18
|
</thead>
|
|
20
19
|
<tbody>
|
|
21
20
|
<tr v-for="column, i in editableColumns" :key="column.name"
|
|
22
|
-
|
|
23
|
-
class="bg-ligftForm dark:bg-gray-800 border-b dark:border-gray-700 block md:table-row"
|
|
21
|
+
class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700"
|
|
24
22
|
>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
>
|
|
120
|
-
|
|
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>
|
|
131
|
-
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
|
|
132
|
-
|
|
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>
|
|
134
|
-
|
|
23
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
|
24
|
+
{{ column.label }}
|
|
25
|
+
<span :data-tooltip-target="`tooltip-show-${i}`" class="relative inline-block">
|
|
26
|
+
<IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
|
|
27
|
+
:class="(columnError(column) && validating) ? 'text-red-500 dark:text-red-400' : 'text-gray-400 dark:text-gray-500'"
|
|
28
|
+
/>
|
|
29
|
+
</span>
|
|
30
|
+
<div :id="`tooltip-show-${i}`"
|
|
31
|
+
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">
|
|
32
|
+
Required field
|
|
33
|
+
<div class="tooltip-arrow" data-popper-arrow></div>
|
|
34
|
+
</div>
|
|
35
|
+
</td>
|
|
36
|
+
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
|
|
37
|
+
<Dropdown
|
|
38
|
+
single
|
|
39
|
+
v-if="column.enum"
|
|
40
|
+
:options="column.enum"
|
|
41
|
+
:modelValue="currentValues[column.name]"
|
|
42
|
+
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
43
|
+
/>
|
|
44
|
+
<Dropdown
|
|
45
|
+
single
|
|
46
|
+
v-else-if="column.type === 'boolean'"
|
|
47
|
+
:options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
|
|
48
|
+
:modelValue="currentValues[column.name]"
|
|
49
|
+
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
50
|
+
/>
|
|
51
|
+
<input
|
|
52
|
+
v-else-if="['integer'].includes(column.type)"
|
|
53
|
+
type="number"
|
|
54
|
+
step="1"
|
|
55
|
+
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"
|
|
56
|
+
placeholder="0"
|
|
57
|
+
:value="currentValues[column.name]"
|
|
58
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
59
|
+
>
|
|
60
|
+
<CustomDatePicker
|
|
61
|
+
v-else-if="['datetime'].includes(column.type)"
|
|
62
|
+
:column="column"
|
|
63
|
+
:valueStart="currentValues[column.name]"
|
|
64
|
+
@update:valueStart="setCurrentValue(column.name, $event)"
|
|
65
|
+
/>
|
|
66
|
+
<input
|
|
67
|
+
v-else-if="['decimal', 'float'].includes(column.type)"
|
|
68
|
+
type="number"
|
|
69
|
+
step="0.1"
|
|
70
|
+
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"
|
|
71
|
+
placeholder="0.0"
|
|
72
|
+
:value="currentValues[column.name]"
|
|
73
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
74
|
+
/>
|
|
75
|
+
<textarea
|
|
76
|
+
v-else-if="['text'].includes(column.type)"
|
|
77
|
+
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"
|
|
78
|
+
placeholder="Text"
|
|
79
|
+
:value="currentValues[column.name]"
|
|
80
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
81
|
+
>
|
|
82
|
+
</textarea>
|
|
83
|
+
<input
|
|
84
|
+
v-else
|
|
85
|
+
:type="!column.masked || unmasked[column.name] ? 'text' : 'password'"
|
|
86
|
+
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"
|
|
87
|
+
placeholder="Text"
|
|
88
|
+
:value="currentValues[column.name]"
|
|
89
|
+
@input="setCurrentValue(column.name, $event.target.value)"
|
|
90
|
+
autocomplete="false"
|
|
91
|
+
data-lpignore="true"
|
|
92
|
+
readonly
|
|
93
|
+
onfocus="this.removeAttribute('readonly');"
|
|
94
|
+
>
|
|
95
|
+
|
|
96
|
+
<button
|
|
97
|
+
v-if="column.masked"
|
|
98
|
+
type="button"
|
|
99
|
+
@click="unmasked[column.name] = !unmasked[column.name]"
|
|
100
|
+
class="h-6 absolute inset-y-2 top-6 right-6 flex items-center pr-2 z-index-100 focus:outline-none"
|
|
101
|
+
>
|
|
102
|
+
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
|
|
103
|
+
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
104
|
+
</button>
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
|
|
109
|
+
|
|
110
|
+
<div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-gray-400 dark:text-gray-500">{{ column.editingNote[mode] }}</div>
|
|
135
111
|
</td>
|
|
136
112
|
</tr>
|
|
137
113
|
|
|
@@ -145,55 +121,33 @@
|
|
|
145
121
|
|
|
146
122
|
<script setup>
|
|
147
123
|
|
|
148
|
-
import
|
|
149
|
-
import Dropdown from '@/components/Dropdown.vue';
|
|
124
|
+
import { ref, computed, onMounted, watch } from 'vue';
|
|
150
125
|
import { useCoreStore } from '@/stores/core';
|
|
151
|
-
import
|
|
152
|
-
import { IconExclamationCircleSolid
|
|
153
|
-
import {
|
|
154
|
-
import {
|
|
155
|
-
import
|
|
156
|
-
import { useRouter } from 'vue-router';
|
|
157
|
-
|
|
158
|
-
const router = useRouter();
|
|
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
|
+
import CustomDatePicker from "@/components/CustomDatePicker.vue";
|
|
159
131
|
|
|
160
132
|
const props = defineProps({
|
|
161
133
|
loading: Boolean,
|
|
162
|
-
|
|
134
|
+
resourceColumns: Object,
|
|
163
135
|
record: Object,
|
|
164
136
|
validating: Boolean,
|
|
165
|
-
source: String,
|
|
166
137
|
});
|
|
167
138
|
|
|
139
|
+
|
|
168
140
|
const unmasked = ref({});
|
|
169
141
|
|
|
170
142
|
const mode = computed(() => props.record && Object.keys(props.record).length ? 'edit' : 'create');
|
|
171
143
|
|
|
172
144
|
const emit = defineEmits(['update:record', 'update:isValid']);
|
|
173
145
|
|
|
174
|
-
const currentValues = ref(
|
|
175
|
-
|
|
176
|
-
const customComponentsInValidity = ref({});
|
|
177
|
-
const customComponentsEmptiness = ref({});
|
|
178
|
-
|
|
146
|
+
const currentValues = ref({});
|
|
179
147
|
|
|
180
148
|
const columnError = (column) => {
|
|
181
149
|
const val = computed(() => {
|
|
182
|
-
if (
|
|
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
|
-
) {
|
|
150
|
+
if ( column.required[mode.value] && (currentValues.value[column.name] === undefined || currentValues.value[column.name] === null || currentValues.value[column.name] === '') ) {
|
|
197
151
|
return 'This field is required';
|
|
198
152
|
}
|
|
199
153
|
if ( column.type === 'string' || column.type === 'text' ) {
|
|
@@ -205,82 +159,39 @@ const columnError = (column) => {
|
|
|
205
159
|
}
|
|
206
160
|
}
|
|
207
161
|
if ( ['integer', 'decimal', 'float'].includes(column.type) ) {
|
|
208
|
-
if ( column.minValue !== undefined
|
|
209
|
-
&& currentValues.value[column.name] !== null
|
|
210
|
-
&& currentValues.value[column.name] < column.minValue
|
|
211
|
-
) {
|
|
162
|
+
if ( column.minValue !== undefined && currentValues.value[column.name] < column.minValue ) {
|
|
212
163
|
return `This field must be greater than ${column.minValue}`;
|
|
213
164
|
}
|
|
214
165
|
if ( column.maxValue !== undefined && currentValues.value[column.name] > column.maxValue ) {
|
|
215
166
|
return `This field must be less than ${column.maxValue}`;
|
|
216
167
|
}
|
|
217
168
|
}
|
|
218
|
-
if ( column.validation && column.validation.length ) {
|
|
219
|
-
const validationArray = column.validation;
|
|
220
|
-
for (let i = 0; i < validationArray.length; i++) {
|
|
221
|
-
if (validationArray[i].regExp) {
|
|
222
|
-
const regExp = new RegExp(validationArray[i].regExp);
|
|
223
|
-
let value = currentValues.value[column.name];
|
|
224
|
-
if (value === undefined || value === null) {
|
|
225
|
-
value = '';
|
|
226
|
-
}
|
|
227
|
-
if (!regExp.test(value)) {
|
|
228
|
-
return validationArray[i].message;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
}
|
|
234
169
|
return null;
|
|
235
170
|
});
|
|
236
171
|
return val.value;
|
|
237
172
|
};
|
|
238
173
|
|
|
239
174
|
|
|
175
|
+
|
|
176
|
+
|
|
240
177
|
const setCurrentValue = (key, value) => {
|
|
241
|
-
|
|
242
|
-
|
|
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);
|
|
178
|
+
currentValues.value[key] = value;
|
|
179
|
+
|
|
249
180
|
emit('update:record', currentValues.value);
|
|
250
181
|
};
|
|
251
182
|
|
|
252
183
|
onMounted(() => {
|
|
253
|
-
|
|
254
|
-
|
|
184
|
+
Object.keys(props.record).forEach((key) => {
|
|
185
|
+
currentValues.value[key] = props.record[key];
|
|
186
|
+
});
|
|
255
187
|
initFlowbite();
|
|
256
|
-
emit('update:isValid', isValid.value);
|
|
257
188
|
});
|
|
258
189
|
|
|
259
|
-
const
|
|
260
|
-
return (await Promise.all(
|
|
261
|
-
Object.values(props.resource.columns).map(async (column) => {
|
|
262
|
-
if (column.foreignResource) {
|
|
263
|
-
const list = await callAdminForthApi({
|
|
264
|
-
method: 'POST',
|
|
265
|
-
path: `/get_resource_foreign_data`,
|
|
266
|
-
body: {
|
|
267
|
-
resourceId: router.currentRoute.value.params.resourceId,
|
|
268
|
-
column: column.name,
|
|
269
|
-
limit: 1000,
|
|
270
|
-
offset: 0,
|
|
271
|
-
},
|
|
272
|
-
});
|
|
273
|
-
return { [column.name]: list.items };
|
|
274
|
-
}
|
|
275
|
-
})
|
|
276
|
-
)).reduce((acc, val) => Object.assign(acc, val), {})
|
|
277
|
-
|
|
278
|
-
}, {});
|
|
279
|
-
|
|
190
|
+
const coreStore = useCoreStore();
|
|
280
191
|
|
|
281
192
|
const editableColumns = computed(() => {
|
|
282
193
|
const mode = props.record ? 'edit' : 'create';
|
|
283
|
-
return props.
|
|
194
|
+
return props.resourceColumns?.filter(column => column.showIn.includes(mode));
|
|
284
195
|
});
|
|
285
196
|
|
|
286
197
|
const isValid = computed(() => {
|
|
@@ -1,30 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<span
|
|
4
|
-
<
|
|
5
|
-
:to="{ name: 'resource-show', params: { resourceId: column.foreignResource.resourceId, primaryKey: record[column.name].pk } }">
|
|
6
|
-
{{ record[column.name].label }}
|
|
7
|
-
</RouterLink>
|
|
8
|
-
<div v-else>
|
|
9
|
-
<span class="text-gray-400">-</span>
|
|
10
|
-
</div>
|
|
11
|
-
</span>
|
|
12
|
-
|
|
13
|
-
<span v-else-if="column.type === 'boolean'">
|
|
14
|
-
<span v-if="record[column.name]" class="bg-green-100 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">Yes</span>
|
|
3
|
+
<span v-if="column.type === 'boolean'">
|
|
4
|
+
<span v-if="row[column.name]" class="bg-green-100 text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">Yes</span>
|
|
15
5
|
<span v-else class="bg-red-100 text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">No</span>
|
|
16
6
|
</span>
|
|
17
7
|
<span v-else-if="column.enum">
|
|
18
|
-
{{
|
|
19
|
-
</span>
|
|
20
|
-
<span v-else-if="column.type === 'datetime'" class="whitespace-nowrap">
|
|
21
|
-
{{ checkEmptyValues(formatDate(record[column.name]),route.meta.type) }}
|
|
8
|
+
{{ column.enum.find(e => e.value === row[column.name])?.label }}
|
|
22
9
|
</span>
|
|
23
|
-
<span v-else-if="column.type === '
|
|
24
|
-
|
|
10
|
+
<span v-else-if="column.type === 'datetime'">
|
|
11
|
+
{{ formatDate(row[column.name]) }}
|
|
12
|
+
|
|
25
13
|
</span>
|
|
26
14
|
<span v-else>
|
|
27
|
-
{{
|
|
15
|
+
{{ row[column.name] }}
|
|
28
16
|
</span>
|
|
29
17
|
</div>
|
|
30
18
|
</template>
|
|
@@ -35,29 +23,19 @@
|
|
|
35
23
|
import dayjs from 'dayjs';
|
|
36
24
|
import utc from 'dayjs/plugin/utc';
|
|
37
25
|
import timezone from 'dayjs/plugin/timezone';
|
|
38
|
-
import {checkEmptyValues} from '@/utils';
|
|
39
|
-
import { useRoute, useRouter } from 'vue-router';
|
|
40
|
-
import sanitizeHtml from 'sanitize-html';
|
|
41
|
-
|
|
42
26
|
|
|
43
27
|
import { useCoreStore } from '@/stores/core';
|
|
44
28
|
|
|
45
29
|
const coreStore = useCoreStore();
|
|
46
|
-
const route = useRoute();
|
|
47
|
-
|
|
48
30
|
|
|
49
31
|
dayjs.extend(utc);
|
|
50
32
|
dayjs.extend(timezone);
|
|
51
33
|
|
|
52
34
|
const props = defineProps({
|
|
53
35
|
column: Object,
|
|
54
|
-
|
|
36
|
+
row: Object
|
|
55
37
|
});
|
|
56
38
|
|
|
57
|
-
function protectAgainstXSS(value) {
|
|
58
|
-
return sanitizeHtml(value);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
39
|
|
|
62
40
|
function formatDate(date) {
|
|
63
41
|
if (!date) return '';
|
package/dist/spa/src/index.scss
CHANGED
package/dist/spa/src/main.ts
CHANGED
|
@@ -6,10 +6,10 @@ import { createPinia } from 'pinia'
|
|
|
6
6
|
import App from './App.vue'
|
|
7
7
|
import router from './router'
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const app = createApp(App)
|
|
10
10
|
/* IMPORTANT:ADMINFORTH COMPONENT REGISTRATIONS */
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
app.use(createPinia())
|
|
14
14
|
app.use(router)
|
|
15
15
|
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { createRouter, createWebHistory } from 'vue-router'
|
|
2
|
+
import HomeView from '../views/HomeView.vue'
|
|
2
3
|
import ResourceParent from '@/views/ResourceParent.vue'
|
|
3
|
-
import
|
|
4
|
-
|
|
4
|
+
import ListView from '@/views/ListView.vue'
|
|
5
|
+
import ShowView from '@/views/ShowView.vue'
|
|
6
|
+
import EditView from '@/views/EditView.vue'
|
|
7
|
+
import CreateView from '@/views/CreateView.vue'
|
|
5
8
|
|
|
6
9
|
const router = createRouter({
|
|
7
10
|
history: createWebHistory(import.meta.env.BASE_URL),
|
|
8
11
|
routes: [
|
|
12
|
+
{
|
|
13
|
+
path: '/',
|
|
14
|
+
name: 'home',
|
|
15
|
+
component: HomeView
|
|
16
|
+
},
|
|
9
17
|
{
|
|
10
18
|
path: '/login',
|
|
11
19
|
name: 'login',
|
|
12
|
-
component: () => import('@/views/LoginView.vue')
|
|
13
|
-
meta: { title: 'login' },
|
|
14
|
-
beforeEnter: async (to, from, next) => {
|
|
15
|
-
if(localStorage.getItem('isAuthorized') === 'true'){
|
|
16
|
-
next({name: 'home'})
|
|
17
|
-
} else {
|
|
18
|
-
next()
|
|
19
|
-
}
|
|
20
|
-
}
|
|
20
|
+
component: () => import('@/views/LoginView.vue')
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
path: '/resource/:resourceId',
|
|
@@ -26,29 +26,23 @@ const router = createRouter({
|
|
|
26
26
|
children: [
|
|
27
27
|
{
|
|
28
28
|
path: '',
|
|
29
|
-
component:
|
|
30
|
-
name: 'resource-list'
|
|
31
|
-
meta: { title: 'list',type: 'list' }
|
|
29
|
+
component: ListView,
|
|
30
|
+
name: 'resource-list'
|
|
32
31
|
},
|
|
33
32
|
{
|
|
34
33
|
path: 'show/:primaryKey',
|
|
35
|
-
component:
|
|
36
|
-
name: 'resource-show'
|
|
37
|
-
meta: { title: 'show', type: 'show'}
|
|
38
|
-
|
|
34
|
+
component: ShowView,
|
|
35
|
+
name: 'resource-show'
|
|
39
36
|
},
|
|
40
37
|
{
|
|
41
38
|
path: 'edit/:primaryKey',
|
|
42
|
-
component:
|
|
43
|
-
name: 'resource-edit'
|
|
44
|
-
meta: { title: 'edit', type: 'edit'}
|
|
39
|
+
component: EditView,
|
|
40
|
+
name: 'resource-edit'
|
|
45
41
|
},
|
|
46
42
|
{
|
|
47
43
|
path: 'create',
|
|
48
|
-
component:
|
|
49
|
-
name: 'resource-create'
|
|
50
|
-
meta: { title: 'create', type: 'create'}
|
|
51
|
-
|
|
44
|
+
component: CreateView,
|
|
45
|
+
name: 'resource-create'
|
|
52
46
|
},
|
|
53
47
|
]
|
|
54
48
|
},
|
|
@@ -56,8 +50,4 @@ const router = createRouter({
|
|
|
56
50
|
]
|
|
57
51
|
})
|
|
58
52
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
53
|
export default router
|