141 lines
6.2 KiB
TypeScript
141 lines
6.2 KiB
TypeScript
|
|
// Archivo: Frontend/src/components/Modals/Suscripciones/PagoManualModal.tsx
|
||
|
|
|
||
|
|
import React, { useState, useEffect } from 'react';
|
||
|
|
import { Modal, Box, Typography, TextField, Button, CircularProgress, Alert, FormControl, InputLabel, Select, MenuItem, type SelectChangeEvent, InputAdornment } from '@mui/material';
|
||
|
|
import type { FacturaDto } from '../../../models/dtos/Suscripciones/FacturaDto';
|
||
|
|
import type { CreatePagoDto } from '../../../models/dtos/Suscripciones/CreatePagoDto';
|
||
|
|
import type { FormaPagoDto } from '../../../models/dtos/Suscripciones/FormaPagoDto';
|
||
|
|
import formaPagoService from '../../../services/Suscripciones/formaPagoService';
|
||
|
|
|
||
|
|
const modalStyle = {
|
||
|
|
position: 'absolute' as 'absolute',
|
||
|
|
top: '50%',
|
||
|
|
left: '50%',
|
||
|
|
transform: 'translate(-50%, -50%)',
|
||
|
|
width: { xs: '95%', sm: '80%', md: '500px' },
|
||
|
|
bgcolor: 'background.paper',
|
||
|
|
border: '2px solid #000',
|
||
|
|
boxShadow: 24,
|
||
|
|
p: 4,
|
||
|
|
maxHeight: '90vh',
|
||
|
|
overflowY: 'auto'
|
||
|
|
};
|
||
|
|
|
||
|
|
interface PagoManualModalProps {
|
||
|
|
open: boolean;
|
||
|
|
onClose: () => void;
|
||
|
|
onSubmit: (data: CreatePagoDto) => Promise<void>;
|
||
|
|
factura: FacturaDto | null;
|
||
|
|
errorMessage?: string | null;
|
||
|
|
clearErrorMessage: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const PagoManualModal: React.FC<PagoManualModalProps> = ({ open, onClose, onSubmit, factura, errorMessage, clearErrorMessage }) => {
|
||
|
|
const [formData, setFormData] = useState<Partial<CreatePagoDto>>({});
|
||
|
|
const [formasDePago, setFormasDePago] = useState<FormaPagoDto[]>([]);
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
const [loadingFormasPago, setLoadingFormasPago] = useState(false);
|
||
|
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
const fetchFormasDePago = async () => {
|
||
|
|
setLoadingFormasPago(true);
|
||
|
|
try {
|
||
|
|
const data = await formaPagoService.getAllFormasDePago();
|
||
|
|
setFormasDePago(data.filter(fp => !fp.requiereCBU));
|
||
|
|
} catch (error) {
|
||
|
|
setLocalErrors(prev => ({ ...prev, formasDePago: 'Error al cargar formas de pago.' }));
|
||
|
|
} finally {
|
||
|
|
setLoadingFormasPago(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (open && factura) {
|
||
|
|
fetchFormasDePago();
|
||
|
|
setFormData({
|
||
|
|
idFactura: factura.idFactura,
|
||
|
|
monto: factura.importeFinal,
|
||
|
|
fechaPago: new Date().toISOString().split('T')[0]
|
||
|
|
});
|
||
|
|
setLocalErrors({});
|
||
|
|
}
|
||
|
|
}, [open, factura]);
|
||
|
|
|
||
|
|
const validate = (): boolean => {
|
||
|
|
const errors: { [key: string]: string | null } = {};
|
||
|
|
if (!formData.idFormaPago) errors.idFormaPago = "Seleccione una forma de pago.";
|
||
|
|
if (!formData.monto || formData.monto <= 0) errors.monto = "El monto debe ser mayor a cero.";
|
||
|
|
if (!formData.fechaPago) errors.fechaPago = "La fecha de pago es obligatoria.";
|
||
|
|
setLocalErrors(errors);
|
||
|
|
return Object.keys(errors).length === 0;
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||
|
|
const { name, value } = e.target;
|
||
|
|
const finalValue = name === 'monto' && value !== '' ? parseFloat(value) : value;
|
||
|
|
setFormData(prev => ({ ...prev, [name]: finalValue }));
|
||
|
|
if (localErrors[name]) setLocalErrors(prev => ({ ...prev, [name]: null }));
|
||
|
|
if (errorMessage) clearErrorMessage();
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSelectChange = (e: SelectChangeEvent<any>) => {
|
||
|
|
const { name, value } = e.target;
|
||
|
|
setFormData(prev => ({ ...prev, [name]: value }));
|
||
|
|
if (localErrors[name]) setLocalErrors(prev => ({ ...prev, [name]: null }));
|
||
|
|
if (errorMessage) clearErrorMessage();
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||
|
|
e.preventDefault();
|
||
|
|
clearErrorMessage();
|
||
|
|
if (!validate()) return;
|
||
|
|
|
||
|
|
setLoading(true);
|
||
|
|
let success = false;
|
||
|
|
try {
|
||
|
|
await onSubmit(formData as CreatePagoDto);
|
||
|
|
success = true;
|
||
|
|
} catch (error) {
|
||
|
|
success = false;
|
||
|
|
} finally {
|
||
|
|
setLoading(false);
|
||
|
|
if (success) onClose();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (!factura) return null;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Modal open={open} onClose={onClose}>
|
||
|
|
<Box sx={modalStyle}>
|
||
|
|
<Typography variant="h6">Registrar Pago Manual</Typography>
|
||
|
|
<Typography variant="body2" color="text.secondary" gutterBottom>
|
||
|
|
Factura #{factura.idFactura} para {factura.nombreSuscriptor}
|
||
|
|
</Typography>
|
||
|
|
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 2 }}>
|
||
|
|
<TextField name="fechaPago" label="Fecha de Pago" type="date" value={formData.fechaPago || ''} onChange={handleInputChange} required fullWidth margin="dense" InputLabelProps={{ shrink: true }} error={!!localErrors.fechaPago} helperText={localErrors.fechaPago} />
|
||
|
|
<FormControl fullWidth margin="dense" error={!!localErrors.idFormaPago}>
|
||
|
|
<InputLabel id="forma-pago-label" required>Forma de Pago</InputLabel>
|
||
|
|
<Select name="idFormaPago" labelId="forma-pago-label" value={formData.idFormaPago || ''} onChange={handleSelectChange} label="Forma de Pago" disabled={loadingFormasPago}>
|
||
|
|
{formasDePago.map(fp => <MenuItem key={fp.idFormaPago} value={fp.idFormaPago}>{fp.nombre}</MenuItem>)}
|
||
|
|
</Select>
|
||
|
|
</FormControl>
|
||
|
|
<TextField name="monto" label="Monto Pagado" type="number" value={formData.monto || ''} onChange={handleInputChange} required fullWidth margin="dense" error={!!localErrors.monto} helperText={localErrors.monto} InputProps={{ startAdornment: <InputAdornment position="start">$</InputAdornment> }} inputProps={{ step: "0.01" }} />
|
||
|
|
<TextField name="referencia" label="Referencia (Opcional)" value={formData.referencia || ''} onChange={handleInputChange} fullWidth margin="dense" />
|
||
|
|
<TextField name="observaciones" label="Observaciones (Opcional)" value={formData.observaciones || ''} onChange={handleInputChange} fullWidth margin="dense" multiline rows={2} />
|
||
|
|
|
||
|
|
{errorMessage && <Alert severity="error" sx={{ mt: 2 }}>{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 || loadingFormasPago}>
|
||
|
|
{loading ? <CircularProgress size={24} /> : 'Registrar Pago'}
|
||
|
|
</Button>
|
||
|
|
</Box>
|
||
|
|
</Box>
|
||
|
|
</Box>
|
||
|
|
</Modal>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default PagoManualModal;
|