Files
chatwoot/app/javascript/dashboard/components-next/fileUpload/FileUpload.vue
T
2025-07-10 13:52:53 +07:00

175 lines
3.9 KiB
Vue

<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import Icon from 'dashboard/components-next/icon/Icon.vue';
const props = defineProps({
accept: {
type: String,
default: '*',
},
multiple: {
type: Boolean,
default: false,
},
maxSizeMB: {
type: Number,
default: 10,
},
disabled: {
type: Boolean,
default: false,
},
placeholder: {
type: String,
default: '',
},
uploadText: {
type: String,
default: '',
},
dragText: {
type: String,
default: '',
},
});
const emit = defineEmits(['fileSelected', 'fileError']);
const { t } = useI18n();
const fileInput = ref(null);
const isDragOver = ref(false);
const acceptTypes = computed(() => props.accept);
const maxSize = computed(() => props.maxSizeMB * 1024 * 1024);
const handleFileClick = () => {
if (!props.disabled) {
fileInput.value?.click();
}
};
const validateFile = file => {
// Check file size
if (file.size > maxSize.value) {
const errorMsg = t(
'CAPTAIN.DOCUMENTS.CREATE.FORM.FILE_UPLOAD.ERROR_TOO_LARGE',
{
fileName: file.name,
maxSize: props.maxSizeMB,
}
);
emit('fileError', errorMsg);
return false;
}
// Check file type if specified
if (
props.accept !== '*' &&
!file.type.match(new RegExp(props.accept.replace(/\*/g, '.*')))
) {
const errorMsg = t(
'CAPTAIN.DOCUMENTS.CREATE.FORM.FILE_UPLOAD.ERROR_TYPE_NOT_SUPPORTED',
{
acceptedTypes: props.accept,
}
);
emit('fileError', errorMsg);
return false;
}
return true;
};
const handleFileChange = event => {
const files = event.target.files;
if (files && files.length > 0) {
const file = files[0];
if (validateFile(file)) {
emit('fileSelected', file);
}
}
// Reset input value to allow selecting the same file again
event.target.value = '';
};
const handleDragOver = event => {
event.preventDefault();
if (!props.disabled) {
isDragOver.value = true;
}
};
const handleDragLeave = event => {
event.preventDefault();
// Only set to false if we're leaving the drop zone completely
if (!event.currentTarget.contains(event.relatedTarget)) {
isDragOver.value = false;
}
};
const handleDrop = event => {
event.preventDefault();
isDragOver.value = false;
if (props.disabled) return;
const files = event.dataTransfer.files;
if (files && files.length > 0) {
const file = files[0];
if (validateFile(file)) {
emit('fileSelected', file);
}
}
};
const displayText = computed(() => {
if (isDragOver.value && props.dragText) {
return props.dragText;
}
return props.uploadText || props.placeholder;
});
</script>
<template>
<div
class="border-2 border-dashed border-n-slate-6 rounded-lg p-6 bg-n-alpha-2 hover:bg-n-alpha-3 cursor-pointer transition-all duration-200 ease-in-out flex flex-col items-center justify-center text-center min-h-[120px] hover:border-n-slate-7"
:class="{
'border-n-blue-9 bg-n-blue-2/50': isDragOver,
'opacity-50 cursor-not-allowed hover:bg-n-alpha-2 hover:border-n-slate-6':
disabled,
}"
@dragover="handleDragOver"
@dragleave="handleDragLeave"
@drop="handleDrop"
@click="handleFileClick"
>
<input
ref="fileInput"
type="file"
:accept="acceptTypes"
:multiple="multiple"
:disabled="disabled"
class="hidden"
@change="handleFileChange"
/>
<div class="flex flex-col items-center gap-3">
<div class="p-2 rounded-full bg-n-alpha-3">
<Icon icon="i-lucide-file-up" class="size-6 text-n-slate-8" />
</div>
<div>
<p class="text-sm font-medium text-n-slate-11">
{{ displayText }}
</p>
<p class="text-xs text-n-slate-8 mt-1">
{{
$t('CAPTAIN.DOCUMENTS.CREATE.FORM.FILE_UPLOAD.MAX_SIZE', {
size: maxSizeMB,
})
}}
</p>
</div>
</div>
</div>
</template>