fix: persist uploads on Render disk and show missing file notice

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
EENE Dashboard
2026-06-05 23:01:00 +09:00
parent 4960fe7352
commit c93df1ae40
3 changed files with 62 additions and 3 deletions

View File

@@ -48,6 +48,9 @@ export function ExcelPreview({ fileId, fileName }: ExcelPreviewProps) {
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-3 p-6 text-center">
<p className="text-lg text-white/60">{error}</p>
<p className="text-sm text-white/40">
. .
</p>
<a
href={fileDownloadUrl(fileId)}
className="rounded bg-white/10 px-4 py-2 text-sm font-bold text-white hover:bg-white/20"

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { ExcelPreview } from './ExcelPreview';
import { fileViewUrl } from '../../lib/apiClient';
import { fileDownloadUrl, fileViewUrl } from '../../lib/apiClient';
import { openLinkOnRightMonitor } from '../../lib/dualMonitor';
import { fileDisplayName, isExcelFile, isVideoFile } from '../../lib/fileDisplay';
import type { FileRecord, MilestoneLink } from '../../types';
@@ -12,10 +12,32 @@ interface ResultPreviewProps {
hasSelectedStage: boolean;
}
function FileMissingNotice({ label, fileId }: { label: string; fileId: string }) {
return (
<div className="flex max-w-lg flex-col items-center gap-3 px-6 text-center">
<p className="text-xl font-bold text-white/70">{label}</p>
<p className="text-base leading-relaxed text-white/45">
.
<br />
· .
<br />
.
</p>
<a
href={fileDownloadUrl(fileId)}
className="rounded-lg bg-white/10 px-4 py-2 text-sm font-bold text-white/80 hover:bg-white/20"
>
</a>
</div>
);
}
export function ResultPreview({ files, links, hasSelectedStage }: ResultPreviewProps) {
const [fileId, setFileId] = useState<string | null>(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 <ExcelPreview fileId={activeFile.id} fileName={label} />;
}
if (fileMissing) {
return <FileMissingNotice label={label} fileId={activeFile.id} />;
}
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 (
<video src={src} controls className="max-h-full max-w-full" title={label} />
<video
src={src}
controls
className="max-h-full max-w-full"
title={label}
onError={() => setFileMissing(true)}
/>
);
}
return (