adminforth 2.4.0-next.17 → 2.4.0-next.171
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/commands/callTsProxy.js +14 -4
- package/commands/cli.js +12 -4
- package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
- package/commands/createApp/templates/index.ts.hbs +10 -2
- package/commands/createApp/templates/package.json.hbs +1 -1
- package/commands/createApp/utils.js +27 -2
- package/commands/createCustomComponent/configLoader.js +3 -0
- package/commands/createCustomComponent/main.js +1 -0
- package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
- package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
- package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
- package/dist/dataConnectors/baseConnector.js +46 -15
- package/dist/dataConnectors/baseConnector.js.map +1 -1
- package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
- package/dist/dataConnectors/clickhouse.js +15 -0
- package/dist/dataConnectors/clickhouse.js.map +1 -1
- package/dist/dataConnectors/mongo.d.ts.map +1 -1
- package/dist/dataConnectors/mongo.js +44 -15
- package/dist/dataConnectors/mongo.js.map +1 -1
- package/dist/dataConnectors/mysql.d.ts.map +1 -1
- package/dist/dataConnectors/mysql.js +11 -0
- package/dist/dataConnectors/mysql.js.map +1 -1
- package/dist/dataConnectors/postgres.d.ts.map +1 -1
- package/dist/dataConnectors/postgres.js +11 -0
- package/dist/dataConnectors/postgres.js.map +1 -1
- package/dist/dataConnectors/sqlite.d.ts.map +1 -1
- package/dist/dataConnectors/sqlite.js +11 -0
- package/dist/dataConnectors/sqlite.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -9
- package/dist/index.js.map +1 -1
- package/dist/modules/codeInjector.d.ts +1 -0
- package/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +42 -5
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +62 -3
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +149 -25
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/styles.d.ts +457 -13
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +513 -31
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +1 -0
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +9 -0
- package/dist/modules/utils.js.map +1 -1
- package/dist/spa/index.html +1 -1
- package/dist/spa/package-lock.json +5 -4
- package/dist/spa/package.json +1 -1
- package/dist/spa/src/App.vue +48 -167
- package/dist/spa/src/adminforth.ts +42 -18
- package/dist/spa/src/afcl/BarChart.vue +2 -2
- package/dist/spa/src/afcl/Button.vue +6 -6
- package/dist/spa/src/afcl/Checkbox.vue +21 -13
- package/dist/spa/src/afcl/CountryFlag.vue +4 -1
- package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
- package/dist/spa/src/afcl/Dialog.vue +44 -27
- package/dist/spa/src/afcl/Dropzone.vue +12 -12
- package/dist/spa/src/afcl/Input.vue +6 -6
- package/dist/spa/src/afcl/JsonViewer.vue +25 -0
- package/dist/spa/src/afcl/LinkButton.vue +1 -1
- package/dist/spa/src/afcl/PieChart.vue +5 -5
- package/dist/spa/src/afcl/ProgressBar.vue +7 -7
- package/dist/spa/src/afcl/Select.vue +68 -34
- package/dist/spa/src/afcl/Skeleton.vue +6 -6
- package/dist/spa/src/afcl/Table.vue +202 -72
- package/dist/spa/src/afcl/Textarea.vue +31 -0
- package/dist/spa/src/afcl/Toggle.vue +32 -0
- package/dist/spa/src/afcl/Tooltip.vue +1 -2
- package/dist/spa/src/afcl/VerticalTabs.vue +16 -7
- package/dist/spa/src/afcl/index.ts +4 -3
- package/dist/spa/src/components/AcceptModal.vue +7 -7
- package/dist/spa/src/components/Breadcrumbs.vue +5 -5
- package/dist/spa/src/components/ColumnValueInput.vue +38 -18
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
- package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
- package/dist/spa/src/components/CustomRangePicker.vue +37 -8
- package/dist/spa/src/components/ErrorMessage.vue +21 -0
- package/dist/spa/src/components/Filters.vue +85 -39
- package/dist/spa/src/components/GroupsTable.vue +9 -8
- package/dist/spa/src/components/MenuLink.vue +47 -23
- package/dist/spa/src/components/ResourceForm.vue +94 -51
- package/dist/spa/src/components/ResourceListTable.vue +78 -80
- package/dist/spa/src/components/ResourceListTableVirtual.vue +70 -72
- package/dist/spa/src/components/ShowTable.vue +17 -12
- package/dist/spa/src/components/Sidebar.vue +419 -0
- package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
- package/dist/spa/src/components/SkeleteLoader.vue +3 -3
- package/dist/spa/src/components/ThreeDotsMenu.vue +73 -14
- package/dist/spa/src/components/Toast.vue +27 -9
- package/dist/spa/src/components/UserMenuSettingsButton.vue +77 -0
- package/dist/spa/src/components/ValueRenderer.vue +43 -16
- package/dist/spa/src/controls/BoolToggle.vue +34 -0
- package/dist/spa/src/i18n.ts +1 -1
- package/dist/spa/src/renderers/CompactField.vue +1 -1
- package/dist/spa/src/renderers/CompactUUID.vue +1 -1
- package/dist/spa/src/router/index.ts +8 -0
- package/dist/spa/src/shims-vue.d.ts +5 -0
- package/dist/spa/src/spa_types/core.ts +12 -1
- package/dist/spa/src/stores/core.ts +1 -1
- package/dist/spa/src/stores/filters.ts +29 -2
- package/dist/spa/src/stores/modal.ts +6 -1
- package/dist/spa/src/stores/toast.ts +22 -3
- package/dist/spa/src/types/Back.ts +131 -22
- package/dist/spa/src/types/Common.ts +55 -31
- package/dist/spa/src/types/FrontendAPI.ts +31 -5
- package/dist/spa/src/types/adapters/CompletionAdapter.ts +25 -0
- package/dist/spa/src/types/adapters/EmailAdapter.ts +27 -0
- package/dist/spa/src/types/adapters/ImageGenerationAdapter.ts +50 -0
- package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
- package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
- package/dist/spa/src/types/adapters/OAuth2Adapter.ts +34 -0
- package/dist/spa/src/types/adapters/StorageAdapter.ts +73 -0
- package/dist/spa/src/types/adapters/index.ts +7 -0
- package/dist/spa/src/utils.ts +217 -7
- package/dist/spa/src/views/CreateView.vue +18 -19
- package/dist/spa/src/views/EditView.vue +25 -19
- package/dist/spa/src/views/ListView.vue +126 -81
- package/dist/spa/src/views/LoginView.vue +24 -33
- package/dist/spa/src/views/ResourceParent.vue +2 -2
- package/dist/spa/src/views/SettingsView.vue +139 -0
- package/dist/spa/src/views/ShowView.vue +59 -39
- package/dist/spa/src/websocket.ts +6 -1
- package/dist/spa/tsconfig.app.json +1 -1
- package/dist/spa/vite.config.ts +45 -2
- package/dist/types/Back.d.ts +96 -14
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js +15 -0
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +48 -28
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/FrontendAPI.d.ts +31 -3
- package/dist/types/FrontendAPI.d.ts.map +1 -1
- package/dist/types/FrontendAPI.js.map +1 -1
- package/dist/types/adapters/CompletionAdapter.d.ts +20 -0
- package/dist/types/adapters/CompletionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/CompletionAdapter.js +2 -0
- package/dist/types/adapters/CompletionAdapter.js.map +1 -0
- package/dist/types/adapters/EmailAdapter.d.ts +20 -0
- package/dist/types/adapters/EmailAdapter.d.ts.map +1 -0
- package/dist/types/adapters/EmailAdapter.js +2 -0
- package/dist/types/adapters/EmailAdapter.js.map +1 -0
- package/dist/types/adapters/ImageGenerationAdapter.d.ts +37 -0
- package/dist/types/adapters/ImageGenerationAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageGenerationAdapter.js +2 -0
- package/dist/types/adapters/ImageGenerationAdapter.js.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
- package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
- package/dist/types/adapters/ImageVisionAdapter.js +2 -0
- package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
- package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
- package/dist/types/adapters/KeyValueAdapter.js +2 -0
- package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
- package/dist/types/adapters/OAuth2Adapter.d.ts +32 -0
- package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -0
- package/dist/types/adapters/OAuth2Adapter.js +2 -0
- package/dist/types/adapters/OAuth2Adapter.js.map +1 -0
- package/dist/types/adapters/StorageAdapter.d.ts +63 -0
- package/dist/types/adapters/StorageAdapter.d.ts.map +1 -0
- package/dist/types/adapters/StorageAdapter.js +2 -0
- package/dist/types/adapters/StorageAdapter.js.map +1 -0
- package/dist/types/adapters/index.d.ts +8 -0
- package/dist/types/adapters/index.d.ts.map +1 -0
- package/dist/types/adapters/index.js +2 -0
- package/dist/types/adapters/index.js.map +1 -0
- package/package.json +3 -2
- package/dist/spa/src/types/Adapters.ts +0 -213
- package/dist/types/Adapters.d.ts +0 -168
- package/dist/types/Adapters.d.ts.map +0 -1
- package/dist/types/Adapters.js +0 -2
- package/dist/types/Adapters.js.map +0 -1
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
<!-- drawer component -->
|
|
3
3
|
<div id="drawer-navigation"
|
|
4
4
|
|
|
5
|
-
class="fixed
|
|
5
|
+
class="af-filters-sidebar fixed right-0 z-50 p-4 overflow-y-auto transition-transform translate-x-full bg-lightFiltersBackgroung w-80 dark:bg-darkFiltersBackgroung 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"
|
|
9
9
|
:style="{ height: `calc(100dvh ` }"
|
|
10
10
|
>
|
|
11
|
-
<h5 id="drawer-navigation-label" class="text-base font-semibold text-
|
|
11
|
+
<h5 id="drawer-navigation-label" class="text-base font-semibold text-lightFiltersHeaderText uppercase dark:text-darkFiltersHeaderText">
|
|
12
12
|
{{ $t('Filters') }}
|
|
13
13
|
|
|
14
|
-
<button type="button" @click="$emit('hide')" class="text-
|
|
14
|
+
<button type="button" @click="$emit('hide')" class="text-lightFiltersCloseIcon bg-transparent hover:bg-lightFiltersCloseIconHoverBackground hover:text-lightFiltersCloseIconHover rounded-lg text-sm p-1.5 absolute end-2.5 inline-flex items-center dark:text-darkFiltersCloseIcon dark:hover:bg-darkFiltersCloseIconHoverBackground dark:hover:text-darkFiltersCloseIconHover" >
|
|
15
15
|
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
|
|
16
16
|
<span class="sr-only">{{ $t('Close menu') }}</span>
|
|
17
17
|
</button>
|
|
@@ -43,9 +43,23 @@
|
|
|
43
43
|
:multiple="c.filterOptions.multiselect"
|
|
44
44
|
class="w-full"
|
|
45
45
|
:options="columnOptions[c.name] || []"
|
|
46
|
+
:searchDisabled="!c.foreignResource.searchableFields"
|
|
47
|
+
@scroll-near-end="loadMoreOptions(c.name)"
|
|
48
|
+
@search="(searchTerm) => {
|
|
49
|
+
if (c.foreignResource.searchableFields && onSearchInput[c.name]) {
|
|
50
|
+
onSearchInput[c.name](searchTerm);
|
|
51
|
+
}
|
|
52
|
+
}"
|
|
46
53
|
@update:modelValue="onFilterInput[c.name]({ column: c, operator: c.filterOptions.multiselect ? 'in' : 'eq', value: c.filterOptions.multiselect ? ($event.length ? $event : undefined) : $event || undefined })"
|
|
47
54
|
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === (c.filterOptions.multiselect ? 'in' : 'eq'))?.value || (c.filterOptions.multiselect ? [] : '')"
|
|
48
|
-
|
|
55
|
+
>
|
|
56
|
+
<template #extra-item v-if="columnLoadingState[c.name]?.loading">
|
|
57
|
+
<div class="text-center text-gray-400 dark:text-gray-300 py-2 flex items-center justify-center gap-2">
|
|
58
|
+
<Spinner class="w-4 h-4" />
|
|
59
|
+
{{ $t('Loading...') }}
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
</Select>
|
|
49
63
|
<Select
|
|
50
64
|
:multiple="c.filterOptions.multiselect"
|
|
51
65
|
class="w-full"
|
|
@@ -94,9 +108,9 @@
|
|
|
94
108
|
:min="getFilterMinValue(c.name)"
|
|
95
109
|
:max="getFilterMaxValue(c.name)"
|
|
96
110
|
:valueStart="getFilterItem({ column: c, operator: 'gte' })"
|
|
97
|
-
@update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: $event
|
|
111
|
+
@update:valueStart="onFilterInput[c.name]({ column: c, operator: 'gte', value: ($event !== '' && $event !== null) ? $event : undefined })"
|
|
98
112
|
:valueEnd="getFilterItem({ column: c, operator: 'lte' })"
|
|
99
|
-
@update:valueEnd="onFilterInput[c.name]({ column: c, operator: 'lte', value: $event
|
|
113
|
+
@update:valueEnd="onFilterInput[c.name]({ column: c, operator: 'lte', value: ($event !== '' && $event !== null) ? $event : undefined })"
|
|
100
114
|
/>
|
|
101
115
|
|
|
102
116
|
<div v-else-if="['integer', 'decimal', 'float'].includes(c.type)" class="flex gap-2">
|
|
@@ -104,14 +118,14 @@
|
|
|
104
118
|
type="number"
|
|
105
119
|
aria-describedby="helper-text-explanation"
|
|
106
120
|
:placeholder="$t('From')"
|
|
107
|
-
@update:modelValue="onFilterInput[c.name]({ column: c, operator: 'gte', value: $event
|
|
121
|
+
@update:modelValue="onFilterInput[c.name]({ column: c, operator: 'gte', value: ($event !== '' && $event !== null) ? $event : undefined })"
|
|
108
122
|
:modelValue="getFilterItem({ column: c, operator: 'gte' })"
|
|
109
123
|
/>
|
|
110
124
|
<Input
|
|
111
125
|
type="number"
|
|
112
126
|
aria-describedby="helper-text-explanation"
|
|
113
127
|
:placeholder="$t('To')"
|
|
114
|
-
@update:modelValue="onFilterInput[c.name]({ column: c, operator: 'lte', value: $event
|
|
128
|
+
@update:modelValue="onFilterInput[c.name]({ column: c, operator: 'lte', value: ($event !== '' && $event !== null) ? $event : undefined })"
|
|
115
129
|
:modelValue="getFilterItem({ column: c, operator: 'lte' })"
|
|
116
130
|
/>
|
|
117
131
|
</div>
|
|
@@ -122,9 +136,9 @@
|
|
|
122
136
|
|
|
123
137
|
<div class="flex justify-end gap-2">
|
|
124
138
|
<button
|
|
125
|
-
:disabled="!filtersStore.
|
|
139
|
+
:disabled="!filtersStore.visibleFiltersCount"
|
|
126
140
|
type="button"
|
|
127
|
-
class="flex items-center py-1 px-3 text-sm font-medium text-
|
|
141
|
+
class="flex items-center py-1 px-3 text-sm font-medium text-lightFiltersClearAllButtonText focus:outline-none bg-lightFiltersClearAllButtonBackground rounded border border-lightFiltersClearAllButtonBorder hover:bg-lightFiltersClearAllButtonBackgroundHover hover:text-lightFiltersClearAllButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightFiltersClearAllButtonFocus dark:focus:ring-darkFiltersClearAllButtonFocus dark:bg-darkFiltersClearAllButtonBackground dark:text-darkFiltersClearAllButtonText dark:border-darkFiltersClearAllButtonBorder dark:hover:text-darkFiltersClearAllButtonTextHover dark:hover:bg-darkFiltersClearAllButtonBackgroundHover disabled:opacity-50 disabled:cursor-not-allowed"
|
|
128
142
|
@click="clear">{{ $t('Clear all') }}</button>
|
|
129
143
|
|
|
130
144
|
</div>
|
|
@@ -136,17 +150,17 @@
|
|
|
136
150
|
</template>
|
|
137
151
|
|
|
138
152
|
<script setup>
|
|
139
|
-
import { watch, computed } from 'vue';
|
|
153
|
+
import { watch, computed, ref, reactive } from 'vue';
|
|
140
154
|
import { useI18n } from 'vue-i18n';
|
|
141
155
|
import CustomDateRangePicker from '@/components/CustomDateRangePicker.vue';
|
|
142
|
-
import { callAdminForthApi } from '@/utils';
|
|
156
|
+
import { callAdminForthApi, loadMoreForeignOptions, searchForeignOptions, createSearchInputHandlers } from '@/utils';
|
|
143
157
|
import { useRouter } from 'vue-router';
|
|
144
|
-
import { computedAsync } from '@vueuse/core'
|
|
145
158
|
import CustomRangePicker from "@/components/CustomRangePicker.vue";
|
|
146
159
|
import { useFiltersStore } from '@/stores/filters';
|
|
147
160
|
import { getCustomComponent } from '@/utils';
|
|
148
161
|
import Input from '@/afcl/Input.vue';
|
|
149
162
|
import Select from '@/afcl/Select.vue';
|
|
163
|
+
import Spinner from '@/afcl/Spinner.vue';
|
|
150
164
|
import debounce from 'debounce';
|
|
151
165
|
|
|
152
166
|
const filtersStore = useFiltersStore();
|
|
@@ -165,31 +179,54 @@ const columnsWithFilter = computed(
|
|
|
165
179
|
() => props.columns?.filter(column => column.showIn.filter) || []
|
|
166
180
|
);
|
|
167
181
|
|
|
168
|
-
const columnOptions =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
});
|
|
186
|
-
ret[column.name] = list.items;
|
|
182
|
+
const columnOptions = ref({});
|
|
183
|
+
const columnLoadingState = reactive({});
|
|
184
|
+
const columnOffsets = reactive({});
|
|
185
|
+
const columnEmptyResultsCount = reactive({});
|
|
186
|
+
|
|
187
|
+
watch(() => props.columns, async (newColumns) => {
|
|
188
|
+
if (!newColumns) return;
|
|
189
|
+
|
|
190
|
+
for (const column of newColumns) {
|
|
191
|
+
if (column.foreignResource) {
|
|
192
|
+
if (!columnOptions.value[column.name]) {
|
|
193
|
+
columnOptions.value[column.name] = [];
|
|
194
|
+
columnLoadingState[column.name] = { loading: false, hasMore: true };
|
|
195
|
+
columnOffsets[column.name] = 0;
|
|
196
|
+
columnEmptyResultsCount[column.name] = 0;
|
|
197
|
+
|
|
198
|
+
await loadMoreOptions(column.name);
|
|
187
199
|
}
|
|
188
|
-
}
|
|
189
|
-
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}, { immediate: true });
|
|
203
|
+
|
|
204
|
+
// Function to load more options for a specific column
|
|
205
|
+
async function loadMoreOptions(columnName, searchTerm = '') {
|
|
206
|
+
return loadMoreForeignOptions({
|
|
207
|
+
columnName,
|
|
208
|
+
searchTerm,
|
|
209
|
+
columns: props.columns,
|
|
210
|
+
resourceId: router.currentRoute.value.params.resourceId,
|
|
211
|
+
columnOptions,
|
|
212
|
+
columnLoadingState,
|
|
213
|
+
columnOffsets,
|
|
214
|
+
columnEmptyResultsCount
|
|
215
|
+
});
|
|
216
|
+
}
|
|
190
217
|
|
|
191
|
-
|
|
192
|
-
|
|
218
|
+
async function searchOptions(columnName, searchTerm) {
|
|
219
|
+
return searchForeignOptions({
|
|
220
|
+
columnName,
|
|
221
|
+
searchTerm,
|
|
222
|
+
columns: props.columns,
|
|
223
|
+
resourceId: router.currentRoute.value.params.resourceId,
|
|
224
|
+
columnOptions,
|
|
225
|
+
columnLoadingState,
|
|
226
|
+
columnOffsets,
|
|
227
|
+
columnEmptyResultsCount
|
|
228
|
+
});
|
|
229
|
+
}
|
|
193
230
|
|
|
194
231
|
|
|
195
232
|
// sync 'body' class 'overflow-hidden' with show prop show
|
|
@@ -221,10 +258,18 @@ const onFilterInput = computed(() => {
|
|
|
221
258
|
}, {});
|
|
222
259
|
});
|
|
223
260
|
|
|
261
|
+
const onSearchInput = computed(() => {
|
|
262
|
+
return createSearchInputHandlers(
|
|
263
|
+
props.columns,
|
|
264
|
+
searchOptions,
|
|
265
|
+
(column) => column.filterOptions?.debounceTimeMs || 300
|
|
266
|
+
);
|
|
267
|
+
});
|
|
268
|
+
|
|
224
269
|
function setFilterItem({ column, operator, value }) {
|
|
225
270
|
|
|
226
271
|
const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
|
|
227
|
-
if (value === undefined) {
|
|
272
|
+
if (value === undefined || value === '' || value === null) {
|
|
228
273
|
if (index !== -1) {
|
|
229
274
|
filtersStore.filters.splice(index, 1);
|
|
230
275
|
}
|
|
@@ -239,11 +284,12 @@ function setFilterItem({ column, operator, value }) {
|
|
|
239
284
|
}
|
|
240
285
|
|
|
241
286
|
function getFilterItem({ column, operator }) {
|
|
242
|
-
|
|
287
|
+
const filterValue = filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value;
|
|
288
|
+
return filterValue !== undefined ? filterValue : '';
|
|
243
289
|
}
|
|
244
290
|
|
|
245
291
|
async function clear() {
|
|
246
|
-
filtersStore.
|
|
292
|
+
filtersStore.filters = [...filtersStore.filters.filter(f => filtersStore.shouldFilterBeHidden(f.field))];
|
|
247
293
|
emits('update:filters', [...filtersStore.filters]);
|
|
248
294
|
}
|
|
249
295
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="rounded-lg shadow-resourseFormShadow dark:shadow-darkResourseFormShadow dark:shadow-2xl">
|
|
3
|
-
<div v-if="group.groupName && !group.noTitle" class="text-md font-semibold px-6 py-3 flex flex-1 items-center dark:border-
|
|
3
|
+
<div v-if="group.groupName && !group.noTitle" class="text-md font-semibold px-6 py-3 flex flex-1 items-center dark:border-darkFormBorder text-lightListTableHeadingText bg-lightFormHeading dark:bg-darkFormHeading dark:text-darkListTableHeadingText rounded-t-lg">
|
|
4
4
|
{{ group.groupName }}
|
|
5
5
|
</div>
|
|
6
|
-
<table class="w-full text-sm text-left rtl:text-right text-
|
|
7
|
-
<thead v-if="!allColumnsHaveCustomComponent" class="text-xs text-
|
|
6
|
+
<table class="w-full text-sm text-left rtl:text-right text-lightFormFieldTextColor dark:text-darkFormFieldTextColor">
|
|
7
|
+
<thead v-if="!allColumnsHaveCustomComponent" class="text-xs text-lightListTableHeadingText uppercase dark:text-darkListTableHeadingText bg-lightFormHeading dark:bg-darkFormHeading block md:table-row-group ">
|
|
8
8
|
<tr>
|
|
9
9
|
<th scope="col" :class="{'rounded-tl-lg': !group.groupName}" class="px-6 py-3 hidden md:w-52 md:table-cell">
|
|
10
10
|
{{ $t('Field') }}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
v-for="(column, i) in group.columns"
|
|
20
20
|
:key="column.name"
|
|
21
21
|
v-if="currentValues !== null"
|
|
22
|
-
class="bg-
|
|
22
|
+
class="bg-lightForm dark:bg-darkForm dark:border-darkFormBorder block md:table-row"
|
|
23
23
|
:class="{ 'border-b': i !== group.columns.length - 1}"
|
|
24
24
|
>
|
|
25
25
|
<td class="px-6 py-4 flex items-center block md:table-cell pb-0 md:pb-4"
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<Tooltip v-if="column.required[mode]">
|
|
30
30
|
|
|
31
31
|
<IconExclamationCircleSolid v-if="column.required[mode]" class="w-4 h-4"
|
|
32
|
-
:class="(columnError(column) && validating) ? 'text-
|
|
32
|
+
:class="(columnError(column) && validating) ? 'text-lightInputErrorColor dark:text-darkInputErrorColor' : 'text-lightRequiredIconColor dark:text-darkRequiredIconColor'"
|
|
33
33
|
/>
|
|
34
34
|
|
|
35
35
|
<template #tooltip>
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
@update:emptiness="customComponentsEmptiness[$event.name] = $event.value"
|
|
56
56
|
:readonly="readonlyColumns?.includes(column.name)"
|
|
57
57
|
/>
|
|
58
|
-
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-
|
|
59
|
-
<div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-
|
|
58
|
+
<div v-if="columnError(column) && validating" class="mt-1 text-xs text-lightInputErrorColor dark:text-darkInputErrorColor">{{ columnError(column) }}</div>
|
|
59
|
+
<div v-if="column.editingNote && column.editingNote[mode]" class="mt-1 text-xs text-lightFormFieldTextColor dark:text-darkFormFieldTextColor">{{ column.editingNote[mode] }}</div>
|
|
60
60
|
</td>
|
|
61
61
|
</tr>
|
|
62
62
|
</tbody>
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
import { ref, computed, watch, nextTick, type Ref } from 'vue';
|
|
71
71
|
import { useI18n } from 'vue-i18n';
|
|
72
72
|
import ColumnValueInputWrapper from "@/components/ColumnValueInputWrapper.vue";
|
|
73
|
+
import type { AdminForthResourceColumnInputCommon } from '@/types/Common';
|
|
73
74
|
|
|
74
75
|
const { t } = useI18n();
|
|
75
76
|
|
|
@@ -89,7 +90,7 @@
|
|
|
89
90
|
const customComponentsInValidity: Ref<Record<string, boolean>> = ref({});
|
|
90
91
|
const customComponentsEmptiness: Ref<Record<string, boolean>> = ref({});
|
|
91
92
|
const allColumnsHaveCustomComponent = computed(() => {
|
|
92
|
-
return props.group.columns.every(column => {
|
|
93
|
+
return props.group.columns.every((column: AdminForthResourceColumnInputCommon) => {
|
|
93
94
|
const componentKey = `${props.source}Row` as keyof typeof column.components;
|
|
94
95
|
return column.components?.[componentKey];
|
|
95
96
|
});
|
|
@@ -1,42 +1,66 @@
|
|
|
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 group items-center py-2 text-lightSidebarText dark:text-darkSidebarText rounded-default
|
|
4
|
+
class="af-menu-link flex group relative items-center w-full py-2 text-lightSidebarText dark:text-darkSidebarText rounded-default transition-all duration-200 ease-in-out"
|
|
5
5
|
:class="{
|
|
6
|
-
'
|
|
7
|
-
'
|
|
6
|
+
'ml-1': isSidebarIconOnly && !isSidebarHovering && isChild,
|
|
7
|
+
'hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover active:bg-lightSidebarActive dark:active:bg-darkSidebarHover': !['divider', 'gap', 'heading'].includes(item.type),
|
|
8
|
+
'px-6': (isChild && !isSidebarIconOnly && !isSidebarHovering) || (isChild && isSidebarIconOnly && isSidebarHovering),
|
|
9
|
+
'px-3.5': !isChild || (isSidebarIconOnly && !isSidebarHovering),
|
|
10
|
+
'max-w-12': isSidebarIconOnly && !isSidebarHovering,
|
|
8
11
|
'bg-lightSidebarItemActive dark:bg-darkSidebarItemActive': item.resourceId ?
|
|
9
12
|
($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
|
|
10
13
|
($route.name === item.path)
|
|
11
14
|
}"
|
|
12
15
|
>
|
|
13
|
-
<component v-if="item.icon" :is="getIcon(item.icon)"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
<component v-if="item.icon" :is="getIcon(item.icon)"
|
|
17
|
+
class="text-lightSidebarIcons dark:text-darkSidebarIcons group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover transition-all duration-200 ease-in-out"
|
|
18
|
+
:class="{
|
|
19
|
+
'min-w-4 min-h-4': isSidebarIconOnly && !isSidebarHovering && isChild,
|
|
20
|
+
'min-w-5 min-h-5': !(isSidebarIconOnly && !isSidebarHovering && isChild)
|
|
21
|
+
}" >
|
|
22
|
+
</component>
|
|
23
|
+
<span
|
|
24
|
+
class="overflow-hidden block ms-3 pr-5 text-left rtl:text-right transition-all duration-200 ease-in-out"
|
|
25
|
+
:class="{
|
|
26
|
+
'opacity-0 ms-0 translate-x-4 flex-none': isSidebarIconOnly && !isSidebarHovering,
|
|
27
|
+
'opacity-100 ms-3 translate-x-0 flex-none': isSidebarIconOnly && isSidebarHovering,
|
|
28
|
+
'opacity-100 ms-3 translate-x-0 flex-1': !isSidebarIconOnly
|
|
29
|
+
}"
|
|
30
|
+
:style="isSidebarIconOnly ? {
|
|
31
|
+
minWidth: isChild
|
|
32
|
+
? 'calc(16.5rem - 0.75rem*2 - 1.5rem*2 - 1.25rem - 0.75rem)'
|
|
33
|
+
: 'calc(16.5rem - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)',
|
|
34
|
+
width: isChild
|
|
35
|
+
? 'calc(16.5rem - 0.75rem*2 - 1.5rem*2 - 1.25rem - 0.75rem)'
|
|
36
|
+
: 'calc(16.5rem - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)'
|
|
37
|
+
} : {}"
|
|
16
38
|
>
|
|
17
|
-
|
|
18
|
-
<Tooltip v-if="item.badgeTooltip">
|
|
19
|
-
<div class="inline-flex items-center justify-center h-3 py-3 px-1 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
20
|
-
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent min-w-[1.5rem] max-w-[3rem]">{{ item.badge }}</div>
|
|
21
|
-
|
|
22
|
-
<template #tooltip>
|
|
23
|
-
{{ item.badgeTooltip }}
|
|
24
|
-
</template>
|
|
25
|
-
</Tooltip>
|
|
26
|
-
<template v-else>
|
|
27
|
-
<div class="inline-flex items-center justify-center h-3 py-3 px-1 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
28
|
-
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent min-w-[1.5rem] max-w-[3rem]">{{ item.badge }}</div>
|
|
29
|
-
</template>
|
|
30
|
-
|
|
39
|
+
{{ item.label }}
|
|
31
40
|
</span>
|
|
32
|
-
|
|
41
|
+
<span class="absolute right-1 top-1/2 -translate-y-1/2" v-if="item.badge && (!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))">
|
|
42
|
+
<Tooltip v-if="item.badgeTooltip">
|
|
43
|
+
<div class="af-badge inline-flex items-center justify-center h-3 py-2.5 px-1 ms-3 text-xs font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
44
|
+
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent min-w-[1.5rem] max-w-[3rem]">{{ item.badge }}</div>
|
|
45
|
+
<template #tooltip>
|
|
46
|
+
{{ item.badgeTooltip }}
|
|
47
|
+
</template>
|
|
48
|
+
</Tooltip>
|
|
49
|
+
<template v-else>
|
|
50
|
+
<div class="af-badge inline-flex items-center justify-center h-3 py-2.5 px-1 ms-3 text-xs font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
51
|
+
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent min-w-[1.5rem] max-w-[3rem]">{{ item.badge }}</div>
|
|
52
|
+
</template>
|
|
53
|
+
</span>
|
|
54
|
+
<div v-if="item.badge && isSidebarIconOnly && !isSidebarHovering" class="af-badge absolute right-1 top-1/2 -translate-y-1/2 inline-flex items-center justify-center h-2 w-2 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
55
|
+
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent">
|
|
56
|
+
</div>
|
|
33
57
|
</RouterLink>
|
|
34
58
|
</template>
|
|
35
59
|
|
|
36
|
-
<script setup lang="ts">
|
|
60
|
+
<script setup lang="ts">
|
|
37
61
|
import { getIcon } from '@/utils';
|
|
38
62
|
import { Tooltip } from '@/afcl';
|
|
39
|
-
const props = defineProps(['item', 'isChild']);
|
|
40
63
|
|
|
64
|
+
defineProps(['item', 'isChild', 'isSidebarIconOnly', 'isSidebarHovering']);
|
|
41
65
|
|
|
42
66
|
</script>
|
|
@@ -63,14 +63,14 @@
|
|
|
63
63
|
|
|
64
64
|
<script setup lang="ts">
|
|
65
65
|
|
|
66
|
-
import { applyRegexValidation, callAdminForthApi} from '@/utils';
|
|
66
|
+
import { applyRegexValidation, callAdminForthApi, loadMoreForeignOptions, searchForeignOptions, createSearchInputHandlers} from '@/utils';
|
|
67
67
|
import { computedAsync } from '@vueuse/core';
|
|
68
|
-
import { computed, onMounted, ref, watch } from 'vue';
|
|
68
|
+
import { computed, onMounted, reactive, ref, watch, provide, type Ref } from 'vue';
|
|
69
69
|
import { useRouter, useRoute } from 'vue-router';
|
|
70
70
|
import { useCoreStore } from "@/stores/core";
|
|
71
71
|
import GroupsTable from '@/components/GroupsTable.vue';
|
|
72
72
|
import { useI18n } from 'vue-i18n';
|
|
73
|
-
import { type AdminForthResourceCommon } from '@/types/Common';
|
|
73
|
+
import { type AdminForthResourceColumnCommon, type AdminForthResourceCommon } from '@/types/Common';
|
|
74
74
|
|
|
75
75
|
const { t } = useI18n();
|
|
76
76
|
|
|
@@ -91,11 +91,16 @@ const mode = computed(() => route.name === 'resource-create' ? 'create' : 'edit'
|
|
|
91
91
|
|
|
92
92
|
const emit = defineEmits(['update:record', 'update:isValid']);
|
|
93
93
|
|
|
94
|
-
const currentValues = ref(null);
|
|
95
|
-
const customComponentsInValidity = ref({});
|
|
96
|
-
const customComponentsEmptiness = ref({});
|
|
94
|
+
const currentValues = ref<any | any[] | null>(null);
|
|
95
|
+
const customComponentsInValidity: Ref<Record<string, AdminForthResourceColumnCommon>> = ref({});
|
|
96
|
+
const customComponentsEmptiness: Ref<Record<string, AdminForthResourceColumnCommon>> = ref({});
|
|
97
97
|
|
|
98
|
-
const
|
|
98
|
+
const columnOptions = ref<Record<string, any[]>>({});
|
|
99
|
+
const columnLoadingState = reactive<Record<string, { loading: boolean; hasMore: boolean }>>({});
|
|
100
|
+
const columnOffsets = reactive<Record<string, number>>({});
|
|
101
|
+
const columnEmptyResultsCount = reactive<Record<string, number>>({});
|
|
102
|
+
|
|
103
|
+
const columnError = (column: AdminForthResourceColumnCommon) => {
|
|
99
104
|
const val = computed(() => {
|
|
100
105
|
if (!currentValues.value) {
|
|
101
106
|
return null;
|
|
@@ -104,7 +109,7 @@ const columnError = (column) => {
|
|
|
104
109
|
return customComponentsInValidity.value?.[column.name];
|
|
105
110
|
}
|
|
106
111
|
|
|
107
|
-
if ( column.required[mode.value] ) {
|
|
112
|
+
if ( column.required?.[mode.value] ) {
|
|
108
113
|
const naturalEmptiness = currentValues.value[column.name] === undefined ||
|
|
109
114
|
currentValues.value[column.name] === null ||
|
|
110
115
|
currentValues.value[column.name] === '' ||
|
|
@@ -131,17 +136,23 @@ const columnError = (column) => {
|
|
|
131
136
|
}
|
|
132
137
|
} else if (column.isArray?.enabled) {
|
|
133
138
|
if (!column.isArray.allowDuplicateItems) {
|
|
134
|
-
if (currentValues.value[column.name].filter((value, index, self) => self.indexOf(value) !== index).length > 0) {
|
|
139
|
+
if (currentValues.value[column.name].filter((value: any, index: any, self: any) => self.indexOf(value) !== index).length > 0) {
|
|
135
140
|
return t('Array cannot contain duplicate items');
|
|
136
141
|
}
|
|
137
142
|
}
|
|
138
143
|
|
|
139
|
-
return currentValues.value[column.name] && currentValues.value[column.name].reduce((error, item) => {
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
return currentValues.value[column.name] && currentValues.value[column.name].reduce((error: any, item: any) => {
|
|
145
|
+
if (column.isArray) {
|
|
146
|
+
return error || validateValue(column.isArray.itemType, item, column) ||
|
|
147
|
+
(item === null || !item.toString() ? t('Array cannot contain empty items') : null);
|
|
148
|
+
} else {
|
|
149
|
+
return error;
|
|
150
|
+
}
|
|
142
151
|
}, null);
|
|
143
152
|
} else {
|
|
144
|
-
|
|
153
|
+
if (column.type) {
|
|
154
|
+
return validateValue(column.type, currentValues.value[column.name], column);
|
|
155
|
+
}
|
|
145
156
|
}
|
|
146
157
|
|
|
147
158
|
return null;
|
|
@@ -149,7 +160,7 @@ const columnError = (column) => {
|
|
|
149
160
|
return val.value;
|
|
150
161
|
};
|
|
151
162
|
|
|
152
|
-
const validateValue = (type, value, column) => {
|
|
163
|
+
const validateValue = (type: string, value: any, column: AdminForthResourceColumnCommon) => {
|
|
153
164
|
if (type === 'string' || type === 'text') {
|
|
154
165
|
if (column.maxLength && value?.length > column.maxLength) {
|
|
155
166
|
return t('This field must be shorter than {maxLength} characters', { maxLength: column.maxLength });
|
|
@@ -157,7 +168,7 @@ const validateValue = (type, value, column) => {
|
|
|
157
168
|
|
|
158
169
|
if (column.minLength && value?.length < column.minLength) {
|
|
159
170
|
// if column.required[mode.value] is false, then we check if the field is empty
|
|
160
|
-
let needToCheckEmpty = column.required[mode.value] || value?.length > 0;
|
|
171
|
+
let needToCheckEmpty = column.required?.[mode.value] || value?.length > 0;
|
|
161
172
|
if (!needToCheckEmpty) {
|
|
162
173
|
return null;
|
|
163
174
|
}
|
|
@@ -186,10 +197,10 @@ const validateValue = (type, value, column) => {
|
|
|
186
197
|
};
|
|
187
198
|
|
|
188
199
|
|
|
189
|
-
const setCurrentValue = (key, value, index=null) => {
|
|
200
|
+
const setCurrentValue = (key: any, value: any, index = null) => {
|
|
190
201
|
const col = props.resource.columns.find((column) => column.name === key);
|
|
191
202
|
// if field is an array, we need to update the array or individual element
|
|
192
|
-
if (col.type === 'json' && col.isArray?.enabled) {
|
|
203
|
+
if (((col?.type && col.type === 'json') && (col?.type && col.isArray?.enabled))) {
|
|
193
204
|
if (index === null) {
|
|
194
205
|
currentValues.value[key] = value;
|
|
195
206
|
} else if (index === currentValues.value[key].length) {
|
|
@@ -204,12 +215,12 @@ const setCurrentValue = (key, value, index=null) => {
|
|
|
204
215
|
} else {
|
|
205
216
|
currentValues.value[key][index] = value;
|
|
206
217
|
}
|
|
207
|
-
if (['text', 'richtext', 'string'].includes(col.isArray.itemType) && col.enforceLowerCase) {
|
|
218
|
+
if (col?.isArray && ['text', 'richtext', 'string'].includes(col.isArray.itemType) && col.enforceLowerCase) {
|
|
208
219
|
currentValues.value[key][index] = currentValues.value[key][index].toLowerCase();
|
|
209
220
|
}
|
|
210
221
|
}
|
|
211
222
|
} else {
|
|
212
|
-
if (['integer', 'float', 'decimal'].includes(col.type)) {
|
|
223
|
+
if (col?.type && ['integer', 'float', 'decimal'].includes(col.type)) {
|
|
213
224
|
if (value || value === 0) {
|
|
214
225
|
currentValues.value[key] = +value;
|
|
215
226
|
} else {
|
|
@@ -218,7 +229,7 @@ const setCurrentValue = (key, value, index=null) => {
|
|
|
218
229
|
} else {
|
|
219
230
|
currentValues.value[key] = value;
|
|
220
231
|
}
|
|
221
|
-
if (['text', 'richtext', 'string'].includes(col
|
|
232
|
+
if (col?.type && ['text', 'richtext', 'string'].includes(col?.type) && col.enforceLowerCase) {
|
|
222
233
|
currentValues.value[key] = currentValues.value[key].toLowerCase();
|
|
223
234
|
}
|
|
224
235
|
}
|
|
@@ -239,11 +250,28 @@ const setCurrentValue = (key, value, index=null) => {
|
|
|
239
250
|
emit('update:record', up);
|
|
240
251
|
};
|
|
241
252
|
|
|
253
|
+
watch(() => props.resource.columns, async (newColumns) => {
|
|
254
|
+
if (!newColumns) return;
|
|
255
|
+
|
|
256
|
+
for (const column of newColumns) {
|
|
257
|
+
if (column.foreignResource) {
|
|
258
|
+
if (!columnOptions.value[column.name]) {
|
|
259
|
+
columnOptions.value[column.name] = [];
|
|
260
|
+
columnLoadingState[column.name] = { loading: false, hasMore: true };
|
|
261
|
+
columnOffsets[column.name] = 0;
|
|
262
|
+
columnEmptyResultsCount[column.name] = 0;
|
|
263
|
+
|
|
264
|
+
await loadMoreOptions(column.name);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}, { immediate: true });
|
|
269
|
+
|
|
242
270
|
onMounted(() => {
|
|
243
271
|
currentValues.value = Object.assign({}, props.record);
|
|
244
272
|
// json values should transform to string
|
|
245
273
|
props.resource.columns.forEach((column) => {
|
|
246
|
-
if (column.type === 'json') {
|
|
274
|
+
if (column.type === 'json' && currentValues.value) {
|
|
247
275
|
if (column.isArray?.enabled) {
|
|
248
276
|
// if value is null or undefined, we should set it to empty array
|
|
249
277
|
if (!currentValues.value[column.name]) {
|
|
@@ -266,33 +294,35 @@ onMounted(() => {
|
|
|
266
294
|
emit('update:isValid', isValid.value);
|
|
267
295
|
});
|
|
268
296
|
|
|
269
|
-
|
|
270
|
-
return (
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
async function loadMoreOptions(columnName: string, searchTerm = '') {
|
|
298
|
+
return loadMoreForeignOptions({
|
|
299
|
+
columnName,
|
|
300
|
+
searchTerm,
|
|
301
|
+
columns: props.resource.columns,
|
|
302
|
+
resourceId: router.currentRoute.value.params.resourceId as string,
|
|
303
|
+
columnOptions,
|
|
304
|
+
columnLoadingState,
|
|
305
|
+
columnOffsets,
|
|
306
|
+
columnEmptyResultsCount
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function searchOptions(columnName: string, searchTerm: string) {
|
|
311
|
+
return searchForeignOptions({
|
|
312
|
+
columnName,
|
|
313
|
+
searchTerm,
|
|
314
|
+
columns: props.resource.columns,
|
|
315
|
+
resourceId: router.currentRoute.value.params.resourceId as string,
|
|
316
|
+
columnOptions,
|
|
317
|
+
columnLoadingState,
|
|
318
|
+
columnOffsets,
|
|
319
|
+
columnEmptyResultsCount
|
|
320
|
+
});
|
|
321
|
+
}
|
|
292
322
|
|
|
293
323
|
|
|
294
324
|
const editableColumns = computed(() => {
|
|
295
|
-
return props.resource?.columns?.filter(column => column.showIn[mode.value]);
|
|
325
|
+
return props.resource?.columns?.filter(column => column.showIn?.[mode.value]);
|
|
296
326
|
});
|
|
297
327
|
|
|
298
328
|
const isValid = computed(() => {
|
|
@@ -302,12 +332,14 @@ const isValid = computed(() => {
|
|
|
302
332
|
|
|
303
333
|
const groups = computed(() => {
|
|
304
334
|
let fieldGroupType;
|
|
305
|
-
if
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
335
|
+
if(coreStore.resource){
|
|
336
|
+
if (mode.value === 'edit' && coreStore.resource.options?.editFieldGroups !== undefined) {
|
|
337
|
+
fieldGroupType = coreStore.resource.options.editFieldGroups;
|
|
338
|
+
} else if (mode.value === 'create' && coreStore.resource.options?.createFieldGroups !== undefined) {
|
|
339
|
+
fieldGroupType = coreStore.resource.options.createFieldGroups;
|
|
340
|
+
} else {
|
|
341
|
+
fieldGroupType = coreStore.resource.options?.fieldGroups;
|
|
342
|
+
}
|
|
311
343
|
}
|
|
312
344
|
return fieldGroupType ?? [];
|
|
313
345
|
});
|
|
@@ -330,6 +362,17 @@ const getOtherColumns = () => {
|
|
|
330
362
|
|
|
331
363
|
const otherColumns = getOtherColumns();
|
|
332
364
|
|
|
365
|
+
const onSearchInput = computed(() => {
|
|
366
|
+
return createSearchInputHandlers(
|
|
367
|
+
props.resource.columns,
|
|
368
|
+
searchOptions
|
|
369
|
+
);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
provide('columnLoadingState', columnLoadingState);
|
|
373
|
+
provide('onSearchInput', onSearchInput);
|
|
374
|
+
provide('loadMoreOptions', loadMoreOptions);
|
|
375
|
+
|
|
333
376
|
watch(() => isValid.value, (value) => {
|
|
334
377
|
emit('update:isValid', value);
|
|
335
378
|
});
|