adminforth 2.13.0-next.5 → 2.13.0-next.50
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/modules/configValidator.d.ts.map +1 -1
- package/dist/modules/configValidator.js +23 -3
- package/dist/modules/configValidator.js.map +1 -1
- package/dist/modules/restApi.d.ts.map +1 -1
- package/dist/modules/restApi.js +15 -7
- package/dist/modules/restApi.js.map +1 -1
- package/dist/modules/utils.d.ts +2 -0
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +51 -0
- package/dist/modules/utils.js.map +1 -1
- package/dist/spa/package-lock.json +768 -1412
- package/dist/spa/package.json +33 -32
- package/dist/spa/src/App.vue +45 -23
- package/dist/spa/src/afcl/Dialog.vue +65 -5
- package/dist/spa/src/afcl/Select.vue +3 -2
- package/dist/spa/src/components/ResourceListTable.vue +17 -9
- package/dist/spa/src/components/ResourceListTableVirtual.vue +18 -10
- package/dist/spa/src/components/Sidebar.vue +15 -3
- package/dist/spa/src/components/ThreeDotsMenu.vue +34 -6
- package/dist/spa/src/components/ValueRenderer.vue +1 -1
- package/dist/spa/src/stores/core.ts +8 -1
- package/dist/spa/src/stores/filters.ts +5 -0
- package/dist/spa/src/types/Back.ts +6 -0
- package/dist/spa/src/types/Common.ts +1 -0
- package/dist/spa/src/types/adapters/OAuth2Adapter.ts +1 -1
- package/dist/spa/src/utils.ts +18 -4
- package/dist/spa/src/views/ListView.vue +6 -0
- package/dist/spa/src/views/LoginView.vue +8 -2
- package/dist/spa/src/views/PageNotFound.vue +2 -2
- package/dist/spa/tsconfig.json +1 -1
- package/dist/types/Back.d.ts +4 -0
- package/dist/types/Back.d.ts.map +1 -1
- package/dist/types/Back.js.map +1 -1
- package/dist/types/Common.d.ts +1 -0
- package/dist/types/Common.d.ts.map +1 -1
- package/dist/types/Common.js.map +1 -1
- package/dist/types/adapters/OAuth2Adapter.d.ts +2 -0
- package/dist/types/adapters/OAuth2Adapter.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/spa/package.json
CHANGED
|
@@ -13,47 +13,48 @@
|
|
|
13
13
|
"i18n:extract": "echo {} > i18n-empty.json && vue-i18n-extract report --vueFiles \"./src/**/*.{js,vue,ts}\" --output ./i18n-messages.json --languageFiles \"i18n-empty.json\" --add"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@iconify-prerendered/vue-flag": "^0.28.
|
|
16
|
+
"@iconify-prerendered/vue-flag": "^0.28.1748584105",
|
|
17
17
|
"@iconify-prerendered/vue-flowbite": "^0.28.1754899090",
|
|
18
|
-
"@
|
|
19
|
-
"@
|
|
18
|
+
"@iconify-prerendered/vue-humbleicons": "^0.28.1754108846",
|
|
19
|
+
"@unhead/vue": "^1.9.12",
|
|
20
|
+
"@vueuse/core": "^10.10.0",
|
|
20
21
|
"apexcharts": "^4.7.0",
|
|
21
|
-
"dayjs": "^1.11.
|
|
22
|
-
"debounce": "^2.
|
|
23
|
-
"flowbite-datepicker": "^1.
|
|
24
|
-
"javascript-time-ago": "^2.5.
|
|
25
|
-
"pinia": "^2.
|
|
26
|
-
"sanitize-html": "^2.
|
|
27
|
-
"unhead": "^1.
|
|
22
|
+
"dayjs": "^1.11.11",
|
|
23
|
+
"debounce": "^2.1.0",
|
|
24
|
+
"flowbite-datepicker": "^1.2.6",
|
|
25
|
+
"javascript-time-ago": "^2.5.11",
|
|
26
|
+
"pinia": "^2.1.7",
|
|
27
|
+
"sanitize-html": "^2.13.0",
|
|
28
|
+
"unhead": "^1.9.12",
|
|
28
29
|
"uuid": "^10.0.0",
|
|
29
|
-
"vue": "^3.5.
|
|
30
|
+
"vue": "^3.5.12",
|
|
30
31
|
"vue-diff": "^1.2.4",
|
|
31
|
-
"vue-i18n": "^10.0.
|
|
32
|
-
"vue-router": "^4.
|
|
32
|
+
"vue-i18n": "^10.0.5",
|
|
33
|
+
"vue-router": "^4.3.0",
|
|
33
34
|
"vue-slider-component": "^4.1.0-beta.7"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
|
-
"@rushstack/eslint-patch": "^1.
|
|
37
|
-
"@tsconfig/node20": "^20.1.
|
|
38
|
-
"@types/node": "^20.
|
|
39
|
-
"@vitejs/plugin-vue": "^
|
|
37
|
+
"@rushstack/eslint-patch": "^1.8.0",
|
|
38
|
+
"@tsconfig/node20": "^20.1.4",
|
|
39
|
+
"@types/node": "^20.12.5",
|
|
40
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
40
41
|
"@vue/eslint-config-typescript": "^13.0.0",
|
|
41
|
-
"@vue/tsconfig": "^0.
|
|
42
|
-
"autoprefixer": "^10.4.
|
|
43
|
-
"eslint": "^8.57.
|
|
44
|
-
"eslint-plugin-vue": "^9.
|
|
45
|
-
"flag-icons": "^7.
|
|
42
|
+
"@vue/tsconfig": "^0.5.1",
|
|
43
|
+
"autoprefixer": "^10.4.19",
|
|
44
|
+
"eslint": "^8.57.0",
|
|
45
|
+
"eslint-plugin-vue": "^9.23.0",
|
|
46
|
+
"flag-icons": "^7.2.3",
|
|
46
47
|
"flowbite": "^3.1.2",
|
|
47
|
-
"i18n-iso-countries": "^7.
|
|
48
|
-
"npm-run-all2": "^6.2
|
|
49
|
-
"portfinder": "^1.0.
|
|
50
|
-
"postcss": "^8.
|
|
51
|
-
"sass": "^1.
|
|
52
|
-
"tailwindcss": "^3.4.
|
|
53
|
-
"typescript": "~5.
|
|
54
|
-
"vite": "^
|
|
48
|
+
"i18n-iso-countries": "^7.12.0",
|
|
49
|
+
"npm-run-all2": "^6.1.2",
|
|
50
|
+
"portfinder": "^1.0.32",
|
|
51
|
+
"postcss": "^8.4.38",
|
|
52
|
+
"sass": "^1.77.2",
|
|
53
|
+
"tailwindcss": "^3.4.17",
|
|
54
|
+
"typescript": "~5.4.0",
|
|
55
|
+
"vite": "^5.2.13",
|
|
55
56
|
"vue-i18n-extract": "^2.0.7",
|
|
56
|
-
"vue-tsc": "^2.
|
|
57
|
-
"vue3-json-viewer": "^2.
|
|
57
|
+
"vue-tsc": "^2.0.11",
|
|
58
|
+
"vue3-json-viewer": "^2.2.2"
|
|
58
59
|
}
|
|
59
60
|
}
|
package/dist/spa/src/App.vue
CHANGED
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
/>
|
|
25
25
|
|
|
26
26
|
<div class="flex items-center ms-3 ">
|
|
27
|
+
<Tooltip>
|
|
28
|
+
<IconWifiOff v-if="coreStore.isInternetError" class="blinking-icon w-8 h-8 text-red-500" />
|
|
29
|
+
<template #tooltip>
|
|
30
|
+
{{$t('Internet connection lost')}}
|
|
31
|
+
</template>
|
|
32
|
+
</Tooltip>
|
|
27
33
|
<span
|
|
28
34
|
v-if="!coreStore.config?.singleTheme"
|
|
29
35
|
@click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black dark:text-darkSidebarTextHover dark:hover:text-darkSidebarTextActive" role="menuitem">
|
|
@@ -35,12 +41,17 @@
|
|
|
35
41
|
ref="dropdownUserButton"
|
|
36
42
|
type="button" class="flex text-sm bg- rounded-full focus:ring-4 focus:ring-lightSidebarDevider dark:focus:ring-darkSidebarDevider dark:bg-" aria-expanded="false" data-dropdown-toggle="dropdown-user">
|
|
37
43
|
<span class="sr-only">{{ $t('Open user menu') }}</span>
|
|
38
|
-
|
|
44
|
+
<img
|
|
45
|
+
v-if="coreStore.userAvatarUrl"
|
|
46
|
+
class="w-8 h-8 rounded-full object-cover"
|
|
47
|
+
:src="coreStore.userAvatarUrl"
|
|
48
|
+
alt="user photo"
|
|
49
|
+
/>
|
|
50
|
+
<svg v-else class="w-8 h-8 text-lightNavbarIcons dark:text-darkNavbarIcons" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
39
51
|
<path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
|
|
40
52
|
</svg>
|
|
41
53
|
</button>
|
|
42
54
|
</div>
|
|
43
|
-
|
|
44
55
|
<div class="z-50 hidden my-4 text-base list-none bg-lightUserMenuBackground divide-y divide-lightUserMenuBorder text-lightUserMenuText rounded shadow dark:shadow-black dark:bg-darkUserMenuBackground dark:divide-darkUserMenuBorder text-darkUserMenuText dark:shadow-black" id="dropdown-user">
|
|
45
56
|
<div class="px-4 py-3" role="none">
|
|
46
57
|
<p class="text-sm text-gray-900 dark:text-darkNavbarText" role="none" v-if="coreStore.userFullname">
|
|
@@ -73,7 +84,7 @@
|
|
|
73
84
|
</nav>
|
|
74
85
|
|
|
75
86
|
<Sidebar
|
|
76
|
-
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout && !headerOnlyLayout"
|
|
87
|
+
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady && defaultLayout && !headerOnlyLayout && coreStore.menu.length > 0"
|
|
77
88
|
:sideBarOpen="sideBarOpen"
|
|
78
89
|
:forceIconOnly="route.meta?.sidebarAndHeader === 'preferIconOnly'"
|
|
79
90
|
@hideSidebar="hideSidebar"
|
|
@@ -129,6 +140,19 @@
|
|
|
129
140
|
|
|
130
141
|
<style lang="scss" scoped>
|
|
131
142
|
|
|
143
|
+
@keyframes blink {
|
|
144
|
+
0%, 100% {
|
|
145
|
+
opacity: 1;
|
|
146
|
+
}
|
|
147
|
+
50% {
|
|
148
|
+
opacity: 0.2;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.blinking-icon {
|
|
153
|
+
animation: blink 2s ease-in-out infinite;
|
|
154
|
+
}
|
|
155
|
+
|
|
132
156
|
.fade-leave-active {
|
|
133
157
|
@apply transition-opacity duration-500;
|
|
134
158
|
}
|
|
@@ -163,6 +187,7 @@ import './index.scss'
|
|
|
163
187
|
import { useCoreStore } from '@/stores/core';
|
|
164
188
|
import { useUserStore } from '@/stores/user';
|
|
165
189
|
import { IconMoonSolid, IconSunSolid } from '@iconify-prerendered/vue-flowbite';
|
|
190
|
+
import { IconWifiOff } from '@iconify-prerendered/vue-humbleicons';
|
|
166
191
|
import AcceptModal from './components/AcceptModal.vue';
|
|
167
192
|
import Sidebar from './components/Sidebar.vue';
|
|
168
193
|
import { useRoute, useRouter } from 'vue-router';
|
|
@@ -173,6 +198,7 @@ import {useToastStore} from '@/stores/toast';
|
|
|
173
198
|
import { initFrontedAPI } from '@/adminforth';
|
|
174
199
|
import adminforth from '@/adminforth';
|
|
175
200
|
import UserMenuSettingsButton from './components/UserMenuSettingsButton.vue';
|
|
201
|
+
import { Tooltip } from '@/afcl'
|
|
176
202
|
|
|
177
203
|
const coreStore = useCoreStore();
|
|
178
204
|
const toastStore = useToastStore();
|
|
@@ -182,8 +208,6 @@ initFrontedAPI()
|
|
|
182
208
|
|
|
183
209
|
createHead()
|
|
184
210
|
const sideBarOpen = ref(false);
|
|
185
|
-
const defaultLayout = ref(true);
|
|
186
|
-
const headerOnlyLayout = ref(false);
|
|
187
211
|
const route = useRoute();
|
|
188
212
|
const router = useRouter();
|
|
189
213
|
const publicConfigLoaded = ref(false);
|
|
@@ -197,6 +221,14 @@ const isSidebarIconOnly = ref(localStorage.getItem('afIconOnlySidebar') === 'tru
|
|
|
197
221
|
|
|
198
222
|
const loggedIn = computed(() => !!coreStore?.adminUser);
|
|
199
223
|
|
|
224
|
+
const defaultLayout = computed(() => {
|
|
225
|
+
return route.meta?.sidebarAndHeader !== 'none';
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const headerOnlyLayout = computed(() => {
|
|
229
|
+
return route.meta?.sidebarAndHeader === 'headerOnly';
|
|
230
|
+
});
|
|
231
|
+
|
|
200
232
|
const expandedWidth = computed(() => coreStore.config?.iconOnlySidebar?.expandedSidebarWidth || '16.5rem');
|
|
201
233
|
|
|
202
234
|
const theme = ref('light');
|
|
@@ -308,19 +340,6 @@ async function loadMenu() {
|
|
|
308
340
|
loginRedirectCheckIsReady.value = true;
|
|
309
341
|
}
|
|
310
342
|
|
|
311
|
-
function handleCustomLayout() {
|
|
312
|
-
if (route.meta?.sidebarAndHeader === 'none') {
|
|
313
|
-
defaultLayout.value = false;
|
|
314
|
-
} else if (route.meta?.sidebarAndHeader === 'preferIconOnly') {
|
|
315
|
-
defaultLayout.value = true;
|
|
316
|
-
isSidebarIconOnly.value = true;
|
|
317
|
-
} else if (route.meta?.sidebarAndHeader === 'headerOnly') {
|
|
318
|
-
headerOnlyLayout.value = true;
|
|
319
|
-
} else {
|
|
320
|
-
defaultLayout.value = true;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
343
|
function humanizeSnake(str: string): string {
|
|
325
344
|
if (!str) {
|
|
326
345
|
return '';
|
|
@@ -345,10 +364,11 @@ watch(title, (title) => {
|
|
|
345
364
|
document.title = title;
|
|
346
365
|
})
|
|
347
366
|
|
|
348
|
-
watch(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
367
|
+
watch(route, () => {
|
|
368
|
+
// Handle preferIconOnly layout
|
|
369
|
+
if (route.meta?.sidebarAndHeader === 'preferIconOnly') {
|
|
370
|
+
isSidebarIconOnly.value = true;
|
|
371
|
+
}
|
|
352
372
|
});
|
|
353
373
|
|
|
354
374
|
|
|
@@ -376,11 +396,13 @@ onMounted(async () => {
|
|
|
376
396
|
loadPublicConfig(); // and this
|
|
377
397
|
// before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
|
|
378
398
|
await initRouter();
|
|
379
|
-
handleCustomLayout();
|
|
380
399
|
|
|
381
400
|
adminforth.menu.refreshMenuBadges = async () => {
|
|
382
401
|
await coreStore.fetchMenuBadges();
|
|
383
402
|
}
|
|
403
|
+
|
|
404
|
+
window.addEventListener('online', () => coreStore.isInternetError = false);
|
|
405
|
+
window.addEventListener('offline', () => coreStore.isInternetError = true);
|
|
384
406
|
})
|
|
385
407
|
|
|
386
408
|
onBeforeMount(()=>{
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
v-if="headerCloseButton"
|
|
23
23
|
type="button"
|
|
24
24
|
class="text-lightDialogCloseButton bg-transparent hover:bg-lightDialogCloseButtonHoverBackground hover:text-lightDialogCloseButtonHover rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:text-darkDialogCloseButton dark:hover:bg-darkDialogCloseButtonHoverBackground dark:hover:text-darkDialogCloseButtonHover"
|
|
25
|
-
@click="
|
|
25
|
+
@click="tryToHideModal"
|
|
26
26
|
>
|
|
27
27
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
|
28
28
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
|
@@ -51,13 +51,41 @@
|
|
|
51
51
|
</div>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
54
|
+
<div>
|
|
55
|
+
<!-- Confirmation Modal -->
|
|
56
|
+
<div
|
|
57
|
+
v-if="showConfirmationOnClose"
|
|
58
|
+
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-60"
|
|
59
|
+
>
|
|
60
|
+
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-lg max-w-sm w-full">
|
|
61
|
+
<h2 class="text-lg font-semibold mb-4 text-lightDialogHeaderText dark:text-darkDialogHeaderText">Confirm Close</h2>
|
|
62
|
+
<p class="mb-6 text-lightDialogBodyText dark:text-darkDialogBodyText">{{ props.closeConfirmationText }}</p>
|
|
63
|
+
<div class="flex justify-end">
|
|
64
|
+
<Button
|
|
65
|
+
class="me-3"
|
|
66
|
+
@click="showConfirmationOnClose = false"
|
|
67
|
+
>
|
|
68
|
+
Cancel
|
|
69
|
+
</Button>
|
|
70
|
+
<Button
|
|
71
|
+
@click="
|
|
72
|
+
showConfirmationOnClose = false;
|
|
73
|
+
modal?.hide();
|
|
74
|
+
"
|
|
75
|
+
>
|
|
76
|
+
Confirm
|
|
77
|
+
</Button>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
54
82
|
</div>
|
|
55
83
|
</Teleport>
|
|
56
84
|
</template>
|
|
57
85
|
|
|
58
86
|
<script setup lang="ts">
|
|
59
87
|
import Button from "./Button.vue";
|
|
60
|
-
import { ref, onMounted, nextTick, onUnmounted, type Ref } from 'vue';
|
|
88
|
+
import { ref, onMounted, nextTick, onUnmounted, computed, type Ref } from 'vue';
|
|
61
89
|
import { Modal } from 'flowbite';
|
|
62
90
|
|
|
63
91
|
const modalEl = ref(null);
|
|
@@ -77,20 +105,42 @@ interface DialogProps {
|
|
|
77
105
|
beforeCloseFunction?: (() => void | Promise<void>) | null
|
|
78
106
|
beforeOpenFunction?: (() => void | Promise<void>) | null
|
|
79
107
|
closable?: boolean
|
|
108
|
+
askForCloseConfirmation?: boolean
|
|
109
|
+
closeConfirmationText?: string
|
|
80
110
|
}
|
|
81
111
|
|
|
82
112
|
const props = withDefaults(defineProps<DialogProps>(), {
|
|
83
113
|
header: '',
|
|
84
114
|
headerCloseButton: true,
|
|
85
|
-
buttons: () => [
|
|
86
|
-
{ label: 'Close', onclick: (dialog: any) => dialog.hide(), type: '' },
|
|
87
|
-
],
|
|
115
|
+
buttons: () => [],
|
|
88
116
|
clickToCloseOutside: true,
|
|
89
117
|
beforeCloseFunction: null,
|
|
90
118
|
beforeOpenFunction: null,
|
|
91
119
|
closable: true,
|
|
120
|
+
askForCloseConfirmation: false,
|
|
121
|
+
closeConfirmationText: 'Are you sure you want to close this dialog?',
|
|
92
122
|
})
|
|
93
123
|
|
|
124
|
+
const buttons = computed<DialogButton[]>(() => {
|
|
125
|
+
if (props.buttons && props.buttons.length > 0) {
|
|
126
|
+
return props.buttons;
|
|
127
|
+
}
|
|
128
|
+
return [
|
|
129
|
+
{
|
|
130
|
+
label: 'Close',
|
|
131
|
+
onclick: (dialog: any) => {
|
|
132
|
+
if (!props.askForCloseConfirmation) {
|
|
133
|
+
dialog.hide();
|
|
134
|
+
} else {
|
|
135
|
+
showConfirmationOnClose.value = true;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
options: {}
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const showConfirmationOnClose = ref(false);
|
|
94
144
|
onMounted(async () => {
|
|
95
145
|
//await one tick when all is mounted
|
|
96
146
|
await nextTick();
|
|
@@ -129,6 +179,16 @@ function close() {
|
|
|
129
179
|
defineExpose({
|
|
130
180
|
open: open,
|
|
131
181
|
close: close,
|
|
182
|
+
tryToHideModal: tryToHideModal,
|
|
132
183
|
})
|
|
133
184
|
|
|
185
|
+
function tryToHideModal() {
|
|
186
|
+
if (!props.askForCloseConfirmation ) {
|
|
187
|
+
modal.value?.hide();
|
|
188
|
+
} else {
|
|
189
|
+
showConfirmationOnClose.value = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
134
194
|
</script>
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
class="block w-full pl-3 pr-10 py-2.5 border border-lightDropownButtonsBorder rounded-md leading-5 bg-lightDropdownButtonsBackground
|
|
14
14
|
placeholder-lightDropdownButtonsPlaceholderText text-lightDropdownButtonsText sm:text-sm transition duration-150 ease-in-out dark:bg-darkDropdownButtonsBackground dark:border-darkDropdownButtonsBorder dark:placeholder-darkDropdownButtonsPlaceholderText
|
|
15
15
|
dark:text-darkDropdownButtonsText focus:ring-lightPrimary focus:border-lightPrimary dark:focus:ring-darkPrimary dark:focus:border-darkPrimary"
|
|
16
|
+
:class="{'cursor-pointer': searchDisabled}"
|
|
16
17
|
autocomplete="off" data-custom="no-autofill"
|
|
17
18
|
:placeholder="
|
|
18
19
|
selectedItems.length && !multiple ? '' : (showDropdown ? $t('Search') : placeholder || $t('Select...'))
|
|
@@ -265,11 +266,11 @@ onMounted(() => {
|
|
|
265
266
|
|
|
266
267
|
watch(() => props.modelValue, (value) => {
|
|
267
268
|
updateFromProps();
|
|
268
|
-
});
|
|
269
|
+
}, {deep: true});
|
|
269
270
|
|
|
270
271
|
watch(() => props.options, () => {
|
|
271
272
|
updateFromProps();
|
|
272
|
-
});
|
|
273
|
+
}, { deep: true });
|
|
273
274
|
|
|
274
275
|
addClickListener();
|
|
275
276
|
|
|
@@ -83,13 +83,20 @@
|
|
|
83
83
|
</td>
|
|
84
84
|
</tr>
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
<component
|
|
87
|
+
v-else
|
|
88
|
+
v-for="(row, rowI) in rows"
|
|
89
|
+
:is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
|
|
90
|
+
:key="`row_${row._primaryKeyValue}`"
|
|
91
|
+
:record="row"
|
|
92
|
+
:resource="resource"
|
|
93
|
+
:adminUser="coreStore.adminUser"
|
|
94
|
+
:meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
|
|
95
|
+
@click="onClick($event, row)"
|
|
96
|
+
ref="rowRefs"
|
|
97
|
+
class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
|
|
98
|
+
:class="{'border-b': rowI !== rows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
|
|
99
|
+
>
|
|
93
100
|
<td class="w-4 p-4 cursor-default sticky-column bg-lightListTable dark:bg-darkListTable" @click="(e)=>e.stopPropagation()">
|
|
94
101
|
<Checkbox
|
|
95
102
|
:model-value="checkboxesInternal.includes(row._primaryKeyValue)"
|
|
@@ -210,7 +217,7 @@
|
|
|
210
217
|
</div>
|
|
211
218
|
|
|
212
219
|
</td>
|
|
213
|
-
|
|
220
|
+
</component>
|
|
214
221
|
</tbody>
|
|
215
222
|
</table>
|
|
216
223
|
</div>
|
|
@@ -328,7 +335,7 @@ import {
|
|
|
328
335
|
} from '@iconify-prerendered/vue-flowbite';
|
|
329
336
|
import router from '@/router';
|
|
330
337
|
import { Tooltip } from '@/afcl';
|
|
331
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon } from '@/types/Common';
|
|
338
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnInputCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
|
|
332
339
|
import adminforth from '@/adminforth';
|
|
333
340
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
334
341
|
|
|
@@ -345,6 +352,7 @@ const props = defineProps<{
|
|
|
345
352
|
noRoundings?: boolean,
|
|
346
353
|
customActionsInjection?: any[],
|
|
347
354
|
tableBodyStartInjection?: any[],
|
|
355
|
+
tableRowReplaceInjection?: AdminForthComponentDeclaration,
|
|
348
356
|
}>();
|
|
349
357
|
|
|
350
358
|
// emits, update page
|
|
@@ -93,14 +93,21 @@
|
|
|
93
93
|
</tr>
|
|
94
94
|
|
|
95
95
|
<!-- Visible rows -->
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
<component
|
|
97
|
+
v-else
|
|
98
|
+
v-for="(row, rowI) in visibleRows"
|
|
99
|
+
:is="tableRowReplaceInjection ? getCustomComponent(tableRowReplaceInjection) : 'tr'"
|
|
100
|
+
:key="`row_${row._primaryKeyValue}`"
|
|
101
|
+
:record="row"
|
|
102
|
+
:resource="resource"
|
|
103
|
+
:adminUser="coreStore.adminUser"
|
|
104
|
+
:meta="tableRowReplaceInjection ? tableRowReplaceInjection.meta : undefined"
|
|
105
|
+
@click="onClick($event, row)"
|
|
106
|
+
ref="rowRefs"
|
|
107
|
+
class="list-table-body-row bg-lightListTable dark:bg-darkListTable border-lightListBorder dark:border-gray-700 hover:bg-lightListTableRowHover dark:hover:bg-darkListTableRowHover"
|
|
108
|
+
:class="{'border-b': rowI !== visibleRows.length - 1, 'cursor-pointer': row._clickUrl !== null}"
|
|
109
|
+
@mounted="(el: any) => updateRowHeight(`row_${row._primaryKeyValue}`, el.offsetHeight)"
|
|
110
|
+
>
|
|
104
111
|
<td class="w-4 p-4 cursor-default sticky-column bg-lightListTableHeading dark:bg-darkListTableHeading" @click="(e)=>e.stopPropagation()">
|
|
105
112
|
<Checkbox
|
|
106
113
|
:model-value="checkboxesInternal.includes(row._primaryKeyValue)"
|
|
@@ -224,7 +231,7 @@
|
|
|
224
231
|
</template>
|
|
225
232
|
</div>
|
|
226
233
|
</td>
|
|
227
|
-
</
|
|
234
|
+
</component>
|
|
228
235
|
|
|
229
236
|
<!-- Bottom spacer -->
|
|
230
237
|
<tr v-if="totalHeight > 0">
|
|
@@ -350,7 +357,7 @@ import {
|
|
|
350
357
|
} from '@iconify-prerendered/vue-flowbite';
|
|
351
358
|
import router from '@/router';
|
|
352
359
|
import { Tooltip } from '@/afcl';
|
|
353
|
-
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon } from '@/types/Common';
|
|
360
|
+
import type { AdminForthResourceCommon, AdminForthResourceColumnCommon, AdminForthComponentDeclaration } from '@/types/Common';
|
|
354
361
|
import adminforth from '@/adminforth';
|
|
355
362
|
import Checkbox from '@/afcl/Checkbox.vue';
|
|
356
363
|
|
|
@@ -370,6 +377,7 @@ const props = defineProps<{
|
|
|
370
377
|
containerHeight?: number,
|
|
371
378
|
itemHeight?: number,
|
|
372
379
|
bufferSize?: number,
|
|
380
|
+
tableRowReplaceInjection?: AdminForthComponentDeclaration
|
|
373
381
|
}>();
|
|
374
382
|
|
|
375
383
|
// emits, update page
|
|
@@ -14,9 +14,21 @@
|
|
|
14
14
|
aria-label="Sidebar"
|
|
15
15
|
>
|
|
16
16
|
<div class="h-full px-3 pb-20 md:pb-4 bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder dark:border-darkSidebarBorder pt-4" :class="{'sidebar-scroll':!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)}">
|
|
17
|
-
<div
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
<div
|
|
18
|
+
class="af-logo-title-wrapper flex relative transition-all duration-300 ease-in-out h-8 items-center"
|
|
19
|
+
:class="{
|
|
20
|
+
'mb-4': isSidebarIconOnly && !isSidebarHovering, 'mx-4 mb-4': !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering),
|
|
21
|
+
'justify-center': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)))
|
|
22
|
+
}"
|
|
23
|
+
>
|
|
24
|
+
<img
|
|
25
|
+
:src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')"
|
|
26
|
+
:alt="`${ coreStore.config?.brandName } Logo`"
|
|
27
|
+
class="af-logo h-8 me-3"
|
|
28
|
+
:class="{
|
|
29
|
+
'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))) }"
|
|
30
|
+
/>
|
|
31
|
+
<img :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
|
|
20
32
|
<span
|
|
21
33
|
v-if="coreStore.config?.showBrandNameInSidebar && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))"
|
|
22
34
|
class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<template >
|
|
2
|
-
<
|
|
2
|
+
<div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionCommon) => action.showInThreeDotsDropdown))">
|
|
3
3
|
<button
|
|
4
|
-
|
|
4
|
+
ref="buttonTriggerRef"
|
|
5
|
+
@click="toggleDropdownVisibility"
|
|
5
6
|
class="flex items-center py-2 px-2 text-sm font-medium text-lightThreeDotsMenuIconDots focus:outline-none bg-lightThreeDotsMenuIconBackground rounded border border-lightThreeDotsMenuIconBackgroundBorder hover:bg-lightThreeDotsMenuIconBackgroundHover hover:text-lightThreeDotsMenuIconDotsHover focus:z-10 focus:ring-4 focus:ring-lightThreeDotsMenuIconFocus dark:focus:ring-darkThreeDotsMenuIconFocus dark:bg-darkThreeDotsMenuIconBackground dark:text-darkThreeDotsMenuIconDots dark:border-darkThreeDotsMenuIconBackgroundBorder dark:hover:text-darkThreeDotsMenuIconDotsHover dark:hover:bg-darkThreeDotsMenuIconBackgroundHover rounded-default"
|
|
6
7
|
>
|
|
7
8
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 4 15">
|
|
@@ -11,8 +12,9 @@
|
|
|
11
12
|
|
|
12
13
|
<!-- Dropdown menu -->
|
|
13
14
|
<div
|
|
14
|
-
|
|
15
|
-
class="
|
|
15
|
+
ref="dropdownRef"
|
|
16
|
+
:class="{'hidden': !showDropdown, 'block': showDropdown }"
|
|
17
|
+
class="absolute z-30 right-0 mt-3 bg-lightThreeDotsMenuBodyBackground divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-darkThreeDotsMenuBodyBackground dark:divide-gray-600">
|
|
16
18
|
<ul class="py-2 text-sm text-lightThreeDotsMenuBodyText dark:text-darkThreeDotsMenuBodyText" aria-labelledby="dropdownMenuIconButton">
|
|
17
19
|
<li v-for="(item, i) in threeDotsDropdownItems" :key="`dropdown-item-${i}`">
|
|
18
20
|
<a href="#"
|
|
@@ -71,7 +73,7 @@
|
|
|
71
73
|
</li>
|
|
72
74
|
</ul>
|
|
73
75
|
</div>
|
|
74
|
-
</
|
|
76
|
+
</div>
|
|
75
77
|
</template>
|
|
76
78
|
|
|
77
79
|
|
|
@@ -82,7 +84,7 @@ import adminforth from '@/adminforth';
|
|
|
82
84
|
import { callAdminForthApi } from '@/utils';
|
|
83
85
|
import { useRoute, useRouter } from 'vue-router';
|
|
84
86
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
85
|
-
import { ref, type ComponentPublicInstance } from 'vue';
|
|
87
|
+
import { ref, type ComponentPublicInstance, onMounted, onUnmounted } from 'vue';
|
|
86
88
|
import type { AdminForthBulkActionCommon, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
87
89
|
import type { AdminForthActionInput } from '@/types/Back';
|
|
88
90
|
|
|
@@ -91,6 +93,9 @@ const route = useRoute();
|
|
|
91
93
|
const coreStore = useCoreStore();
|
|
92
94
|
const router = useRouter();
|
|
93
95
|
const threeDotsDropdownItemsRefs = ref<Array<ComponentPublicInstance | null>>([]);
|
|
96
|
+
const showDropdown = ref(false);
|
|
97
|
+
const dropdownRef = ref<HTMLElement | null>(null);
|
|
98
|
+
const buttonTriggerRef = ref<HTMLElement | null>(null);
|
|
94
99
|
|
|
95
100
|
const props = defineProps({
|
|
96
101
|
threeDotsDropdownItems: Array<AdminForthComponentDeclarationFull>,
|
|
@@ -169,6 +174,7 @@ async function handleActionClick(action: AdminForthActionInput, payload: any) {
|
|
|
169
174
|
function startBulkAction(actionId: string) {
|
|
170
175
|
adminforth.list.closeThreeDotsDropdown();
|
|
171
176
|
emit('startBulkAction', actionId);
|
|
177
|
+
showDropdown.value = false;
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
async function injectedComponentClick(index: number) {
|
|
@@ -176,5 +182,27 @@ async function injectedComponentClick(index: number) {
|
|
|
176
182
|
if (componentRef && 'click' in componentRef) {
|
|
177
183
|
(componentRef as any).click?.();
|
|
178
184
|
}
|
|
185
|
+
showDropdown.value = false;
|
|
179
186
|
}
|
|
187
|
+
|
|
188
|
+
function toggleDropdownVisibility() {
|
|
189
|
+
showDropdown.value = !showDropdown.value;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function handleClickOutside(e: MouseEvent) {
|
|
193
|
+
if (!dropdownRef.value) return
|
|
194
|
+
|
|
195
|
+
if (!dropdownRef.value.contains(e.target as Node) && !buttonTriggerRef.value?.contains(e.target as Node)) {
|
|
196
|
+
showDropdown.value = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
onMounted(() => {
|
|
201
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
onUnmounted(() => {
|
|
205
|
+
document.removeEventListener('mousedown', handleClickOutside)
|
|
206
|
+
})
|
|
207
|
+
|
|
180
208
|
</script>
|
|
@@ -117,7 +117,7 @@ import timezone from 'dayjs/plugin/timezone';
|
|
|
117
117
|
import {checkEmptyValues} from '@/utils';
|
|
118
118
|
import { useRoute, useRouter } from 'vue-router';
|
|
119
119
|
import { JsonViewer } from "vue3-json-viewer";
|
|
120
|
-
import "vue3-json-viewer/dist/
|
|
120
|
+
import "vue3-json-viewer/dist/index.css";
|
|
121
121
|
import type { AdminForthResourceColumnCommon } from '@/types/Common';
|
|
122
122
|
|
|
123
123
|
import { useCoreStore } from '@/stores/core';
|
|
@@ -17,6 +17,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
17
17
|
const resource: Ref<AdminForthResourceCommon | null> = ref(null);
|
|
18
18
|
const userData: Ref<UserData | null> = ref(null);
|
|
19
19
|
const isResourceFetching = ref(false);
|
|
20
|
+
const isInternetError = ref(false);
|
|
20
21
|
|
|
21
22
|
const resourceColumnsWithFilters = computed(() => {
|
|
22
23
|
if (!resource.value) {
|
|
@@ -221,6 +222,10 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
221
222
|
return userData.value && userFullnameField && userData.value[userFullnameField];
|
|
222
223
|
})
|
|
223
224
|
|
|
225
|
+
const userAvatarUrl = computed(() => {
|
|
226
|
+
return userData.value?.userAvatarUrl || null;
|
|
227
|
+
});
|
|
228
|
+
|
|
224
229
|
const isIos = computed(() => {
|
|
225
230
|
return (
|
|
226
231
|
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
@@ -234,6 +239,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
234
239
|
menu,
|
|
235
240
|
username,
|
|
236
241
|
userFullname,
|
|
242
|
+
userAvatarUrl,
|
|
237
243
|
getPublicConfig,
|
|
238
244
|
fetchMenuAndResource,
|
|
239
245
|
getLoginFormConfig,
|
|
@@ -251,6 +257,7 @@ export const useCoreStore = defineStore('core', () => {
|
|
|
251
257
|
resetAdminUser,
|
|
252
258
|
resetResource,
|
|
253
259
|
isResourceFetching,
|
|
254
|
-
isIos
|
|
260
|
+
isIos,
|
|
261
|
+
isInternetError,
|
|
255
262
|
}
|
|
256
263
|
})
|
|
@@ -14,6 +14,11 @@ export const useFiltersStore = defineStore('filters', () => {
|
|
|
14
14
|
return sort.value;
|
|
15
15
|
}
|
|
16
16
|
const setFilter = (filter: any) => {
|
|
17
|
+
const index = filters.value.findIndex(f => f.field === filter.field);
|
|
18
|
+
if (filters.value[index]) {
|
|
19
|
+
filters.value[index] = filter;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
17
22
|
filters.value.push(filter);
|
|
18
23
|
}
|
|
19
24
|
const setFilters = (f: any) => {
|