From c93df1ae40707465150c2f2647d0a415a1471979 Mon Sep 17 00:00:00 2001 From: EENE Dashboard Date: Fri, 5 Jun 2026 23:01:00 +0900 Subject: [PATCH] fix: persist uploads on Render disk and show missing file notice Co-authored-by: Cursor --- .../src/components/detail/ExcelPreview.tsx | 3 + .../src/components/detail/ResultPreview.tsx | 55 ++++++++++++++++++- render.yaml | 7 ++- 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/detail/ExcelPreview.tsx b/frontend/src/components/detail/ExcelPreview.tsx index 32b5fa1..a447ba9 100644 --- a/frontend/src/components/detail/ExcelPreview.tsx +++ b/frontend/src/components/detail/ExcelPreview.tsx @@ -48,6 +48,9 @@ export function ExcelPreview({ fileId, fileName }: ExcelPreviewProps) { return (

{error}

+

+ 서버 재배포 등으로 파일이 삭제되었을 수 있습니다. 단계 수정에서 다시 첨부해 주세요. +

+

{label}

+

+ 첨부 파일을 서버에서 찾을 수 없습니다. +
+ 코드 배포·서버 재시작 시 파일이 삭제될 수 있습니다. +
+ 단계 수정에서 같은 파일을 다시 첨부해 주세요. +

+
+ 다운로드 재시도 + +
+ ); +} + export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewProps) { const [fileId, setFileId] = useState(null); const [zoom, setZoom] = useState(1); const [fullscreen, setFullscreen] = useState(false); + const [fileMissing, setFileMissing] = useState(false); useEffect(() => { if (files.length > 0) { @@ -27,6 +49,25 @@ export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewP }, [files]); const activeFile = fileId ? files.find((f) => f.id === fileId) ?? null : null; + + useEffect(() => { + if (!activeFile || isExcelFile(activeFile)) { + setFileMissing(false); + return; + } + let cancelled = false; + setFileMissing(false); + fetch(fileViewUrl(activeFile.id), { method: 'HEAD' }) + .then((res) => { + if (!cancelled) setFileMissing(!res.ok); + }) + .catch(() => { + if (!cancelled) setFileMissing(true); + }); + return () => { + cancelled = true; + }; + }, [activeFile?.id, activeFile?.mimetype]); const fileIndex = activeFile ? files.findIndex((f) => f.id === activeFile.id) : -1; const isImage = activeFile?.mimetype.includes('image') ?? false; const isVideo = activeFile ? isVideoFile(activeFile) : false; @@ -54,6 +95,9 @@ export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewP if (isExcel) { return ; } + if (fileMissing) { + return ; + } const src = fileViewUrl(activeFile.id); if (isImage) { return ( @@ -63,12 +107,19 @@ export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewP className="max-h-full max-w-full object-contain transition-transform duration-150" style={{ transform: `scale(${zoom})` }} draggable={false} + onError={() => setFileMissing(true)} /> ); } if (isVideo) { return ( -