193 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			193 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | import React, { useState, useEffect } from 'react'; | ||
|  | import { | ||
|  |     Modal, Box, Typography, TextField, Button, CircularProgress, Alert, | ||
|  |     FormControl, InputLabel, Select, MenuItem, InputAdornment | ||
|  | } from '@mui/material'; | ||
|  | import type { RecargoZonaDto } from '../../../models/dtos/Distribucion/RecargoZonaDto'; | ||
|  | import type { CreateRecargoZonaDto } from '../../../models/dtos/Distribucion/CreateRecargoZonaDto'; | ||
|  | import type { UpdateRecargoZonaDto } from '../../../models/dtos/Distribucion/UpdateRecargoZonaDto'; | ||
|  | import type { ZonaDto } from '../../../models/dtos/Zonas/ZonaDto'; // Para el dropdown de zonas
 | ||
|  | import zonaService from '../../../services/Distribucion/zonaService'; // Para cargar zonas
 | ||
|  | 
 | ||
|  | 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 RecargoZonaFormModalProps { | ||
|  |   open: boolean; | ||
|  |   onClose: () => void; | ||
|  |   onSubmit: (data: CreateRecargoZonaDto | UpdateRecargoZonaDto, idRecargo?: number) => Promise<void>; | ||
|  |   idPublicacion: number; | ||
|  |   initialData?: RecargoZonaDto | null; // Para editar
 | ||
|  |   errorMessage?: string | null; | ||
|  |   clearErrorMessage: () => void; | ||
|  | } | ||
|  | 
 | ||
|  | const RecargoZonaFormModal: React.FC<RecargoZonaFormModalProps> = ({ | ||
|  |   open, | ||
|  |   onClose, | ||
|  |   onSubmit, | ||
|  |   idPublicacion, | ||
|  |   initialData, | ||
|  |   errorMessage, | ||
|  |   clearErrorMessage | ||
|  | }) => { | ||
|  |   const [idZona, setIdZona] = useState<number | string>(''); | ||
|  |   const [vigenciaD, setVigenciaD] = useState(''); // "yyyy-MM-dd"
 | ||
|  |   const [vigenciaH, setVigenciaH] = useState(''); // "yyyy-MM-dd"
 | ||
|  |   const [valor, setValor] = useState<string>(''); | ||
|  | 
 | ||
|  |   const [zonas, setZonas] = useState<ZonaDto[]>([]); | ||
|  |   const [loading, setLoading] = useState(false); | ||
|  |   const [loadingZonas, setLoadingZonas] = useState(false); | ||
|  |   const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({}); | ||
|  | 
 | ||
|  |   const isEditing = Boolean(initialData); | ||
|  | 
 | ||
|  |   useEffect(() => { | ||
|  |     const fetchZonas = async () => { | ||
|  |         setLoadingZonas(true); | ||
|  |         try { | ||
|  |             const data = await zonaService.getAllZonas(); // Asume que devuelve zonas activas
 | ||
|  |             setZonas(data); | ||
|  |         } catch (error) { | ||
|  |             console.error("Error al cargar zonas", error); | ||
|  |             setLocalErrors(prev => ({...prev, zonas: 'Error al cargar zonas.'})); | ||
|  |         } finally { | ||
|  |             setLoadingZonas(false); | ||
|  |         } | ||
|  |     }; | ||
|  | 
 | ||
|  |     if (open) { | ||
|  |         fetchZonas(); | ||
|  |         setIdZona(initialData?.idZona || ''); | ||
|  |         setVigenciaD(initialData?.vigenciaD || ''); | ||
|  |         setVigenciaH(initialData?.vigenciaH || ''); | ||
|  |         setValor(initialData?.valor?.toString() || ''); | ||
|  |         setLocalErrors({}); | ||
|  |         clearErrorMessage(); | ||
|  |     } | ||
|  |   }, [open, initialData, clearErrorMessage]); | ||
|  | 
 | ||
|  |   const validate = (): boolean => { | ||
|  |     const errors: { [key: string]: string | null } = {}; | ||
|  |     if (!idZona) errors.idZona = 'Debe seleccionar una zona.'; | ||
|  |     if (!isEditing && !vigenciaD.trim()) { | ||
|  |         errors.vigenciaD = 'La Vigencia Desde es obligatoria.'; | ||
|  |     } else if (vigenciaD.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaD)) { | ||
|  |         errors.vigenciaD = 'Formato de Vigencia Desde inválido (YYYY-MM-DD).'; | ||
|  |     } | ||
|  |     if (vigenciaH.trim() && !/^\d{4}-\d{2}-\d{2}$/.test(vigenciaH)) { | ||
|  |         errors.vigenciaH = 'Formato de Vigencia Hasta inválido (YYYY-MM-DD).'; | ||
|  |     } else if (vigenciaH.trim() && vigenciaD.trim() && new Date(vigenciaH) < new Date(vigenciaD)) { | ||
|  |         errors.vigenciaH = 'Vigencia Hasta no puede ser anterior a Vigencia Desde.'; | ||
|  |     } | ||
|  |     if (!valor.trim()) errors.valor = 'El valor es obligatorio.'; | ||
|  |     else if (isNaN(parseFloat(valor)) || parseFloat(valor) < 0) { | ||
|  |         errors.valor = 'El valor debe ser un número positivo.'; | ||
|  |     } | ||
|  |     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 valorNum = parseFloat(valor); | ||
|  | 
 | ||
|  |       if (isEditing && initialData) { | ||
|  |         const dataToSubmit: UpdateRecargoZonaDto = { | ||
|  |             valor: valorNum, | ||
|  |             vigenciaH: vigenciaH.trim() ? vigenciaH : null, | ||
|  |         }; | ||
|  |         await onSubmit(dataToSubmit, initialData.idRecargo); | ||
|  |       } else { | ||
|  |         const dataToSubmit: CreateRecargoZonaDto = { | ||
|  |             idPublicacion, | ||
|  |             idZona: Number(idZona), | ||
|  |             vigenciaD, | ||
|  |             valor: valorNum, | ||
|  |         }; | ||
|  |         await onSubmit(dataToSubmit); | ||
|  |       } | ||
|  |       onClose(); | ||
|  |     } catch (error: any) { | ||
|  |       console.error("Error en submit de RecargoZonaFormModal:", error); | ||
|  |     } finally { | ||
|  |        setLoading(false); | ||
|  |     } | ||
|  |   }; | ||
|  | 
 | ||
|  |   return ( | ||
|  |     <Modal open={open} onClose={onClose}> | ||
|  |       <Box sx={modalStyle}> | ||
|  |         <Typography variant="h6" component="h2" gutterBottom> | ||
|  |           {isEditing ? 'Editar Recargo por Zona' : 'Agregar Nuevo Recargo por Zona'} | ||
|  |         </Typography> | ||
|  |         <Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}> | ||
|  |             <FormControl fullWidth margin="dense" error={!!localErrors.idZona}> | ||
|  |                 <InputLabel id="zona-recargo-select-label" required>Zona</InputLabel> | ||
|  |                 <Select labelId="zona-recargo-select-label" label="Zona" value={idZona} | ||
|  |                     onChange={(e) => {setIdZona(e.target.value as number); handleInputChange('idZona');}} | ||
|  |                     disabled={loading || loadingZonas || isEditing} // Zona no se edita
 | ||
|  |                 > | ||
|  |                     <MenuItem value="" disabled><em>Seleccione una zona</em></MenuItem> | ||
|  |                     {zonas.map((z) => (<MenuItem key={z.idZona} value={z.idZona}>{z.nombre}</MenuItem>))} | ||
|  |                 </Select> | ||
|  |                 {localErrors.idZona && <Typography color="error" variant="caption">{localErrors.idZona}</Typography>} | ||
|  |             </FormControl> | ||
|  |             <TextField label="Vigencia Desde" type="date" value={vigenciaD} required={!isEditing} | ||
|  |                 onChange={(e) => {setVigenciaD(e.target.value); handleInputChange('vigenciaD');}} | ||
|  |                 margin="dense" fullWidth error={!!localErrors.vigenciaD} helperText={localErrors.vigenciaD || ''} | ||
|  |                 disabled={loading || isEditing} InputLabelProps={{ shrink: true }} autoFocus={!isEditing} | ||
|  |             /> | ||
|  |             {isEditing && ( | ||
|  |                 <TextField label="Vigencia Hasta (Opcional)" type="date" value={vigenciaH} | ||
|  |                     onChange={(e) => {setVigenciaH(e.target.value); handleInputChange('vigenciaH');}} | ||
|  |                     margin="dense" fullWidth error={!!localErrors.vigenciaH} helperText={localErrors.vigenciaH || ''} | ||
|  |                     disabled={loading} InputLabelProps={{ shrink: true }} | ||
|  |                 /> | ||
|  |             )} | ||
|  |             <TextField label="Valor Recargo" type="number" value={valor} required | ||
|  |                 onChange={(e) => {setValor(e.target.value); handleInputChange('valor');}} | ||
|  |                 margin="dense" fullWidth error={!!localErrors.valor} helperText={localErrors.valor || ''} | ||
|  |                 disabled={loading} | ||
|  |                 InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }} | ||
|  |                 inputProps={{ step: "0.01", lang:"es-AR" }} | ||
|  |             /> | ||
|  | 
 | ||
|  |           {errorMessage && <Alert severity="error" sx={{ mt: 2, width: '100%' }}>{errorMessage}</Alert>} | ||
|  |           {localErrors.zonas && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.zonas}</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 || loadingZonas}> | ||
|  |               {loading ? <CircularProgress size={24} /> : (isEditing ? 'Guardar Cambios' : 'Agregar Recargo')} | ||
|  |             </Button> | ||
|  |           </Box> | ||
|  |         </Box> | ||
|  |       </Box> | ||
|  |     </Modal> | ||
|  |   ); | ||
|  | }; | ||
|  | 
 | ||
|  | export default RecargoZonaFormModal; |