@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.
@@ -5,20 +5,65 @@
5
5
  is-sidebar-group
6
6
  full-screen
7
7
  >
8
- <p>dasdasd</p>
9
- <Card> asdasdasdasdasdasd </Card>
10
- <div class="bg-white p-4">
11
- <Tabs
12
- variant="link"
13
- :items="items"
14
- class="w-full"
15
- />
16
- <Tabs
17
- orientation="vertical"
18
- :items="items"
19
- class="w-full"
20
- />
21
- </div>
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
- <div class="w-full bg-gray-50 pt-[64px] lg:pt-[72px]">
117
- <main
118
- :class="[
119
- 'mx-auto min-h-full flex-1 px-6 py-10 lg:px-8',
120
- fullScreen ? 'w-full 2xl:max-w-[90%]' : 'max-w-7xl 2xl:max-w-[70%]',
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
- <Breadcrumb
124
- v-if="
125
- !app.pageMeta.isHideBreadcrumbs && breadcrumbsItems.length > 1
126
- "
127
- :items="breadcrumbsItems"
128
- class="mb-6"
129
- :ui="{
130
- item: 'max-w-2/3',
131
- list: 'w-full',
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-if="app.pageMeta.title"
136
- class="mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4 lg:flex-row lg:items-start"
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 class="flex flex-1 flex-col">
139
- <h1
140
- class="text-3xl font-bold wrap-break-word lg:max-w-2/3"
141
- :title="app.pageMeta.title"
142
- >
143
- {{ app.pageMeta.title }}
144
- <span id="page-title-extra" />
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
- <slot />
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
@@ -10,6 +10,9 @@ const {
10
10
 
11
11
  export default defineNuxtConfig({
12
12
  modules: ['@nuxt/eslint', '@nuxt/test-utils', '@finema/core'],
13
+ imports: {
14
+ dirs: ['./constants', './loaders', './loaders/pmo', './helpers', './types'],
15
+ },
13
16
  devtools: {
14
17
  enabled: true,
15
18
  },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@finema/finework-layer",
3
3
  "type": "module",
4
- "version": "0.2.38",
4
+ "version": "0.2.40",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground -o",