@finema/finework-layer 0.2.38 → 0.2.40
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/.playground/app/pages/layout-admin.vue +146 -14
- package/CHANGELOG.md +8 -0
- package/app/components/Layout/Admin/index.vue +112 -38
- package/nuxt.config.ts +3 -0
- package/package.json +1 -1
|
@@ -5,20 +5,65 @@
|
|
|
5
5
|
is-sidebar-group
|
|
6
6
|
full-screen
|
|
7
7
|
>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
8
|
+
<NuxtLayout>
|
|
9
|
+
<TeleportSafe to="#page-filters">
|
|
10
|
+
<div class="mb-4 flex items-center justify-between gap-1">
|
|
11
|
+
<div class="font-bold">
|
|
12
|
+
Filters
|
|
13
|
+
<Badge
|
|
14
|
+
v-if="countForm"
|
|
15
|
+
class="size-6 items-center justify-center rounded-full"
|
|
16
|
+
:label="countForm"
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
<Button
|
|
20
|
+
label="Clear all"
|
|
21
|
+
variant="ghost"
|
|
22
|
+
class="text-muted p-0 text-sm"
|
|
23
|
+
@click="form.resetForm()"
|
|
24
|
+
/>
|
|
25
|
+
</div>
|
|
26
|
+
<FormFields
|
|
27
|
+
:form="form"
|
|
28
|
+
:options="formFields"
|
|
29
|
+
class="grid w-full gap-x-3"
|
|
30
|
+
/>
|
|
31
|
+
</TeleportSafe>
|
|
32
|
+
<div class="mb-4 flex gap-4">
|
|
33
|
+
<FormFields
|
|
34
|
+
:form="form"
|
|
35
|
+
:options="formSearchFields"
|
|
36
|
+
class="w-full max-w-[600px] gap-3"
|
|
37
|
+
/>
|
|
38
|
+
<Button
|
|
39
|
+
icon="material-symbols:filter-list-rounded"
|
|
40
|
+
variant="outline"
|
|
41
|
+
color="neutral"
|
|
42
|
+
class="w-fit"
|
|
43
|
+
@click="showFilter = !showFilter"
|
|
44
|
+
>
|
|
45
|
+
Filters
|
|
46
|
+
<Badge
|
|
47
|
+
v-if="countForm"
|
|
48
|
+
class="size-6 items-center justify-center rounded-full"
|
|
49
|
+
:label="countForm"
|
|
50
|
+
/>
|
|
51
|
+
</Button>
|
|
52
|
+
</div>
|
|
53
|
+
<Card> asdasdasdasdasdasd </Card>
|
|
54
|
+
<div class="bg-white p-4">
|
|
55
|
+
<Tabs
|
|
56
|
+
variant="link"
|
|
57
|
+
:items="items"
|
|
58
|
+
class="w-full"
|
|
59
|
+
/>
|
|
60
|
+
<Tabs
|
|
61
|
+
orientation="vertical"
|
|
62
|
+
:items="items"
|
|
63
|
+
class="w-full"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
</NuxtLayout>
|
|
22
67
|
</LayoutAdmin>
|
|
23
68
|
</template>
|
|
24
69
|
|
|
@@ -26,6 +71,7 @@
|
|
|
26
71
|
import type { NavigationMenuItem } from '@nuxt/ui'
|
|
27
72
|
|
|
28
73
|
const auth = useAuth()
|
|
74
|
+
const showFilter = useState('pageFilterOpen')
|
|
29
75
|
const items = ref([
|
|
30
76
|
{
|
|
31
77
|
label: 'Account',
|
|
@@ -53,6 +99,32 @@ useApp().definePage({
|
|
|
53
99
|
],
|
|
54
100
|
})
|
|
55
101
|
|
|
102
|
+
const form = useForm({
|
|
103
|
+
validationSchema: toTypedSchema(
|
|
104
|
+
v.object({
|
|
105
|
+
q: v.optional(v.pipe(v.string()), ''),
|
|
106
|
+
status: v.optional(v.pipe(v.string()), ''),
|
|
107
|
+
user_id: v.optional(v.pipe(v.string()), ''),
|
|
108
|
+
date_range: v.nullish(
|
|
109
|
+
v.object({
|
|
110
|
+
start: v.union([v.date(), v.string()]),
|
|
111
|
+
end: v.union([v.date(), v.string()]),
|
|
112
|
+
}),
|
|
113
|
+
),
|
|
114
|
+
}),
|
|
115
|
+
),
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const countForm = computed(
|
|
119
|
+
() =>
|
|
120
|
+
[
|
|
121
|
+
form.values.q,
|
|
122
|
+
form.values.status,
|
|
123
|
+
form.values.user_id,
|
|
124
|
+
form.values.date_range?.start || form.values.date_range?.end,
|
|
125
|
+
].filter(Boolean).length,
|
|
126
|
+
)
|
|
127
|
+
|
|
56
128
|
auth.me.set({
|
|
57
129
|
id: 'edab2396-2b6f-4855-b04e-a7c9ae9b70ae',
|
|
58
130
|
created_at: '2025-09-02T10:16:37.195Z',
|
|
@@ -152,4 +224,64 @@ const sidebarItems: NavigationMenuItem[] = [
|
|
|
152
224
|
],
|
|
153
225
|
},
|
|
154
226
|
]
|
|
227
|
+
|
|
228
|
+
const formSearchFields = createFormFields(() => [
|
|
229
|
+
{
|
|
230
|
+
type: INPUT_TYPES.SEARCH,
|
|
231
|
+
props: {
|
|
232
|
+
name: 'q',
|
|
233
|
+
placeholder: 'ค้นหา',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
])
|
|
237
|
+
|
|
238
|
+
const formFields = createFormFields(() => [
|
|
239
|
+
{
|
|
240
|
+
type: INPUT_TYPES.SEARCH,
|
|
241
|
+
props: {
|
|
242
|
+
name: 'q',
|
|
243
|
+
placeholder: 'ค้นหา',
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: INPUT_TYPES.DATE_RANGE,
|
|
248
|
+
props: {
|
|
249
|
+
label: 'Tender Date',
|
|
250
|
+
name: 'date_range',
|
|
251
|
+
placeholder: 'ระบุช่วงเวลา Tender Date',
|
|
252
|
+
clearable: true,
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
type: INPUT_TYPES.SELECT,
|
|
257
|
+
props: {
|
|
258
|
+
name: 'user_id',
|
|
259
|
+
label: 'Main Responsibility',
|
|
260
|
+
placeholder: 'Main Responsibility',
|
|
261
|
+
options: [
|
|
262
|
+
{
|
|
263
|
+
value: 'test',
|
|
264
|
+
label: 'test',
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
searchable: true,
|
|
268
|
+
clearable: true,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
type: INPUT_TYPES.SELECT,
|
|
273
|
+
props: {
|
|
274
|
+
name: 'status',
|
|
275
|
+
label: 'status',
|
|
276
|
+
placeholder: 'All Status',
|
|
277
|
+
clearable: true,
|
|
278
|
+
options: [
|
|
279
|
+
{
|
|
280
|
+
value: 'test',
|
|
281
|
+
label: 'test',
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
])
|
|
155
287
|
</script>
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.40](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.2.39...0.2.40) (2025-10-31)
|
|
4
|
+
|
|
5
|
+
## [0.2.39](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.2.38...0.2.39) (2025-10-31)
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
* filter ([93ff377](https://gitlab.finema.co/finema/finework/finework-frontend-layer/commit/93ff377a0f40af2247bb89905d82a6700588a978))
|
|
10
|
+
|
|
3
11
|
## [0.2.38](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.2.37...0.2.38) (2025-10-30)
|
|
4
12
|
|
|
5
13
|
## [0.2.37](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.2.36...0.2.37) (2025-10-29)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="relative flex min-h-screen flex-1">
|
|
2
|
+
<div class="relative flex min-h-screen flex-1 overflow-hidden">
|
|
3
3
|
<div
|
|
4
4
|
:class="[
|
|
5
5
|
`
|
|
@@ -113,49 +113,96 @@
|
|
|
113
113
|
</DropdownMenu>
|
|
114
114
|
</div>
|
|
115
115
|
</nav>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
]"
|
|
116
|
+
|
|
117
|
+
<div class="flex transition-all">
|
|
118
|
+
<div
|
|
119
|
+
class="flex-1 bg-gray-50 pt-[64px] transition-[padding] duration-300 ease-in-out lg:pt-[72px]"
|
|
120
|
+
:class="{ 'md:pr-80': showFilter }"
|
|
122
121
|
>
|
|
123
|
-
<
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
<main
|
|
123
|
+
:class="[
|
|
124
|
+
'mx-auto min-h-full flex-1 px-6 py-10 lg:px-8',
|
|
125
|
+
fullScreen ? 'w-full' : 'max-w-7xl 2xl:max-w-[70%]',
|
|
126
|
+
]"
|
|
127
|
+
>
|
|
128
|
+
<Breadcrumb
|
|
129
|
+
v-if="
|
|
130
|
+
!app.pageMeta.isHideBreadcrumbs && breadcrumbsItems.length > 1
|
|
131
|
+
"
|
|
132
|
+
:items="breadcrumbsItems"
|
|
133
|
+
class="mb-6"
|
|
134
|
+
:ui="{
|
|
135
|
+
item: 'max-w-2/3',
|
|
136
|
+
list: 'w-full',
|
|
137
|
+
}"
|
|
138
|
+
/>
|
|
139
|
+
<div
|
|
140
|
+
v-if="app.pageMeta.title"
|
|
141
|
+
class="mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4 lg:flex-row lg:items-start"
|
|
142
|
+
>
|
|
143
|
+
<div class="flex flex-1 flex-col">
|
|
144
|
+
<h1
|
|
145
|
+
class="text-3xl font-bold wrap-break-word lg:max-w-2/3"
|
|
146
|
+
:title="app.pageMeta.title"
|
|
147
|
+
>
|
|
148
|
+
{{ app.pageMeta.title }}
|
|
149
|
+
<span id="page-title-extra" />
|
|
150
|
+
</h1>
|
|
151
|
+
|
|
152
|
+
<div id="page-subtitle" />
|
|
153
|
+
<p
|
|
154
|
+
v-if="app.pageMeta.sub_title"
|
|
155
|
+
class="text-[#475467]"
|
|
156
|
+
>
|
|
157
|
+
{{ app.pageMeta.sub_title }}
|
|
158
|
+
</p>
|
|
159
|
+
</div>
|
|
160
|
+
<div id="page-header" />
|
|
161
|
+
</div>
|
|
162
|
+
<slot />
|
|
163
|
+
</main>
|
|
164
|
+
</div>
|
|
165
|
+
<DefineTemplate>
|
|
166
|
+
<Button
|
|
167
|
+
icon="i-heroicons-x-mark"
|
|
168
|
+
variant="ghost"
|
|
169
|
+
color="neutral"
|
|
170
|
+
class="mb-3 ml-auto flex p-0 md:mt-3"
|
|
171
|
+
@click="showFilter = false"
|
|
133
172
|
/>
|
|
173
|
+
<div id="page-filters" />
|
|
174
|
+
</DefineTemplate>
|
|
175
|
+
<!-- แผง Filter (push ข้างๆ) Desktop -->
|
|
176
|
+
<div
|
|
177
|
+
v-if="!isMobile"
|
|
178
|
+
v-show="showFilter"
|
|
179
|
+
class="fixed top-0 right-0 hidden h-screen w-80 transform flex-col border-l border-gray-200 bg-white shadow-lg transition-transform duration-300 ease-in-out md:flex!"
|
|
180
|
+
:class="showFilter ? 'translate-x-0' : 'translate-x-full'"
|
|
181
|
+
>
|
|
182
|
+
<div class="flex-1 overflow-y-auto px-4 pt-[64px]! lg:pt-[72px]!">
|
|
183
|
+
<ReuseTemplate />
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
<!-- mobile -->
|
|
187
|
+
<Transition
|
|
188
|
+
v-else
|
|
189
|
+
name="slide-up"
|
|
190
|
+
>
|
|
134
191
|
<div
|
|
135
|
-
v-
|
|
136
|
-
class="
|
|
192
|
+
v-show="showFilter"
|
|
193
|
+
class="fixed inset-0 z-50 flex flex-col bg-black/30 md:hidden!"
|
|
194
|
+
@click.self="showFilter = false"
|
|
137
195
|
>
|
|
138
|
-
<div
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
</h1>
|
|
146
|
-
|
|
147
|
-
<div id="page-subtitle" />
|
|
148
|
-
<p
|
|
149
|
-
v-if="app.pageMeta.sub_title"
|
|
150
|
-
class="text-[#475467]"
|
|
151
|
-
>
|
|
152
|
-
{{ app.pageMeta.sub_title }}
|
|
153
|
-
</p>
|
|
196
|
+
<div
|
|
197
|
+
class="mt-auto flex max-h-[80vh] w-full flex-col rounded-t-2xl bg-white shadow-lg"
|
|
198
|
+
>
|
|
199
|
+
<!-- scrollable content -->
|
|
200
|
+
<div class="flex-1 overflow-y-auto p-4">
|
|
201
|
+
<ReuseTemplate />
|
|
202
|
+
</div>
|
|
154
203
|
</div>
|
|
155
|
-
<div id="page-header" />
|
|
156
204
|
</div>
|
|
157
|
-
|
|
158
|
-
</main>
|
|
205
|
+
</Transition>
|
|
159
206
|
</div>
|
|
160
207
|
</div>
|
|
161
208
|
</div>
|
|
@@ -166,6 +213,7 @@ import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
|
|
|
166
213
|
import { computed } from 'vue'
|
|
167
214
|
import Sidebar from './Sidebar.vue'
|
|
168
215
|
import Apps from '../Apps.vue'
|
|
216
|
+
import { createReusableTemplate, useBreakpoints } from '@vueuse/core'
|
|
169
217
|
|
|
170
218
|
defineProps<{
|
|
171
219
|
label: string
|
|
@@ -174,9 +222,17 @@ defineProps<{
|
|
|
174
222
|
fullScreen?: boolean
|
|
175
223
|
}>()
|
|
176
224
|
|
|
225
|
+
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
|
|
177
226
|
const app = useApp()
|
|
178
227
|
const isShowSidebarMobile = ref(false)
|
|
228
|
+
const showFilter = useState('pageFilterOpen', () => false)
|
|
179
229
|
const auth = useAuth()
|
|
230
|
+
const breakpoints = useBreakpoints({
|
|
231
|
+
mobile: 0,
|
|
232
|
+
desktop: 768,
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
const isMobile = breakpoints.smaller('desktop')
|
|
180
236
|
// Cookie to store user preference for desktop
|
|
181
237
|
const isCollapsed = useCookie<boolean>('app.admin.sidebar.isCollapsed', {
|
|
182
238
|
default: () => false,
|
|
@@ -216,4 +272,22 @@ const breadcrumbsItems = computed(() => {
|
|
|
216
272
|
|
|
217
273
|
return [home, ...extra]
|
|
218
274
|
})
|
|
275
|
+
|
|
276
|
+
watch(isMobile, (newVal, oldVal) => {
|
|
277
|
+
if (newVal !== oldVal) {
|
|
278
|
+
window.location.reload()
|
|
279
|
+
}
|
|
280
|
+
})
|
|
219
281
|
</script>
|
|
282
|
+
|
|
283
|
+
<style scoped>
|
|
284
|
+
.slide-enter-active,
|
|
285
|
+
.slide-leave-active {
|
|
286
|
+
transition: all 0.3s ease;
|
|
287
|
+
}
|
|
288
|
+
.slide-enter-from,
|
|
289
|
+
.slide-leave-to {
|
|
290
|
+
transform: translateX(100%);
|
|
291
|
+
opacity: 0;
|
|
292
|
+
}
|
|
293
|
+
</style>
|
package/nuxt.config.ts
CHANGED