@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.
- package/.playground/app/pages/layout-admin/test/[id]/index.vue +286 -0
- package/.playground/app/pages/layout-admin.vue +2 -2
- package/@finema finework-layer - LLM Documentation Guide.md +566 -0
- package/API_REFERENCE.md +780 -0
- package/ARCHITECTURE.md +592 -0
- package/CHANGELOG.md +6 -0
- package/COMPONENT_EXAMPLES.md +893 -0
- package/DOCUMENTATION_INDEX.md +354 -0
- package/EXAMPLES.md +597 -0
- package/QUICK_START.md +678 -0
- package/README.md +199 -33
- package/TROUBLESHOOTING.md +646 -0
- package/app/components/Button/Back.vue +1 -1
- package/app/components/Layout/Admin/Sidebar.vue +10 -3
- package/app/components/Layout/Admin/index.vue +9 -13
- package/package.json +1 -1
package/API_REFERENCE.md
ADDED
|
@@ -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
|
+
|