From b6ac71d598a0c6200a1197b9107704270332f2cd Mon Sep 17 00:00:00 2001 From: Anuarbek Zhakhangir Date: Wed, 30 Jul 2025 17:35:38 +0900 Subject: [PATCH] feat: add Fileupload component and integrate it into the exhibition form --- src/components/form/Fileupload.tsx | 250 +++++++++++++++++++++++++++++ src/pages/forms/exhibition.tsx | 9 ++ 2 files changed, 259 insertions(+) create mode 100644 src/components/form/Fileupload.tsx diff --git a/src/components/form/Fileupload.tsx b/src/components/form/Fileupload.tsx new file mode 100644 index 0000000..17594fb --- /dev/null +++ b/src/components/form/Fileupload.tsx @@ -0,0 +1,250 @@ +import { gothampro } from "@/utils/fonts"; +import React, { useState, useRef, DragEvent, ChangeEvent } from "react"; + +interface FileUploadProps { + label?: string; + onFileSelect?: (files: File[]) => void; + acceptedTypes?: string[]; + maxFileSize?: number; // in MB + multiple?: boolean; + disabled?: boolean; + placeholder?: string; + id?: string; + name?: string; + required?: boolean; +} + +function Fileupload({ + label, + onFileSelect, + acceptedTypes = ["image/*", "application/pdf", ".doc", ".docx"], + maxFileSize = 10, + multiple = false, + disabled = false, + placeholder = "Файл в формате jpg или png до N мб", + id, + name, + required = false, +}: FileUploadProps) { + const [dragActive, setDragActive] = useState(false); + const [selectedFiles, setSelectedFiles] = useState([]); + const [errors, setErrors] = useState([]); + const fileInputRef = useRef(null); + + const validateFiles = ( + files: File[] + ): { valid: File[]; errors: string[] } => { + const validFiles: File[] = []; + const fileErrors: string[] = []; + + files.forEach((file) => { + // Check file size + if (file.size > maxFileSize * 1024 * 1024) { + fileErrors.push(`${file.name} is too large (max ${maxFileSize}MB)`); + return; + } + + // Check file type + const isValidType = acceptedTypes.some((type) => { + if (type.includes("*")) { + return file.type.startsWith(type.split("*")[0]); + } + return file.type === type || file.name.toLowerCase().endsWith(type); + }); + + if (!isValidType) { + fileErrors.push(`${file.name} is not a supported file type`); + return; + } + + validFiles.push(file); + }); + + return { valid: validFiles, errors: fileErrors }; + }; + + const handleFiles = (files: FileList | null) => { + if (!files) return; + + const fileArray = Array.from(files); + const { valid, errors } = validateFiles(fileArray); + + setErrors(errors); + + if (valid.length > 0) { + const newFiles = multiple ? [...selectedFiles, ...valid] : valid; + setSelectedFiles(newFiles); + onFileSelect?.(newFiles); + } + }; + + const handleDrag = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.type === "dragenter" || e.type === "dragover") { + setDragActive(true); + } else if (e.type === "dragleave") { + setDragActive(false); + } + }; + + const handleDrop = (e: DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + + if (disabled) return; + + if (e.dataTransfer.files) { + handleFiles(e.dataTransfer.files); + } + }; + + const handleInputChange = (e: ChangeEvent) => { + handleFiles(e.target.files); + }; + + const removeFile = (index: number) => { + const updatedFiles = selectedFiles.filter((_, i) => i !== index); + setSelectedFiles(updatedFiles); + onFileSelect?.(updatedFiles); + }; + + const openFileDialog = () => { + if (!disabled && fileInputRef.current) { + fileInputRef.current.click(); + } + }; + + return ( +
+ {label && ( + + )} + + {/* Upload Area */} +
+ + + {/* Upload Button */} + + + {/* File Display Area */} +
+ {selectedFiles.length > 0 ? ( +
+ + {selectedFiles[0].name} + + +
+ ) : ( + + {placeholder.replace("N", maxFileSize.toString())} + + )} +
+
+ + {/* Error Messages */} + {errors.length > 0 && ( +
+ {errors.map((error, index) => ( +
{error}
+ ))} +
+ )} + + {/* Additional Files (if multiple) */} + {multiple && selectedFiles.length > 1 && ( +
+ {selectedFiles.slice(1).map((file, index) => ( +
+ {file.name} + +
+ ))} +
+ )} +
+ ); +} + +export default Fileupload; diff --git a/src/pages/forms/exhibition.tsx b/src/pages/forms/exhibition.tsx index 03ccdc8..122e6ce 100644 --- a/src/pages/forms/exhibition.tsx +++ b/src/pages/forms/exhibition.tsx @@ -1,4 +1,5 @@ import Checkbox from "@/components/form/Checkbox"; +import Fileupload from "@/components/form/Fileupload"; import Input from "@/components/form/Input"; import Radio from "@/components/form/Radio"; import Select from "@/components/form/Select"; @@ -78,6 +79,14 @@ function ExhibtionFormPage() { direction="vertical" name="termsAgreement" /> + + console.log("Selected files:", files)} + acceptedTypes={["image/*", "application/pdf"]} + maxFileSize={5} + multiple={true} + /> ); }