Final de creación de Módulos de Reportes. Se procede a testeos y ordenamientos...
This commit is contained in:
		| @@ -0,0 +1,225 @@ | ||||
| import React, { useState, useCallback } from 'react'; | ||||
| import { | ||||
|   Box, Typography, Paper, CircularProgress, Alert, Button, | ||||
|   TableContainer, Table, TableHead, TableRow, TableCell, TableBody, | ||||
|   TableFooter | ||||
| } from '@mui/material'; | ||||
| import reportesService from '../../services/Reportes/reportesService'; | ||||
| import type { ComparativaConsumoBobinasDto } from '../../models/dtos/Reportes/ComparativaConsumoBobinasDto'; | ||||
| import SeleccionaReporteComparativaConsumoBobinas from './SeleccionaReporteComparativaConsumoBobinas'; | ||||
| import * as XLSX from 'xlsx'; | ||||
| import axios from 'axios'; | ||||
|  | ||||
| const ReporteComparativaConsumoBobinasPage: React.FC = () => { | ||||
|   const [reportData, setReportData] = useState<ComparativaConsumoBobinasDto[]>([]); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|   const [loadingPdf, setLoadingPdf] = useState(false); | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
|   const [apiErrorParams, setApiErrorParams] = useState<string | null>(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; // Para el PDF | ||||
|   } | null>(null); | ||||
|  | ||||
|   const handleGenerarReporte = useCallback(async (params: { | ||||
|     fechaInicioMesA: string; fechaFinMesA: string; | ||||
|     fechaInicioMesB: string; fechaFinMesB: string; | ||||
|     idPlanta?: number | null; consolidado: boolean; | ||||
|   }) => { | ||||
|     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"; | ||||
|     } | ||||
|     setCurrentParams({...params, nombrePlanta: plantaNombre}); | ||||
|      | ||||
|     try { | ||||
|       const data = await reportesService.getComparativaConsumoBobinas(params); | ||||
|       setReportData(data); | ||||
|       if (data.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(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, | ||||
|     })); | ||||
|  | ||||
|     // Totales | ||||
|     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]); | ||||
|  | ||||
|   if (showParamSelector) { | ||||
|     return ( | ||||
|       <Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}> | ||||
|         <Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}> | ||||
|           <SeleccionaReporteComparativaConsumoBobinas | ||||
|             onGenerarReporte={handleGenerarReporte} | ||||
|             onCancel={handleVolverAParametros} | ||||
|             isLoading={loading} | ||||
|             apiErrorMessage={apiErrorParams} | ||||
|           /> | ||||
|         </Paper> | ||||
|       </Box> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <Box sx={{ p: 2 }}> | ||||
|       <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2, flexWrap: 'wrap', gap: 1 }}> | ||||
|         <Typography variant="h5">Reporte: Comparativa Consumo de Bobinas</Typography> | ||||
|         <Box sx={{ display: 'flex', gap: 1 }}> | ||||
|           <Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || reportData.length === 0 || !!error} size="small"> | ||||
|             {loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"} | ||||
|           </Button> | ||||
|           <Button onClick={handleExportToExcel} variant="outlined" disabled={reportData.length === 0 || !!error} size="small"> | ||||
|             Exportar a Excel | ||||
|           </Button> | ||||
|           <Button onClick={handleVolverAParametros} variant="outlined" color="secondary" size="small"> | ||||
|             Nuevos Parámetros | ||||
|           </Button> | ||||
|         </Box> | ||||
|       </Box> | ||||
|  | ||||
|       {loading && <Box sx={{ textAlign: 'center' }}><CircularProgress /></Box>} | ||||
|       {error && !loading && <Alert severity="error" sx={{ my: 2 }}>{error}</Alert>} | ||||
|  | ||||
|       {!loading && !error && reportData.length > 0 && ( | ||||
|         <TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}> | ||||
|           <Table stickyHeader size="small"> | ||||
|             <TableHead> | ||||
|               <TableRow> | ||||
|                 <TableCell>Tipo Bobina</TableCell> | ||||
|                 <TableCell align="right">Cant. Mes A</TableCell> | ||||
|                 <TableCell align="right">Cant. Mes B</TableCell> | ||||
|                 <TableCell align="right">Dif. Cant.</TableCell> | ||||
|                 <TableCell align="right" sx={{ borderLeft: '2px solid grey' }}>Kg Mes A</TableCell> | ||||
|                 <TableCell align="right">Kg Mes B</TableCell> | ||||
|                 <TableCell align="right">Dif. Kg</TableCell> | ||||
|               </TableRow> | ||||
|             </TableHead> | ||||
|             <TableBody> | ||||
|               {reportData.map((row, idx) => ( | ||||
|                 <TableRow key={`${row.tipoBobina}-${idx}`}> | ||||
|                   <TableCell>{row.tipoBobina}</TableCell> | ||||
|                   <TableCell align="right">{row.bobinasUtilizadasMesA.toLocaleString('es-AR')}</TableCell> | ||||
|                   <TableCell align="right">{row.bobinasUtilizadasMesB.toLocaleString('es-AR')}</TableCell> | ||||
|                   <TableCell align="right">{row.diferenciaBobinasUtilizadas.toLocaleString('es-AR')}</TableCell> | ||||
|                   <TableCell align="right" sx={{ borderLeft: '2px solid grey' }}>{row.kilosUtilizadosMesA.toLocaleString('es-AR')}</TableCell> | ||||
|                   <TableCell align="right">{row.kilosUtilizadosMesB.toLocaleString('es-AR')}</TableCell> | ||||
|                   <TableCell align="right">{row.diferenciaKilosUtilizados.toLocaleString('es-AR')}</TableCell> | ||||
|                 </TableRow> | ||||
|               ))} | ||||
|             </TableBody> | ||||
|              <TableFooter> | ||||
|                 <TableRow sx={{backgroundColor: 'grey.300'}}> | ||||
|                     <TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>TOTALES:</TableCell> | ||||
|                     <TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesA, 0).toLocaleString('es-AR')}</TableCell> | ||||
|                     <TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.bobinasUtilizadasMesB, 0).toLocaleString('es-AR')}</TableCell> | ||||
|                     <TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.diferenciaBobinasUtilizadas, 0).toLocaleString('es-AR')}</TableCell> | ||||
|                     <TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem', borderLeft: '2px solid grey'}}>{reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesA, 0).toLocaleString('es-AR')}</TableCell> | ||||
|                     <TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.kilosUtilizadosMesB, 0).toLocaleString('es-AR')}</TableCell> | ||||
|                     <TableCell align="right" sx={{fontWeight: 'bold', fontSize: '1.1rem'}}>{reportData.reduce((sum, item) => sum + item.diferenciaKilosUtilizados, 0).toLocaleString('es-AR')}</TableCell> | ||||
|                 </TableRow> | ||||
|             </TableFooter> | ||||
|           </Table> | ||||
|         </TableContainer> | ||||
|       )} | ||||
|       {!loading && !error && reportData.length === 0 && (<Typography>No se encontraron datos para los criterios seleccionados.</Typography>)} | ||||
|     </Box> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default ReporteComparativaConsumoBobinasPage; | ||||
		Reference in New Issue
	
	Block a user