225 lines
10 KiB
TypeScript
225 lines
10 KiB
TypeScript
|
|
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;
|