@finema/finework-layer 0.2.50 → 0.2.51

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.
@@ -0,0 +1,780 @@
1
+ # API Reference
2
+
3
+ ## 🎯 Composables
4
+
5
+ ### useAuth()
6
+
7
+ Authentication and authorization composable.
8
+
9
+ ```typescript
10
+ const auth = useAuth()
11
+ ```
12
+
13
+ #### Properties
14
+
15
+ | Property | Type | Description |
16
+ |----------|------|-------------|
17
+ | `token` | `Ref<string \| undefined>` | Authentication token (cookie-based) |
18
+ | `isAuthenticated` | `ComputedRef<boolean>` | Whether user is authenticated |
19
+ | `me.value` | `IUser \| null` | Current user object |
20
+ | `me.timestamp` | `number \| null` | Last fetch timestamp |
21
+ | `isSuperAdmin` | `ComputedRef<boolean>` | Whether user is super admin |
22
+ | `menusNavbar` | `ComputedRef<MenuItem[]>` | Navigation menu items |
23
+
24
+ #### Methods
25
+
26
+ ##### `loginSlack()`
27
+ Redirect to Slack OAuth login.
28
+
29
+ ```typescript
30
+ auth.loginSlack()
31
+ ```
32
+
33
+ ##### `loginMs()`
34
+ Redirect to Microsoft OAuth login.
35
+
36
+ ```typescript
37
+ auth.loginMs()
38
+ ```
39
+
40
+ ##### `fetchMe.run()`
41
+ Fetch current user data.
42
+
43
+ ```typescript
44
+ await auth.fetchMe.run()
45
+
46
+ // Access data
47
+ if (auth.fetchMe.status.value.isSuccess) {
48
+ console.log(auth.me.value)
49
+ }
50
+ ```
51
+
52
+ ##### `updateMe.run(data)`
53
+ Update current user profile.
54
+
55
+ ```typescript
56
+ await auth.updateMe.run({
57
+ data: {
58
+ display_name: 'New Name',
59
+ position: 'Senior Developer'
60
+ }
61
+ })
62
+ ```
63
+
64
+ ##### `hasPermission(module, ...permissions)`
65
+ Check if user has specific permissions.
66
+
67
+ ```typescript
68
+ // Single permission
69
+ auth.hasPermission(UserModule.PMO, Permission.ADMIN)
70
+
71
+ // Multiple permissions (OR logic)
72
+ auth.hasPermission(UserModule.PMO, Permission.ADMIN, Permission.SUPER)
73
+ ```
74
+
75
+ **Parameters:**
76
+ - `module`: `UserModule` - The module to check
77
+ - `permissions`: `Permission[]` - One or more permissions
78
+
79
+ **Returns:** `boolean`
80
+
81
+ ---
82
+
83
+ ### useRequestOptions()
84
+
85
+ HTTP request configuration provider.
86
+
87
+ ```typescript
88
+ const { auth, base, mock, file } = useRequestOptions()
89
+ ```
90
+
91
+ #### Methods
92
+
93
+ ##### `auth()`
94
+ Returns authenticated request configuration.
95
+
96
+ ```typescript
97
+ const options = auth()
98
+ // {
99
+ // baseURL: 'https://api.example.com',
100
+ // headers: {
101
+ // Authorization: 'Bearer <token>'
102
+ // }
103
+ // }
104
+ ```
105
+
106
+ ##### `base()`
107
+ Returns base request configuration (no auth).
108
+
109
+ ```typescript
110
+ const options = base()
111
+ // {
112
+ // baseURL: 'https://api.example.com'
113
+ // }
114
+ ```
115
+
116
+ ##### `mock()`
117
+ Returns mock API configuration.
118
+
119
+ ```typescript
120
+ const options = mock()
121
+ // {
122
+ // baseURL: 'http://localhost:3000/api/mock'
123
+ // }
124
+ ```
125
+
126
+ ##### `file()`
127
+ Returns file upload configuration.
128
+
129
+ ```typescript
130
+ const options = file()
131
+ // {
132
+ // baseURL: 'https://api.example.com/uploads',
133
+ // headers: {
134
+ // Authorization: 'Bearer <token>'
135
+ // }
136
+ // }
137
+ ```
138
+
139
+ ---
140
+
141
+ ### useApp()
142
+
143
+ Application-level utilities (from @finema/core).
144
+
145
+ ```typescript
146
+ const app = useApp()
147
+ ```
148
+
149
+ #### Methods
150
+
151
+ ##### `definePage(options)`
152
+ Define page metadata.
153
+
154
+ ```typescript
155
+ app.definePage({
156
+ title: 'Projects',
157
+ breadcrumbs: [
158
+ { label: 'Home', to: '/' },
159
+ { label: 'Projects', to: '/projects' }
160
+ ]
161
+ })
162
+ ```
163
+
164
+ ---
165
+
166
+ ### useObjectLoader<T>()
167
+
168
+ Single object data loader (from @finema/core).
169
+
170
+ ```typescript
171
+ const loader = useObjectLoader<T>({
172
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
173
+ url: string,
174
+ getRequestOptions: () => AxiosRequestConfig
175
+ })
176
+ ```
177
+
178
+ #### Properties
179
+
180
+ | Property | Type | Description |
181
+ |----------|------|-------------|
182
+ | `data` | `Ref<T \| null>` | Loaded data |
183
+ | `status` | `Ref<IStatus>` | Request status |
184
+ | `status.value.isLoading` | `boolean` | Loading state |
185
+ | `status.value.isSuccess` | `boolean` | Success state |
186
+ | `status.value.isError` | `boolean` | Error state |
187
+ | `status.value.errorData` | `any` | Error details |
188
+
189
+ #### Methods
190
+
191
+ ##### `run(options?)`
192
+ Execute the request.
193
+
194
+ ```typescript
195
+ // GET request
196
+ await loader.run()
197
+
198
+ // POST/PUT/PATCH with data
199
+ await loader.run({
200
+ data: { name: 'Project Name' }
201
+ })
202
+
203
+ // With query params
204
+ await loader.run({
205
+ params: { id: '123' }
206
+ })
207
+ ```
208
+
209
+ ---
210
+
211
+ ### useListLoader<T>()
212
+
213
+ List/pagination data loader (from @finema/core).
214
+
215
+ ```typescript
216
+ const loader = useListLoader<T>({
217
+ method: 'GET',
218
+ url: string,
219
+ getRequestOptions: () => AxiosRequestConfig
220
+ })
221
+ ```
222
+
223
+ #### Properties
224
+
225
+ Same as `useObjectLoader`, but `data.value` has structure:
226
+
227
+ ```typescript
228
+ {
229
+ items: T[],
230
+ total: number,
231
+ page: number,
232
+ per_page: number
233
+ }
234
+ ```
235
+
236
+ #### Methods
237
+
238
+ ##### `run(options?)`
239
+
240
+ ```typescript
241
+ await loader.run({
242
+ params: {
243
+ page: 1,
244
+ per_page: 10,
245
+ q: 'search term',
246
+ status: 'active'
247
+ }
248
+ })
249
+ ```
250
+
251
+ ---
252
+
253
+ ### useForm()
254
+
255
+ Form handling with validation (from @finema/core).
256
+
257
+ ```typescript
258
+ import * as v from 'valibot'
259
+ import { toTypedSchema } from '@vee-validate/valibot'
260
+
261
+ const form = useForm({
262
+ validationSchema: toTypedSchema(
263
+ v.object({
264
+ name: v.pipe(v.string(), v.minLength(3)),
265
+ email: v.pipe(v.string(), v.email())
266
+ })
267
+ ),
268
+ initialValues: {
269
+ name: '',
270
+ email: ''
271
+ }
272
+ })
273
+ ```
274
+
275
+ #### Properties
276
+
277
+ | Property | Type | Description |
278
+ |----------|------|-------------|
279
+ | `values` | `Ref<FormValues>` | Current form values |
280
+ | `errors` | `Ref<FormErrors>` | Validation errors |
281
+ | `isSubmitting` | `Ref<boolean>` | Submission state |
282
+ | `isValid` | `Ref<boolean>` | Validation state |
283
+
284
+ #### Methods
285
+
286
+ ##### `handleSubmit(callback)`
287
+ Create submit handler.
288
+
289
+ ```typescript
290
+ const onSubmit = form.handleSubmit(async (values) => {
291
+ // values are validated
292
+ await saveData(values)
293
+ })
294
+ ```
295
+
296
+ ##### `setFieldValue(field, value)`
297
+ Set single field value.
298
+
299
+ ```typescript
300
+ form.setFieldValue('name', 'John Doe')
301
+ ```
302
+
303
+ ##### `setValues(values)`
304
+ Set multiple field values.
305
+
306
+ ```typescript
307
+ form.setValues({
308
+ name: 'John Doe',
309
+ email: 'john@example.com'
310
+ })
311
+ ```
312
+
313
+ ##### `resetForm()`
314
+ Reset form to initial values.
315
+
316
+ ```typescript
317
+ form.resetForm()
318
+ ```
319
+
320
+ ##### `setErrors(errors)`
321
+ Set validation errors manually.
322
+
323
+ ```typescript
324
+ form.setErrors({
325
+ name: 'Name is required',
326
+ email: 'Invalid email'
327
+ })
328
+ ```
329
+
330
+ ---
331
+
332
+ ## 🎨 Components
333
+
334
+ ### StatusBox
335
+
336
+ Loading/error/empty state handler.
337
+
338
+ ```vue
339
+ <StatusBox
340
+ :status="IStatus"
341
+ :data="T | null"
342
+ :skip="boolean"
343
+ >
344
+ <template #default="{ data: T }">
345
+ <!-- Content -->
346
+ </template>
347
+ </StatusBox>
348
+ ```
349
+
350
+ #### Props
351
+
352
+ | Prop | Type | Required | Description |
353
+ |------|------|----------|-------------|
354
+ | `status` | `IStatus` | Yes | Request status object |
355
+ | `data` | `T \| null` | Yes | Data to display |
356
+ | `skip` | `boolean` | No | Skip status checking |
357
+
358
+ #### Slots
359
+
360
+ | Slot | Props | Description |
361
+ |------|-------|-------------|
362
+ | `default` | `{ data: T }` | Content when data loaded |
363
+
364
+ ---
365
+
366
+ ### InfoItemList
367
+
368
+ Key-value information display.
369
+
370
+ ```vue
371
+ <InfoItemList
372
+ :items="InfoItem[]"
373
+ :vertical="boolean"
374
+ :inline="boolean"
375
+ :customClass="string"
376
+ />
377
+ ```
378
+
379
+ #### Props
380
+
381
+ | Prop | Type | Default | Description |
382
+ |------|------|---------|-------------|
383
+ | `items` | `InfoItem[]` | Required | Items to display |
384
+ | `vertical` | `boolean` | `false` | Stack vertically |
385
+ | `inline` | `boolean` | `false` | Inline label/value |
386
+ | `customClass` | `string` | `''` | Additional CSS classes |
387
+
388
+ #### InfoItem Interface
389
+
390
+ ```typescript
391
+ interface InfoItem {
392
+ label: string // Display label
393
+ value?: any // Display value
394
+ component?: Component // Custom component
395
+ props?: Record<string, any> // Component props
396
+ max?: number // Truncate length
397
+ key?: string // Slot key
398
+ type?: 'text' | 'number' | 'currency' | 'date' | 'date_time' | 'boolean'
399
+ customClass?: string // Item CSS classes
400
+ hide?: boolean // Conditional hide
401
+ }
402
+ ```
403
+
404
+ #### Slots
405
+
406
+ Dynamic slots based on item `key`:
407
+
408
+ ```vue
409
+ <template #[key]-item="{ value, item, row, label }">
410
+ <!-- Custom rendering -->
411
+ </template>
412
+ ```
413
+
414
+ ---
415
+
416
+ ### ButtonActionIcon
417
+
418
+ Icon-only action button.
419
+
420
+ ```vue
421
+ <ButtonActionIcon
422
+ :icon="string"
423
+ :color="'neutral' | 'error' | 'primary' | 'success' | 'warning' | 'info'"
424
+ :disabled="boolean"
425
+ :loading="boolean"
426
+ :to="string"
427
+ @click="handler"
428
+ />
429
+ ```
430
+
431
+ #### Props
432
+
433
+ | Prop | Type | Default | Description |
434
+ |------|------|---------|-------------|
435
+ | `icon` | `string` | Required | Icon name |
436
+ | `color` | `string` | `'neutral'` | Button color |
437
+ | `disabled` | `boolean` | `false` | Disabled state |
438
+ | `loading` | `boolean` | `false` | Loading state |
439
+ | `to` | `string` | - | Navigation target |
440
+
441
+ #### Events
442
+
443
+ | Event | Payload | Description |
444
+ |-------|---------|-------------|
445
+ | `click` | - | Click handler |
446
+
447
+ ---
448
+
449
+ ### ButtonBack
450
+
451
+ Back navigation button.
452
+
453
+ ```vue
454
+ <ButtonBack
455
+ :label="string"
456
+ :to="string"
457
+ @click="handler"
458
+ />
459
+ ```
460
+
461
+ #### Props
462
+
463
+ | Prop | Type | Default | Description |
464
+ |------|------|---------|-------------|
465
+ | `label` | `string` | `'กลับ'` | Button label |
466
+ | `to` | `string` | - | Navigation target |
467
+
468
+ #### Events
469
+
470
+ | Event | Payload | Description |
471
+ |-------|---------|-------------|
472
+ | `click` | - | Click handler |
473
+
474
+ ---
475
+
476
+ ### LayoutAdmin
477
+
478
+ Admin layout with sidebar.
479
+
480
+ ```vue
481
+ <LayoutAdmin
482
+ :label="string"
483
+ :items="NavigationMenuItem[]"
484
+ :isGroup="boolean"
485
+ >
486
+ <!-- Content -->
487
+ </LayoutAdmin>
488
+ ```
489
+
490
+ #### Props
491
+
492
+ | Prop | Type | Required | Description |
493
+ |------|------|----------|-------------|
494
+ | `label` | `string` | Yes | Layout title |
495
+ | `items` | `NavigationMenuItem[]` | Yes | Navigation items |
496
+ | `isGroup` | `boolean` | No | Group navigation |
497
+
498
+ #### NavigationMenuItem Interface
499
+
500
+ ```typescript
501
+ interface NavigationMenuItem {
502
+ label: string
503
+ icon?: string
504
+ to?: string
505
+ children?: NavigationMenuItem[]
506
+ badge?: string | number
507
+ disabled?: boolean
508
+ }
509
+ ```
510
+
511
+ ---
512
+
513
+ ### LayoutUser
514
+
515
+ User-facing layout.
516
+
517
+ ```vue
518
+ <LayoutUser>
519
+ <!-- Content -->
520
+ </LayoutUser>
521
+ ```
522
+
523
+ No props required.
524
+
525
+ ---
526
+
527
+ ## 📊 Types & Interfaces
528
+
529
+ ### IUser
530
+
531
+ ```typescript
532
+ interface IUser {
533
+ id: string
534
+ email: string
535
+ full_name: string
536
+ display_name: string
537
+ position: string
538
+ team: ITeam
539
+ team_code: string
540
+ avatar_url: string
541
+ company: string
542
+ access_level: IUserAccessLevel
543
+ is_active: boolean
544
+ joined_date: string
545
+ slack_id: string
546
+ created_at: string
547
+ updated_at: string
548
+ }
549
+ ```
550
+
551
+ ### ITeam
552
+
553
+ ```typescript
554
+ interface ITeam {
555
+ id: string
556
+ name: string
557
+ code: string
558
+ color: string
559
+ description?: string
560
+ working_start_at: string
561
+ working_end_at: string
562
+ users: IUser[] | null
563
+ created_at: string
564
+ updated_at: string
565
+ }
566
+ ```
567
+
568
+ ### IUserAccessLevel
569
+
570
+ ```typescript
571
+ interface IUserAccessLevel {
572
+ used_id: string
573
+ pmo: Permission.USER | Permission.ADMIN | Permission.SUPER
574
+ clockin: Permission.USER | Permission.ADMIN
575
+ timesheet: Permission.USER | Permission.ADMIN
576
+ setting: Permission.USER | Permission.SUPER
577
+ }
578
+ ```
579
+
580
+ ### IStatus
581
+
582
+ ```typescript
583
+ interface IStatus {
584
+ isLoading: boolean
585
+ isSuccess: boolean
586
+ isError: boolean
587
+ errorData?: {
588
+ code?: string
589
+ message?: string
590
+ [key: string]: any
591
+ }
592
+ }
593
+ ```
594
+
595
+ ---
596
+
597
+ ## 🔐 Enums
598
+
599
+ ### Permission
600
+
601
+ ```typescript
602
+ enum Permission {
603
+ USER = 'USER',
604
+ ADMIN = 'ADMIN',
605
+ SUPER = 'SUPER',
606
+ NONE = 'NONE'
607
+ }
608
+ ```
609
+
610
+ ### UserModule
611
+
612
+ ```typescript
613
+ enum UserModule {
614
+ CHECKIN = 'clockin',
615
+ PMO = 'pmo',
616
+ TIMESHEET = 'timesheet',
617
+ SETTING = 'setting'
618
+ }
619
+ ```
620
+
621
+ ---
622
+
623
+ ## 🛣️ Constants
624
+
625
+ ### routes
626
+
627
+ All application routes.
628
+
629
+ ```typescript
630
+ const routes = {
631
+ home: { label: 'หน้าแรก', to: '/' },
632
+ login: { label: 'เลือก', to: '/login' },
633
+ logout: { label: 'Log out', to: '/api/auth/logout' },
634
+
635
+ account: {
636
+ profile: { label: 'โปรไฟล์', to: '/account/profile', icon: 'mage:user' }
637
+ },
638
+
639
+ clockin: {
640
+ home: { label: 'Clock-In', icon: 'i-heroicons-outline:location-marker', to: '/clockin' }
641
+ },
642
+
643
+ timesheet: {
644
+ home: { label: 'Timesheet', icon: 'mage:dashboard', to: '/timesheet' }
645
+ },
646
+
647
+ pmo: {
648
+ project: {
649
+ projects: {
650
+ label: 'โครงการของฉัน',
651
+ to: '/pmo/projects',
652
+ icon: 'lucide:layers',
653
+ permissions: ['pmo:USER', 'pmo:ADMIN', 'pmo:SUPER']
654
+ }
655
+ }
656
+ },
657
+
658
+ admin: {
659
+ users: { label: 'จัดการผู้ใช้งาน', icon: 'hugeicons:user-circle-02', to: '/admin/users' }
660
+ }
661
+ } as const
662
+ ```
663
+
664
+ Usage:
665
+
666
+ ```typescript
667
+ navigateTo(routes.pmo.project.projects.to)
668
+ ```
669
+
670
+ ---
671
+
672
+ ## 🎯 Middleware
673
+
674
+ ### auth
675
+
676
+ Requires authentication.
677
+
678
+ ```typescript
679
+ definePageMeta({
680
+ middleware: 'auth'
681
+ })
682
+ ```
683
+
684
+ **Behavior:**
685
+ - Redirects to login if not authenticated
686
+ - Fetches user data if needed
687
+ - Checks if user is active
688
+ - Redirects to team selection if no team
689
+
690
+ ### permissions
691
+
692
+ Permission-based access control.
693
+
694
+ ```typescript
695
+ definePageMeta({
696
+ middleware: ['auth', 'permissions'],
697
+ accessGuard: {
698
+ permissions: ['pmo:ADMIN', 'pmo:SUPER'],
699
+ redirectTo: '/unauthorized' // Optional
700
+ }
701
+ })
702
+ ```
703
+
704
+ **Behavior:**
705
+ - Checks if user has any of the specified permissions
706
+ - Redirects to `redirectTo` or returns 403
707
+
708
+ ### guest
709
+
710
+ Guest-only routes.
711
+
712
+ ```typescript
713
+ definePageMeta({
714
+ middleware: 'guest'
715
+ })
716
+ ```
717
+
718
+ **Behavior:**
719
+ - Redirects authenticated users to home
720
+
721
+ ### common
722
+
723
+ Common pre-route logic.
724
+
725
+ ```typescript
726
+ definePageMeta({
727
+ middleware: 'common'
728
+ })
729
+ ```
730
+
731
+ **Behavior:**
732
+ - Fetches user data if authenticated and needed
733
+ - Runs on all routes
734
+
735
+ ---
736
+
737
+ ## 🎨 App Config
738
+
739
+ ```typescript
740
+ // app/app.config.ts
741
+ export default defineAppConfig({
742
+ core: {
743
+ is_thai_year: boolean,
744
+ is_thai_month: boolean,
745
+ date_format: string,
746
+ month_format: string,
747
+ date_time_format: string,
748
+ color: string,
749
+ limit_per_page: number,
750
+ locale: string
751
+ },
752
+ ui: {
753
+ // Nuxt UI customization
754
+ }
755
+ })
756
+ ```
757
+
758
+ Access in components:
759
+
760
+ ```typescript
761
+ const appConfig = useAppConfig()
762
+ console.log(appConfig.core.date_format)
763
+ ```
764
+
765
+ ---
766
+
767
+ ## 🔧 Runtime Config
768
+
769
+ ```typescript
770
+ const config = useRuntimeConfig()
771
+
772
+ config.public.baseAPI // API base URL
773
+ config.public.baseAPIMock // Mock API URL
774
+ ```
775
+
776
+ ---
777
+
778
+ **Last Updated:** 2025-11-07
779
+ **Version:** 0.2.50
780
+