adminforth 1.2.41 → 1.2.43
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 +1 -2
- package/dist/dataConnectors/mongo.js +1 -2
- package/dist/index.js +5 -0
- package/dist/spa/index.html +4 -4
- package/dist/spa/package-lock.json +206 -5
- package/dist/spa/package.json +8 -2
- package/dist/spa/public/assets/favicon.png +0 -0
- package/dist/spa/src/App.vue +171 -72
- package/dist/spa/src/assets/logo.svg +19 -1
- package/dist/spa/src/components/AcceptModal.vue +3 -10
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +1 -1
- package/dist/spa/src/components/CustomDatePicker.vue +19 -6
- package/dist/spa/src/components/CustomDateRangePicker.vue +14 -5
- package/dist/spa/src/components/CustomRangePicker.vue +148 -0
- package/dist/spa/src/components/Dropdown.vue +19 -5
- package/dist/spa/src/components/Filters.vue +91 -37
- package/dist/spa/src/components/MenuLink.vue +2 -2
- package/dist/spa/src/components/ResourceForm.vue +163 -103
- 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 +22 -6
- 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 -19
- package/dist/spa/src/spa_types/core.ts +51 -0
- package/dist/spa/src/stores/core.ts +65 -59
- 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 +65 -8
- package/dist/spa/src/views/CreateView.vue +76 -16
- package/dist/spa/src/views/EditView.vue +76 -14
- package/dist/spa/src/views/ListView.vue +88 -360
- package/dist/spa/src/views/LoginView.vue +17 -5
- package/dist/spa/src/views/ResourceParent.vue +1 -1
- package/dist/spa/src/views/ShowView.vue +114 -19
- package/dist/spa/tailwind.config.js +5 -2
- package/dist/spa/vite.config.ts +6 -0
- package/index.ts +5 -0
- package/package.json +1 -1
- package/dist/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/public/favicon.ico +0 -0
- package/dist/spa/spa/src/views/HomeView.vue +0 -8
- package/dist/spa/src/views/HomeView.vue +0 -8
- package/dist/types.js +0 -30
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-wrap gap-2">
|
|
3
|
+
<input
|
|
4
|
+
type="number" aria-describedby="helper-text-explanation"
|
|
5
|
+
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"
|
|
6
|
+
placeholder="From"
|
|
7
|
+
v-model="start"
|
|
8
|
+
>
|
|
9
|
+
|
|
10
|
+
<input
|
|
11
|
+
type="number" aria-describedby="helper-text-explanation"
|
|
12
|
+
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"
|
|
13
|
+
placeholder="To"
|
|
14
|
+
v-model="end"
|
|
15
|
+
>
|
|
16
|
+
|
|
17
|
+
<button
|
|
18
|
+
v-if="isChanged"
|
|
19
|
+
type="button"
|
|
20
|
+
class="flex items-center p-0.5 ml-auto 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"
|
|
21
|
+
@click="clear">Clear
|
|
22
|
+
</button>
|
|
23
|
+
|
|
24
|
+
<div class="w-full">
|
|
25
|
+
<vue-slider
|
|
26
|
+
class="custom-slider"
|
|
27
|
+
:dot-size="20"
|
|
28
|
+
height="7.99px"
|
|
29
|
+
:min="minFormatted"
|
|
30
|
+
:max="maxFormatted"
|
|
31
|
+
v-model="sliderValue"
|
|
32
|
+
@update:model-value="updateFromSlider($event)"
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import VueSlider from 'vue-slider-component';
|
|
39
|
+
import 'vue-slider-component/theme/antd.css'
|
|
40
|
+
import {computed, onMounted, ref, watch} from "vue";
|
|
41
|
+
import debounce from 'debounce'
|
|
42
|
+
|
|
43
|
+
const props = defineProps({
|
|
44
|
+
valueStart: {
|
|
45
|
+
default: '',
|
|
46
|
+
},
|
|
47
|
+
valueEnd: {
|
|
48
|
+
default: '',
|
|
49
|
+
},
|
|
50
|
+
min: {
|
|
51
|
+
default: 0,
|
|
52
|
+
},
|
|
53
|
+
max: {
|
|
54
|
+
default: 10,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const emit = defineEmits(['update:valueStart', 'update:valueEnd']);
|
|
59
|
+
|
|
60
|
+
const minFormatted = computed(() => Math.floor(props.min));
|
|
61
|
+
const maxFormatted = computed(() => Math.ceil(props.max));
|
|
62
|
+
|
|
63
|
+
const isChanged = computed(() => {
|
|
64
|
+
return start.value && start.value !== minFormatted.value || end.value && end.value !== maxFormatted.value;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const start = ref(props.valueStart);
|
|
68
|
+
const end = ref(props.valueEnd);
|
|
69
|
+
|
|
70
|
+
const sliderValue = ref([minFormatted.value, maxFormatted.value]);
|
|
71
|
+
|
|
72
|
+
const updateFromSlider =
|
|
73
|
+
debounce((value: [number, number]) => {
|
|
74
|
+
start.value = value[0] === minFormatted.value ? '': value[0];
|
|
75
|
+
end.value = value[1] === maxFormatted.value ? '': value[1];
|
|
76
|
+
}, 500);
|
|
77
|
+
|
|
78
|
+
onMounted(() => {
|
|
79
|
+
updateStartFromProps();
|
|
80
|
+
updateEndFromProps();
|
|
81
|
+
|
|
82
|
+
watch(() => props.valueStart, (value) => {
|
|
83
|
+
updateStartFromProps();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
watch(() => props.valueEnd, (value) => {
|
|
87
|
+
updateEndFromProps();
|
|
88
|
+
});
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
function updateStartFromProps() {
|
|
92
|
+
start.value = props.valueStart;
|
|
93
|
+
setSliderValues(start.value, end.value)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function updateEndFromProps() {
|
|
97
|
+
end.value = props.valueEnd;
|
|
98
|
+
setSliderValues(start.value, end.value)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
watch(start, () => {
|
|
102
|
+
console.log('⚡ emit', start.value)
|
|
103
|
+
emit('update:valueStart', start.value)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
watch(end, () => {
|
|
107
|
+
console.log('⚡ emit', end.value)
|
|
108
|
+
emit('update:valueEnd', end.value);
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const clear = () => {
|
|
112
|
+
start.value = ''
|
|
113
|
+
end.value = ''
|
|
114
|
+
setSliderValues('', '')
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function setSliderValues(start, end) {
|
|
118
|
+
sliderValue.value = [start || minFormatted.value, end || maxFormatted.value];
|
|
119
|
+
}
|
|
120
|
+
</script>
|
|
121
|
+
|
|
122
|
+
<style lang="scss" scoped>
|
|
123
|
+
.custom-slider {
|
|
124
|
+
&:deep(.vue-slider-rail) {
|
|
125
|
+
background-color: rgb(229 231 235);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
&:deep(.vue-slider-dot-handle) {
|
|
129
|
+
background-color: #1c64f2;
|
|
130
|
+
border: none;
|
|
131
|
+
box-shadow: none;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&:deep(.vue-slider-dot-handle:hover) {
|
|
135
|
+
background-color: #1c64f2;
|
|
136
|
+
border: none;
|
|
137
|
+
box-shadow: none;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&:deep(.vue-slider-process) {
|
|
141
|
+
background-color: rgb(225 239 254);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&:deep(.vue-slider-process:hover) {
|
|
145
|
+
background-color: rgb(225 239 254);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
</style>
|
|
@@ -6,10 +6,11 @@
|
|
|
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
|
+
{{ }}
|
|
13
|
+
<div v-for="item in selectedItems" :key="item?.name" class="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300">
|
|
13
14
|
<span>{{ item.label }}</span>
|
|
14
15
|
<button
|
|
15
16
|
type="button"
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
<IconCaretUpSolid v-else class="h-5 w-5 text-gray-400" />
|
|
35
36
|
</div>
|
|
36
37
|
</div>
|
|
37
|
-
<div v-if="showDropdown" class="absolute z-10 mt-1 w-full bg-white shadow-lg rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
|
|
38
|
+
<div v-if="showDropdown" class="absolute z-10 mt-1 w-full bg-white shadow-lg dark:shadow-black rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm">
|
|
38
39
|
<div
|
|
39
40
|
v-for="item in filteredItems"
|
|
40
41
|
:key="item.value"
|
|
@@ -66,6 +67,11 @@ const props = defineProps({
|
|
|
66
67
|
type: Boolean,
|
|
67
68
|
default: false,
|
|
68
69
|
},
|
|
70
|
+
placeholder: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: '',
|
|
73
|
+
|
|
74
|
+
},
|
|
69
75
|
});
|
|
70
76
|
|
|
71
77
|
const emit = defineEmits(['update:modelValue']);
|
|
@@ -76,10 +82,14 @@ const showDropdown = ref(false);
|
|
|
76
82
|
const selectedItems = ref([]);
|
|
77
83
|
|
|
78
84
|
function updateFromProps() {
|
|
79
|
-
console.log('⚡ updateFromProps', props.modelValue)
|
|
80
85
|
if (props.modelValue !== undefined) {
|
|
81
86
|
if (props.single) {
|
|
82
|
-
|
|
87
|
+
const el = props.options.find(item => item.value === props.modelValue);
|
|
88
|
+
if (el) {
|
|
89
|
+
selectedItems.value = [el];
|
|
90
|
+
} else {
|
|
91
|
+
selectedItems.value = [];
|
|
92
|
+
}
|
|
83
93
|
} else {
|
|
84
94
|
selectedItems.value = props.options.filter(item => props.modelValue.includes(item.value));
|
|
85
95
|
}
|
|
@@ -93,6 +103,10 @@ onMounted(() => {
|
|
|
93
103
|
updateFromProps();
|
|
94
104
|
});
|
|
95
105
|
|
|
106
|
+
watch(() => props.options, () => {
|
|
107
|
+
updateFromProps();
|
|
108
|
+
});
|
|
109
|
+
|
|
96
110
|
addClickListener();
|
|
97
111
|
|
|
98
112
|
});
|
|
@@ -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
|
|
@@ -22,11 +22,17 @@
|
|
|
22
22
|
<li v-for="c in columnsWithFilter" :key="c">
|
|
23
23
|
<p class="dark:text-gray-400">{{ c.label }}</p>
|
|
24
24
|
|
|
25
|
-
<Dropdown
|
|
26
|
-
v-if="c.
|
|
25
|
+
<Dropdown
|
|
26
|
+
v-if="c.foreignResource"
|
|
27
|
+
:options="columnOptions[c.name] || []"
|
|
28
|
+
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
29
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
30
|
+
/>
|
|
31
|
+
<Dropdown
|
|
32
|
+
v-else-if="c.type === 'boolean'"
|
|
27
33
|
:options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
|
|
28
34
|
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
29
|
-
: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 || []"
|
|
30
36
|
/>
|
|
31
37
|
|
|
32
38
|
<Dropdown
|
|
@@ -34,7 +40,7 @@
|
|
|
34
40
|
:options="c.enum"
|
|
35
41
|
:allowCustom="c.allowCustom"
|
|
36
42
|
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
37
|
-
: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 || []"
|
|
38
44
|
/>
|
|
39
45
|
|
|
40
46
|
<input
|
|
@@ -48,9 +54,9 @@
|
|
|
48
54
|
<CustomDateRangePicker
|
|
49
55
|
v-else-if="['datetime'].includes(c.type)"
|
|
50
56
|
:column="c"
|
|
51
|
-
: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"
|
|
52
58
|
@update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
|
|
53
|
-
: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"
|
|
54
60
|
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
|
|
55
61
|
/>
|
|
56
62
|
|
|
@@ -62,22 +68,32 @@
|
|
|
62
68
|
:value="getFilterItem({ column: c, operator: 'ilike' })"
|
|
63
69
|
>
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
<CustomRangePicker
|
|
72
|
+
v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
|
|
73
|
+
:min="c.min"
|
|
74
|
+
:max="c.max"
|
|
75
|
+
:valueStart="getFilterItem({ column: c, operator: 'gte' })"
|
|
76
|
+
@update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
|
|
77
|
+
:valueEnd="getFilterItem({ column: c, operator: 'lte' })"
|
|
78
|
+
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
|
|
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>
|
|
81
97
|
|
|
82
98
|
</li>
|
|
83
99
|
</ul>
|
|
@@ -85,33 +101,71 @@
|
|
|
85
101
|
|
|
86
102
|
<div class="flex justify-end gap-2">
|
|
87
103
|
<button
|
|
88
|
-
:disabled="!filters.length"
|
|
104
|
+
:disabled="!filtersStore.filters.length"
|
|
89
105
|
type="button"
|
|
90
|
-
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"
|
|
91
107
|
@click="clear">Clear all</button>
|
|
92
108
|
|
|
93
109
|
</div>
|
|
94
110
|
</div>
|
|
95
111
|
|
|
96
|
-
<div v-if="show"
|
|
112
|
+
<div v-if="show" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
|
|
97
113
|
@click="$emit('hide')">
|
|
98
114
|
</div>
|
|
99
115
|
</template>
|
|
100
116
|
|
|
101
117
|
<script setup>
|
|
102
|
-
import { watch, computed } from 'vue'
|
|
118
|
+
import { watch, computed, ref, onMounted } from 'vue'
|
|
103
119
|
import Dropdown from '@/components/Dropdown.vue';
|
|
104
120
|
import CustomDateRangePicker from '@/components/CustomDateRangePicker.vue';
|
|
121
|
+
import { callAdminForthApi } from '@/utils';
|
|
122
|
+
import { useRouter } from 'vue-router';
|
|
123
|
+
import { computedAsync } from '@vueuse/core'
|
|
124
|
+
import CustomRangePicker from "@/components/CustomRangePicker.vue";
|
|
125
|
+
import { useFiltersStore } from '@/stores/filters';
|
|
126
|
+
|
|
127
|
+
const filtersStore = useFiltersStore();
|
|
128
|
+
|
|
105
129
|
|
|
106
130
|
// props: columns
|
|
107
131
|
// add support for v-model:filers
|
|
108
132
|
const props = defineProps(['columns', 'filters', 'show', 'columnsMinMax']);
|
|
109
133
|
const emits = defineEmits(['update:filters', 'hide']);
|
|
110
134
|
|
|
111
|
-
const
|
|
135
|
+
const router = useRouter();
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
const columnsWithFilter = computed(
|
|
112
139
|
() => props.columns?.filter(column => column.showIn.includes('filter')) || []
|
|
113
140
|
);
|
|
114
141
|
|
|
142
|
+
const columnOptions = computedAsync(async () => {
|
|
143
|
+
const ret = {};
|
|
144
|
+
if (!props.columns) {
|
|
145
|
+
return ret;
|
|
146
|
+
}
|
|
147
|
+
await Promise.all(
|
|
148
|
+
Object.values(props.columns).map(async (column) => {
|
|
149
|
+
if (column.foreignResource) {
|
|
150
|
+
const list = await callAdminForthApi({
|
|
151
|
+
method: 'POST',
|
|
152
|
+
path: `/get_resource_foreign_data`,
|
|
153
|
+
body: {
|
|
154
|
+
resourceId: router.currentRoute.value.params.resourceId,
|
|
155
|
+
column: column.name,
|
|
156
|
+
limit: 1000,
|
|
157
|
+
offset: 0,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
ret[column.name] = list.items;
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return ret;
|
|
166
|
+
}, {});
|
|
167
|
+
|
|
168
|
+
|
|
115
169
|
// sync 'body' class 'overflow-hidden' with show prop show
|
|
116
170
|
watch(() => props.show, (show) => {
|
|
117
171
|
if (show) {
|
|
@@ -130,28 +184,28 @@ watch(() => props.show, (show) => {
|
|
|
130
184
|
|
|
131
185
|
function setFilterItem({ column, operator, value }) {
|
|
132
186
|
|
|
133
|
-
const index =
|
|
187
|
+
const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
|
|
134
188
|
if (value === undefined) {
|
|
135
189
|
if (index !== -1) {
|
|
136
|
-
|
|
190
|
+
filtersStore.filters.splice(index, 1);
|
|
137
191
|
}
|
|
138
|
-
emits('update:filters', [...
|
|
192
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
139
193
|
return;
|
|
140
194
|
} else {
|
|
141
195
|
if (index === -1) {
|
|
142
|
-
|
|
196
|
+
filtersStore.setFilter({ field: column.name, value, operator });
|
|
143
197
|
} else {
|
|
144
|
-
|
|
198
|
+
filtersStore.setFilters([...filtersStore.filters.slice(0, index), { field: column.name, value, operator }, ...filtersStore.filters.slice(index + 1)])
|
|
145
199
|
}
|
|
146
200
|
}
|
|
147
|
-
emits('update:filters', [...
|
|
201
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
148
202
|
}
|
|
149
203
|
|
|
150
204
|
function getFilterItem({ column, operator }) {
|
|
151
|
-
return
|
|
205
|
+
return filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
|
|
152
206
|
}
|
|
153
207
|
|
|
154
208
|
async function clear() {
|
|
155
|
-
|
|
209
|
+
filtersStore.clearFilters();
|
|
156
210
|
}
|
|
157
211
|
</script>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<RouterLink
|
|
3
3
|
:to="{name: item.resourceId ? 'resource-list' : item.path, params: item.resourceId ? { resourceId: item.resourceId }: {}}"
|
|
4
|
-
class="flex items-center py-2 text-
|
|
4
|
+
class="flex items-center py-2 text-nav-menu-text rounded-default hover:bg-nav-menu-bg-hover active:bg-nav-menu-bg-active" role="menuitem"
|
|
5
5
|
:class="{
|
|
6
6
|
'px-4': isChild,
|
|
7
7
|
'px-2': !isChild,
|
|
8
|
-
'bg-
|
|
8
|
+
'bg-nav-menu-bg-active dark:bg-gray-700': item.resourceId ?
|
|
9
9
|
($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
|
|
10
10
|
($route.name === item.path)
|
|
11
11
|
}"
|