adminforth 1.1.32 → 1.1.33
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/index.js +9 -0
- package/dist/modules/codeInjector.js +19 -7
- package/dist/modules/styles.js +2 -2
- package/dist/spa/index.html +2 -2
- package/dist/spa/package-lock.json +17 -4
- package/dist/spa/package.json +2 -1
- package/dist/spa/src/App.vue +125 -45
- package/dist/spa/src/assets/logo.svg +19 -1
- package/dist/spa/src/components/AcceptModal.vue +2 -9
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +16 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +11 -2
- package/dist/spa/src/components/Dropdown.vue +6 -1
- package/dist/spa/src/components/Filters.vue +40 -19
- package/dist/spa/src/components/ResourceForm.vue +26 -24
- package/dist/spa/src/components/ValueRenderer.vue +14 -8
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +30 -20
- package/dist/spa/src/stores/core.ts +48 -31
- package/dist/spa/src/stores/modal.ts +13 -3
- package/dist/spa/src/utils.ts +62 -7
- package/dist/spa/src/views/CreateView.vue +67 -15
- package/dist/spa/src/views/EditView.vue +45 -11
- package/dist/spa/src/views/ListView.vue +82 -366
- package/dist/spa/src/views/LoginView.vue +16 -4
- package/dist/spa/src/views/ShowView.vue +105 -21
- package/dist/spa/tailwind.config.js +1 -1
- package/dist/spa/vite.config.ts +6 -0
- package/index.ts +11 -0
- package/modules/codeInjector.ts +19 -1
- package/modules/styles.ts +2 -2
- package/package.json +1 -1
- package/spa/index.html +1 -1
- package/spa/src/App.vue +2 -3
- package/types/AdminForthConfig.ts +15 -0
- package/dist/plugins/AccessControl/index.js +0 -66
- package/dist/plugins/AccessControl/types.js +0 -1
- package/dist/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/.eslintrc.cjs +0 -14
- package/dist/spa/spa/.vscode/extensions.json +0 -6
- package/dist/spa/spa/README.md +0 -39
- package/dist/spa/spa/env.d.ts +0 -1
- package/dist/spa/spa/index.html +0 -23
- package/dist/spa/spa/package-lock.json +0 -4359
- package/dist/spa/spa/package.json +0 -47
- package/dist/spa/spa/postcss.config.js +0 -6
- package/dist/spa/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/src/App.vue +0 -272
- package/dist/spa/spa/src/assets/base.css +0 -0
- package/dist/spa/spa/src/assets/logo.svg +0 -19
- package/dist/spa/spa/src/components/AcceptModal.vue +0 -45
- package/dist/spa/spa/src/components/Breadcrumbs.vue +0 -40
- package/dist/spa/spa/src/components/BreadcrumbsWithButtons.vue +0 -26
- package/dist/spa/spa/src/components/CustomDatePicker.vue +0 -174
- package/dist/spa/spa/src/components/CustomDateRangePicker.vue +0 -218
- package/dist/spa/spa/src/components/CustomRangePicker.vue +0 -148
- package/dist/spa/spa/src/components/Dropdown.vue +0 -168
- package/dist/spa/spa/src/components/Filters.vue +0 -211
- package/dist/spa/spa/src/components/HelloWorld.vue +0 -17
- package/dist/spa/spa/src/components/MenuLink.vue +0 -24
- package/dist/spa/spa/src/components/ResourceForm.vue +0 -265
- package/dist/spa/spa/src/components/SingleSkeletLoader.vue +0 -13
- package/dist/spa/spa/src/components/ValueRenderer.vue +0 -60
- package/dist/spa/spa/src/components/icons/IconCalendar.vue +0 -5
- package/dist/spa/spa/src/components/icons/IconCommunity.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconDocumentation.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconEcosystem.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconSupport.vue +0 -7
- package/dist/spa/spa/src/components/icons/IconTime.vue +0 -5
- package/dist/spa/spa/src/components/icons/IconTooling.vue +0 -19
- package/dist/spa/spa/src/index.scss +0 -26
- package/dist/spa/spa/src/main.ts +0 -18
- package/dist/spa/spa/src/router/index.ts +0 -64
- package/dist/spa/spa/src/stores/core.ts +0 -144
- package/dist/spa/spa/src/stores/modal.ts +0 -48
- package/dist/spa/spa/src/utils.ts +0 -101
- package/dist/spa/spa/src/views/CreateView.vue +0 -163
- package/dist/spa/spa/src/views/EditView.vue +0 -165
- package/dist/spa/spa/src/views/ListView.vue +0 -257
- package/dist/spa/spa/src/views/LoginView.vue +0 -134
- package/dist/spa/spa/src/views/ResourceParent.vue +0 -18
- package/dist/spa/spa/src/views/ShowView.vue +0 -188
- package/dist/spa/spa/tailwind.config.js +0 -15
- package/dist/spa/spa/tsconfig.app.json +0 -14
- package/dist/spa/spa/tsconfig.json +0 -11
- package/dist/spa/spa/tsconfig.node.json +0 -19
- package/dist/spa/spa/vite.config.ts +0 -49
- package/dist/types.js +0 -30
- /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
- /package/dist/spa/{spa/src → src}/components/ResourceListTable.vue +0 -0
- /package/dist/spa/{spa/src → src}/components/Toast.vue +0 -0
- /package/dist/spa/{spa/src → src}/composables/useFrontendApi.ts +0 -0
- /package/dist/spa/{spa/src → src}/composables/useStores.ts +0 -0
- /package/dist/spa/{spa/src → src}/spa_types/core.ts +0 -0
- /package/dist/spa/{spa/src → src}/stores/filters.ts +0 -0
- /package/dist/spa/{spa/src → src}/stores/toast.ts +0 -0
- /package/dist/spa/{spa/src → src}/stores/user.ts +0 -0
- /package/spa/public/{favicon.png → assets/favicon.png} +0 -0
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
<!-- drawer component -->
|
|
3
3
|
<div id="drawer-navigation"
|
|
4
4
|
|
|
5
|
-
class="fixed
|
|
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-black"
|
|
6
6
|
|
|
7
7
|
:class="show ? 'top-0 transform-none' : ''"
|
|
8
8
|
tabindex="-1" aria-labelledby="drawer-navigation-label"
|
|
9
|
-
:style="{ height: `calc(100vh
|
|
9
|
+
:style="{ height: `calc(100vh ` }"
|
|
10
10
|
>
|
|
11
11
|
<h5 id="drawer-navigation-label" class="text-base font-semibold text-gray-500 uppercase dark:text-gray-400">
|
|
12
12
|
Filters
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
v-if="c.foreignResource"
|
|
27
27
|
:options="columnOptions[c.name] || []"
|
|
28
28
|
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
29
|
-
:modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
29
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
30
30
|
/>
|
|
31
31
|
<Dropdown
|
|
32
32
|
v-else-if="c.type === 'boolean'"
|
|
33
33
|
:options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
|
|
34
34
|
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
35
|
-
:modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
35
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
36
36
|
/>
|
|
37
37
|
|
|
38
38
|
<Dropdown
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
:options="c.enum"
|
|
41
41
|
:allowCustom="c.allowCustom"
|
|
42
42
|
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
43
|
-
:modelValue="filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
43
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
44
44
|
/>
|
|
45
45
|
|
|
46
46
|
<input
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
<CustomDateRangePicker
|
|
55
55
|
v-else-if="['datetime'].includes(c.type)"
|
|
56
56
|
:column="c"
|
|
57
|
-
:valueStart="filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
|
|
57
|
+
:valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
|
|
58
58
|
@update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
|
|
59
|
-
:valueEnd="filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
|
|
59
|
+
:valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
|
|
60
60
|
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
|
|
61
61
|
/>
|
|
62
62
|
|
|
@@ -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>
|
|
@@ -84,15 +101,15 @@
|
|
|
84
101
|
|
|
85
102
|
<div class="flex justify-end gap-2">
|
|
86
103
|
<button
|
|
87
|
-
:disabled="!filters.length"
|
|
104
|
+
:disabled="!filtersStore.filters.length"
|
|
88
105
|
type="button"
|
|
89
|
-
class="flex items-center py-1 px-3
|
|
106
|
+
class="flex items-center py-1 px-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
90
107
|
@click="clear">Clear all</button>
|
|
91
108
|
|
|
92
109
|
</div>
|
|
93
110
|
</div>
|
|
94
111
|
|
|
95
|
-
<div v-if="show"
|
|
112
|
+
<div v-if="show" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
|
|
96
113
|
@click="$emit('hide')">
|
|
97
114
|
</div>
|
|
98
115
|
</template>
|
|
@@ -105,6 +122,10 @@ import { callAdminForthApi } from '@/utils';
|
|
|
105
122
|
import { useRouter } from 'vue-router';
|
|
106
123
|
import { computedAsync } from '@vueuse/core'
|
|
107
124
|
import CustomRangePicker from "@/components/CustomRangePicker.vue";
|
|
125
|
+
import { useFiltersStore } from '@/stores/filters';
|
|
126
|
+
|
|
127
|
+
const filtersStore = useFiltersStore();
|
|
128
|
+
|
|
108
129
|
|
|
109
130
|
// props: columns
|
|
110
131
|
// add support for v-model:filers
|
|
@@ -163,28 +184,28 @@ watch(() => props.show, (show) => {
|
|
|
163
184
|
|
|
164
185
|
function setFilterItem({ column, operator, value }) {
|
|
165
186
|
|
|
166
|
-
const index =
|
|
187
|
+
const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
|
|
167
188
|
if (value === undefined) {
|
|
168
189
|
if (index !== -1) {
|
|
169
|
-
|
|
190
|
+
filtersStore.filters.splice(index, 1);
|
|
170
191
|
}
|
|
171
|
-
emits('update:filters', [...
|
|
192
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
172
193
|
return;
|
|
173
194
|
} else {
|
|
174
195
|
if (index === -1) {
|
|
175
|
-
|
|
196
|
+
filtersStore.setFilter({ field: column.name, value, operator });
|
|
176
197
|
} else {
|
|
177
|
-
|
|
198
|
+
filtersStore.setFilters([...filtersStore.filters.slice(0, index), { field: column.name, value, operator }, ...filtersStore.filters.slice(index + 1)])
|
|
178
199
|
}
|
|
179
200
|
}
|
|
180
|
-
emits('update:filters', [...
|
|
201
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
181
202
|
}
|
|
182
203
|
|
|
183
204
|
function getFilterItem({ column, operator }) {
|
|
184
|
-
return
|
|
205
|
+
return filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
|
|
185
206
|
}
|
|
186
207
|
|
|
187
208
|
async function clear() {
|
|
188
|
-
|
|
209
|
+
filtersStore.clearFilters();
|
|
189
210
|
}
|
|
190
211
|
</script>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div class="rounded-default">
|
|
3
3
|
|
|
4
4
|
<div
|
|
5
|
-
class="relative shadow-resourse-form-shadow sm:rounded-lg dark:shadow-2xl dark:shadow-black"
|
|
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">
|
|
8
|
+
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 ">
|
|
9
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">
|
|
@@ -30,28 +30,28 @@
|
|
|
30
30
|
/>
|
|
31
31
|
</template>
|
|
32
32
|
<template v-else>
|
|
33
|
-
<td class="px-6 py-4 whitespace-nowrap">
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
45
|
</td>
|
|
46
46
|
<td class="px-6 py-4 whitespace-nowrap whitespace-pre-wrap relative">
|
|
47
|
-
|
|
48
47
|
<Dropdown
|
|
49
48
|
single
|
|
50
49
|
v-if="column.foreignResource"
|
|
51
50
|
:options="columnOptions[column.name] || []"
|
|
51
|
+
:placeholder = "columnOptions[column.name]?.length || 'There are no options available'"
|
|
52
52
|
:modelValue="currentValues[column.name]"
|
|
53
53
|
@update:modelValue="setCurrentValue(column.name, $event)"
|
|
54
|
-
|
|
54
|
+
></Dropdown>
|
|
55
55
|
<Dropdown
|
|
56
56
|
single
|
|
57
57
|
v-else-if="column.enum"
|
|
@@ -79,6 +79,7 @@
|
|
|
79
79
|
v-else-if="['datetime'].includes(column.type)"
|
|
80
80
|
:column="column"
|
|
81
81
|
:valueStart="currentValues[column.name]"
|
|
82
|
+
auto-hide
|
|
82
83
|
@update:valueStart="setCurrentValue(column.name, $event)"
|
|
83
84
|
/>
|
|
84
85
|
<input
|
|
@@ -120,9 +121,6 @@
|
|
|
120
121
|
<IconEyeSolid class="w-6 h-6 text-gray-400" v-if="!unmasked[column.name]" />
|
|
121
122
|
<IconEyeSlashSolid class="w-6 h-6 text-gray-400" v-else />
|
|
122
123
|
</button>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
124
|
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-red-500 dark:text-red-400">{{ columnError(column) }}</div>
|
|
127
125
|
|
|
128
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>
|
|
@@ -154,7 +152,7 @@ const router = useRouter();
|
|
|
154
152
|
|
|
155
153
|
const props = defineProps({
|
|
156
154
|
loading: Boolean,
|
|
157
|
-
|
|
155
|
+
resource: Object,
|
|
158
156
|
record: Object,
|
|
159
157
|
validating: Boolean,
|
|
160
158
|
customComponentsPerColumn: Object,
|
|
@@ -196,7 +194,11 @@ const columnError = (column) => {
|
|
|
196
194
|
for (let i = 0; i < validationArray.length; i++) {
|
|
197
195
|
if (validationArray[i].regExp) {
|
|
198
196
|
const regExp = new RegExp(validationArray[i].regExp);
|
|
199
|
-
|
|
197
|
+
let value = currentValues.value[column.name];
|
|
198
|
+
if (value === undefined || value === null) {
|
|
199
|
+
value = '';
|
|
200
|
+
}
|
|
201
|
+
if (!regExp.test(value)) {
|
|
200
202
|
return validationArray[i].message;
|
|
201
203
|
}
|
|
202
204
|
}
|
|
@@ -226,7 +228,7 @@ onMounted(() => {
|
|
|
226
228
|
|
|
227
229
|
const columnOptions = computedAsync(async () => {
|
|
228
230
|
return (await Promise.all(
|
|
229
|
-
Object.values(props.
|
|
231
|
+
Object.values(props.resource.columns).map(async (column) => {
|
|
230
232
|
if (column.foreignResource) {
|
|
231
233
|
const list = await callAdminForthApi({
|
|
232
234
|
method: 'POST',
|
|
@@ -249,7 +251,7 @@ const coreStore = useCoreStore();
|
|
|
249
251
|
|
|
250
252
|
const editableColumns = computed(() => {
|
|
251
253
|
const mode = props.record ? 'edit' : 'create';
|
|
252
|
-
return props.
|
|
254
|
+
return props.resource?.columns?.filter(column => column.showIn.includes(mode));
|
|
253
255
|
});
|
|
254
256
|
|
|
255
257
|
const isValid = computed(() => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
+
|
|
3
4
|
<span v-if="column.foreignResource">
|
|
4
|
-
<RouterLink v-if="
|
|
5
|
-
:to="{ name: 'resource-show', params: { resourceId: column.foreignResource.resourceId, primaryKey:
|
|
6
|
-
{{
|
|
5
|
+
<RouterLink v-if="record[column.name]" class="font-medium text-blue-600 dark:text-blue-500 hover:brightness-110"
|
|
6
|
+
:to="{ name: 'resource-show', params: { resourceId: column.foreignResource.resourceId, primaryKey: record[column.name].pk } }">
|
|
7
|
+
{{ record[column.name].label }}
|
|
7
8
|
</RouterLink>
|
|
8
9
|
<div v-else>
|
|
9
10
|
<span class="text-gray-400">-</span>
|
|
@@ -11,18 +12,18 @@
|
|
|
11
12
|
</span>
|
|
12
13
|
|
|
13
14
|
<span v-else-if="column.type === 'boolean'">
|
|
14
|
-
<span v-if="
|
|
15
|
+
<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>
|
|
15
16
|
<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
17
|
</span>
|
|
17
18
|
<span v-else-if="column.enum">
|
|
18
|
-
{{ column.enum.find(e => e.value ===
|
|
19
|
+
{{ checkEmptyValues(column.enum.find(e => e.value === record[column.name])?.label,route.meta.type) }}
|
|
19
20
|
</span>
|
|
20
21
|
<span v-else-if="column.type === 'datetime'">
|
|
21
|
-
{{ formatDate(
|
|
22
|
+
{{ checkEmptyValues(formatDate(record[column.name]),route.meta.type) }}
|
|
22
23
|
|
|
23
24
|
</span>
|
|
24
25
|
<span v-else>
|
|
25
|
-
{{
|
|
26
|
+
{{ checkEmptyValues(record[column.name],route.meta.type) }}
|
|
26
27
|
</span>
|
|
27
28
|
</div>
|
|
28
29
|
</template>
|
|
@@ -33,17 +34,22 @@
|
|
|
33
34
|
import dayjs from 'dayjs';
|
|
34
35
|
import utc from 'dayjs/plugin/utc';
|
|
35
36
|
import timezone from 'dayjs/plugin/timezone';
|
|
37
|
+
import {checkEmptyValues} from '@/utils';
|
|
38
|
+
import { useRoute, useRouter } from 'vue-router';
|
|
39
|
+
|
|
36
40
|
|
|
37
41
|
import { useCoreStore } from '@/stores/core';
|
|
38
42
|
|
|
39
43
|
const coreStore = useCoreStore();
|
|
44
|
+
const route = useRoute();
|
|
45
|
+
|
|
40
46
|
|
|
41
47
|
dayjs.extend(utc);
|
|
42
48
|
dayjs.extend(timezone);
|
|
43
49
|
|
|
44
50
|
const props = defineProps({
|
|
45
51
|
column: Object,
|
|
46
|
-
|
|
52
|
+
record: Object
|
|
47
53
|
});
|
|
48
54
|
|
|
49
55
|
|
package/dist/spa/src/main.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
import { createRouter, createWebHistory } from 'vue-router'
|
|
2
2
|
import ResourceParent from '@/views/ResourceParent.vue'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import EditView from '@/views/EditView.vue'
|
|
6
|
-
import CreateView from '@/views/CreateView.vue'
|
|
3
|
+
import { useUserStore } from '@/stores/user'
|
|
4
|
+
/* IMPORTANT:ADMINFORTH ROUTES IMPORTS */
|
|
7
5
|
|
|
8
6
|
const router = createRouter({
|
|
9
7
|
history: createWebHistory(import.meta.env.BASE_URL),
|
|
10
8
|
routes: [
|
|
11
|
-
{
|
|
12
|
-
path: '/',
|
|
13
|
-
name: 'home',
|
|
14
|
-
//redirect to login
|
|
15
|
-
/* IMPORTANT:ADMINFORTH REDIRECT */
|
|
16
|
-
|
|
17
|
-
},
|
|
18
9
|
{
|
|
19
10
|
path: '/login',
|
|
20
11
|
name: 'login',
|
|
21
|
-
component: () => import('@/views/LoginView.vue')
|
|
12
|
+
component: () => import('@/views/LoginView.vue'),
|
|
13
|
+
meta: { title: 'login' },
|
|
14
|
+
beforeEnter: async (to, from, next) => {
|
|
15
|
+
const userStore = useUserStore()
|
|
16
|
+
if(localStorage.getItem('isAuthorized') === 'true'){
|
|
17
|
+
next({name: 'home'})
|
|
18
|
+
} else {
|
|
19
|
+
next()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
path: '/resource/:resourceId',
|
|
@@ -27,23 +27,29 @@ const router = createRouter({
|
|
|
27
27
|
children: [
|
|
28
28
|
{
|
|
29
29
|
path: '',
|
|
30
|
-
component: ListView,
|
|
31
|
-
name: 'resource-list'
|
|
30
|
+
component: () => import('@/views/ListView.vue'),
|
|
31
|
+
name: 'resource-list',
|
|
32
|
+
meta: { title: 'list',type: 'list' }
|
|
32
33
|
},
|
|
33
34
|
{
|
|
34
35
|
path: 'show/:primaryKey',
|
|
35
|
-
component: ShowView,
|
|
36
|
-
name: 'resource-show'
|
|
36
|
+
component: () => import('@/views/ShowView.vue'),
|
|
37
|
+
name: 'resource-show',
|
|
38
|
+
meta: { title: 'show', type: 'show'}
|
|
39
|
+
|
|
37
40
|
},
|
|
38
41
|
{
|
|
39
42
|
path: 'edit/:primaryKey',
|
|
40
|
-
component: EditView,
|
|
41
|
-
name: 'resource-edit'
|
|
43
|
+
component: () => import('@/views/EditView.vue'),
|
|
44
|
+
name: 'resource-edit',
|
|
45
|
+
meta: { title: 'edit', type: 'edit'}
|
|
42
46
|
},
|
|
43
47
|
{
|
|
44
48
|
path: 'create',
|
|
45
|
-
component: CreateView,
|
|
46
|
-
name: 'resource-create'
|
|
49
|
+
component: () => import('@/views/CreateView.vue'),
|
|
50
|
+
name: 'resource-create',
|
|
51
|
+
meta: { title: 'create', type: 'create'}
|
|
52
|
+
|
|
47
53
|
},
|
|
48
54
|
]
|
|
49
55
|
},
|
|
@@ -51,4 +57,8 @@ const router = createRouter({
|
|
|
51
57
|
]
|
|
52
58
|
})
|
|
53
59
|
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
54
64
|
export default router
|
|
@@ -1,39 +1,50 @@
|
|
|
1
1
|
import { ref, computed } from 'vue'
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
3
|
import { callAdminForthApi } from '@/utils';
|
|
4
|
+
import type { AdminForthResource, AdminForthResourceColumn } from '@/types/AdminForthConfig';
|
|
5
|
+
import type { Ref } from 'vue'
|
|
4
6
|
|
|
5
7
|
export const useCoreStore = defineStore('core', () => {
|
|
6
|
-
const resourceById = ref(
|
|
8
|
+
const resourceById: Ref<Object> = ref({});
|
|
7
9
|
const menu = ref([]);
|
|
8
10
|
const config = ref({});
|
|
9
|
-
const record = ref({});
|
|
10
|
-
const
|
|
11
|
+
const record: Ref<any | null> = ref({});
|
|
12
|
+
const resource: Ref<AdminForthResource | null> = ref(null);
|
|
13
|
+
|
|
14
|
+
const resourceColumnsWithFilters = computed(() => {
|
|
15
|
+
if (!resource.value) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return resource.value.columns.filter((col: AdminForthResourceColumn) => col.showIn?.includes('filter'));
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const resourceOptions = ref(null);
|
|
11
22
|
const resourceColumnsError = ref('');
|
|
12
23
|
const resourceColumnsId = ref(null);
|
|
13
|
-
const
|
|
24
|
+
const adminUser = ref(null);
|
|
14
25
|
|
|
15
26
|
async function fetchMenuAndResource() {
|
|
16
27
|
const resp = await callAdminForthApi({
|
|
17
28
|
path: '/get_base_config',
|
|
18
29
|
method: 'GET',
|
|
19
30
|
});
|
|
31
|
+
if(!resp){
|
|
32
|
+
return
|
|
33
|
+
}
|
|
20
34
|
menu.value = resp.menu;
|
|
21
|
-
resourceById.value = resp.resources.reduce((acc, resource) => {
|
|
35
|
+
resourceById.value = resp.resources.reduce((acc: Object, resource: AdminForthResource) => {
|
|
22
36
|
acc[resource.resourceId] = resource;
|
|
23
37
|
return acc;
|
|
24
38
|
}, {});
|
|
25
39
|
config.value = resp.config;
|
|
26
|
-
|
|
40
|
+
adminUser.value = resp.user;
|
|
27
41
|
console.log('🌍 AdminForth v', resp.version);
|
|
28
|
-
|
|
29
|
-
// find homepage:true in menu recuresively
|
|
30
|
-
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
async function fetchRecord({ resourceId, primaryKey }) {
|
|
34
45
|
record.value = null;
|
|
35
46
|
|
|
36
|
-
if (!
|
|
47
|
+
if (!resource.value) {
|
|
37
48
|
throw new Error('Columns not fetched yet');
|
|
38
49
|
}
|
|
39
50
|
|
|
@@ -45,7 +56,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
45
56
|
resourceId: resourceId,
|
|
46
57
|
filters: [
|
|
47
58
|
{
|
|
48
|
-
field:
|
|
59
|
+
field: resource.value.columns.find((col: AdminForthResourceColumn) => col.primaryKey).name,
|
|
49
60
|
operator: 'eq',
|
|
50
61
|
value: primaryKey
|
|
51
62
|
}
|
|
@@ -56,30 +67,39 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
56
67
|
}
|
|
57
68
|
});
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
|
|
70
|
+
if (respData.error) {
|
|
71
|
+
window.adminforth.alert({
|
|
72
|
+
message: respData.error,
|
|
73
|
+
variant: 'danger',
|
|
74
|
+
timeout: 'unlimited'
|
|
75
|
+
});
|
|
76
|
+
record.value = {};
|
|
77
|
+
} else {
|
|
78
|
+
record.value = respData.data[0];
|
|
79
|
+
}
|
|
80
|
+
|
|
61
81
|
}
|
|
62
82
|
|
|
63
|
-
async function
|
|
64
|
-
if (resourceColumnsId.value === resourceId &&
|
|
83
|
+
async function fetchResourceFull({ resourceId }: { resourceId: string }) {
|
|
84
|
+
if (resourceColumnsId.value === resourceId && resource.value) {
|
|
65
85
|
// already fetched
|
|
66
86
|
return;
|
|
67
87
|
}
|
|
68
88
|
resourceColumnsId.value = resourceId;
|
|
69
|
-
resourceColumns.value = null;
|
|
70
89
|
resourceColumnsError.value = '';
|
|
71
90
|
const res = await callAdminForthApi({
|
|
72
|
-
path: '/
|
|
91
|
+
path: '/get_resource',
|
|
73
92
|
method: 'POST',
|
|
74
93
|
body: {
|
|
75
94
|
resourceId,
|
|
76
95
|
}
|
|
77
|
-
}
|
|
78
|
-
);
|
|
96
|
+
});
|
|
79
97
|
if (res.error) {
|
|
80
98
|
resourceColumnsError.value = res.error;
|
|
81
99
|
} else {
|
|
82
|
-
|
|
100
|
+
resourceById.value[resourceId] = res.resource;
|
|
101
|
+
resource.value = res.resource;
|
|
102
|
+
resourceOptions.value = res.resource.options;
|
|
83
103
|
}
|
|
84
104
|
}
|
|
85
105
|
|
|
@@ -91,21 +111,16 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
91
111
|
config.value = {...config.value, ...res};
|
|
92
112
|
}
|
|
93
113
|
|
|
94
|
-
|
|
95
|
-
await callAdminForthApi({
|
|
96
|
-
path: '/logout',
|
|
97
|
-
method: 'POST',
|
|
98
|
-
});
|
|
99
|
-
}
|
|
114
|
+
|
|
100
115
|
|
|
101
116
|
const username = computed(() => {
|
|
102
117
|
const usernameField = config.value.usernameField;
|
|
103
|
-
return
|
|
118
|
+
return adminUser.value && adminUser.value[usernameField];
|
|
104
119
|
});
|
|
105
120
|
|
|
106
121
|
const userFullname = computed(() => {
|
|
107
122
|
const userFullnameField = config.value.userFullnameField;
|
|
108
|
-
return
|
|
123
|
+
return adminUser.value && adminUser.value[userFullnameField];
|
|
109
124
|
})
|
|
110
125
|
|
|
111
126
|
|
|
@@ -119,9 +134,11 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
119
134
|
fetchMenuAndResource,
|
|
120
135
|
fetchRecord,
|
|
121
136
|
record,
|
|
122
|
-
|
|
123
|
-
fetchColumns,
|
|
137
|
+
fetchResourceFull,
|
|
124
138
|
resourceColumnsError,
|
|
125
|
-
|
|
139
|
+
resourceOptions,
|
|
140
|
+
resource,
|
|
141
|
+
adminUser,
|
|
142
|
+
resourceColumnsWithFilters
|
|
126
143
|
}
|
|
127
144
|
})
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { ref } from 'vue'
|
|
2
2
|
import { defineStore } from 'pinia'
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
type ModalContentType = {
|
|
5
|
+
title?: string;
|
|
6
|
+
content?: string;
|
|
7
|
+
acceptText?: string;
|
|
8
|
+
cancelText?: string;
|
|
9
|
+
}
|
|
4
10
|
|
|
5
11
|
|
|
6
12
|
export const useModalStore = defineStore('modal', () => {
|
|
@@ -12,13 +18,17 @@ export const useModalStore = defineStore('modal', () => {
|
|
|
12
18
|
});
|
|
13
19
|
const isOpened = ref(false);
|
|
14
20
|
const onAcceptFunction: any = ref(()=>{});
|
|
21
|
+
const onCancelFunction: any = ref(()=>{});
|
|
15
22
|
function togleModal() {
|
|
16
23
|
isOpened.value = !isOpened.value;
|
|
17
24
|
}
|
|
18
25
|
function setOnAcceptFunction(func: Function) {
|
|
19
26
|
onAcceptFunction.value = func;
|
|
20
27
|
}
|
|
21
|
-
function
|
|
28
|
+
function setOnCancelFunction(func: Function) {
|
|
29
|
+
onCancelFunction.value = func;
|
|
30
|
+
}
|
|
31
|
+
function setModalContent(content: ModalContentType) {
|
|
22
32
|
modalContent.value = content;
|
|
23
33
|
}
|
|
24
34
|
function resetmodalState() {
|
|
@@ -33,6 +43,6 @@ export const useModalStore = defineStore('modal', () => {
|
|
|
33
43
|
|
|
34
44
|
}
|
|
35
45
|
|
|
36
|
-
return {isOpened, setModalContent, togleModal,modalContent, setOnAcceptFunction, onAcceptFunction,resetmodalState}
|
|
46
|
+
return {isOpened, setModalContent,onCancelFunction, togleModal,modalContent, setOnAcceptFunction, onAcceptFunction,resetmodalState,setOnCancelFunction}
|
|
37
47
|
|
|
38
48
|
})
|