feat: enhance CursosVista with competencias management, including loading, editing, and removing competencies
This commit is contained in:
parent
30dffd85e1
commit
dd101da359
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue