/** * XMLHttpRequest를 사용하여 파일 업로드 진행도 추적 */ export interface UploadProgressCallback { onProgress: (fileIndex: number, progress: number) => void; onFileComplete: (fileIndex: number) => void; onFileError: (fileIndex: number, error: string) => void; } export interface UploadFilesWithProgressOptions { uploadId: string; userId: string; files: File[]; callbacks: UploadProgressCallback; } export interface UploadResult { success: boolean; uploadedCount?: number; error?: string; } /** * 진행도 추적을 지원하는 파일 업로드 함수 */ export async function uploadFilesWithProgress({ uploadId, userId, files, callbacks, }: UploadFilesWithProgressOptions): Promise { return new Promise((resolve) => { const formData = new FormData(); formData.append("uploadId", uploadId); formData.append("userId", userId); formData.append("fileCount", String(files.length)); files.forEach((file, index) => { formData.append(`file_${index}`, file); }); const xhr = new XMLHttpRequest(); // 전체 업로드 진행도 (단순화: 전체 진행도를 각 파일에 분배) xhr.upload.addEventListener("progress", (event) => { if (event.lengthComputable) { const totalProgress = (event.loaded / event.total) * 100; // 현재 업로드 중인 파일 인덱스 추정 const filesCompleted = Math.floor((totalProgress / 100) * files.length); const currentFileIndex = Math.min(filesCompleted, files.length - 1); // 각 파일별 진행도 계산 files.forEach((_, index) => { if (index < filesCompleted) { callbacks.onProgress(index, 100); callbacks.onFileComplete(index); } else if (index === currentFileIndex) { const fileProgress = ((totalProgress / 100) * files.length - filesCompleted) * 100; callbacks.onProgress(index, Math.min(fileProgress, 99)); } else { callbacks.onProgress(index, 0); } }); } }); xhr.addEventListener("load", () => { if (xhr.status >= 200 && xhr.status < 300) { try { const response = JSON.parse(xhr.responseText); // 모든 파일 완료 처리 files.forEach((_, index) => { callbacks.onProgress(index, 100); callbacks.onFileComplete(index); }); resolve(response); } catch (error) { const errorMsg = "응답 파싱 실패"; files.forEach((_, index) => { callbacks.onFileError(index, errorMsg); }); resolve({ success: false, error: errorMsg, }); } } else { const errorMsg = `업로드 실패: ${xhr.status} ${xhr.statusText}`; files.forEach((_, index) => { callbacks.onFileError(index, errorMsg); }); resolve({ success: false, error: errorMsg, }); } }); xhr.addEventListener("error", () => { const errorMsg = "네트워크 오류"; files.forEach((_, index) => { callbacks.onFileError(index, errorMsg); }); resolve({ success: false, error: errorMsg, }); }); xhr.addEventListener("abort", () => { const errorMsg = "업로드가 취소되었습니다"; files.forEach((_, index) => { callbacks.onFileError(index, errorMsg); }); resolve({ success: false, error: errorMsg, }); }); xhr.open("POST", "/api/dolce/upload-files"); xhr.send(formData); }); }