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 [competenciasGuardadas, setCompetenciasGuardadas] = useState([]);
|
||||||
const [mostrarModal, setMostrarModal] = useState(false);
|
const [mostrarModal, setMostrarModal] = useState(false);
|
||||||
const [modalMensaje, setModalMensaje] = useState("");
|
const [modalMensaje, setModalMensaje] = useState("");
|
||||||
|
const [todasCompetencias, setTodasCompetencias] = useState([]);
|
||||||
|
|
||||||
// Estado para confirmación de eliminación
|
// Para eliminar curso
|
||||||
const [confirmarEliminar, setConfirmarEliminar] = useState(false);
|
const [confirmarEliminar, setConfirmarEliminar] = useState(false);
|
||||||
const [cursoAEliminar, setCursoAEliminar] = useState(null);
|
const [cursoAEliminar, setCursoAEliminar] = useState(null);
|
||||||
|
|
||||||
|
// Para eliminar competencia
|
||||||
|
const [dialogQuitarComp, setDialogQuitarComp] = useState(false);
|
||||||
|
const [compAEliminar, setCompAEliminar] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cargarCursos();
|
cargarCursos();
|
||||||
|
cargarTodasCompetencias();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const cargarCursos = async () => {
|
const cargarCursos = async () => {
|
||||||
|
@ -41,7 +47,7 @@ export default function CursosVista() {
|
||||||
curso_competencia (
|
curso_competencia (
|
||||||
competencia (
|
competencia (
|
||||||
id,
|
id,
|
||||||
nombre
|
descripcion
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
`)
|
`)
|
||||||
|
@ -49,7 +55,6 @@ export default function CursosVista() {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error al cargar cursos:", error.message);
|
console.error("Error al cargar cursos:", error.message);
|
||||||
} else {
|
} else {
|
||||||
// Transformar los datos para que las competencias estén directamente en el objeto curso
|
|
||||||
const cursosConCompetencias = data.map((curso) => ({
|
const cursosConCompetencias = data.map((curso) => ({
|
||||||
...curso,
|
...curso,
|
||||||
competencias: curso.curso_competencia.map((cc) => cc.competencia),
|
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) => {
|
const iniciarEdicion = (curso) => {
|
||||||
setCursoEditando(curso.id);
|
setCursoEditando(curso.id);
|
||||||
setNuevoNombre(curso.nombre);
|
setNuevoNombre(curso.nombre);
|
||||||
|
@ -74,19 +87,47 @@ export default function CursosVista() {
|
||||||
setCompetenciasGuardadas([]);
|
setCompetenciasGuardadas([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Guardar cambios en curso y competencias
|
||||||
const guardarEdicion = async (id) => {
|
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")
|
.from("curso")
|
||||||
.update({
|
.update({
|
||||||
nombre: nuevoNombre,
|
nombre: nuevoNombre,
|
||||||
descripcion: nuevaDescripcion,
|
descripcion: nuevaDescripcion,
|
||||||
horas: nuevaHoras,
|
horas: nuevaHoras,
|
||||||
//competencias: competenciasGuardadas,
|
|
||||||
})
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
// Actualiza competencias (tabla pivote)
|
||||||
console.error("Error actualizando curso:", error.message);
|
// 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");
|
setModalMensaje("Error al actualizar el curso");
|
||||||
} else {
|
} else {
|
||||||
setModalMensaje("Curso actualizado exitosamente");
|
setModalMensaje("Curso actualizado exitosamente");
|
||||||
|
@ -107,7 +148,6 @@ export default function CursosVista() {
|
||||||
.delete()
|
.delete()
|
||||||
.eq("id", cursoAEliminar);
|
.eq("id", cursoAEliminar);
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error eliminando curso:", error.message);
|
|
||||||
setModalMensaje("Error al eliminar el curso");
|
setModalMensaje("Error al eliminar el curso");
|
||||||
} else {
|
} else {
|
||||||
setModalMensaje("Curso eliminado exitosamente");
|
setModalMensaje("Curso eliminado exitosamente");
|
||||||
|
@ -117,6 +157,18 @@ export default function CursosVista() {
|
||||||
setMostrarModal(true);
|
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 (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="w-[80vw] pt-10 flex flex-col items-center text-black">
|
<div className="w-[80vw] pt-10 flex flex-col items-center text-black">
|
||||||
|
@ -157,14 +209,53 @@ export default function CursosVista() {
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 px-4 border-b">
|
<td className="py-2 px-4 border-b">
|
||||||
<Input
|
<div className="flex flex-col gap-2">
|
||||||
type="text"
|
{competenciasGuardadas.map((comp, idx) => (
|
||||||
value={competenciasGuardadas}
|
<div key={idx} className="flex items-center gap-2">
|
||||||
onChange={(e) =>
|
<select
|
||||||
setCompetenciasGuardadas(e.target.value.split(", "))
|
className="border rounded px-2 py-1"
|
||||||
}
|
value={comp?.id || ""}
|
||||||
placeholder="Competencias (separadas por comas)"
|
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>
|
||||||
<td className="py-2 px-4 border-b flex justify-center">
|
<td className="py-2 px-4 border-b flex justify-center">
|
||||||
<Button
|
<Button
|
||||||
|
@ -183,12 +274,19 @@ export default function CursosVista() {
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
<tr key={curso.id}>
|
<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.nombre}</td>
|
||||||
<td className="py-2 px-4 border-b">{curso.descripcion}</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">{curso.horas}</td>
|
||||||
<td className="py-2 px-4 border-b">
|
<td className="py-2 px-4 border-b">
|
||||||
{Array.isArray(curso.competencias) && curso.competencias.length > 0
|
{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"}
|
: "Sin competencias"}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 px-4 border-b space-x-2">
|
<td className="py-2 px-4 border-b space-x-2">
|
||||||
|
@ -212,7 +310,7 @@ export default function CursosVista() {
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal de confirmación */}
|
{/* Dialog para eliminar curso */}
|
||||||
<Dialog open={confirmarEliminar} onOpenChange={setConfirmarEliminar}>
|
<Dialog open={confirmarEliminar} onOpenChange={setConfirmarEliminar}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
@ -241,6 +339,34 @@ export default function CursosVista() {
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</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 */}
|
{/* Modal de resultado */}
|
||||||
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
|
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
@ -257,4 +383,4 @@ export default function CursosVista() {
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
Reference in New Issue