@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,342 +1,342 @@
1
- <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <div
4
- ref="dropzoneRef"
5
- :class="[
6
- ui.base,
7
- {
8
- [ui.disabled]: isDisabled,
9
- [ui.background.default]: !isOverDropZone && !isDisabled,
10
- [ui.background.dragover]: isOverDropZone && (!isDisabled || !isReadonly),
11
- [ui.failed]: upload.status.value.isError,
12
- },
13
- ]"
14
- >
15
- <input
16
- ref="fileInputRef"
17
- type="file"
18
- class="hidden"
19
- :name="name"
20
- :accept="fileAllocate.acceptFile.value"
21
- :disabled="isDisabled || isReadonly"
22
- @change="handleChange"
23
- />
24
- <div :class="[ui.wrapper]">
25
- <div v-if="!selectedFile && !value" :class="[ui.placeholderWrapper]">
26
- <Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
27
- <div :class="[ui.labelWrapper]">
28
- <p class="text-primary cursor-pointer" @click="handleOpenFile">
29
- {{ selectFileLabel }}
30
- </p>
31
- <p>{{ selectFileSubLabel }}</p>
32
- </div>
33
- <p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
34
- </div>
35
-
36
- <!-- Loading State -->
37
- <div v-if="selectedFile && upload.status.value.isLoading" :class="[ui.onLoading.wrapper]">
38
- <div :class="[ui.onLoading.placeholderWrapper]">
39
- <Icon
40
- :name="
41
- isImage(selectedFile)
42
- ? ui.onLoading.placeholderImgIcon || ui.default.placeholderImgIcon
43
- : ui.onLoading.placeholderFileIcon || ui.default.filePreviewIcon
44
- "
45
- :class="[ui.onLoading.placeholderIconClass]"
46
- />
47
- </div>
48
- <div :class="[ui.onLoading.textWrapper]">
49
- <div class="truncate">
50
- <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
51
- <p class="truncate font-light text-gray-400">
52
- {{
53
- fileAllocate.isSelectedFileUseMb.value
54
- ? `${fileAllocate.selectedFileSizeMb.value} MB`
55
- : `${fileAllocate.selectedFileSizeKb.value} KB`
56
- }}
57
- - {{ percent }}% {{ uploadingLabel }}
58
- </p>
59
- </div>
60
- <div>
61
- <Icon
62
- :name="ui.onLoading.loadingIcon || ui.default.loadingIcon"
63
- :class="[ui.onLoading.loadingIconClass]"
64
- />
65
- </div>
66
- </div>
67
- </div>
68
-
69
- <!-- Success State -->
70
- <div v-if="value" :class="[ui.onPreview.wrapper]">
71
- <div :class="[ui.onPreview.previewImgWrapper]">
72
- <div v-if="isImageFromPath(value.path)" class="size-full overflow-hidden">
73
- <img :src="value.url" :class="[ui.onPreview.previewImgClass]" alt="img-preview" />
74
- </div>
75
- <div v-else>
76
- <Icon
77
- :name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
78
- :class="[ui.onPreview.previewFileClass]"
79
- />
80
- </div>
81
- </div>
82
- <div :class="[ui.onPreview.textWrapper]">
83
- <div class="truncate">
84
- <h1 class="truncate font-bold">{{ value.name }}</h1>
85
- <p class="truncate text-sm font-light text-gray-400">
86
- {{
87
- fileAllocateFromPath.isSelectedFileUseMb.value
88
- ? `${fileAllocate.selectedFileSizeMb.value} MB`
89
- : `${fileAllocateFromPath.selectedFileSizeKb.value} KB`
90
- }}
91
- </p>
92
- </div>
93
- <div :class="[ui.action.wrapper]">
94
- <Icon
95
- v-if="isImageFromPath(value.path)"
96
- :name="ui.action.previewIcon"
97
- :class="[ui.action.iconClass]"
98
- title="ดูตัวอย่าง"
99
- @click="() => (isPreviewOpen = true)"
100
- />
101
- <Icon
102
- :name="ui.action.downloadIcon"
103
- :class="[ui.action.iconClass]"
104
- title="ดาวน์โหลดไฟล์"
105
- @click="handleDownloadFile"
106
- />
107
- <Icon
108
- v-if="!(isDisabled || isReadonly)"
109
- :name="ui.action.deleteIcon"
110
- :class="[ui.action.iconClass]"
111
- title="ลบไฟล์"
112
- @click="handleDeleteFile"
113
- />
114
- <Modal v-model="isPreviewOpen" :title="value.name">
115
- <img :src="value.url" alt="img-preview" />
116
- </Modal>
117
- </div>
118
- </div>
119
- </div>
120
-
121
- <!-- Failed State -->
122
- <div v-if="selectedFile && upload.status.value.isError" :class="[ui.onFailed.wrapper]">
123
- <div :class="[ui.onFailed.failedImgWrapper]">
124
- <Icon
125
- :name="
126
- isImage(selectedFile)
127
- ? ui.onFailed.failedImgIcon || ui.default.placeholderImgIcon
128
- : ui.onFailed.failedFileIcon || ui.default.filePreviewIcon
129
- "
130
- :class="[ui.onFailed.failedIconClass]"
131
- />
132
- </div>
133
- <div :class="[ui.onFailed.textWrapper]">
134
- <div class="truncate">
135
- <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
136
- <p class="text-danger truncate font-light">
137
- {{ uploadFailedLabel }}
138
- </p>
139
- <Button
140
- variant="ghost"
141
- :label="retryLabel"
142
- :leading-icon="ui.action.retryIcon"
143
- :class="[ui.action.retryBtnClass]"
144
- size="sm"
145
- @click="handleRetryUpload"
146
- />
147
- </div>
148
- <div :class="[ui.action.wrapper]">
149
- <Icon
150
- title="ลบไฟล์"
151
- :name="ui.action.deleteIcon"
152
- :class="[ui.action.deleteIconClass]"
153
- @click="handleDeleteFile"
154
- />
155
- </div>
156
- </div>
157
- </div>
158
- </div>
159
- </div>
160
- </FieldWrapper>
161
- </template>
162
-
163
- <script lang="ts" setup>
164
- import { useDropZone } from '@vueuse/core'
165
- import { type IUploadDropzoneAutoProps } from './types'
166
- import {
167
- checkMaxSize,
168
- checkFileType,
169
- isImage,
170
- downloadFileFromURL,
171
- useFileAllocate,
172
- useFileProgress,
173
- isImageFromPath,
174
- useFileSize,
175
- } from '#core/helpers/componentHelper'
176
- import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
177
- import { useFieldHOC } from '#core/composables/useForm'
178
- import {
179
- type IUploadRequest,
180
- ref,
181
- computed,
182
- toRef,
183
- useUI,
184
- useUiConfig,
185
- useUploadLoader,
186
- useWatchTrue,
187
- StringHelper,
188
- _get,
189
- } from '#imports'
190
- import { uploadFileDropzone } from '#core/ui.config'
191
- import i18next from 'i18next'
192
- import type { IFileValue } from '#core/components/Form/types'
193
-
194
- const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
195
-
196
- const props = withDefaults(defineProps<IUploadDropzoneAutoProps>(), {
197
- bodyKey: 'file',
198
- responseURL: 'url',
199
- responsePath: 'path',
200
- selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
201
- selectFileSubLabel: 'หรือ ลากและวางที่นี่',
202
- uploadingLabel: 'กำลังอัพโหลด...',
203
- uploadFailedLabel: 'อัพโหลดล้มเหลว, กรุณาลองอีกครั้ง',
204
- })
205
-
206
- const emits = defineEmits(['change', 'success', 'delete'])
207
-
208
- const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<IFileValue>(props)
209
-
210
- const request: IUploadRequest = {
211
- pathURL: props.uploadPathURL,
212
- requestOptions: props.requestOptions,
213
- }
214
-
215
- const upload = useUploadLoader(request)
216
-
217
- const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
218
-
219
- const fileInputRef = ref<HTMLInputElement>()
220
- const dropzoneRef = ref<HTMLDivElement>()
221
- const selectedFile = ref<File | undefined>()
222
- const isPreviewOpen = ref<boolean>(false)
223
-
224
- const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
225
- const fileAllocate = useFileAllocate(selectedFile, props)
226
- const fileAllocateFromPath = computed(() => useFileSize(value.value?.size))
227
-
228
- const onDrop = (files: File[] | null) => {
229
- if (props.isDisabled || files?.length === 0 || !files) return
230
-
231
- const file = files[0]
232
- const result = handleCheckFileCondition(file)
233
-
234
- if (result && file) {
235
- selectedFile.value = file
236
- const formData = new FormData()
237
-
238
- emits('change', file)
239
-
240
- formData.append(props.bodyKey, file)
241
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
242
- }
243
- }
244
-
245
- const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
246
- onDrop,
247
- })
248
-
249
- const handleChange = (e: Event) => {
250
- if (props.isDisabled || props.isReadonly) return
251
-
252
- const file = (e.target as HTMLInputElement).files?.[0]
253
- const result = handleCheckFileCondition(file)
254
-
255
- if (result && file) {
256
- selectedFile.value = file
257
- const formData = new FormData()
258
-
259
- emits('change', file)
260
-
261
- formData.append(props.bodyKey, file)
262
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
263
- }
264
- }
265
-
266
- const handleOpenFile = () => {
267
- fileInputRef.value?.click()
268
- }
269
-
270
- const handleDeleteFile = () => {
271
- fileInputRef.value?.value && (fileInputRef.value.value = '')
272
- selectedFile.value = undefined
273
- onChange(undefined)
274
- emits('delete')
275
- }
276
-
277
- const handleCheckFileCondition = (file: File | undefined): boolean => {
278
- if (!file) return false
279
-
280
- const fileType = checkFileType(file, fileAllocate.acceptFile.value ?? '')
281
-
282
- if (!fileType) {
283
- setErrors(i18next.t('custom:invalid_file_type'))
284
-
285
- return false
286
- }
287
-
288
- const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
289
-
290
- if (!maxSize) {
291
- if (fileAllocate.isAcceptFileUseMb.value) {
292
- setErrors(
293
- i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
294
- )
295
- } else {
296
- setErrors(
297
- i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
298
- )
299
- }
300
-
301
- return false
302
- }
303
-
304
- setErrors('')
305
-
306
- return true
307
- }
308
-
309
- const handleDownloadFile = () => {
310
- downloadFileFromURL(value.value?.url, value.value?.name)
311
- }
312
-
313
- const handleRetryUpload = () => {
314
- if (selectedFile.value) {
315
- const formData = new FormData()
316
-
317
- formData.append(props.bodyKey, selectedFile.value)
318
- upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
319
- }
320
- }
321
-
322
- useWatchTrue(
323
- () => upload.status.value.isSuccess,
324
- () => {
325
- value.value = {
326
- url: _get(upload.data.value, props.responseURL),
327
- path: _get(upload.data.value, props.responsePath),
328
- name: upload.data.value.file_name,
329
- size: upload.data.value.size,
330
- }
331
-
332
- emits('success', value.value)
333
- }
334
- )
335
-
336
- useWatchTrue(
337
- () => upload.status.value.isError,
338
- () => {
339
- setErrors(StringHelper.getError(upload.status.value.errorData))
340
- }
341
- )
342
- </script>
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div
4
+ ref="dropzoneRef"
5
+ :class="[
6
+ ui.base,
7
+ {
8
+ [ui.disabled]: isDisabled,
9
+ [ui.background.default]: !isOverDropZone && !isDisabled,
10
+ [ui.background.dragover]: isOverDropZone && (!isDisabled || !isReadonly),
11
+ [ui.failed]: upload.status.value.isError,
12
+ },
13
+ ]"
14
+ >
15
+ <input
16
+ ref="fileInputRef"
17
+ type="file"
18
+ class="hidden"
19
+ :name="name"
20
+ :accept="fileAllocate.acceptFile.value"
21
+ :disabled="isDisabled || isReadonly"
22
+ @change="handleChange"
23
+ />
24
+ <div :class="[ui.wrapper]">
25
+ <div v-if="!selectedFile && !value" :class="[ui.placeholderWrapper]">
26
+ <Icon :name="ui.default.uploadIcon" :class="[ui.labelIcon]" />
27
+ <div :class="[ui.labelWrapper]">
28
+ <p class="text-primary cursor-pointer" @click="handleOpenFile">
29
+ {{ selectFileLabel }}
30
+ </p>
31
+ <p>{{ selectFileSubLabel }}</p>
32
+ </div>
33
+ <p v-if="placeholder" :class="[ui.placeholder]">{{ placeholder }}</p>
34
+ </div>
35
+
36
+ <!-- Loading State -->
37
+ <div v-if="selectedFile && upload.status.value.isLoading" :class="[ui.onLoading.wrapper]">
38
+ <div :class="[ui.onLoading.placeholderWrapper]">
39
+ <Icon
40
+ :name="
41
+ isImage(selectedFile)
42
+ ? ui.onLoading.placeholderImgIcon || ui.default.placeholderImgIcon
43
+ : ui.onLoading.placeholderFileIcon || ui.default.filePreviewIcon
44
+ "
45
+ :class="[ui.onLoading.placeholderIconClass]"
46
+ />
47
+ </div>
48
+ <div :class="[ui.onLoading.textWrapper]">
49
+ <div class="truncate">
50
+ <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
51
+ <p class="truncate font-light text-gray-400">
52
+ {{
53
+ fileAllocate.isSelectedFileUseMb.value
54
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
55
+ : `${fileAllocate.selectedFileSizeKb.value} KB`
56
+ }}
57
+ - {{ percent }}% {{ uploadingLabel }}
58
+ </p>
59
+ </div>
60
+ <div>
61
+ <Icon
62
+ :name="ui.onLoading.loadingIcon || ui.default.loadingIcon"
63
+ :class="[ui.onLoading.loadingIconClass]"
64
+ />
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Success State -->
70
+ <div v-if="value" :class="[ui.onPreview.wrapper]">
71
+ <div :class="[ui.onPreview.previewImgWrapper]">
72
+ <div v-if="isImageFromPath(value.path)" class="size-full overflow-hidden">
73
+ <img :src="value.url" :class="[ui.onPreview.previewImgClass]" alt="img-preview" />
74
+ </div>
75
+ <div v-else>
76
+ <Icon
77
+ :name="ui.onPreview.previewFileIcon || ui.default.filePreviewIcon"
78
+ :class="[ui.onPreview.previewFileClass]"
79
+ />
80
+ </div>
81
+ </div>
82
+ <div :class="[ui.onPreview.textWrapper]">
83
+ <div class="truncate">
84
+ <h1 class="truncate font-bold">{{ value.name }}</h1>
85
+ <p class="truncate text-sm font-light text-gray-400">
86
+ {{
87
+ fileAllocateFromPath.isSelectedFileUseMb.value
88
+ ? `${fileAllocate.selectedFileSizeMb.value} MB`
89
+ : `${fileAllocateFromPath.selectedFileSizeKb.value} KB`
90
+ }}
91
+ </p>
92
+ </div>
93
+ <div :class="[ui.action.wrapper]">
94
+ <Icon
95
+ v-if="isImageFromPath(value.path)"
96
+ :name="ui.action.previewIcon"
97
+ :class="[ui.action.iconClass]"
98
+ title="ดูตัวอย่าง"
99
+ @click="() => (isPreviewOpen = true)"
100
+ />
101
+ <Icon
102
+ :name="ui.action.downloadIcon"
103
+ :class="[ui.action.iconClass]"
104
+ title="ดาวน์โหลดไฟล์"
105
+ @click="handleDownloadFile"
106
+ />
107
+ <Icon
108
+ v-if="!(isDisabled || isReadonly)"
109
+ :name="ui.action.deleteIcon"
110
+ :class="[ui.action.iconClass]"
111
+ title="ลบไฟล์"
112
+ @click="handleDeleteFile"
113
+ />
114
+ <Modal v-model="isPreviewOpen" :title="value.name">
115
+ <img :src="value.url" alt="img-preview" />
116
+ </Modal>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- Failed State -->
122
+ <div v-if="selectedFile && upload.status.value.isError" :class="[ui.onFailed.wrapper]">
123
+ <div :class="[ui.onFailed.failedImgWrapper]">
124
+ <Icon
125
+ :name="
126
+ isImage(selectedFile)
127
+ ? ui.onFailed.failedImgIcon || ui.default.placeholderImgIcon
128
+ : ui.onFailed.failedFileIcon || ui.default.filePreviewIcon
129
+ "
130
+ :class="[ui.onFailed.failedIconClass]"
131
+ />
132
+ </div>
133
+ <div :class="[ui.onFailed.textWrapper]">
134
+ <div class="truncate">
135
+ <h1 class="truncate font-bold">{{ selectedFile?.name }}</h1>
136
+ <p class="text-danger truncate font-light">
137
+ {{ uploadFailedLabel }}
138
+ </p>
139
+ <Button
140
+ variant="ghost"
141
+ :label="retryLabel"
142
+ :leading-icon="ui.action.retryIcon"
143
+ :class="[ui.action.retryBtnClass]"
144
+ size="sm"
145
+ @click="handleRetryUpload"
146
+ />
147
+ </div>
148
+ <div :class="[ui.action.wrapper]">
149
+ <Icon
150
+ title="ลบไฟล์"
151
+ :name="ui.action.deleteIcon"
152
+ :class="[ui.action.deleteIconClass]"
153
+ @click="handleDeleteFile"
154
+ />
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+ </FieldWrapper>
161
+ </template>
162
+
163
+ <script lang="ts" setup>
164
+ import { useDropZone } from '@vueuse/core'
165
+ import { type IUploadDropzoneAutoProps } from './types'
166
+ import {
167
+ checkMaxSize,
168
+ checkFileType,
169
+ isImage,
170
+ downloadFileFromURL,
171
+ useFileAllocate,
172
+ useFileProgress,
173
+ isImageFromPath,
174
+ useFileSize,
175
+ } from '#core/helpers/componentHelper'
176
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
177
+ import { useFieldHOC } from '#core/composables/useForm'
178
+ import {
179
+ type IUploadRequest,
180
+ ref,
181
+ computed,
182
+ toRef,
183
+ useUI,
184
+ useUiConfig,
185
+ useUploadLoader,
186
+ useWatchTrue,
187
+ StringHelper,
188
+ _get,
189
+ } from '#imports'
190
+ import { uploadFileDropzone } from '#core/ui.config'
191
+ import i18next from 'i18next'
192
+ import type { IFileValue } from '#core/components/Form/types'
193
+
194
+ const config = useUiConfig<typeof uploadFileDropzone>(uploadFileDropzone, 'uploadFileDropzone')
195
+
196
+ const props = withDefaults(defineProps<IUploadDropzoneAutoProps>(), {
197
+ bodyKey: 'file',
198
+ responseURL: 'url',
199
+ responsePath: 'path',
200
+ selectFileLabel: 'คลิกเพื่อเลือกไฟล์',
201
+ selectFileSubLabel: 'หรือ ลากและวางที่นี่',
202
+ uploadingLabel: 'กำลังอัพโหลด...',
203
+ uploadFailedLabel: 'อัพโหลดล้มเหลว, กรุณาลองอีกครั้ง',
204
+ })
205
+
206
+ const emits = defineEmits(['change', 'success', 'delete'])
207
+
208
+ const { wrapperProps, handleChange: onChange, setErrors, value } = useFieldHOC<IFileValue>(props)
209
+
210
+ const request: IUploadRequest = {
211
+ pathURL: props.uploadPathURL,
212
+ requestOptions: props.requestOptions,
213
+ }
214
+
215
+ const upload = useUploadLoader(request)
216
+
217
+ const { ui } = useUI('uploadFileDropzone', toRef(props, 'ui'), config)
218
+
219
+ const fileInputRef = ref<HTMLInputElement>()
220
+ const dropzoneRef = ref<HTMLDivElement>()
221
+ const selectedFile = ref<File | undefined>()
222
+ const isPreviewOpen = ref<boolean>(false)
223
+
224
+ const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
225
+ const fileAllocate = useFileAllocate(selectedFile, props)
226
+ const fileAllocateFromPath = computed(() => useFileSize(value.value?.size))
227
+
228
+ const onDrop = (files: File[] | null) => {
229
+ if (props.isDisabled || files?.length === 0 || !files) return
230
+
231
+ const file = files[0]
232
+ const result = handleCheckFileCondition(file)
233
+
234
+ if (result && file) {
235
+ selectedFile.value = file
236
+ const formData = new FormData()
237
+
238
+ emits('change', file)
239
+
240
+ formData.append(props.bodyKey, file)
241
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
242
+ }
243
+ }
244
+
245
+ const { isOverDropZone } = useDropZone(dropzoneRef as unknown as HTMLElement, {
246
+ onDrop,
247
+ })
248
+
249
+ const handleChange = (e: Event) => {
250
+ if (props.isDisabled || props.isReadonly) return
251
+
252
+ const file = (e.target as HTMLInputElement).files?.[0]
253
+ const result = handleCheckFileCondition(file)
254
+
255
+ if (result && file) {
256
+ selectedFile.value = file
257
+ const formData = new FormData()
258
+
259
+ emits('change', file)
260
+
261
+ formData.append(props.bodyKey, file)
262
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
263
+ }
264
+ }
265
+
266
+ const handleOpenFile = () => {
267
+ fileInputRef.value?.click()
268
+ }
269
+
270
+ const handleDeleteFile = () => {
271
+ fileInputRef.value?.value && (fileInputRef.value.value = '')
272
+ selectedFile.value = undefined
273
+ onChange(undefined)
274
+ emits('delete')
275
+ }
276
+
277
+ const handleCheckFileCondition = (file: File | undefined): boolean => {
278
+ if (!file) return false
279
+
280
+ const fileType = checkFileType(file, fileAllocate.acceptFile.value ?? '')
281
+
282
+ if (!fileType) {
283
+ setErrors(i18next.t('custom:invalid_file_type'))
284
+
285
+ return false
286
+ }
287
+
288
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
289
+
290
+ if (!maxSize) {
291
+ if (fileAllocate.isAcceptFileUseMb.value) {
292
+ setErrors(
293
+ i18next.t('custom:invalid_file_size_mb', { size: fileAllocate.acceptFileSizeMb.value })
294
+ )
295
+ } else {
296
+ setErrors(
297
+ i18next.t('custom:invalid_file_size_kb', { size: fileAllocate.acceptFileSizeKb.value })
298
+ )
299
+ }
300
+
301
+ return false
302
+ }
303
+
304
+ setErrors('')
305
+
306
+ return true
307
+ }
308
+
309
+ const handleDownloadFile = () => {
310
+ downloadFileFromURL(value.value?.url, value.value?.name)
311
+ }
312
+
313
+ const handleRetryUpload = () => {
314
+ if (selectedFile.value) {
315
+ const formData = new FormData()
316
+
317
+ formData.append(props.bodyKey, selectedFile.value)
318
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
319
+ }
320
+ }
321
+
322
+ useWatchTrue(
323
+ () => upload.status.value.isSuccess,
324
+ () => {
325
+ value.value = {
326
+ url: _get(upload.data.value, props.responseURL),
327
+ path: _get(upload.data.value, props.responsePath),
328
+ name: upload.data.value.file_name,
329
+ size: upload.data.value.size,
330
+ }
331
+
332
+ emits('success', value.value)
333
+ }
334
+ )
335
+
336
+ useWatchTrue(
337
+ () => upload.status.value.isError,
338
+ () => {
339
+ setErrors(StringHelper.getError(upload.status.value.errorData))
340
+ }
341
+ )
342
+ </script>