Spaces:
Sleeping
Sleeping
| import { useCallback, useState } from "react"; | |
| import { FileItem, UploadStatus } from "../types"; | |
| export function useFileUpload() { | |
| const [files, setFiles] = useState<FileItem[]>([]); | |
| const [isUploading, setIsUploading] = useState(false); | |
| // ✅ App.tsx + FileUploader truyền FileItem[] | |
| const addFiles = useCallback((items: FileItem[]) => { | |
| setFiles((prev) => [...prev, ...items]); | |
| }, []); | |
| const removeFile = useCallback((id: string) => { | |
| setFiles((prev) => prev.filter((f) => f.id !== id)); | |
| }, []); | |
| const updateFilePath = useCallback((id: string, path: string) => { | |
| setFiles((prev) => | |
| prev.map((f) => (f.id === id ? { ...f, path } : f)) | |
| ); | |
| }, []); | |
| const uploadOne = useCallback((item: FileItem) => { | |
| return new Promise<void>((resolve, reject) => { | |
| const form = new FormData(); | |
| form.append("file", item.file); | |
| form.append("path", item.path); | |
| 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: UploadStatus.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: UploadStatus.SUCCESS, | |
| url: res.url, | |
| } | |
| : f | |
| ) | |
| ); | |
| resolve(); | |
| } else { | |
| setFiles((prev) => | |
| prev.map((f) => | |
| f.id === item.id | |
| ? { ...f, status: UploadStatus.ERROR } | |
| : f | |
| ) | |
| ); | |
| reject(new Error("Upload failed")); | |
| } | |
| }; | |
| xhr.onerror = () => { | |
| setFiles((prev) => | |
| prev.map((f) => | |
| f.id === item.id | |
| ? { ...f, status: UploadStatus.ERROR } | |
| : f | |
| ) | |
| ); | |
| reject(new Error("Network error")); | |
| }; | |
| xhr.send(form); | |
| }); | |
| }, []); | |
| const startUpload = useCallback(async () => { | |
| if (isUploading) return; | |
| setIsUploading(true); | |
| try { | |
| for (const f of files) { | |
| if ( | |
| f.status === UploadStatus.IDLE || | |
| f.status === UploadStatus.ERROR | |
| ) { | |
| await uploadOne(f); | |
| } | |
| } | |
| } finally { | |
| setIsUploading(false); | |
| } | |
| }, [files, uploadOne, isUploading]); | |
| return { | |
| files, | |
| isUploading, | |
| addFiles, | |
| removeFile, | |
| updateFilePath, | |
| startUpload, | |
| }; | |
| } | |