Refinamiento de permisos y ajustes en controles. Añade gestión sobre saldos y visualización. Entre otros..

This commit is contained in:
2025-06-06 18:33:09 -03:00
parent 8fb94f8cef
commit 35e24ab7d2
104 changed files with 5917 additions and 1205 deletions

View File

@@ -0,0 +1,162 @@
import React, { useState, useEffect } from 'react';
import {
Modal, Box, Typography, TextField, Button, CircularProgress, Alert, InputAdornment
} from '@mui/material';
import type { SaldoGestionDto } from '../../../models/dtos/Contables/SaldoGestionDto';
import type { AjusteSaldoRequestDto } from '../../../models/dtos/Contables/AjusteSaldoRequestDto';
const modalStyle = {
position: 'absolute' as 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: { xs: '90%', sm: 450 },
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 3,
};
interface AjusteSaldoModalProps {
open: boolean;
onClose: () => void;
onSubmit: (data: AjusteSaldoRequestDto) => Promise<void>; // El padre maneja la recarga
saldoParaAjustar: SaldoGestionDto | null;
errorMessage?: string | null;
clearErrorMessage: () => void;
}
const AjusteSaldoModal: React.FC<AjusteSaldoModalProps> = ({
open,
onClose,
onSubmit,
saldoParaAjustar,
errorMessage,
clearErrorMessage
}) => {
const [montoAjuste, setMontoAjuste] = useState<string>('');
const [justificacion, setJustificacion] = useState('');
const [loading, setLoading] = useState(false);
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
useEffect(() => {
if (open) {
setMontoAjuste('');
setJustificacion('');
setLocalErrors({});
clearErrorMessage();
}
}, [open, clearErrorMessage]);
const validate = (): boolean => {
const errors: { [key: string]: string | null } = {};
const numMontoAjuste = parseFloat(montoAjuste);
if (!montoAjuste.trim()) {
errors.montoAjuste = 'El monto de ajuste es obligatorio.';
} else if (isNaN(numMontoAjuste)) {
errors.montoAjuste = 'El monto debe ser un número.';
} else if (numMontoAjuste === 0) {
errors.montoAjuste = 'El monto de ajuste no puede ser cero.';
}
if (!justificacion.trim()) {
errors.justificacion = 'La justificación es obligatoria.';
} else if (justificacion.trim().length < 5 || justificacion.trim().length > 250) {
errors.justificacion = 'La justificación debe tener entre 5 y 250 caracteres.';
}
setLocalErrors(errors);
return Object.keys(errors).length === 0;
};
const handleInputChange = (fieldName: 'montoAjuste' | 'justificacion') => {
if (localErrors[fieldName]) setLocalErrors(prev => ({ ...prev, [fieldName]: null }));
if (errorMessage) clearErrorMessage();
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
clearErrorMessage();
if (!validate() || !saldoParaAjustar) return;
setLoading(true);
try {
const dataToSubmit: AjusteSaldoRequestDto = {
destino: saldoParaAjustar.destino as 'Distribuidores' | 'Canillas',
idDestino: saldoParaAjustar.idDestino,
idEmpresa: saldoParaAjustar.idEmpresa,
montoAjuste: parseFloat(montoAjuste),
justificacion,
};
await onSubmit(dataToSubmit);
onClose(); // Cerrar en éxito (el padre recargará)
} catch (error: any) {
// El error de API es manejado por la página padre
console.error("Error en submit de AjusteSaldoModal:", error);
} finally {
setLoading(false);
}
};
if (!saldoParaAjustar) return null;
return (
<Modal open={open} onClose={onClose}>
<Box sx={modalStyle}>
<Typography variant="h6" component="h2" gutterBottom>
Ajustar Saldo Manualmente
</Typography>
<Typography variant="body2" gutterBottom>
Destinatario: <strong>{saldoParaAjustar.nombreDestinatario}</strong> ({saldoParaAjustar.destino})
</Typography>
<Typography variant="body2" gutterBottom>
Empresa: <strong>{saldoParaAjustar.nombreEmpresa}</strong>
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
Saldo Actual: <strong>{saldoParaAjustar.monto.toLocaleString('es-AR', { style: 'currency', currency: 'ARS' })}</strong>
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 2 }}>
<TextField
label="Monto de Ajuste (+/-)"
type="number"
fullWidth
required
value={montoAjuste}
onChange={(e) => { setMontoAjuste(e.target.value); handleInputChange('montoAjuste'); }}
margin="normal"
error={!!localErrors.montoAjuste}
helperText={localErrors.montoAjuste || 'Ingrese un valor positivo para aumentar deuda o negativo para disminuirla.'}
disabled={loading}
InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }}
inputProps={{ step: "0.01" }}
autoFocus
/>
<TextField
label="Justificación del Ajuste"
fullWidth
required
value={justificacion}
onChange={(e) => { setJustificacion(e.target.value); handleInputChange('justificacion'); }}
margin="normal"
multiline
rows={3}
error={!!localErrors.justificacion}
helperText={localErrors.justificacion || ''}
disabled={loading}
inputProps={{ maxLength: 250 }}
/>
{errorMessage && <Alert severity="error" sx={{ mt: 1 }}>{errorMessage}</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}>
{loading ? <CircularProgress size={24} /> : 'Aplicar Ajuste'}
</Button>
</Box>
</Box>
</Box>
</Modal>
);
};
export default AjusteSaldoModal;