Continuidad de reportes Frontend. Se sigue..
This commit is contained in:
@@ -13,7 +13,7 @@ using System.Reflection;
|
|||||||
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyCompanyAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+cdd4d3e0f71f866aabb489394a273ab4d013284c")]
|
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+2273ebb1e018273a6e35d3f9ab0afe55ae1814bc")]
|
||||||
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyProductAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
[assembly: System.Reflection.AssemblyTitleAttribute("GestionIntegral.Api")]
|
||||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface ListadoDistribucionCanillasPromedioDiaDto {
|
||||||
|
dia: string; // Nombre del día de la semana
|
||||||
|
cant: number;
|
||||||
|
llevados: number;
|
||||||
|
devueltos: number;
|
||||||
|
promedio_Llevados: number;
|
||||||
|
promedio_Devueltos: number;
|
||||||
|
promedio_Ventas: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { ListadoDistribucionCanillasSimpleDto } from './ListadoDistribucionCanillasSimpleDto';
|
||||||
|
import type { ListadoDistribucionCanillasPromedioDiaDto } from './ListadoDistribucionCanillasPromedioDiaDto';
|
||||||
|
|
||||||
|
export interface ListadoDistribucionCanillasResponseDto {
|
||||||
|
detalleSimple: ListadoDistribucionCanillasSimpleDto[];
|
||||||
|
promediosPorDia: ListadoDistribucionCanillasPromedioDiaDto[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface ListadoDistribucionCanillasSimpleDto {
|
||||||
|
dia: number; // Día del mes
|
||||||
|
llevados: number;
|
||||||
|
devueltos: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface ListadoDistribucionGeneralPromedioDiaDto {
|
||||||
|
dia: string;
|
||||||
|
cantidadDias: number;
|
||||||
|
promedioTirada: number;
|
||||||
|
promedioSinCargo: number;
|
||||||
|
promedioPerdidos: number;
|
||||||
|
promedioLlevados: number;
|
||||||
|
promedioDevueltos: number;
|
||||||
|
promedioVendidos: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { ListadoDistribucionGeneralResumenDto } from './ListadoDistribucionGeneralResumenDto';
|
||||||
|
import type { ListadoDistribucionGeneralPromedioDiaDto } from './ListadoDistribucionGeneralPromedioDiaDto';
|
||||||
|
|
||||||
|
export interface ListadoDistribucionGeneralResponseDto {
|
||||||
|
resumen: ListadoDistribucionGeneralResumenDto[];
|
||||||
|
promediosPorDia: ListadoDistribucionGeneralPromedioDiaDto[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface ListadoDistribucionGeneralResumenDto {
|
||||||
|
fecha: string; // o Date, si prefieres parsear en el frontend
|
||||||
|
cantidadTirada: number;
|
||||||
|
sinCargo: number;
|
||||||
|
perdidos: number;
|
||||||
|
llevados: number;
|
||||||
|
devueltos: number;
|
||||||
|
vendidos: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export interface MovimientoBobinaEstadoDetalleDto {
|
||||||
|
tipoBobina: string;
|
||||||
|
numeroRemito: string;
|
||||||
|
fechaMovimiento: string; // o Date, pero string es más simple para la tabla si ya viene formateado
|
||||||
|
cantidad: number;
|
||||||
|
tipoMovimiento: string; // "Ingreso", "Utilizada", "Dañada"
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface MovimientoBobinaEstadoTotalDto {
|
||||||
|
tipoMovimiento: string; // "Ingresos", "Utilizadas", "Dañadas"
|
||||||
|
totalBobinas: number;
|
||||||
|
totalKilos: number;
|
||||||
|
}
|
||||||
13
Frontend/src/models/dtos/Reportes/MovimientoBobinasDto.ts
Normal file
13
Frontend/src/models/dtos/Reportes/MovimientoBobinasDto.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export interface MovimientoBobinasDto {
|
||||||
|
tipoBobina: string;
|
||||||
|
bobinasIniciales: number;
|
||||||
|
kilosIniciales: number;
|
||||||
|
bobinasCompradas: number;
|
||||||
|
kilosComprados: number;
|
||||||
|
bobinasConsumidas: number;
|
||||||
|
kilosConsumidos: number;
|
||||||
|
bobinasDaniadas: number;
|
||||||
|
kilosDaniados: number;
|
||||||
|
bobinasFinales: number;
|
||||||
|
kilosFinales: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { MovimientoBobinaEstadoDetalleDto } from "./MovimientoBobinaEstadoDetalleDto";
|
||||||
|
import type { MovimientoBobinaEstadoTotalDto } from "./MovimientoBobinaEstadoTotalDto";
|
||||||
|
|
||||||
|
export interface MovimientoBobinasPorEstadoResponseDto {
|
||||||
|
detalle: MovimientoBobinaEstadoDetalleDto[];
|
||||||
|
totales: MovimientoBobinaEstadoTotalDto[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { ListadoDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasResponseDto';
|
||||||
|
import SeleccionaReporteListadoDistribucionCanillas from './SeleccionaReporteListadoDistribucionCanillas';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReporteListadoDistribucionCanillasPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ListadoDistribucionCanillasResponseDto | null>(null);
|
||||||
|
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<{
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
nombrePublicacion?: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
|
||||||
|
const pubService = (await import('../../services/Distribucion/publicacionService')).default;
|
||||||
|
const pubData = await pubService.getPublicacionById(params.idPublicacion);
|
||||||
|
|
||||||
|
setCurrentParams({...params, nombrePublicacion: pubData?.nombre });
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getListadoDistribucionCanillas(params);
|
||||||
|
setReportData(data);
|
||||||
|
if ((!data.detalleSimple || data.detalleSimple.length === 0) && (!data.promediosPorDia || data.promediosPorDia.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.detalleSimple?.length && !reportData.promediosPorDia?.length)) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
if (reportData.detalleSimple?.length) {
|
||||||
|
const simpleToExport = reportData.detalleSimple.map(item => ({
|
||||||
|
"Día": item.dia,
|
||||||
|
"Llevados": item.llevados,
|
||||||
|
"Devueltos": item.devueltos,
|
||||||
|
"Vendidos": item.llevados - item.devueltos,
|
||||||
|
}));
|
||||||
|
const wsSimple = XLSX.utils.json_to_sheet(simpleToExport);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsSimple, "DetalleDiario");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reportData.promediosPorDia?.length) {
|
||||||
|
const promediosToExport = reportData.promediosPorDia.map(item => ({
|
||||||
|
"Día Semana": item.dia,
|
||||||
|
"Cant. Días": item.cant,
|
||||||
|
"Prom. Llevados": item.promedio_Llevados,
|
||||||
|
"Prom. Devueltos": item.promedio_Devueltos,
|
||||||
|
"Prom. Vendidos": item.promedio_Ventas,
|
||||||
|
}));
|
||||||
|
const wsPromedios = XLSX.utils.json_to_sheet(promediosToExport);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsPromedios, "PromediosPorDiaSemana");
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = "ListadoDistribucionCanillas";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.nombrePublicacion?.replace(/\s+/g, '') ?? `Pub${currentParams.idPublicacion}`}`;
|
||||||
|
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.getListadoDistribucionCanillasPdf(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}>
|
||||||
|
<SeleccionaReporteListadoDistribucionCanillas
|
||||||
|
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: Listado Distribución Canillitas</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!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 && (
|
||||||
|
<>
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>Detalle Diario</Typography>
|
||||||
|
{reportData.detalleSimple && reportData.detalleSimple.length > 0 ? (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 3 }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Día</TableCell>
|
||||||
|
<TableCell align="right">Llevados</TableCell>
|
||||||
|
<TableCell align="right">Devueltos</TableCell>
|
||||||
|
<TableCell align="right">Vendidos</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.detalleSimple.map((row, idx) => (
|
||||||
|
<TableRow key={`simple-${idx}`}>
|
||||||
|
<TableCell>{row.dia}</TableCell>
|
||||||
|
<TableCell align="right">{row.llevados.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.devueltos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{(row.llevados - row.devueltos).toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
) : (<Typography>No hay datos de detalle diario.</Typography>)}
|
||||||
|
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>Promedios por Día de Semana</Typography>
|
||||||
|
{reportData.promediosPorDia && reportData.promediosPorDia.length > 0 ? (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '300px' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Día Semana</TableCell>
|
||||||
|
<TableCell align="right">Cant. Días</TableCell>
|
||||||
|
<TableCell align="right">Prom. Llevados</TableCell>
|
||||||
|
<TableCell align="right">Prom. Devueltos</TableCell>
|
||||||
|
<TableCell align="right">Prom. Ventas</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.promediosPorDia.map((row, idx) => (
|
||||||
|
<TableRow key={`promedio-${idx}`}>
|
||||||
|
<TableCell>{row.dia}</TableCell>
|
||||||
|
<TableCell align="right">{row.cant.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedio_Llevados.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedio_Devueltos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedio_Ventas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
) : (<Typography>No hay datos de promedios por día.</Typography>)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteListadoDistribucionCanillasPage;
|
||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { ListadoDistribucionGeneralResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionGeneralResponseDto';
|
||||||
|
import SeleccionaReporteListadoDistribucionGeneral from './SeleccionaReporteListadoDistribucionGeneral';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReporteListadoDistribucionGeneralPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<ListadoDistribucionGeneralResponseDto | null>(null);
|
||||||
|
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<{
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string; // Primer día del mes
|
||||||
|
fechaHasta: string; // Último día del mes
|
||||||
|
nombrePublicacion?: string; // Para el nombre del archivo
|
||||||
|
mesAnioParaNombreArchivo?: string; // Para el nombre del archivo (ej. YYYY-MM)
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
|
||||||
|
// Para el nombre del archivo y título del PDF
|
||||||
|
const pubService = (await import('../../services/Distribucion/publicacionService')).default;
|
||||||
|
const pubData = await pubService.getPublicacionById(params.idPublicacion);
|
||||||
|
const mesAnioParts = params.fechaDesde.split('-'); // YYYY-MM-DD -> [YYYY, MM, DD]
|
||||||
|
const mesAnioNombre = `${mesAnioParts[1]}/${mesAnioParts[0]}`;
|
||||||
|
|
||||||
|
|
||||||
|
setCurrentParams({...params, nombrePublicacion: pubData?.nombre, mesAnioParaNombreArchivo: mesAnioNombre });
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getListadoDistribucionGeneral(params);
|
||||||
|
setReportData(data);
|
||||||
|
if ((!data.resumen || data.resumen.length === 0) && (!data.promediosPorDia || data.promediosPorDia.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.resumen?.length && !reportData.promediosPorDia?.length)) {
|
||||||
|
alert("No hay datos para exportar.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const wb = XLSX.utils.book_new();
|
||||||
|
|
||||||
|
if (reportData.resumen?.length) {
|
||||||
|
const resumenToExport = reportData.resumen.map(item => ({
|
||||||
|
"Fecha": item.fecha ? new Date(item.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-',
|
||||||
|
"Tirada": item.cantidadTirada,
|
||||||
|
"Sin Cargo": item.sinCargo,
|
||||||
|
"Perdidos": item.perdidos,
|
||||||
|
"Llevados": item.llevados,
|
||||||
|
"Devueltos": item.devueltos,
|
||||||
|
"Vendidos": item.vendidos,
|
||||||
|
}));
|
||||||
|
const wsResumen = XLSX.utils.json_to_sheet(resumenToExport);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsResumen, "ResumenDiario");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reportData.promediosPorDia?.length) {
|
||||||
|
const promediosToExport = reportData.promediosPorDia.map(item => ({
|
||||||
|
"Día Semana": item.dia,
|
||||||
|
"Cant. Días": item.cantidadDias,
|
||||||
|
"Prom. Tirada": item.promedioTirada,
|
||||||
|
"Prom. Sin Cargo": item.promedioSinCargo,
|
||||||
|
"Prom. Perdidos": item.promedioPerdidos,
|
||||||
|
"Prom. Llevados": item.promedioLlevados,
|
||||||
|
"Prom. Devueltos": item.promedioDevueltos,
|
||||||
|
"Prom. Vendidos": item.promedioVendidos,
|
||||||
|
}));
|
||||||
|
const wsPromedios = XLSX.utils.json_to_sheet(promediosToExport);
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsPromedios, "PromediosPorDia");
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = "ListadoDistribucionGeneral";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.nombrePublicacion?.replace(/\s+/g, '') ?? `Pub${currentParams.idPublicacion}`}`;
|
||||||
|
fileName += `_${currentParams.mesAnioParaNombreArchivo?.replace('/', '-')}`;
|
||||||
|
}
|
||||||
|
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.getListadoDistribucionGeneralPdf({
|
||||||
|
idPublicacion: currentParams.idPublicacion,
|
||||||
|
fechaDesde: currentParams.fechaDesde, // El servicio y SP esperan fechaDesde para el mes/año
|
||||||
|
fechaHasta: currentParams.fechaHasta // El SP no usa esta, pero el servicio de reporte sí para el nombre
|
||||||
|
});
|
||||||
|
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}>
|
||||||
|
<SeleccionaReporteListadoDistribucionGeneral
|
||||||
|
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: Listado Distribución General</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerarYAbrirPdf} variant="contained" disabled={loadingPdf || !reportData || !!error} size="small">
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleExportToExcel} variant="outlined" disabled={!reportData || !!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 && (
|
||||||
|
<>
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>Resumen Diario</Typography>
|
||||||
|
{reportData.resumen && reportData.resumen.length > 0 ? (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '300px', mb: 3 }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Fecha</TableCell>
|
||||||
|
<TableCell align="right">Tirada</TableCell>
|
||||||
|
<TableCell align="right">Sin Cargo</TableCell>
|
||||||
|
<TableCell align="right">Perdidos</TableCell>
|
||||||
|
<TableCell align="right">Llevados</TableCell>
|
||||||
|
<TableCell align="right">Devueltos</TableCell>
|
||||||
|
<TableCell align="right">Vendidos</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.resumen.map((row, idx) => (
|
||||||
|
<TableRow key={`resumen-${idx}`}>
|
||||||
|
<TableCell>{row.fecha ? new Date(row.fecha).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-'}</TableCell>
|
||||||
|
<TableCell align="right">{row.cantidadTirada.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.sinCargo.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.perdidos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.llevados.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.devueltos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.vendidos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
) : (<Typography>No hay datos de resumen diario.</Typography>)}
|
||||||
|
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>Promedios por Día de Semana</Typography>
|
||||||
|
{reportData.promediosPorDia && reportData.promediosPorDia.length > 0 ? (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '300px' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Día</TableCell>
|
||||||
|
<TableCell align="right">Cant. Días</TableCell>
|
||||||
|
<TableCell align="right">Prom. Tirada</TableCell>
|
||||||
|
<TableCell align="right">Prom. Sin Cargo</TableCell>
|
||||||
|
<TableCell align="right">Prom. Perdidos</TableCell>
|
||||||
|
<TableCell align="right">Prom. Llevados</TableCell>
|
||||||
|
<TableCell align="right">Prom. Devueltos</TableCell>
|
||||||
|
<TableCell align="right">Prom. Vendidos</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.promediosPorDia.map((row, idx) => (
|
||||||
|
<TableRow key={`promedio-${idx}`}>
|
||||||
|
<TableCell>{row.dia}</TableCell>
|
||||||
|
<TableCell align="right">{row.cantidadDias.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedioTirada.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedioSinCargo.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedioPerdidos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedioLlevados.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedioDevueltos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.promedioVendidos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
) : (<Typography>No hay datos de promedios por día.</Typography>)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteListadoDistribucionGeneralPage;
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
import reportesService from '../../services/Reportes/reportesService';
|
||||||
|
import type { MovimientoBobinasPorEstadoResponseDto } from '../../models/dtos/Reportes/MovimientoBobinasPorEstadoResponseDto';
|
||||||
|
import SeleccionaReporteMovimientoBobinasEstado from './SeleccionaReporteMovimientoBobinasEstado';
|
||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const ReporteMovimientoBobinasEstadoPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<MovimientoBobinasPorEstadoResponseDto | null>(null);
|
||||||
|
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<{
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(params);
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getMovimientoBobinasEstado(params);
|
||||||
|
setReportData(data);
|
||||||
|
if ((!data.detalle || data.detalle.length === 0) && (!data.totales || data.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();
|
||||||
|
|
||||||
|
// Hoja de Detalles
|
||||||
|
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 => {
|
||||||
|
const maxLen = detalleToExport.reduce((prev, row) => {
|
||||||
|
const cell = (row as any)[h]?.toString() ?? '';
|
||||||
|
return Math.max(prev, cell.length);
|
||||||
|
}, h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
wsDetalle['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsDetalle, "DetalleMovimientos");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hoja de Totales
|
||||||
|
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 => {
|
||||||
|
const maxLen = totalesToExport.reduce((prev, row) => {
|
||||||
|
const cell = (row as any)[h]?.toString() ?? '';
|
||||||
|
return Math.max(prev, cell.length);
|
||||||
|
}, h.length);
|
||||||
|
return { wch: maxLen + 2 };
|
||||||
|
});
|
||||||
|
wsTotales['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||||
|
XLSX.utils.book_append_sheet(wb, wsTotales, "TotalesPorEstado");
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = "ReporteMovimientoBobinasEstado";
|
||||||
|
if (currentParams) {
|
||||||
|
fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}_Planta${currentParams.idPlanta}`;
|
||||||
|
}
|
||||||
|
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]);
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteMovimientoBobinasEstado
|
||||||
|
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: Movimiento de Bobinas por Estado</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}>
|
||||||
|
<Button
|
||||||
|
onClick={handleGenerarYAbrirPdf}
|
||||||
|
variant="contained"
|
||||||
|
disabled={loadingPdf || !reportData || (!reportData.detalle?.length && !reportData.totales?.length) || !!error}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{loadingPdf ? <CircularProgress size={20} color="inherit" /> : "Abrir PDF"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleExportToExcel}
|
||||||
|
variant="outlined"
|
||||||
|
disabled={!reportData || (!reportData.detalle?.length && !reportData.totales?.length) || !!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 && (
|
||||||
|
<> {/* Usamos un Fragmento React para agrupar los elementos sin añadir un div extra */}
|
||||||
|
{/* Tabla de Detalle de Movimientos */}
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}>
|
||||||
|
Detalle de Movimientos
|
||||||
|
</Typography>
|
||||||
|
{reportData.detalle && reportData.detalle.length > 0 ? (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: '400px', mb: 3 }}> {/* Añadido mb: 3 para espaciado */}
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Tipo Bobina</TableCell>
|
||||||
|
<TableCell>Nro Remito</TableCell>
|
||||||
|
<TableCell>Fecha Movimiento</TableCell>
|
||||||
|
<TableCell align="right">Cantidad</TableCell>
|
||||||
|
<TableCell>Tipo Movimiento</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.detalle.map((row, idx) => (
|
||||||
|
<TableRow key={`detalle-${idx}`}>
|
||||||
|
<TableCell>{row.tipoBobina}</TableCell>
|
||||||
|
<TableCell>{row.numeroRemito}</TableCell>
|
||||||
|
<TableCell>{row.fechaMovimiento ? new Date(row.fechaMovimiento).toLocaleDateString('es-AR', { timeZone: 'UTC' }) : '-'}</TableCell>
|
||||||
|
<TableCell align="right">{row.cantidad.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell>{row.tipoMovimiento}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
) : (
|
||||||
|
<Typography sx={{ mb: 3 }}>No hay detalles de movimientos para mostrar.</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tabla de Totales por Estado */}
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Totales por Estado
|
||||||
|
</Typography>
|
||||||
|
{reportData.totales && reportData.totales.length > 0 ? (
|
||||||
|
<TableContainer component={Paper} sx={{ maxWidth: '600px' }}> {/* Limitamos el ancho para tablas pequeñas */}
|
||||||
|
<Table size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Tipo Movimiento</TableCell>
|
||||||
|
<TableCell align="right">Total Bobinas</TableCell>
|
||||||
|
<TableCell align="right">Total Kilos</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.totales.map((row, idx) => (
|
||||||
|
<TableRow key={`total-${idx}`}>
|
||||||
|
<TableCell>{row.tipoMovimiento}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalBobinas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.totalKilos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
) : (
|
||||||
|
<Typography>No hay totales para mostrar.</Typography>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteMovimientoBobinasEstadoPage;
|
||||||
212
Frontend/src/pages/Reportes/ReporteMovimientoBobinasPage.tsx
Normal file
212
Frontend/src/pages/Reportes/ReporteMovimientoBobinasPage.tsx
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Paper, CircularProgress, Alert, Button,
|
||||||
|
TableContainer, Table, TableHead, TableRow, TableCell, TableBody
|
||||||
|
} from '@mui/material';
|
||||||
|
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';
|
||||||
|
|
||||||
|
const ReporteMovimientoBobinasPage: React.FC = () => {
|
||||||
|
const [reportData, setReportData] = useState<MovimientoBobinasDto[]>([]);
|
||||||
|
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<{
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGenerarReporte = useCallback(async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
}) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
setApiErrorParams(null);
|
||||||
|
setCurrentParams(params);
|
||||||
|
try {
|
||||||
|
const data = await reportesService.getMovimientoBobinas(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);
|
||||||
|
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,
|
||||||
|
"Bobinas Iniciales": item.bobinasIniciales,
|
||||||
|
"Kg Iniciales": item.kilosIniciales,
|
||||||
|
"Bobinas Compradas": item.bobinasCompradas,
|
||||||
|
"Kg Comprados": item.kilosComprados,
|
||||||
|
"Bobinas Consumidas": item.bobinasConsumidas,
|
||||||
|
"Kg Consumidos": item.kilosConsumidos,
|
||||||
|
"Bobinas Dañadas": item.bobinasDaniadas,
|
||||||
|
"Kg Dañados": item.kilosDaniados,
|
||||||
|
"Bobinas Finales": item.bobinasFinales,
|
||||||
|
"Kg Finales": item.kilosFinales,
|
||||||
|
}));
|
||||||
|
|
||||||
|
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) {
|
||||||
|
fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}_Planta${currentParams.idPlanta}`;
|
||||||
|
}
|
||||||
|
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]);
|
||||||
|
|
||||||
|
if (showParamSelector) {
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', mt: 2 }}>
|
||||||
|
<Paper sx={{ width: '100%', maxWidth: 600 }} elevation={3}>
|
||||||
|
<SeleccionaReporteMovimientoBobinas
|
||||||
|
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: Movimiento 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 && (
|
||||||
|
<TableContainer component={Paper} sx={{ maxHeight: 'calc(100vh - 240px)' }}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>Tipo Bobina</TableCell>
|
||||||
|
<TableCell align="right">Cant. Ini.</TableCell>
|
||||||
|
<TableCell align="right">Kg Ini.</TableCell>
|
||||||
|
<TableCell align="right">Compradas</TableCell>
|
||||||
|
<TableCell align="right">Kg Compr.</TableCell>
|
||||||
|
<TableCell align="right">Consum.</TableCell>
|
||||||
|
<TableCell align="right">Kg Consum.</TableCell>
|
||||||
|
<TableCell align="right">Dañadas</TableCell>
|
||||||
|
<TableCell align="right">Kg Dañ.</TableCell>
|
||||||
|
<TableCell align="right">Cant. Fin.</TableCell>
|
||||||
|
<TableCell align="right">Kg Finales</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{reportData.map((row, idx) => (
|
||||||
|
<TableRow key={row.tipoBobina + idx}>
|
||||||
|
<TableCell>{row.tipoBobina}</TableCell>
|
||||||
|
<TableCell align="right">{row.bobinasIniciales.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.kilosIniciales.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.bobinasCompradas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.kilosComprados.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.bobinasConsumidas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.kilosConsumidos.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.bobinasDaniadas.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.kilosDaniados.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.bobinasFinales.toLocaleString('es-AR')}</TableCell>
|
||||||
|
<TableCell align="right">{row.kilosFinales.toLocaleString('es-AR')}</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReporteMovimientoBobinasPage;
|
||||||
@@ -4,8 +4,10 @@ import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
|||||||
|
|
||||||
const reportesSubModules = [
|
const reportesSubModules = [
|
||||||
{ label: 'Existencia de Papel', path: 'existencia-papel' },
|
{ label: 'Existencia de Papel', path: 'existencia-papel' },
|
||||||
// { label: 'Consumo Bobinas Mensual', path: 'consumo-bobinas-mensual' }, // Ejemplo
|
{ label: 'Movimiento de Bobinas', path: 'movimiento-bobinas' },
|
||||||
// ... agregar otros reportes aquí a medida que se implementen
|
{ label: 'Mov. Bobinas por Estado', path: 'movimiento-bobinas-estado' },
|
||||||
|
{ label: 'Distribución General', path: 'listado-distribucion-general' },
|
||||||
|
{ label: 'Distribución Canillas', path: 'listado-distribucion-canillas' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const ReportesIndexPage: React.FC = () => {
|
const ReportesIndexPage: React.FC = () => {
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ interface SeleccionaReporteExistenciaPapelProps {
|
|||||||
|
|
||||||
const SeleccionaReporteExistenciaPapel: React.FC<SeleccionaReporteExistenciaPapelProps> = ({
|
const SeleccionaReporteExistenciaPapel: React.FC<SeleccionaReporteExistenciaPapelProps> = ({
|
||||||
onGenerarReporte,
|
onGenerarReporte,
|
||||||
onCancel,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
apiErrorMessage
|
apiErrorMessage
|
||||||
}) => {
|
}) => {
|
||||||
@@ -142,9 +141,6 @@ const SeleccionaReporteExistenciaPapel: React.FC<SeleccionaReporteExistenciaPape
|
|||||||
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
<Button onClick={onCancel} color="secondary" disabled={isLoading}>
|
|
||||||
Cancelar
|
|
||||||
</Button>
|
|
||||||
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import publicacionService from '../../services/Distribucion/publicacionService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteListadoDistribucionCanillasProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteListadoDistribucionCanillas: React.FC<SeleccionaReporteListadoDistribucionCanillasProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
|
||||||
|
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
|
||||||
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPublicaciones = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const data = await publicacionService.getAllPublicaciones(undefined, undefined, true);
|
||||||
|
setPublicaciones(data.map(p => p));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar publicaciones:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar publicaciones.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPublicaciones();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idPublicacion) errors.idPublicacion = 'Debe seleccionar una publicación.';
|
||||||
|
if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.';
|
||||||
|
if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.';
|
||||||
|
if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) {
|
||||||
|
errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
idPublicacion: Number(idPublicacion),
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Listado Distribución Canillitas
|
||||||
|
</Typography>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPublicacion} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="publicacion-select-label-can" required>Publicación</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="publicacion-select-label-can"
|
||||||
|
label="Publicación"
|
||||||
|
value={idPublicacion}
|
||||||
|
onChange={(e) => { setIdPublicacion(e.target.value as number); setLocalErrors(p => ({ ...p, idPublicacion: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una publicación</em></MenuItem>
|
||||||
|
{publicaciones.map((p) => (
|
||||||
|
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPublicacion && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPublicacion}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Desde"
|
||||||
|
type="date"
|
||||||
|
value={fechaDesde}
|
||||||
|
onChange={(e) => { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaDesde}
|
||||||
|
helperText={localErrors.fechaDesde}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Hasta"
|
||||||
|
type="date"
|
||||||
|
value={fechaHasta}
|
||||||
|
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaHasta}
|
||||||
|
helperText={localErrors.fechaHasta}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteListadoDistribucionCanillas;
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
// src/pages/Reportes/SeleccionaReporteListadoDistribucionGeneral.tsx
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem, TextField
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PublicacionDto } from '../../models/dtos/Distribucion/PublicacionDto';
|
||||||
|
import publicacionService from '../../services/Distribucion/publicacionService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteListadoDistribucionGeneralProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string; // Será el primer día del mes seleccionado
|
||||||
|
fechaHasta: string; // Será el último día del mes seleccionado
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteListadoDistribucionGeneral: React.FC<SeleccionaReporteListadoDistribucionGeneralProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [idPublicacion, setIdPublicacion] = useState<number | string>('');
|
||||||
|
// Para el selector de mes/año, usamos un input type="month"
|
||||||
|
const [mesAnio, setMesAnio] = useState<string>(new Date().toISOString().substring(0, 7)); // Formato "YYYY-MM"
|
||||||
|
|
||||||
|
const [publicaciones, setPublicaciones] = useState<PublicacionDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPublicaciones = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
// Asumiendo que quieres solo publicaciones habilitadas
|
||||||
|
const data = await publicacionService.getAllPublicaciones(undefined, undefined, true);
|
||||||
|
setPublicaciones(data.map(p => p)); // El servicio devuelve tupla
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar publicaciones:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar publicaciones.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPublicaciones();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!idPublicacion) errors.idPublicacion = 'Debe seleccionar una publicación.';
|
||||||
|
if (!mesAnio) errors.mesAnio = 'Debe seleccionar un Mes/Año.';
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
|
||||||
|
const [year, month] = mesAnio.split('-').map(Number);
|
||||||
|
const fechaDesde = new Date(year, month - 1, 1).toISOString().split('T')[0];
|
||||||
|
const fechaHasta = new Date(year, month, 0).toISOString().split('T')[0]; // Último día del mes
|
||||||
|
|
||||||
|
onGenerarReporte({
|
||||||
|
idPublicacion: Number(idPublicacion),
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Listado Distribución General
|
||||||
|
</Typography>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPublicacion} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="publicacion-select-label" required>Publicación</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="publicacion-select-label"
|
||||||
|
label="Publicación"
|
||||||
|
value={idPublicacion}
|
||||||
|
onChange={(e) => { setIdPublicacion(e.target.value as number); setLocalErrors(p => ({ ...p, idPublicacion: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una publicación</em></MenuItem>
|
||||||
|
{publicaciones.map((p) => (
|
||||||
|
<MenuItem key={p.idPublicacion} value={p.idPublicacion}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPublicacion && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPublicacion}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
label="Mes y Año"
|
||||||
|
type="month" // Esto generará un selector de mes/año nativo del navegador
|
||||||
|
value={mesAnio}
|
||||||
|
onChange={(e) => { setMesAnio(e.target.value); setLocalErrors(p => ({ ...p, mesAnio: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.mesAnio}
|
||||||
|
helperText={localErrors.mesAnio}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteListadoDistribucionGeneral;
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto'; // Asumo que ya tienes este DTO
|
||||||
|
import plantaService from '../../services/Impresion/plantaService'; // Asumo que ya tienes este servicio
|
||||||
|
|
||||||
|
interface SeleccionaReporteMovimientoBobinasProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number; // idPlanta es obligatoria aquí
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteMovimientoBobinas: React.FC<SeleccionaReporteMovimientoBobinasProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [idPlanta, setIdPlanta] = useState<number | string>(''); // Puede ser string inicialmente por el MenuItem vacío
|
||||||
|
|
||||||
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlantas = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const plantasData = await plantaService.getAllPlantas(); // Asumiendo que esto devuelve todas
|
||||||
|
setPlantas(plantasData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar plantas:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar plantas.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPlantas();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.';
|
||||||
|
if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.';
|
||||||
|
if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) {
|
||||||
|
errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.';
|
||||||
|
}
|
||||||
|
if (!idPlanta) {
|
||||||
|
errors.idPlanta = 'Debe seleccionar una planta.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta,
|
||||||
|
idPlanta: Number(idPlanta) // Asegurarse de que es un número
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Movimiento de Bobinas
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Desde"
|
||||||
|
type="date"
|
||||||
|
value={fechaDesde}
|
||||||
|
onChange={(e) => { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaDesde}
|
||||||
|
helperText={localErrors.fechaDesde}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Hasta"
|
||||||
|
type="date"
|
||||||
|
value={fechaHasta}
|
||||||
|
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaHasta}
|
||||||
|
helperText={localErrors.fechaHasta}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="planta-select-label" required>Planta</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="planta-select-label"
|
||||||
|
label="Planta"
|
||||||
|
value={idPlanta}
|
||||||
|
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una planta</em></MenuItem>
|
||||||
|
{plantas.map((p) => (
|
||||||
|
<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteMovimientoBobinas;
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||||
|
FormControl, InputLabel, Select, MenuItem
|
||||||
|
} from '@mui/material';
|
||||||
|
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||||
|
import plantaService from '../../services/Impresion/plantaService';
|
||||||
|
|
||||||
|
interface SeleccionaReporteMovimientoBobinasEstadoProps {
|
||||||
|
onGenerarReporte: (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
}) => Promise<void>;
|
||||||
|
onCancel: () => void;
|
||||||
|
isLoading?: boolean;
|
||||||
|
apiErrorMessage?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SeleccionaReporteMovimientoBobinasEstado: React.FC<SeleccionaReporteMovimientoBobinasEstadoProps> = ({
|
||||||
|
onGenerarReporte,
|
||||||
|
isLoading,
|
||||||
|
apiErrorMessage
|
||||||
|
}) => {
|
||||||
|
const [fechaDesde, setFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [fechaHasta, setFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);
|
||||||
|
const [idPlanta, setIdPlanta] = useState<number | string>('');
|
||||||
|
|
||||||
|
const [plantas, setPlantas] = useState<PlantaDto[]>([]);
|
||||||
|
const [loadingDropdowns, setLoadingDropdowns] = useState(false);
|
||||||
|
const [localErrors, setLocalErrors] = useState<{ [key: string]: string | null }>({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPlantas = async () => {
|
||||||
|
setLoadingDropdowns(true);
|
||||||
|
try {
|
||||||
|
const plantasData = await plantaService.getAllPlantas();
|
||||||
|
setPlantas(plantasData);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar plantas:", error);
|
||||||
|
setLocalErrors(prev => ({ ...prev, dropdowns: 'Error al cargar plantas.' }));
|
||||||
|
} finally {
|
||||||
|
setLoadingDropdowns(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchPlantas();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validate = (): boolean => {
|
||||||
|
const errors: { [key: string]: string | null } = {};
|
||||||
|
if (!fechaDesde) errors.fechaDesde = 'Fecha Desde es obligatoria.';
|
||||||
|
if (!fechaHasta) errors.fechaHasta = 'Fecha Hasta es obligatoria.';
|
||||||
|
if (fechaDesde && fechaHasta && new Date(fechaDesde) > new Date(fechaHasta)) {
|
||||||
|
errors.fechaHasta = 'Fecha Hasta no puede ser anterior a Fecha Desde.';
|
||||||
|
}
|
||||||
|
if (!idPlanta) {
|
||||||
|
errors.idPlanta = 'Debe seleccionar una planta.';
|
||||||
|
}
|
||||||
|
setLocalErrors(errors);
|
||||||
|
return Object.keys(errors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGenerar = () => {
|
||||||
|
if (!validate()) return;
|
||||||
|
onGenerarReporte({
|
||||||
|
fechaDesde,
|
||||||
|
fechaHasta,
|
||||||
|
idPlanta: Number(idPlanta)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Parámetros: Movimiento de Bobinas por Estado
|
||||||
|
</Typography>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Desde"
|
||||||
|
type="date"
|
||||||
|
value={fechaDesde}
|
||||||
|
onChange={(e) => { setFechaDesde(e.target.value); setLocalErrors(p => ({ ...p, fechaDesde: null, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaDesde}
|
||||||
|
helperText={localErrors.fechaDesde}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Fecha Hasta"
|
||||||
|
type="date"
|
||||||
|
value={fechaHasta}
|
||||||
|
onChange={(e) => { setFechaHasta(e.target.value); setLocalErrors(p => ({ ...p, fechaHasta: null })); }}
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={!!localErrors.fechaHasta}
|
||||||
|
helperText={localErrors.fechaHasta}
|
||||||
|
disabled={isLoading}
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns}>
|
||||||
|
<InputLabel id="planta-select-label-estado" required>Planta</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="planta-select-label-estado"
|
||||||
|
label="Planta"
|
||||||
|
value={idPlanta}
|
||||||
|
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
|
||||||
|
>
|
||||||
|
<MenuItem value="" disabled><em>Seleccione una planta</em></MenuItem>
|
||||||
|
{plantas.map((p) => (
|
||||||
|
<MenuItem key={p.idPlanta} value={p.idPlanta}>{p.nombre}</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
{localErrors.idPlanta && <Typography color="error" variant="caption" sx={{ml:1.5}}>{localErrors.idPlanta}</Typography>}
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
{apiErrorMessage && <Alert severity="error" sx={{ mt: 2 }}>{apiErrorMessage}</Alert>}
|
||||||
|
{localErrors.dropdowns && <Alert severity="warning" sx={{ mt: 1 }}>{localErrors.dropdowns}</Alert>}
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end', gap: 1 }}>
|
||||||
|
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||||
|
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SeleccionaReporteMovimientoBobinasEstado;
|
||||||
@@ -53,8 +53,12 @@ import GestionarCancionesPage from '../pages/Radios/GestionarCancionesPage';
|
|||||||
import GenerarListasRadioPage from '../pages/Radios/GenerarListasRadioPage';
|
import GenerarListasRadioPage from '../pages/Radios/GenerarListasRadioPage';
|
||||||
|
|
||||||
// Reportes
|
// Reportes
|
||||||
import ReportesIndexPage from '../pages/Reportes/ReportesIndexPage'; // Crear este si no existe
|
import ReportesIndexPage from '../pages/Reportes/ReportesIndexPage';
|
||||||
import ReporteExistenciaPapelPage from '../pages/Reportes/ReporteExistenciaPapelPage';
|
import ReporteExistenciaPapelPage from '../pages/Reportes/ReporteExistenciaPapelPage';
|
||||||
|
import ReporteMovimientoBobinasPage from '../pages/Reportes/ReporteMovimientoBobinasPage';
|
||||||
|
import ReporteMovimientoBobinasEstadoPage from '../pages/Reportes/ReporteMovimientoBobinasEstadoPage';
|
||||||
|
import ReporteListadoDistribucionGeneralPage from '../pages/Reportes/ReporteListadoDistribucionGeneralPage';
|
||||||
|
import ReporteListadoDistribucionCanillasPage from '../pages/Reportes/ReporteListadoDistribucionCanillasPage';
|
||||||
|
|
||||||
// Auditorias
|
// Auditorias
|
||||||
import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage';
|
import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage';
|
||||||
@@ -153,7 +157,10 @@ const AppRoutes = () => {
|
|||||||
<Route path="reportes" element={<ReportesIndexPage />}> {/* Página principal del módulo */}
|
<Route path="reportes" element={<ReportesIndexPage />}> {/* Página principal del módulo */}
|
||||||
<Route index element={<Typography sx={{p:2}}>Seleccione un reporte del menú lateral.</Typography>} /> {/* Placeholder */}
|
<Route index element={<Typography sx={{p:2}}>Seleccione un reporte del menú lateral.</Typography>} /> {/* Placeholder */}
|
||||||
<Route path="existencia-papel" element={<ReporteExistenciaPapelPage />} />
|
<Route path="existencia-papel" element={<ReporteExistenciaPapelPage />} />
|
||||||
{/* Aquí se añadirán las rutas para otros reportes */}
|
<Route path="movimiento-bobinas" element={<ReporteMovimientoBobinasPage />} />
|
||||||
|
<Route path="movimiento-bobinas-estado" element={<ReporteMovimientoBobinasEstadoPage />} />
|
||||||
|
<Route path="listado-distribucion-general" element={<ReporteListadoDistribucionGeneralPage />} />
|
||||||
|
<Route path="listado-distribucion-canillas" element={<ReporteListadoDistribucionCanillasPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
{/* Módulo de Radios (anidado) */}
|
{/* Módulo de Radios (anidado) */}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import apiClient from '../apiClient';
|
import apiClient from '../apiClient';
|
||||||
import type { ExistenciaPapelDto } from '../../models/dtos/Reportes/ExistenciaPapelDto';
|
import type { ExistenciaPapelDto } from '../../models/dtos/Reportes/ExistenciaPapelDto';
|
||||||
|
import type { MovimientoBobinasDto } from '../../models/dtos/Reportes/MovimientoBobinasDto';
|
||||||
|
import type { MovimientoBobinasPorEstadoResponseDto } from '../../models/dtos/Reportes/MovimientoBobinasPorEstadoResponseDto';
|
||||||
|
import type { ListadoDistribucionGeneralResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionGeneralResponseDto';
|
||||||
|
import type { ListadoDistribucionCanillasResponseDto } from '../../models/dtos/Reportes/ListadoDistribucionCanillasResponseDto';
|
||||||
|
|
||||||
interface GetExistenciaPapelParams {
|
interface GetExistenciaPapelParams {
|
||||||
fechaDesde: string; // yyyy-MM-dd
|
fechaDesde: string; // yyyy-MM-dd
|
||||||
@@ -40,13 +44,102 @@ const getExistenciaPapel = async (params: GetExistenciaPapelParams): Promise<Exi
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMovimientoBobinas = async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
}): Promise<MovimientoBobinasDto[]> => {
|
||||||
|
const response = await apiClient.get<MovimientoBobinasDto[]>('/reportes/movimiento-bobinas', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
// ... Aquí irán los métodos para otros reportes ...
|
const getMovimientoBobinasPdf = async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/movimiento-bobinas/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMovimientoBobinasEstado = async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
}): Promise<MovimientoBobinasPorEstadoResponseDto> => { // <- Devuelve el DTO combinado
|
||||||
|
const response = await apiClient.get<MovimientoBobinasPorEstadoResponseDto>('/reportes/movimiento-bobinas-estado', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMovimientoBobinasEstadoPdf = async (params: {
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
idPlanta: number;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/movimiento-bobinas-estado/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getListadoDistribucionGeneral = async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string; // YYYY-MM-DD (primer día del mes)
|
||||||
|
fechaHasta: string; // YYYY-MM-DD (último día del mes)
|
||||||
|
}): Promise<ListadoDistribucionGeneralResponseDto> => {
|
||||||
|
const response = await apiClient.get<ListadoDistribucionGeneralResponseDto>('/reportes/listado-distribucion-general', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getListadoDistribucionGeneralPdf = async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/listado-distribucion-general/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getListadoDistribucionCanillas = async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}): Promise<ListadoDistribucionCanillasResponseDto> => {
|
||||||
|
const response = await apiClient.get<ListadoDistribucionCanillasResponseDto>('/reportes/listado-distribucion-canillas', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getListadoDistribucionCanillasPdf = async (params: {
|
||||||
|
idPublicacion: number;
|
||||||
|
fechaDesde: string;
|
||||||
|
fechaHasta: string;
|
||||||
|
}): Promise<Blob> => {
|
||||||
|
const response = await apiClient.get('/reportes/listado-distribucion-canillas/pdf', {
|
||||||
|
params,
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
const reportesService = {
|
const reportesService = {
|
||||||
getExistenciaPapel,
|
getExistenciaPapel,
|
||||||
getExistenciaPapelPdf,
|
getExistenciaPapelPdf,
|
||||||
// ...
|
getMovimientoBobinas,
|
||||||
|
getMovimientoBobinasPdf,
|
||||||
|
getMovimientoBobinasEstado,
|
||||||
|
getMovimientoBobinasEstadoPdf,
|
||||||
|
getListadoDistribucionGeneral,
|
||||||
|
getListadoDistribucionGeneralPdf,
|
||||||
|
getListadoDistribucionCanillas,
|
||||||
|
getListadoDistribucionCanillasPdf
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reportesService;
|
export default reportesService;
|
||||||
Reference in New Issue
Block a user