216 lines
6.8 KiB
Vue
216 lines
6.8 KiB
Vue
<script setup>
|
|
import { FileUploadStore } from '@/stores/FileUploadStore';
|
|
import { useAuth } from '@websanova/vue-auth/src/v3.js';
|
|
import { usePrimeVue } from 'primevue/config';
|
|
import { useToast } from 'primevue/usetoast';
|
|
import { ref, watch } from 'vue';
|
|
|
|
const props = defineProps({
|
|
inputName: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
label: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
tooltipText: {
|
|
type: String,
|
|
default: 'Upload files'
|
|
},
|
|
uploadUrl: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
isMultiple: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
acceptedFormats: {
|
|
type: String,
|
|
default: '.docx'
|
|
},
|
|
folderName: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
uploadedFiles: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
});
|
|
|
|
const emit = defineEmits(['upload', 'remove', 'update:uploadedFiles']);
|
|
|
|
const fileUploadStore = FileUploadStore();
|
|
const toast = useToast();
|
|
const auth = useAuth();
|
|
const $primevue = usePrimeVue();
|
|
|
|
const localUploadedFiles = ref(props.uploadedFiles);
|
|
|
|
// Sync uploaded files with parent
|
|
watch(
|
|
localUploadedFiles,
|
|
(newFiles) => {
|
|
emit('update:uploadedFiles', newFiles);
|
|
},
|
|
{ deep: true }
|
|
);
|
|
|
|
watch(
|
|
() => props.uploadedFiles,
|
|
(newFiles) => {
|
|
localUploadedFiles.value = newFiles;
|
|
},
|
|
{ deep: true }
|
|
);
|
|
|
|
const onBeforeSend = (event) => {
|
|
const { xhr } = event;
|
|
const token = auth.token();
|
|
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
|
|
};
|
|
|
|
const handleUpload = (event) => {
|
|
const { xhr } = event;
|
|
|
|
if (xhr.status === 200) {
|
|
const uploadedFileName = event.files && event.files.length > 0 ? event.files[0].name : 'UnknownFile';
|
|
|
|
toast.add({
|
|
severity: 'success',
|
|
summary: 'Success',
|
|
detail: 'File uploaded successfully!',
|
|
life: 3000
|
|
});
|
|
|
|
emit('upload', {
|
|
fileName: uploadedFileName,
|
|
response: xhr.response,
|
|
files: event.files,
|
|
inputName: props.inputName
|
|
});
|
|
} else {
|
|
toast.add({
|
|
severity: 'error',
|
|
summary: 'Error',
|
|
detail: `Failed to upload file. Status: ${xhr.status}`,
|
|
life: 3000
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleRemove = async (event, removeUploadedFileCallback) => {
|
|
const { file, index } = event;
|
|
|
|
try {
|
|
const response = await fileUploadStore.deleteFile(file.name, props.folderName);
|
|
|
|
if (response.status === 200) {
|
|
toast.add({
|
|
severity: 'success',
|
|
summary: 'Success',
|
|
detail: 'File removed successfully!',
|
|
life: 3000
|
|
});
|
|
|
|
removeUploadedFileCallback(index);
|
|
emit('remove', { fileName: file.name, inputName: props.inputName });
|
|
} else {
|
|
toast.add({
|
|
severity: 'error',
|
|
summary: 'Error',
|
|
detail: `Failed to remove file. Status: ${response.statusText}`,
|
|
life: 3000
|
|
});
|
|
}
|
|
} catch (error) {
|
|
toast.add({
|
|
severity: 'error',
|
|
summary: 'Error',
|
|
detail: `Error while removing file: ${error.message}`,
|
|
life: 3000
|
|
});
|
|
}
|
|
};
|
|
|
|
const formatSize = (bytes) => {
|
|
const k = 1024;
|
|
const sizes = $primevue.config.locale.fileSizeTypes;
|
|
|
|
if (bytes === 0) {
|
|
return `0 ${sizes[0]}`;
|
|
}
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
const truncatedSize = Math.trunc(bytes / Math.pow(k, i));
|
|
|
|
return `${truncatedSize} ${sizes[i]}`;
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<label :for="inputName">
|
|
<b>{{ label }}</b>
|
|
<i class="pi pi-info-circle text-violet-600 cursor-pointer" v-tooltip="tooltipText"></i>
|
|
</label>
|
|
<div>
|
|
<FileUpload
|
|
name="MultiFileUpload"
|
|
:customUpload="false"
|
|
:url="uploadUrl"
|
|
@upload="handleUpload"
|
|
:multiple="isMultiple"
|
|
:accept="acceptedFormats"
|
|
auto
|
|
:showUploadButton="false"
|
|
:showCancelButton="false"
|
|
:maxFileSize="52428800"
|
|
:invalidFileSizeMessage="'Invalid file size, file size should be smaller than 20 MB'"
|
|
v-model:files="localUploadedFiles"
|
|
@before-send="onBeforeSend"
|
|
>
|
|
<template #content="{ uploadedFiles, removeUploadedFileCallback }">
|
|
<div class="pt-4">
|
|
<div v-if="uploadedFiles.length > 0">
|
|
<table class="table-auto w-full border-collapse border border-gray-200">
|
|
<thead>
|
|
<tr>
|
|
<th class="border border-gray-300 p-2">Name</th>
|
|
<th class="border border-gray-300 p-2">Dimension</th>
|
|
<th class="border border-gray-300 p-2">Status</th>
|
|
<th class="border border-gray-300 p-2">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="(file, index) in uploadedFiles" :key="file.name + file.size" class="hover:bg-gray-50">
|
|
<td class="border border-gray-300 p-2">{{ file.name }}</td>
|
|
<td class="border border-gray-300 p-2">{{ formatSize(file.size) }}</td>
|
|
<td class="border border-gray-300 p-2">
|
|
<Badge value="UPLOADED" severity="success" />
|
|
</td>
|
|
<td class="border border-gray-300 p-2">
|
|
<Button label="Remove" @click="handleRemove({ file, index }, removeUploadedFileCallback)" />
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<template #empty>
|
|
<div class="flex items-center justify-center flex-col">
|
|
<div class="!border !border-violet-600 !rounded-full !w-24 !h-24 flex items-center justify-center">
|
|
<i class="pi pi-cloud-upload !text-4xl !-violet-600"></i>
|
|
</div>
|
|
<p class="mt-2 mb-2 text-m">Drag and drop files here to upload.</p>
|
|
</div>
|
|
</template>
|
|
</FileUpload>
|
|
</div>
|
|
</div>
|
|
</template>
|