import { Upload, File, X, FileText } from 'lucide-react' import { useState, useCallback, useEffect, useRef } from 'react' import { Button } from '@/components/ui/button' import { formatFileSize } from '@/features/planes/utils/format-file-size' import { cn } from '@/lib/utils' export interface UploadedFile { id: string // Necesario para React (key) file: File // La fuente de verdad (contiene name, size, type) preview?: string // Opcional: si fueran imágenes } interface FileDropzoneProps { persistentFiles?: Array onFilesChange?: (files: Array) => void acceptedTypes?: string maxFiles?: number title?: string description?: string autoScrollToDropzone?: boolean } export function FileDropzone({ persistentFiles, onFilesChange, acceptedTypes = '.doc,.docx,.pdf', maxFiles = 5, title = 'Arrastra archivos aquí', description = 'o haz clic para seleccionar', autoScrollToDropzone = false, }: FileDropzoneProps) { const [isDragging, setIsDragging] = useState(false) const [files, setFiles] = useState>(persistentFiles ?? []) const onFilesChangeRef = useRef(onFilesChange) const bottomRef = useRef(null) const prevFilesLengthRef = useRef(files.length) const addFiles = useCallback( (incomingFiles: Array) => { console.log( 'incoming files:', incomingFiles.map((file) => file.name), ) setFiles((previousFiles) => { console.log( 'previous files', previousFiles.map((f) => f.file.name), ) // Evitar duplicados por nombre (comprobación global en los archivos existentes) const existingFileNames = new Set( previousFiles.map((uploaded) => uploaded.file.name), ) const uniqueNewFiles = incomingFiles.filter( (incomingFile) => !existingFileNames.has(incomingFile.name), ) // Convertir archivos a objetos con ID único para manejo en React const filesToUpload: Array = uniqueNewFiles.map( (incomingFile) => ({ id: typeof crypto !== 'undefined' && 'randomUUID' in crypto ? (crypto as any).randomUUID() : `file-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, file: incomingFile, }), ) // Calcular espacio disponible respetando el límite máximo const room = Math.max(0, maxFiles - previousFiles.length) const nextFiles = [ ...previousFiles, ...filesToUpload.slice(0, room), ].slice(0, maxFiles) return nextFiles }) }, [maxFiles], ) // Manejador para cuando se arrastran archivos sobre la zona const handleDragOver = useCallback((e: React.DragEvent) => { e.preventDefault() setIsDragging(true) }, []) // Manejador para cuando se sale de la zona de arrastre const handleDragLeave = useCallback((e: React.DragEvent) => { e.preventDefault() setIsDragging(false) }, []) // Manejador para cuando se sueltan los archivos const handleDrop = useCallback( (e: React.DragEvent) => { e.preventDefault() setIsDragging(false) const droppedFiles = Array.from(e.dataTransfer.files) addFiles(droppedFiles) }, [addFiles], ) // Manejador para la selección de archivos mediante el input nativo const handleFileInput = useCallback( (e: React.ChangeEvent) => { if (e.target.files) { const selectedFiles = Array.from(e.target.files) addFiles(selectedFiles) // Corrección de bug: Limpiar el valor para permitir seleccionar el mismo archivo nuevamente si fue eliminado e.target.value = '' } }, [addFiles], ) // Función para eliminar un archivo específico por su ID const removeFile = useCallback((fileId: string) => { setFiles((previousFiles) => { console.log( 'previous files', previousFiles.map((f) => f.file.name), ) const remainingFiles = previousFiles.filter( (uploadedFile) => uploadedFile.id !== fileId, ) return remainingFiles }) }, []) // Mantener la referencia actualizada de la función callback externa para evitar loops en useEffect useEffect(() => { onFilesChangeRef.current = onFilesChange }, [onFilesChange]) // Notificar al componente padre cuando cambia la lista de archivos useEffect(() => { if (onFilesChangeRef.current) onFilesChangeRef.current(files) }, [files]) // Scroll automático hacia abajo solo cuando se pasa de 0 a 1 o más archivos useEffect(() => { if ( autoScrollToDropzone && prevFilesLengthRef.current === 0 && files.length > 0 ) { // Usar un pequeño timeout para asegurar que el renderizado se complete const timer = setTimeout(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start', }) }, 100) // Actualizar la referencia prevFilesLengthRef.current = files.length return () => clearTimeout(timer) } // Mantener sincronizada la referencia en otros casos prevFilesLengthRef.current = files.length }, [files.length, autoScrollToDropzone]) // Determinar el icono a mostrar según la extensión del archivo const getFileIcon = (type: string) => { switch (type.toLowerCase()) { case 'pdf': return case 'doc': case 'docx': return default: return } } return (
{/* Elemento invisible para referencia de scroll */}
{/* Área principal de dropzone */}
= maxFiles} />
{/* Lista de archivos subidos (Orden inverso: más recientes primero) */}
{files.length > 0 && (
{[...files].reverse().map((uploadedFile) => (
{getFileIcon(uploadedFile.file.type)}

{uploadedFile.file.name}

{formatFileSize(uploadedFile.file.size)}

))}
)}
) }