adminforth 1.1.65 → 1.1.66
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/dataConnectors/mongo.ts +7 -13
- package/dataConnectors/postgres.ts +2 -2
- package/dataConnectors/sqlite.ts +2 -2
- package/dist/dataConnectors/mongo.js +1 -9
- package/dist/index.js +1 -3
- package/dist/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
- package/dist/plugins/AuditLogPlugin/index.js +18 -13
- 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/spa/src/App.vue +24 -12
- package/dist/spa/spa/src/composables/useStores.ts +16 -4
- package/dist/spa/spa/src/main.ts +1 -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/ResourceListTable.vue +419 -0
- package/dist/spa/src/components/Toast.vue +66 -0
- package/dist/spa/src/components/ValueRenderer.vue +14 -8
- 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 -20
- package/dist/spa/src/spa_types/core.ts +51 -0
- package/dist/spa/src/stores/core.ts +48 -31
- 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 +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 +4 -5
- package/package.json +1 -1
- package/plugins/AuditLogPlugin/custom/AuditLogView.vue +7 -6
- package/plugins/AuditLogPlugin/index.ts +23 -15
- package/spa/src/App.vue +24 -12
- package/spa/src/composables/useStores.ts +16 -4
- package/spa/src/main.ts +1 -1
- package/types/AdminForthConfig.ts +16 -3
- package/types/FrontendAPI.ts +1 -15
- 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/public/favicon.ico +0 -0
- package/dist/types.js +0 -30
- /package/dist/spa/{spa/public → public/assets}/favicon.png +0 -0
|
@@ -1 +1,19 @@
|
|
|
1
|
-
<svg
|
|
1
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g clip-path="url(#clip0_1_8)">
|
|
3
|
+
<path d="M8.034 6.006V13H6.097V12.194C5.59433 12.8007 4.86633 13.104 3.913 13.104C3.25433 13.104 2.65633 12.9567 2.119 12.662C1.59033 12.3673 1.17433 11.947 0.871 11.401C0.567667 10.855 0.416 10.2223 0.416 9.503C0.416 8.78367 0.567667 8.151 0.871 7.605C1.17433 7.059 1.59033 6.63867 2.119 6.344C2.65633 6.04933 3.25433 5.902 3.913 5.902C4.80567 5.902 5.50333 6.18367 6.006 6.747V6.006H8.034ZM4.264 11.44C4.77533 11.44 5.2 11.2667 5.538 10.92C5.876 10.5647 6.045 10.0923 6.045 9.503C6.045 8.91367 5.876 8.44567 5.538 8.099C5.2 7.74367 4.77533 7.566 4.264 7.566C3.744 7.566 3.315 7.74367 2.977 8.099C2.639 8.44567 2.47 8.91367 2.47 9.503C2.47 10.0923 2.639 10.5647 2.977 10.92C3.315 11.2667 3.744 11.44 4.264 11.44Z" fill="url(#paint0_linear_1_8)"/>
|
|
4
|
+
<path d="M13.317 5.538C12.589 5.538 12.0387 5.69833 11.666 6.019C11.2933 6.331 11.107 6.80333 11.107 7.436V7.995H14.851V9.685H11.107V13H9.001V7.449C9.001 6.279 9.365 5.369 10.093 4.719C10.8297 4.069 11.8567 3.744 13.174 3.744C13.694 3.744 14.1837 3.80033 14.643 3.913C15.1023 4.017 15.501 4.173 15.839 4.381L15.189 6.045C14.669 5.707 14.045 5.538 13.317 5.538Z" fill="url(#paint1_linear_1_8)"/>
|
|
5
|
+
</g>
|
|
6
|
+
<defs>
|
|
7
|
+
<linearGradient id="paint0_linear_1_8" x1="1.5" y1="6.93333" x2="7.31883" y2="11.8926" gradientUnits="userSpaceOnUse">
|
|
8
|
+
<stop stop-color="#656E7A"/>
|
|
9
|
+
<stop offset="1" stop-color="#99A6B8"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
<linearGradient id="paint1_linear_1_8" x1="12.5" y1="3.73333" x2="9.68642" y2="12.7016" gradientUnits="userSpaceOnUse">
|
|
12
|
+
<stop stop-color="#48C5FF"/>
|
|
13
|
+
<stop offset="1" stop-color="#A1E1FF"/>
|
|
14
|
+
</linearGradient>
|
|
15
|
+
<clipPath id="clip0_1_8">
|
|
16
|
+
<rect width="16" height="16" fill="white"/>
|
|
17
|
+
</clipPath>
|
|
18
|
+
</defs>
|
|
19
|
+
</svg>
|
|
@@ -3,13 +3,6 @@ import { ref,onMounted } from 'vue'
|
|
|
3
3
|
import { useModalStore } from '@/stores/modal';
|
|
4
4
|
|
|
5
5
|
const modalStore = useModalStore();
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
6
|
</script>
|
|
14
7
|
|
|
15
8
|
<template>
|
|
@@ -28,10 +21,10 @@ const modalStore = useModalStore();
|
|
|
28
21
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 11V6m0 8h.01M19 10a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
|
|
29
22
|
</svg>
|
|
30
23
|
<h3 class="mb-5 text-lg font-normal text-gray-500 dark:text-gray-400">{{ modalStore?.modalContent?.content }}</h3>
|
|
31
|
-
<button @click="()=>{modalStore.onAcceptFunction();modalStore.togleModal()}" type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
|
|
24
|
+
<button @click="()=>{ modalStore.onAcceptFunction(true);modalStore.togleModal()}" type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
|
|
32
25
|
{{ modalStore?.modalContent?.acceptText }}
|
|
33
26
|
</button>
|
|
34
|
-
<button @click="modalStore.togleModal" type="button" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 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">{{ modalStore?.modalContent?.cancelText }}</button>
|
|
27
|
+
<button @click="()=>{modalStore.onAcceptFunction(false);modalStore.togleModal()}" type="button" class="py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 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">{{ modalStore?.modalContent?.cancelText }}</button>
|
|
35
28
|
</div>
|
|
36
29
|
</div>
|
|
37
30
|
</div>
|
|
@@ -67,6 +67,9 @@ const props = defineProps({
|
|
|
67
67
|
},
|
|
68
68
|
label: {
|
|
69
69
|
type: String,
|
|
70
|
+
},
|
|
71
|
+
autoHide: {
|
|
72
|
+
type: Boolean,
|
|
70
73
|
}
|
|
71
74
|
});
|
|
72
75
|
|
|
@@ -100,8 +103,7 @@ function updateFromProps() {
|
|
|
100
103
|
if (!props.valueStart) {
|
|
101
104
|
datepickerStartEl.value.value = '';
|
|
102
105
|
startTime.value = '';
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
106
|
+
} else {
|
|
105
107
|
datepickerObject.value.setDate(dayjs(props.valueStart).format('DD MMM YYYY'));
|
|
106
108
|
startTime.value = dayjs(props.valueStart).format('HH:mm:ss')
|
|
107
109
|
}
|
|
@@ -121,7 +123,13 @@ watch(start, () => {
|
|
|
121
123
|
})
|
|
122
124
|
|
|
123
125
|
function initDatepickers() {
|
|
124
|
-
|
|
126
|
+
const options = {format: 'dd M yyyy'};
|
|
127
|
+
|
|
128
|
+
if (props.autoHide) {
|
|
129
|
+
options.autohide = true;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
datepickerObject.value = new Datepicker(datepickerStartEl.value, options);
|
|
125
133
|
|
|
126
134
|
addChangeDateListener();
|
|
127
135
|
}
|
|
@@ -134,6 +142,10 @@ function removeChangeDateListener() {
|
|
|
134
142
|
datepickerStartEl.value.removeEventListener('changeDate', setStartDate);
|
|
135
143
|
}
|
|
136
144
|
|
|
145
|
+
function destroyDatepickerElement() {
|
|
146
|
+
datepickerObject.value.destroy();
|
|
147
|
+
}
|
|
148
|
+
|
|
137
149
|
function setStartDate(event) {
|
|
138
150
|
startDate.value = event.detail.date
|
|
139
151
|
}
|
|
@@ -157,5 +169,6 @@ onMounted(() => {
|
|
|
157
169
|
|
|
158
170
|
onBeforeUnmount(() => {
|
|
159
171
|
removeChangeDateListener();
|
|
172
|
+
destroyDatepickerElement();
|
|
160
173
|
});
|
|
161
174
|
</script>
|
|
@@ -101,6 +101,9 @@ const endDate = ref('');
|
|
|
101
101
|
const startTime = ref('');
|
|
102
102
|
const endTime = ref('');
|
|
103
103
|
|
|
104
|
+
const datepickerStartObject = ref('')
|
|
105
|
+
const datepickerEndObject = ref('')
|
|
106
|
+
|
|
104
107
|
const start = computed(() => {
|
|
105
108
|
if (!startDate.value) {
|
|
106
109
|
return;
|
|
@@ -162,9 +165,9 @@ watch(end, () => {
|
|
|
162
165
|
|
|
163
166
|
function initDatepickers() {
|
|
164
167
|
|
|
165
|
-
new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
|
|
168
|
+
datepickerStartObject.value = new Datepicker(datepickerStartEl.value, {format: 'dd M yyyy'});
|
|
166
169
|
|
|
167
|
-
new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
|
|
170
|
+
datepickerEndObject.value = new Datepicker(datepickerEndEl.value, {format: 'dd M yyyy'});
|
|
168
171
|
addChangeDateListener();
|
|
169
172
|
}
|
|
170
173
|
|
|
@@ -178,6 +181,11 @@ function removeChangeDateListener() {
|
|
|
178
181
|
datepickerEndEl.value.removeEventListener('changeDate', setEndDate);
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
function destroyDatepickerElement() {
|
|
185
|
+
datepickerStartObject.value.destroy();
|
|
186
|
+
datepickerEndObject.value.destroy();
|
|
187
|
+
}
|
|
188
|
+
|
|
181
189
|
function setStartDate(event) {
|
|
182
190
|
startDate.value = event.detail.date
|
|
183
191
|
}
|
|
@@ -205,5 +213,6 @@ onMounted(() => {
|
|
|
205
213
|
|
|
206
214
|
onBeforeUnmount(() => {
|
|
207
215
|
removeChangeDateListener();
|
|
216
|
+
destroyDatepickerElement();
|
|
208
217
|
})
|
|
209
218
|
</script>
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
v-model="search"
|
|
7
7
|
@focus="showDropdown = true"
|
|
8
8
|
class="block w-full pl-3 pr-10 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 sm:text-sm transition duration-150 ease-in-out 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"
|
|
9
|
-
:placeholder="selectedItems.length ? '' : 'Select...'"
|
|
9
|
+
:placeholder="selectedItems.length ? '' : placeholder || 'Select...'"
|
|
10
10
|
/>
|
|
11
11
|
<div class="absolute inset-y-0 left-2 flex items-center pr-2 flex-wrap">
|
|
12
12
|
{{ }}
|
|
@@ -67,6 +67,11 @@ const props = defineProps({
|
|
|
67
67
|
type: Boolean,
|
|
68
68
|
default: false,
|
|
69
69
|
},
|
|
70
|
+
placeholder: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: '',
|
|
73
|
+
|
|
74
|
+
},
|
|
70
75
|
});
|
|
71
76
|
|
|
72
77
|
const emit = defineEmits(['update:modelValue']);
|
|
@@ -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(() => {
|