testupload / frontend /hooks /useFileUpload.ts
Twan07's picture
Update frontend/hooks/useFileUpload.ts
0195b42 verified
raw
history blame
3.23 kB
import { useCallback, useMemo, useState } from "react";
/* ================= TYPES ================= */
export interface FileItem {
id: string;
file: File;
path: string;
progress: number;
status: "idle" | "uploading" | "done" | "error";
url?: string;
error?: string;
}
/* ================= HOOK ================= */
export function useFileUpload() {
const [files, setFiles] = useState<FileItem[]>([]);
const [isUploading, setIsUploading] = useState(false);
/* ---------- ADD FILES ---------- */
const addFiles = useCallback((input: FileList | File[]) => {
const list = Array.from(input).map<FileItem>((file) => ({
id: crypto.randomUUID(),
file,
path: file.name, // mặc định
progress: 0,
status: "idle",
}));
setFiles((prev) => [...prev, ...list]);
}, []);
/* ---------- REMOVE FILE ---------- */
const removeFile = useCallback((id: string) => {
setFiles((prev) => prev.filter((f) => f.id !== id));
}, []);
/* ---------- UPDATE PATH ---------- */
const updateFilePath = useCallback((id: string, path: string) => {
setFiles((prev) =>
prev.map((f) =>
f.id === id ? { ...f, path } : f
)
);
}, []);
/* ---------- UPLOAD SINGLE ---------- */
const uploadOne = useCallback(async (item: FileItem) => {
const form = new FormData();
form.append("file", item.file);
form.append("path", item.path);
return new Promise<void>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("POST", "/api/upload");
xhr.upload.onprogress = (e) => {
if (!e.lengthComputable) return;
const percent = Math.round((e.loaded / e.total) * 100);
setFiles((prev) =>
prev.map((f) =>
f.id === item.id
? { ...f, progress: percent, status: "uploading" }
: f
)
);
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
const res = JSON.parse(xhr.responseText);
setFiles((prev) =>
prev.map((f) =>
f.id === item.id
? {
...f,
progress: 100,
status: "done",
url: res.url,
}
: f
)
);
resolve();
} else {
reject(new Error("Upload failed"));
}
};
xhr.onerror = () => reject(new Error("Network error"));
xhr.send(form);
});
}, []);
/* ---------- START UPLOAD (App.tsx dùng) ---------- */
const startUpload = useCallback(async () => {
if (isUploading) return;
setIsUploading(true);
try {
for (const f of files) {
if (f.status === "idle") {
await uploadOne(f);
}
}
} finally {
setIsUploading(false);
}
}, [files, uploadOne, isUploading]);
/* ---------- RETURN (KHỚP App.tsx) ---------- */
return {
files, // ✅ App.tsx dùng
isUploading, // ✅ App.tsx dùng
addFiles,
removeFile, // ✅ App.tsx dùng
updateFilePath, // ✅ App.tsx dùng
startUpload, // ✅ App.tsx dùng
};
}