Ya perdí el hilo de los cambios pero ahi van.
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
Modal, Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||
FormControl, InputLabel, Select, MenuItem, RadioGroup, FormControlLabel, Radio, InputAdornment
|
||||
} from '@mui/material';
|
||||
import type { NotaCreditoDebitoDto } from '../../../models/dtos/Contables/NotaCreditoDebitoDto';
|
||||
import type { CreateNotaDto } from '../../../models/dtos/Contables/CreateNotaDto';
|
||||
import type { UpdateNotaDto } from '../../../models/dtos/Contables/UpdateNotaDto';
|
||||
import type { EmpresaDto } from '../../../models/dtos/Distribucion/EmpresaDto';
|
||||
import type { DistribuidorDto } from '../../../models/dtos/Distribucion/DistribuidorDto';
|
||||
import type { CanillaDto } from '../../../models/dtos/Distribucion/CanillaDto'; // Para el dropdown
|
||||
import empresaService from '../../../services/Distribucion/empresaService';
|
||||
import distribuidorService from '../../../services/Distribucion/distribuidorService';
|
||||
import canillaService from '../../../services/Distribucion/canillaService'; // Para cargar canillitas
|
||||
|
||||
const modalStyle = { /* ... (mismo estilo) ... */
|
||||
position: 'absolute' as 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: { xs: '90%', sm: 550 },
|
||||
bgcolor: 'background.paper',
|
||||
border: '2px solid #000',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
maxHeight: '90vh',
|
||||
overflowY: 'auto'
|
||||
};
|
||||
|
||||
type DestinoType = 'Distribuidores' | 'Canillas';
|
||||
type TipoNotaType = 'Credito' | 'Debito';
|
||||
|
||||
interface NotaCreditoDebitoFormModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (data: CreateNotaDto | UpdateNotaDto, idNota?: number) => Promise<void>;
|
||||
initialData?: NotaCreditoDebitoDto | null;
|
||||
errorMessage?: string | null;
|
||||
clearErrorMessage: () => void;
|
||||
}
|
||||
|
||||
const NotaCreditoDebitoFormModal: React.FC<NotaCreditoDebitoFormModalProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
onSubmit,
|
||||
initialData,
|
||||
errorMessage,
|
||||
clearErrorMessage
|
||||
}) => {
|
||||
const [destino, setDestino] = useState<DestinoType>('Distribuidores');
|
||||
const [idDestino, setIdDestino] = useState<number | string>('');
|
||||
const [referencia, setReferencia] = useState('');
|
||||
const [tipo, setTipo] = useState<TipoNotaType>('Credito');
|
||||
const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||
const [monto, setMonto] = useState<string>('');
|
||||
const [observaciones, setObservaciones] = useState('');
|
||||
const [idEmpresa, setIdEmpresa] = useState<number | string>('');
|
||||
|
||||
const [destinatarios, setDestinatarios] = useState<(DistribuidorDto | CanillaDto)[]>([]);
|
||||
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||
|
||||
const isEditing = Boolean(initialData);
|
||||
|
||||
const fetchEmpresas = useCallback(async () => {
|
||||
setLoadingDropdowns(true);
|
||||
try {
|
||||
const data = await empresaService.getAllEmpresas();
|
||||
setEmpresas(data);
|
||||
} catch (error) {
|
||||
console.error("Error al cargar empresas", error);
|
||||
setLocalErrors(prev => ({...prev, dropdowns: 'Error al cargar empresas.'}));
|
||||
} finally {
|
||||
setLoadingDropdowns(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchDestinatarios = useCallback(async (tipoDestino: DestinoType) => {
|
||||
setLoadingDropdowns(true);
|
||||
setIdDestino(''); // Resetear selección de destinatario al cambiar tipo
|
||||
setDestinatarios([]);
|
||||
try {
|
||||
if (tipoDestino === 'Distribuidores') {
|
||||
const data = await distribuidorService.getAllDistribuidores();
|
||||
setDestinatarios(data);
|
||||
} else if (tipoDestino === 'Canillas') {
|
||||
const data = await canillaService.getAllCanillas(undefined, undefined, true); // Solo activos
|
||||
setDestinatarios(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error al cargar ${tipoDestino}`, error);
|
||||
setLocalErrors(prev => ({...prev, dropdowns: `Error al cargar ${tipoDestino}.`}));
|
||||
} finally {
|
||||
setLoadingDropdowns(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
fetchEmpresas();
|
||||
// Cargar destinatarios basados en el initialData o el default
|
||||
const initialDestinoType = initialData?.destino || 'Distribuidores';
|
||||
setDestino(initialDestinoType as DestinoType);
|
||||
fetchDestinatarios(initialDestinoType as DestinoType);
|
||||
|
||||
setIdDestino(initialData?.idDestino || '');
|
||||
setReferencia(initialData?.referencia || '');
|
||||
setTipo(initialData?.tipo as TipoNotaType || 'Credito');
|
||||
setFecha(initialData?.fecha || new Date().toISOString().split('T')[0]);
|
||||
setMonto(initialData?.monto?.toString() || '');
|
||||
setObservaciones(initialData?.observaciones || '');
|
||||
setIdEmpresa(initialData?.idEmpresa || '');
|
||||
setLocalErrors({});
|
||||
clearErrorMessage();
|
||||
}
|
||||
}, [open, initialData, clearErrorMessage, fetchEmpresas, fetchDestinatarios]);
|
||||
|
||||
useEffect(() => {
|
||||
if(open && !isEditing) { // Solo cambiar destinatarios si es creación y cambia el tipo de Destino
|
||||
fetchDestinatarios(destino);
|
||||
}
|
||||
}, [destino, open, isEditing, fetchDestinatarios]);
|
||||
|
||||
|
||||
const validate = (): boolean => {
|
||||
const errors: { [key: string]: string | null } = {};
|
||||
if (!destino) errors.destino = 'Seleccione un tipo de destino.';
|
||||
if (!idDestino) errors.idDestino = 'Seleccione un destinatario.';
|
||||
if (!tipo) errors.tipo = 'Seleccione el tipo de nota.';
|
||||
if (!fecha.trim()) errors.fecha = 'La fecha es obligatoria.';
|
||||
else if (!/^\d{4}-\d{2}-\d{2}$/.test(fecha)) errors.fecha = 'Formato de fecha inválido.';
|
||||
if (!monto.trim() || isNaN(parseFloat(monto)) || parseFloat(monto) <= 0) errors.monto = 'Monto debe ser un número positivo.';
|
||||
if (!idEmpresa) errors.idEmpresa = 'Seleccione una empresa (para el saldo).';
|
||||
|
||||
setLocalErrors(errors);
|
||||
return Object.keys(errors).length === 0;
|
||||
};
|
||||
|
||||
const handleInputChange = (fieldName: string) => {
|
||||
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
|
||||
if (errorMessage) clearErrorMessage();
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
clearErrorMessage();
|
||||
if (!validate()) return;
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
const montoNum = parseFloat(monto);
|
||||
|
||||
if (isEditing && initialData) {
|
||||
const dataToSubmit: UpdateNotaDto = {
|
||||
monto: montoNum,
|
||||
observaciones: observaciones || undefined,
|
||||
};
|
||||
await onSubmit(dataToSubmit, initialData.idNota);
|
||||
} else {
|
||||
const dataToSubmit: CreateNotaDto = {
|
||||
destino,
|
||||
idDestino: Number(idDestino),
|
||||
referencia: referencia || undefined,
|
||||
tipo,
|
||||
fecha,
|
||||
monto: montoNum,
|
||||
observaciones: observaciones || undefined,
|
||||
idEmpresa: Number(idEmpresa),
|
||||
};
|
||||
await onSubmit(dataToSubmit);
|
||||
}
|
||||
onClose();
|
||||
} catch (error: any) {
|
||||
console.error("Error en submit de NotaCreditoDebitoFormModal:", error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<Box sx={modalStyle}>
|
||||
<Typography variant="h6" component="h2" gutterBottom>
|
||||
{isEditing ? 'Editar Nota de Crédito/Débito' : 'Registrar Nota de Crédito/Débito'}
|
||||
</Typography>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||
<FormControl component="fieldset" margin="dense" required disabled={isEditing}>
|
||||
<Typography component="legend" variant="body2" sx={{mb:0.5}}>Destino</Typography>
|
||||
<RadioGroup row value={destino} onChange={(e) => {setDestino(e.target.value as DestinoType); handleInputChange('destino'); }}>
|
||||
<FormControlLabel value="Distribuidores" control={<Radio size="small"/>} label="Distribuidor" disabled={loading || isEditing}/>
|
||||
<FormControlLabel value="Canillas" control={<Radio size="small"/>} label="Canillita" disabled={loading || isEditing}/>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idDestino} required disabled={isEditing}>
|
||||
<InputLabel id="destinatario-nota-select-label">Destinatario</InputLabel>
|
||||
<Select labelId="destinatario-nota-select-label" label="Destinatario" value={idDestino}
|
||||
onChange={(e) => {setIdDestino(e.target.value as number); handleInputChange('idDestino');}}
|
||||
disabled={loading || loadingDropdowns || isEditing || destinatarios.length === 0}
|
||||
>
|
||||
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||
{destinatarios.map((d) => (
|
||||
<MenuItem key={'idDistribuidor' in d ? d.idDistribuidor : d.idCanilla} value={'idDistribuidor' in d ? d.idDistribuidor : d.idCanilla}>
|
||||
{'nomApe' in d ? d.nomApe : d.nombre} {/* Muestra nomApe para Canilla, nombre para Distribuidor */}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{localErrors.idDestino && <Typography color="error" variant="caption">{localErrors.idDestino}</Typography>}
|
||||
</FormControl>
|
||||
|
||||
<FormControl fullWidth margin="dense" error={!!localErrors.idEmpresa} required disabled={isEditing}>
|
||||
<InputLabel id="empresa-nota-select-label">Empresa (del Saldo)</InputLabel>
|
||||
<Select labelId="empresa-nota-select-label" label="Empresa (del Saldo)" value={idEmpresa}
|
||||
onChange={(e) => {setIdEmpresa(e.target.value as number); handleInputChange('idEmpresa');}}
|
||||
disabled={loading || loadingDropdowns || isEditing}
|
||||
>
|
||||
<MenuItem value="" disabled><em>Seleccione</em></MenuItem>
|
||||
{empresas.map((e) => (<MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>))}
|
||||
</Select>
|
||||
{localErrors.idEmpresa && <Typography color="error" variant="caption">{localErrors.idEmpresa}</Typography>}
|
||||
</FormControl>
|
||||
|
||||
<TextField label="Fecha Nota" type="date" value={fecha} required
|
||||
onChange={(e) => {setFecha(e.target.value); handleInputChange('fecha');}}
|
||||
margin="dense" fullWidth error={!!localErrors.fecha} helperText={localErrors.fecha || ''}
|
||||
disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing}
|
||||
/>
|
||||
<TextField label="Referencia (Opcional)" value={referencia}
|
||||
onChange={(e) => setReferencia(e.target.value)}
|
||||
margin="dense" fullWidth disabled={loading || isEditing}
|
||||
/>
|
||||
<FormControl component="fieldset" margin="dense" error={!!localErrors.tipo} required>
|
||||
<Typography component="legend" variant="body2" sx={{mb:0.5}}>Tipo de Nota</Typography>
|
||||
<RadioGroup row value={tipo} onChange={(e) => {setTipo(e.target.value as TipoNotaType); handleInputChange('tipo');}} >
|
||||
<FormControlLabel value="Credito" control={<Radio size="small"/>} label="Crédito" disabled={loading || isEditing}/>
|
||||
<FormControlLabel value="Debito" control={<Radio size="small"/>} label="Débito" disabled={loading || isEditing}/>
|
||||
</RadioGroup>
|
||||
{localErrors.tipo && <Typography color="error" variant="caption">{localErrors.tipo}</Typography>}
|
||||
</FormControl>
|
||||
<TextField label="Monto" type="number" value={monto} required
|
||||
onChange={(e) => {setMonto(e.target.value); handleInputChange('monto');}}
|
||||
margin="dense" fullWidth error={!!localErrors.monto} helperText={localErrors.monto || ''}
|
||||
disabled={loading} inputProps={{step: "0.01", min:0.01, lang:"es-AR" }}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
|
||||
/>
|
||||
<TextField label="Observaciones (Opcional)" value={observaciones}
|
||||
onChange={(e) => setObservaciones(e.target.value)}
|
||||
margin="dense" fullWidth multiline rows={2} disabled={loading}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>}
|
||||
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||
|
||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||
<Button onClick={onClose} color="secondary" disabled={loading}>Cancelar</Button>
|
||||
<Button type="submit" variant="contained" disabled={loading || loadingDropdowns}>
|
||||
{loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Registrar Nota')}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotaCreditoDebitoFormModal;
|
||||
Reference in New Issue
Block a user