reporte por mes y año
This commit is contained in:
@@ -10,6 +10,7 @@ import { saveAs } from "file-saver";
|
||||
interface Periodo {
|
||||
id_periodo: number;
|
||||
semestre: string;
|
||||
fecha_inicio_servicio: string;
|
||||
}
|
||||
|
||||
interface ApiResponse {
|
||||
@@ -21,6 +22,8 @@ interface ApiResponse {
|
||||
total: string;
|
||||
}
|
||||
|
||||
type Tipo = "periodo" | "mes" | "anio";
|
||||
|
||||
type TipoConteo = "WINDOWS" | "MACINTOSH" | "LINUX" | "PROFESORES";
|
||||
|
||||
interface TablaRow {
|
||||
@@ -37,13 +40,20 @@ interface TablaRow {
|
||||
|
||||
export default function Inscripciones() {
|
||||
const [periodo, setPeriodo] = useState<Periodo[]>([]);
|
||||
const [periodoInicio, setPeriodoInicio] = useState<number | null>(null);
|
||||
const [periodoFin, setPeriodoFin] = useState<number | null>(null);
|
||||
const [tipo, setTipo] = useState<Tipo>("periodo");
|
||||
|
||||
const [periodoInicio, setPeriodoInicio] = useState<any>(null);
|
||||
const [periodoFin, setPeriodoFin] = useState<any>(null);
|
||||
|
||||
const [anio, setAnio] = useState<number | null>(null);
|
||||
|
||||
const [tablaData, setTablaData] = useState<TablaRow[]>([]);
|
||||
const [rawData, setRawData] = useState<ApiResponse[]>([]);
|
||||
const [rawData, setRawData] = useState<ApiResponse[]>([]);
|
||||
|
||||
const token = Cookies.get("token");
|
||||
const headers = { Authorization: `Bearer ${token}` };
|
||||
|
||||
// Obtener periodos
|
||||
useEffect(() => {
|
||||
const getPeriodo = async () => {
|
||||
const response = await axios.get(`${envConfig.apiUrl}/periodo`, { headers });
|
||||
@@ -52,77 +62,101 @@ export default function Inscripciones() {
|
||||
getPeriodo();
|
||||
}, []);
|
||||
|
||||
const handleBuscar = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
// Obtener años desde fechas
|
||||
const aniosDisponibles = Array.from(
|
||||
new Set(
|
||||
periodo.map((p) =>
|
||||
new Date(p.fecha_inicio_servicio).getFullYear()
|
||||
)
|
||||
)
|
||||
).sort();
|
||||
|
||||
if (!periodoInicio || !periodoFin) {
|
||||
alert("Selecciona ambos periodos");
|
||||
return;
|
||||
}
|
||||
// ============================
|
||||
// 🔍 BUSCAR
|
||||
// ============================
|
||||
const handleBuscar = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (periodoInicio > periodoFin) {
|
||||
alert("El periodo inicio no puede ser mayor al final");
|
||||
return;
|
||||
}
|
||||
let url = "";
|
||||
|
||||
const esMismoPeriodo = periodoInicio === periodoFin;
|
||||
|
||||
try {
|
||||
const url = esMismoPeriodo
|
||||
? `${envConfig.apiUrl}/alumno-inscrito/inscritos/${periodoInicio}` // 👈 uno solo
|
||||
: `${envConfig.apiUrl}/alumno-inscrito/inscritos/${periodoInicio}/${periodoFin}`; // 👈 rango
|
||||
|
||||
const response = await axios.get(url, { headers });
|
||||
|
||||
const data: ApiResponse[] = response.data;
|
||||
|
||||
setRawData(data);
|
||||
|
||||
const agrupado: Record<string, TablaRow> = {};
|
||||
|
||||
data.forEach((item) => {
|
||||
const key = `${item.carrera}-${item.semestre}`;
|
||||
|
||||
if (!agrupado[key]) {
|
||||
agrupado[key] = {
|
||||
carrera: item.carrera,
|
||||
semestre: item.semestre,
|
||||
WINDOWS: 0,
|
||||
MACINTOSH: 0,
|
||||
LINUX: 0,
|
||||
PROFESORES: 0,
|
||||
TOTAL: 0,
|
||||
femenino: 0,
|
||||
masculino: 0,
|
||||
};
|
||||
if (tipo === "periodo") {
|
||||
if (!periodoInicio || !periodoFin) {
|
||||
alert("Selecciona ambos periodos");
|
||||
return;
|
||||
}
|
||||
|
||||
const fila = agrupado[key];
|
||||
const tipoProfesor = item.profesor as TipoConteo;
|
||||
url = `${envConfig.apiUrl}/alumno-inscrito/inscritos/periodo/${periodoInicio}/${periodoFin}`;
|
||||
}
|
||||
|
||||
const total = Number(item.total);
|
||||
const fem = Number(item.femenino);
|
||||
const masc = Number(item.masculino);
|
||||
if (tipo === "anio") {
|
||||
if (!periodoInicio || !periodoFin) {
|
||||
alert("Selecciona ambos años");
|
||||
return;
|
||||
}
|
||||
|
||||
fila[tipoProfesor] += total;
|
||||
fila.TOTAL += total;
|
||||
fila.femenino += fem;
|
||||
fila.masculino += masc;
|
||||
});
|
||||
url = `${envConfig.apiUrl}/alumno-inscrito/inscritos/anio/${periodoInicio}/${periodoFin}`;
|
||||
}
|
||||
|
||||
setTablaData(Object.values(agrupado));
|
||||
} catch (error) {
|
||||
console.error("Error al obtener inscritos", error);
|
||||
if (tipo === "mes") {
|
||||
if (!anio) {
|
||||
alert("Selecciona un año");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Export Excel
|
||||
url = `${envConfig.apiUrl}/alumno-inscrito/inscritos/mes/${anio}`;
|
||||
}
|
||||
try {
|
||||
console.log("URL:", url);
|
||||
|
||||
const response = await axios.get(url, { headers });
|
||||
const data: ApiResponse[] = response.data;
|
||||
|
||||
setRawData(data);
|
||||
|
||||
const agrupado: Record<string, TablaRow> = {};
|
||||
|
||||
data.forEach((item) => {
|
||||
const key = `${item.carrera}-${item.semestre}`;
|
||||
|
||||
if (!agrupado[key]) {
|
||||
agrupado[key] = {
|
||||
carrera: item.carrera,
|
||||
semestre: item.semestre,
|
||||
WINDOWS: 0,
|
||||
MACINTOSH: 0,
|
||||
LINUX: 0,
|
||||
PROFESORES: 0,
|
||||
TOTAL: 0,
|
||||
femenino: 0,
|
||||
masculino: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const fila = agrupado[key];
|
||||
const tipoProfesor = item.profesor as TipoConteo;
|
||||
|
||||
const total = Number(item.total);
|
||||
const fem = Number(item.femenino);
|
||||
const masc = Number(item.masculino);
|
||||
|
||||
fila[tipoProfesor] += total;
|
||||
fila.TOTAL += total;
|
||||
fila.femenino += fem;
|
||||
fila.masculino += masc;
|
||||
});
|
||||
|
||||
setTablaData(Object.values(agrupado));
|
||||
} catch (error) {
|
||||
console.error("Error al obtener inscritos", error);
|
||||
}
|
||||
};
|
||||
|
||||
const exportarExcel = () => {
|
||||
if (!rawData.length) return;
|
||||
|
||||
const periodosUnicos = Array.from(
|
||||
new Set(rawData.map((t) => t.semestre))
|
||||
).sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
|
||||
);
|
||||
|
||||
const carrerasUnicas = Array.from(
|
||||
new Set(rawData.map((t) => t.carrera))
|
||||
@@ -132,8 +166,6 @@ export default function Inscripciones() {
|
||||
const fila: any = { Carrera: carrera };
|
||||
|
||||
let totalFila = 0;
|
||||
let totalFemenino = 0;
|
||||
let totalMasculino = 0;
|
||||
|
||||
periodosUnicos.forEach((periodo) => {
|
||||
const items = rawData.filter(
|
||||
@@ -147,36 +179,16 @@ export default function Inscripciones() {
|
||||
|
||||
fila[periodo] = totalPeriodo;
|
||||
totalFila += totalPeriodo;
|
||||
|
||||
totalFemenino += items.reduce(
|
||||
(acc, item) => acc + Number(item.femenino),
|
||||
0
|
||||
);
|
||||
|
||||
totalMasculino += items.reduce(
|
||||
(acc, item) => acc + Number(item.masculino),
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
fila["Femenino"] = totalFemenino;
|
||||
fila["Masculino"] = totalMasculino;
|
||||
fila["Total"] = totalFila;
|
||||
|
||||
return fila;
|
||||
});
|
||||
|
||||
const worksheet = XLSX.utils.json_to_sheet(dataExcel);
|
||||
|
||||
worksheet["!cols"] = [
|
||||
{ wch: 25 },
|
||||
...periodosUnicos.map(() => ({ wch: 15 })),
|
||||
{ wch: 15 },
|
||||
{ wch: 15 },
|
||||
{ wch: 15 },
|
||||
];
|
||||
|
||||
const workbook = XLSX.utils.book_new();
|
||||
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "Inscritos");
|
||||
|
||||
const excelBuffer = XLSX.write(workbook, {
|
||||
@@ -196,41 +208,87 @@ export default function Inscripciones() {
|
||||
<h2 className="title">INSCRITOS</h2>
|
||||
|
||||
<form className="containerForm" onSubmit={handleBuscar}>
|
||||
<label>Seleccione rango de periodos</label>
|
||||
<label>Filtro</label>
|
||||
|
||||
{/* Selector tipo */}
|
||||
<select value={tipo} onChange={(e) => setTipo(e.target.value as Tipo)}>
|
||||
<option value="periodo">Periodo</option>
|
||||
<option value="anio">Año</option>
|
||||
<option value="mes">Mes</option>
|
||||
</select>
|
||||
|
||||
<div className="groupInput">
|
||||
<select onChange={(e) => setPeriodoInicio(Number(e.target.value))} defaultValue="">
|
||||
<option value="" disabled>Inicio</option>
|
||||
{periodo.map((p) => (
|
||||
<option key={p.id_periodo} value={p.id_periodo}>
|
||||
{p.semestre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* PERIODO */}
|
||||
{tipo === "periodo" && (
|
||||
<>
|
||||
<select onChange={(e) => setPeriodoInicio(Number(e.target.value))}>
|
||||
<option value="">Inicio</option>
|
||||
{periodo.map((p) => (
|
||||
<option key={p.id_periodo} value={p.id_periodo}>
|
||||
{p.semestre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select onChange={(e) => setPeriodoFin(Number(e.target.value))} defaultValue="">
|
||||
<option value="" disabled>Fin</option>
|
||||
{periodo.map((p) => (
|
||||
<option key={p.id_periodo} value={p.id_periodo}>
|
||||
{p.semestre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select onChange={(e) => setPeriodoFin(Number(e.target.value))}>
|
||||
<option value="">Fin</option>
|
||||
{periodo.map((p) => (
|
||||
<option key={p.id_periodo} value={p.id_periodo}>
|
||||
{p.semestre}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* AÑO */}
|
||||
{tipo === "anio" && (
|
||||
<>
|
||||
<select onChange={(e) => setPeriodoInicio(Number(e.target.value))}>
|
||||
<option value="">Año inicio</option>
|
||||
{aniosDisponibles.map((a) => (
|
||||
<option key={a} value={a}>
|
||||
{a}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<select onChange={(e) => setPeriodoFin(Number(e.target.value))}>
|
||||
<option value="">Año fin</option>
|
||||
{aniosDisponibles.map((a) => (
|
||||
<option key={a} value={a}>
|
||||
{a}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* MES */}
|
||||
{tipo === "mes" && (
|
||||
<>
|
||||
<select onChange={(e) => setAnio(Number(e.target.value))}>
|
||||
<option value="">Año</option>
|
||||
{aniosDisponibles.map((a) => (
|
||||
<option key={a} value={a}>
|
||||
{a}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</>
|
||||
)}
|
||||
|
||||
<button className="button buttonSearch" type="submit">
|
||||
Buscar
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="button buttonSearch"
|
||||
onClick={exportarExcel}
|
||||
>
|
||||
<button type="button" className="button buttonSearch" onClick={exportarExcel}>
|
||||
Exportar Excel
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{/* TABLA */}
|
||||
<div className="containerTable">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -250,7 +308,7 @@ export default function Inscripciones() {
|
||||
<tbody>
|
||||
{tablaData.map((row, index) => (
|
||||
<tr key={index}>
|
||||
<td className="textLeft">{row.carrera}</td>
|
||||
<td>{row.carrera}</td>
|
||||
<td>{row.semestre}</td>
|
||||
<td>{row.femenino}</td>
|
||||
<td>{row.masculino}</td>
|
||||
|
||||
Generated
+18
@@ -696,6 +696,7 @@
|
||||
"version": "2.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz",
|
||||
"integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
@@ -735,6 +736,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -755,6 +757,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -775,6 +778,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -795,6 +799,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -815,6 +820,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -835,6 +841,7 @@
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -855,6 +862,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -875,6 +883,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -895,6 +904,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -915,6 +925,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -935,6 +946,7 @@
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -955,6 +967,7 @@
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -975,6 +988,7 @@
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
@@ -2635,6 +2649,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
@@ -2677,6 +2692,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
@@ -3190,6 +3206,7 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
@@ -3290,6 +3307,7 @@
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
|
||||
Reference in New Issue
Block a user