Comenzando la implementación final de permisos y depuración. Se sigue...

This commit is contained in:
2025-06-03 18:42:56 -03:00
parent 062cc05fd0
commit 8fb94f8cef
46 changed files with 711 additions and 493 deletions

View File

@@ -5,20 +5,19 @@ import {
import { DataGrid, type GridColDef, GridFooterContainer, GridFooter } from '@mui/x-data-grid';
import { esES } from '@mui/x-data-grid/locales';
import reportesService from '../../services/Reportes/reportesService';
// Corregir importaciones de DTOs
import type { MovimientoBobinasPorEstadoResponseDto } from '../../models/dtos/Reportes/MovimientoBobinasPorEstadoResponseDto';
import type { MovimientoBobinaEstadoDetalleDto } from '../../models/dtos/Reportes/MovimientoBobinaEstadoDetalleDto';
import type { MovimientoBobinaEstadoTotalDto } from '../../models/dtos/Reportes/MovimientoBobinaEstadoTotalDto';
import type { MovimientoBobinaEstadoDetalleDto } from '../../models/dtos/Reportes/MovimientoBobinaEstadoDetalleDto';
import type { MovimientoBobinaEstadoTotalDto } from '../../models/dtos/Reportes/MovimientoBobinaEstadoTotalDto';
import { usePermissions } from '../../hooks/usePermissions';
import SeleccionaReporteMovimientoBobinasEstado from './SeleccionaReporteMovimientoBobinasEstado';
import * as XLSX from 'xlsx';
import axios from 'axios';
// Interfaces extendidas para DataGrid con 'id'
interface DetalleMovimientoDataGrid extends MovimientoBobinaEstadoDetalleDto { // Usar el DTO correcto
interface DetalleMovimientoDataGrid extends MovimientoBobinaEstadoDetalleDto {
id: string;
}
interface TotalPorEstadoDataGrid extends MovimientoBobinaEstadoTotalDto { // Usar el DTO correcto
interface TotalPorEstadoDataGrid extends MovimientoBobinaEstadoTotalDto {
id: string;
}
@@ -35,10 +34,12 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => {
idPlanta: number;
nombrePlanta?: string;
} | null>(null);
const { tienePermiso, isSuperAdmin } = usePermissions();
const puedeVerReporte = isSuperAdmin || tienePermiso("RR006");
const numberLocaleFormatter = (value: number | null | undefined) =>
value != null ? Number(value).toLocaleString('es-AR') : '';
const dateLocaleFormatter = (value: string | null | undefined) =>
value ? new Date(value).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-';
@@ -48,13 +49,18 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => {
fechaHasta: string;
idPlanta: number;
}) => {
if (!puedeVerReporte) {
setError("No tiene permiso para generar este reporte.");
setLoading(false);
return;
}
setLoading(true);
setError(null);
setApiErrorParams(null);
setCurrentParams(params);
try {
const data = await reportesService.getMovimientoBobinasEstado(params);
const processedData: MovimientoBobinasPorEstadoResponseDto = {
detalle: data.detalle?.map((item, index) => ({ ...item, id: `detalle-${index}-${item.numeroRemito}-${item.tipoBobina}` })) || [],
totales: data.totales?.map((item, index) => ({ ...item, id: `total-${index}-${item.tipoMovimiento}` })) || []
@@ -176,35 +182,38 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => {
// eslint-disable-next-line react/display-name
const CustomFooterDetalle = () => (
<GridFooterContainer sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
borderTop: (theme) => `1px solid ${theme.palette.divider}`,
minHeight: '52px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
width: '100%',
borderTop: (theme) => `1px solid ${theme.palette.divider}`,
minHeight: '52px',
}}>
<Box sx={{
flexGrow:1,
display:'flex',
justifyContent:'flex-start',
}}>
<GridFooter
sx={{
borderTop: 'none',
width: 'auto',
'& .MuiToolbar-root': {
paddingLeft: (theme) => theme.spacing(1),
paddingRight: (theme) => theme.spacing(1),
},
'& .MuiDataGrid-selectedRowCount': { display: 'none' },
}}
/>
</Box>
<Box sx={{
flexGrow: 1,
display: 'flex',
justifyContent: 'flex-start',
}}>
<GridFooter
sx={{
borderTop: 'none',
width: 'auto',
'& .MuiToolbar-root': {
paddingLeft: (theme) => theme.spacing(1),
paddingRight: (theme) => theme.spacing(1),
},
'& .MuiDataGrid-selectedRowCount': { display: 'none' },
}}
/>
</Box>
</GridFooterContainer>
);
if (showParamSelector) {
if (!loading && !puedeVerReporte) { // Si no tiene permiso Y no está cargando, muestra error
return <Alert severity="error" sx={{ m: 2 }}>No tiene permiso para acceder a este reporte.</Alert>;
}
return (
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
@@ -218,7 +227,7 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => {
</Box>
);
}
return (
<Box sx={{ p: 2 }}>
@@ -247,12 +256,12 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => {
</Box>
</Box>
{loading && <Box sx={{ textAlign: 'center', my:2 }}><CircularProgress /></Box>}
{loading && <Box sx={{ textAlign: 'center', my: 2 }}><CircularProgress /></Box>}
{error && !loading && <Alert severity="info" sx={{ my: 2 }}>{error}</Alert>}
{!loading && !error && reportData && (
<>
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
Detalle de Movimientos
</Typography>
{rowsDetalle.length > 0 ? (
@@ -264,7 +273,7 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => {
density="compact"
sx={{ height: 'calc(100vh - 350px)' }}
slots={{ footer: CustomFooterDetalle }}
/>
</Paper>
) : (
@@ -287,11 +296,11 @@ const ReporteMovimientoBobinasEstadoPage: React.FC = () => {
/>
</Paper>
) : (
<Typography sx={{fontStyle: 'italic'}}>No hay totales por estado para mostrar.</Typography>
<Typography sx={{ fontStyle: 'italic' }}>No hay totales por estado para mostrar.</Typography>
)}
</>
)}
{!loading && !error && !reportData && currentParams && (<Typography sx={{mt: 2, fontStyle: 'italic'}}>No se encontraron datos para los criterios seleccionados.</Typography>)}
{!loading && !error && !reportData && currentParams && (<Typography sx={{ mt: 2, fontStyle: 'italic' }}>No se encontraron datos para los criterios seleccionados.</Typography>)}
</Box>
);
};