import React, { type ChangeEvent, type DragEvent, Fragment, type MouseEvent, useRef, useState } from 'react' import { Placeholder } from './Placeholder' import { Column, Divider, Icon, Text } from '../../atoms' import { getGlobalStyle } from '../../../helpers' import { type NotificationProps } from '../../../types/global' import styles from './styles.module.css' interface InputFilesProps { actionCallBack?: boolean reset?: boolean allowedFileTypes?: string[] limit?: number callBack?: () => void removeAllFiles?: () => void removeLastFile?: () => void onChange?: (images: File[], previewImg: PreviewImage[]) => void sendNotification?: (notification: NotificationProps) => void } interface PreviewImage { temPath: string name: string ext: string } /** * InputFiles component to handle file uploads with drag-and-drop support. * @param {InputFilesProps} props - The properties for the component. * @returns {React.FC} */ export const InputFiles: React.FC = ({ actionCallBack = false, limit = 100, allowedFileTypes = [], callBack = () => { }, onChange = () => { }, removeAllFiles = () => { }, removeLastFile = () => { }, sendNotification = () => { } }) => { const [images, setImages] = useState([]) const [dragIn, setDragIn] = useState(false) const [previewImg, setPreviewImg] = useState([]) const fileInputRef = useRef(null) const fileIconMap = { '.png': , // Placeholder, will be replaced dynamically '.svg': , // Placeholder, will be replaced dynamically '.jpg': , // Placeholder, will be replaced dynamically '.jpeg': , // Placeholder, will be replaced dynamically '.docx': DocWord, '.docm': DocWord, '.dotx': DocWord, '.dotm': DocWord, '.xlsx': , '.xlsm': , '.xlsb': , '.xltx': , '.xls': , '.csv': , default: FILE COMUN } const isFileTypeAllowed = (file: File): boolean => { const fileExt = file.name.substring(file.name.lastIndexOf('.')).toLowerCase() return allowedFileTypes.includes(fileExt) } const resetFileInput = (): void => { if (fileInputRef.current != null) { fileInputRef.current.value = '' } } const onFileInputChange = (event: ChangeEvent): void => { const { files } = event.target if (files == null) return const newFiles = Array.from(files).filter(file => { if (!isFileTypeAllowed(file)) { sendNotification({ title: 'Error', backgroundColor: 'warning', description: `Tipo de archivo no permitido: ${file.name}` }) return false } return true }) const totalFilesCount = newFiles.length + images.length if (limit > 0 && totalFilesCount > limit) { sendNotification({ description: `Se permite un máximo de ${limit} ${limit > 1 ? 'archivos' : 'archivo'}.`, title: 'Límite alcanzado', backgroundColor: 'warning' }) resetFileInput() return } if (newFiles.length === 0) { resetFileInput() return } setImages([...images, ...newFiles]) onChange([...images, ...newFiles], [...previewImg]) const newFilesPreview = newFiles.map(file => ({ temPath: URL.createObjectURL(file), name: file.name, ext: file.name.substring(file.name.lastIndexOf('.')) })) setPreviewImg([...previewImg, ...newFilesPreview]) if (actionCallBack) { callBack() } resetFileInput() } const handleRemove = (): void => { setImages([]) setPreviewImg([]) resetFileInput() } const handleDelete = (e: MouseEvent, item: PreviewImage, index: number): void => { e.stopPropagation() const newImages = images.filter((_, i) => i !== index) const previewNewImages = previewImg.filter((_, i) => i !== index) setImages(newImages) setPreviewImg(previewNewImages) resetFileInput() if (newImages.length === 0 && previewNewImages.length === 0) { removeLastFile() } } const handleDrop = (event: DragEvent): void => { event.preventDefault() const files = event.dataTransfer.files const newFiles = Array.from(files).filter(file => { if (!isFileTypeAllowed(file)) { sendNotification({ title: 'Error', backgroundColor: 'warning', description: `Tipo de archivo no permitido: ${file.name}` }) return false } return true }) const totalFilesCount = newFiles.length + images.length if (limit > 0 && totalFilesCount > limit) { sendNotification({ description: `Se permite un máximo de ${limit} ${limit > 1 ? 'archivos' : 'archivo'}.`, title: 'Límite alcanzado', backgroundColor: 'warning' }) resetFileInput() return } setImages([...images, ...newFiles]) onChange([...images, ...newFiles], [...previewImg]) const newFilesPreview = newFiles.map(file => ({ temPath: URL.createObjectURL(file), name: file.name, ext: file.name.substring(file.name.lastIndexOf('.')) })) setPreviewImg([...previewImg, ...newFilesPreview]) resetFileInput() } const handleDrag = (e: DragEvent): void => { e.preventDefault() e.stopPropagation() setDragIn(e.type !== 'dragleave' && e.type !== 'drop') } return (
) => { e.stopPropagation() document.getElementById('dropZone')?.click() }} style={{ borderWidth: !dragIn ? '3px' : '2px', borderColor: dragIn ? getGlobalStyle('--color-neutral-gray-silver') : getGlobalStyle('--color-neutral-gray') }} > {(previewImg.length === 0) && } {previewImg.length > 0 && (
{previewImg.map((x, i) => (
{React.cloneElement(fileIconMap[x.ext] ?? fileIconMap.default, { src: x.temPath })} {x.ext !== '.jpg' && x.ext !== '.png' && {x.name} }
))}
)}
) }