@finema/finework-layer 0.0.13 → 0.0.15
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/CHANGELOG.md +13 -0
- package/app/components/Layout/Admin/Sidebar.vue +50 -3
- package/app/components/Layout/Admin/index.vue +220 -220
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.0.15](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.0.14...0.0.15) (2025-10-14)
|
|
4
|
+
|
|
5
|
+
### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* **admin:** always show logo in sidebar ([914c216](https://gitlab.finema.co/finema/finework/finework-frontend-layer/commit/914c216503b911574d85d658b7cf76618d58e007))
|
|
8
|
+
|
|
9
|
+
## [0.0.14](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.0.13...0.0.14) (2025-10-14)
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **ui:** adjust responsive header height and padding ([000e5d7](https://gitlab.finema.co/finema/finework/finework-frontend-layer/commit/000e5d75d2779c5fd94354cf4426f5a9085af733))
|
|
14
|
+
* **ui:** adjust responsive layout of admin header elements ([0c46820](https://gitlab.finema.co/finema/finework/finework-frontend-layer/commit/0c468205b498a1ff6c260eefd32b4e244a671f96))
|
|
15
|
+
|
|
3
16
|
## [0.0.13](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.0.12...0.0.13) (2025-10-14)
|
|
4
17
|
|
|
5
18
|
### Features
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
>
|
|
14
14
|
<NuxtLink :to="routes.home.to">
|
|
15
15
|
<img
|
|
16
|
-
v-if="!isCollapsed"
|
|
17
16
|
src="/logo-mini.png"
|
|
18
17
|
alt="logo_mini_color"
|
|
19
18
|
class="h-[27px]"
|
|
@@ -148,6 +147,7 @@
|
|
|
148
147
|
import type { NavigationMenuItem } from '@nuxt/ui'
|
|
149
148
|
import { computed } from 'vue'
|
|
150
149
|
import { useRoute } from 'vue-router'
|
|
150
|
+
import type { Permission, UserModule } from '#imports'
|
|
151
151
|
import Navbar from './Apps.vue'
|
|
152
152
|
|
|
153
153
|
defineEmits<{
|
|
@@ -218,9 +218,56 @@ const mappedItems = (items: NavigationMenuItem[]): NavigationMenuItem[] => {
|
|
|
218
218
|
})
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
// filter items base on auth.hasPermission and nested children , permissions: ['pmo:USER', 'pmo:ADMIN', 'pmo:SUPER']
|
|
222
|
+
const filterItems = (items: NavigationMenuItem[]): NavigationMenuItem[] => {
|
|
223
|
+
return items.filter((item) => {
|
|
224
|
+
if (item.permissions && Array.isArray(item.permissions)) {
|
|
225
|
+
// permissions: ['pmo:USER', 'pmo:ADMIN', 'pmo:SUPER'] => module: 'pmo', permissions: ['USER', 'ADMIN', 'SUPER']
|
|
226
|
+
const permissionStrings = item.permissions as string[]
|
|
227
|
+
|
|
228
|
+
// Extract module from first permission (all should have same module)
|
|
229
|
+
const firstPermission = permissionStrings[0]
|
|
230
|
+
|
|
231
|
+
if (!firstPermission) {
|
|
232
|
+
return true
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const [moduleStr] = firstPermission.split(':')
|
|
236
|
+
|
|
237
|
+
// Extract all permission levels
|
|
238
|
+
const permissionLevels = permissionStrings.map((p) => p.split(':')[1]) as Permission[]
|
|
239
|
+
|
|
240
|
+
return auth.hasPermission(moduleStr as UserModule, ...permissionLevels)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Recursively filter children
|
|
244
|
+
if (item.children) {
|
|
245
|
+
const filteredChildren = filterItems(item.children as NavigationMenuItem[])
|
|
246
|
+
|
|
247
|
+
// Only include parent if it has visible children or no permission requirement
|
|
248
|
+
return filteredChildren.length > 0
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return true
|
|
252
|
+
}).map((item) => {
|
|
253
|
+
// Apply filtering to children
|
|
254
|
+
if (item.children) {
|
|
255
|
+
return {
|
|
256
|
+
...item,
|
|
257
|
+
children: filterItems(item.children as NavigationMenuItem[]),
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return item
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
|
|
221
265
|
const navigationItems = computed<NavigationMenuItem[]>(() => {
|
|
266
|
+
// First filter items based on permissions
|
|
267
|
+
const filteredItems = filterItems(props.items)
|
|
268
|
+
|
|
222
269
|
if (props.isGroup) {
|
|
223
|
-
return
|
|
270
|
+
return filteredItems.map((group) => {
|
|
224
271
|
return {
|
|
225
272
|
...group,
|
|
226
273
|
children: mappedItems(group.children as NavigationMenuItem[]),
|
|
@@ -228,6 +275,6 @@ const navigationItems = computed<NavigationMenuItem[]>(() => {
|
|
|
228
275
|
})
|
|
229
276
|
}
|
|
230
277
|
|
|
231
|
-
return mappedItems(
|
|
278
|
+
return mappedItems(filteredItems)
|
|
232
279
|
})
|
|
233
280
|
</script>
|
|
@@ -1,220 +1,220 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="relative flex min-h-screen flex-1">
|
|
3
|
-
<div
|
|
4
|
-
:class="[`
|
|
5
|
-
fixed inset-0 z-30 hidden w-auto
|
|
6
|
-
lg:block
|
|
7
|
-
`, {
|
|
8
|
-
'max-w-[88px]': isCollapsed,
|
|
9
|
-
'max-w-[260px]': !isCollapsed,
|
|
10
|
-
}]"
|
|
11
|
-
>
|
|
12
|
-
<Sidebar
|
|
13
|
-
:label="label"
|
|
14
|
-
:is-collapsed="isCollapsed"
|
|
15
|
-
:items="sidebarItems"
|
|
16
|
-
:is-group="isSidebarGroup"
|
|
17
|
-
@toggle-collapsed="isCollapsed = !isCollapsed"
|
|
18
|
-
/>
|
|
19
|
-
</div>
|
|
20
|
-
<div
|
|
21
|
-
:class="['w-full bg-gray-50', {
|
|
22
|
-
'lg:pl-[88px]': isCollapsed,
|
|
23
|
-
'lg:pl-[260px]': !isCollapsed,
|
|
24
|
-
}]"
|
|
25
|
-
>
|
|
26
|
-
<nav
|
|
27
|
-
class="
|
|
28
|
-
fixed top-0 left-0 z-20 flex min-h-[
|
|
29
|
-
|
|
30
|
-
lg:justify-end
|
|
31
|
-
"
|
|
32
|
-
style="box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.04);"
|
|
33
|
-
>
|
|
34
|
-
<div class="flex items-center gap-4">
|
|
35
|
-
<Slideover
|
|
36
|
-
v-model:open="isShowSidebarMobile"
|
|
37
|
-
:ui="{
|
|
38
|
-
content: 'w-[80%] max-w-[260px] lg:hidden',
|
|
39
|
-
overlay: 'lg:hidden',
|
|
40
|
-
}"
|
|
41
|
-
side="left"
|
|
42
|
-
>
|
|
43
|
-
<svg
|
|
44
|
-
class="
|
|
45
|
-
cursor-pointer
|
|
46
|
-
lg:hidden
|
|
47
|
-
"
|
|
48
|
-
width="19"
|
|
49
|
-
height="18"
|
|
50
|
-
viewBox="0 0 19 18"
|
|
51
|
-
fill="none"
|
|
52
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
-
transform="scale(-1, 1)"
|
|
54
|
-
@click="isShowSidebarMobile = true"
|
|
55
|
-
>
|
|
56
|
-
<path
|
|
57
|
-
d="M10 18L10 16L19 16L19 18L10 18ZM6 2L6 -2.62268e-07L19 -8.30516e-07L19 2L6 2ZM-3.49691e-07 10L-4.37114e-07 8L19 8L19 10L-3.49691e-07 10Z"
|
|
58
|
-
fill="#4B5563"
|
|
59
|
-
/>
|
|
60
|
-
</svg>
|
|
61
|
-
|
|
62
|
-
<template #content>
|
|
63
|
-
<Sidebar
|
|
64
|
-
:label="label"
|
|
65
|
-
:is-collapsed="false"
|
|
66
|
-
:is-mobile="true"
|
|
67
|
-
:items="sidebarItems"
|
|
68
|
-
:is-group="isSidebarGroup"
|
|
69
|
-
@toggle-collapsed="isShowSidebarMobile = false"
|
|
70
|
-
/>
|
|
71
|
-
</template>
|
|
72
|
-
</Slideover>
|
|
73
|
-
<NuxtLink :to="routes.home.to">
|
|
74
|
-
<img
|
|
75
|
-
src="/logo-mini.png"
|
|
76
|
-
alt="logo_mini_color"
|
|
77
|
-
class="
|
|
78
|
-
|
|
79
|
-
lg:
|
|
80
|
-
"
|
|
81
|
-
/>
|
|
82
|
-
</NuxtLink>
|
|
83
|
-
<Apps class="lg:hidden" />
|
|
84
|
-
</div>
|
|
85
|
-
|
|
86
|
-
<div class="flex items-center justify-center gap-4">
|
|
87
|
-
<DropdownMenu
|
|
88
|
-
size="xl"
|
|
89
|
-
:items="userMenuItems"
|
|
90
|
-
:content="{
|
|
91
|
-
align: 'end',
|
|
92
|
-
side: 'bottom',
|
|
93
|
-
}"
|
|
94
|
-
:ui="{
|
|
95
|
-
content: 'w-48',
|
|
96
|
-
}"
|
|
97
|
-
>
|
|
98
|
-
<div class="relative flex cursor-pointer items-center gap-2">
|
|
99
|
-
<div
|
|
100
|
-
class="
|
|
101
|
-
>
|
|
102
|
-
<p class="font-bold">
|
|
103
|
-
{{ auth.me.value?.display_name || auth.me.value?.full_name }}
|
|
104
|
-
</p>
|
|
105
|
-
<p class="text-muted text-sm">
|
|
106
|
-
{{ auth.me.value?.email || '' }}
|
|
107
|
-
</p>
|
|
108
|
-
</div>
|
|
109
|
-
<Avatar
|
|
110
|
-
class="border-muted size-[40px] border text-2xl"
|
|
111
|
-
icon="ri:user-line"
|
|
112
|
-
:src="auth.me.value?.avatar_url || ''"
|
|
113
|
-
/>
|
|
114
|
-
<Icon
|
|
115
|
-
name="i-ph:caret-down-light"
|
|
116
|
-
class="size-5"
|
|
117
|
-
/>
|
|
118
|
-
</div>
|
|
119
|
-
</DropdownMenu>
|
|
120
|
-
</div>
|
|
121
|
-
</nav>
|
|
122
|
-
<div class="w-full bg-gray-50 pt-[72px]">
|
|
123
|
-
<main
|
|
124
|
-
class="
|
|
125
|
-
mx-auto min-h-full w-full flex-1 px-6 py-10 lg:px-8
|
|
126
|
-
"
|
|
127
|
-
>
|
|
128
|
-
<Breadcrumb
|
|
129
|
-
v-if="!app.pageMeta.isHideBreadcrumbs && breadcrumbsItems.length > 1"
|
|
130
|
-
:items="breadcrumbsItems"
|
|
131
|
-
class="mb-6"
|
|
132
|
-
:ui="{
|
|
133
|
-
item: 'max-w-2/3',
|
|
134
|
-
list: 'w-full',
|
|
135
|
-
}"
|
|
136
|
-
/>
|
|
137
|
-
<div
|
|
138
|
-
v-if="app.pageMeta.title"
|
|
139
|
-
class="
|
|
140
|
-
mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4
|
|
141
|
-
lg:flex-row lg:items-start
|
|
142
|
-
"
|
|
143
|
-
>
|
|
144
|
-
<div class="flex flex-1 flex-col">
|
|
145
|
-
<h1
|
|
146
|
-
class="
|
|
147
|
-
text-3xl font-bold wrap-break-word
|
|
148
|
-
lg:max-w-2/3
|
|
149
|
-
"
|
|
150
|
-
:title="app.pageMeta.title"
|
|
151
|
-
>
|
|
152
|
-
{{ app.pageMeta.title }}
|
|
153
|
-
<span id="page-title-extra" />
|
|
154
|
-
</h1>
|
|
155
|
-
|
|
156
|
-
<div id="page-subtitle" />
|
|
157
|
-
<p
|
|
158
|
-
v-if="app.pageMeta.sub_title"
|
|
159
|
-
class="text-[#475467]"
|
|
160
|
-
>
|
|
161
|
-
{{ app.pageMeta.sub_title }}
|
|
162
|
-
</p>
|
|
163
|
-
</div>
|
|
164
|
-
<div id="page-header" />
|
|
165
|
-
</div>
|
|
166
|
-
<slot />
|
|
167
|
-
</main>
|
|
168
|
-
</div>
|
|
169
|
-
</div>
|
|
170
|
-
</div>
|
|
171
|
-
</template>
|
|
172
|
-
|
|
173
|
-
<script lang="ts" setup>
|
|
174
|
-
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
|
|
175
|
-
import { computed } from 'vue'
|
|
176
|
-
import Sidebar from './Sidebar.vue'
|
|
177
|
-
import Apps from './Apps.vue'
|
|
178
|
-
|
|
179
|
-
defineProps<{
|
|
180
|
-
label: string
|
|
181
|
-
sidebarItems: NavigationMenuItem[]
|
|
182
|
-
isSidebarGroup?: boolean
|
|
183
|
-
}>()
|
|
184
|
-
|
|
185
|
-
const app = useApp()
|
|
186
|
-
const isShowSidebarMobile = ref(false)
|
|
187
|
-
const auth = useAuth()
|
|
188
|
-
// Cookie to store user preference for desktop
|
|
189
|
-
const isCollapsed = useCookie<boolean>('app.admin.sidebar.isCollapsed', {
|
|
190
|
-
default: () => false,
|
|
191
|
-
path: '/',
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
const userMenuItems: DropdownMenuItem[] = [
|
|
195
|
-
{
|
|
196
|
-
label: 'View profile',
|
|
197
|
-
icon: 'i-lucide-user',
|
|
198
|
-
to: routes.account.profile.to,
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
{
|
|
202
|
-
label: routes.logout.label,
|
|
203
|
-
icon: 'i-lucide-log-out',
|
|
204
|
-
to: routes.logout.to,
|
|
205
|
-
external: true,
|
|
206
|
-
},
|
|
207
|
-
]
|
|
208
|
-
|
|
209
|
-
const breadcrumbsItems = computed(() => [
|
|
210
|
-
// {
|
|
211
|
-
// label: '',
|
|
212
|
-
// icon: 'ph:house',
|
|
213
|
-
// to: '/',
|
|
214
|
-
// },
|
|
215
|
-
...(app.pageMeta.breadcrumbs || []).map((item: any) => ({
|
|
216
|
-
...item,
|
|
217
|
-
icon: '',
|
|
218
|
-
})),
|
|
219
|
-
])
|
|
220
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div class="relative flex min-h-screen flex-1">
|
|
3
|
+
<div
|
|
4
|
+
:class="[`
|
|
5
|
+
fixed inset-0 z-30 hidden w-auto
|
|
6
|
+
lg:block
|
|
7
|
+
`, {
|
|
8
|
+
'max-w-[88px]': isCollapsed,
|
|
9
|
+
'max-w-[260px]': !isCollapsed,
|
|
10
|
+
}]"
|
|
11
|
+
>
|
|
12
|
+
<Sidebar
|
|
13
|
+
:label="label"
|
|
14
|
+
:is-collapsed="isCollapsed"
|
|
15
|
+
:items="sidebarItems"
|
|
16
|
+
:is-group="isSidebarGroup"
|
|
17
|
+
@toggle-collapsed="isCollapsed = !isCollapsed"
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
<div
|
|
21
|
+
:class="['w-full bg-gray-50', {
|
|
22
|
+
'lg:pl-[88px]': isCollapsed,
|
|
23
|
+
'lg:pl-[260px]': !isCollapsed,
|
|
24
|
+
}]"
|
|
25
|
+
>
|
|
26
|
+
<nav
|
|
27
|
+
class="
|
|
28
|
+
fixed top-0 left-0 z-20 flex min-h-[64px] w-screen items-center justify-between
|
|
29
|
+
gap-4 bg-white px-5 lg:min-h-[72px]
|
|
30
|
+
lg:justify-end
|
|
31
|
+
"
|
|
32
|
+
style="box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.04);"
|
|
33
|
+
>
|
|
34
|
+
<div class="flex items-center gap-4">
|
|
35
|
+
<Slideover
|
|
36
|
+
v-model:open="isShowSidebarMobile"
|
|
37
|
+
:ui="{
|
|
38
|
+
content: 'w-[80%] max-w-[260px] lg:hidden',
|
|
39
|
+
overlay: 'lg:hidden',
|
|
40
|
+
}"
|
|
41
|
+
side="left"
|
|
42
|
+
>
|
|
43
|
+
<svg
|
|
44
|
+
class="
|
|
45
|
+
cursor-pointer
|
|
46
|
+
lg:hidden
|
|
47
|
+
"
|
|
48
|
+
width="19"
|
|
49
|
+
height="18"
|
|
50
|
+
viewBox="0 0 19 18"
|
|
51
|
+
fill="none"
|
|
52
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
transform="scale(-1, 1)"
|
|
54
|
+
@click="isShowSidebarMobile = true"
|
|
55
|
+
>
|
|
56
|
+
<path
|
|
57
|
+
d="M10 18L10 16L19 16L19 18L10 18ZM6 2L6 -2.62268e-07L19 -8.30516e-07L19 2L6 2ZM-3.49691e-07 10L-4.37114e-07 8L19 8L19 10L-3.49691e-07 10Z"
|
|
58
|
+
fill="#4B5563"
|
|
59
|
+
/>
|
|
60
|
+
</svg>
|
|
61
|
+
|
|
62
|
+
<template #content>
|
|
63
|
+
<Sidebar
|
|
64
|
+
:label="label"
|
|
65
|
+
:is-collapsed="false"
|
|
66
|
+
:is-mobile="true"
|
|
67
|
+
:items="sidebarItems"
|
|
68
|
+
:is-group="isSidebarGroup"
|
|
69
|
+
@toggle-collapsed="isShowSidebarMobile = false"
|
|
70
|
+
/>
|
|
71
|
+
</template>
|
|
72
|
+
</Slideover>
|
|
73
|
+
<NuxtLink :to="routes.home.to">
|
|
74
|
+
<img
|
|
75
|
+
src="/logo-mini.png"
|
|
76
|
+
alt="logo_mini_color"
|
|
77
|
+
class="
|
|
78
|
+
w-[126px] min-w-[126px] lg:hidden
|
|
79
|
+
lg:w-[172px] lg:min-w-[172px]
|
|
80
|
+
"
|
|
81
|
+
/>
|
|
82
|
+
</NuxtLink>
|
|
83
|
+
<Apps class="lg:hidden" />
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="flex items-center justify-center gap-4">
|
|
87
|
+
<DropdownMenu
|
|
88
|
+
size="xl"
|
|
89
|
+
:items="userMenuItems"
|
|
90
|
+
:content="{
|
|
91
|
+
align: 'end',
|
|
92
|
+
side: 'bottom',
|
|
93
|
+
}"
|
|
94
|
+
:ui="{
|
|
95
|
+
content: 'w-48',
|
|
96
|
+
}"
|
|
97
|
+
>
|
|
98
|
+
<div class="relative flex cursor-pointer items-center gap-2">
|
|
99
|
+
<div
|
|
100
|
+
class="hidden flex-col justify-end text-right lg:flex"
|
|
101
|
+
>
|
|
102
|
+
<p class="font-bold">
|
|
103
|
+
{{ auth.me.value?.display_name || auth.me.value?.full_name }}
|
|
104
|
+
</p>
|
|
105
|
+
<p class="text-muted text-sm">
|
|
106
|
+
{{ auth.me.value?.email || '' }}
|
|
107
|
+
</p>
|
|
108
|
+
</div>
|
|
109
|
+
<Avatar
|
|
110
|
+
class="border-muted size-[40px] border text-2xl"
|
|
111
|
+
icon="ri:user-line"
|
|
112
|
+
:src="auth.me.value?.avatar_url || ''"
|
|
113
|
+
/>
|
|
114
|
+
<Icon
|
|
115
|
+
name="i-ph:caret-down-light"
|
|
116
|
+
class="size-5"
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
119
|
+
</DropdownMenu>
|
|
120
|
+
</div>
|
|
121
|
+
</nav>
|
|
122
|
+
<div class="w-full bg-gray-50 pt-[64px] lg:pt-[72px]">
|
|
123
|
+
<main
|
|
124
|
+
class="
|
|
125
|
+
mx-auto min-h-full w-full flex-1 px-6 py-10 lg:px-8
|
|
126
|
+
"
|
|
127
|
+
>
|
|
128
|
+
<Breadcrumb
|
|
129
|
+
v-if="!app.pageMeta.isHideBreadcrumbs && breadcrumbsItems.length > 1"
|
|
130
|
+
:items="breadcrumbsItems"
|
|
131
|
+
class="mb-6"
|
|
132
|
+
:ui="{
|
|
133
|
+
item: 'max-w-2/3',
|
|
134
|
+
list: 'w-full',
|
|
135
|
+
}"
|
|
136
|
+
/>
|
|
137
|
+
<div
|
|
138
|
+
v-if="app.pageMeta.title"
|
|
139
|
+
class="
|
|
140
|
+
mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4
|
|
141
|
+
lg:flex-row lg:items-start
|
|
142
|
+
"
|
|
143
|
+
>
|
|
144
|
+
<div class="flex flex-1 flex-col">
|
|
145
|
+
<h1
|
|
146
|
+
class="
|
|
147
|
+
text-3xl font-bold wrap-break-word
|
|
148
|
+
lg:max-w-2/3
|
|
149
|
+
"
|
|
150
|
+
:title="app.pageMeta.title"
|
|
151
|
+
>
|
|
152
|
+
{{ app.pageMeta.title }}
|
|
153
|
+
<span id="page-title-extra" />
|
|
154
|
+
</h1>
|
|
155
|
+
|
|
156
|
+
<div id="page-subtitle" />
|
|
157
|
+
<p
|
|
158
|
+
v-if="app.pageMeta.sub_title"
|
|
159
|
+
class="text-[#475467]"
|
|
160
|
+
>
|
|
161
|
+
{{ app.pageMeta.sub_title }}
|
|
162
|
+
</p>
|
|
163
|
+
</div>
|
|
164
|
+
<div id="page-header" />
|
|
165
|
+
</div>
|
|
166
|
+
<slot />
|
|
167
|
+
</main>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
</template>
|
|
172
|
+
|
|
173
|
+
<script lang="ts" setup>
|
|
174
|
+
import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
|
|
175
|
+
import { computed } from 'vue'
|
|
176
|
+
import Sidebar from './Sidebar.vue'
|
|
177
|
+
import Apps from './Apps.vue'
|
|
178
|
+
|
|
179
|
+
defineProps<{
|
|
180
|
+
label: string
|
|
181
|
+
sidebarItems: NavigationMenuItem[]
|
|
182
|
+
isSidebarGroup?: boolean
|
|
183
|
+
}>()
|
|
184
|
+
|
|
185
|
+
const app = useApp()
|
|
186
|
+
const isShowSidebarMobile = ref(false)
|
|
187
|
+
const auth = useAuth()
|
|
188
|
+
// Cookie to store user preference for desktop
|
|
189
|
+
const isCollapsed = useCookie<boolean>('app.admin.sidebar.isCollapsed', {
|
|
190
|
+
default: () => false,
|
|
191
|
+
path: '/',
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const userMenuItems: DropdownMenuItem[] = [
|
|
195
|
+
{
|
|
196
|
+
label: 'View profile',
|
|
197
|
+
icon: 'i-lucide-user',
|
|
198
|
+
to: routes.account.profile.to,
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
{
|
|
202
|
+
label: routes.logout.label,
|
|
203
|
+
icon: 'i-lucide-log-out',
|
|
204
|
+
to: routes.logout.to,
|
|
205
|
+
external: true,
|
|
206
|
+
},
|
|
207
|
+
]
|
|
208
|
+
|
|
209
|
+
const breadcrumbsItems = computed(() => [
|
|
210
|
+
// {
|
|
211
|
+
// label: '',
|
|
212
|
+
// icon: 'ph:house',
|
|
213
|
+
// to: '/',
|
|
214
|
+
// },
|
|
215
|
+
...(app.pageMeta.breadcrumbs || []).map((item: any) => ({
|
|
216
|
+
...item,
|
|
217
|
+
icon: '',
|
|
218
|
+
})),
|
|
219
|
+
])
|
|
220
|
+
</script>
|