@finema/finework-layer 1.0.81 → 1.0.83
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 +4 -0
- package/app/app.vue +1 -0
- package/app/components/IssueLog/BadgePriority.vue +16 -0
- package/app/components/IssueLog/BadgeStatus.vue +16 -0
- package/app/components/IssueLog/BadgeType.vue +16 -0
- package/app/components/IssueLog/FormIssue.vue +191 -0
- package/app/components/IssueLog/index.vue +25 -0
- package/app/components/PortalApp.vue +1 -1
- package/app/constants/issue-log/priority.ts +35 -0
- package/app/constants/issue-log/routes.ts +7 -0
- package/app/constants/issue-log/status.ts +31 -0
- package/app/constants/issue-log/type.ts +31 -0
- package/app/constants/routes.ts +7 -0
- package/app/loaders/issue-log/issue-log.ts +28 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.83](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/1.0.82...1.0.83) (2026-06-24)
|
|
4
|
+
|
|
5
|
+
## [1.0.82](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/1.0.81...1.0.82) (2026-06-24)
|
|
6
|
+
|
|
3
7
|
## [1.0.81](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/1.0.80...1.0.81) (2026-06-23)
|
|
4
8
|
|
|
5
9
|
### Features
|
package/app/app.vue
CHANGED
|
@@ -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,25 @@
|
|
|
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
|
+
<FormIssue
|
|
16
|
+
:is-open="isOpen"
|
|
17
|
+
@update="(v) => isOpen = v"
|
|
18
|
+
/>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts" setup>
|
|
22
|
+
import FormIssue from './FormIssue.vue'
|
|
23
|
+
|
|
24
|
+
const isOpen = ref(false)
|
|
25
|
+
</script>
|
|
@@ -380,7 +380,7 @@ const managementApps = computed(() => [
|
|
|
380
380
|
},
|
|
381
381
|
]
|
|
382
382
|
: []),
|
|
383
|
-
...(auth.hasPermission(UserModule.SETTING, Permission.
|
|
383
|
+
...(auth.hasPermission(UserModule.SETTING, Permission.ADMIN, Permission.SUPER)
|
|
384
384
|
? [
|
|
385
385
|
{
|
|
386
386
|
name: 'report',
|
|
@@ -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,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
|
+
}))
|
package/app/constants/routes.ts
CHANGED
|
@@ -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
|
+
}
|