@finema/finework-layer 0.2.74 → 0.2.76

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.
Files changed (50) hide show
  1. package/.husky/pre-commit +1 -1
  2. package/.playground/.env.example +5 -0
  3. package/.playground/app/assets/css/main.css +6 -6
  4. package/.playground/app/pages/layout-admin/[id]/index.vue +145 -145
  5. package/.playground/app/pages/layout-admin/test/[id]/index.vue +286 -286
  6. package/.playground/app/pages/layout-admin.vue +285 -285
  7. package/.playground/app/pages/layout-user.vue +284 -0
  8. package/.playground/app/pages/submenu/layout-admin.vue +210 -210
  9. package/.playground/public/admin/clock-in-admin-logo.png +0 -0
  10. package/.playground/public/admin/clock-in-logo.png +0 -0
  11. package/.playground/public/admin/clock-in.png +0 -0
  12. package/.playground/public/admin/pmo-logo.png +0 -0
  13. package/.playground/public/admin/spider-web.png +0 -0
  14. package/.playground/public/admin/super-admin-logo.png +0 -0
  15. package/.playground/public/admin/super-admin.png +0 -0
  16. package/.playground/public/admin/timesheet-admin-logo.png +0 -0
  17. package/.playground/public/admin/timesheet-logo.png +0 -0
  18. package/.playground/public/admin/timesheet.png +0 -0
  19. package/CHANGELOG.md +374 -357
  20. package/app/app.config.ts +144 -144
  21. package/app/app.vue +10 -10
  22. package/app/assets/css/main.css +77 -77
  23. package/app/components/Button/ActionIcon.vue +29 -29
  24. package/app/components/Button/Back.vue +22 -22
  25. package/app/components/Format/Currency.vue +17 -0
  26. package/app/components/Format/Date.vue +24 -0
  27. package/app/components/Format/DateTime.vue +24 -0
  28. package/app/components/Format/Number.vue +17 -0
  29. package/app/components/Format/Percent.vue +38 -0
  30. package/app/components/Format/TimeFromNow.vue +38 -0
  31. package/app/components/InfoItemList.vue +196 -196
  32. package/app/components/Layout/Admin/Sidebar.vue +329 -329
  33. package/app/components/Layout/Admin/index.vue +224 -224
  34. package/app/components/Layout/Apps.vue +45 -45
  35. package/app/components/Layout/User/index.vue +102 -102
  36. package/app/components/Notifications/index.vue +147 -0
  37. package/app/components/StatusBox.vue +56 -56
  38. package/app/composables/useAuth.ts +207 -207
  39. package/app/composables/useNotification.ts +64 -0
  40. package/app/composables/useRequestOptions.ts +86 -69
  41. package/app/constants/routes.ts +86 -86
  42. package/app/error.vue +218 -218
  43. package/app/middleware/auth.ts +45 -45
  44. package/app/middleware/common.ts +12 -12
  45. package/app/middleware/guest.ts +7 -7
  46. package/app/middleware/permissions.ts +29 -29
  47. package/bun.lock +2758 -2758
  48. package/index.d.ts +16 -16
  49. package/nuxt.config.ts +41 -41
  50. package/package.json +38 -38
@@ -1,285 +1,285 @@
1
- <template>
2
- <LayoutAdmin
3
- label="Test Layout"
4
- :sidebar-items="sidebarItems"
5
- is-sidebar-group
6
- root-app-path="/layout-admin"
7
- >
8
- <NuxtLayout>
9
- <div class="mb-4 flex gap-4">
10
- <FormFields
11
- :form="form"
12
- :options="formSearchFields"
13
- class="w-full max-w-[600px] gap-3"
14
- />
15
- <Button
16
- icon="material-symbols:filter-list-rounded"
17
- variant="outline"
18
- color="neutral"
19
- class="w-fit"
20
- @click="showFilter = !showFilter"
21
- >
22
- Filters
23
- <Badge
24
- v-if="countForm"
25
- class="size-6 items-center justify-center rounded-full"
26
- :label="countForm"
27
- />
28
- </Button>
29
- </div>
30
- <Card> asdasdasdasdasdasd </Card>
31
- <div class="bg-white p-4">
32
- <Tabs
33
- variant="link"
34
- :items="items"
35
- class="w-full"
36
- />
37
- <Tabs
38
- orientation="vertical"
39
- :items="items"
40
- class="w-full"
41
- />
42
- </div>
43
- <Button
44
- variant="outline"
45
- color="error"
46
- icon="ph:trash"
47
- />
48
- <Button
49
- variant="outline"
50
- color="neutral"
51
- icon="ph:trash"
52
- />
53
- <InfoItemList
54
- :items="[
55
- {
56
- label: 'Status:',
57
- value: 'Active',
58
- },
59
- {
60
- label: '',
61
- value: '42',
62
- },
63
- ]"
64
- />
65
- </NuxtLayout>
66
- </LayoutAdmin>
67
- </template>
68
-
69
- <script lang="ts" setup>
70
- import type { NavigationMenuItem } from '@nuxt/ui'
71
-
72
- const auth = useAuth()
73
- const showFilter = useState('pageFilterOpen')
74
- const items = ref([
75
- {
76
- label: 'Account',
77
- icon: 'i-lucide-user',
78
- content: 'This is the account content.',
79
- },
80
- {
81
- label: 'Password',
82
- icon: 'i-lucide-lock',
83
- content: 'This is the password content.',
84
- },
85
- ])
86
-
87
- useApp().definePage({
88
- title: 'Test Layout',
89
- breadcrumbs: [
90
- {
91
- label: 'Home',
92
- to: '/',
93
- },
94
- {
95
- label: 'Test Layout',
96
- to: '/test-layout',
97
- },
98
- ],
99
- })
100
-
101
- const form = useForm({
102
- validationSchema: toTypedSchema(
103
- v.object({
104
- q: v.optional(v.pipe(v.string()), ''),
105
- status: v.optional(v.pipe(v.string()), ''),
106
- user_id: v.optional(v.pipe(v.string()), ''),
107
- date_range: v.nullish(
108
- v.object({
109
- start: v.union([v.date(), v.string()]),
110
- end: v.union([v.date(), v.string()]),
111
- }),
112
- ),
113
- }),
114
- ),
115
- })
116
-
117
- const countForm = computed(
118
- () =>
119
- [
120
- form.values.q,
121
- form.values.status,
122
- form.values.user_id,
123
- form.values.date_range?.start || form.values.date_range?.end,
124
- ].filter(Boolean).length,
125
- )
126
-
127
- auth.me.set({
128
- id: 'edab2396-2b6f-4855-b04e-a7c9ae9b70ae',
129
- created_at: '2025-09-02T10:16:37.195Z',
130
- updated_at: '2025-10-11T10:14:10.296Z',
131
- email: 'long@finema.co',
132
- full_name: 'Passakon Puttasuwan',
133
- display_name: 'Long',
134
- position: 'Technical Specialist',
135
- team_code: 'DEV',
136
- company: 'finema',
137
- avatar_url:
138
- 'https://avatars.slack-edge.com/2023-09-25/5968557597936_c750902e3ffc4f690d80_512.jpg',
139
- slack_id: 'U0147KLKKH8',
140
- azure_id: 'fd7fbb55-a249-4b79-b439-2270981bd3ae',
141
- is_active: true,
142
- joined_date: '2025-09-02T00:00:00Z',
143
- access_level: {
144
- user_id: 'edab2396-2b6f-4855-b04e-a7c9ae9b70ae',
145
- clockin: 'ADMIN',
146
- timesheet: 'ADMIN',
147
- pmo: 'SUPER',
148
- setting: 'SUPER',
149
- created_at: '2025-08-15T16:40:29.319Z',
150
- updated_at: '2025-09-30T10:14:59.228Z',
151
- },
152
- team: {
153
- id: 'ef2abb00-d57f-4a38-a5d1-660ee63d2e1f',
154
- created_at: '2025-08-26T10:36:53.598Z',
155
- updated_at: '2025-10-01T06:00:29.941Z',
156
- name: 'Developer Team',
157
- code: 'DEV',
158
- color: 'violet',
159
- description: '',
160
- working_start_at: '12:02:00',
161
- working_end_at: '20:00:00',
162
- created_by_id: null,
163
- updated_by_id: '96d3ec63-be14-41c6-9268-ea8095d2a73b',
164
- deleted_by_id: null,
165
- },
166
- })
167
-
168
- const sidebarItems: NavigationMenuItem[] = [
169
- {
170
- children: [
171
- {
172
- label: 'Submenu 1',
173
- to: '/layout-admin',
174
- icon: 'i-heroicons-outline:home',
175
- },
176
- {
177
- label: 'Submenu 2',
178
- to: '/layout-admin/test/1',
179
- icon: 'i-heroicons-outline:home',
180
- },
181
- ],
182
- },
183
- {
184
- label: 'DASHBOARD',
185
- children: [
186
- {
187
- label: 'Submenu 1',
188
- to: '/submenu',
189
- icon: 'i-heroicons-outline:home',
190
- },
191
- {
192
- label: 'Submenu 2',
193
- to: '/submenu/layout-admin',
194
- icon: 'i-heroicons-outline:home',
195
- },
196
- ],
197
- },
198
- {
199
- label: 'Users',
200
- children: [
201
- {
202
- label: 'Submenu 1',
203
- to: '/submenu1',
204
- icon: 'i-heroicons-outline:home',
205
- },
206
- {
207
- label: 'Submenu 2',
208
- to: '/layout-admin',
209
- icon: 'i-heroicons-outline:home',
210
- children: [
211
- {
212
- label: 'Submenu 2-1',
213
- to: '/layout-admin/1',
214
- icon: 'i-heroicons-outline:home',
215
- },
216
- {
217
- label: 'Submenu 2-2',
218
- to: '/layout-admin/2',
219
- },
220
- ],
221
- },
222
- ],
223
- },
224
- ]
225
-
226
- const formSearchFields = createFormFields(() => [
227
- {
228
- type: INPUT_TYPES.SEARCH,
229
- props: {
230
- name: 'q',
231
- placeholder: 'ค้นหา',
232
- },
233
- },
234
- ])
235
-
236
- const formFields = createFormFields(() => [
237
- {
238
- type: INPUT_TYPES.SEARCH,
239
- props: {
240
- name: 'q',
241
- placeholder: 'ค้นหา',
242
- },
243
- },
244
- {
245
- type: INPUT_TYPES.DATE_RANGE,
246
- props: {
247
- label: 'Tender Date',
248
- name: 'date_range',
249
- placeholder: 'ระบุช่วงเวลา Tender Date',
250
- clearable: true,
251
- },
252
- },
253
- {
254
- type: INPUT_TYPES.SELECT,
255
- props: {
256
- name: 'user_id',
257
- label: 'Main Responsibility',
258
- placeholder: 'Main Responsibility',
259
- options: [
260
- {
261
- value: 'test',
262
- label: 'test',
263
- },
264
- ],
265
- searchable: true,
266
- clearable: true,
267
- },
268
- },
269
- {
270
- type: INPUT_TYPES.SELECT,
271
- props: {
272
- name: 'status',
273
- label: 'status',
274
- placeholder: 'All Status',
275
- clearable: true,
276
- options: [
277
- {
278
- value: 'test',
279
- label: 'test',
280
- },
281
- ],
282
- },
283
- },
284
- ])
285
- </script>
1
+ <template>
2
+ <LayoutAdmin
3
+ label="Test Layout"
4
+ :sidebar-items="sidebarItems"
5
+ is-sidebar-group
6
+ root-app-path="/layout-admin"
7
+ >
8
+ <NuxtLayout>
9
+ <div class="mb-4 flex gap-4">
10
+ <FormFields
11
+ :form="form"
12
+ :options="formSearchFields"
13
+ class="w-full max-w-[600px] gap-3"
14
+ />
15
+ <Button
16
+ icon="material-symbols:filter-list-rounded"
17
+ variant="outline"
18
+ color="neutral"
19
+ class="w-fit"
20
+ @click="showFilter = !showFilter"
21
+ >
22
+ Filters
23
+ <Badge
24
+ v-if="countForm"
25
+ class="size-6 items-center justify-center rounded-full"
26
+ :label="countForm"
27
+ />
28
+ </Button>
29
+ </div>
30
+ <Card> asdasdasdasdasdasd </Card>
31
+ <div class="bg-white p-4">
32
+ <Tabs
33
+ variant="link"
34
+ :items="items"
35
+ class="w-full"
36
+ />
37
+ <Tabs
38
+ orientation="vertical"
39
+ :items="items"
40
+ class="w-full"
41
+ />
42
+ </div>
43
+ <Button
44
+ variant="outline"
45
+ color="error"
46
+ icon="ph:trash"
47
+ />
48
+ <Button
49
+ variant="outline"
50
+ color="neutral"
51
+ icon="ph:trash"
52
+ />
53
+ <InfoItemList
54
+ :items="[
55
+ {
56
+ label: 'Status:',
57
+ value: 'Active',
58
+ },
59
+ {
60
+ label: '',
61
+ value: '42',
62
+ },
63
+ ]"
64
+ />
65
+ </NuxtLayout>
66
+ </LayoutAdmin>
67
+ </template>
68
+
69
+ <script lang="ts" setup>
70
+ import type { NavigationMenuItem } from '@nuxt/ui'
71
+
72
+ const auth = useAuth()
73
+ const showFilter = useState('pageFilterOpen')
74
+ const items = ref([
75
+ {
76
+ label: 'Account',
77
+ icon: 'i-lucide-user',
78
+ content: 'This is the account content.',
79
+ },
80
+ {
81
+ label: 'Password',
82
+ icon: 'i-lucide-lock',
83
+ content: 'This is the password content.',
84
+ },
85
+ ])
86
+
87
+ useApp().definePage({
88
+ title: 'Test Layout',
89
+ breadcrumbs: [
90
+ {
91
+ label: 'Home',
92
+ to: '/',
93
+ },
94
+ {
95
+ label: 'Test Layout',
96
+ to: '/test-layout',
97
+ },
98
+ ],
99
+ })
100
+
101
+ const form = useForm({
102
+ validationSchema: toTypedSchema(
103
+ v.object({
104
+ q: v.optional(v.pipe(v.string()), ''),
105
+ status: v.optional(v.pipe(v.string()), ''),
106
+ user_id: v.optional(v.pipe(v.string()), ''),
107
+ date_range: v.nullish(
108
+ v.object({
109
+ start: v.union([v.date(), v.string()]),
110
+ end: v.union([v.date(), v.string()]),
111
+ }),
112
+ ),
113
+ }),
114
+ ),
115
+ })
116
+
117
+ const countForm = computed(
118
+ () =>
119
+ [
120
+ form.values.q,
121
+ form.values.status,
122
+ form.values.user_id,
123
+ form.values.date_range?.start || form.values.date_range?.end,
124
+ ].filter(Boolean).length,
125
+ )
126
+
127
+ auth.me.set({
128
+ id: 'edab2396-2b6f-4855-b04e-a7c9ae9b70ae',
129
+ created_at: '2025-09-02T10:16:37.195Z',
130
+ updated_at: '2025-10-11T10:14:10.296Z',
131
+ email: 'long@finema.co',
132
+ full_name: 'Passakon Puttasuwan',
133
+ display_name: 'Long',
134
+ position: 'Technical Specialist',
135
+ team_code: 'DEV',
136
+ company: 'finema',
137
+ avatar_url:
138
+ 'https://avatars.slack-edge.com/2023-09-25/5968557597936_c750902e3ffc4f690d80_512.jpg',
139
+ slack_id: 'U0147KLKKH8',
140
+ azure_id: 'fd7fbb55-a249-4b79-b439-2270981bd3ae',
141
+ is_active: true,
142
+ joined_date: '2025-09-02T00:00:00Z',
143
+ access_level: {
144
+ user_id: 'edab2396-2b6f-4855-b04e-a7c9ae9b70ae',
145
+ clockin: 'ADMIN',
146
+ timesheet: 'ADMIN',
147
+ pmo: 'SUPER',
148
+ setting: 'SUPER',
149
+ created_at: '2025-08-15T16:40:29.319Z',
150
+ updated_at: '2025-09-30T10:14:59.228Z',
151
+ },
152
+ team: {
153
+ id: 'ef2abb00-d57f-4a38-a5d1-660ee63d2e1f',
154
+ created_at: '2025-08-26T10:36:53.598Z',
155
+ updated_at: '2025-10-01T06:00:29.941Z',
156
+ name: 'Developer Team',
157
+ code: 'DEV',
158
+ color: 'violet',
159
+ description: '',
160
+ working_start_at: '12:02:00',
161
+ working_end_at: '20:00:00',
162
+ created_by_id: null,
163
+ updated_by_id: '96d3ec63-be14-41c6-9268-ea8095d2a73b',
164
+ deleted_by_id: null,
165
+ },
166
+ })
167
+
168
+ const sidebarItems: NavigationMenuItem[] = [
169
+ {
170
+ children: [
171
+ {
172
+ label: 'Submenu 1',
173
+ to: '/layout-admin',
174
+ icon: 'i-heroicons-outline:home',
175
+ },
176
+ {
177
+ label: 'Submenu 2',
178
+ to: '/layout-admin/test/1',
179
+ icon: 'i-heroicons-outline:home',
180
+ },
181
+ ],
182
+ },
183
+ {
184
+ label: 'DASHBOARD',
185
+ children: [
186
+ {
187
+ label: 'Submenu 1',
188
+ to: '/submenu',
189
+ icon: 'i-heroicons-outline:home',
190
+ },
191
+ {
192
+ label: 'Submenu 2',
193
+ to: '/submenu/layout-admin',
194
+ icon: 'i-heroicons-outline:home',
195
+ },
196
+ ],
197
+ },
198
+ {
199
+ label: 'Users',
200
+ children: [
201
+ {
202
+ label: 'Submenu 1',
203
+ to: '/submenu1',
204
+ icon: 'i-heroicons-outline:home',
205
+ },
206
+ {
207
+ label: 'Submenu 2',
208
+ to: '/layout-admin',
209
+ icon: 'i-heroicons-outline:home',
210
+ children: [
211
+ {
212
+ label: 'Submenu 2-1',
213
+ to: '/layout-admin/1',
214
+ icon: 'i-heroicons-outline:home',
215
+ },
216
+ {
217
+ label: 'Submenu 2-2',
218
+ to: '/layout-admin/2',
219
+ },
220
+ ],
221
+ },
222
+ ],
223
+ },
224
+ ]
225
+
226
+ const formSearchFields = createFormFields(() => [
227
+ {
228
+ type: INPUT_TYPES.SEARCH,
229
+ props: {
230
+ name: 'q',
231
+ placeholder: 'ค้นหา',
232
+ },
233
+ },
234
+ ])
235
+
236
+ const formFields = createFormFields(() => [
237
+ {
238
+ type: INPUT_TYPES.SEARCH,
239
+ props: {
240
+ name: 'q',
241
+ placeholder: 'ค้นหา',
242
+ },
243
+ },
244
+ {
245
+ type: INPUT_TYPES.DATE_RANGE,
246
+ props: {
247
+ label: 'Tender Date',
248
+ name: 'date_range',
249
+ placeholder: 'ระบุช่วงเวลา Tender Date',
250
+ clearable: true,
251
+ },
252
+ },
253
+ {
254
+ type: INPUT_TYPES.SELECT,
255
+ props: {
256
+ name: 'user_id',
257
+ label: 'Main Responsibility',
258
+ placeholder: 'Main Responsibility',
259
+ options: [
260
+ {
261
+ value: 'test',
262
+ label: 'test',
263
+ },
264
+ ],
265
+ searchable: true,
266
+ clearable: true,
267
+ },
268
+ },
269
+ {
270
+ type: INPUT_TYPES.SELECT,
271
+ props: {
272
+ name: 'status',
273
+ label: 'status',
274
+ placeholder: 'All Status',
275
+ clearable: true,
276
+ options: [
277
+ {
278
+ value: 'test',
279
+ label: 'test',
280
+ },
281
+ ],
282
+ },
283
+ },
284
+ ])
285
+ </script>