Actualizar frontend/src/pages/Ocupabilidad.jsx
This commit is contained in:
@@ -3,14 +3,14 @@ import { useState, useEffect, useMemo } from "react";
|
|||||||
import { api } from "../lib/api";
|
import { api } from "../lib/api";
|
||||||
import { Loader, ErrorBox, ProgressBar, Filters, Select } from "../components/UI";
|
import { Loader, ErrorBox, ProgressBar, Filters, Select } from "../components/UI";
|
||||||
import { useColumnasAjustables } from "../lib/useColumnasAjustables";
|
import { useColumnasAjustables } from "../lib/useColumnasAjustables";
|
||||||
|
import Modal from "../components/Modal";
|
||||||
|
|
||||||
const MESES = ["ENERO","FEBRERO","MARZO","ABRIL","MAYO","JUNIO","JULIO","AGOSTO","SEPTIEMBRE","OCTUBRE","NOVIEMBRE","DICIEMBRE"];
|
const MESES = ["ENERO","FEBRERO","MARZO","ABRIL","MAYO","JUNIO","JULIO","AGOSTO","SEPTIEMBRE","OCTUBRE","NOVIEMBRE","DICIEMBRE"];
|
||||||
const ANOS = [2024, 2025, 2026];
|
const ANOS = [2024, 2025, 2026];
|
||||||
const SEDES = ["TODOS", "LIMA", "AREQUIPA", "PIURA", "TRUJILLO"];
|
const SEDES = ["TODOS", "LIMA", "AREQUIPA", "PIURA", "TRUJILLO"];
|
||||||
const PROGRAMAS = ["TODOS", "SEMINARIOS", "OTROS", "TEAC", "TERC"];
|
const PROGRAMAS = ["TODOS", "SEMINARIOS", "OTROS", "TEAC", "TERC"];
|
||||||
|
|
||||||
const COLS = ["PROGRAMA","FECHA INICIO","DÍAS PARA INICIO","TOTAL INSCRITOS","RETIRADOS","INSCRITOS EN CURSO","META INSCRITOS","AVANCE INSCRITOS","INSCRITOS MES","INSCRITOS P.C","INSCRITOS REFRIPERU","INSCRITOS CONTINUIDAD"];
|
const COLS = ["PROGRAMA","FECHA INICIO","DÍAS PARA INICIO","TOTAL INSCRITOS","RETIRADOS","INSCRITOS EN CURSO","META INSCRITOS","AVANCE INSCRITOS","INSCRITOS MES","INSCRITOS P.C","INSCRITOS REFRIPERU","INSCRITOS CONTINUIDAD","OPC."];
|
||||||
|
|
||||||
function toNum(v) { const n = parseFloat(String(v).replace("%","").trim()); return isNaN(n) ? 0 : n; }
|
function toNum(v) { const n = parseFloat(String(v).replace("%","").trim()); return isNaN(n) ? 0 : n; }
|
||||||
|
|
||||||
export default function Ocupabilidad() {
|
export default function Ocupabilidad() {
|
||||||
@@ -23,7 +23,8 @@ export default function Ocupabilidad() {
|
|||||||
const [datos, setDatos] = useState([]);
|
const [datos, setDatos] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const cols = useColumnasAjustables([280, 110, 120, 110, 100, 120, 110, 120, 100, 100, 130, 140]);
|
const [modalCurso, setModalCurso] = useState(null);
|
||||||
|
const cols = useColumnasAjustables([280, 110, 120, 110, 100, 120, 110, 120, 100, 100, 130, 140, 80]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let activo = true;
|
let activo = true;
|
||||||
@@ -170,6 +171,14 @@ export default function Ocupabilidad() {
|
|||||||
<td>{cel(parseInt(d.Inscritos_PC)||0)}</td>
|
<td>{cel(parseInt(d.Inscritos_PC)||0)}</td>
|
||||||
<td>{cel(parseInt(d.Descuento)||0)}</td>
|
<td>{cel(parseInt(d.Descuento)||0)}</td>
|
||||||
<td>{cel(parseInt(d.Inscritos_Continuidad)||0)}</td>
|
<td>{cel(parseInt(d.Inscritos_Continuidad)||0)}</td>
|
||||||
|
<td>
|
||||||
|
{esInicio ? "-" : (
|
||||||
|
<button className="btn btn-ghost" style={{padding:"4px 10px",fontSize:11}}
|
||||||
|
onClick={() => setModalCurso({ num_indice: d.num_indice, programa: prog })}>
|
||||||
|
👁️ Ver
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -183,12 +192,110 @@ export default function Ocupabilidad() {
|
|||||||
<td>{totales.meta}</td>
|
<td>{totales.meta}</td>
|
||||||
<td><ProgressBar pct={totales.meta>0 ? totales.total/totales.meta*100 : 0} /></td>
|
<td><ProgressBar pct={totales.meta>0 ? totales.total/totales.meta*100 : 0} /></td>
|
||||||
<td>{totales.mes}</td><td>{totales.pc}</td><td>{totales.refri}</td><td>{totales.cont}</td>
|
<td>{totales.mes}</td><td>{totales.pc}</td><td>{totales.refri}</td><td>{totales.cont}</td>
|
||||||
|
<td>—</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
|
{modalCurso && (
|
||||||
|
<ModalAlumnosCurso
|
||||||
|
numIndice={modalCurso.num_indice}
|
||||||
|
programa={modalCurso.programa}
|
||||||
|
onClose={() => setModalCurso(null)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ModalAlumnosCurso({ numIndice, programa, onClose }) {
|
||||||
|
const [alumnos, setAlumnos] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let activo = true;
|
||||||
|
setLoading(true);
|
||||||
|
api.ocupabilidadAlumnos(numIndice)
|
||||||
|
.then((res) => { if (activo) { setAlumnos(res.alumnos || []); setLoading(false); } })
|
||||||
|
.catch(() => { if (activo) { setAlumnos([]); setLoading(false); } });
|
||||||
|
return () => { activo = false; };
|
||||||
|
}, [numIndice]);
|
||||||
|
|
||||||
|
const colorEstado = (e) => {
|
||||||
|
if (e === "ALU") return { bg:"#ecfdf5", fg:"#047857", bd:"#a7f3d0" };
|
||||||
|
if (e === "PRE") return { bg:"#eff6ff", fg:"#1e40af", bd:"#bfdbfe" };
|
||||||
|
if (e === "RET") return { bg:"#fef2f2", fg:"#b91c1c", bd:"#fecaca" };
|
||||||
|
return { bg:"#f1f5f9", fg:"#475569", bd:"#e2e8f0" };
|
||||||
|
};
|
||||||
|
|
||||||
|
const conteo = useMemo(() => {
|
||||||
|
const c = {};
|
||||||
|
alumnos.forEach((a) => { c[a.estado] = (c[a.estado] || 0) + 1; });
|
||||||
|
return c;
|
||||||
|
}, [alumnos]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title={`👥 Alumnos Inscritos — ${programa}`} onClose={onClose} width={1050}>
|
||||||
|
{loading ? <Loader text="Cargando alumnos..." /> :
|
||||||
|
alumnos.length === 0 ? <div style={{color:"#94a3b8",padding:20,textAlign:"center"}}>Sin alumnos.</div> :
|
||||||
|
<>
|
||||||
|
<div style={{display:"flex",gap:10,marginBottom:14,flexWrap:"wrap"}}>
|
||||||
|
<span style={{display:"inline-flex",alignItems:"center",gap:6,padding:"6px 14px",
|
||||||
|
borderRadius:999,background:"#eff6ff",color:"#1e40af",fontSize:13,fontWeight:700,
|
||||||
|
border:"1px solid #bfdbfe"}}>
|
||||||
|
👥 Total: {alumnos.length} alumno{alumnos.length!==1?"s":""}
|
||||||
|
</span>
|
||||||
|
{["ALU","PRE","RET"].map((e)=> conteo[e] ? (
|
||||||
|
<span key={e} style={{display:"inline-flex",alignItems:"center",gap:6,padding:"6px 14px",
|
||||||
|
borderRadius:999,background:colorEstado(e).bg,color:colorEstado(e).fg,fontSize:13,fontWeight:700,
|
||||||
|
border:`1px solid ${colorEstado(e).bd}`}}>
|
||||||
|
{e}: {conteo[e]}
|
||||||
|
</span>
|
||||||
|
) : null)}
|
||||||
|
</div>
|
||||||
|
<div className="table-wrap" style={{overflowX:"auto"}}>
|
||||||
|
<table className="cob-table" style={{minWidth:900}}>
|
||||||
|
<thead><tr>
|
||||||
|
<th style={{textAlign:"left"}}>ALUMNO</th>
|
||||||
|
<th>ESTADO</th>
|
||||||
|
<th style={{textAlign:"left"}}>CURSO ANTERIOR (TEAC/TERC)</th>
|
||||||
|
<th>ESTADO ANTERIOR</th>
|
||||||
|
</tr></thead>
|
||||||
|
<tbody>
|
||||||
|
{alumnos.map((a, i) => {
|
||||||
|
const c = colorEstado(a.estado);
|
||||||
|
const tieneAnt = a.curso_anterior && a.curso_anterior !== "-";
|
||||||
|
const cAnt = colorEstado(a.estado_anterior);
|
||||||
|
return (
|
||||||
|
<tr key={i}>
|
||||||
|
<td className="col-name" style={{textAlign:"left"}}>{a.alumno}</td>
|
||||||
|
<td style={{textAlign:"center"}}>
|
||||||
|
<span style={{display:"inline-block",padding:"2px 10px",borderRadius:999,
|
||||||
|
background:c.bg,color:c.fg,fontWeight:700,fontSize:12,border:`1px solid ${c.bd}`}}>
|
||||||
|
{a.estado}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="col-name" style={{textAlign:"left",color:tieneAnt?undefined:"#cbd5e1"}}>
|
||||||
|
{a.curso_anterior || "-"}
|
||||||
|
</td>
|
||||||
|
<td style={{textAlign:"center"}}>
|
||||||
|
{tieneAnt ? (
|
||||||
|
<span style={{display:"inline-block",padding:"2px 10px",borderRadius:999,
|
||||||
|
background:cAnt.bg,color:cAnt.fg,fontWeight:700,fontSize:12,border:`1px solid ${cAnt.bd}`}}>
|
||||||
|
{a.estado_anterior}
|
||||||
|
</span>
|
||||||
|
) : <span style={{color:"#cbd5e1"}}>-</span>}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user