fix(frontend) : refresh documents locally after upload/delete and improve progress UX
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,8 +28,10 @@
|
|||||||
<div class="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-neutral-200">
|
<div class="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-neutral-200">
|
||||||
<div
|
<div
|
||||||
class="h-full rounded-full transition-all"
|
class="h-full rounded-full transition-all"
|
||||||
:class="upload.error ? 'bg-red-500' : 'bg-blue-500'"
|
:class="[
|
||||||
:style="{ width: `${upload.progress}%` }"
|
upload.error ? 'bg-red-500' : upload.uploading ? 'animate-pulse bg-blue-400' : 'bg-green-500',
|
||||||
|
]"
|
||||||
|
:style="{ width: upload.uploading ? '70%' : `${upload.progress}%` }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,6 +66,7 @@ const isDragging = ref(false)
|
|||||||
type UploadState = {
|
type UploadState = {
|
||||||
name: string
|
name: string
|
||||||
progress: number
|
progress: number
|
||||||
|
uploading: boolean
|
||||||
error: boolean
|
error: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,19 +102,18 @@ async function processFiles(files: File[]) {
|
|||||||
|
|
||||||
const state: UploadState = reactive({
|
const state: UploadState = reactive({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
progress: 0,
|
progress: 30,
|
||||||
|
uploading: true,
|
||||||
error: false,
|
error: false,
|
||||||
})
|
})
|
||||||
uploads.value.push(state)
|
uploads.value.push(state)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await uploadFile(props.taskId, file)
|
await uploadFile(props.taskId, file)
|
||||||
|
state.uploading = false
|
||||||
state.progress = 100
|
state.progress = 100
|
||||||
toast.success({
|
|
||||||
title: 'Succès',
|
|
||||||
message: t('taskDocuments.uploaded'),
|
|
||||||
})
|
|
||||||
} catch {
|
} catch {
|
||||||
|
state.uploading = false
|
||||||
state.error = true
|
state.error = true
|
||||||
state.progress = 100
|
state.progress = 100
|
||||||
toast.error({
|
toast.error({
|
||||||
@@ -119,13 +121,13 @@ async function processFiles(files: File[]) {
|
|||||||
message: t('taskDocuments.uploadError'),
|
message: t('taskDocuments.uploadError'),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit('uploaded')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up completed uploads after a delay
|
// Clean up completed uploads after a delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uploads.value = uploads.value.filter(u => u.error)
|
uploads.value = uploads.value.filter(u => u.error)
|
||||||
}, 2000)
|
}, 1500)
|
||||||
|
|
||||||
emit('uploaded')
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -375,15 +375,26 @@ watch(() => props.modelValue, async (open) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { create, update, remove } = useTaskService()
|
const { create, update, remove } = useTaskService()
|
||||||
const { remove: removeDocument } = useTaskDocumentService()
|
const { remove: removeDocument, getByTask: getDocumentsByTask } = useTaskDocumentService()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const isAdmin = computed(() => authStore.user?.roles?.includes('ROLE_ADMIN') ?? false)
|
const isAdmin = computed(() => authStore.user?.roles?.includes('ROLE_ADMIN') ?? false)
|
||||||
|
|
||||||
const documents = computed(() => props.task?.documents ?? [])
|
const localDocuments = ref<TaskDocument[]>([])
|
||||||
|
const documents = computed(() => localDocuments.value)
|
||||||
const previewDoc = ref<TaskDocument | null>(null)
|
const previewDoc = ref<TaskDocument | null>(null)
|
||||||
|
|
||||||
|
// Sync documents from task prop when modal opens or task changes
|
||||||
|
watch(() => props.task?.documents, (docs) => {
|
||||||
|
localDocuments.value = docs ? [...docs] : []
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
async function refreshDocuments() {
|
||||||
|
if (!props.task) return
|
||||||
|
localDocuments.value = await getDocumentsByTask(props.task.id)
|
||||||
|
}
|
||||||
|
|
||||||
const previewIndex = computed(() => {
|
const previewIndex = computed(() => {
|
||||||
if (!previewDoc.value) return -1
|
if (!previewDoc.value) return -1
|
||||||
return documents.value.findIndex(d => d.id === previewDoc.value!.id)
|
return documents.value.findIndex(d => d.id === previewDoc.value!.id)
|
||||||
@@ -408,11 +419,11 @@ function nextPreview() {
|
|||||||
async function handleDeleteDocument(doc: TaskDocument) {
|
async function handleDeleteDocument(doc: TaskDocument) {
|
||||||
if (!confirm(t('taskDocuments.confirmDeleteMessage'))) return
|
if (!confirm(t('taskDocuments.confirmDeleteMessage'))) return
|
||||||
await removeDocument(doc.id)
|
await removeDocument(doc.id)
|
||||||
emit('saved')
|
await refreshDocuments()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDocumentUploaded() {
|
async function handleDocumentUploaded() {
|
||||||
emit('saved')
|
await refreshDocuments()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete() {
|
async function handleDelete() {
|
||||||
|
|||||||
Reference in New Issue
Block a user