diff --git a/src/app/(dashboard)/jobs/[jobId]/components/job-files-panel.tsx b/src/app/(dashboard)/jobs/[jobId]/components/job-files-panel.tsx index 91d5d36..2b99eeb 100644 --- a/src/app/(dashboard)/jobs/[jobId]/components/job-files-panel.tsx +++ b/src/app/(dashboard)/jobs/[jobId]/components/job-files-panel.tsx @@ -64,23 +64,26 @@ export function JobFilesPanel({ const MAX_FILE_BYTES = 200 * 1024 * 1024; const MAX_BATCH_BYTES = 200 * 1024 * 1024; // proxy cap; one large file fills the whole batch +type UploadPhase = "idle" | "uploading" | "processing"; + function UploadForm({ jobId }: { jobId: string }) { const router = useRouter(); const inputRef = useRef(null); const xhrRef = useRef(null); const [selected, setSelected] = useState([]); - const [uploading, setUploading] = useState(false); + const [phase, setPhase] = useState("idle"); const [progress, setProgress] = useState(0); // 0–100 const totalBytes = selected.reduce((s, f) => s + f.size, 0); const overSize = selected.find((f) => f.size > MAX_FILE_BYTES); const overBatch = totalBytes > MAX_BATCH_BYTES; const blocked = Boolean(overSize) || overBatch; + const busy = phase !== "idle"; function reset() { setSelected([]); setProgress(0); - setUploading(false); + setPhase("idle"); if (inputRef.current) inputRef.current.value = ""; } @@ -91,16 +94,24 @@ function UploadForm({ jobId }: { jobId: string }) { const xhr = new XMLHttpRequest(); xhrRef.current = xhr; - setUploading(true); + setPhase("uploading"); setProgress(0); xhr.upload.addEventListener("progress", (e) => { if (!e.lengthComputable) return; - setProgress(Math.round((e.loaded / e.total) * 100)); + const pct = Math.round((e.loaded / e.total) * 100); + setProgress(pct); + }); + + // The browser has finished pushing bytes; the server is now writing to + // Appwrite. Flip to "processing" so the user sees something is still + // happening (large files take 30-60s to land in Storage). + xhr.upload.addEventListener("load", () => { + setPhase("processing"); + setProgress(100); }); xhr.addEventListener("load", () => { - setUploading(false); let payload: { ok?: boolean; uploaded?: number; error?: string } = {}; try { payload = JSON.parse(xhr.responseText); @@ -113,18 +124,19 @@ function UploadForm({ jobId }: { jobId: string }) { router.refresh(); } else { toast.error(payload.error || `Yükleme başarısız (HTTP ${xhr.status}).`); + setPhase("idle"); setProgress(0); } }); xhr.addEventListener("error", () => { - setUploading(false); + setPhase("idle"); setProgress(0); toast.error("Ağ hatası. Tekrar deneyin."); }); xhr.addEventListener("abort", () => { - setUploading(false); + setPhase("idle"); setProgress(0); toast.message("Yükleme iptal edildi."); }); @@ -157,7 +169,7 @@ function UploadForm({ jobId }: { jobId: string }) { variant="outline" size="sm" onClick={() => inputRef.current?.click()} - disabled={uploading} + disabled={busy} > Dosya seç @@ -181,10 +193,15 @@ function UploadForm({ jobId }: { jobId: string }) { "Tarama (STL/OBJ), görsel veya PDF — max 200MB / dosya" )} - {uploading ? ( + {phase === "uploading" ? ( + ) : phase === "processing" ? ( + ) : (