fix: persist user documents immediately after FilePond upload
FilePond success only stored files in the temp bucket, so the documents table stayed empty unless a second commit click was used.
This commit is contained in:
@@ -139,6 +139,23 @@ const isPdf = (img) => {
|
||||
return name.includes('pdf') || name.includes('application/pdf');
|
||||
};
|
||||
|
||||
const parseServerFileMeta = (serverId) => {
|
||||
if (!serverId) return { tempFileName: '', originalName: '', mimeType: '' };
|
||||
if (typeof serverId === 'object') {
|
||||
const nested = serverId.data && typeof serverId.data === 'object' ? serverId.data : serverId;
|
||||
return {
|
||||
tempFileName: nested.tempFileName || nested.filename || '',
|
||||
originalName: nested.originalName || nested.fileName || '',
|
||||
mimeType: nested.mimeType || ''
|
||||
};
|
||||
}
|
||||
try {
|
||||
return parseServerFileMeta(JSON.parse(serverId));
|
||||
} catch {
|
||||
return { tempFileName: String(serverId), originalName: '', mimeType: '' };
|
||||
}
|
||||
};
|
||||
|
||||
const serverOptions = computed(() => ({
|
||||
url: API_BASE_URL,
|
||||
process: {
|
||||
@@ -147,17 +164,12 @@ const serverOptions = computed(() => ({
|
||||
Authorization: `Bearer ${authStore.accessToken}`
|
||||
},
|
||||
onload: (response) => {
|
||||
try {
|
||||
const res = typeof response === 'string' ? JSON.parse(response) : response;
|
||||
const fileData = res.data || res;
|
||||
return JSON.stringify({
|
||||
tempFileName: fileData.tempFileName || fileData.filename,
|
||||
originalName: fileData.originalName,
|
||||
mimeType: fileData.mimeType
|
||||
});
|
||||
} catch (e) {
|
||||
return response;
|
||||
}
|
||||
const meta = parseServerFileMeta(response);
|
||||
return JSON.stringify({
|
||||
tempFileName: meta.tempFileName,
|
||||
originalName: meta.originalName,
|
||||
mimeType: meta.mimeType
|
||||
});
|
||||
}
|
||||
},
|
||||
revert: null
|
||||
@@ -179,20 +191,12 @@ const fetchTempSignedUrl = async (tempFileName) => {
|
||||
const handleProcessFile = async (error, file) => {
|
||||
if (error) return;
|
||||
|
||||
let tempFileName = '';
|
||||
let originalName = file.filename;
|
||||
let mimeType = file.fileType || '';
|
||||
const meta = parseServerFileMeta(file.serverId);
|
||||
const tempFileName = meta.tempFileName || '';
|
||||
const originalName = meta.originalName || file.filename;
|
||||
const mimeType = meta.mimeType || file.fileType || '';
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(file.serverId);
|
||||
tempFileName = parsed.tempFileName || '';
|
||||
originalName = parsed.originalName || originalName;
|
||||
mimeType = parsed.mimeType || mimeType;
|
||||
} catch {
|
||||
tempFileName = file.serverId;
|
||||
}
|
||||
|
||||
const signedUrl = await fetchTempSignedUrl(tempFileName);
|
||||
const signedUrl = tempFileName ? await fetchTempSignedUrl(tempFileName) : '';
|
||||
|
||||
const newItem = {
|
||||
name: originalName,
|
||||
@@ -206,10 +210,14 @@ const handleProcessFile = async (error, file) => {
|
||||
height: 1200
|
||||
};
|
||||
|
||||
const updated = [...imagesList.value, newItem];
|
||||
emit('update:modelValue', updated);
|
||||
emit('update:modelValue', [...imagesList.value, newItem]);
|
||||
emit('upload-success', newItem);
|
||||
await nextTick();
|
||||
try {
|
||||
pond.value?.removeFile?.(file.id);
|
||||
} catch {
|
||||
// FilePond may already have dropped the item
|
||||
}
|
||||
reinitLightbox();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<!-- Upload certificates/documents: FilePond → temp, then commit on submit -->
|
||||
<!-- Upload certificates/documents: FilePond → temp, then immediately persist -->
|
||||
<template>
|
||||
<div class="user-files-section flex flex-column gap-4">
|
||||
<div class="surface-card p-3 sm:p-4 border-round border-1 border-color">
|
||||
<div class="flex align-items-center justify-content-between mb-3 flex-wrap gap-2">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold m-0">گواهینامهها</h3>
|
||||
<p class="text-muted text-sm m-0 mt-1">آپلود به باکت موقت، سپس ثبت نهایی در باکت certificates</p>
|
||||
<p class="text-muted text-sm m-0 mt-1">پس از آپلود، گواهینامه بهصورت خودکار در پرونده کاربر ثبت میشود</p>
|
||||
</div>
|
||||
<Button
|
||||
v-if="pendingCertificates.length"
|
||||
type="button"
|
||||
label="ثبت گواهینامهها"
|
||||
label="ثبت گواهینامههای باقیمانده"
|
||||
icon="pi pi-check"
|
||||
size="small"
|
||||
:loading="savingCertificates"
|
||||
@@ -25,6 +25,7 @@
|
||||
:allow-multiple="true"
|
||||
upload-endpoint="/files/admin/upload-temp"
|
||||
temp-bucket="temp"
|
||||
@upload-success="onCertificateUploaded"
|
||||
@remove="onCertificateRemove"
|
||||
/>
|
||||
</div>
|
||||
@@ -33,12 +34,12 @@
|
||||
<div class="flex align-items-center justify-content-between mb-3 flex-wrap gap-2">
|
||||
<div>
|
||||
<h3 class="text-lg font-bold m-0">اسناد و مدارک</h3>
|
||||
<p class="text-muted text-sm m-0 mt-1">آپلود به باکت موقت، سپس ثبت نهایی در باکت documents (خصوصی)</p>
|
||||
<p class="text-muted text-sm m-0 mt-1">پس از آپلود، سند بهصورت خودکار در پرونده کاربر ثبت میشود</p>
|
||||
</div>
|
||||
<Button
|
||||
v-if="pendingDocuments.length"
|
||||
type="button"
|
||||
label="ثبت اسناد"
|
||||
label="ثبت اسناد باقیمانده"
|
||||
icon="pi pi-check"
|
||||
size="small"
|
||||
severity="help"
|
||||
@@ -47,8 +48,8 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" v-if="pendingDocuments.length">
|
||||
<label class="font-semibold text-sm block mb-2">عنوان سند (اختیاری برای همه فایلهای در انتظار)</label>
|
||||
<div class="mb-3">
|
||||
<label class="font-semibold text-sm block mb-2">عنوان سند (اختیاری برای آپلودهای بعدی)</label>
|
||||
<InputText v-model="documentTitle" class="w-full text-sm" placeholder="مثلاً کارت ملی / شناسنامه" />
|
||||
</div>
|
||||
|
||||
@@ -59,6 +60,7 @@
|
||||
:allow-multiple="true"
|
||||
upload-endpoint="/files/admin/upload-temp"
|
||||
temp-bucket="temp"
|
||||
@upload-success="onDocumentUploaded"
|
||||
@remove="onDocumentRemove"
|
||||
/>
|
||||
</div>
|
||||
@@ -92,6 +94,16 @@ const savingDocuments = ref(false);
|
||||
const pendingCertificates = computed(() => certificateItems.value.filter((i) => i.pending && i.tempFileName));
|
||||
const pendingDocuments = computed(() => documentItems.value.filter((i) => i.pending && i.tempFileName));
|
||||
|
||||
const unwrapList = (res) => {
|
||||
const body = res && typeof res === 'object' && !Array.isArray(res) && res.data !== undefined
|
||||
? res.data
|
||||
: res;
|
||||
if (Array.isArray(body)) return body;
|
||||
if (Array.isArray(body?.data)) return body.data;
|
||||
if (Array.isArray(body?.items)) return body.items;
|
||||
return [];
|
||||
};
|
||||
|
||||
const toGalleryItem = (item) => ({
|
||||
id: item._id || item.id,
|
||||
name: item.title || item.fileName || item.originalName || 'فایل',
|
||||
@@ -113,28 +125,84 @@ const loadFiles = async () => {
|
||||
certificateApi.getByUser(props.userId),
|
||||
documentApi.getByUser(props.userId)
|
||||
]);
|
||||
const certs = certsRes.data || certsRes;
|
||||
const docs = docsRes.data || docsRes;
|
||||
certificateItems.value = (Array.isArray(certs) ? certs : []).map(toGalleryItem);
|
||||
documentItems.value = (Array.isArray(docs) ? docs : []).map(toGalleryItem);
|
||||
const pendingCerts = certificateItems.value.filter((i) => i.pending && i.tempFileName);
|
||||
const pendingDocs = documentItems.value.filter((i) => i.pending && i.tempFileName);
|
||||
certificateItems.value = [...unwrapList(certsRes).map(toGalleryItem), ...pendingCerts];
|
||||
documentItems.value = [...unwrapList(docsRes).map(toGalleryItem), ...pendingDocs];
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
}
|
||||
};
|
||||
|
||||
const keepPendingItem = (listRef, file) => {
|
||||
const exists = listRef.value.some((i) => i.pending && i.tempFileName === file.tempFileName);
|
||||
if (!exists) {
|
||||
listRef.value = [...listRef.value, { ...file, pending: true }];
|
||||
}
|
||||
};
|
||||
|
||||
const persistCertificate = async (file) => {
|
||||
if (!file?.tempFileName) {
|
||||
throw new Error('آپلود ناقص است؛ نام فایل موقت دریافت نشد');
|
||||
}
|
||||
await certificateApi.create({
|
||||
user: props.userId,
|
||||
tempFileName: file.tempFileName,
|
||||
originalName: file.fileName || file.name,
|
||||
fileName: file.fileName || file.name,
|
||||
mimeType: file.mimeType,
|
||||
title: file.name || 'گواهینامه'
|
||||
});
|
||||
};
|
||||
|
||||
const persistDocument = async (file) => {
|
||||
if (!file?.tempFileName) {
|
||||
throw new Error('آپلود ناقص است؛ نام فایل موقت دریافت نشد');
|
||||
}
|
||||
await documentApi.create({
|
||||
user: props.userId,
|
||||
tempFileName: file.tempFileName,
|
||||
originalName: file.fileName || file.name,
|
||||
fileName: file.fileName || file.name,
|
||||
mimeType: file.mimeType,
|
||||
title: documentTitle.value || file.name || 'سند'
|
||||
});
|
||||
};
|
||||
|
||||
const dropPending = (listRef, tempFileName) => {
|
||||
listRef.value = listRef.value.filter((i) => !(i.pending && i.tempFileName === tempFileName));
|
||||
};
|
||||
|
||||
const onCertificateUploaded = async (file) => {
|
||||
try {
|
||||
await persistCertificate(file);
|
||||
dropPending(certificateItems, file.tempFileName);
|
||||
showSuccess('گواهینامه ثبت شد');
|
||||
await loadFiles();
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
keepPendingItem(certificateItems, file);
|
||||
}
|
||||
};
|
||||
|
||||
const onDocumentUploaded = async (file) => {
|
||||
try {
|
||||
await persistDocument(file);
|
||||
dropPending(documentItems, file.tempFileName);
|
||||
showSuccess('سند ثبت شد');
|
||||
await loadFiles();
|
||||
} catch (err) {
|
||||
showError(err);
|
||||
keepPendingItem(documentItems, file);
|
||||
}
|
||||
};
|
||||
|
||||
const commitCertificates = async () => {
|
||||
if (!pendingCertificates.value.length) return;
|
||||
savingCertificates.value = true;
|
||||
try {
|
||||
for (const file of pendingCertificates.value) {
|
||||
await certificateApi.create({
|
||||
user: props.userId,
|
||||
tempFileName: file.tempFileName,
|
||||
originalName: file.fileName || file.name,
|
||||
fileName: file.fileName || file.name,
|
||||
mimeType: file.mimeType,
|
||||
title: file.name || 'گواهینامه'
|
||||
});
|
||||
await persistCertificate(file);
|
||||
}
|
||||
showSuccess('گواهینامهها با موفقیت ثبت شدند');
|
||||
await loadFiles();
|
||||
@@ -150,14 +218,7 @@ const commitDocuments = async () => {
|
||||
savingDocuments.value = true;
|
||||
try {
|
||||
for (const file of pendingDocuments.value) {
|
||||
await documentApi.create({
|
||||
user: props.userId,
|
||||
tempFileName: file.tempFileName,
|
||||
originalName: file.fileName || file.name,
|
||||
fileName: file.fileName || file.name,
|
||||
mimeType: file.mimeType,
|
||||
title: documentTitle.value || file.name || 'سند'
|
||||
});
|
||||
await persistDocument(file);
|
||||
}
|
||||
documentTitle.value = '';
|
||||
showSuccess('اسناد با موفقیت ثبت شدند');
|
||||
@@ -169,6 +230,13 @@ const commitDocuments = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const commitPending = async () => {
|
||||
await Promise.all([
|
||||
pendingCertificates.value.length ? commitCertificates() : Promise.resolve(),
|
||||
pendingDocuments.value.length ? commitDocuments() : Promise.resolve()
|
||||
]);
|
||||
};
|
||||
|
||||
const onCertificateRemove = async (item) => {
|
||||
if (!item?.id || item.pending) return;
|
||||
try {
|
||||
@@ -198,5 +266,5 @@ watch(
|
||||
|
||||
onMounted(loadFiles);
|
||||
|
||||
defineExpose({ reload: loadFiles });
|
||||
defineExpose({ reload: loadFiles, commitPending });
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user