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 type { Theme } from '@mui/material/styles'; import { esES } from '@mui/x-data-grid/locales'; import reportesService from '../../services/Reportes/reportesService'; import type { ComparativaConsumoBobinasDto } from '../../models/dtos/Reportes/ComparativaConsumoBobinasDto'; import SeleccionaReporteComparativaConsumoBobinas from './SeleccionaReporteComparativaConsumoBobinas'; import { usePermissions } from '../../hooks/usePermissions'; import * as XLSX from 'xlsx'; import axios from 'axios'; // Interfaz extendida para DataGrid interface ComparativaConsumoBobinasDataGridDto extends ComparativaConsumoBobinasDto { id: string; } const ReporteComparativaConsumoBobinasPage: React.FC = () => { const [reportData, setReportData] = useState([]); // Usar tipo extendido 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<{ fechaInicioMesA: string; fechaFinMesA: string; fechaInicioMesB: string; fechaFinMesB: string; idPlanta?: number | null; consolidado: boolean; nombrePlanta?: string; mesA?: string; mesB?: string; } | null>(null); const { tienePermiso, isSuperAdmin } = usePermissions(); const puedeVerReporte = isSuperAdmin || tienePermiso("RR007"); const numberLocaleFormatter = (value: number | null | undefined) => value != null ? Number(value).toLocaleString('es-AR') : ''; const handleGenerarReporte = useCallback(async (params: { fechaInicioMesA: string; fechaFinMesA: string; fechaInicioMesB: string; fechaFinMesB: string; idPlanta?: number | null; consolidado: boolean; }) => { if (!puedeVerReporte) { setError("No tiene permiso para generar este reporte."); setLoading(false); return; } setLoading(true); setError(null); setApiErrorParams(null); setReportData([]); let plantaNombre = "Consolidado"; if (!params.consolidado && params.idPlanta) { const plantaService = (await import('../../services/Impresion/plantaService')).default; const plantaData = await plantaService.getPlantaById(params.idPlanta); plantaNombre = plantaData?.nombre ?? "N/A"; } // Formatear nombres de meses para el PDF const formatMonthYear = (dateString: string) => { const date = new Date(dateString + 'T00:00:00'); // Asegurar que se parsea como local return date.toLocaleDateString('es-AR', { month: 'long', year: 'numeric', timeZone: 'UTC' }); }; setCurrentParams({ ...params, nombrePlanta: plantaNombre, mesA: formatMonthYear(params.fechaInicioMesA), mesB: formatMonthYear(params.fechaInicioMesB) }); try { const data = await reportesService.getComparativaConsumoBobinas(params); const dataWithIds = data.map((item, index) => ({ ...item, id: `${item.tipoBobina}-${index}` })); setReportData(dataWithIds); if (dataWithIds.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); } finally { setLoading(false); } }, []); const handleVolverAParametros = useCallback(() => { setShowParamSelector(true); setReportData([]); setError(null); setApiErrorParams(null); setCurrentParams(null); }, []); const handleExportToExcel = useCallback(() => { if (reportData.length === 0) { alert("No hay datos para exportar."); return; } const dataToExport = reportData.map(({ ...rest }) => rest).map(item => ({ "Tipo Bobina": item.tipoBobina, "Cant. Mes A": item.bobinasUtilizadasMesA, "Cant. Mes B": item.bobinasUtilizadasMesB, "Dif. Cant.": item.diferenciaBobinasUtilizadas, "Kg Mes A": item.kilosUtilizadosMesA, "Kg Mes B": item.kilosUtilizadosMesB, "Dif. Kg": item.diferenciaKilosUtilizados, })); const totales = dataToExport.reduce((acc, row) => { acc.cantA += Number(row["Cant. Mes A"]); acc.cantB += Number(row["Cant. Mes B"]); acc.difCant += Number(row["Dif. Cant."]); acc.kgA += Number(row["Kg Mes A"]); acc.kgB += Number(row["Kg Mes B"]); acc.difKg += Number(row["Dif. Kg"]); return acc; }, { cantA: 0, cantB: 0, difCant: 0, kgA: 0, kgB: 0, difKg: 0 }); dataToExport.push({ "Tipo Bobina": "TOTALES", "Cant. Mes A": totales.cantA, "Cant. Mes B": totales.cantB, "Dif. Cant.": totales.difCant, "Kg Mes A": totales.kgA, "Kg Mes B": totales.kgB, "Dif. Kg": totales.difKg, }); const ws = XLSX.utils.json_to_sheet(dataToExport); const headers = Object.keys(dataToExport[0] || {}); ws['!cols'] = headers.map(h => { const maxLen = Math.max(...dataToExport.map(row => (row as any)[h]?.toString().length ?? 0), h.length); return { wch: maxLen + 2 }; }); ws['!freeze'] = { xSplit: 0, ySplit: 1 }; const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "ComparativaConsumo"); let fileName = "ReporteComparativaConsumoBobinas"; if (currentParams) { fileName += `_${currentParams.consolidado ? 'Consolidado' : `Planta${currentParams.idPlanta}`}`; fileName += `_${currentParams.fechaInicioMesA}_vs_${currentParams.fechaInicioMesB}`; } 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.getComparativaConsumoBobinasPdf(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]); const columns: GridColDef[] = [ // Tipar con la interfaz correcta { field: 'tipoBobina', headerName: 'Tipo Bobina', width: 250, flex: 1.5 }, { field: 'bobinasUtilizadasMesA', headerName: 'Cant. Mes A', type: 'number', width: 120, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'bobinasUtilizadasMesB', headerName: 'Cant. Mes B', type: 'number', width: 120, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'diferenciaBobinasUtilizadas', headerName: 'Dif. Cant.', type: 'number', width: 110, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'kilosUtilizadosMesA', headerName: 'Kg Mes A', type: 'number', width: 120, align: 'right', headerAlign: 'right', cellClassName: 'separator-left', headerClassName: 'separator-left', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'kilosUtilizadosMesB', headerName: 'Kg Mes B', type: 'number', width: 120, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'diferenciaKilosUtilizados', headerName: 'Dif. Kg', type: 'number', width: 110, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, ]; const rows = useMemo(() => reportData, [reportData]); // Calcular totales para el footer const totalesGenerales = useMemo(() => { if (reportData.length === 0) return null; return { bobinasUtilizadasMesA: reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesA, 0), bobinasUtilizadasMesB: reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesB, 0), diferenciaBobinasUtilizadas: reportData.reduce((sum, item) => sum + item.diferenciaBobinasUtilizadas, 0), kilosUtilizadosMesA: reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesA, 0), kilosUtilizadosMesB: reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesB, 0), diferenciaKilosUtilizados: reportData.reduce((sum, item) => sum + item.diferenciaKilosUtilizados, 0), }; }, [reportData]); // eslint-disable-next-line react/display-name const CustomFooter = () => { if (!totalesGenerales) return null; const getCellStyle = (field: (typeof columns)[number]['field'] | 'label', isLabel: boolean = false) => { const colConfig = columns.find(c => c.field === field); // Ajustar anchos para los totales para que sean más compactos let targetWidth: number | string = 'auto'; let targetMinWidth: number | string = 'auto'; if (isLabel) { targetWidth = colConfig?.width ? Math.max(80, colConfig.width * 0.5) : 100; // Más corto para "TOTALES:" targetMinWidth = 80; } else if (colConfig) { targetWidth = colConfig.width ? Math.max(70, colConfig.width * 0.75) : 90; // 75% del ancho de columna, mínimo 70 targetMinWidth = 70; } const style: React.CSSProperties = { minWidth: targetMinWidth, width: targetWidth, textAlign: isLabel ? 'left' : (colConfig?.align || 'right') as 'left' | 'right' | 'center', paddingRight: isLabel ? 1 : (field === 'diferenciaKilosUtilizados' ? 0 : 1), // pr en theme units fontWeight: 'bold', whiteSpace: 'nowrap', }; // Aplicar el separador si es la columna 'kilosUtilizadosMesA' if (field === 'kilosUtilizadosMesA') { style.borderLeft = `2px solid grey`; // O theme.palette.divider style.paddingLeft = '8px'; // Espacio después del separador } return style; }; return ( `1px solid ${theme.palette.divider}`, minHeight: '52px', }}> TOTALES: {numberLocaleFormatter(totalesGenerales.bobinasUtilizadasMesA)} {numberLocaleFormatter(totalesGenerales.bobinasUtilizadasMesB)} {numberLocaleFormatter(totalesGenerales.diferenciaBobinasUtilizadas)} {numberLocaleFormatter(totalesGenerales.kilosUtilizadosMesA)} {numberLocaleFormatter(totalesGenerales.kilosUtilizadosMesB)} {numberLocaleFormatter(totalesGenerales.diferenciaKilosUtilizados)} ); }; if (showParamSelector) { if (!loading && !puedeVerReporte) { return No tiene permiso para acceder a este reporte.; } return ( ); } return ( Reporte: Comparativa Consumo de Bobinas {loading && } {error && !loading && {error}} {!loading && !error && reportData.length > 0 && ( `2px solid ${theme.palette.divider}`, }, }}> )} {!loading && !error && reportData.length === 0 && currentParams && (No se encontraron datos para los criterios seleccionados.)} ); }; export default ReporteComparativaConsumoBobinasPage;