Continuación de CRUDs e inicio de Reportes.
This commit is contained in:
182
Frontend/package-lock.json
generated
182
Frontend/package-lock.json
generated
@@ -11,12 +11,14 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^7.0.2",
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/x-data-grid": "^8.4.0",
|
||||
"axios": "^1.9.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.5.3"
|
||||
"react-router-dom": "^7.5.3",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
@@ -1426,6 +1428,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.4.0.tgz",
|
||||
"integrity": "sha512-c0fgMhvQTjCSo3LgRK1Mdk2msktCl9uwMYUYlP6bbqJ7I03IvS+1aZ+s3nSLmaq1aVh7sE2Bnuz63OnVerTLJA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.0.2",
|
||||
"@mui/x-internals": "8.4.0",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^5.1.1",
|
||||
"use-sync-external-store": "^1.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-internals": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.4.0.tgz",
|
||||
"integrity": "sha512-Z7FCahC4MLfTVzEwnKOB7P1fiR9DzFuMzHOPRNaMXc/rsS7unbtBKAG94yvsRzReCyjzZUVA7h37lnQ1DoPKJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -2136,6 +2196,15 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/adler-32": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
|
||||
"integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -2363,6 +2432,19 @@
|
||||
],
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/cfb": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
|
||||
"integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"crc-32": "~1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -2389,6 +2471,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/codepage": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
|
||||
"integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -2516,6 +2607,18 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/crc-32": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"crc32": "bin/crc32.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -3239,6 +3342,15 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/frac": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
|
||||
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||
@@ -4335,6 +4447,12 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
@@ -4670,6 +4788,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ssf": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
|
||||
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"frac": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
@@ -4927,6 +5057,15 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
@@ -5056,6 +5195,24 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wmf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||
"integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
|
||||
"integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
@@ -5073,6 +5230,27 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/xlsx": {
|
||||
"version": "0.18.5",
|
||||
"resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
|
||||
"integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"adler-32": "~1.3.0",
|
||||
"cfb": "~1.2.1",
|
||||
"codepage": "~1.15.0",
|
||||
"crc-32": "~1.2.1",
|
||||
"ssf": "~0.11.2",
|
||||
"wmf": "~1.0.1",
|
||||
"word": "~0.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"xlsx": "bin/xlsx.njs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^7.0.2",
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/x-data-grid": "^8.4.0",
|
||||
"axios": "^1.9.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.5.3"
|
||||
"react-router-dom": "^7.5.3",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
|
||||
8
Frontend/src/models/dtos/Reportes/ExistenciaPapelDto.ts
Normal file
8
Frontend/src/models/dtos/Reportes/ExistenciaPapelDto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export interface ExistenciaPapelDto {
|
||||
tipoBobina: string;
|
||||
bobinasEnStock: number | null;
|
||||
totalKilosEnStock: number | null;
|
||||
consumoAcumulado: number | null;
|
||||
promedioDiasDisponibles: number | null;
|
||||
fechaEstimacionFinStock?: string | null;
|
||||
}
|
||||
@@ -4,10 +4,10 @@ import { Box, Tabs, Tab, Paper, Typography } from '@mui/material';
|
||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
// Define las sub-pestañas del módulo Contables
|
||||
const contablesSubModules = [
|
||||
{ label: 'Tipos de Pago', path: 'tipos-pago' }, // Se convertirá en /contables/tipos-pago
|
||||
const contablesSubModules = [
|
||||
{ label: 'Pagos Distribuidores', path: 'pagos-distribuidores' },
|
||||
{ label: 'Notas Crédito/Débito', path: 'notas-cd' },
|
||||
{ label: 'Tipos de Pago', path: 'tipos-pago' },
|
||||
];
|
||||
|
||||
const ContablesIndexPage: React.FC = () => {
|
||||
|
||||
@@ -36,8 +36,8 @@ const GestionarNotasCDPage: React.FC = () => {
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroDestino, setFiltroDestino] = useState<DestinoFiltroType>('');
|
||||
const [filtroIdDestinatario, setFiltroIdDestinatario] = useState<number | string>('');
|
||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||
|
||||
@@ -31,8 +31,8 @@ const GestionarPagosDistribuidorPage: React.FC = () => {
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]);//useState('');
|
||||
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||
const [filtroTipoMov, setFiltroTipoMov] = useState<'Recibido' | 'Realizado' | ''>('');
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
const CtrlDevolucionesPage: React.FC = () => {
|
||||
return <Typography variant="h6">Página de Gestión del Control de Devoluciones</Typography>;
|
||||
};
|
||||
export default CtrlDevolucionesPage;
|
||||
@@ -150,7 +150,7 @@ const GestionarCanillitasPage: React.FC = () => {
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
label="Solo Activos"
|
||||
label="Ver Activos"
|
||||
sx={{ flexShrink: 0 }} // Para que el label no se comprima demasiado
|
||||
/>
|
||||
{/* <Button variant="contained" onClick={cargarCanillitas} size="small">Buscar</Button> */}
|
||||
|
||||
@@ -29,8 +29,8 @@ const GestionarControlDevolucionesPage: React.FC = () => {
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroIdEmpresa, setFiltroIdEmpresa] = useState<number | string>('');
|
||||
|
||||
const [empresas, setEmpresas] = useState<EmpresaDto[]>([]);
|
||||
|
||||
@@ -36,8 +36,8 @@ const GestionarEntradasSalidasCanillaPage: React.FC = () => {
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||
const [filtroIdCanilla, setFiltroIdCanilla] = useState<number | string>('');
|
||||
const [filtroEstadoLiquidacion, setFiltroEstadoLiquidacion] = useState<'todos' | 'liquidados' | 'noLiquidados'>('noLiquidados');
|
||||
|
||||
@@ -32,8 +32,8 @@ const GestionarEntradasSalidasDistPage: React.FC = () => {
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||
const [filtroIdDistribuidor, setFiltroIdDistribuidor] = useState<number | string>('');
|
||||
const [filtroTipoMov, setFiltroTipoMov] = useState<'Salida' | 'Entrada' | ''>('');
|
||||
|
||||
@@ -30,8 +30,8 @@ const GestionarSalidasOtrosDestinosPage: React.FC = () => {
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroIdPublicacion, setFiltroIdPublicacion] = useState<number | string>('');
|
||||
const [filtroIdDestino, setFiltroIdDestino] = useState<number | string>('');
|
||||
|
||||
|
||||
260
Frontend/src/pages/Reportes/ReporteExistenciaPapelPage.tsx
Normal file
260
Frontend/src/pages/Reportes/ReporteExistenciaPapelPage.tsx
Normal file
@@ -0,0 +1,260 @@
|
||||
// src/pages/Reportes/ReporteExistenciaPapelPage.tsx
|
||||
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 { ExistenciaPapelDto } from '../../models/dtos/Reportes/ExistenciaPapelDto';
|
||||
import SeleccionaReporteExistenciaPapel from './SeleccionaReporteExistenciaPapel';
|
||||
import * as XLSX from 'xlsx';
|
||||
import axios from 'axios';
|
||||
|
||||
const ReporteExistenciaPapelPage: React.FC = () => {
|
||||
const [reportData, setReportData] = useState<ExistenciaPapelDto[]>([]);
|
||||
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;
|
||||
consolidado: boolean;
|
||||
} | null>(null);
|
||||
|
||||
const handleGenerarReporte = useCallback(async (params: {
|
||||
fechaDesde: string;
|
||||
fechaHasta: string;
|
||||
idPlanta?: number | null;
|
||||
consolidado: boolean;
|
||||
}) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
setApiErrorParams(null);
|
||||
setCurrentParams(params);
|
||||
try {
|
||||
const data = await reportesService.getExistenciaPapel(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;
|
||||
}
|
||||
|
||||
// 1) Data inicial formateada
|
||||
const dataToExport: Record<string, any>[] = reportData.map(item => {
|
||||
let fechaString = '-';
|
||||
if (item.fechaEstimacionFinStock) {
|
||||
const d = new Date(item.fechaEstimacionFinStock);
|
||||
if (!isNaN(d.getTime())) {
|
||||
fechaString = d.toLocaleDateString('es-AR', { timeZone: 'UTC' });
|
||||
}
|
||||
}
|
||||
return {
|
||||
"Tipo Bobina": item.tipoBobina,
|
||||
"Bobinas Stock": item.bobinasEnStock ?? 0,
|
||||
"Kg Stock": item.totalKilosEnStock ?? 0,
|
||||
"Consumo Acum. (Kg)": item.consumoAcumulado ?? 0,
|
||||
"Días Disp. (Prom.)": item.promedioDiasDisponibles != null
|
||||
? Math.round(item.promedioDiasDisponibles)
|
||||
: '-',
|
||||
"Fecha Est. Fin Stock": fechaString,
|
||||
};
|
||||
});
|
||||
|
||||
// 2) Cálculo de totales
|
||||
const totales = dataToExport.reduce(
|
||||
(acc, row) => {
|
||||
acc.bobinas += Number(row["Bobinas Stock"]);
|
||||
acc.kilos += Number(row["Kg Stock"]);
|
||||
acc.consumo += Number(row["Consumo Acum. (Kg)"]);
|
||||
return acc;
|
||||
},
|
||||
{ bobinas: 0, kilos: 0, consumo: 0 }
|
||||
);
|
||||
|
||||
// 3) Insertamos la fila de totales
|
||||
dataToExport.push({
|
||||
"Tipo Bobina": "Totales",
|
||||
"Bobinas Stock": totales.bobinas,
|
||||
"Kg Stock": totales.kilos,
|
||||
"Consumo Acum. (Kg)": totales.consumo,
|
||||
"Días Disp. (Prom.)": '-', // o lo que prefieras
|
||||
"Fecha Est. Fin Stock": '-' // vacío o guión
|
||||
});
|
||||
|
||||
// 4) Creamos la hoja
|
||||
const ws = XLSX.utils.json_to_sheet(dataToExport);
|
||||
|
||||
// 5) Auto‐anchos
|
||||
const headers = Object.keys(dataToExport[0]);
|
||||
ws['!cols'] = headers.map(h => {
|
||||
const maxLen = dataToExport.reduce((prev, row) => {
|
||||
const cell = row[h]?.toString() ?? '';
|
||||
return Math.max(prev, cell.length);
|
||||
}, h.length);
|
||||
return { wch: maxLen + 2 };
|
||||
});
|
||||
|
||||
// 6) Congelamos la primera fila
|
||||
ws['!freeze'] = { xSplit: 0, ySplit: 1 };
|
||||
|
||||
// 7) Libro y guardado
|
||||
const wb = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(wb, ws, "ExistenciaPapel");
|
||||
|
||||
let fileName = "ReporteExistenciaPapel";
|
||||
if (currentParams) {
|
||||
fileName += `_${currentParams.fechaDesde}_a_${currentParams.fechaHasta}`;
|
||||
if (currentParams.consolidado) fileName += "_Consolidado";
|
||||
else if (currentParams.idPlanta) fileName += `_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.getExistenciaPapelPdf(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}>
|
||||
<SeleccionaReporteExistenciaPapel
|
||||
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: Existencia de Papel</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. Stock</TableCell>
|
||||
<TableCell align="right">Kg. Stock</TableCell>
|
||||
<TableCell align="right">Consumo Acum. (Kg)</TableCell>
|
||||
<TableCell align="right">Días Disp. (Prom.)</TableCell>
|
||||
<TableCell>Fecha Est. Fin Stock</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{reportData.map((row, idx) => {
|
||||
const d = row.fechaEstimacionFinStock ? new Date(row.fechaEstimacionFinStock) : null;
|
||||
const fechaFmt = d && !isNaN(d.getTime())
|
||||
? d.toLocaleDateString('es-AR', { timeZone: 'UTC' })
|
||||
: '-';
|
||||
return (
|
||||
<TableRow key={row.tipoBobina + idx}>
|
||||
<TableCell>{row.tipoBobina}</TableCell>
|
||||
<TableCell align="right">{row.bobinasEnStock?.toLocaleString('es-AR') ?? '-'}</TableCell>
|
||||
<TableCell align="right">{row.totalKilosEnStock?.toLocaleString('es-AR') ?? '-'}</TableCell>
|
||||
<TableCell align="right">{row.consumoAcumulado?.toLocaleString('es-AR') ?? '-'}</TableCell>
|
||||
<TableCell align="right">{row.promedioDiasDisponibles != null ? Math.round(row.promedioDiasDisponibles).toLocaleString('es-AR') : '-'}</TableCell>
|
||||
<TableCell>{fechaFmt}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReporteExistenciaPapelPage;
|
||||
91
Frontend/src/pages/Reportes/ReportesIndexPage.tsx
Normal file
91
Frontend/src/pages/Reportes/ReportesIndexPage.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Box, Tabs, Tab, Paper, Typography } from '@mui/material';
|
||||
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
const reportesSubModules = [
|
||||
{ label: 'Existencia de Papel', path: 'existencia-papel' },
|
||||
// { label: 'Consumo Bobinas Mensual', path: 'consumo-bobinas-mensual' }, // Ejemplo
|
||||
// ... agregar otros reportes aquí a medida que se implementen
|
||||
];
|
||||
|
||||
const ReportesIndexPage: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [selectedSubTab, setSelectedSubTab] = useState<number | false>(false);
|
||||
|
||||
useEffect(() => {
|
||||
const currentBasePath = '/reportes';
|
||||
// Extrae la parte de la ruta que sigue a '/reportes/'
|
||||
const subPathSegment = location.pathname.startsWith(currentBasePath + '/')
|
||||
? location.pathname.substring(currentBasePath.length + 1).split('/')[0] // Toma solo el primer segmento
|
||||
: undefined;
|
||||
|
||||
let activeTabIndex = -1;
|
||||
|
||||
if (subPathSegment) {
|
||||
activeTabIndex = reportesSubModules.findIndex(
|
||||
(subModule) => subModule.path === subPathSegment
|
||||
);
|
||||
}
|
||||
|
||||
if (activeTabIndex !== -1) {
|
||||
setSelectedSubTab(activeTabIndex);
|
||||
} else {
|
||||
// Si estamos exactamente en '/reportes' y hay sub-módulos, navegar al primero.
|
||||
if (location.pathname === currentBasePath && reportesSubModules.length > 0) {
|
||||
navigate(reportesSubModules[0].path, { replace: true }); // Navega a la sub-ruta
|
||||
// setSelectedSubTab(0); // Esto se manejará en la siguiente ejecución del useEffect debido al cambio de ruta
|
||||
} else {
|
||||
setSelectedSubTab(false); // Ninguna sub-ruta activa o conocida, o no hay sub-módulos
|
||||
}
|
||||
}
|
||||
}, [location.pathname, navigate]); // Solo depende de location.pathname y navigate
|
||||
|
||||
const handleSubTabChange = (_event: React.SyntheticEvent, newValue: number) => {
|
||||
// No es necesario setSelectedSubTab aquí directamente, el useEffect lo manejará.
|
||||
navigate(reportesSubModules[newValue].path);
|
||||
};
|
||||
|
||||
// Si no hay sub-módulos definidos, podría ser un estado inicial
|
||||
if (reportesSubModules.length === 0) {
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h5" gutterBottom>Módulo de Reportes</Typography>
|
||||
<Typography>No hay reportes configurados.</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Módulo de Reportes
|
||||
</Typography>
|
||||
<Paper square elevation={1}>
|
||||
<Tabs
|
||||
value={selectedSubTab} // 'false' es un valor válido para Tabs si ninguna pestaña está seleccionada
|
||||
onChange={handleSubTabChange}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
aria-label="sub-módulos de reportes"
|
||||
>
|
||||
{reportesSubModules.map((subModule) => (
|
||||
<Tab key={subModule.path} label={subModule.label} />
|
||||
))}
|
||||
</Tabs>
|
||||
</Paper>
|
||||
<Box sx={{ pt: 2 }}>
|
||||
{/* Outlet renderizará ReporteExistenciaPapelPage u otros
|
||||
Solo renderiza el Outlet si hay una pestaña seleccionada VÁLIDA.
|
||||
Si selectedSubTab es 'false' (porque ninguna ruta coincide con los sub-módulos),
|
||||
se muestra el mensaje.
|
||||
*/}
|
||||
{selectedSubTab !== false ? <Outlet /> : <Typography sx={{p:2}}>Seleccione un reporte del menú lateral o de las pestañas.</Typography>}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReportesIndexPage;
|
||||
156
Frontend/src/pages/Reportes/SeleccionaReporteExistenciaPapel.tsx
Normal file
156
Frontend/src/pages/Reportes/SeleccionaReporteExistenciaPapel.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box, Typography, TextField, Button, CircularProgress, Alert,
|
||||
FormControl, InputLabel, Select, MenuItem, Checkbox, FormControlLabel
|
||||
} from '@mui/material';
|
||||
import type { PlantaDto } from '../../models/dtos/Impresion/PlantaDto';
|
||||
import plantaService from '../../services/Impresion/plantaService';
|
||||
|
||||
interface SeleccionaReporteExistenciaPapelProps {
|
||||
onGenerarReporte: (params: {
|
||||
fechaDesde: string;
|
||||
fechaHasta: string;
|
||||
idPlanta?: number | null;
|
||||
consolidado: boolean;
|
||||
}) => Promise<void>; // La función que realmente llama al servicio y maneja los datos
|
||||
onCancel: () => void; // Para cerrar el modal/componente
|
||||
isLoading?: boolean; // Para mostrar estado de carga desde el padre
|
||||
apiErrorMessage?: string | null; // Para mostrar errores de API desde el padre
|
||||
}
|
||||
|
||||
const SeleccionaReporteExistenciaPapel: React.FC<SeleccionaReporteExistenciaPapelProps> = ({
|
||||
onGenerarReporte,
|
||||
onCancel,
|
||||
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 [consolidado, setConsolidado] = useState<boolean>(false);
|
||||
|
||||
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();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Si se marca consolidado, limpiar y deshabilitar la selección de planta
|
||||
if (consolidado) {
|
||||
setIdPlanta('');
|
||||
}
|
||||
}, [consolidado]);
|
||||
|
||||
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 (!consolidado && !idPlanta) {
|
||||
errors.idPlanta = 'Seleccione una planta si no es consolidado.';
|
||||
}
|
||||
setLocalErrors(errors);
|
||||
return Object.keys(errors).length === 0;
|
||||
};
|
||||
|
||||
const handleGenerar = () => {
|
||||
if (!validate()) return;
|
||||
onGenerarReporte({
|
||||
fechaDesde,
|
||||
fechaHasta,
|
||||
idPlanta: consolidado ? null : Number(idPlanta),
|
||||
consolidado
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2, border: '1px solid #ccc', borderRadius: '4px', minWidth: 380 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Parámetros: Existencia de Papel
|
||||
</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 }}
|
||||
/>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={consolidado}
|
||||
onChange={(e) => setConsolidado(e.target.checked)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
}
|
||||
label="Consolidado (Todas las Plantas)"
|
||||
sx={{ mt: 1, mb: 1 }}
|
||||
/>
|
||||
<FormControl fullWidth margin="normal" error={!!localErrors.idPlanta} disabled={isLoading || loadingDropdowns || consolidado}>
|
||||
<InputLabel id="planta-select-label" required={!consolidado}>Planta</InputLabel>
|
||||
<Select
|
||||
labelId="planta-select-label"
|
||||
label="Planta"
|
||||
value={consolidado ? '' : idPlanta} // Limpiar selección si es consolidado
|
||||
onChange={(e) => { setIdPlanta(e.target.value as number); setLocalErrors(p => ({ ...p, idPlanta: null })); }}
|
||||
>
|
||||
<MenuItem value="" disabled><em>{consolidado ? 'N/A (Consolidado)' : '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={onCancel} color="secondary" disabled={isLoading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button onClick={handleGenerar} variant="contained" disabled={isLoading || loadingDropdowns}>
|
||||
{isLoading ? <CircularProgress size={24} /> : 'Generar Reporte'}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SeleccionaReporteExistenciaPapel;
|
||||
@@ -20,8 +20,8 @@ const GestionarAuditoriaUsuariosPage: React.FC = () => {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Filtros
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState('');
|
||||
const [filtroFechaDesde, setFiltroFechaDesde] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroFechaHasta, setFiltroFechaHasta] = useState<string>(new Date().toISOString().split('T')[0]); //useState('');
|
||||
const [filtroIdUsuarioAfectado, setFiltroIdUsuarioAfectado] = useState<UsuarioDto | null>(null);
|
||||
const [filtroIdUsuarioModifico, setFiltroIdUsuarioModifico] = useState<UsuarioDto | null>(null);
|
||||
const [filtroTipoMod, setFiltroTipoMod] = useState('');
|
||||
|
||||
@@ -52,6 +52,10 @@ import GestionarRitmosPage from '../pages/Radios/GestionarRitmosPage';
|
||||
import GestionarCancionesPage from '../pages/Radios/GestionarCancionesPage';
|
||||
import GenerarListasRadioPage from '../pages/Radios/GenerarListasRadioPage';
|
||||
|
||||
// Reportes
|
||||
import ReportesIndexPage from '../pages/Reportes/ReportesIndexPage'; // Crear este si no existe
|
||||
import ReporteExistenciaPapelPage from '../pages/Reportes/ReporteExistenciaPapelPage';
|
||||
|
||||
// Auditorias
|
||||
import GestionarAuditoriaUsuariosPage from '../pages/Usuarios/Auditoria/GestionarAuditoriaUsuariosPage';
|
||||
|
||||
@@ -150,8 +154,12 @@ const AppRoutes = () => {
|
||||
<Route path="tiradas" element={<GestionarTiradasPage />} />
|
||||
</Route>
|
||||
|
||||
{/* Otros Módulos Principales (estos son "finales", no tienen más hijos) */}
|
||||
<Route path="reportes" element={<PlaceholderPage moduleName="Reportes" />} />
|
||||
{/* Módulo de Reportes */}
|
||||
<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 path="existencia-papel" element={<ReporteExistenciaPapelPage />} />
|
||||
{/* Aquí se añadirán las rutas para otros reportes */}
|
||||
</Route>
|
||||
|
||||
{/* Módulo de Radios (anidado) */}
|
||||
<Route path="radios" element={<RadiosIndexPage />}>
|
||||
|
||||
52
Frontend/src/services/Reportes/reportesService.ts
Normal file
52
Frontend/src/services/Reportes/reportesService.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import apiClient from '../apiClient';
|
||||
import type { ExistenciaPapelDto } from '../../models/dtos/Reportes/ExistenciaPapelDto';
|
||||
|
||||
interface GetExistenciaPapelParams {
|
||||
fechaDesde: string; // yyyy-MM-dd
|
||||
fechaHasta: string; // yyyy-MM-dd
|
||||
idPlanta?: number | null;
|
||||
consolidado: boolean;
|
||||
}
|
||||
|
||||
const getExistenciaPapelPdf = async (params: GetExistenciaPapelParams): Promise<Blob> => {
|
||||
const queryParams: Record<string, string | number | boolean> = {
|
||||
fechaDesde: params.fechaDesde,
|
||||
fechaHasta: params.fechaHasta,
|
||||
consolidado: params.consolidado,
|
||||
};
|
||||
if (params.idPlanta && !params.consolidado) {
|
||||
queryParams.idPlanta = params.idPlanta;
|
||||
}
|
||||
|
||||
const response = await apiClient.get('/reportes/existencia-papel/pdf', {
|
||||
params: queryParams,
|
||||
responseType: 'blob', // ¡Importante para descargar archivos!
|
||||
});
|
||||
return response.data; // response.data será un Blob
|
||||
};
|
||||
|
||||
const getExistenciaPapel = async (params: GetExistenciaPapelParams): Promise<ExistenciaPapelDto[]> => {
|
||||
// Construir los query params, omitiendo idPlanta si es consolidado o no está definido
|
||||
const queryParams: Record<string, string | number | boolean> = {
|
||||
fechaDesde: params.fechaDesde,
|
||||
fechaHasta: params.fechaHasta,
|
||||
consolidado: params.consolidado,
|
||||
};
|
||||
if (params.idPlanta && !params.consolidado) {
|
||||
queryParams.idPlanta = params.idPlanta;
|
||||
}
|
||||
|
||||
const response = await apiClient.get<ExistenciaPapelDto[]>('/reportes/existencia-papel', { params: queryParams });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
|
||||
// ... Aquí irán los métodos para otros reportes ...
|
||||
|
||||
const reportesService = {
|
||||
getExistenciaPapel,
|
||||
getExistenciaPapelPdf,
|
||||
// ...
|
||||
};
|
||||
|
||||
export default reportesService;
|
||||
Reference in New Issue
Block a user