201 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			201 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | import React, { useState, useEffect } from 'react'; | ||
|  | import { | ||
|  |     Modal, Box, Typography, TextField, Button, CircularProgress, Alert, | ||
|  |     FormControl, InputLabel, Select, MenuItem | ||
|  | } from '@mui/material'; | ||
|  | import type { ControlDevolucionesDto } from '../../../models/dtos/Distribucion/ControlDevolucionesDto'; | ||
|  | import type { CreateControlDevolucionesDto } from '../../../models/dtos/Distribucion/CreateControlDevolucionesDto'; | ||
|  | import type { UpdateControlDevolucionesDto } from '../../../models/dtos/Distribucion/UpdateControlDevolucionesDto'; | ||
|  | import type { EmpresaDto } from '../../../models/dtos/Distribucion/EmpresaDto'; // DTO de Empresa
 | ||
|  | import empresaService from '../../../services//Distribucion/empresaService'; // Servicio de Empresa
 | ||
|  | 
 | ||
|  | const modalStyle = { /* ... (mismo estilo) ... */ | ||
|  |     position: 'absolute' as 'absolute', | ||
|  |     top: '50%', | ||
|  |     left: '50%', | ||
|  |     transform: 'translate(-50%, -50%)', | ||
|  |     width: { xs: '90%', sm: 500 }, | ||
|  |     bgcolor: 'background.paper', | ||
|  |     border: '2px solid #000', | ||
|  |     boxShadow: 24, | ||
|  |     p: 4, | ||
|  |     maxHeight: '90vh', | ||
|  |     overflowY: 'auto' | ||
|  | }; | ||
|  | 
 | ||
|  | interface ControlDevolucionesFormModalProps { | ||
|  |   open: boolean; | ||
|  |   onClose: () => void; | ||
|  |   onSubmit: (data: CreateControlDevolucionesDto | UpdateControlDevolucionesDto, idControl?: number) => Promise<void>; | ||
|  |   initialData?: ControlDevolucionesDto | null; | ||
|  |   errorMessage?: string | null; | ||
|  |   clearErrorMessage: () => void; | ||
|  | } | ||
|  | 
 | ||
|  | const ControlDevolucionesFormModal: React.FC<ControlDevolucionesFormModalProps> = ({ | ||
|  |   open, | ||
|  |   onClose, | ||
|  |   onSubmit, | ||
|  |   initialData, | ||
|  |   errorMessage, | ||
|  |   clearErrorMessage | ||
|  | }) => { | ||
|  |   const [idEmpresa, setIdEmpresa] = useState<number | string>(''); | ||
|  |   const [fecha, setFecha] = useState<string>(new Date().toISOString().split('T')[0]); | ||
|  |   const [entrada, setEntrada] = useState<string>(''); | ||
|  |   const [sobrantes, setSobrantes] = useState<string>(''); | ||
|  |   const [detalle, setDetalle] = useState(''); | ||
|  |   const [sinCargo, setSinCargo] = useState<string>('0'); // Default 0
 | ||
|  | 
 | ||
|  |   const [empresas, setEmpresas] = useState<EmpresaDto[]>([]); | ||
|  |   const [loading, setLoading] = useState(false); | ||
|  |   const [loadingEmpresas, setLoadingEmpresas] = useState(false); | ||
|  |   const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); | ||
|  | 
 | ||
|  |   const isEditing = Boolean(initialData); | ||
|  | 
 | ||
|  |   useEffect(() => { | ||
|  |     const fetchEmpresas = async () => { | ||
|  |         setLoadingEmpresas(true); | ||
|  |         try { | ||
|  |             const data = await empresaService.getAllEmpresas(); | ||
|  |             setEmpresas(data); | ||
|  |         } catch (error) { | ||
|  |             console.error("Error al cargar empresas", error); | ||
|  |             setLocalErrors(prev => ({...prev, empresas: 'Error al cargar empresas.'})); | ||
|  |         } finally { | ||
|  |             setLoadingEmpresas(false); | ||
|  |         } | ||
|  |     }; | ||
|  | 
 | ||
|  |     if (open) { | ||
|  |         fetchEmpresas(); | ||
|  |         setIdEmpresa(initialData?.idEmpresa || ''); | ||
|  |         setFecha(initialData?.fecha || new Date().toISOString().split('T')[0]); | ||
|  |         setEntrada(initialData?.entrada?.toString() || '0'); | ||
|  |         setSobrantes(initialData?.sobrantes?.toString() || '0'); | ||
|  |         setDetalle(initialData?.detalle || ''); | ||
|  |         setSinCargo(initialData?.sinCargo?.toString() || '0'); | ||
|  |         setLocalErrors({}); | ||
|  |         clearErrorMessage(); | ||
|  |     } | ||
|  |   }, [open, initialData, clearErrorMessage]); | ||
|  | 
 | ||
|  |   const validate = (): boolean => { | ||
|  |     const errors: { [key: string]: string | null } = {}; | ||
|  |     if (!idEmpresa) errors.idEmpresa = 'Seleccione una empresa.'; | ||
|  |     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.'; | ||
|  | 
 | ||
|  |     const entradaNum = parseInt(entrada, 10); | ||
|  |     const sobrantesNum = parseInt(sobrantes, 10); | ||
|  |     const sinCargoNum = parseInt(sinCargo, 10); | ||
|  | 
 | ||
|  |     if (entrada.trim() === '' || isNaN(entradaNum) || entradaNum < 0) errors.entrada = 'Entrada debe ser un número >= 0.'; | ||
|  |     if (sobrantes.trim() === '' || isNaN(sobrantesNum) || sobrantesNum < 0) errors.sobrantes = 'Sobrantes debe ser un número >= 0.'; | ||
|  |     if (sinCargo.trim() === '' || isNaN(sinCargoNum) || sinCargoNum < 0) errors.sinCargo = 'Sin Cargo debe ser un número >= 0.'; | ||
|  | 
 | ||
|  |     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 commonData = { | ||
|  |         entrada: parseInt(entrada, 10), | ||
|  |         sobrantes: parseInt(sobrantes, 10), | ||
|  |         detalle: detalle || undefined, | ||
|  |         sinCargo: parseInt(sinCargo, 10), | ||
|  |       }; | ||
|  | 
 | ||
|  |       if (isEditing && initialData) { | ||
|  |         const dataToSubmit: UpdateControlDevolucionesDto = { ...commonData }; | ||
|  |         await onSubmit(dataToSubmit, initialData.idControl); | ||
|  |       } else { | ||
|  |         const dataToSubmit: CreateControlDevolucionesDto = { | ||
|  |             ...commonData, | ||
|  |             idEmpresa: Number(idEmpresa), | ||
|  |             fecha, | ||
|  |         }; | ||
|  |         await onSubmit(dataToSubmit); | ||
|  |       } | ||
|  |       onClose(); | ||
|  |     } catch (error: any) { | ||
|  |       console.error("Error en submit de ControlDevolucionesFormModal:", error); | ||
|  |     } finally { | ||
|  |        setLoading(false); | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   return ( | ||
|  |     <Modal open={open} onClose={onClose}> | ||
|  |       <Box sx={modalStyle}> | ||
|  |         <Typography variant="h6" component="h2" gutterBottom> | ||
|  |           {isEditing ? 'Editar Control de Devoluciones' : 'Registrar Control de Devoluciones'} | ||
|  |         </Typography> | ||
|  |         <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}> | ||
|  |             <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> | ||
|  |                 <FormControl fullWidth margin="dense" error={!!localErrors.idEmpresa} required> | ||
|  |                     <InputLabel id="empresa-cd-select-label">Empresa</InputLabel> | ||
|  |                     <Select labelId="empresa-cd-select-label" label="Empresa" value={idEmpresa} | ||
|  |                         onChange={(e) => {setIdEmpresa(e.target.value as number); handleInputChange('idEmpresa');}} | ||
|  |                         disabled={loading || loadingEmpresas || 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" 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="Entrada (Devolución Total)" type="number" value={entrada} required | ||
|  |                     onChange={(e) => {setEntrada(e.target.value); handleInputChange('entrada');}} | ||
|  |                     margin="dense" fullWidth error={!!localErrors.entrada} helperText={localErrors.entrada || ''} | ||
|  |                     disabled={loading} inputProps={{min:0}} | ||
|  |                 /> | ||
|  |                  <TextField label="Sobrantes" type="number" value={sobrantes} required | ||
|  |                     onChange={(e) => {setSobrantes(e.target.value); handleInputChange('sobrantes');}} | ||
|  |                     margin="dense" fullWidth error={!!localErrors.sobrantes} helperText={localErrors.sobrantes || ''} | ||
|  |                     disabled={loading} inputProps={{min:0}} | ||
|  |                 /> | ||
|  |                  <TextField label="Sin Cargo" type="number" value={sinCargo} required | ||
|  |                     onChange={(e) => {setSinCargo(e.target.value); handleInputChange('sinCargo');}} | ||
|  |                     margin="dense" fullWidth error={!!localErrors.sinCargo} helperText={localErrors.sinCargo || ''} | ||
|  |                     disabled={loading} inputProps={{min:0}} | ||
|  |                 /> | ||
|  |                 <TextField label="Detalle (Opcional)" value={detalle} | ||
|  |                     onChange={(e) => setDetalle(e.target.value)} | ||
|  |                     margin="dense" fullWidth multiline rows={2} disabled={loading} | ||
|  |                 /> | ||
|  |             </Box> | ||
|  | 
 | ||
|  |           {errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>} | ||
|  |           {localErrors.empresas && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.empresas}</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 || loadingEmpresas}> | ||
|  |               {loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Registrar Control')} | ||
|  |             </Button> | ||
|  |           </Box> | ||
|  |         </Box> | ||
|  |       </Box> | ||
|  |     </Modal> | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | export default ControlDevolucionesFormModal; |