"use client";
import { useActionState, useEffect, useRef, useState, useTransition } from "react";
import { useRouter } from "next/navigation";
import { Download, FileText, ImageIcon, Layers, Loader2, Trash2, Upload } from "lucide-react";
import { toast } from "sonner";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Progress } from "@/components/ui/progress";
import { deleteJobFileAction } from "@/lib/appwrite/job-file-actions";
import {
initialJobFileActionState,
JOB_FILE_KIND_LABELS,
} from "@/lib/appwrite/job-file-types";
import type { JobFileWithUrl } from "@/lib/appwrite/job-file-queries";
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
}
function kindIcon(kind: string) {
if (kind === "image") return ;
if (kind === "scan") return ;
return ;
}
export function JobFilesPanel({
jobId,
files,
}: {
jobId: string;
files: JobFileWithUrl[];
}) {
return (
{files.length === 0 ? (
Henüz dosya yok.
) : (
)}
);
}
const MAX_FILE_BYTES = 200 * 1024 * 1024;
const MAX_BATCH_BYTES = 200 * 1024 * 1024; // proxy cap; one large file fills the whole batch
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 [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;
function reset() {
setSelected([]);
setProgress(0);
setUploading(false);
if (inputRef.current) inputRef.current.value = "";
}
function startUpload() {
if (selected.length === 0 || blocked) return;
const formData = new FormData();
for (const f of selected) formData.append("files", f);
const xhr = new XMLHttpRequest();
xhrRef.current = xhr;
setUploading(true);
setProgress(0);
xhr.upload.addEventListener("progress", (e) => {
if (!e.lengthComputable) return;
setProgress(Math.round((e.loaded / e.total) * 100));
});
xhr.addEventListener("load", () => {
setUploading(false);
let payload: { ok?: boolean; uploaded?: number; error?: string } = {};
try {
payload = JSON.parse(xhr.responseText);
} catch {
/* non-JSON response */
}
if (xhr.status >= 200 && xhr.status < 300 && payload.ok) {
toast.success(`${payload.uploaded ?? selected.length} dosya yüklendi.`);
reset();
router.refresh();
} else {
toast.error(payload.error || `Yükleme başarısız (HTTP ${xhr.status}).`);
setProgress(0);
}
});
xhr.addEventListener("error", () => {
setUploading(false);
setProgress(0);
toast.error("Ağ hatası. Tekrar deneyin.");
});
xhr.addEventListener("abort", () => {
setUploading(false);
setProgress(0);
toast.message("Yükleme iptal edildi.");
});
xhr.open("POST", `/api/jobs/${jobId}/files`);
xhr.send(formData);
}
function cancelUpload() {
xhrRef.current?.abort();
}
return (
{
const list = e.target.files ? Array.from(e.target.files) : [];
setSelected(list);
setProgress(0);
}}
/>
{selected.length > 0 ? (
overSize ? (
{overSize.name} 200MB'tan büyük (her dosya maksimum 200MB).
) : overBatch ? (
Toplam {formatSize(totalBytes)} — 200MB sınırını aşıyor. Daha az dosya seçin.
) : (
<>
{selected.length} dosya seçildi ({formatSize(totalBytes)})
>
)
) : (
"Tarama (STL/OBJ), görsel veya PDF — max 200MB / dosya"
)}
{uploading ? (
) : (
)}
{uploading && (
)}
);
}
function FileRow({ file }: { file: JobFileWithUrl }) {
const [state, action, pending] = useActionState(
deleteJobFileAction,
initialJobFileActionState,
);
const [, startTransition] = useTransition();
const [open, setOpen] = useState(false);
useEffect(() => {
if (state.ok) {
toast.success("Dosya silindi.");
setOpen(false);
} else if (state.error) {
toast.error(state.error);
}
}, [state]);
return (
{kindIcon(file.kind)}
{file.name}
{JOB_FILE_KIND_LABELS[file.kind] ?? file.kind} · {formatSize(file.size)}
{JOB_FILE_KIND_LABELS[file.kind] ?? file.kind}
);
}