test-crud
Version:
es una prueba acerca de como publicar un package name
238 lines (219 loc) • 7.05 kB
JSX
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useFormikContext } from 'formik'
import { useDropzone } from 'react-dropzone'
import {
Box,
Typography,
List,
ListItem,
ListItemText,
IconButton,
FormHelperText,
} from '@mui/material'
import DeleteIcon from '@mui/icons-material/Delete'
import ChangeCircleIcon from '@mui/icons-material/ChangeCircle'
import CloudUploadIcon from '@mui/icons-material/CloudUpload'
const EXTENSION_MIME_MAP = {
PDF: 'application/pdf',
DOC: 'application/msword',
DOCX: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
TXT: 'text/plain',
PNG: 'image/png',
JPG: 'image/jpeg',
JPEG: 'image/jpeg',
XLS: 'application/vnd.ms-excel',
XLSX: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}
function AppDropBox({
name,
label = '',
acceptedFormats = ['PDF'],
multiple = true,
showIcon = true,
fileInfo = {},
editable = true,
}) {
const { setFieldValue, setFieldTouched, errors, touched, values } =
useFormikContext()
const [fileExist, setFileExist] = useState(false) // Para controlar si está el archivo precargado
// Archivos actuales en Formik
const files = values[name] || []
// 🔑 Definir si mostrar archivo precargado o los subidos
const showPreloadedFile = fileExist && fileInfo?.primaryLabel
const showUploadedFiles = files.length > 0
// Al cargar el componente, si hay archivo precargado, activar fileExist
useEffect(() => {
if (fileInfo?.primaryLabel) setFileExist(true)
else setFileExist(false)
}, [fileInfo])
// Mapear extensiones a MIME types
const acceptedMimes = useMemo(() => {
return acceptedFormats
.map((ext) => EXTENSION_MIME_MAP[ext.toUpperCase()])
.filter(Boolean)
}, [acceptedFormats])
// MIME types para Dropzone
const acceptedMimeObj = useMemo(() => {
return acceptedMimes.reduce((acc, mime) => {
acc[mime] = []
return acc
}, {})
}, [acceptedMimes])
// Verificar duplicados
const isDuplicate = (newFile) => {
return files.some(
(file) => file.name === newFile.name && file.size === newFile.size
)
}
// Añadir o reemplazar archivos
const onDrop = useCallback(
(acceptedFiles, indexToReplace = null) => {
setFieldTouched(name, true)
const filteredFiles = acceptedFiles.filter(
(file) => acceptedMimes.includes(file.type) && !isDuplicate(file)
)
if (filteredFiles.length === 0) return
// Al subir archivo nuevo, quitar precargado
if (fileExist) setFileExist(false)
if (indexToReplace !== null) {
const newFiles = [...files]
newFiles[indexToReplace] = filteredFiles[0]
setFieldValue(name, newFiles)
} else {
setFieldValue(
name,
multiple ? [...files, ...filteredFiles] : filteredFiles.slice(0, 1)
)
}
},
[
name,
setFieldTouched,
setFieldValue,
files,
multiple,
acceptedMimes,
fileExist,
]
)
// Eliminar archivo
const handleRemoveFile = (index) => {
const newFiles = files.filter((_, i) => i !== index)
setFieldValue(name, newFiles)
}
// Configuración Dropzone
const { getRootProps, getInputProps } = useDropzone({
onDrop: (acceptedFiles) => onDrop(acceptedFiles),
accept: acceptedMimeObj,
multiple,
})
return (
<div>
<div className="dropbox">
<Typography variant="h6">{label}</Typography>
{/* Solo mostrar dropzone si no hay archivos ni archivo precargado */}
{!showPreloadedFile && !showUploadedFiles && (
<Box
{...getRootProps()}
sx={{
border: '2px dashed #aaa',
cursor: 'pointer',
borderRadius: 2,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
minHeight: '40px',
width: '100%',
textAlign: 'center',
}}
>
<input {...getInputProps()} />
{showIcon && (
<CloudUploadIcon
fontSize="small"
color="action"
sx={{ marginRight: 1 }}
/>
)}
<Typography variant="body2" sx={{ lineHeight: 1 }}>
Selecciona o arrastra un archivo
</Typography>
</Box>
)}
</div>
{/* Mostrar archivos */}
<List sx={{ padding: 0, margin: 0 }}>
{showPreloadedFile ? (
<ListItem>
<ListItemText
primary={fileInfo.primaryLabel}
secondary={fileInfo.secondaryLabel}
/>
{/* Botones adicionales */}
{fileInfo?.buttonProps?.map((btn, index) => (
<span key={index}>{btn.button}</span>
))}
{editable && (
<>
<input
type="file"
accept={acceptedMimes.join(',')}
style={{ display: 'none' }}
id="file-input-reemplazo"
onChange={(event) => {
if (event.target.files.length > 0) {
onDrop([event.target.files[0]])
}
}}
/>
<label htmlFor="file-input-reemplazo">
<IconButton edge="end" component="span" title="Reemplazar">
<ChangeCircleIcon color="primary" />
</IconButton>
</label>
</>
)}
</ListItem>
) : showUploadedFiles ? (
files.map((file, index) => (
<ListItem key={index}>
<ListItemText
primary={file.name}
secondary={`${(file.size / 1024).toFixed(2)} KB`}
/>
<input
type="file"
accept={acceptedMimes.join(',')}
style={{ display: 'none' }}
id={`file-input-${index}`}
onChange={(event) => {
if (event.target.files.length > 0) {
onDrop([event.target.files[0]], index)
}
}}
/>
<label htmlFor={`file-input-${index}`}>
<IconButton edge="end" component="span" title="Reemplazar">
<ChangeCircleIcon color="primary" />
</IconButton>
</label>
{/* Eliminar */}
<IconButton
edge="end"
onClick={() => handleRemoveFile(index)}
title="Eliminar"
>
<DeleteIcon color="error" />
</IconButton>
</ListItem>
))
) : null}
</List>
{touched[name] && errors[name] && (
<FormHelperText error>{errors[name]}</FormHelperText>
)}
</div>
)
}
export default AppDropBox