// File: components/custom/file-upload-zone.tsx "use client"; import React, { useCallback, useState } from "react"; import { UploadCloud, File as FileIcon, X, AlertTriangle, CheckCircle } from "lucide-react"; import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { ScrollArea } from "@/components/ui/scroll-area"; export interface FileWithMeta extends File { preview?: string; validationError?: string; } interface FileUploadZoneProps { /** Callback เมื่อไฟล์มีการเปลี่ยนแปลง */ onFilesChanged: (files: FileWithMeta[]) => void; /** ประเภทไฟล์ที่ยอมรับ (เช่น .pdf, .dwg) */ accept?: string[]; /** ขนาดไฟล์สูงสุด (Bytes) Default: 50MB */ maxSize?: number; /** อนุญาตให้อัปโหลดหลายไฟล์หรือไม่ */ multiple?: boolean; /** ไฟล์ที่มีอยู่เดิม (ถ้ามี) */ initialFiles?: FileWithMeta[]; className?: string; } /** * Helper: แปลง Bytes เป็นหน่วยที่อ่านง่าย */ const formatBytes = (bytes: number, decimals = 2) => { if (!+bytes) return "0 Bytes"; const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ["Bytes", "KB", "MB", "GB", "TB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; }; /** * FileUploadZone Component * รองรับ Drag & Drop, Validation และแสดงรายการไฟล์ */ export function FileUploadZone({ onFilesChanged, accept = [".pdf", ".dwg", ".docx", ".xlsx", ".zip"], maxSize = 50 * 1024 * 1024, // 50MB Default multiple = true, initialFiles = [], className, }: FileUploadZoneProps) { const [files, setFiles] = useState(initialFiles); const [isDragging, setIsDragging] = useState(false); // ตรวจสอบไฟล์ const validateFile = (file: File): string | undefined => { // 1. Check Size if (file.size > maxSize) { return `ขนาดไฟล์เกินกำหนด (${formatBytes(maxSize)})`; } // 2. Check Type (Extension based validation for simplicity on client) const fileExtension = "." + file.name.split(".").pop()?.toLowerCase(); if (accept.length > 0 && !accept.includes(fileExtension)) { return `ประเภทไฟล์ไม่รองรับ (อนุญาต: ${accept.join(", ")})`; } return undefined; }; const handleFileSelect = useCallback( (newFiles: File[]) => { const processedFiles: FileWithMeta[] = newFiles.map((file) => { const error = validateFile(file); // สร้าง Object ใหม่เพื่อไม่ให้กระทบ File object เดิม const fileWithMeta = new File([file], file.name, { type: file.type } as any) as FileWithMeta; fileWithMeta.validationError = error; return fileWithMeta; }); setFiles((prev) => { const updated = multiple ? [...prev, ...processedFiles] : processedFiles; onFilesChanged(updated); return updated; }); }, [maxSize, accept, multiple, onFilesChanged] ); // Drag Events const onDragOver = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(true); }; const onDragLeave = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); }; const onDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragging(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { handleFileSelect(Array.from(e.dataTransfer.files)); } }; const removeFile = (indexToRemove: number) => { setFiles((prev) => { const updated = prev.filter((_, index) => index !== indexToRemove); onFilesChanged(updated); return updated; }); }; return (
{/* Drop Zone */}
document.getElementById("file-input")?.click()} > { if (e.target.files) handleFileSelect(Array.from(e.target.files)); }} />

คลิกเพื่อเลือกไฟล์ หรือ ลากไฟล์มาวางที่นี่

รองรับ: {accept.join(", ")} (สูงสุด {formatBytes(maxSize)})

{/* File List */} {files.length > 0 && (
{files.map((file, index) => (

{file.name}

{formatBytes(file.size)} {file.validationError ? ( {file.validationError} ) : ( Ready )}
))}
)}
); }