adminforth 1.3.55-next.0 → 1.3.55
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/spa/.eslintrc.cjs +14 -0
- package/dist/spa/README.md +39 -0
- package/dist/spa/env.d.ts +1 -0
- package/dist/spa/index.html +23 -0
- package/dist/spa/package-lock.json +4659 -0
- package/dist/spa/package.json +52 -0
- package/dist/spa/postcss.config.js +6 -0
- package/dist/spa/public/assets/favicon.png +0 -0
- package/dist/spa/src/App.vue +418 -0
- package/dist/spa/src/assets/base.css +2 -0
- package/dist/spa/src/assets/logo.svg +19 -0
- package/dist/spa/src/components/AcceptModal.vue +45 -0
- package/dist/spa/src/components/Breadcrumbs.vue +41 -0
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +26 -0
- package/dist/spa/src/components/CustomDatePicker.vue +176 -0
- package/dist/spa/src/components/CustomDateRangePicker.vue +218 -0
- package/dist/spa/src/components/CustomRangePicker.vue +156 -0
- package/dist/spa/src/components/Dropdown.vue +168 -0
- package/dist/spa/src/components/Filters.vue +222 -0
- package/dist/spa/src/components/HelloWorld.vue +17 -0
- package/dist/spa/src/components/MenuLink.vue +27 -0
- package/dist/spa/src/components/ResourceForm.vue +325 -0
- package/dist/spa/src/components/ResourceListTable.vue +466 -0
- package/dist/spa/src/components/SingleSkeletLoader.vue +13 -0
- package/dist/spa/src/components/SkeleteLoader.vue +23 -0
- package/dist/spa/src/components/ThreeDotsMenu.vue +43 -0
- package/dist/spa/src/components/Toast.vue +78 -0
- package/dist/spa/src/components/ValueRenderer.vue +141 -0
- package/dist/spa/src/components/icons/IconCalendar.vue +5 -0
- package/dist/spa/src/components/icons/IconCommunity.vue +7 -0
- package/dist/spa/src/components/icons/IconDocumentation.vue +7 -0
- package/dist/spa/src/components/icons/IconEcosystem.vue +7 -0
- package/dist/spa/src/components/icons/IconSupport.vue +7 -0
- package/dist/spa/src/components/icons/IconTime.vue +5 -0
- package/dist/spa/src/components/icons/IconTooling.vue +19 -0
- package/dist/spa/src/composables/useFrontendApi.ts +26 -0
- package/dist/spa/src/composables/useStores.ts +131 -0
- package/dist/spa/src/index.scss +31 -0
- package/dist/spa/src/main.ts +18 -0
- package/dist/spa/src/renderers/CompactUUID.vue +48 -0
- package/dist/spa/src/renderers/CountryFlag.vue +69 -0
- package/dist/spa/src/router/index.ts +59 -0
- package/dist/spa/src/spa_types/core.ts +53 -0
- package/dist/spa/src/stores/core.ts +148 -0
- package/dist/spa/src/stores/filters.ts +27 -0
- package/dist/spa/src/stores/modal.ts +48 -0
- package/dist/spa/src/stores/toast.ts +31 -0
- package/dist/spa/src/stores/user.ts +72 -0
- package/dist/spa/src/types/AdminForthConfig.ts +1762 -0
- package/dist/spa/src/types/FrontendAPI.ts +143 -0
- package/dist/spa/src/utils.ts +160 -0
- package/dist/spa/src/views/CreateView.vue +167 -0
- package/dist/spa/src/views/EditView.vue +170 -0
- package/dist/spa/src/views/ListView.vue +352 -0
- package/dist/spa/src/views/LoginView.vue +192 -0
- package/dist/spa/src/views/ResourceParent.vue +17 -0
- package/dist/spa/src/views/ShowView.vue +194 -0
- package/dist/spa/tailwind.config.js +17 -0
- package/dist/spa/tsconfig.app.json +14 -0
- package/dist/spa/tsconfig.json +11 -0
- package/dist/spa/tsconfig.node.json +19 -0
- package/dist/spa/vite.config.ts +56 -0
- package/package.json +2 -2
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative inline-block w-full" id="dropd">
|
|
3
|
+
<div class="relative">
|
|
4
|
+
<input
|
|
5
|
+
type="text"
|
|
6
|
+
v-model="search"
|
|
7
|
+
@focus="showDropdown = true"
|
|
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 ? '' : placeholder || 'Select...'"
|
|
10
|
+
/>
|
|
11
|
+
<div class="absolute inset-y-0 left-2 flex items-center pr-2 flex-wrap">
|
|
12
|
+
{{ }}
|
|
13
|
+
<div v-for="item in selectedItems" :key="item?.name" class="bg-lightPrimaryOpacity text-lightPrimary text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-darkPrimaryOpacity dark:text-darkPrimary">
|
|
14
|
+
<span>{{ item.label }}</span>
|
|
15
|
+
<button
|
|
16
|
+
type="button"
|
|
17
|
+
@click="toogleItem(item)"
|
|
18
|
+
class="z-index-100 flex-shrink-0 ml-1 h-4 w-4 -mr-1 rounded-full inline-flex items-center justify-center text-gray-400 hover:text-gray-500 focus:outline-none focus:text-gray-500 focus:bg-gray-100"
|
|
19
|
+
>
|
|
20
|
+
<span class="sr-only">Remove item</span>
|
|
21
|
+
<svg class="h-2 w-2" stroke="currentColor" fill="none" viewBox="0 0 8 8">
|
|
22
|
+
<path
|
|
23
|
+
stroke-linecap="round"
|
|
24
|
+
stroke-linejoin="round"
|
|
25
|
+
stroke-width="1.5"
|
|
26
|
+
d="M1 1l6 6m0-6L1 7"
|
|
27
|
+
/>
|
|
28
|
+
</svg>
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="absolute inset-y-0 right-2 flex items-center pointer-events-none">
|
|
33
|
+
<!-- triangle icon -->
|
|
34
|
+
<IconCaretDownSolid v-if="!showDropdown" class="h-5 w-5 text-gray-400" />
|
|
35
|
+
<IconCaretUpSolid v-else class="h-5 w-5 text-gray-400" />
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
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">
|
|
39
|
+
<div
|
|
40
|
+
v-for="item in filteredItems"
|
|
41
|
+
:key="item.value"
|
|
42
|
+
class="px-4 py-2 cursor-pointer hover:bg-gray-100"
|
|
43
|
+
:class="{ 'bg-lightPrimaryOpacity': selectedItems.includes(item) }"
|
|
44
|
+
@click="toogleItem(item)"
|
|
45
|
+
>
|
|
46
|
+
<label :for="item.value">{{ item.label }}</label>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script setup>
|
|
53
|
+
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
|
54
|
+
import { IconCaretDownSolid, IconCaretUpSolid } from '@iconify-prerendered/vue-flowbite';
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
const props = defineProps({
|
|
58
|
+
options: Array,
|
|
59
|
+
modelValue: {
|
|
60
|
+
default: undefined,
|
|
61
|
+
},
|
|
62
|
+
allowCustom: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: false,
|
|
65
|
+
},
|
|
66
|
+
single: {
|
|
67
|
+
type: Boolean,
|
|
68
|
+
default: false,
|
|
69
|
+
},
|
|
70
|
+
placeholder: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: '',
|
|
73
|
+
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const emit = defineEmits(['update:modelValue']);
|
|
78
|
+
|
|
79
|
+
const search = ref('');
|
|
80
|
+
const showDropdown = ref(false);
|
|
81
|
+
|
|
82
|
+
const selectedItems = ref([]);
|
|
83
|
+
|
|
84
|
+
function updateFromProps() {
|
|
85
|
+
if (props.modelValue !== undefined) {
|
|
86
|
+
if (props.single) {
|
|
87
|
+
const el = props.options.find(item => item.value === props.modelValue);
|
|
88
|
+
if (el) {
|
|
89
|
+
selectedItems.value = [el];
|
|
90
|
+
} else {
|
|
91
|
+
selectedItems.value = [];
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
selectedItems.value = props.options.filter(item => props.modelValue.includes(item.value));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
onMounted(() => {
|
|
100
|
+
updateFromProps();
|
|
101
|
+
|
|
102
|
+
watch(() => props.modelValue, (value) => {
|
|
103
|
+
updateFromProps();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
watch(() => props.options, () => {
|
|
107
|
+
updateFromProps();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
addClickListener();
|
|
111
|
+
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const filteredItems = computed(() => {
|
|
115
|
+
return props.options.filter(item =>
|
|
116
|
+
item.label.toLowerCase().includes(search.value.toLowerCase())
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const handleClickOutside = (event) => {
|
|
121
|
+
if (!event.target.closest('#dropd')) {
|
|
122
|
+
showDropdown.value = false;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const addClickListener = () => {
|
|
127
|
+
document.addEventListener('click', handleClickOutside);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const removeClickListener = () => {
|
|
131
|
+
document.removeEventListener('click', handleClickOutside);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const toogleItem = (item) => {
|
|
135
|
+
if (selectedItems.value.includes(item)) {
|
|
136
|
+
selectedItems.value = selectedItems.value.filter(i => i !== item);
|
|
137
|
+
} else {
|
|
138
|
+
if (props.single) {
|
|
139
|
+
selectedItems.value = [item];
|
|
140
|
+
} else {
|
|
141
|
+
selectedItems.value = [...selectedItems.value, item];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (props.single) {
|
|
145
|
+
showDropdown.value = false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
const list = selectedItems.value.map(item => item.value);
|
|
150
|
+
const updValue = list.length ? list : null;
|
|
151
|
+
let emitValue;
|
|
152
|
+
if (props.single) {
|
|
153
|
+
emitValue = updValue ? updValue[0] : null;
|
|
154
|
+
} else {
|
|
155
|
+
emitValue = updValue;
|
|
156
|
+
}
|
|
157
|
+
console.log('⚡ emit', emitValue)
|
|
158
|
+
emit('update:modelValue', emitValue);
|
|
159
|
+
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
onUnmounted(() => {
|
|
164
|
+
removeClickListener();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
</script>
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- drawer component -->
|
|
3
|
+
<div id="drawer-navigation"
|
|
4
|
+
|
|
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-gray-900"
|
|
6
|
+
|
|
7
|
+
:class="show ? 'top-0 transform-none' : ''"
|
|
8
|
+
tabindex="-1" aria-labelledby="drawer-navigation-label"
|
|
9
|
+
:style="{ height: `calc(100vh ` }"
|
|
10
|
+
>
|
|
11
|
+
<h5 id="drawer-navigation-label" class="text-base font-semibold text-gray-500 uppercase dark:text-gray-400">
|
|
12
|
+
Filters
|
|
13
|
+
|
|
14
|
+
<button type="button" @click="$emit('hide')" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 absolute end-2.5 inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white" >
|
|
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
|
+
<span class="sr-only">Close menu</span>
|
|
17
|
+
</button>
|
|
18
|
+
</h5>
|
|
19
|
+
|
|
20
|
+
<div class="py-4 ">
|
|
21
|
+
<ul class="space-y-3 font-medium">
|
|
22
|
+
<li v-for="c in columnsWithFilter" :key="c">
|
|
23
|
+
<p class="dark:text-gray-400">{{ c.label }}</p>
|
|
24
|
+
|
|
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'"
|
|
33
|
+
:options="[{ label: 'Yes', value: true }, { label: 'No', value: false }, { label: 'Unset', value: null }]"
|
|
34
|
+
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
35
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
36
|
+
/>
|
|
37
|
+
|
|
38
|
+
<Dropdown
|
|
39
|
+
v-else-if="c.enum"
|
|
40
|
+
:options="c.enum"
|
|
41
|
+
:allowCustom="c.allowCustom"
|
|
42
|
+
@update:modelValue="setFilterItem({ column: c, operator: 'in', value: $event })"
|
|
43
|
+
:modelValue="filtersStore.filters.find(f => f.field === c.name && f.operator === 'in')?.value || []"
|
|
44
|
+
/>
|
|
45
|
+
|
|
46
|
+
<input
|
|
47
|
+
v-else-if="[ 'string', 'text' ].includes(c.type)"
|
|
48
|
+
type="text" class="w-full py-1 px-2 border border-gray-300 rounded-md 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"
|
|
49
|
+
placeholder="Search"
|
|
50
|
+
@input="setFilterItem({ column: c, operator: 'ilike', value: $event.target.value || undefined })"
|
|
51
|
+
:value="getFilterItem({ column: c, operator: 'ilike' })"
|
|
52
|
+
>
|
|
53
|
+
|
|
54
|
+
<CustomDateRangePicker
|
|
55
|
+
v-else-if="['datetime'].includes(c.type)"
|
|
56
|
+
:column="c"
|
|
57
|
+
:valueStart="filtersStore.filters.find(f => f.field === c.name && f.operator === 'gte')?.value || undefined"
|
|
58
|
+
@update:valueStart="setFilterItem({ column: c, operator: 'gte', value: $event || undefined })"
|
|
59
|
+
:valueEnd="filtersStore.filters.find(f => f.field === c.name && f.operator === 'lte')?.value || undefined"
|
|
60
|
+
@update:valueEnd="setFilterItem({ column: c, operator: 'lte', value: $event || undefined })"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<input
|
|
64
|
+
v-else-if="[ 'date', 'time' ].includes(c.type)"
|
|
65
|
+
type="text" class="w-full py-1 px-2 border border-gray-300 rounded-md"
|
|
66
|
+
placeholder="Search datetime"
|
|
67
|
+
@input="setFilterItem({ column: c, operator: 'ilike', value: $event.target.value || undefined })"
|
|
68
|
+
:value="getFilterItem({ column: c, operator: 'ilike' })"
|
|
69
|
+
>
|
|
70
|
+
|
|
71
|
+
<CustomRangePicker
|
|
72
|
+
v-else-if="['integer', 'decimal', 'float'].includes(c.type) && c.allowMinMaxQuery"
|
|
73
|
+
:min="getFilterMinValue(c.name)"
|
|
74
|
+
:max="getFilterMaxValue(c.name)"
|
|
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>
|
|
97
|
+
|
|
98
|
+
</li>
|
|
99
|
+
</ul>
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div class="flex justify-end gap-2">
|
|
103
|
+
<button
|
|
104
|
+
:disabled="!filtersStore.filters.length"
|
|
105
|
+
type="button"
|
|
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-lightPrimary 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"
|
|
107
|
+
@click="clear">Clear all</button>
|
|
108
|
+
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<div v-if="show" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-30"
|
|
113
|
+
@click="$emit('hide')">
|
|
114
|
+
</div>
|
|
115
|
+
</template>
|
|
116
|
+
|
|
117
|
+
<script setup>
|
|
118
|
+
import { watch, computed } from 'vue'
|
|
119
|
+
import Dropdown from '@/components/Dropdown.vue';
|
|
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
|
+
|
|
129
|
+
|
|
130
|
+
// props: columns
|
|
131
|
+
// add support for v-model:filers
|
|
132
|
+
const props = defineProps(['columns', 'filters', 'show', 'columnsMinMax']);
|
|
133
|
+
const emits = defineEmits(['update:filters', 'hide']);
|
|
134
|
+
|
|
135
|
+
const router = useRouter();
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
const columnsWithFilter = computed(
|
|
139
|
+
() => props.columns?.filter(column => column.showIn.includes('filter')) || []
|
|
140
|
+
);
|
|
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
|
+
|
|
169
|
+
// sync 'body' class 'overflow-hidden' with show prop show
|
|
170
|
+
watch(() => props.show, (show) => {
|
|
171
|
+
if (show) {
|
|
172
|
+
document.body.classList.add('overflow-hidden');
|
|
173
|
+
} else {
|
|
174
|
+
document.body.classList.remove('overflow-hidden');
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// filters is a array of objects
|
|
179
|
+
// {
|
|
180
|
+
// field: 'name',
|
|
181
|
+
// value: 'John',
|
|
182
|
+
// operator: 'like'
|
|
183
|
+
// }
|
|
184
|
+
|
|
185
|
+
function setFilterItem({ column, operator, value }) {
|
|
186
|
+
|
|
187
|
+
const index = filtersStore.filters.findIndex(f => f.field === column.name && f.operator === operator);
|
|
188
|
+
if (value === undefined) {
|
|
189
|
+
if (index !== -1) {
|
|
190
|
+
filtersStore.filters.splice(index, 1);
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
if (index === -1) {
|
|
194
|
+
filtersStore.setFilter({ field: column.name, value, operator });
|
|
195
|
+
} else {
|
|
196
|
+
filtersStore.setFilters([...filtersStore.filters.slice(0, index), { field: column.name, value, operator }, ...filtersStore.filters.slice(index + 1)])
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function getFilterItem({ column, operator }) {
|
|
203
|
+
return filtersStore.filters.find(f => f.field === column.name && f.operator === operator)?.value || '';
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function clear() {
|
|
207
|
+
filtersStore.clearFilters();
|
|
208
|
+
emits('update:filters', [...filtersStore.filters]);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getFilterMinValue(columnName) {
|
|
212
|
+
if(props.columnsMinMax && props.columnsMinMax[columnName]) {
|
|
213
|
+
return props.columnsMinMax[columnName]?.min
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getFilterMaxValue(columnName) {
|
|
218
|
+
if(props.columnsMinMax && props.columnsMinMax[columnName]) {
|
|
219
|
+
return props.columnsMinMax[columnName]?.max
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
</script>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{
|
|
3
|
+
msg: string
|
|
4
|
+
}>()
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div class="greetings">
|
|
9
|
+
<h1 class="green">{{ msg }}</h1>
|
|
10
|
+
<h3>
|
|
11
|
+
You’ve successfully created a project with
|
|
12
|
+
</h3>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<style scoped>
|
|
17
|
+
</style>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<RouterLink
|
|
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 hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextHover active:bg-lightSidebarActive dark:active:bg-darkSidebarHover" role="menuitem"
|
|
5
|
+
:class="{
|
|
6
|
+
'px-4': isChild,
|
|
7
|
+
'px-2': !isChild,
|
|
8
|
+
'bg-lightSidebarItemActive dark:bg-darkSidebarItemActive': item.resourceId ?
|
|
9
|
+
($route.params.resourceId === item.resourceId && $route.name === 'resource-list') :
|
|
10
|
+
($route.name === item.path)
|
|
11
|
+
}"
|
|
12
|
+
>
|
|
13
|
+
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons dark:text-darkSidebarIcons transition duration-75 group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover" ></component>
|
|
14
|
+
<span class="ms-3">{{ item.label }}</span>
|
|
15
|
+
<span v-if="item.badge" class="inline-flex items-center justify-center w-3 h-3 p-3 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
|
|
16
|
+
fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent">{{ item.badge }}</span>
|
|
17
|
+
|
|
18
|
+
</RouterLink>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { getIcon } from '@/utils';
|
|
23
|
+
|
|
24
|
+
const props = defineProps(['item', 'isChild']);
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
</script>
|