Ya perdí el hilo de los cambios pero ahi van.
This commit is contained in:
231
Frontend/src/pages/Contables/GestionarPagosDistribuidorPage.tsx
Normal file
231
Frontend/src/pages/Contables/GestionarPagosDistribuidorPage.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import {
|
||||
Box, Typography, TextField, Button, Paper, IconButton, Menu, MenuItem, Chip,
|
||||
Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TablePagination,
|
||||
CircularProgress, Alert, FormControl, InputLabel, Select, Tooltip
|
||||
} from '@mui/material';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import FilterListIcon from '@mui/icons-material/FilterList';
|
||||
|
||||
import pagoDistribuidorService from '../../services/Contables/pagoDistribuidorService';
|
||||
import distribuidorService from '../../services/Distribucion/distribuidorService';
|
||||
import empresaService from '../../services/Distribucion/empresaService';
|
||||
|
||||
import type { PagoDistribuidorDto } from '../../models/dtos/Contables/PagoDistribuidorDto';
|
||||
import type { CreatePagoDistribuidorDto } from '../../models/dtos/Contables/CreatePagoDistribuidorDto';
|
||||
import type { UpdatePagoDistribuidorDto } from '../../models/dtos/Contables/UpdatePagoDistribuidorDto';
|
||||
import type { DistribuidorDto } from '../../models/dtos/Distribucion/DistribuidorDto';
|
||||
import type { EmpresaDto } from '../../models/dtos/Distribucion/EmpresaDto';
|
||||
|
||||
import PagoDistribuidorFormModal from '../../components/Modals/Contables/PagoDistribuidorFormModal';
|
||||
import { usePermissions } from '../../hooks/usePermissions';
|
||||
import axios from 'axios';
|
||||
|
||||
const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
const [pagos, setPagos] = useState<PagoDistribuidorDto[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||
const [filtroTipoMov, setFiltroTipoMov] = useState<'Recibido' | 'Realizado' | ''>('');
|
||||
|
||||
const [distribuidores, setDistribuidores] = useState<DistribuidorDto[]>([]);
|
||||
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||
const [loadingFiltersDropdown, setLoadingFiltersDropdown] = useState(false);
|
||||
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [editingPago, setEditingPago] = useState<PagoDistribuidorDto | null>(null);
|
||||
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [selectedRow, setSelectedRow] = useState<PagoDistribuidorDto | null>(null);
|
||||
|
||||
const { tienePermiso, isSuperAdmin } = usePermissions();
|
||||
// Permisos CP001 (Ver), CP002 (Crear), CP003 (Modificar), CP004 (Eliminar)
|
||||
const puedeVer = isSuperAdmin || tienePermiso("CP001");
|
||||
const puedeCrear = isSuperAdmin || tienePermiso("CP002");
|
||||
const puedeModificar = isSuperAdmin || tienePermiso("CP003");
|
||||
const puedeEliminar = isSuperAdmin || tienePermiso("CP004");
|
||||
|
||||
const fetchFiltersDropdownData = useCallback(async () => {
|
||||
setLoadingFiltersDropdown(true);
|
||||
try {
|
||||
const [distData, empData] = await Promise.all([
|
||||
distribuidorService.getAllDistribuidores(),
|
||||
empresaService.getAllEmpresas()
|
||||
]);
|
||||
setDistribuidores(distData);
|
||||
setEmpresas(empData);
|
||||
} catch (err) { console.error(err); setError("Error al cargar opciones de filtro.");
|
||||
} finally { setLoadingFiltersDropdown(false); }
|
||||
}, []);
|
||||
|
||||
useEffect(() => { fetchFiltersDropdownData(); }, [fetchFiltersDropdownData]);
|
||||
|
||||
const cargarPagos = useCallback(async () => {
|
||||
if (!puedeVer) { setError("No tiene permiso."); setLoading(false); return; }
|
||||
setLoading(true); setError(null); setApiErrorMessage(null);
|
||||
try {
|
||||
const params = {
|
||||
fechaDesde: filtroFechaDesde || null, fechaHasta: filtroFechaHasta || null,
|
||||
idDistribuidor: filtroIdDistribuidor ? Number(filtroIdDistribuidor) : null,
|
||||
idEmpresa: filtroIdEmpresa ? Number(filtroIdEmpresa) : null,
|
||||
tipoMovimiento: filtroTipoMov || null,
|
||||
};
|
||||
const data = await pagoDistribuidorService.getAllPagosDistribuidor(params);
|
||||
setPagos(data);
|
||||
} catch (err) { console.error(err); setError('Error al cargar los pagos.');
|
||||
} finally { setLoading(false); }
|
||||
}, [puedeVer, filtroFechaDesde, filtroFechaHasta, filtroIdDistribuidor, filtroIdEmpresa, filtroTipoMov]);
|
||||
|
||||
useEffect(() => { cargarPagos(); }, [cargarPagos]);
|
||||
|
||||
const handleOpenModal = (item?: PagoDistribuidorDto) => {
|
||||
setEditingPago(item || null); setApiErrorMessage(null); setModalOpen(true);
|
||||
};
|
||||
const handleCloseModal = () => { setModalOpen(false); setEditingPago(null); };
|
||||
|
||||
const handleSubmitModal = async (data: CreatePagoDistribuidorDto | UpdatePagoDistribuidorDto, idPago?: number) => {
|
||||
setApiErrorMessage(null);
|
||||
try {
|
||||
if (idPago && editingPago) {
|
||||
await pagoDistribuidorService.updatePagoDistribuidor(idPago, data as UpdatePagoDistribuidorDto);
|
||||
} else {
|
||||
await pagoDistribuidorService.createPagoDistribuidor(data as CreatePagoDistribuidorDto);
|
||||
}
|
||||
cargarPagos();
|
||||
} catch (err: any) {
|
||||
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar el pago.';
|
||||
setApiErrorMessage(message); throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (idPago: number) => {
|
||||
if (window.confirm(`¿Seguro de eliminar este pago (ID: ${idPago})? Esta acción revertirá el impacto en el saldo.`)) {
|
||||
setApiErrorMessage(null);
|
||||
try { await pagoDistribuidorService.deletePagoDistribuidor(idPago); cargarPagos(); }
|
||||
catch (err: any) { const msg = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar.'; setApiErrorMessage(msg); }
|
||||
}
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: PagoDistribuidorDto) => {
|
||||
setAnchorEl(event.currentTarget); setSelectedRow(item);
|
||||
};
|
||||
const handleMenuClose = () => { setAnchorEl(null); setSelectedRow(null); };
|
||||
|
||||
const handleChangePage = (_event: unknown, newPage: number) => setPage(newPage);
|
||||
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRowsPerPage(parseInt(event.target.value, 10)); setPage(0);
|
||||
};
|
||||
const displayData = pagos.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage);
|
||||
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString + 'T00:00:00Z').toLocaleDateString('es-AR') : '-';
|
||||
|
||||
if (!loading && !puedeVer && !loadingFiltersDropdown) return <Box sx={{ p: 2 }}><Alert severity="error">{error || "Acceso denegado."}</Alert></Box>;
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h4" gutterBottom>Pagos de Distribuidores</Typography>
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>Filtros <FilterListIcon fontSize="small"/></Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, alignItems: 'center', mb: 2}}>
|
||||
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde} onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170}}/>
|
||||
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta} onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }} sx={{minWidth: 170}}/>
|
||||
<FormControl size="small" sx={{minWidth: 200, flexGrow: 1}} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Distribuidor</InputLabel>
|
||||
<Select value={filtroIdDistribuidor} label="Distribuidor" onChange={(e) => setFiltroIdDistribuidor(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
{distribuidores.map(d => <MenuItem key={d.idDistribuidor} value={d.idDistribuidor}>{d.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{minWidth: 200, flexGrow: 1}} disabled={loadingFiltersDropdown}>
|
||||
<InputLabel>Empresa (Saldo)</InputLabel>
|
||||
<Select value={filtroIdEmpresa} label="Empresa (Saldo)" onChange={(e) => setFiltroIdEmpresa(e.target.value as number | string)}>
|
||||
<MenuItem value=""><em>Todas</em></MenuItem>
|
||||
{empresas.map(e => <MenuItem key={e.idEmpresa} value={e.idEmpresa}>{e.nombre}</MenuItem>)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{minWidth: 150, flexGrow: 1}}>
|
||||
<InputLabel>Tipo Mov.</InputLabel>
|
||||
<Select value={filtroTipoMov} label="Tipo Mov." onChange={(e) => setFiltroTipoMov(e.target.value as 'Recibido' | 'Realizado' | '')}>
|
||||
<MenuItem value=""><em>Todos</em></MenuItem>
|
||||
<MenuItem value="Recibido">Recibido</MenuItem>
|
||||
<MenuItem value="Realizado">Realizado</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
{puedeCrear && (<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()}>Registrar Pago</Button>)}
|
||||
</Paper>
|
||||
|
||||
{loading && <Box sx={{ display: 'flex', justifyContent: 'center', my: 2 }}><CircularProgress /></Box>}
|
||||
{error && !loading && <Alert severity="error" sx={{my: 2}}>{error}</Alert>}
|
||||
{apiErrorMessage && <Alert severity="error" sx={{my: 2}}>{apiErrorMessage}</Alert>}
|
||||
|
||||
{!loading && !error && puedeVer && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table size="small">
|
||||
<TableHead><TableRow>
|
||||
<TableCell>Fecha</TableCell><TableCell>Distribuidor</TableCell><TableCell>Empresa (Saldo)</TableCell>
|
||||
<TableCell>Tipo Mov.</TableCell><TableCell>Recibo N°</TableCell>
|
||||
<TableCell align="right">Monto</TableCell><TableCell>Tipo Pago</TableCell>
|
||||
<TableCell>Detalle</TableCell>
|
||||
{(puedeModificar || puedeEliminar) && <TableCell align="right">Acciones</TableCell>}
|
||||
</TableRow></TableHead>
|
||||
<TableBody>
|
||||
{displayData.length === 0 ? (
|
||||
<TableRow><TableCell colSpan={puedeModificar || puedeEliminar ? 9 : 8} align="center">No se encontraron pagos.</TableCell></TableRow>
|
||||
) : (
|
||||
displayData.map((p) => (
|
||||
<TableRow key={p.idPago} hover>
|
||||
<TableCell>{formatDate(p.fecha)}</TableCell><TableCell>{p.nombreDistribuidor}</TableCell>
|
||||
<TableCell>{p.nombreEmpresa}</TableCell>
|
||||
<TableCell>
|
||||
<Chip label={p.tipoMovimiento} color={p.tipoMovimiento === 'Recibido' ? 'success' : 'warning'} size="small"/>
|
||||
</TableCell>
|
||||
<TableCell>{p.recibo}</TableCell>
|
||||
<TableCell align="right">${p.monto.toFixed(2)}</TableCell>
|
||||
<TableCell>{p.nombreTipoPago}</TableCell>
|
||||
<TableCell><Tooltip title={p.detalle || ''}><Box sx={{maxWidth: 150, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'}}>{p.detalle || '-'}</Box></Tooltip></TableCell>
|
||||
{(puedeModificar || puedeEliminar) && (
|
||||
<TableCell align="right">
|
||||
<IconButton onClick={(e) => handleMenuOpen(e, p)} disabled={!puedeModificar && !puedeEliminar}><MoreVertIcon /></IconButton>
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
)))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[10, 25, 50]} component="div" count={pagos.length}
|
||||
rowsPerPage={rowsPerPage} page={page} onPageChange={handleChangePage}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage} labelRowsPerPage="Filas por página:"
|
||||
/>
|
||||
</TableContainer>
|
||||
)}
|
||||
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
{puedeModificar && selectedRow && (
|
||||
<MenuItem onClick={() => { handleOpenModal(selectedRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Modificar</MenuItem>)}
|
||||
{puedeEliminar && selectedRow && (
|
||||
<MenuItem onClick={() => handleDelete(selectedRow.idPago)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar</MenuItem>)}
|
||||
</Menu>
|
||||
|
||||
<PagoDistribuidorFormModal
|
||||
open={modalOpen} onClose={handleCloseModal} onSubmit={handleSubmitModal}
|
||||
initialData={editingPago} errorMessage={apiErrorMessage}
|
||||
clearErrorMessage={() => setApiErrorMessage(null)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default GestionarPagosDistribuidorPage;
|
||||
Reference in New Issue
Block a user