@finema/finework-layer 1.0.82 → 1.0.84

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.84](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/1.0.83...1.0.84) (2026-06-24)
4
+
5
+ ## [1.0.83](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/1.0.82...1.0.83) (2026-06-24)
6
+
3
7
  ## [1.0.82](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/1.0.81...1.0.82) (2026-06-24)
4
8
 
5
9
  ## [1.0.81](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/1.0.80...1.0.81) (2026-06-23)
package/app/app.vue CHANGED
@@ -6,5 +6,6 @@
6
6
  }"
7
7
  >
8
8
  <NuxtPage />
9
+ <IssueLog />
9
10
  </App>
10
11
  </template>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <Badge
3
+ variant="outline"
4
+ :leading-icon="IssueLogPriorityIcon[props.value]"
5
+ :color="IssueLogPriorityColor[props.value]"
6
+ :label="IssueLogPriorityLabel[props.value]"
7
+ />
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ import { IssueLogPriorityColor, IssueLogPriorityIcon, IssueLogPriorityLabel, type IssueLogPriority } from '#layer/app/constants/issue-log/priority'
12
+
13
+ const props = defineProps<{
14
+ value: IssueLogPriority
15
+ }>()
16
+ </script>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <Badge
3
+ variant="outline"
4
+ leading-icon="ci:dot-04-l"
5
+ :color="IssueLogStatusColor[props.value]"
6
+ :label="IssueLogStatusLabel[props.value]"
7
+ />
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ import { IssueLogStatusColor, IssueLogStatusLabel, type IssueLogStatus } from '#layer/app/constants/issue-log/status'
12
+
13
+ const props = defineProps<{
14
+ value: IssueLogStatus
15
+ }>()
16
+ </script>
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <Badge
3
+ variant="outline"
4
+ :leading-icon="IssueLogTypeIcon[props.value]"
5
+ :color="IssueLogTypeColor[props.value]"
6
+ :label="IssueLogTypeLabel[props.value]"
7
+ />
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ import { IssueLogTypeColor, IssueLogTypeIcon, IssueLogTypeLabel, type IssueLogType } from '#layer/app/constants/issue-log/type'
12
+
13
+ const props = defineProps<{
14
+ value: IssueLogType
15
+ }>()
16
+ </script>
@@ -0,0 +1,191 @@
1
+ <template>
2
+ <Slideover
3
+ :open="isOpen"
4
+ :portal="true"
5
+ :modal="true"
6
+ :ui="{ content: 'w-full max-w-[480px]' }"
7
+ side="right"
8
+ @update:open="(val) => emit('update', val)"
9
+ >
10
+ <template #content>
11
+ <div class="flex h-full flex-col">
12
+ <!-- Header -->
13
+ <div class="flex items-center justify-between border-b border-gray-200 px-5 py-4">
14
+ <div class="flex items-center gap-3">
15
+ <div class="bg-primary-100 flex size-9 items-center justify-center rounded-lg">
16
+ <Icon
17
+ name="ph:bug-beetle"
18
+ class="text-primary-600 size-5"
19
+ />
20
+ </div>
21
+ <div>
22
+ <h2 class="text-base font-semibold text-gray-900">
23
+ แจ้งปัญหา / ขอ Feature
24
+ </h2>
25
+ <p class="text-xs text-gray-500">
26
+ Issue Log
27
+ </p>
28
+ </div>
29
+ </div>
30
+ <div class="flex items-center gap-1">
31
+ <NuxtLink
32
+ :to="routes.issueLog.issues.to"
33
+ class="flex size-8 cursor-pointer items-center justify-center rounded-lg text-gray-400 transition hover:bg-gray-100 hover:text-gray-600"
34
+ title="ดูรายการทั้งหมด"
35
+ @click="emit('update', false)"
36
+ >
37
+ <Icon
38
+ name="ph:arrow-square-out"
39
+ class="size-5"
40
+ />
41
+ </NuxtLink>
42
+ <button
43
+ class="flex size-8 cursor-pointer items-center justify-center rounded-lg text-gray-400 transition hover:bg-gray-100 hover:text-gray-600"
44
+ type="button"
45
+ @click="emit('update', false)"
46
+ >
47
+ <Icon
48
+ name="ph:x"
49
+ class="size-5"
50
+ />
51
+ </button>
52
+ </div>
53
+ </div>
54
+
55
+ <!-- Form body -->
56
+ <div class="flex-1 overflow-y-auto px-5 py-4">
57
+ <FormFields
58
+ :form="form"
59
+ :options="formFields"
60
+ class="grid grid-cols-2 gap-4 space-y-0"
61
+ />
62
+ </div>
63
+
64
+ <!-- Footer -->
65
+ <div class="flex gap-3 border-t border-gray-100 p-4">
66
+ <Button
67
+ variant="outline"
68
+ color="neutral"
69
+ class="flex-1 justify-center"
70
+ @click="emit('update', false)"
71
+ >
72
+ ยกเลิก
73
+ </Button>
74
+ <Button
75
+ class="flex-1 justify-center"
76
+ :loading="addLoader.status.value.isLoading"
77
+ :disabled="!form.meta.value.dirty || addLoader.status.value.isLoading"
78
+ @click="onSubmit"
79
+ >
80
+ ส่งรายงาน
81
+ </Button>
82
+ </div>
83
+ </div>
84
+ </template>
85
+ </Slideover>
86
+ </template>
87
+
88
+ <script lang="ts" setup>
89
+ import { IssueLogTypeOptions } from '#layer/app/constants/issue-log/type'
90
+ import { useIssueLogAddLoader } from '#layer/app/loaders/issue-log/issue-log'
91
+
92
+ const emit = defineEmits<{
93
+ (e: 'update', value: boolean): void
94
+ }>()
95
+
96
+ defineProps<{
97
+ isOpen: boolean
98
+ }>()
99
+
100
+ const addLoader = useIssueLogAddLoader()
101
+ const noti = useNotification()
102
+
103
+ const form = useForm({
104
+ validationSchema: toTypedSchema(
105
+ v.object({
106
+ title: v.pipe(v.string(), v.minLength(1, 'กรุณาระบุหัวข้อ')),
107
+ description: v.pipe(v.string(), v.minLength(1, 'กรุณาระบุรายละเอียด')),
108
+ type: v.pipe(v.string(), v.minLength(1, 'กรุณาเลือกประเภท')),
109
+ attachments: v.optional(v.array(v.any()), []),
110
+ }),
111
+ ),
112
+ })
113
+
114
+ const formFields = createFormFields(() => [
115
+ {
116
+ type: INPUT_TYPES.TEXT,
117
+ class: 'col-span-2',
118
+ props: {
119
+ form,
120
+ name: 'title',
121
+ label: 'หัวข้อ',
122
+ placeholder: 'อธิบายปัญหาหรือ feature ที่ต้องการสั้นๆ',
123
+ required: true,
124
+ },
125
+ },
126
+ {
127
+ type: INPUT_TYPES.SELECT,
128
+ class: 'col-span-2',
129
+ props: {
130
+ form,
131
+ name: 'type',
132
+ label: 'ประเภท',
133
+ placeholder: 'เลือกประเภท',
134
+ options: IssueLogTypeOptions,
135
+ required: true,
136
+ },
137
+ },
138
+ {
139
+ type: INPUT_TYPES.TEXTAREA,
140
+ class: 'col-span-2',
141
+ props: {
142
+ form,
143
+ name: 'description',
144
+ label: 'รายละเอียด',
145
+ placeholder: 'อธิบายปัญหาหรือ feature ที่ต้องการอย่างละเอียด เช่น ขั้นตอนที่ทำให้เกิดปัญหา, ผลลัพธ์ที่ต้องการ',
146
+ rows: 4,
147
+ required: true,
148
+ },
149
+ },
150
+ {
151
+ type: INPUT_TYPES.UPLOAD_DROPZONE_AUTO,
152
+ class: 'col-span-2',
153
+ props: {
154
+ form,
155
+ name: 'attachments',
156
+ label: 'แนบรูปภาพ (ถ้ามี)',
157
+ placeholder: 'PNG, JPG, GIF, WEBP (สูงสุด 5MB)',
158
+ requestOptions: useRequestOptions().file(FileAppType.COMMON),
159
+ accept: '.jpg,.jpeg,.png,.gif,.webp',
160
+ maxSize: 5120,
161
+ },
162
+ },
163
+ ])
164
+
165
+ const onSubmit = form.handleSubmit((values) => {
166
+ addLoader.run({
167
+ data: values,
168
+ })
169
+ })
170
+
171
+ useWatchTrue(
172
+ () => addLoader.status.value.isSuccess,
173
+ () => {
174
+ emit('update', false)
175
+ form.resetForm()
176
+ noti.success({
177
+ title: 'ส่งรายงานสำเร็จ ขอบคุณ!',
178
+ })
179
+ },
180
+ )
181
+
182
+ useWatchTrue(
183
+ () => addLoader.status.value.isError,
184
+ () => {
185
+ noti.error({
186
+ title: 'ส่งรายงานไม่สำเร็จ',
187
+ description: StringHelper.getError(addLoader.status.value.errorData, 'กรุณาลองใหม่ภายหลัง'),
188
+ })
189
+ },
190
+ )
191
+ </script>
@@ -0,0 +1,23 @@
1
+ <template>
2
+ <div class="fixed right-6 bottom-6 z-[200]">
3
+ <button
4
+ class="group bg-primary-600 ring-primary-200 flex size-14 cursor-pointer items-center justify-center rounded-full shadow-lg ring-2 transition-all duration-200 hover:bg-primary-700 hover:scale-105 hover:shadow-xl"
5
+ title="แจ้งปัญหา / ขอ Feature"
6
+ @click="isOpen = true"
7
+ >
8
+ <Icon
9
+ name="ph:bug-beetle"
10
+ class="size-7 text-white transition-transform duration-200 group-hover:rotate-12"
11
+ />
12
+ </button>
13
+ </div>
14
+
15
+ <IssueLogFormIssue
16
+ :is-open="isOpen"
17
+ @update="(v) => isOpen = v"
18
+ />
19
+ </template>
20
+
21
+ <script lang="ts" setup>
22
+ const isOpen = ref(false)
23
+ </script>
@@ -0,0 +1,35 @@
1
+ export enum IssueLogPriority {
2
+ CRITICAL = 'CRITICAL',
3
+ HIGH = 'HIGH',
4
+ MEDIUM = 'MEDIUM',
5
+ LOW = 'LOW',
6
+ }
7
+
8
+ export const IssueLogPriorityLabel: Record<IssueLogPriority, string> = {
9
+ [IssueLogPriority.CRITICAL]: 'Critical',
10
+ [IssueLogPriority.HIGH]: 'High',
11
+ [IssueLogPriority.MEDIUM]: 'Medium',
12
+ [IssueLogPriority.LOW]: 'Low',
13
+ }
14
+
15
+ export const IssueLogPriorityColor: Record<IssueLogPriority, 'info' | 'neutral' | 'success' | 'warning' | 'error'> = {
16
+ [IssueLogPriority.CRITICAL]: 'error',
17
+ [IssueLogPriority.HIGH]: 'warning',
18
+ [IssueLogPriority.MEDIUM]: 'info',
19
+ [IssueLogPriority.LOW]: 'neutral',
20
+ }
21
+
22
+ export const IssueLogPriorityIcon: Record<IssueLogPriority, string> = {
23
+ [IssueLogPriority.CRITICAL]: 'ph:warning-circle-fill',
24
+ [IssueLogPriority.HIGH]: 'ph:arrow-up',
25
+ [IssueLogPriority.MEDIUM]: 'ph:minus',
26
+ [IssueLogPriority.LOW]: 'ph:arrow-down',
27
+ }
28
+
29
+ export const IssueLogPriorityOptions = Object.entries(IssueLogPriorityLabel).map(([value, label]) => ({
30
+ value,
31
+ label,
32
+ chip: {
33
+ color: IssueLogPriorityColor[value as IssueLogPriority],
34
+ },
35
+ }))
@@ -0,0 +1,7 @@
1
+ export const issueLogRoutes = {
2
+ issues: {
3
+ label: 'Issue Log',
4
+ to: '/issue-logs',
5
+ icon: 'ph:bug-beetle',
6
+ },
7
+ } as const
@@ -0,0 +1,31 @@
1
+ export enum IssueLogStatus {
2
+ OPEN = 'OPEN',
3
+ IN_PROGRESS = 'IN_PROGRESS',
4
+ RESOLVED = 'RESOLVED',
5
+ CLOSED = 'CLOSED',
6
+ DUPLICATE = 'DUPLICATE',
7
+ }
8
+
9
+ export const IssueLogStatusLabel: Record<IssueLogStatus, string> = {
10
+ [IssueLogStatus.OPEN]: 'เปิด',
11
+ [IssueLogStatus.IN_PROGRESS]: 'กำลังดำเนินการ',
12
+ [IssueLogStatus.RESOLVED]: 'แก้ไขแล้ว',
13
+ [IssueLogStatus.CLOSED]: 'ปิด',
14
+ [IssueLogStatus.DUPLICATE]: 'ซ้ำกับที่แจ้งแล้ว',
15
+ }
16
+
17
+ export const IssueLogStatusColor: Record<IssueLogStatus, 'info' | 'neutral' | 'success' | 'warning' | 'error'> = {
18
+ [IssueLogStatus.OPEN]: 'info',
19
+ [IssueLogStatus.IN_PROGRESS]: 'warning',
20
+ [IssueLogStatus.RESOLVED]: 'success',
21
+ [IssueLogStatus.CLOSED]: 'error',
22
+ [IssueLogStatus.DUPLICATE]: 'error',
23
+ }
24
+
25
+ export const IssueLogStatusOptions = Object.entries(IssueLogStatusLabel).map(([value, label]) => ({
26
+ value,
27
+ label,
28
+ chip: {
29
+ color: IssueLogStatusColor[value as IssueLogStatus],
30
+ },
31
+ }))
@@ -0,0 +1,31 @@
1
+ export enum IssueLogType {
2
+ BUG = 'BUG',
3
+ FEATURE_REQUEST = 'FEATURE_REQUEST',
4
+ IMPROVEMENT = 'IMPROVEMENT',
5
+ }
6
+
7
+ export const IssueLogTypeLabel: Record<IssueLogType, string> = {
8
+ [IssueLogType.BUG]: 'Report Bug',
9
+ [IssueLogType.FEATURE_REQUEST]: 'Request Feature',
10
+ [IssueLogType.IMPROVEMENT]: 'Improve',
11
+ }
12
+
13
+ export const IssueLogTypeColor: Record<IssueLogType, 'info' | 'neutral' | 'success' | 'warning' | 'error'> = {
14
+ [IssueLogType.BUG]: 'error',
15
+ [IssueLogType.FEATURE_REQUEST]: 'info',
16
+ [IssueLogType.IMPROVEMENT]: 'warning',
17
+ }
18
+
19
+ export const IssueLogTypeIcon: Record<IssueLogType, string> = {
20
+ [IssueLogType.BUG]: 'ph:bug',
21
+ [IssueLogType.FEATURE_REQUEST]: 'ph:sparkle',
22
+ [IssueLogType.IMPROVEMENT]: 'ph:wrench',
23
+ }
24
+
25
+ export const IssueLogTypeOptions = Object.entries(IssueLogTypeLabel).map(([value, label]) => ({
26
+ value,
27
+ label,
28
+ chip: {
29
+ color: IssueLogTypeColor[value as IssueLogType],
30
+ },
31
+ }))
@@ -239,6 +239,13 @@ export const routes = {
239
239
  permissions: ['setting:USER', 'setting:ADMIN', 'setting:SUPER'],
240
240
  },
241
241
  },
242
+ issueLog: {
243
+ issues: {
244
+ label: 'Issue Log',
245
+ to: '/issue-logs',
246
+ icon: 'ph:bug-beetle',
247
+ },
248
+ },
242
249
 
243
250
  } as const
244
251
 
@@ -0,0 +1,28 @@
1
+ export const useIssueLogPageLoader = () => {
2
+ const options = useRequestOptions()
3
+
4
+ return usePageLoader({
5
+ baseURL: `/issue-logs`,
6
+ getBaseRequestOptions: options.auth,
7
+ })
8
+ }
9
+
10
+ export const useIssueLogAddLoader = () => {
11
+ const options = useRequestOptions()
12
+
13
+ return useObjectLoader({
14
+ method: 'POST',
15
+ url: `/issue-logs`,
16
+ getRequestOptions: options.auth,
17
+ })
18
+ }
19
+
20
+ export const useIssueLogDeleteLoader = (id: string) => {
21
+ const options = useRequestOptions()
22
+
23
+ return useObjectLoader({
24
+ method: 'DELETE',
25
+ url: `/issue-logs/${id}`,
26
+ getRequestOptions: options.auth,
27
+ })
28
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@finema/finework-layer",
3
3
  "type": "module",
4
- "version": "1.0.82",
4
+ "version": "1.0.84",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground -o",