Fase 3:
- Backend API: Autenticación y autorización básicas con JWT implementadas. Cambio de contraseña funcional. Módulo "Tipos de Pago" (CRUD completo) implementado en el backend (Controlador, Servicio, Repositorio) usando Dapper, transacciones y con lógica de historial. Se incluyen permisos en el token JWT. - Frontend React: Estructura base con Vite, TypeScript, MUI. Contexto de autenticación (AuthContext) que maneja el estado del usuario y el token. Página de Login. Modal de Cambio de Contraseña (forzado y opcional). Hook usePermissions para verificar permisos. Página GestionarTiposPagoPage con tabla, paginación, filtro, modal para crear/editar, y menú de acciones, respetando permisos. Layout principal (MainLayout) con navegación por Tabs (funcionalidad básica de navegación). Estructura de enrutamiento (AppRoutes) que maneja rutas públicas, protegidas y anidadas para módulos.
This commit is contained in:
		
							
								
								
									
										129
									
								
								Frontend/src/components/Modals/TipoPagoFormModal.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								Frontend/src/components/Modals/TipoPagoFormModal.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import { Modal, Box, Typography, TextField, Button, CircularProgress, Alert } from '@mui/material'; | ||||
| import type { TipoPago } from '../../models/Entities/TipoPago'; | ||||
| import type { CreateTipoPagoDto } from '../../models/dtos/tiposPago/CreateTipoPagoDto'; | ||||
|  | ||||
| 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 TipoPagoFormModalProps { | ||||
|   open: boolean; | ||||
|   onClose: () => void; | ||||
|   onSubmit: (data: CreateTipoPagoDto | (CreateTipoPagoDto & { idTipoPago: number })) => Promise<void>; // Puede ser para crear o actualizar | ||||
|   initialData?: TipoPago | null; // Datos para editar | ||||
|   errorMessage?: string | null; | ||||
|   clearErrorMessage: () => void; | ||||
| } | ||||
|  | ||||
| const TipoPagoFormModal: React.FC<TipoPagoFormModalProps> = ({ | ||||
|   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); | ||||
|  | ||||
|  | ||||
|   const isEditing = Boolean(initialData); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (open) { | ||||
|         setNombre(initialData?.nombre || ''); | ||||
|         setDetalle(initialData?.detalle || ''); | ||||
|         setLocalError(null); // Limpiar errores locales al abrir | ||||
|         clearErrorMessage(); // Limpiar errores del padre | ||||
|     } | ||||
|   }, [open, initialData, clearErrorMessage]); | ||||
|  | ||||
|   const handleInputChange = () => { | ||||
|     if (localError) setLocalError(null); | ||||
|     if (errorMessage) clearErrorMessage(); | ||||
|   }; | ||||
|  | ||||
|   const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { | ||||
|     event.preventDefault(); | ||||
|     setLocalError(null); | ||||
|     clearErrorMessage(); | ||||
|  | ||||
|     if (!nombre.trim()) { | ||||
|         setLocalError('El nombre es obligatorio.'); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     setLoading(true); | ||||
|     try { | ||||
|       const dataToSubmit: CreateTipoPagoDto = { nombre, detalle: detalle || undefined }; | ||||
|       if (isEditing && initialData) { | ||||
|         await onSubmit({ ...dataToSubmit, idTipoPago: initialData.idTipoPago }); | ||||
|       } else { | ||||
|         await onSubmit(dataToSubmit); | ||||
|       } | ||||
|       onClose(); // Cerrar modal en éxito | ||||
|     } catch (error: any) { | ||||
|       // El error de la API ya se debería manejar en el componente padre | ||||
|       // y pasarse a través de 'errorMessage', pero podemos loggear aquí si es un error inesperado | ||||
|       console.error("Error en submit de TipoPagoFormModal:", error); | ||||
|       // No seteamos localError aquí si el padre maneja 'errorMessage' | ||||
|     } finally { | ||||
|       setLoading(false); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <Modal open={open} onClose={onClose}> | ||||
|       <Box sx={modalStyle}> | ||||
|         <Typography variant="h6" component="h2"> | ||||
|           {isEditing ? 'Editar Tipo de Pago' : 'Agregar Nuevo Tipo de Pago'} | ||||
|         </Typography> | ||||
|         <Box component="form" onSubmit={handleSubmit} sx={{ mt: 2 }}> | ||||
|           <TextField | ||||
|             label="Nombre" | ||||
|             fullWidth | ||||
|             required | ||||
|             value={nombre} | ||||
|             onChange={(e) => { setNombre(e.target.value); handleInputChange(); }} | ||||
|             margin="normal" | ||||
|             error={!!localError && nombre.trim() === ''} | ||||
|             helperText={localError && nombre.trim() === '' ? localError : ''} | ||||
|             disabled={loading} | ||||
|           /> | ||||
|           <TextField | ||||
|             label="Detalle (Opcional)" | ||||
|             fullWidth | ||||
|             value={detalle} | ||||
|             onChange={(e) => { setDetalle(e.target.value); handleInputChange();}} | ||||
|             margin="normal" | ||||
|             multiline | ||||
|             rows={3} | ||||
|             disabled={loading} | ||||
|           /> | ||||
|           {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 TipoPagoFormModal; | ||||
		Reference in New Issue
	
	Block a user