Backend API:
- Canillitas (`dist_dtCanillas`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Lógica para manejo de `Accionista`, `Baja`, `FechaBaja`.
- Auditoría en `dist_dtCanillas_H`.
- Validación de legajo único y lógica de empresa vs accionista.
- Distribuidores (`dist_dtDistribuidores`):
- Implementado CRUD completo (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Auditoría en `dist_dtDistribuidores_H`.
- Creación de saldos iniciales para el nuevo distribuidor en todas las empresas.
- Verificación de NroDoc único y Nombre opcionalmente único.
- Precios de Publicación (`dist_Precios`):
- Implementado CRUD básico (Modelos, DTOs, Repositorio, Servicio, Controlador).
- Endpoints anidados bajo `/publicaciones/{idPublicacion}/precios`.
- Lógica de negocio para cerrar período de precio anterior al crear uno nuevo.
- Lógica de negocio para reabrir período de precio anterior al eliminar el último.
- Auditoría en `dist_Precios_H`.
- Auditoría en Eliminación de Publicaciones:
- Extendido `PublicacionService.EliminarAsync` para eliminar en cascada registros de precios, recargos, porcentajes de pago (distribuidores y canillitas) y secciones de publicación.
- Repositorios correspondientes (`PrecioRepository`, `RecargoZonaRepository`, `PorcPagoRepository`, `PorcMonCanillaRepository`, `PubliSeccionRepository`) actualizados con métodos `DeleteByPublicacionIdAsync` que registran en sus respectivas tablas `_H` (si existen y se implementó la lógica).
- Asegurada la correcta propagación del `idUsuario` para la auditoría en cascada.
- Correcciones de Nulabilidad:
- Ajustados los métodos `MapToDto` y su uso en `CanillaService` y `PublicacionService` para manejar correctamente tipos anulables.
Frontend React:
- Canillitas:
- `canillaService.ts`.
- `CanillaFormModal.tsx` con selectores para Zona y Empresa, y lógica de Accionista.
- `GestionarCanillitasPage.tsx` con filtros, paginación, y acciones (editar, toggle baja).
- Distribuidores:
- `distribuidorService.ts`.
- `DistribuidorFormModal.tsx` con múltiples campos y selector de Zona.
- `GestionarDistribuidoresPage.tsx` con filtros, paginación, y acciones (editar, eliminar).
- Precios de Publicación:
- `precioService.ts`.
- `PrecioFormModal.tsx` para crear/editar períodos de precios (VigenciaD, VigenciaH opcional, precios por día).
- `GestionarPreciosPublicacionPage.tsx` accesible desde la gestión de publicaciones, para listar y gestionar los períodos de precios de una publicación específica.
- Layout:
- Reemplazado el uso de `Grid` por `Box` con Flexbox en `CanillaFormModal`, `GestionarCanillitasPage` (filtros), `DistribuidorFormModal` y `PrecioFormModal` para resolver problemas de tipos y mejorar la consistencia del layout de formularios.
- Navegación:
- Actualizadas las rutas y pestañas para los nuevos módulos y sub-módulos.
142 lines
5.4 KiB
TypeScript
142 lines
5.4 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Modal, Box, Typography, TextField, Button, CircularProgress, Alert } from '@mui/material';
|
|
import type { EmpresaDto } from '../../../models/dtos/Distribucion/EmpresaDto';
|
|
import type { CreateEmpresaDto } from '../../../models/dtos/Distribucion/CreateEmpresaDto';
|
|
import type { UpdateEmpresaDto } from '../../../models/dtos/Distribucion/UpdateEmpresaDto'; // Necesitamos Update DTO también
|
|
|
|
const modalStyle = {
|
|
position: 'absolute' as 'absolute',
|
|
top: '50%',
|
|
left: '50%',
|
|
transform: 'translate(-50%, -50%)',
|
|
width: 400,
|
|
bgcolor: 'background.paper',
|
|
border: '2px solid #000',
|
|
boxShadow: 24,
|
|
p: 4,
|
|
};
|
|
|
|
interface EmpresaFormModalProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
// El tipo de dato enviado depende si es creación o edición
|
|
onSubmit: (data: CreateEmpresaDto | (UpdateEmpresaDto & { idEmpresa: number })) => Promise<void>;
|
|
initialData?: EmpresaDto | null; // Para editar
|
|
errorMessage?: string | null; // Error de la API pasado desde el padre
|
|
clearErrorMessage: () => void; // Función para limpiar el error en el padre
|
|
}
|
|
|
|
const EmpresaFormModal: React.FC<EmpresaFormModalProps> = ({
|
|
open,
|
|
onClose,
|
|
onSubmit,
|
|
initialData,
|
|
errorMessage,
|
|
clearErrorMessage
|
|
}) => {
|
|
const [nombre, setNombre] = useState('');
|
|
const [detalle, setDetalle] = useState('');
|
|
const [loading, setLoading] = useState(false);
|
|
const [localError, setLocalError] = useState<string | null>(null); // Para validaciones locales (ej: campo requerido)
|
|
|
|
const isEditing = Boolean(initialData);
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
// Poblar formulario si es edición, limpiar si es creación
|
|
setNombre(initialData?.nombre || '');
|
|
setDetalle(initialData?.detalle || '');
|
|
setLocalError(null); // Limpiar error local al abrir/cambiar
|
|
clearErrorMessage(); // Limpiar error API del padre
|
|
}
|
|
// No necesitamos limpiar al cerrar, ya que el useEffect se ejecuta al abrir `open = true`
|
|
}, [open, initialData, clearErrorMessage]);
|
|
|
|
// Limpia errores cuando el usuario empieza a escribir
|
|
const handleInputChange = () => {
|
|
if (localError) setLocalError(null);
|
|
if (errorMessage) clearErrorMessage();
|
|
};
|
|
|
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
setLocalError(null);
|
|
clearErrorMessage(); // Limpiar errores antes de enviar
|
|
|
|
// Validación local simple
|
|
if (!nombre.trim()) {
|
|
setLocalError('El nombre es obligatorio.');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
try {
|
|
// Preparar datos según sea creación o edición
|
|
const dataToSubmit = { nombre, detalle: detalle || undefined }; // Usa undefined si detalle está vacío
|
|
|
|
if (isEditing && initialData) {
|
|
// Si es edición, combina los datos del formulario con el ID existente
|
|
await onSubmit({ ...dataToSubmit, idEmpresa: initialData.idEmpresa });
|
|
} else {
|
|
// Si es creación, envía solo los datos del formulario
|
|
await onSubmit(dataToSubmit as CreateEmpresaDto);
|
|
}
|
|
onClose(); // Cierra el modal SOLO si onSubmit fue exitoso
|
|
} catch (error: any) {
|
|
// El error de la API será manejado por el componente padre y mostrado vía 'errorMessage'
|
|
// No necesitamos setear localError aquí a menos que sea un error específico del submit no cubierto por la API
|
|
console.error("Error en submit de EmpresaFormModal:", error);
|
|
// El componente padre es responsable de poner setLoading(false) si onSubmit falla
|
|
} finally {
|
|
// Asegúrate de que el loading se detenga incluso si hay un error no capturado por el padre
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal open={open} onClose={onClose}>
|
|
<Box sx={modalStyle}>
|
|
<Typography variant="h6" component="h2">
|
|
{isEditing ? 'Editar Empresa' : 'Agregar Nueva Empresa'}
|
|
</Typography>
|
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 2 }}>
|
|
<TextField
|
|
label="Nombre"
|
|
fullWidth
|
|
required // HTML5 required
|
|
value={nombre}
|
|
onChange={(e) => {setNombre(e.target.value); handleInputChange();} }
|
|
margin="normal"
|
|
error={!!localError} // Mostrar error si localError tiene un mensaje
|
|
helperText={localError ? localError : ''} // Mostrar el mensaje de error local
|
|
disabled={loading}
|
|
autoFocus // Poner el foco en el primer campo
|
|
/>
|
|
<TextField
|
|
label="Detalle (Opcional)"
|
|
fullWidth
|
|
value={detalle}
|
|
onChange={(e) => {setDetalle(e.target.value); handleInputChange();}}
|
|
margin="normal"
|
|
multiline
|
|
rows={3}
|
|
disabled={loading}
|
|
/>
|
|
{/* Mostrar error de la API si existe */}
|
|
{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} /> : (isEditing ? 'Guardar Cambios' : 'Agregar')}
|
|
</Button>
|
|
</Box>
|
|
</Box>
|
|
</Box>
|
|
</Modal>
|
|
);
|
|
};
|
|
|
|
export default EmpresaFormModal; |