@finema/core 1.4.201 → 1.4.203

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 (72) hide show
  1. package/README.md +60 -60
  2. package/dist/module.d.mts +1 -0
  3. package/dist/module.d.ts +1 -0
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +2 -1
  6. package/dist/runtime/components/Alert.vue +48 -48
  7. package/dist/runtime/components/Avatar.vue +27 -27
  8. package/dist/runtime/components/Badge.vue +11 -11
  9. package/dist/runtime/components/Breadcrumb.vue +44 -44
  10. package/dist/runtime/components/Button/Group.vue +37 -37
  11. package/dist/runtime/components/Button/index.vue +75 -75
  12. package/dist/runtime/components/Card.vue +38 -38
  13. package/dist/runtime/components/Core.vue +45 -45
  14. package/dist/runtime/components/Dialog/index.vue +108 -108
  15. package/dist/runtime/components/Dropdown/index.vue +70 -70
  16. package/dist/runtime/components/FlexDeck/Base.vue +152 -152
  17. package/dist/runtime/components/FlexDeck/index.vue +68 -68
  18. package/dist/runtime/components/Form/FieldWrapper.vue +23 -23
  19. package/dist/runtime/components/Form/Fields.vue +230 -230
  20. package/dist/runtime/components/Form/InputCheckbox/index.vue +28 -28
  21. package/dist/runtime/components/Form/InputDateTime/index.vue +61 -61
  22. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +83 -83
  23. package/dist/runtime/components/Form/InputNumber/index.vue +27 -27
  24. package/dist/runtime/components/Form/InputRadio/index.vue +27 -27
  25. package/dist/runtime/components/Form/InputSelect/index.vue +45 -45
  26. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +54 -54
  27. package/dist/runtime/components/Form/InputStatic/index.vue +16 -16
  28. package/dist/runtime/components/Form/InputTags/index.vue +141 -141
  29. package/dist/runtime/components/Form/InputText/index.vue +68 -68
  30. package/dist/runtime/components/Form/InputTextarea/index.vue +25 -25
  31. package/dist/runtime/components/Form/InputToggle/index.vue +27 -27
  32. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +206 -206
  33. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +342 -342
  34. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemUpload.vue +241 -241
  35. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemView.vue +89 -89
  36. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +170 -170
  37. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemUpload.vue +161 -161
  38. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemView.vue +64 -64
  39. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/index.vue +178 -178
  40. package/dist/runtime/components/Form/InputUploadFileClassic/index.vue +95 -95
  41. package/dist/runtime/components/Form/InputUploadFileClassicAuto/index.vue +151 -151
  42. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +219 -219
  43. package/dist/runtime/components/Form/InputWYSIWYG/UploadImageForm.vue +55 -55
  44. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +228 -228
  45. package/dist/runtime/components/Form/index.vue +6 -6
  46. package/dist/runtime/components/Icon.vue +23 -23
  47. package/dist/runtime/components/Image.vue +36 -36
  48. package/dist/runtime/components/Loader.vue +27 -27
  49. package/dist/runtime/components/Modal/index.vue +146 -146
  50. package/dist/runtime/components/QRCode.vue +22 -22
  51. package/dist/runtime/components/SimplePagination.vue +96 -96
  52. package/dist/runtime/components/Slideover/index.vue +110 -110
  53. package/dist/runtime/components/Table/Base.vue +153 -153
  54. package/dist/runtime/components/Table/ColumnDate.vue +16 -16
  55. package/dist/runtime/components/Table/ColumnDateTime.vue +18 -18
  56. package/dist/runtime/components/Table/ColumnImage.vue +15 -15
  57. package/dist/runtime/components/Table/ColumnNumber.vue +14 -14
  58. package/dist/runtime/components/Table/ColumnText.vue +29 -29
  59. package/dist/runtime/components/Table/Simple.vue +69 -69
  60. package/dist/runtime/components/Table/index.vue +65 -65
  61. package/dist/runtime/components/Tabs/index.vue +64 -64
  62. package/dist/runtime/components/TeleportSafe.vue +40 -40
  63. package/dist/runtime/core.config.d.ts +1 -0
  64. package/dist/runtime/core.config.mjs +2 -1
  65. package/dist/runtime/ui.config/notifications.d.ts +3 -0
  66. package/dist/runtime/ui.config/notifications.mjs +3 -0
  67. package/dist/runtime/utils/TimeHelper.mjs +15 -10
  68. package/dist/runtime/utils/TimeHelper.spec.mjs +11 -4
  69. package/dist/runtime/utils/TimeHelper.thai.spec.mjs +2 -1
  70. package/package.json +102 -101
  71. package/dist/runtime/components/Form/InputDateTime/index.vue~ +0 -61
  72. package/dist/runtime/ui.config/table.ts~ +0 -48
@@ -1,170 +1,170 @@
1
- <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <div class="space-y-3">
4
- <div
5
- ref="dropzoneRef"
6
- :class="[
7
- ui.base,
8
- {
9
- [ui.disabled]: isDisabled,
10
- [ui.background.default]: !isOverDropZone && !isDisabled,
11
- [ui.background.dragover]: isOverDropZone && !isDisabled,
12
- },
13
- ]"
14
- >
15
- <input
16
- ref="fileInputRef"
17
- type="file"
18
- class="hidden"
19
- :name="name"
20
- :accept="acceptFile"
21
- :disabled="isDisabled"
22
- multiple
23
- @change="handleChange"
24
- />
25
- <div :class="[ui.wrapper]">
26
- <div :class="[ui.placeholderWrapper]">
27
- <Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
28
- <div :class="[ui.labelWrapper]">
29
- <p class="text-primary cursor-pointer" @click="handleOpenFile">
30
- {{ selectFileLabel }}
31
- </p>
32
- <p>{{ selectFileSubLabel }}</p>
33
- </div>
34
- <p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
35
- </div>
36
- </div>
37
- </div>
38
- <ItemView
39
- v-for="(file, index) in value"
40
- :key="file.url"
41
- v-bind="$props"
42
- :ui="ui"
43
- :selected-file="file"
44
- @delete="handleDeleteFileView(index)"
45
- />
46
- <ItemUpload
47
- v-for="(file, index) in selectedFiles"
48
- :key="file.key"
49
- v-bind="$props"
50
- :ui="ui"
51
- :selected-file="file.file"
52
- @success="handleSuccess(index, $event)"
53
- @delete="handleDeleteFile(index)"
54
- @error="handleError(index, $event)"
55
- />
56
- </div>
57
- </FieldWrapper>
58
- </template>
59
-
60
- <script lang="ts" setup>
61
- import { useDropZone } from '@vueuse/core'
62
- import { type IUploadDropzoneAutoMultipleProps } from './types'
63
- import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
64
- import { useFieldHOC } from '#core/composables/useForm'
65
- import { _get, computed, ref, toRef, useUI, useUiConfig, StringHelper, ArrayHelper } from '#imports'
66
- import { uploadFileDropzone } from '#core/ui.config'
67
- import ItemView from './ItemView.vue'
68
- import ItemUpload from './ItemUpload.vue'
69
- import type { IFileValue } from '#core/components/Form/types'
70
-
71
- const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
72
- const props = withDefaults(defineProps<IUploadDropzoneAutoMultipleProps>(), {
73
- bodyKey: 'file',
74
- responseURL: 'url',
75
- responsePath: 'path',
76
- selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
77
- selectFileSubLabel: 'หรือ ลากและวางที่นี่',
78
- retryLabel: 'ลองอีกครั้ง',
79
- uploadingLabel: 'กำลังอัพโหลด...',
80
- uploadFailedLabel: 'อัพโหลดล้มเหลว, กรุณาลองอีกครั้ง',
81
- })
82
-
83
- const emits = defineEmits(['change', 'success', 'delete'])
84
- const selectedFiles = ref<Array<{ file: File; key: string }>>([])
85
- const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<IFileValue[]>(props)
86
-
87
- const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
88
-
89
- const fileInputRef = ref<HTMLInputElement | null>()
90
- const dropzoneRef = ref<HTMLDivElement>()
91
-
92
- const acceptFile = computed(() =>
93
- typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
94
- )
95
-
96
- const onDrop = (files: File[] | null) => {
97
- if (props.isDisabled || files?.length === 0 || !files) return
98
-
99
- for (const file of files) {
100
- selectedFiles.value = [
101
- {
102
- file,
103
- key: StringHelper.genString(),
104
- },
105
- ...selectedFiles.value,
106
- ]
107
-
108
- emits('change', value.value)
109
- }
110
- }
111
-
112
- const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
113
- onDrop,
114
- })
115
-
116
- const handleChange = (e: Event) => {
117
- if (props.isDisabled) return
118
-
119
- for (const file of (e.target as HTMLInputElement).files ?? []) {
120
- selectedFiles.value = [
121
- {
122
- file,
123
- key: StringHelper.genString(),
124
- },
125
- ...selectedFiles.value,
126
- ]
127
-
128
- emits('change', value.value)
129
- }
130
-
131
- fileInputRef.value?.value && (fileInputRef.value.value = '')
132
- }
133
-
134
- const handleOpenFile = () => {
135
- fileInputRef.value?.click()
136
- }
137
-
138
- const handleDeleteFile = (index: number) => {
139
- const updatedValue = [...selectedFiles.value]
140
-
141
- updatedValue.splice(index, 1)
142
- selectedFiles.value = updatedValue
143
- }
144
-
145
- const handleDeleteFileView = (index: number) => {
146
- const updatedValue = [...value.value]
147
-
148
- updatedValue.splice(index, 1)
149
- value.value = updatedValue
150
- emits('delete')
151
- }
152
-
153
- const handleError = (index: number, error: any) => {}
154
-
155
- const handleSuccess = (index: number, res: any) => {
156
- value.value = [
157
- {
158
- url: _get(res, props.responseURL),
159
- path: _get(res, props.responsePath),
160
- name: res.name,
161
- size: res.size,
162
- },
163
- ...ArrayHelper.toArray(value.value),
164
- ]
165
-
166
- selectedFiles.value.splice(index, 1)
167
- emits('change', value.value)
168
- emits('success', value.value)
169
- }
170
- </script>
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div class="space-y-3">
4
+ <div
5
+ ref="dropzoneRef"
6
+ :class="[
7
+ ui.base,
8
+ {
9
+ [ui.disabled]: isDisabled,
10
+ [ui.background.default]: !isOverDropZone && !isDisabled,
11
+ [ui.background.dragover]: isOverDropZone && !isDisabled,
12
+ },
13
+ ]"
14
+ >
15
+ <input
16
+ ref="fileInputRef"
17
+ type="file"
18
+ class="hidden"
19
+ :name="name"
20
+ :accept="acceptFile"
21
+ :disabled="isDisabled"
22
+ multiple
23
+ @change="handleChange"
24
+ />
25
+ <div :class="[ui.wrapper]">
26
+ <div :class="[ui.placeholderWrapper]">
27
+ <Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
28
+ <div :class="[ui.labelWrapper]">
29
+ <p class="text-primary cursor-pointer" @click="handleOpenFile">
30
+ {{ selectFileLabel }}
31
+ </p>
32
+ <p>{{ selectFileSubLabel }}</p>
33
+ </div>
34
+ <p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ <ItemView
39
+ v-for="(file, index) in value"
40
+ :key="file.url"
41
+ v-bind="$props"
42
+ :ui="ui"
43
+ :selected-file="file"
44
+ @delete="handleDeleteFileView(index)"
45
+ />
46
+ <ItemUpload
47
+ v-for="(file, index) in selectedFiles"
48
+ :key="file.key"
49
+ v-bind="$props"
50
+ :ui="ui"
51
+ :selected-file="file.file"
52
+ @success="handleSuccess(index, $event)"
53
+ @delete="handleDeleteFile(index)"
54
+ @error="handleError(index, $event)"
55
+ />
56
+ </div>
57
+ </FieldWrapper>
58
+ </template>
59
+
60
+ <script lang="ts" setup>
61
+ import { useDropZone } from '@vueuse/core'
62
+ import { type IUploadDropzoneAutoMultipleProps } from './types'
63
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
64
+ import { useFieldHOC } from '#core/composables/useForm'
65
+ import { _get, computed, ref, toRef, useUI, useUiConfig, StringHelper, ArrayHelper } from '#imports'
66
+ import { uploadFileDropzone } from '#core/ui.config'
67
+ import ItemView from './ItemView.vue'
68
+ import ItemUpload from './ItemUpload.vue'
69
+ import type { IFileValue } from '#core/components/Form/types'
70
+
71
+ const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
72
+ const props = withDefaults(defineProps<IUploadDropzoneAutoMultipleProps>(), {
73
+ bodyKey: 'file',
74
+ responseURL: 'url',
75
+ responsePath: 'path',
76
+ selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
77
+ selectFileSubLabel: 'หรือ ลากและวางที่นี่',
78
+ retryLabel: 'ลองอีกครั้ง',
79
+ uploadingLabel: 'กำลังอัพโหลด...',
80
+ uploadFailedLabel: 'อัพโหลดล้มเหลว, กรุณาลองอีกครั้ง',
81
+ })
82
+
83
+ const emits = defineEmits(['change', 'success', 'delete'])
84
+ const selectedFiles = ref<Array<{ file: File; key: string }>>([])
85
+ const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<IFileValue[]>(props)
86
+
87
+ const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
88
+
89
+ const fileInputRef = ref<HTMLInputElement | null>()
90
+ const dropzoneRef = ref<HTMLDivElement>()
91
+
92
+ const acceptFile = computed(() =>
93
+ typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
94
+ )
95
+
96
+ const onDrop = (files: File[] | null) => {
97
+ if (props.isDisabled || files?.length === 0 || !files) return
98
+
99
+ for (const file of files) {
100
+ selectedFiles.value = [
101
+ {
102
+ file,
103
+ key: StringHelper.genString(),
104
+ },
105
+ ...selectedFiles.value,
106
+ ]
107
+
108
+ emits('change', value.value)
109
+ }
110
+ }
111
+
112
+ const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
113
+ onDrop,
114
+ })
115
+
116
+ const handleChange = (e: Event) => {
117
+ if (props.isDisabled) return
118
+
119
+ for (const file of (e.target as HTMLInputElement).files ?? []) {
120
+ selectedFiles.value = [
121
+ {
122
+ file,
123
+ key: StringHelper.genString(),
124
+ },
125
+ ...selectedFiles.value,
126
+ ]
127
+
128
+ emits('change', value.value)
129
+ }
130
+
131
+ fileInputRef.value?.value && (fileInputRef.value.value = '')
132
+ }
133
+
134
+ const handleOpenFile = () => {
135
+ fileInputRef.value?.click()
136
+ }
137
+
138
+ const handleDeleteFile = (index: number) => {
139
+ const updatedValue = [...selectedFiles.value]
140
+
141
+ updatedValue.splice(index, 1)
142
+ selectedFiles.value = updatedValue
143
+ }
144
+
145
+ const handleDeleteFileView = (index: number) => {
146
+ const updatedValue = [...value.value]
147
+
148
+ updatedValue.splice(index, 1)
149
+ value.value = updatedValue
150
+ emits('delete')
151
+ }
152
+
153
+ const handleError = (index: number, error: any) => {}
154
+
155
+ const handleSuccess = (index: number, res: any) => {
156
+ value.value = [
157
+ {
158
+ url: _get(res, props.responseURL),
159
+ path: _get(res, props.responsePath),
160
+ name: res.name,
161
+ size: res.size,
162
+ },
163
+ ...ArrayHelper.toArray(value.value),
164
+ ]
165
+
166
+ selectedFiles.value.splice(index, 1)
167
+ emits('change', value.value)
168
+ emits('success', value.value)
169
+ }
170
+ </script>
@@ -1,161 +1,161 @@
1
- <template>
2
- <div :class="[ui.imageItem.wrapper]">
3
- <div v-if="isAddingBtn" class="w-full">
4
- <div :class="[ui.action.addingWrapper]" @click="$emit('add')">
5
- <Icon :name="ui.action.addingIcon" :class="[ui.action.addingBtnClass]" />
6
- <p :class="[ui.action.addingTextClass]">{{ uploadAddLabel }}</p>
7
- </div>
8
- </div>
9
-
10
- <!-- Loading State -->
11
- <div v-if="selectedFile && upload.status.value.isLoading" class="w-full">
12
- <div :class="[ui.imageItem.onLoading.wrapper]">
13
- <div :class="[ui.imageItem.onLoading.percentClass]">{{ percent }}%</div>
14
- <UProgress :value="percent" />
15
- </div>
16
- </div>
17
-
18
- <!-- Success State -->
19
- <div v-if="selectedFile && upload.status.value.isSuccess" class="w-full">
20
- <div :class="[ui.imageItem.onPreview.wrapper]">
21
- <img
22
- :class="[ui.imageItem.onPreview.previewImgClass]"
23
- :src="upload.data.value[responseURL!]"
24
- alt="img"
25
- />
26
- <div :class="[ui.imageItem.onPreview.previewActionWrapper]">
27
- <Icon
28
- title="ลบไฟล์"
29
- :name="ui.action.deleteIcon"
30
- :class="[ui.imageItem.onPreview.actionBtnClass]"
31
- @click="handleDeleteFile"
32
- />
33
- </div>
34
- </div>
35
-
36
- <div :class="[ui.imageItem.onPreview.previewTextWrapper]">
37
- <p :class="[ui.imageItem.onPreview.previewText]">{{ selectedFile.name }}</p>
38
- </div>
39
- </div>
40
-
41
- <!-- Failed State -->
42
- <div v-if="selectedFile && upload.status.value.isError" class="w-full">
43
- <div :class="[ui.imageItem.onFailed.wrapper]">
44
- <img
45
- :class="[ui.imageItem.onFailed.failedImgClass]"
46
- :src="generateURL(selectedFile)"
47
- alt="img"
48
- />
49
- <div :class="[ui.imageItem.onFailed.failedActionWrapper]">
50
- <Icon
51
- title="ลบไฟล์"
52
- :name="ui.action.deleteIcon"
53
- :class="[ui.imageItem.onFailed.actionBtnClass]"
54
- @click="handleDeleteFile"
55
- />
56
- </div>
57
- </div>
58
- </div>
59
- </div>
60
- </template>
61
-
62
- <script lang="ts" setup>
63
- import {
64
- checkFileType,
65
- checkMaxSize,
66
- generateURL,
67
- useFileAllocate,
68
- useFileProgress,
69
- } from '#core/helpers/componentHelper'
70
- import { type uploadFileDropzoneImage } from '#core/ui.config'
71
- import type { IUploadDropzoneImageAutoMultipleProps } from '#core/components/Form/InputUploadDropzoneImageAutoMultiple/types'
72
- import { type IUploadRequest, onMounted, ref, toRef, useUploadLoader, useWatchTrue } from '#imports'
73
- import i18next from 'i18next'
74
-
75
- const emits = defineEmits(['success', 'error', 'delete', 'add'])
76
- const props = defineProps<
77
- {
78
- ui: typeof uploadFileDropzoneImage
79
- selectedFile?: File
80
- isAddingBtn?: boolean
81
- } & IUploadDropzoneImageAutoMultipleProps
82
- >()
83
-
84
- const request: IUploadRequest = {
85
- pathURL: props.uploadPathURL,
86
- requestOptions: props.requestOptions,
87
- }
88
-
89
- const upload = useUploadLoader(request)
90
- const errMsg = ref<string>('')
91
-
92
- const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
93
- const fileAllocate = useFileAllocate(toRef(props.selectedFile), props)
94
-
95
- const handleDeleteFile = () => {
96
- emits('delete')
97
- }
98
-
99
- const handleCheckFileCondition = (file: File | undefined): boolean => {
100
- if (!file) return false
101
-
102
- const fileType = checkFileType(file, fileAllocate.acceptFile.value ?? '')
103
-
104
- if (!fileType) {
105
- errMsg.value = i18next.t('custom:invalid_file_type')
106
-
107
- return false
108
- }
109
-
110
- const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
111
-
112
- if (!maxSize) {
113
- if (fileAllocate.isAcceptFileUseMb.value) {
114
- errMsg.value = i18next.t('custom:invalid_file_size_mb', {
115
- size: fileAllocate.acceptFileSizeMb.value,
116
- })
117
- } else {
118
- errMsg.value = i18next.t('custom:invalid_file_size_kb', {
119
- size: fileAllocate.acceptFileSizeKb.value,
120
- })
121
- }
122
-
123
- return false
124
- }
125
-
126
- errMsg.value = ''
127
-
128
- return true
129
- }
130
-
131
- onMounted(() => {
132
- if (props.isAddingBtn) return
133
-
134
- const result = handleCheckFileCondition(props.selectedFile)
135
-
136
- if (result) {
137
- const formData = new FormData()
138
-
139
- formData.append(props.bodyKey!, props.selectedFile!)
140
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
141
- }
142
- })
143
-
144
- useWatchTrue(
145
- () => upload.status.value.isSuccess,
146
- () => {
147
- emits('success', {
148
- ...upload.data.value,
149
- name: props.selectedFile?.name,
150
- size: +(props.selectedFile?.size || 0),
151
- })
152
- }
153
- )
154
-
155
- useWatchTrue(
156
- () => upload.status.value.isError,
157
- () => {
158
- emits('error', upload.status.value.errorData)
159
- }
160
- )
161
- </script>
1
+ <template>
2
+ <div :class="[ui.imageItem.wrapper]">
3
+ <div v-if="isAddingBtn" class="w-full">
4
+ <div :class="[ui.action.addingWrapper]" @click="$emit('add')">
5
+ <Icon :name="ui.action.addingIcon" :class="[ui.action.addingBtnClass]" />
6
+ <p :class="[ui.action.addingTextClass]">{{ uploadAddLabel }}</p>
7
+ </div>
8
+ </div>
9
+
10
+ <!-- Loading State -->
11
+ <div v-if="selectedFile && upload.status.value.isLoading" class="w-full">
12
+ <div :class="[ui.imageItem.onLoading.wrapper]">
13
+ <div :class="[ui.imageItem.onLoading.percentClass]">{{ percent }}%</div>
14
+ <UProgress :value="percent" />
15
+ </div>
16
+ </div>
17
+
18
+ <!-- Success State -->
19
+ <div v-if="selectedFile && upload.status.value.isSuccess" class="w-full">
20
+ <div :class="[ui.imageItem.onPreview.wrapper]">
21
+ <img
22
+ :class="[ui.imageItem.onPreview.previewImgClass]"
23
+ :src="upload.data.value[responseURL!]"
24
+ alt="img"
25
+ />
26
+ <div :class="[ui.imageItem.onPreview.previewActionWrapper]">
27
+ <Icon
28
+ title="ลบไฟล์"
29
+ :name="ui.action.deleteIcon"
30
+ :class="[ui.imageItem.onPreview.actionBtnClass]"
31
+ @click="handleDeleteFile"
32
+ />
33
+ </div>
34
+ </div>
35
+
36
+ <div :class="[ui.imageItem.onPreview.previewTextWrapper]">
37
+ <p :class="[ui.imageItem.onPreview.previewText]">{{ selectedFile.name }}</p>
38
+ </div>
39
+ </div>
40
+
41
+ <!-- Failed State -->
42
+ <div v-if="selectedFile && upload.status.value.isError" class="w-full">
43
+ <div :class="[ui.imageItem.onFailed.wrapper]">
44
+ <img
45
+ :class="[ui.imageItem.onFailed.failedImgClass]"
46
+ :src="generateURL(selectedFile)"
47
+ alt="img"
48
+ />
49
+ <div :class="[ui.imageItem.onFailed.failedActionWrapper]">
50
+ <Icon
51
+ title="ลบไฟล์"
52
+ :name="ui.action.deleteIcon"
53
+ :class="[ui.imageItem.onFailed.actionBtnClass]"
54
+ @click="handleDeleteFile"
55
+ />
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </template>
61
+
62
+ <script lang="ts" setup>
63
+ import {
64
+ checkFileType,
65
+ checkMaxSize,
66
+ generateURL,
67
+ useFileAllocate,
68
+ useFileProgress,
69
+ } from '#core/helpers/componentHelper'
70
+ import { type uploadFileDropzoneImage } from '#core/ui.config'
71
+ import type { IUploadDropzoneImageAutoMultipleProps } from '#core/components/Form/InputUploadDropzoneImageAutoMultiple/types'
72
+ import { type IUploadRequest, onMounted, ref, toRef, useUploadLoader, useWatchTrue } from '#imports'
73
+ import i18next from 'i18next'
74
+
75
+ const emits = defineEmits(['success', 'error', 'delete', 'add'])
76
+ const props = defineProps<
77
+ {
78
+ ui: typeof uploadFileDropzoneImage
79
+ selectedFile?: File
80
+ isAddingBtn?: boolean
81
+ } & IUploadDropzoneImageAutoMultipleProps
82
+ >()
83
+
84
+ const request: IUploadRequest = {
85
+ pathURL: props.uploadPathURL,
86
+ requestOptions: props.requestOptions,
87
+ }
88
+
89
+ const upload = useUploadLoader(request)
90
+ const errMsg = ref<string>('')
91
+
92
+ const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
93
+ const fileAllocate = useFileAllocate(toRef(props.selectedFile), props)
94
+
95
+ const handleDeleteFile = () => {
96
+ emits('delete')
97
+ }
98
+
99
+ const handleCheckFileCondition = (file: File | undefined): boolean => {
100
+ if (!file) return false
101
+
102
+ const fileType = checkFileType(file, fileAllocate.acceptFile.value ?? '')
103
+
104
+ if (!fileType) {
105
+ errMsg.value = i18next.t('custom:invalid_file_type')
106
+
107
+ return false
108
+ }
109
+
110
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
111
+
112
+ if (!maxSize) {
113
+ if (fileAllocate.isAcceptFileUseMb.value) {
114
+ errMsg.value = i18next.t('custom:invalid_file_size_mb', {
115
+ size: fileAllocate.acceptFileSizeMb.value,
116
+ })
117
+ } else {
118
+ errMsg.value = i18next.t('custom:invalid_file_size_kb', {
119
+ size: fileAllocate.acceptFileSizeKb.value,
120
+ })
121
+ }
122
+
123
+ return false
124
+ }
125
+
126
+ errMsg.value = ''
127
+
128
+ return true
129
+ }
130
+
131
+ onMounted(() => {
132
+ if (props.isAddingBtn) return
133
+
134
+ const result = handleCheckFileCondition(props.selectedFile)
135
+
136
+ if (result) {
137
+ const formData = new FormData()
138
+
139
+ formData.append(props.bodyKey!, props.selectedFile!)
140
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
141
+ }
142
+ })
143
+
144
+ useWatchTrue(
145
+ () => upload.status.value.isSuccess,
146
+ () => {
147
+ emits('success', {
148
+ ...upload.data.value,
149
+ name: props.selectedFile?.name,
150
+ size: +(props.selectedFile?.size || 0),
151
+ })
152
+ }
153
+ )
154
+
155
+ useWatchTrue(
156
+ () => upload.status.value.isError,
157
+ () => {
158
+ emits('error', upload.status.value.errorData)
159
+ }
160
+ )
161
+ </script>