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'; // Importaciones para DataGrid import { esES } from '@mui/x-data-grid/locales'; // Para localización import reportesService from '../../services/Reportes/reportesService'; import type { MovimientoBobinasDto } from '../../models/dtos/Reportes/MovimientoBobinasDto'; import SeleccionaReporteMovimientoBobinas from './SeleccionaReporteMovimientoBobinas'; import * as XLSX from 'xlsx'; import axios from 'axios'; // Definición de la interfaz extendida para DataGrid (con 'id') interface MovimientoBobinasDataGridDto extends MovimientoBobinasDto { id: string; } const ReporteMovimientoBobinasPage: React.FC = () => { const [reportData, setReportData] = useState([]); // Usar el 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<{ fechaDesde: string; fechaHasta: string; idPlanta: number; nombrePlanta?: string; } | null>(null); const numberLocaleFormatter = (value: number | null | undefined) => value != null ? Number(value).toLocaleString('es-AR') : ''; const handleGenerarReporte = useCallback(async (params: { fechaDesde: string; fechaHasta: string; idPlanta: number; }) => { setLoading(true); setError(null); setApiErrorParams(null); // Opcional: Obtener nombre de la planta // const plantaService = (await import('../../services/Maestros/plantaService')).default; // const plantaData = await plantaService.getPlantaById(params.idPlanta); // setCurrentParams({...params, nombrePlanta: plantaData?.nombre}); setCurrentParams(params); try { const data = await reportesService.getMovimientoBobinas(params); // Añadir 'id' único a cada fila para DataGrid const dataWithIds = data.map((item, index) => ({ ...item, id: `${item.tipoBobina}-${index}` // Asumiendo que tipoBobina es único por reporte o combinar con 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); setReportData([]); } 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(item => ({ "Tipo Bobina": item.tipoBobina, "Cant. Inicial": item.bobinasIniciales, "Kg Iniciales": item.kilosIniciales, "Compradas": item.bobinasCompradas, "Kg Comprados": item.kilosComprados, "Consumidas": item.bobinasConsumidas, "Kg Consumidos": item.kilosConsumidos, "Dañadas": item.bobinasDaniadas, "Kg Dañados": item.kilosDaniados, "Cant. Final": item.bobinasFinales, "Kg Finales": item.kilosFinales, })); // Añadir fila de totales const totalesRow = { "Tipo Bobina": "Totales", "Cant. Inicial": reportData.reduce((sum, item) => sum + item.bobinasIniciales, 0), "Kg Iniciales": reportData.reduce((sum, item) => sum + item.kilosIniciales, 0), "Compradas": reportData.reduce((sum, item) => sum + item.bobinasCompradas, 0), "Kg Comprados": reportData.reduce((sum, item) => sum + item.kilosComprados, 0), "Consumidas": reportData.reduce((sum, item) => sum + item.bobinasConsumidas, 0), "Kg Consumidos": reportData.reduce((sum, item) => sum + item.kilosConsumidos, 0), "Dañadas": reportData.reduce((sum, item) => sum + item.bobinasDaniadas, 0), "Kg Dañados": reportData.reduce((sum, item) => sum + item.kilosDaniados, 0), "Cant. Final": reportData.reduce((sum, item) => sum + item.bobinasFinales, 0), "Kg Finales": reportData.reduce((sum, item) => sum + item.kilosFinales, 0), }; dataToExport.push(totalesRow); const ws = XLSX.utils.json_to_sheet(dataToExport); const headers = Object.keys(dataToExport[0]); ws['!cols'] = headers.map(h => { const maxLen = dataToExport.reduce((prev, row) => { const cell = (row as any)[h]?.toString() ?? ''; return Math.max(prev, cell.length); }, 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, "MovimientoBobinas"); let fileName = "ReporteMovimientoBobinas"; if (currentParams) { // Asumiendo que currentParams.nombrePlanta está disponible o se usa idPlanta 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.getMovimientoBobinasPdf(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]); // Definiciones de Columnas para DataGrid const columns: GridColDef[] = [ { field: 'tipoBobina', headerName: 'Tipo Bobina', width: 200, flex: 1.5 }, { field: 'bobinasIniciales', headerName: 'Cant. Ini.', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'kilosIniciales', headerName: 'Kg Ini.', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'bobinasCompradas', headerName: 'Compradas', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'kilosComprados', headerName: 'Kg Compr.', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'bobinasConsumidas', headerName: 'Consum.', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'kilosConsumidos', headerName: 'Kg Consum.', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'bobinasDaniadas', headerName: 'Dañadas', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'kilosDaniados', headerName: 'Kg Dañ.', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'bobinasFinales', headerName: 'Cant. Fin.', type: 'number', width: 100, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, { field: 'kilosFinales', headerName: 'Kg Finales', type: 'number', width: 110, align: 'right', headerAlign: 'right', valueFormatter: (value) => numberLocaleFormatter(Number(value)) }, ]; const rows = useMemo(() => reportData, [reportData]); // Calcular totales para el footer const totales = useMemo(() => { if (reportData.length === 0) return null; return { bobinasIniciales: reportData.reduce((sum, item) => sum + item.bobinasIniciales, 0), kilosIniciales: reportData.reduce((sum, item) => sum + item.kilosIniciales, 0), bobinasCompradas: reportData.reduce((sum, item) => sum + item.bobinasCompradas, 0), kilosComprados: reportData.reduce((sum, item) => sum + item.kilosComprados, 0), bobinasConsumidas: reportData.reduce((sum, item) => sum + item.bobinasConsumidas, 0), kilosConsumidos: reportData.reduce((sum, item) => sum + item.kilosConsumidos, 0), bobinasDaniadas: reportData.reduce((sum, item) => sum + item.bobinasDaniadas, 0), kilosDaniados: reportData.reduce((sum, item) => sum + item.kilosDaniados, 0), bobinasFinales: reportData.reduce((sum, item) => sum + item.bobinasFinales, 0), kilosFinales: reportData.reduce((sum, item) => sum + item.kilosFinales, 0), }; }, [reportData]); // eslint-disable-next-line react/display-name const CustomFooter = () => { if (!totales) return null; const getCellStyle = (field: (typeof columns)[number]['field'] | 'label', isLabel: boolean = false) => { const colConfig = columns.find(c => c.field === field); let targetWidth: number | string = 'auto'; // Por defecto, dejar que el contenido decida let targetMinWidth: number | string = 'auto'; if (isLabel) { // Para la etiqueta "TOTALES:", un ancho más ajustado. // Podrías basarlo en el ancho de la primera columna si es consistentemente la de "Tipo Bobina" // o un valor fijo que sepas que funciona. targetWidth = colConfig?.width ? Math.max(80, colConfig.width * 0.6) : 120; // Ej: 60% del ancho de la columna o 120px targetMinWidth = 80; // Un mínimo razonable para "TOTALES:" } else if (colConfig) { // Para los valores numéricos, podemos ser un poco más conservadores que el ancho de la columna. // O usar el ancho de la columna si es pequeño. targetWidth = colConfig.width ? Math.max(70, colConfig.width * 0.85) : 90; // Ej: 85% del ancho de la columna o 90px targetMinWidth = 70; // Un mínimo para números } return { minWidth: targetMinWidth, width: targetWidth, textAlign: isLabel ? 'left' : (colConfig?.align || 'right') as 'right' | 'left' | 'center', pr: isLabel ? 1 : (field === 'kilosFinales' ? 0 : 1), // padding-right fontWeight: 'bold', // Añadimos overflow y textOverflow para manejar texto largo en la etiqueta si fuera necesario overflow: isLabel ? 'hidden' : undefined, textOverflow: isLabel ? 'ellipsis' : undefined, whiteSpace: 'nowrap', // Asegurar que no haya saltos de línea en los totales }; }; return ( `1px solid ${theme.palette.divider}`, minHeight: '52px', }}> {/* Box para la paginación estándar */} {/* Box para los totales personalizados */} TOTALES: {numberLocaleFormatter(totales.bobinasIniciales)} {numberLocaleFormatter(totales.kilosIniciales)} {numberLocaleFormatter(totales.bobinasCompradas)} {numberLocaleFormatter(totales.kilosComprados)} {numberLocaleFormatter(totales.bobinasConsumidas)} {numberLocaleFormatter(totales.kilosConsumidos)} {numberLocaleFormatter(totales.bobinasDaniadas)} {numberLocaleFormatter(totales.kilosDaniados)} {numberLocaleFormatter(totales.bobinasFinales)} {numberLocaleFormatter(totales.kilosFinales)} ); }; if (showParamSelector) { return ( ); } return ( Reporte: Movimiento de Bobinas {currentParams?.nombrePlanta ? `(${currentParams.nombrePlanta})` : ''} {loading && } {error && !loading && {error}} {!loading && !error && reportData.length > 0 && ( )} {!loading && !error && reportData.length === 0 && currentParams && (No se encontraron datos para los criterios seleccionados.)} ); }; export default ReporteMovimientoBobinasPage;