adminforth 1.2.56 → 1.2.57
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/index.html +1 -1
- package/dist/spa/package-lock.json +215 -1
- package/dist/spa/package.json +2 -0
- package/dist/spa/spa/src/types/AdminForthConfig.ts +1477 -0
- package/dist/spa/spa/src/types/FrontendAPI.ts +121 -0
- package/dist/spa/src/App.vue +176 -133
- package/dist/spa/src/assets/base.css +2 -0
- package/dist/spa/src/components/Breadcrumbs.vue +3 -2
- package/dist/spa/src/components/CustomDatePicker.vue +6 -4
- package/dist/spa/src/components/CustomRangePicker.vue +11 -7
- package/dist/spa/src/components/Filters.vue +16 -5
- package/dist/spa/src/components/MenuLink.vue +3 -3
- package/dist/spa/src/components/ResourceForm.vue +140 -111
- package/dist/spa/src/components/ResourceListTable.vue +76 -101
- package/dist/spa/src/components/SkeleteLoader.vue +23 -0
- package/dist/spa/src/components/Toast.vue +18 -7
- package/dist/spa/src/components/ValueRenderer.vue +10 -4
- package/dist/spa/src/composables/useStores.ts +25 -11
- package/dist/spa/src/index.scss +2 -1
- package/dist/spa/src/main.ts +1 -1
- package/dist/spa/src/router/index.ts +0 -1
- package/dist/spa/src/stores/user.ts +21 -3
- package/dist/spa/src/types/AdminForthConfig.ts +1477 -0
- package/dist/spa/src/types/FrontendAPI.ts +121 -0
- package/dist/spa/src/utils.ts +6 -4
- package/dist/spa/src/views/CreateView.vue +2 -9
- package/dist/spa/src/views/EditView.vue +1 -9
- package/dist/spa/src/views/ListView.vue +17 -16
- package/dist/spa/src/views/LoginView.vue +57 -31
- package/dist/spa/src/views/ResourceParent.vue +1 -2
- package/dist/spa/src/views/ShowView.vue +17 -21
- package/dist/spa/tailwind.config.js +3 -1
- package/dist/spa/vite.config.ts +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
|
|
2
|
+
export interface FrontendAPIInterface {
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Show a confirmation dialog
|
|
6
|
+
*
|
|
7
|
+
* The dialog will be displayed to the user
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const isConfirmed = await window.adminforth.confirm({message: 'Are you sure?', yes: 'Yes', no: 'No'})
|
|
13
|
+
* if (isConfirmed) {
|
|
14
|
+
* your code...
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @param params - The parameters of the dialog
|
|
19
|
+
* @returns A promise that resolves when the user confirms the dialog
|
|
20
|
+
*/
|
|
21
|
+
confirm(params:ConfirmParams ): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Show an alert
|
|
24
|
+
*
|
|
25
|
+
* The alert will be displayed to the user
|
|
26
|
+
*
|
|
27
|
+
* Example:
|
|
28
|
+
*
|
|
29
|
+
* ```ts
|
|
30
|
+
* window.adminforth.alert({message: 'Hello', variant: 'success'})
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @param params - The parameters of the alert
|
|
34
|
+
*/
|
|
35
|
+
alert(params:AlertParams): void;
|
|
36
|
+
/**
|
|
37
|
+
* Add a filter to the list of filters.
|
|
38
|
+
* Works only when user located on the list page.
|
|
39
|
+
* Can be used to set filter from charts or other components in pageInjections.
|
|
40
|
+
*
|
|
41
|
+
* Example:
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* window.adminforth.updateListFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @param filter - The filter to add
|
|
48
|
+
*/
|
|
49
|
+
setListFilter(filter: any): void;
|
|
50
|
+
/**
|
|
51
|
+
* Update a filter in the list of filters
|
|
52
|
+
*
|
|
53
|
+
* Example:
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* window.adminforth.updateListFilter({field: 'name', operator: 'ilike', value: 'john'})
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param filter - The filter to update
|
|
60
|
+
*/
|
|
61
|
+
updateListFilter(filter: any): void;
|
|
62
|
+
/**
|
|
63
|
+
* Clear all filters from the list
|
|
64
|
+
*/
|
|
65
|
+
clearListFilters(): void;
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type ConfirmParams = {
|
|
70
|
+
/**
|
|
71
|
+
* The message to display in the dialog
|
|
72
|
+
*/
|
|
73
|
+
message?: string;
|
|
74
|
+
/**
|
|
75
|
+
* The text to display in the "accept" button
|
|
76
|
+
*/
|
|
77
|
+
yes?: string;
|
|
78
|
+
/**
|
|
79
|
+
* The text to display in the "cancel" button
|
|
80
|
+
*/
|
|
81
|
+
no?: string;
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export type AlertParams = {
|
|
86
|
+
/**
|
|
87
|
+
* The message to display in the alert
|
|
88
|
+
*/
|
|
89
|
+
message?: string;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The message to display in the alert as HTML (can be used instead of message)
|
|
93
|
+
*/
|
|
94
|
+
messageHtml?: string;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The variant of the alert
|
|
98
|
+
*/
|
|
99
|
+
variant?: AlertVariant | keyof typeof AlertVariant;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The timeout of the alert in seconds or 'unlimited' to keep the alert open until the user closes it.
|
|
103
|
+
* Default is 10 seconds;
|
|
104
|
+
*/
|
|
105
|
+
timeout?: number | 'unlimited';
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
export enum AlertVariant {
|
|
112
|
+
danger = 'danger',
|
|
113
|
+
success = 'success',
|
|
114
|
+
warning = 'warning',
|
|
115
|
+
info = 'info'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
package/dist/spa/src/App.vue
CHANGED
|
@@ -1,145 +1,162 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</div>
|
|
17
|
-
<div class="flex items-center">
|
|
18
|
-
<div class="flex items-center ms-3 ">
|
|
19
|
-
<div>
|
|
20
|
-
<button type="button" class="flex text-sm bg-header-bg-hover rounded-full focus:ring-4 focus:ring-nav-menu-devider dark:focus:ring-nav-menu-devider dark:bg-header-bg-hover" aria-expanded="false" data-dropdown-toggle="dropdown-user">
|
|
21
|
-
<span class="sr-only">Open user menu</span>
|
|
22
|
-
<svg class="w-8 h-8 text-nav-menu-icons dark:text-header-icons" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
23
|
-
<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"/>
|
|
2
|
+
<div v-if="defaultLayout" >
|
|
3
|
+
|
|
4
|
+
<nav
|
|
5
|
+
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady"
|
|
6
|
+
class="fixed h-14 top-0 z-20 w-full border-b shadow-sm bg-lightNavbar shadow-headerShadow dark:bg-darkNavbar dark:border-darkSidebarDevider">
|
|
7
|
+
<div class="px-3 py-3 lg:px-5 lg:pl-3">
|
|
8
|
+
<div class="flex items-center justify-between">
|
|
9
|
+
<div class="flex items-center justify-start rtl:justify-end">
|
|
10
|
+
<button @click="sideBarOpen = !sideBarOpen"
|
|
11
|
+
type="button" class="inline-flex items-center p-2 text-sm rounded-lg sm:hidden hover:bg-lightSidebarItemHover focus:outline-none focus:ring-2 focus:ring-lightSidebarDevider dark:text-darkSidebarIcons dark:hover:bg-darkSidebarHover dark:focus:ring-lightSidebarDevider">
|
|
12
|
+
<span class="sr-only">Open sidebar</span>
|
|
13
|
+
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
14
|
+
<path clip-rule="evenodd" fill-rule="evenodd" d="M2 4.75A.75.75 0 012.75 4h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 4.75zm0 10.5a.75.75 0 01.75-.75h7.5a.75.75 0 010 1.5h-7.5a.75.75 0 01-.75-.75zM2 10a.75.75 0 01.75-.75h14.5a.75.75 0 010 1.5H2.75A.75.75 0 012 10z"></path>
|
|
24
15
|
</svg>
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
16
|
+
</button>
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
<div class="flex items-center">
|
|
20
|
+
<div class="flex items-center ms-3 ">
|
|
21
|
+
<div>
|
|
22
|
+
<button 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">
|
|
23
|
+
<span class="sr-only">Open user menu</span>
|
|
24
|
+
<svg 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">
|
|
25
|
+
<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"/>
|
|
26
|
+
</svg>
|
|
27
|
+
</button>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded shadow dark:shadow-black dark:bg-darkSidebar dark:divide-darkSidebarDevider dark:shadow-black" id="dropdown-user">
|
|
30
|
+
<div class="px-4 py-3" role="none">
|
|
31
|
+
<p class="text-sm text-gray-900 dark:text-darkNavbarText" role="none" v-if="coreStore.userFullname">
|
|
32
|
+
{{ coreStore.userFullname }}
|
|
33
|
+
</p>
|
|
34
|
+
<p class="text-sm font-medium text-gray-900 truncate dark:text-darkSidebarText" role="none">
|
|
35
|
+
{{ coreStore.username }}
|
|
36
|
+
</p>
|
|
37
|
+
</div>
|
|
38
|
+
<ul class="py-1" role="none">
|
|
39
|
+
<li>
|
|
40
|
+
<span @click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-lightHtml dark:text-darkSidebarTextHover dark:hover:bg-darkHtml dark:hover:text-darkSidebarTextActive" role="menuitem">
|
|
41
|
+
{{ theme === 'dark' ? 'Light' : 'Dark' }}
|
|
42
|
+
<IconMoonSolid class="w-5 h-5 text-blue-300" v-if="theme !== 'dark'" />
|
|
43
|
+
<IconSunSolid class="w-5 h-5 text-yellow-300" v-else />
|
|
44
|
+
mode
|
|
45
|
+
</span>
|
|
46
|
+
</li>
|
|
47
|
+
<li>
|
|
48
|
+
<button @click="logout" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html dark:text-darkSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextActive w-full" role="menuitem">Sign out</button>
|
|
49
|
+
</li>
|
|
50
|
+
</ul>
|
|
35
51
|
</div>
|
|
36
|
-
<ul class="py-1" role="none">
|
|
37
|
-
<li>
|
|
38
|
-
<span @click="toggleTheme" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active" role="menuitem">
|
|
39
|
-
{{ theme === 'dark' ? 'Light' : 'Dark' }}
|
|
40
|
-
<IconMoonSolid class="w-5 h-5 text-blue-300" v-if="theme !== 'dark'" />
|
|
41
|
-
<IconSunSolid class="w-5 h-5 text-yellow-300" v-else />
|
|
42
|
-
mode
|
|
43
|
-
</span>
|
|
44
|
-
</li>
|
|
45
|
-
<li>
|
|
46
|
-
<button @click="logout" class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black hover:bg-html-bg dark:text-nav-menu-text-hover dark:hover:bg-nav-menu-hover dark:hover:text-nav-menu-text-active w-full" role="menuitem">Sign out</button>
|
|
47
|
-
</li>
|
|
48
|
-
</ul>
|
|
49
52
|
</div>
|
|
50
53
|
</div>
|
|
51
54
|
</div>
|
|
52
55
|
</div>
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
</nav>
|
|
57
|
+
|
|
55
58
|
|
|
56
59
|
|
|
60
|
+
<aside
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady"
|
|
59
63
|
|
|
60
|
-
|
|
64
|
+
id="logo-lightSidebar" class="fixed bg-lightSidebar border-none top-0 left-0 z-20 w-64 h-screen transition-transform bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder sm:translate-x-0 dark:bg-darkSidebar dark:border-darkSidebarBorder"
|
|
65
|
+
:class="{ '-translate-x-full': !sideBarOpen, 'transform-none': sideBarOpen }"
|
|
66
|
+
aria-label="Sidebar"
|
|
67
|
+
>
|
|
68
|
+
<div class="h-full px-3 pb-4 overflow-y-auto bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder dark:border-darkSidebarBorder">
|
|
69
|
+
<div class="flex ms-2 md:me-24 m-4 ">
|
|
70
|
+
<img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="h-8 me-3" />
|
|
71
|
+
<span class="self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText">
|
|
72
|
+
{{ coreStore.config?.brandName }}
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
61
75
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
<ul class="space-y-2 font-medium">
|
|
77
|
+
<template v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
|
|
78
|
+
<div v-if="item.type === 'divider'" class="border-t border-lightSidebarDevider dark:border-darkSidebarDevider"></div>
|
|
79
|
+
<div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
|
|
80
|
+
<div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-lightSidebarHeading dark:text-darkSidebarHeading
|
|
81
|
+
">{{ item.label }}</div>
|
|
82
|
+
<li v-else-if="item.children" class=" ">
|
|
83
|
+
<button @click="clickOnMenuItem(i)" type="button" class="flex items-center w-full p-2 text-base text-lightSidebarText rounded-default transition duration-75 group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarHover dark:hover:text-darkSidebarTextHover"
|
|
84
|
+
:aria-controls="`dropdown-example${i}`"
|
|
85
|
+
:data-collapse-toggle="`dropdown-example${i}`"
|
|
86
|
+
>
|
|
87
|
+
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
|
|
88
|
+
|
|
89
|
+
<span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
|
|
90
|
+
<svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
|
91
|
+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
|
92
|
+
</svg>
|
|
93
|
+
</button>
|
|
94
|
+
|
|
95
|
+
<ul :id="`dropdown-example${i}`" role="none" class="pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
|
|
96
|
+
<template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
|
|
97
|
+
<li>
|
|
98
|
+
<MenuLink :item="child" isChild="true" />
|
|
99
|
+
</li>
|
|
100
|
+
</template>
|
|
101
|
+
</ul>
|
|
102
|
+
</li>
|
|
103
|
+
<li v-else>
|
|
104
|
+
<MenuLink :item="item" />
|
|
105
|
+
</li>
|
|
106
|
+
</template>
|
|
107
|
+
</ul>
|
|
72
108
|
</div>
|
|
109
|
+
</aside>
|
|
110
|
+
|
|
73
111
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
">{{ item.label }}</div>
|
|
80
|
-
<li v-else-if="item.children">
|
|
81
|
-
<button @click="clickOnMenuItem(i)" type="button" class="flex items-center w-full p-2 text-base text-nav-menu-text rounded-default transition duration-75 group hover:bg-nav-menu-bg-hover dark:text-white dark:hover:bg-gray-700"
|
|
82
|
-
:aria-controls="`dropdown-example${i}`"
|
|
83
|
-
:data-collapse-toggle="`dropdown-example${i}`"
|
|
84
|
-
>
|
|
85
|
-
<component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-gray-500 transition duration-75 dark:text-gray-400 dark:group-hover:text-white" ></component>
|
|
86
|
-
|
|
87
|
-
<span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}</span>
|
|
88
|
-
<svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
|
89
|
-
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
|
90
|
-
</svg>
|
|
91
|
-
</button>
|
|
112
|
+
<div class="sm:ml-64" v-if="loggedIn && routerIsReady && loginRedirectCheckIsReady">
|
|
113
|
+
<div class="p-0 dark:border-gray-700 mt-14">
|
|
114
|
+
<RouterView/>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
92
117
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
<li>
|
|
96
|
-
<MenuLink :item="child" isChild="true" />
|
|
97
|
-
</li>
|
|
98
|
-
</template>
|
|
99
|
-
</ul>
|
|
100
|
-
</li>
|
|
101
|
-
<li v-else>
|
|
102
|
-
<MenuLink :item="item" />
|
|
103
|
-
</li>
|
|
104
|
-
</template>
|
|
105
|
-
</ul>
|
|
118
|
+
<div v-else-if="routerIsReady && loginRedirectCheckIsReady">
|
|
119
|
+
<RouterView/>
|
|
106
120
|
</div>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<RouterView/>
|
|
121
|
+
<div v-else class="flex items-center justify-center h-screen">
|
|
122
|
+
<div class="text-3xl text-gray-400 animate-bounce">
|
|
123
|
+
<svg aria-hidden="true" class="w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/><path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/></svg>
|
|
124
|
+
<span class="sr-only">Loading...</span>
|
|
125
|
+
</div>
|
|
113
126
|
</div>
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
<AcceptModal />
|
|
128
|
+
<div v-if="toastStore.toasts.length>0" class="fixed bottom-5 right-5 flex gap-1 flex-col-reverse">
|
|
129
|
+
<transition-group
|
|
130
|
+
name="fade"
|
|
131
|
+
tag="div"
|
|
132
|
+
class="flex flex-col-reverse gap-1"
|
|
133
|
+
>
|
|
134
|
+
<Toast :toast="t" @close="toastStore.removeToast(t)" v-for="(t,i) in toastStore.toasts" :key="`t-${t.id}`" ></Toast>
|
|
135
|
+
</transition-group>
|
|
123
136
|
</div>
|
|
124
|
-
</div>
|
|
125
|
-
<AcceptModal />
|
|
126
|
-
<div v-if="toastStore.toasts.length>0" class="fixed bottom-5 right-5 flex gap-1 flex-col-reverse">
|
|
127
|
-
<transition-group
|
|
128
|
-
name="fade"
|
|
129
|
-
tag="div"
|
|
130
|
-
class="flex flex-col-reverse gap-1"
|
|
131
|
-
>
|
|
132
|
-
<Toast :toast="t" @close="toastStore.removeToast(t)" v-for="(t,i) in toastStore.toasts" :key="`t-${t.id}`" ></Toast>
|
|
133
|
-
</transition-group>
|
|
134
|
-
</div>
|
|
135
|
-
|
|
136
|
-
<div v-if="sideBarOpen"
|
|
137
|
-
@click="sideBarOpen = false"
|
|
138
|
-
|
|
139
|
-
drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-5"></div>
|
|
140
137
|
|
|
138
|
+
<div v-if="sideBarOpen"
|
|
139
|
+
@click="sideBarOpen = false"
|
|
140
|
+
|
|
141
|
+
drawer-backdrop="" class="bg-gray-900/50 dark:bg-gray-900/80 fixed inset-0 z-5"></div>
|
|
141
142
|
|
|
142
143
|
|
|
144
|
+
</div>
|
|
145
|
+
<div v-else>
|
|
146
|
+
<div v-if="routerIsReady && loginRedirectCheckIsReady">
|
|
147
|
+
<RouterView/>
|
|
148
|
+
</div>
|
|
149
|
+
<AcceptModal />
|
|
150
|
+
<div v-if="toastStore.toasts.length>0" class="fixed bottom-5 right-5 flex gap-1 flex-col-reverse">
|
|
151
|
+
<transition-group
|
|
152
|
+
name="fade"
|
|
153
|
+
tag="div"
|
|
154
|
+
class="flex flex-col-reverse gap-1"
|
|
155
|
+
>
|
|
156
|
+
<Toast :toast="t" @close="toastStore.removeToast(t)" v-for="(t,i) in toastStore.toasts" :key="`t-${t.id}`" ></Toast>
|
|
157
|
+
</transition-group>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
143
160
|
</template>
|
|
144
161
|
|
|
145
162
|
<style lang="scss" scoped>
|
|
@@ -163,7 +180,7 @@
|
|
|
163
180
|
</style>
|
|
164
181
|
|
|
165
182
|
<script setup lang="ts">
|
|
166
|
-
import { computed, onMounted, ref, watch, defineComponent } from 'vue';
|
|
183
|
+
import { computed, onMounted, ref, watch, defineComponent, onBeforeMount } from 'vue';
|
|
167
184
|
import { RouterLink, RouterView } from 'vue-router';
|
|
168
185
|
import { initFlowbite } from 'flowbite'
|
|
169
186
|
import './index.scss'
|
|
@@ -194,6 +211,7 @@ const splitAtLast = (str: string, separator: string) => {
|
|
|
194
211
|
}
|
|
195
212
|
createHead()
|
|
196
213
|
const sideBarOpen = ref(false);
|
|
214
|
+
const defaultLayout = ref(true);
|
|
197
215
|
const route = useRoute();
|
|
198
216
|
const router = useRouter();
|
|
199
217
|
const title = ref('');
|
|
@@ -211,6 +229,8 @@ const theme = ref('light');
|
|
|
211
229
|
function toggleTheme() {
|
|
212
230
|
theme.value = theme.value === 'light' ? 'dark' : 'light';
|
|
213
231
|
document.documentElement.classList.toggle('dark');
|
|
232
|
+
window.localStorage.setItem('theme', theme.value);
|
|
233
|
+
|
|
214
234
|
}
|
|
215
235
|
|
|
216
236
|
function clickOnMenuItem (label: string) {
|
|
@@ -237,35 +257,58 @@ async function initRouter() {
|
|
|
237
257
|
}
|
|
238
258
|
|
|
239
259
|
async function loadMenu() {
|
|
240
|
-
await coreStore.fetchMenuAndResource()
|
|
260
|
+
await coreStore.fetchMenuAndResource();
|
|
241
261
|
loginRedirectCheckIsReady.value = true;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function handleCustomLayout() {
|
|
265
|
+
if (route.meta?.customLayout) {
|
|
266
|
+
defaultLayout.value = false;
|
|
267
|
+
} else {
|
|
268
|
+
defaultLayout.value = true;
|
|
269
|
+
}
|
|
248
270
|
}
|
|
249
271
|
|
|
250
272
|
|
|
251
273
|
|
|
252
274
|
watch(route, () => {
|
|
275
|
+
handleCustomLayout()
|
|
253
276
|
title.value = `${coreStore.config?.title || coreStore.config?.brandName || 'Adminforth'} | ${ Object.values(route.params)[0] || route.meta.title || ' '}`;
|
|
254
277
|
useHead({
|
|
255
278
|
title: title.value,
|
|
256
279
|
})
|
|
280
|
+
|
|
281
|
+
|
|
257
282
|
});
|
|
258
283
|
|
|
284
|
+
watch (()=>coreStore.menu, () => {
|
|
285
|
+
coreStore.menu.forEach((item, i) => {
|
|
286
|
+
if (item.open) {
|
|
287
|
+
opened.value.push(i);
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
watch([loggedIn, routerIsReady, loginRedirectCheckIsReady], ([l,r,lr]) => {
|
|
293
|
+
if (l && r && lr) {
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
initFlowbite();
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
259
300
|
// initialize components based on data attribute selectors
|
|
260
301
|
onMounted(async () => {
|
|
261
302
|
loadMenu(); // run this in async mode
|
|
262
303
|
// before init flowbite we have to wait router initialized because it affects dom(our v-ifs) and fetch menu
|
|
263
304
|
await initRouter()
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
305
|
+
handleCustomLayout()
|
|
306
|
+
|
|
307
|
+
})
|
|
268
308
|
|
|
309
|
+
onBeforeMount(()=>{
|
|
310
|
+
theme.value = window.localStorage.getItem('theme') || 'light';
|
|
311
|
+
document.documentElement.classList.toggle('dark', theme.value === 'dark');
|
|
269
312
|
})
|
|
270
313
|
|
|
271
314
|
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<nav class="flex" aria-label="Breadcrumb">
|
|
3
|
-
<ol class="inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse">
|
|
3
|
+
<ol class="inline-flex items-center space-x-1 md:space-x-2 rtl:space-x-reverse flex-wrap">
|
|
4
4
|
<li class="inline-flex items-center">
|
|
5
5
|
<RouterLink :to="{name: 'home'}" class="inline-flex items-center text-sm font-medium text-gray-700 hover:text-blue-600 dark:text-gray-400 dark:hover:text-white">
|
|
6
6
|
<svg class="w-3 h-3 me-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
<svg class="rtl:rotate-180 w-3 h-3 text-gray-400 mx-1" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 6 10">
|
|
24
24
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 9 4-4-4-4"/>
|
|
25
25
|
</svg>
|
|
26
|
-
<span class="ms-1 text-sm font-medium text-gray-500 md:ms-2 dark:text-gray-400
|
|
26
|
+
<span class="ms-1 text-sm font-medium text-gray-500 md:ms-2 dark:text-gray-400
|
|
27
|
+
max-w-80 truncate">
|
|
27
28
|
{{ $route.name === 'resource-edit' ? 'Edit' : 'Show' }} {{ coreStore.record?._label }}</span>
|
|
28
29
|
</div>
|
|
29
30
|
</li>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<div class="
|
|
3
|
+
<div class="grid w-40 gap-4 mb-2">
|
|
4
4
|
<div>
|
|
5
5
|
<label for="start-time" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ label }}</label>
|
|
6
6
|
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
|
|
12
12
|
<input ref="datepickerStartEl" type="text"
|
|
13
13
|
class="bg-gray-50 border leading-none border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full 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"
|
|
14
|
-
placeholder="
|
|
14
|
+
placeholder="Select date">
|
|
15
15
|
</div>
|
|
16
16
|
</div>
|
|
17
17
|
</div>
|
|
18
18
|
|
|
19
19
|
<div>
|
|
20
|
-
<div class="
|
|
20
|
+
<div class="grid w-40 gap-4 mb-2" :class="{hidden: !showTimeInputs}">
|
|
21
21
|
<div>
|
|
22
22
|
<div class="relative">
|
|
23
23
|
<div class="absolute inset-y-0 end-0 top-0 flex items-center pe-3.5 pointer-events-none">
|
|
@@ -99,11 +99,13 @@ const start = computed(() => {
|
|
|
99
99
|
return date.utc().toISOString();
|
|
100
100
|
})
|
|
101
101
|
|
|
102
|
-
function updateFromProps() {
|
|
102
|
+
async function updateFromProps() {
|
|
103
103
|
if (!props.valueStart) {
|
|
104
104
|
datepickerStartEl.value.value = '';
|
|
105
105
|
startTime.value = '';
|
|
106
106
|
} else {
|
|
107
|
+
// wait ref to initialize
|
|
108
|
+
await (new Promise(resolve => setTimeout(resolve, 0)));
|
|
107
109
|
datepickerObject.value.setDate(dayjs(props.valueStart).format('DD MMM YYYY'));
|
|
108
110
|
startTime.value = dayjs(props.valueStart).format('HH:mm:ss')
|
|
109
111
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="flex flex-wrap gap-2">
|
|
3
3
|
<input
|
|
4
|
+
:min="minFormatted"
|
|
5
|
+
:max="maxFormatted"
|
|
4
6
|
type="number" aria-describedby="helper-text-explanation"
|
|
5
7
|
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
8
|
placeholder="From"
|
|
@@ -8,6 +10,8 @@
|
|
|
8
10
|
>
|
|
9
11
|
|
|
10
12
|
<input
|
|
13
|
+
:min="minFormatted"
|
|
14
|
+
:max="maxFormatted"
|
|
11
15
|
type="number" aria-describedby="helper-text-explanation"
|
|
12
16
|
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
17
|
placeholder="To"
|
|
@@ -21,7 +25,7 @@
|
|
|
21
25
|
@click="clear">Clear
|
|
22
26
|
</button>
|
|
23
27
|
|
|
24
|
-
<div class="w-full">
|
|
28
|
+
<div v-if="min && max" class="w-full px-2.5">
|
|
25
29
|
<vue-slider
|
|
26
30
|
class="custom-slider"
|
|
27
31
|
:dot-size="20"
|
|
@@ -47,12 +51,8 @@ const props = defineProps({
|
|
|
47
51
|
valueEnd: {
|
|
48
52
|
default: '',
|
|
49
53
|
},
|
|
50
|
-
min: {
|
|
51
|
-
|
|
52
|
-
},
|
|
53
|
-
max: {
|
|
54
|
-
default: 10,
|
|
55
|
-
},
|
|
54
|
+
min: {},
|
|
55
|
+
max: {},
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
const emit = defineEmits(['update:valueStart', 'update:valueEnd']);
|
|
@@ -108,6 +108,10 @@ watch(end, () => {
|
|
|
108
108
|
emit('update:valueEnd', end.value);
|
|
109
109
|
})
|
|
110
110
|
|
|
111
|
+
watch([minFormatted,maxFormatted], () => {
|
|
112
|
+
setSliderValues(minFormatted.value, maxFormatted.value)
|
|
113
|
+
})
|
|
114
|
+
|
|
111
115
|
const clear = () => {
|
|
112
116
|
start.value = ''
|
|
113
117
|
end.value = ''
|