import React, { useState, useCallback, useMemo } from 'react'; import { Box, Typography, Paper, CircularProgress, Alert, Button } from '@mui/material'; 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'; 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 { 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 { id: string; } interface TotalPorEstadoDataGrid extends MovimientoBobinaEstadoTotalDto { id: string; } const ReporteMovimientoBobinasEstadoPage: React.FC = () => { const [reportData, setReportData] = useState(null); const [loading, setLoading] = useState(false); const [loadingPdf, setLoadingPdf] = useState(false); const [error, setError] = useState(null); const [apiErrorParams, setApiErrorParams] = useState(null); const [showParamSelector, setShowParamSelector] = useState(true); const [currentParams, setCurrentParams] = useState<{ fechaDesde: string; fechaHasta: string; 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' }) : '-'; const handleGenerarReporte = useCallback(async (params: { fechaDesde: string; 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}` })) || [] }; setReportData(processedData); if ((!processedData.detalle || processedData.detalle.length === 0) && (!processedData.totales || processedData.totales.length === 0)) { setError("No se encontraron datos para los parámetros seleccionados."); } setShowParamSelector(false); } catch (err: any) { const message = axios.isAxiosError(err) && err.response?.data?.message ? err.response.data.message : 'Ocurrió un error al generar el reporte.'; setApiErrorParams(message); setReportData(null); } finally { setLoading(false); } }, []); const handleVolverAParametros = useCallback(() => { setShowParamSelector(true); setReportData(null); setError(null); setApiErrorParams(null); setCurrentParams(null); }, []); const handleExportToExcel = useCallback(() => { if (!reportData || (!reportData.detalle?.length && !reportData.totales?.length)) { alert("No hay datos para exportar."); return; } const wb = XLSX.utils.book_new(); if (reportData.detalle?.length) { const detalleToExport = reportData.detalle.map(item => ({ "Tipo Bobina": item.tipoBobina, "Nro Remito": item.numeroRemito, "Fecha Movimiento": item.fechaMovimiento ? new Date(item.fechaMovimiento).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-', "Cantidad": item.cantidad, "Tipo Movimiento": item.tipoMovimiento, })); const wsDetalle = XLSX.utils.json_to_sheet(detalleToExport); const headersDetalle = Object.keys(detalleToExport[0] || {}); wsDetalle['!cols'] = headersDetalle.map(h => ({ wch: Math.max(...detalleToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length) + 2 })); wsDetalle['!freeze'] = { xSplit: 0, ySplit: 1 }; XLSX.utils.book_append_sheet(wb, wsDetalle, "DetalleMovimientos"); } if (reportData.totales?.length) { const totalesToExport = reportData.totales.map(item => ({ "Tipo Movimiento": item.tipoMovimiento, "Total Bobinas": item.totalBobinas, "Total Kilos": item.totalKilos, })); const wsTotales = XLSX.utils.json_to_sheet(totalesToExport); const headersTotales = Object.keys(totalesToExport[0] || {}); wsTotales['!cols'] = headersTotales.map(h => ({ wch: Math.max(...totalesToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length) + 2 })); wsTotales['!freeze'] = { xSplit: 0, ySplit: 1 }; XLSX.utils.book_append_sheet(wb, wsTotales, "TotalesPorEstado"); } let fileName = "ReporteMovimientoBobinasEstado"; if (currentParams) { fileName += `_${currentParams.nombrePlanta || `Planta${currentParams.idPlanta}`}`; fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`; } fileName += ".xlsx"; XLSX.writeFile(wb, fileName); }, [reportData, currentParams]); const handleGenerarYAbrirPdf = useCallback(async () => { if (!currentParams) { setError("Primero debe generar el reporte en pantalla o seleccionar parámetros."); return; } setLoadingPdf(true); setError(null); try { const blob = await reportesService.getMovimientoBobinasEstadoPdf(currentParams); if (blob.type === "application/json") { const text = await blob.text(); const msg = JSON.parse(text).message ?? "Error inesperado al generar PDF."; setError(msg); } else { const url = URL.createObjectURL(blob); const w = window.open(url, '_blank'); if (!w) alert("Permite popups para ver el PDF."); } } catch { setError('Ocurrió un error al generar el PDF.'); } finally { setLoadingPdf(false); } }, [currentParams]); // Columnas para DataGrid de Detalle de Movimientos const columnsDetalle: GridColDef[] = [ // Tipar con la interfaz correcta { field: 'tipoBobina', headerName: 'Tipo Bobina', width: 220, flex: 1.5 }, { field: 'numeroRemito', headerName: 'Nro Remito', width: 130, flex: 0.8 }, { field: 'fechaMovimiento', headerName: 'Fecha Movimiento', width: 150, flex: 1, valueFormatter: (value) => dateLocaleFormatter(value as string) }, { field: 'cantidad', headerName: 'Cantidad', type: 'number', width: 120, align: 'right', headerAlign: 'right', flex: 0.7, valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'tipoMovimiento', headerName: 'Tipo Movimiento', width: 150, flex: 1 }, ]; // Columnas para DataGrid de Totales por Estado const columnsTotales: GridColDef[] = [ // Tipar con la interfaz correcta { field: 'tipoMovimiento', headerName: 'Tipo Movimiento', width: 200, flex: 1 }, { field: 'totalBobinas', headerName: 'Total Bobinas', type: 'number', width: 150, align: 'right', headerAlign: 'right', flex: 0.8, valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'totalKilos', headerName: 'Total Kilos', type: 'number', width: 150, align: 'right', headerAlign: 'right', flex: 0.8, valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, ]; const rowsDetalle = useMemo(() => (reportData?.detalle as DetalleMovimientoDataGrid[]) || [], [reportData]); const rowsTotales = useMemo(() => (reportData?.totales as TotalPorEstadoDataGrid[]) || [], [reportData]); // eslint-disable-next-line react/display-name const CustomFooterDetalle = () => ( `1px solid ${theme.palette.divider}`, minHeight: '52px', }}> theme.spacing(1), paddingRight: (theme) => theme.spacing(1), }, '& .MuiDataGrid-selectedRowCount': { display: 'none' }, }} /> ); if (showParamSelector) { if (!loading && !puedeVerReporte) { // Si no tiene permiso Y no está cargando, muestra error return No tiene permiso para acceder a este reporte.; } return ( ); } return ( Reporte: Movimiento de Bobinas por Estado {currentParams?.nombrePlanta ? `(${currentParams.nombrePlanta})` : ''} {loading && } {error && !loading && {error}} {!loading && !error && reportData && ( <> Detalle de Movimientos {rowsDetalle.length > 0 ? ( ) : ( No hay detalles de movimientos para mostrar. )} Totales por Estado {rowsTotales.length > 0 ? ( ) : ( No hay totales por estado para mostrar. )} )} {!loading && !error && !reportData && currentParams && (No se encontraron datos para los criterios seleccionados.)} ); }; export default ReporteMovimientoBobinasEstadoPage;