301 lines
14 KiB
TypeScript
301 lines
14 KiB
TypeScript
// src/pages/Distribucion/GestionarNovedadesCanillaPage.tsx
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
import {
|
|
Box, Typography, Button, Paper, IconButton, Menu, MenuItem,
|
|
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
|
|
CircularProgress, Alert, TextField, Tooltip
|
|
} from '@mui/material';
|
|
import AddIcon from '@mui/icons-material/Add';
|
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
import EditIcon from '@mui/icons-material/Edit';
|
|
import DeleteIcon from '@mui/icons-material/Delete';
|
|
import FilterListIcon from '@mui/icons-material/FilterList';
|
|
|
|
import novedadCanillaService from '../../services/Distribucion/novedadCanillaService';
|
|
import canillaService from '../../services/Distribucion/canillaService';
|
|
import type { NovedadCanillaDto } from '../../models/dtos/Distribucion/NovedadCanillaDto';
|
|
import type { CreateNovedadCanillaDto } from '../../models/dtos/Distribucion/CreateNovedadCanillaDto';
|
|
import type { UpdateNovedadCanillaDto } from '../../models/dtos/Distribucion/UpdateNovedadCanillaDto';
|
|
import type { CanillaDto } from '../../models/dtos/Distribucion/CanillaDto';
|
|
import NovedadCanillaFormModal from '../../components/Modals/Distribucion/NovedadCanillaFormModal';
|
|
import { usePermissions } from '../../hooks/usePermissions';
|
|
import axios from 'axios';
|
|
|
|
const GestionarNovedadesCanillaPage: React.FC = () => {
|
|
const { idCanilla: idCanillaStr } = useParams<{ idCanilla: string }>();
|
|
const navigate = useNavigate();
|
|
const idCanilla = Number(idCanillaStr);
|
|
|
|
const [canillita, setCanillita] = useState<CanillaDto | null>(null);
|
|
const [novedades, setNovedades] = useState<NovedadCanillaDto[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [errorPage, setErrorPage] = useState<string | null>(null); // Error general de la página
|
|
|
|
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>('');
|
|
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>('');
|
|
|
|
const [modalOpen, setModalOpen] = useState(false);
|
|
const [editingNovedad, setEditingNovedad] = useState<NovedadCanillaDto | null>(null);
|
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null); // Para modal/delete
|
|
|
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
|
const [selectedNovedadRow, setSelectedNovedadRow] = useState<NovedadCanillaDto | null>(null);
|
|
|
|
const { tienePermiso, isSuperAdmin } = usePermissions();
|
|
const puedeGestionarNovedades = isSuperAdmin || tienePermiso("CG006");
|
|
const puedeVerCanillitas = isSuperAdmin || tienePermiso("CG001");
|
|
|
|
// Cargar datos del canillita (solo una vez o si idCanilla cambia)
|
|
useEffect(() => {
|
|
if (isNaN(idCanilla)) {
|
|
setErrorPage("ID de Canillita inválido.");
|
|
setLoading(false); // Detener carga principal
|
|
return;
|
|
}
|
|
if (!puedeVerCanillitas && !puedeGestionarNovedades) {
|
|
setErrorPage("No tiene permiso para acceder a esta sección.");
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
setLoading(true); // Iniciar carga para datos del canillita
|
|
const fetchCanillita = async () => {
|
|
try {
|
|
if (puedeVerCanillitas) {
|
|
const canData = await canillaService.getCanillaById(idCanilla);
|
|
setCanillita(canData);
|
|
} else {
|
|
// Si no puede ver detalles del canillita pero sí novedades, al menos mostrar ID
|
|
setCanillita({ idCanilla, nomApe: `ID ${idCanilla}` } as CanillaDto);
|
|
}
|
|
} catch (err) {
|
|
console.error("Error cargando datos del canillita:", err);
|
|
setErrorPage(`Error al cargar datos del canillita (ID: ${idCanilla}).`);
|
|
}
|
|
// No ponemos setLoading(false) aquí, porque la carga de novedades sigue.
|
|
};
|
|
fetchCanillita();
|
|
}, [idCanilla, puedeVerCanillitas, puedeGestionarNovedades]);
|
|
|
|
|
|
// Cargar/filtrar novedades
|
|
const cargarNovedades = useCallback(async () => {
|
|
if (isNaN(idCanilla) || (!puedeGestionarNovedades && !puedeVerCanillitas)) {
|
|
// Los permisos ya se validaron en el useEffect anterior, pero es bueno tenerlo
|
|
return;
|
|
}
|
|
// Si ya está cargando los datos del canillita, no iniciar otra carga paralela
|
|
// Se usará el mismo 'loading' para ambas operaciones iniciales.
|
|
// if (!loading) setLoading(true); // No es necesario si el useEffect anterior ya lo hizo
|
|
|
|
setApiErrorMessage(null); // Limpiar errores de API de acciones previas
|
|
// setErrorPage(null); // No limpiar error de página aquí, podría ser por el canillita
|
|
|
|
try {
|
|
const params = {
|
|
fechaDesde: filtroFechaDesde || null,
|
|
fechaHasta: filtroFechaHasta || null,
|
|
};
|
|
const dataNovedades = await novedadCanillaService.getNovedadesPorCanilla(idCanilla, params);
|
|
setNovedades(dataNovedades);
|
|
// Si no hay datos con filtros, no es un error de API, simplemente no hay datos.
|
|
// El mensaje de "no hay novedades" se maneja en la tabla.
|
|
} catch (err: any) {
|
|
console.error("Error al cargar/filtrar novedades:", err);
|
|
const message = axios.isAxiosError(err) && err.response?.data?.message
|
|
? err.response.data.message
|
|
: 'Error al cargar las novedades.';
|
|
setErrorPage(message); // Usar el error de página para problemas de carga de novedades
|
|
setNovedades([]); // Limpiar en caso de error
|
|
} finally {
|
|
// Solo poner setLoading(false) después de que AMBAS cargas (canillita y novedades) se intenten.
|
|
// Como se llaman en secuencia implícita por los useEffect, el último setLoading(false) es el de novedades.
|
|
setLoading(false);
|
|
}
|
|
}, [idCanilla, puedeGestionarNovedades, puedeVerCanillitas, filtroFechaDesde, filtroFechaHasta]);
|
|
|
|
// useEffect para cargar novedades cuando los filtros o el canillita (o permisos) cambian
|
|
useEffect(() => {
|
|
// Solo cargar si tenemos un idCanilla válido y permisos
|
|
if (!isNaN(idCanilla) && (puedeGestionarNovedades || puedeVerCanillitas)) {
|
|
cargarNovedades();
|
|
} else if (isNaN(idCanilla)){
|
|
setErrorPage("ID de Canillita inválido.");
|
|
setLoading(false);
|
|
} else if (!puedeGestionarNovedades && !puedeVerCanillitas) {
|
|
setErrorPage("No tiene permiso para acceder a esta sección.");
|
|
setLoading(false);
|
|
}
|
|
}, [idCanilla, cargarNovedades, puedeGestionarNovedades, puedeVerCanillitas]); // `cargarNovedades` ya tiene sus dependencias
|
|
|
|
|
|
const handleOpenModal = (item?: NovedadCanillaDto) => {
|
|
if (!puedeGestionarNovedades) {
|
|
setApiErrorMessage("No tiene permiso para agregar o editar novedades.");
|
|
return;
|
|
}
|
|
setEditingNovedad(item || null); setApiErrorMessage(null); setModalOpen(true);
|
|
};
|
|
const handleCloseModal = () => {
|
|
setModalOpen(false); setEditingNovedad(null);
|
|
};
|
|
|
|
const handleSubmitModal = async (data: CreateNovedadCanillaDto | UpdateNovedadCanillaDto, idNovedad?: number) => {
|
|
if (!puedeGestionarNovedades) return;
|
|
setApiErrorMessage(null);
|
|
try {
|
|
if (editingNovedad && idNovedad) {
|
|
await novedadCanillaService.updateNovedad(idNovedad, data as UpdateNovedadCanillaDto);
|
|
} else {
|
|
await novedadCanillaService.createNovedad(data as CreateNovedadCanillaDto);
|
|
}
|
|
cargarNovedades(); // Recargar lista de novedades
|
|
} catch (err: any) {
|
|
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al guardar la novedad.';
|
|
setApiErrorMessage(message); throw err;
|
|
}
|
|
};
|
|
|
|
const handleDelete = async (idNovedadDelRow: number) => {
|
|
if (!puedeGestionarNovedades) return;
|
|
if (window.confirm(`¿Seguro de eliminar esta novedad (ID: ${idNovedadDelRow})?`)) {
|
|
setApiErrorMessage(null);
|
|
try {
|
|
await novedadCanillaService.deleteNovedad(idNovedadDelRow);
|
|
cargarNovedades(); // Recargar lista de novedades
|
|
} catch (err: any) {
|
|
const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Error al eliminar la novedad.';
|
|
setApiErrorMessage(message);
|
|
}
|
|
}
|
|
handleMenuClose();
|
|
};
|
|
|
|
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>, item: NovedadCanillaDto) => {
|
|
setAnchorEl(event.currentTarget); setSelectedNovedadRow(item);
|
|
};
|
|
const handleMenuClose = () => {
|
|
setAnchorEl(null); setSelectedNovedadRow(null);
|
|
};
|
|
|
|
const formatDate = (dateString?: string | null) => dateString ? new Date(dateString).toLocaleDateString('es-AR', {timeZone: 'UTC'}) : '-';
|
|
|
|
|
|
if (loading && !canillita) { // Muestra cargando solo si aún no tenemos los datos del canillita
|
|
return <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}><CircularProgress /></Box>;
|
|
}
|
|
|
|
if (errorPage && !canillita) { // Si hay un error al cargar el canillita, no mostrar nada más
|
|
return <Alert severity="error" sx={{ m: 2 }}>{errorPage}</Alert>;
|
|
}
|
|
|
|
// Si no tiene permiso para la sección en general
|
|
if (!puedeGestionarNovedades && !puedeVerCanillitas) {
|
|
return <Alert severity="error" sx={{ m: 2 }}>Acceso denegado.</Alert>;
|
|
}
|
|
|
|
|
|
return (
|
|
<Box sx={{ p: 2 }}>
|
|
<Button startIcon={<ArrowBackIcon />} onClick={() => navigate(`/distribucion/canillas`)} sx={{ mb: 2 }}>
|
|
Volver a Canillitas
|
|
</Button>
|
|
<Typography variant="h5" gutterBottom>
|
|
Novedades de: {canillita?.nomApe || `Canillita ID ${idCanilla}`}
|
|
</Typography>
|
|
|
|
<Paper sx={{ p: 2, mb: 2 }}>
|
|
<Box sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 2}}>
|
|
{puedeGestionarNovedades && (
|
|
<Button variant="contained" startIcon={<AddIcon />} onClick={() => handleOpenModal()} sx={{ mb: {xs: 2, sm:0} }}>
|
|
Agregar Novedad
|
|
</Button>
|
|
)}
|
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
<FilterListIcon sx={{color: 'action.active', alignSelf:'center'}} />
|
|
<TextField label="Fecha Desde" type="date" size="small" value={filtroFechaDesde}
|
|
onChange={(e) => setFiltroFechaDesde(e.target.value)} InputLabelProps={{ shrink: true }}
|
|
disabled={loading} // Deshabilitar durante cualquier carga
|
|
/>
|
|
<TextField label="Fecha Hasta" type="date" size="small" value={filtroFechaHasta}
|
|
onChange={(e) => setFiltroFechaHasta(e.target.value)} InputLabelProps={{ shrink: true }}
|
|
disabled={loading} // Deshabilitar durante cualquier carga
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
</Paper>
|
|
|
|
{/* Mostrar error de API (de submit/delete) o error de carga de novedades */}
|
|
{(apiErrorMessage || (errorPage && novedades.length === 0 && !loading)) && (
|
|
<Alert severity="error" sx={{ my: 2 }}>{apiErrorMessage || errorPage}</Alert>
|
|
)}
|
|
|
|
{loading && <Box sx={{display:'flex', justifyContent:'center', my:2}}><CircularProgress size={30} /></Box>}
|
|
|
|
<TableContainer component={Paper}>
|
|
<Table size="small">
|
|
<TableHead><TableRow>
|
|
<TableCell sx={{ fontWeight: 'bold' }}>Fecha</TableCell>
|
|
<TableCell sx={{ fontWeight: 'bold', width: '70%' }}>Detalle de Novedad</TableCell>
|
|
{puedeGestionarNovedades && <TableCell align="right" sx={{ fontWeight: 'bold' }}>Acciones</TableCell>}
|
|
</TableRow></TableHead>
|
|
<TableBody>
|
|
{novedades.length === 0 && !loading ? (
|
|
<TableRow><TableCell colSpan={puedeGestionarNovedades ? 3 : 2} align="center">
|
|
No hay novedades registradas { (filtroFechaDesde || filtroFechaHasta) && "con los filtros aplicados"}.
|
|
</TableCell></TableRow>
|
|
) : (
|
|
novedades.map((nov) => (
|
|
<TableRow key={nov.idNovedad} hover>
|
|
<TableCell>{formatDate(nov.fecha)}</TableCell>
|
|
<TableCell>
|
|
<Tooltip title={nov.detalle || ''} arrow>
|
|
<Typography variant="body2" sx={{
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
maxWidth: '500px'
|
|
}}>
|
|
{nov.detalle || '-'}
|
|
</Typography>
|
|
</Tooltip>
|
|
</TableCell>
|
|
{puedeGestionarNovedades && (
|
|
<TableCell align="right">
|
|
<IconButton onClick={(e) => handleMenuOpen(e, nov)} disabled={!puedeGestionarNovedades}>
|
|
<MoreVertIcon />
|
|
</IconButton>
|
|
</TableCell>
|
|
)}
|
|
</TableRow>
|
|
)))}
|
|
</TableBody>
|
|
</Table>
|
|
</TableContainer>
|
|
|
|
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
|
{puedeGestionarNovedades && selectedNovedadRow && (
|
|
<MenuItem onClick={() => { handleOpenModal(selectedNovedadRow); handleMenuClose(); }}><EditIcon fontSize="small" sx={{mr:1}}/> Editar</MenuItem>)}
|
|
{puedeGestionarNovedades && selectedNovedadRow && (
|
|
<MenuItem onClick={() => handleDelete(selectedNovedadRow.idNovedad)}><DeleteIcon fontSize="small" sx={{mr:1}}/> Eliminar</MenuItem>)}
|
|
</Menu>
|
|
|
|
{idCanilla &&
|
|
<NovedadCanillaFormModal
|
|
open={modalOpen}
|
|
onClose={handleCloseModal}
|
|
onSubmit={handleSubmitModal}
|
|
idCanilla={idCanilla}
|
|
nombreCanilla={canillita?.nomApe}
|
|
initialData={editingNovedad}
|
|
errorMessage={apiErrorMessage}
|
|
clearErrorMessage={() => setApiErrorMessage(null)}
|
|
/>
|
|
}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default GestionarNovedadesCanillaPage; |