feat: enhance CursosVista with competencias management, including loading, editing, and removing competencies

This commit is contained in:
BenitoBB 2025-05-14 08:14:52 -06:00
parent 30dffd85e1
commit dd101da359
1 changed files with 145 additions and 19 deletions

View File

@ -21,13 +21,19 @@ export default function CursosVista() {
const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]);
const [mostrarModal, setMostrarModal] = useState(false);
const [modalMensaje, setModalMensaje] = useState("");
const [todasCompetencias, setTodasCompetencias] = useState([]);
// Estado para confirmación de eliminación
// Para eliminar curso
const [confirmarEliminar, setConfirmarEliminar] = useState(false);
const [cursoAEliminar, setCursoAEliminar] = useState(null);
// Para eliminar competencia
const [dialogQuitarComp, setDialogQuitarComp] = useState(false);
const [compAEliminar, setCompAEliminar] = useState(null);
useEffect(() => {
cargarCursos();
cargarTodasCompetencias();
}, []);
const cargarCursos = async () => {
@ -41,7 +47,7 @@ export default function CursosVista() {
curso_competencia (
competencia (
id,
nombre
descripcion
)
)
`)
@ -49,7 +55,6 @@ export default function CursosVista() {
if (error) {
console.error("Error al cargar cursos:", error.message);
} else {
// Transformar los datos para que las competencias estén directamente en el objeto curso
const cursosConCompetencias = data.map((curso) => ({
...curso,
competencias: curso.curso_competencia.map((cc) => cc.competencia),
@ -58,6 +63,14 @@ export default function CursosVista() {
}
};
const cargarTodasCompetencias = async () => {
const { data, error } = await supabaseClient
.from("competencia")
.select("id, descripcion")
.order("id", { ascending: true });
if (!error) setTodasCompetencias(data);
};
const iniciarEdicion = (curso) => {
setCursoEditando(curso.id);
setNuevoNombre(curso.nombre);
@ -74,19 +87,47 @@ export default function CursosVista() {
setCompetenciasGuardadas([]);
};
// Guardar cambios en curso y competencias
const guardarEdicion = async (id) => {
const { error } = await supabaseClient
// Validar que no haya competencias repetidas
const ids = competenciasGuardadas.map(c => c?.id).filter(Boolean);
const setIds = new Set(ids);
if (ids.length !== setIds.size) {
setModalMensaje("No puedes repetir competencias en un curso.");
setMostrarModal(true);
return;
}
// Actualiza datos del curso
const { error: errorCurso } = await supabaseClient
.from("curso")
.update({
nombre: nuevoNombre,
descripcion: nuevaDescripcion,
horas: nuevaHoras,
//competencias: competenciasGuardadas,
})
.eq("id", id);
if (error) {
console.error("Error actualizando curso:", error.message);
// Actualiza competencias (tabla pivote)
// 1. Elimina todas las competencias actuales del curso
await supabaseClient
.from("curso_competencia")
.delete()
.eq("curso_id", id);
// 2. Inserta las nuevas competencias seleccionadas
const competenciasAInsertar = competenciasGuardadas
.filter(c => c && c.id)
.map(c => ({
curso_id: id,
competencia_id: c.id,
}));
if (competenciasAInsertar.length > 0) {
await supabaseClient
.from("curso_competencia")
.insert(competenciasAInsertar);
}
if (errorCurso) {
setModalMensaje("Error al actualizar el curso");
} else {
setModalMensaje("Curso actualizado exitosamente");
@ -107,7 +148,6 @@ export default function CursosVista() {
.delete()
.eq("id", cursoAEliminar);
if (error) {
console.error("Error eliminando curso:", error.message);
setModalMensaje("Error al eliminar el curso");
} else {
setModalMensaje("Curso eliminado exitosamente");
@ -117,6 +157,18 @@ export default function CursosVista() {
setMostrarModal(true);
};
// Dialog para quitar competencia
const pedirConfirmacionQuitarComp = (idx) => {
setCompAEliminar(idx);
setDialogQuitarComp(true);
};
const quitarCompetencia = () => {
setCompetenciasGuardadas(competenciasGuardadas.filter((_, i) => i !== compAEliminar));
setDialogQuitarComp(false);
setCompAEliminar(null);
};
return (
<Layout>
<div className="w-[80vw] pt-10 flex flex-col items-center text-black">
@ -157,14 +209,53 @@ export default function CursosVista() {
/>
</td>
<td className="py-2 px-4 border-b">
<Input
type="text"
value={competenciasGuardadas}
onChange={(e) =>
setCompetenciasGuardadas(e.target.value.split(", "))
}
placeholder="Competencias (separadas por comas)"
/>
<div className="flex flex-col gap-2">
{competenciasGuardadas.map((comp, idx) => (
<div key={idx} className="flex items-center gap-2">
<select
className="border rounded px-2 py-1"
value={comp?.id || ""}
onChange={e => {
const nuevaLista = [...competenciasGuardadas];
const nuevaComp = todasCompetencias.find(c => c.id === Number(e.target.value));
nuevaLista[idx] = nuevaComp;
setCompetenciasGuardadas(nuevaLista);
}}
>
<option value="">Selecciona competencia</option>
{todasCompetencias.map(tc => (
<option
key={tc.id}
value={tc.id}
disabled={
// Deshabilita si ya está seleccionada en otro select
competenciasGuardadas.some(
(c, i) => c && c.id === tc.id && i !== idx
)
}
>
{tc.descripcion}
</option>
))}
</select>
<Button
type="button"
className="bg-red-500 hover:bg-red-700 text-white px-2 py-1 rounded"
onClick={() => pedirConfirmacionQuitarComp(idx)}
>
Quitar
</Button>
</div>
))}
<Button
type="button"
className="bg-blue-500 hover:bg-blue-700 text-white px-2 py-1 rounded mt-2"
onClick={() => setCompetenciasGuardadas([...competenciasGuardadas, null])}
disabled={competenciasGuardadas.length >= todasCompetencias.length}
>
Agregar competencia
</Button>
</div>
</td>
<td className="py-2 px-4 border-b flex justify-center">
<Button
@ -183,12 +274,19 @@ export default function CursosVista() {
</tr>
) : (
<tr key={curso.id}>
<td className="py-2 px-4 border-b">{curso.id}</td>
<td className="py-2 px-4 border-b">{curso.nombre}</td>
<td className="py-2 px-4 border-b">{curso.descripcion}</td>
<td className="py-2 px-4 border-b">{curso.horas}</td>
<td className="py-2 px-4 border-b">
{Array.isArray(curso.competencias) && curso.competencias.length > 0
? curso.competencias.map((comp) => comp.nombre).join(", ")
? (
<ul className="list-disc pl-4">
{curso.competencias.map((comp) => (
<li key={comp.id}>{comp.descripcion}</li>
))}
</ul>
)
: "Sin competencias"}
</td>
<td className="py-2 px-4 border-b space-x-2">
@ -212,7 +310,7 @@ export default function CursosVista() {
</table>
</div>
{/* Modal de confirmación */}
{/* Dialog para eliminar curso */}
<Dialog open={confirmarEliminar} onOpenChange={setConfirmarEliminar}>
<DialogContent>
<DialogHeader>
@ -241,6 +339,34 @@ export default function CursosVista() {
</DialogContent>
</Dialog>
{/* Dialog para eliminar competencia */}
<Dialog open={dialogQuitarComp} onOpenChange={setDialogQuitarComp}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">
Quitar competencia
</DialogTitle>
<DialogDescription>
¿Estás seguro de que deseas quitar esta competencia del curso?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
className="bg-red-500 hover:bg-red-700 text-white"
onClick={quitarCompetencia}
>
Quitar
</Button>
<Button
className="bg-gray-400 hover:bg-gray-600 text-white"
onClick={() => setDialogQuitarComp(false)}
>
Cancelar
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Modal de resultado */}
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
<DialogContent>
@ -257,4 +383,4 @@ export default function CursosVista() {
</Dialog>
</Layout>
);
}
}