Refactor curso and diploma management pages for improved readability and functionality

- Updated `cursosArchivo.jsx` to enhance dialog imports and improve code formatting.
- Refactored `cursosManual.jsx` to streamline competency management and improve user feedback through dialogs.
- Enhanced `cursosVista.jsx` for better table layout and added functionality for managing course competencies.
- Improved `diplomasVista.jsx` layout for better responsiveness and removed unused dialog component.
This commit is contained in:
SirRobert-1 2025-05-23 15:33:53 -06:00
parent 65893bf052
commit 985af4f2fd
9 changed files with 617 additions and 706 deletions

View File

@ -1,7 +1,26 @@
"use client"; "use client";
import { AppSidebar } from "@/components/app-sidebar"; import { AppSidebar } from "@/components/app-sidebar";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
useSidebar,
} from "@/components/ui/sidebar";
function MainContent({ children }) {
const { open } = useSidebar();
return (
<div
className={`p-4 min-h-screen flex justify-center transition-all duration-200 ${
open ? "w-screen md:w-[80vw]" : "w-screen"
}`}
>
{children}
</div>
);
}
export default function Layout({ children }) { export default function Layout({ children }) {
return ( return (
@ -9,7 +28,8 @@ export default function Layout({ children }) {
<div className="flex"> <div className="flex">
<AppSidebar /> <AppSidebar />
<SidebarInset> <SidebarInset>
<div className="p-4 w-full">{children}</div> <SidebarTrigger className="-ml-1 text-black" />
<MainContent>{children}</MainContent>
</SidebarInset> </SidebarInset>
</div> </div>
</SidebarProvider> </SidebarProvider>

View File

@ -238,7 +238,7 @@ function SidebarTrigger({ className, onClick, ...props }) {
data-slot="sidebar-trigger" data-slot="sidebar-trigger"
variant="ghost" variant="ghost"
size="icon" size="icon"
className={cn("size-7", className)} className={cn("size-10", className)}
onClick={(event) => { onClick={(event) => {
onClick?.(event); onClick?.(event);
toggleSidebar(); toggleSidebar();

View File

@ -3,7 +3,15 @@ import Papa from "papaparse";
import * as XLSX from "xlsx"; import * as XLSX from "xlsx";
import Layout from "@/components/layout/Layout"; import Layout from "@/components/layout/Layout";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { CursosManualForm } from "./cursosManual"; // Importa el formulario sin Layout import { CursosManualForm } from "./cursosManual"; // Importa el formulario sin Layout
import { supabaseClient } from "@/utils/supabase"; import { supabaseClient } from "@/utils/supabase";
@ -26,11 +34,12 @@ export default function AlumnosArchivo() {
for (const alumno of datos) { for (const alumno of datos) {
// 1. Verifica si el curso existe // 1. Verifica si el curso existe
const { data: cursosEncontrados, error: errorCurso } = await supabaseClient const { data: cursosEncontrados, error: errorCurso } =
.from("curso") await supabaseClient
.select("id") .from("curso")
.eq("nombre", alumno.nombreCurso) .select("id")
.maybeSingle(); .eq("nombre", alumno.nombreCurso)
.maybeSingle();
if (errorCurso) { if (errorCurso) {
errores.push({ alumno, error: "Error al buscar el curso" }); errores.push({ alumno, error: "Error al buscar el curso" });
@ -41,7 +50,9 @@ export default function AlumnosArchivo() {
// Si no existe el curso, muestra el dialog para registrar el curso // Si no existe el curso, muestra el dialog para registrar el curso
setCursoFaltante(alumno.nombreCurso); setCursoFaltante(alumno.nombreCurso);
setMostrarDialogCurso(true); setMostrarDialogCurso(true);
setMensajeDialogo(`El curso "${alumno.nombreCurso}" no existe. Por favor, regístralo primero.`); setMensajeDialogo(
`El curso "${alumno.nombreCurso}" no existe. Por favor, regístralo primero.`
);
setDialogoAbierto(true); setDialogoAbierto(true);
return; // Detiene el registro de alumnos return; // Detiene el registro de alumnos
} }
@ -65,7 +76,9 @@ export default function AlumnosArchivo() {
} }
if (errores.length > 0) { if (errores.length > 0) {
setMensajeDialogo(`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`); setMensajeDialogo(
`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`
);
} else { } else {
setMensajeDialogo("Todos los alumnos fueron registrados correctamente."); setMensajeDialogo("Todos los alumnos fueron registrados correctamente.");
setArchivo(null); setArchivo(null);
@ -130,8 +143,8 @@ export default function AlumnosArchivo() {
return ( return (
<Layout> <Layout>
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center text-black"> <div className="w-full pt-10 flex flex-col items-start md:items-center md:justify-center text-black">
<div className="bg-white p-8 font-sans text-center w-[70%] flex flex-col items-center"> <div className="bg-white font-sans text-center w-full flex flex-col items-center">
<h1 className="text-xl font-semibold mb-4 text-black"> <h1 className="text-xl font-semibold mb-4 text-black">
Nuevo alumno Nuevo alumno
</h1> </h1>
@ -144,7 +157,9 @@ export default function AlumnosArchivo() {
{archivo ? ( {archivo ? (
<span className="text-black font-medium">{archivo.name}</span> <span className="text-black font-medium">{archivo.name}</span>
) : ( ) : (
<span>Arrastra y suelta un archivo o haz clic para seleccionarlo</span> <span>
Arrastra y suelta un archivo o haz clic para seleccionarlo
</span>
)} )}
<input <input
type="file" type="file"
@ -169,7 +184,9 @@ export default function AlumnosArchivo() {
<thead className="bg-gray-100 text-gray-700"> <thead className="bg-gray-100 text-gray-700">
<tr> <tr>
{Object.keys(datos[0]).map((columna, index) => ( {Object.keys(datos[0]).map((columna, index) => (
<th key={index} className="border px-4 py-2">{columna}</th> <th key={index} className="border px-4 py-2">
{columna}
</th>
))} ))}
</tr> </tr>
</thead> </thead>
@ -177,7 +194,9 @@ export default function AlumnosArchivo() {
{datos.map((fila, index) => ( {datos.map((fila, index) => (
<tr key={index}> <tr key={index}>
{Object.values(fila).map((valor, i) => ( {Object.values(fila).map((valor, i) => (
<td key={i} className="border px-4 py-1">{valor}</td> <td key={i} className="border px-4 py-1">
{valor}
</td>
))} ))}
</tr> </tr>
))} ))}
@ -196,7 +215,8 @@ export default function AlumnosArchivo() {
Registrar curso faltante Registrar curso faltante
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
El curso <b>{cursoFaltante}</b> no existe. Por favor, regístralo antes de continuar. El curso <b>{cursoFaltante}</b> no existe. Por favor, regístralo
antes de continuar.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<CursosManualForm nombreSugerido={cursoFaltante} /> <CursosManualForm nombreSugerido={cursoFaltante} />
@ -217,4 +237,4 @@ export default function AlumnosArchivo() {
</Dialog> </Dialog>
</Layout> </Layout>
); );
} }

View File

@ -84,80 +84,66 @@ export default function AlumnosManual() {
return ( return (
<Layout> <Layout>
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center text-black"> <div className="w-full bg-white font-sans text-center md:w-[80%] pt-10 flex flex-col items-center justify-start text-black">
<div className="bg-white p-8 font-sans text-center w-[70%]"> <h1 className="text-xl font-semibold mb-10 text-black my-10">
<h1 className="text-xl font-semibold mb-4 text-black"> Nuevo alumno
Nuevo alumno </h1>
</h1> <form onSubmit={handleSubmit(manejarGuardar)} className="w-full">
<form onSubmit={handleSubmit(manejarGuardar)}> <Input
<div className="mb-3"> type="text"
<Input placeholder="Nombre"
type="text" {...register("nombre")}
placeholder="Nombre" className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3"
{...register("nombre")} />
className="w-full px-3 py-2 border border-gray-300 rounded-md" {errors.nombre && (
/> <p className="text-red-500 text-sm mt-1">{errors.nombre.message}</p>
{errors.nombre && ( )}
<p className="text-red-500 text-sm mt-1"> <Input
{errors.nombre.message} type="text"
</p> placeholder="Email"
)} {...register("correo")}
</div> className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3"
<div className="mb-3"> />
<Input {errors.correo && (
type="text" <p className="text-red-500 text-sm mt-1">{errors.correo.message}</p>
placeholder="Email" )}
{...register("correo")} <Input
className="w-full px-3 py-2 border border-gray-300 rounded-md" type="text"
/> placeholder="Teléfono"
{errors.correo && ( {...register("telefono")}
<p className="text-red-500 text-sm mt-1"> className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3"
{errors.correo.message} />
</p> {errors.telefono && (
)} <p className="text-red-500 text-sm mt-1">
</div> {errors.telefono.message}
<div className="mb-3"> </p>
<Input )}
type="text" <Select
placeholder="Teléfono" onValueChange={(value) => setValue("cursoSeleccionado", value)}
{...register("telefono")} >
className="w-full px-3 py-2 border border-gray-300 rounded-md" <SelectTrigger className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3">
/> <SelectValue placeholder="Selecciona un curso" />
{errors.telefono && ( </SelectTrigger>
<p className="text-red-500 text-sm mt-1"> <SelectContent>
{errors.telefono.message} {cursos.map((curso) => (
</p> <SelectItem key={curso.id} value={curso.id.toString()}>
)} {curso.nombre}
</div> </SelectItem>
<div className="mb-4"> ))}
<Select </SelectContent>
onValueChange={(value) => setValue("cursoSeleccionado", value)} </Select>
> {errors.cursoSeleccionado && (
<SelectTrigger className="w-full px-3 py-2 border border-gray-300 rounded-md"> <p className="text-red-500 text-sm mt-1">
<SelectValue placeholder="Selecciona un curso" /> {errors.cursoSeleccionado.message}
</SelectTrigger> </p>
<SelectContent> )}
{cursos.map((curso) => ( <Button
<SelectItem key={curso.id} value={curso.id.toString()}> type="submit"
{curso.nombre} className="bg-green-400 hover:bg-green-500 font-bold py-2 px-4 rounded-md text-white"
</SelectItem> >
))} Registrar
</SelectContent> </Button>
</Select> </form>
{errors.cursoSeleccionado && (
<p className="text-red-500 text-sm mt-1">
{errors.cursoSeleccionado.message}
</p>
)}
</div>
<Button
type="submit"
className="bg-green-400 hover:bg-green-500 font-bold py-2 px-4 rounded-md text-black"
>
Registrar
</Button>
</form>
</div>
</div> </div>
{/* Diálogo de confirmación */} {/* Diálogo de confirmación */}

View File

@ -142,124 +142,126 @@ export default function AlumnosVista() {
return ( return (
<Layout> <Layout>
<div className="w-[80vw] pt-10 flex flex-col items-center text-black"> <div className="w-full pt-5 flex flex-col items-center text-black">
<h1 className="text-2xl font-semibold mb-6 text-black"> <h1 className="text-2xl font-semibold mt-5 mb-10 text-black">
Lista de Alumnos Lista de Alumnos
</h1> </h1>
<table className="min-w-full bg-white border"> <div className="overflow-x-auto w-full">
<thead> <table className="min-w-full bg-white border">
<tr className="bg-gray-100"> <thead>
<th className="py-2 border-b">ID</th> <tr className="bg-gray-100">
<th className="py-2 border-b">Nombre</th> <th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Correo</th> <th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Teléfono</th> <th className="py-2 border-b">Correo</th>
<th className="py-2 border-b">Curso</th> <th className="py-2 border-b">Teléfono</th>
<th className="py-2 border-b">Acciones</th> <th className="py-2 border-b">Curso</th>
</tr> <th className="py-2 border-b">Acciones</th>
</thead> </tr>
<tbody> </thead>
{alumnos.map((alumno) => <tbody>
alumnoEditando === alumno.id ? ( {alumnos.map((alumno) =>
<tr key={alumno.id}> alumnoEditando === alumno.id ? (
<td className="py-2 px-4 border-b text-center"> <tr key={alumno.id}>
{alumno.id} <td className="py-2 px-4 border-b text-center">
</td> {alumno.id}
<td className="py-2 px-4 border-b"> </td>
<Input type="text" {...register("nombre")} /> <td className="py-2 px-4 border-b">
{errors.nombre && ( <Input type="text" {...register("nombre")} />
<span className="text-red-500 text-xs"> {errors.nombre && (
{errors.nombre.message} <span className="text-red-500 text-xs">
</span> {errors.nombre.message}
)} </span>
</td> )}
<td className="py-2 px-4 border-b"> </td>
<Input type="email" {...register("correo")} /> <td className="py-2 px-4 border-b">
{errors.correo && ( <Input type="email" {...register("correo")} />
<span className="text-red-500 text-xs"> {errors.correo && (
{errors.correo.message} <span className="text-red-500 text-xs">
</span> {errors.correo.message}
)} </span>
</td> )}
<td className="py-2 px-4 border-b"> </td>
<Input type="text" {...register("telefono")} /> <td className="py-2 px-4 border-b">
{errors.telefono && ( <Input type="text" {...register("telefono")} />
<span className="text-red-500 text-xs"> {errors.telefono && (
{errors.telefono.message} <span className="text-red-500 text-xs">
</span> {errors.telefono.message}
)} </span>
</td> )}
<td className="py-2 px-4 border-b"> </td>
<Select <td className="py-2 px-4 border-b">
value={undefined} <Select
onValueChange={(value) => value={(alumno.curso_id || "").toString()}
setValue("cursoSeleccionado", value) onValueChange={(value) =>
} setValue("cursoSeleccionado", value)
{...register("cursoSeleccionado")} }
> {...register("cursoSeleccionado")}
<SelectTrigger> >
<SelectValue placeholder="Selecciona un curso" /> <SelectTrigger>
</SelectTrigger> <SelectValue placeholder="Selecciona un curso" />
<SelectContent> </SelectTrigger>
{cursos.map((curso) => ( <SelectContent>
<SelectItem {cursos.map((curso) => (
key={curso.id} <SelectItem
value={curso.id.toString()} key={curso.id}
> value={curso.id.toString()}
{curso.nombre} >
</SelectItem> {curso.nombre}
))} </SelectItem>
</SelectContent> ))}
</Select> </SelectContent>
{errors.cursoSeleccionado && ( </Select>
<span className="text-red-500 text-xs"> {errors.cursoSeleccionado && (
{errors.cursoSeleccionado.message} <span className="text-red-500 text-xs">
</span> {errors.cursoSeleccionado.message}
)} </span>
</td> )}
<td className="py-2 px-4 border-b flex justify-center"> </td>
<Button <td className="py-2 px-4 border-b flex justify-center">
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-2 rounded" <Button
onClick={handleSubmit(guardarEdicion)} className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-1 rounded"
> onClick={handleSubmit(guardarEdicion)}
Guardar >
</Button> Guardar
<Button </Button>
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-2 rounded" <Button
onClick={cancelarEdicion} className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-1 rounded"
> onClick={cancelarEdicion}
Cancelar >
</Button> Cancelar
</td> </Button>
</tr> </td>
) : ( </tr>
<tr key={alumno.id}> ) : (
<td className="py-2 px-4 border-b">{alumno.id}</td> <tr key={alumno.id}>
<td className="py-2 px-4 border-b">{alumno.nombre}</td> <td className="py-2 px-4 border-b">{alumno.id}</td>
<td className="py-2 px-4 border-b">{alumno.correo}</td> <td className="py-2 px-4 border-b">{alumno.nombre}</td>
<td className="py-2 px-4 border-b">{alumno.telefono}</td> <td className="py-2 px-4 border-b">{alumno.correo}</td>
<td className="py-2 px-4 border-b"> <td className="py-2 px-4 border-b">{alumno.telefono}</td>
{alumno.curso?.nombre || "Sin curso"} <td className="py-2 px-4 border-b">
</td> {alumno.curso?.nombre || "Sin curso"}
<td className="py-2 px-4 border-b space-x-2"> </td>
<Button <td className="py-2 px-4 border-b flex justify-center">
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded" <Button
onClick={() => iniciarEdicion(alumno)} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 m-1 rounded"
> onClick={() => iniciarEdicion(alumno)}
Editar >
</Button> Editar
</Button>
<Button <Button
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded" className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 m-1 rounded"
onClick={() => confirmarEliminacion(alumno.id)} onClick={() => confirmarEliminacion(alumno.id)}
> >
Eliminar Eliminar
</Button> </Button>
</td> </td>
</tr> </tr>
) )
)} )}
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
{/* Modal de confirmación */} {/* Modal de confirmación */}

View File

@ -3,7 +3,15 @@ import Papa from "papaparse";
import * as XLSX from "xlsx"; import * as XLSX from "xlsx";
import Layout from "@/components/layout/Layout"; import Layout from "@/components/layout/Layout";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog"; import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
export default function cursosArchivo() { export default function cursosArchivo() {
@ -53,7 +61,7 @@ export default function cursosArchivo() {
horas: curso.horas, horas: curso.horas,
descripcion: curso.descripcion, descripcion: curso.descripcion,
competencias: curso.competencias competencias: curso.competencias
? curso.competencias.split(",").map(c => c.trim()) ? curso.competencias.split(",").map((c) => c.trim())
: [], : [],
}), }),
}); });
@ -67,7 +75,9 @@ export default function cursosArchivo() {
setDialogoCargando(false); // Ocultar dialogo de carga setDialogoCargando(false); // Ocultar dialogo de carga
if (errores.length > 0) { if (errores.length > 0) {
setMensajeDialogo(`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`); setMensajeDialogo(
`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`
);
} else { } else {
setMensajeDialogo("Todos los cursos fueron registrados correctamente."); setMensajeDialogo("Todos los cursos fueron registrados correctamente.");
setArchivo(null); setArchivo(null);
@ -132,11 +142,9 @@ export default function cursosArchivo() {
return ( return (
<Layout> <Layout>
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center text-black"> <div className="w-full pt-10 flex flex-col items-start md:items-center md:justify-center text-black">
<div className="bg-white p-8 font-sans text-center w-[70%] flex flex-col items-center"> <div className="bg-white font-sans text-center w-full flex flex-col items-center">
<h1 className="text-xl font-semibold mb-4 text-black"> <h1 className="text-xl font-semibold mb-4 text-black">Nuevo curso</h1>
Nuevo curso
</h1>
<label <label
htmlFor="archivo" htmlFor="archivo"
onDrop={manejarSoltar} onDrop={manejarSoltar}
@ -146,7 +154,9 @@ export default function cursosArchivo() {
{archivo ? ( {archivo ? (
<span className="text-black font-medium">{archivo.name}</span> <span className="text-black font-medium">{archivo.name}</span>
) : ( ) : (
<span>Arrastra y suelta un archivo o haz clic para seleccionarlo</span> <span>
Arrastra y suelta un archivo o haz clic para seleccionarlo
</span>
)} )}
<input <input
type="file" type="file"
@ -171,7 +181,9 @@ export default function cursosArchivo() {
<thead className="bg-gray-100 text-gray-700"> <thead className="bg-gray-100 text-gray-700">
<tr> <tr>
{Object.keys(datos[0]).map((columna, index) => ( {Object.keys(datos[0]).map((columna, index) => (
<th key={index} className="border px-4 py-2">{columna}</th> <th key={index} className="border px-4 py-2">
{columna}
</th>
))} ))}
</tr> </tr>
</thead> </thead>
@ -179,7 +191,9 @@ export default function cursosArchivo() {
{datos.map((fila, index) => ( {datos.map((fila, index) => (
<tr key={index}> <tr key={index}>
{Object.values(fila).map((valor, i) => ( {Object.values(fila).map((valor, i) => (
<td key={i} className="border px-4 py-1">{valor}</td> <td key={i} className="border px-4 py-1">
{valor}
</td>
))} ))}
</tr> </tr>
))} ))}
@ -216,7 +230,8 @@ export default function cursosArchivo() {
<DialogHeader> <DialogHeader>
<DialogTitle className="text-black">Advertencia</DialogTitle> <DialogTitle className="text-black">Advertencia</DialogTitle>
<DialogDescription> <DialogDescription>
Si cambias de ventana perderás la subida del archivo. ¿Deseas continuar? Si cambias de ventana perderás la subida del archivo. ¿Deseas
continuar?
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter> <DialogFooter>
@ -242,4 +257,4 @@ export default function cursosArchivo() {
</Dialog> </Dialog>
</Layout> </Layout>
); );
} }

View File

@ -6,7 +6,7 @@ import Layout from "@/components/layout/Layout";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { supabaseClient } from "@/utils/supabase"; // Importar el cliente de Supabase import { supabaseClient } from "@/utils/supabase";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -17,11 +17,11 @@ import {
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
export default function CursosManual() { export default function CursosManual() {
const [addCompetencia, setAddCompetencia] = useState(false); const [competencias, setCompetencias] = useState([]); // [{id, descripcion}]
const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]); const [showDialog, setShowDialog] = useState(false);
const [dialogMsg, setDialogMsg] = useState("");
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [mostrarDialog, setMostrarDialog] = useState(false); const [addCompetencia, setAddCompetencia] = useState(false);
const [mensajeDialog, setMensajeDialog] = useState("");
const form = useForm({ const form = useForm({
resolver: zodResolver(cursosSchema), resolver: zodResolver(cursosSchema),
@ -32,7 +32,6 @@ export default function CursosManual() {
nuevaCompetencia: "", nuevaCompetencia: "",
}, },
}); });
const { const {
register, register,
handleSubmit, handleSubmit,
@ -41,348 +40,211 @@ export default function CursosManual() {
formState: { errors }, formState: { errors },
} = form; } = form;
const handleAddCompetencia = () => { // Añadir competencia (busca o crea en BD)
setAddCompetencia(true);
};
const handleCancel = () => {
setAddCompetencia(false);
setValue("nuevaCompetencia", "");
};
const handleSaveCompetencia = (e) => {
e.preventDefault();
const nuevaCompetencia = getValues("nuevaCompetencia");
if (nuevaCompetencia.trim() !== "") {
setCompetenciasGuardadas([
...competenciasGuardadas,
nuevaCompetencia.trim(),
]);
handleCancel();
}
};
const handleDeleteCompetencia = (index) => {
setCompetenciasGuardadas(
competenciasGuardadas.filter((_, i) => i !== index)
);
};
const onSubmit = async (data) => {
const { nombre, descripcion } = data;
const horas = parseInt(data.horas, 10); // Convertir horas a número
const competencias = competenciasGuardadas;
setLoading(true); // Mostrar estado de carga
try {
const { error } = await supabaseClient.from("curso").insert([
{
nombre,
descripcion,
horas,
competencias, // Guardar competencias como array
},
]);
if (error) {
console.error("Error al guardar en Supabase:", error.message);
alert("Error al guardar el curso: " + error.message);
} else {
setMensajeDialog("Curso guardado exitosamente");
setMostrarDialog(true);
form.reset(); // Reiniciar el formulario
setCompetenciasGuardadas([]); // Limpiar competencias guardadas
}
} catch (err) {
console.error("Error inesperado:", err);
alert("Ocurrió un error inesperado");
} finally {
setLoading(false); // Ocultar estado de carga
}
};
return (
<Layout>
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center">
<div className="bg-white p-8 font-sans text-center w-[70%] flex flex-col items-center">
<h1 className="text-xl font-semibold mb-4 text-black">Nuevo curso</h1>
<CursosManualForm nombreSugerido="" />
</div>
</div>
</Layout>
);
}
export function CursosManualForm({ nombreSugerido = "" }) {
const [addCompetencia, setAddCompetencia] = useState(false);
const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]); // [{id, descripcion}]
const [loading, setLoading] = useState(false);
const [mostrarDialog, setMostrarDialog] = useState(false);
const [mensajeDialog, setMensajeDialog] = useState("");
// Estado para dialog de competencia agregada
const [mostrarDialogCompetencia, setMostrarDialogCompetencia] = useState(false);
const form = useForm({
resolver: zodResolver(cursosSchema),
defaultValues: {
nombre: nombreSugerido,
descripcion: "",
horas: 0,
nuevaCompetencia: "",
},
});
const {
register,
handleSubmit,
setValue,
getValues,
formState: { errors },
} = form;
// Cambia handleSaveCompetencia para mostrar el dialog
const handleSaveCompetencia = async (e) => { const handleSaveCompetencia = async (e) => {
e.preventDefault(); e.preventDefault();
const nuevaCompetencia = getValues("nuevaCompetencia").trim(); const desc = getValues("nuevaCompetencia").trim();
if (!nuevaCompetencia) return; if (!desc) return;
if (competencias.some((c) => c.descripcion === desc)) {
// Verifica si ya existe en el estado setDialogMsg("La competencia ya fue agregada.");
if (competenciasGuardadas.some((c) => c.descripcion === nuevaCompetencia)) { setShowDialog(true);
alert("La competencia ya fue agregada.");
return; return;
} }
// Verifica si ya existe en la base de datos
let competenciaId = null;
try { try {
// Busca si ya existe let { data: existente } = await supabaseClient
const { data: existente } = await supabaseClient
.from("competencia") .from("competencia")
.select("id") .select("id")
.eq("descripcion", nuevaCompetencia) .eq("descripcion", desc)
.maybeSingle(); .maybeSingle();
let id = existente?.id;
if (existente && existente.id) { if (!id) {
competenciaId = existente.id;
} else {
// Si no existe, la crea
const { data: insertada, error } = await supabaseClient const { data: insertada, error } = await supabaseClient
.from("competencia") .from("competencia")
.insert([{ descripcion: nuevaCompetencia }]) .insert([{ descripcion: desc }])
.select("id") .select("id")
.single(); .single();
if (error) throw error; if (error) throw error;
competenciaId = insertada.id; id = insertada.id;
} }
setCompetencias([...competencias, { id, descripcion: desc }]);
setCompetenciasGuardadas([
...competenciasGuardadas,
{ id: competenciaId, descripcion: nuevaCompetencia },
]);
setAddCompetencia(false); setAddCompetencia(false);
setValue("nuevaCompetencia", ""); setValue("nuevaCompetencia", "");
setMostrarDialogCompetencia(true); // Mostrar dialog de éxito setDialogMsg("¡La competencia fue agregada exitosamente!");
setShowDialog(true);
} catch (err) { } catch (err) {
alert("Error al guardar la competencia: " + (err.message || err)); setDialogMsg("Error al guardar la competencia: " + (err.message || err));
setShowDialog(true);
} }
}; };
// Eliminar competencia
const handleDeleteCompetencia = (index) => { const handleDeleteCompetencia = (index) => {
setCompetenciasGuardadas( setCompetencias(competencias.filter((_, i) => i !== index));
competenciasGuardadas.filter((_, i) => i !== index)
);
}; };
// Guardar curso y asociar competencias // Guardar curso y asociar competencias
const onSubmit = async (data) => { const onSubmit = async (data) => {
const { nombre, descripcion } = data;
const horas = parseInt(data.horas, 10);
setLoading(true); setLoading(true);
try { try {
// 1. Inserta el curso const { nombre, descripcion, horas } = data;
const { data: cursoInsertado, error: errorCurso } = await supabaseClient const { data: curso, error: errorCurso } = await supabaseClient
.from("curso") .from("curso")
.insert([{ nombre, descripcion, horas }]) .insert([{ nombre, descripcion, horas: parseInt(horas, 10) }])
.select("id") .select("id")
.single(); .single();
if (errorCurso) throw errorCurso;
if (errorCurso) { if (competencias.length) {
setMensajeDialog("Error al guardar el curso: " + errorCurso.message); const relaciones = competencias.map((c) => ({
setMostrarDialog(true); curso_id: curso.id,
setLoading(false); competencia_id: c.id,
return; }));
}
// 2. Inserta en la tabla pivote curso_competencia
const cursoId = cursoInsertado.id;
const relaciones = competenciasGuardadas.map((c) => ({
curso_id: cursoId,
competencia_id: c.id,
}));
if (relaciones.length > 0) {
const { error: errorPivote } = await supabaseClient const { error: errorPivote } = await supabaseClient
.from("curso_competencia") .from("curso_competencia")
.insert(relaciones); .insert(relaciones);
if (errorPivote) { if (errorPivote) throw errorPivote;
setMensajeDialog("Error al asociar competencias: " + errorPivote.message);
setMostrarDialog(true);
setLoading(false);
return;
}
} }
setDialogMsg("Curso guardado exitosamente");
setMensajeDialog("Curso guardado exitosamente"); setCompetencias([]);
setMostrarDialog(true);
form.reset(); form.reset();
setCompetenciasGuardadas([]);
} catch (err) { } catch (err) {
alert("Ocurrió un error inesperado"); setDialogMsg("Error: " + (err.message || err));
} finally { } finally {
setShowDialog(true);
setLoading(false); setLoading(false);
} }
}; };
return ( return (
<form onSubmit={handleSubmit(onSubmit)} className="w-full"> <Layout>
<Input <div className="w-full bg-white pt-5 font-sans text-center md:w-[80%] flex flex-col items-center justify-start text-black">
type="text" <h1 className="text-xl font-semibold mb-10 text-black">Nuevo curso</h1>
placeholder="Nombre del curso" <form onSubmit={handleSubmit(onSubmit)} className="w-full">
{...register("nombre")} <Input
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black" type="text"
/> placeholder="Nombre del curso"
{errors.nombre && ( {...register("nombre")}
<p className="text-red-500 text-sm">{errors.nombre.message}</p> className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
)} />
{errors.nombre && (
<p className="text-red-500 text-sm">{errors.nombre.message}</p>
)}
<Textarea <Textarea
placeholder="Descripción" placeholder="Descripción"
{...register("descripcion")} {...register("descripcion")}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24 text-black" className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24 text-black"
/> />
{errors.descripcion && ( {errors.descripcion && (
<p className="text-red-500 text-sm">{errors.descripcion.message}</p> <p className="text-red-500 text-sm">{errors.descripcion.message}</p>
)} )}
<Input <Input
type="number" type="number"
placeholder="Horas del curso" placeholder="Horas del curso"
{...register("horas", { valueAsNumber: true })} {...register("horas", { valueAsNumber: true })}
className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black" className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
/> />
{errors.horas && ( {errors.horas && (
<p className="text-red-500 text-sm">{errors.horas.message}</p> <p className="text-red-500 text-sm">{errors.horas.message}</p>
)} )}
<h2 className="text-lg font-semibold mb-3 text-black">Competencias</h2> <h2 className="text-lg font-semibold mb-3 text-black">
<p className="text-xs text-gray-500 mb-2"> Competencias
Puedes agregar competencias nuevas sin necesidad de crear un nuevo curso. Las competencias se guardarán y podrás asociarlas a otros cursos después. </h2>
</p> <p className="text-xs text-gray-500 mb-2">
Puedes agregar competencias nuevas sin necesidad de crear un nuevo
curso. Las competencias se guardarán y podrás asociarlas a otros
cursos después.
</p>
{competenciasGuardadas.length > 0 && ( {competencias.length > 0 && (
<div className="mt-5 w-full flex-wrap"> <div className="mt-5 w-full flex-wrap">
{competenciasGuardadas.map((competencia, index) => ( {competencias.map((c, i) => (
<div <div
key={index} key={i}
className="w-full flex justify-between items-center px-2 mb-2" className="w-full flex justify-between items-center px-2 mb-2"
> >
<span className="text-black">{competencia.descripcion}</span> <span className="text-black">{c.descripcion}</span>
<Button <Button
type="button" type="button"
onClick={() => handleDeleteCompetencia(index)} onClick={() => handleDeleteCompetencia(i)}
className="bg-red-400 hover:bg-red-500 text-white font-bold py-1 px-3 rounded-md" className="bg-red-400 hover:bg-red-500 text-white font-bold py-1 px-3 rounded-md"
> >
X X
</Button> </Button>
</div>
))}
</div> </div>
))} )}
</div>
)}
{addCompetencia && ( {addCompetencia && (
<div className="w-full flex flex-col md:flex-row mt-5"> <div className="w-full flex flex-col md:flex-row mt-5">
<div className="flex flex-col"> <div className="flex flex-col">
<Input <Input
type="text" type="text"
placeholder="Nueva competencia" placeholder="Nueva competencia"
{...register("nuevaCompetencia")} {...register("nuevaCompetencia")}
className="w-80 px-3 py-2 border border-gray-300 rounded-md mb-3 text-black" className="w-80 px-3 py-2 border border-gray-300 rounded-md mb-3 text-black"
/> />
{errors.nuevaCompetencia && ( {errors.nuevaCompetencia && (
<p className="text-red-500 text-sm"> <p className="text-red-500 text-sm">
{errors.nuevaCompetencia.message} {errors.nuevaCompetencia.message}
</p> </p>
)} )}
</div> </div>
<div className="flex flex-row"> <div className="flex flex-row">
<Button
type="button"
onClick={handleSaveCompetencia}
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md mr-2"
>
Guardar
</Button>
<Button
type="button"
onClick={() => {
setAddCompetencia(false);
setValue("nuevaCompetencia", "");
}}
className="bg-gray-400 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-md"
>
Cancelar
</Button>
</div>
</div>
)}
<Button
type="button"
onClick={() => setAddCompetencia(true)}
className="w-full bg-blue-400 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded-md mt-5"
>
Agregar competencia
</Button>
<div className="flex justify-center w-full mt-5">
<Button <Button
type="button" type="submit"
onClick={handleSaveCompetencia} className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md"
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md mr-2" disabled={loading}
> >
Guardar {loading ? "Guardando..." : "Guardar curso"}
</Button>
<Button
type="button"
onClick={() => {
setAddCompetencia(false);
setValue("nuevaCompetencia", "");
}}
className="bg-gray-400 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-md"
>
Cancelar
</Button> </Button>
</div> </div>
</div>
)}
<Button <Dialog open={showDialog} onOpenChange={setShowDialog}>
type="button" <DialogContent>
onClick={() => setAddCompetencia(true)} <DialogHeader>
className="w-full bg-blue-400 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded-md mt-5" <DialogTitle className="text-black">Resultado</DialogTitle>
> <DialogDescription>{dialogMsg}</DialogDescription>
Agregar competencia </DialogHeader>
</Button> <DialogFooter>
<Button onClick={() => setShowDialog(false)}>Cerrar</Button>
<div className="flex justify-center w-full mt-5"> </DialogFooter>
<Button </DialogContent>
type="submit" </Dialog>
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md" </form>
disabled={loading}
>
{loading ? "Guardando..." : "Guardar curso"}
</Button>
</div> </div>
</Layout>
<Dialog open={mostrarDialog} onOpenChange={setMostrarDialog}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Resultado</DialogTitle>
<DialogDescription>{mensajeDialog}</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setMostrarDialog(false)}>Cerrar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<Dialog open={mostrarDialogCompetencia} onOpenChange={setMostrarDialogCompetencia}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Competencia agregada</DialogTitle>
<DialogDescription>
¡La competencia fue agregada exitosamente!
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button onClick={() => setMostrarDialogCompetencia(false)}>Cerrar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</form>
); );
} }

View File

@ -39,7 +39,8 @@ export default function CursosVista() {
const cargarCursos = async () => { const cargarCursos = async () => {
const { data, error } = await supabaseClient const { data, error } = await supabaseClient
.from("curso") .from("curso")
.select(` .select(
`
id, id,
nombre, nombre,
descripcion, descripcion,
@ -50,7 +51,8 @@ export default function CursosVista() {
descripcion descripcion
) )
) )
`) `
)
.order("id", { ascending: true }); .order("id", { ascending: true });
if (error) { if (error) {
console.error("Error al cargar cursos:", error.message); console.error("Error al cargar cursos:", error.message);
@ -90,7 +92,7 @@ export default function CursosVista() {
// Guardar cambios en curso y competencias // Guardar cambios en curso y competencias
const guardarEdicion = async (id) => { const guardarEdicion = async (id) => {
// Validar que no haya competencias repetidas // Validar que no haya competencias repetidas
const ids = competenciasGuardadas.map(c => c?.id).filter(Boolean); const ids = competenciasGuardadas.map((c) => c?.id).filter(Boolean);
const setIds = new Set(ids); const setIds = new Set(ids);
if (ids.length !== setIds.size) { if (ids.length !== setIds.size) {
setModalMensaje("No puedes repetir competencias en un curso."); setModalMensaje("No puedes repetir competencias en un curso.");
@ -109,15 +111,12 @@ export default function CursosVista() {
// Actualiza competencias (tabla pivote) // Actualiza competencias (tabla pivote)
// 1. Elimina todas las competencias actuales del curso // 1. Elimina todas las competencias actuales del curso
await supabaseClient await supabaseClient.from("curso_competencia").delete().eq("curso_id", id);
.from("curso_competencia")
.delete()
.eq("curso_id", id);
// 2. Inserta las nuevas competencias seleccionadas // 2. Inserta las nuevas competencias seleccionadas
const competenciasAInsertar = competenciasGuardadas const competenciasAInsertar = competenciasGuardadas
.filter(c => c && c.id) .filter((c) => c && c.id)
.map(c => ({ .map((c) => ({
curso_id: id, curso_id: id,
competencia_id: c.id, competencia_id: c.id,
})); }));
@ -157,7 +156,9 @@ export default function CursosVista() {
} }
if (alumnosInscritos && alumnosInscritos.length > 0) { if (alumnosInscritos && alumnosInscritos.length > 0) {
setModalMensaje("No se puede eliminar el curso porque hay alumnos inscritos a este curso."); setModalMensaje(
"No se puede eliminar el curso porque hay alumnos inscritos a este curso."
);
setConfirmarEliminar(false); setConfirmarEliminar(false);
setMostrarModal(true); setMostrarModal(true);
return; return;
@ -192,150 +193,167 @@ export default function CursosVista() {
}; };
const quitarCompetencia = () => { const quitarCompetencia = () => {
setCompetenciasGuardadas(competenciasGuardadas.filter((_, i) => i !== compAEliminar)); setCompetenciasGuardadas(
competenciasGuardadas.filter((_, i) => i !== compAEliminar)
);
setDialogQuitarComp(false); setDialogQuitarComp(false);
setCompAEliminar(null); setCompAEliminar(null);
}; };
return ( return (
<Layout> <Layout>
<div className="w-[80vw] pt-10 flex flex-col items-center text-black"> <div className="w-full pt-10 flex flex-col items-center text-black">
<h1 className="text-2xl font-semibold mb-6">Lista de Cursos</h1> <h1 className="text-2xl font-semibold mb-6">Lista de Cursos</h1>
<table className="min-w-full bg-white border"> <div className="overflow-x-auto w-full">
<thead> <table className="min-w-full bg-white border">
<tr className="bg-gray-100"> <thead>
<th className="py-2 border-b">ID</th> <tr className="bg-gray-100">
<th className="py-2 border-b">Nombre</th> <th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Descripción</th> <th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Horas</th> <th className="py-2 border-b">Descripción</th>
<th className="py-2 border-b">Competencias</th> <th className="py-2 border-b">Horas</th>
<th className="py-2 border-b">Acciones</th> <th className="py-2 border-b">Competencias</th>
</tr> <th className="py-2 border-b">Acciones</th>
</thead> </tr>
<tbody> </thead>
{cursos.map((curso) => <tbody>
cursoEditando === curso.id ? ( {cursos.map((curso) =>
<tr key={curso.id}> cursoEditando === curso.id ? (
<td className="py-2 px-4 border-b text-center">{curso.id}</td> <tr key={curso.id}>
<td className="py-2 px-4 border-b"> <td className="py-2 px-4 border-b text-center">
<Input {curso.id}
value={nuevoNombre} </td>
onChange={(e) => setNuevoNombre(e.target.value)} <td className="py-2 px-4 border-b">
/> <Input
</td> value={nuevoNombre}
<td className="py-2 px-4 border-b"> onChange={(e) => setNuevoNombre(e.target.value)}
<Input />
value={nuevaDescripcion} </td>
onChange={(e) => setNuevaDescripcion(e.target.value)} <td className="py-2 px-4 border-b">
/> <Input
</td> value={nuevaDescripcion}
<td className="py-2 px-4 border-b"> onChange={(e) => setNuevaDescripcion(e.target.value)}
<Input />
type="number" </td>
value={nuevaHoras} <td className="py-2 px-4 border-b">
onChange={(e) => setNuevaHoras(e.target.value)} <Input
/> type="number"
</td> value={nuevaHoras}
<td className="py-2 px-4 border-b"> onChange={(e) => setNuevaHoras(e.target.value)}
<div className="flex flex-col gap-2"> />
{competenciasGuardadas.map((comp, idx) => ( </td>
<div key={idx} className="flex items-center gap-2"> <td className="py-2 px-4 border-b">
<select <div className="flex flex-col gap-2">
className="border rounded px-2 py-1" {competenciasGuardadas.map((comp, idx) => (
value={comp?.id || ""} <div key={idx} className="flex items-center gap-2">
onChange={e => { <select
const nuevaLista = [...competenciasGuardadas]; className="border rounded px-2 py-1"
const nuevaComp = todasCompetencias.find(c => c.id === Number(e.target.value)); value={comp?.id || ""}
nuevaLista[idx] = nuevaComp; onChange={(e) => {
setCompetenciasGuardadas(nuevaLista); const nuevaLista = [...competenciasGuardadas];
}} const nuevaComp = todasCompetencias.find(
> (c) => c.id === Number(e.target.value)
<option value="">Selecciona competencia</option> );
{todasCompetencias.map(tc => ( nuevaLista[idx] = nuevaComp;
<option setCompetenciasGuardadas(nuevaLista);
key={tc.id} }}
value={tc.id} >
disabled={ <option value="">Selecciona competencia</option>
// Deshabilita si ya está seleccionada en otro select {todasCompetencias.map((tc) => (
competenciasGuardadas.some( <option
(c, i) => c && c.id === tc.id && i !== idx key={tc.id}
) value={tc.id}
} disabled={
> // Deshabilita si ya está seleccionada en otro select
{tc.descripcion} competenciasGuardadas.some(
</option> (c, i) => c && c.id === tc.id && i !== idx
))} )
</select> }
<Button >
type="button" {tc.descripcion}
className="bg-red-500 hover:bg-red-700 text-white px-2 py-1 rounded" </option>
onClick={() => pedirConfirmacionQuitarComp(idx)} ))}
> </select>
Quitar <Button
</Button> type="button"
</div> 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 <Button
type="button" className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-2 rounded"
className="bg-blue-500 hover:bg-blue-700 text-white px-2 py-1 rounded mt-2" onClick={() => guardarEdicion(curso.id)}
onClick={() => setCompetenciasGuardadas([...competenciasGuardadas, null])}
disabled={competenciasGuardadas.length >= todasCompetencias.length}
> >
Agregar competencia Guardar
</Button> </Button>
</div> <Button
</td> className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-2 rounded"
<td className="py-2 px-4 border-b flex justify-center"> onClick={cancelarEdicion}
<Button >
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-2 rounded" Cancelar
onClick={() => guardarEdicion(curso.id)} </Button>
> </td>
Guardar </tr>
</Button> ) : (
<Button <tr key={curso.id}>
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-2 rounded" <td className="py-2 px-4 border-b">{curso.id}</td>
onClick={cancelarEdicion} <td className="py-2 px-4 border-b">{curso.nombre}</td>
> <td className="py-2 px-4 border-b">{curso.descripcion}</td>
Cancelar <td className="py-2 px-4 border-b">{curso.horas}</td>
</Button> <td className="py-2 px-4 border-b">
</td> {Array.isArray(curso.competencias) &&
</tr> curso.competencias.length > 0 ? (
) : (
<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
? (
<ul className="list-disc pl-4"> <ul className="list-disc pl-4">
{curso.competencias.map((comp) => ( {curso.competencias.map((comp) => (
<li key={comp.id}>{comp.descripcion}</li> <li key={comp.id}>{comp.descripcion}</li>
))} ))}
</ul> </ul>
) ) : (
: "Sin competencias"} "Sin competencias"
</td> )}
<td className="py-2 px-4 border-b space-x-2"> </td>
<Button <td className="py-2 px-4 border-b flex justify-center">
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded" <Button
onClick={() => iniciarEdicion(curso)} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 m-1 rounded"
> onClick={() => iniciarEdicion(curso)}
Editar >
</Button> Editar
<Button </Button>
className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 rounded" <Button
onClick={() => confirmarEliminacion(curso.id)} className="bg-red-500 hover:bg-red-700 text-white font-bold py-1 px-3 m-1 rounded"
> onClick={() => confirmarEliminacion(curso.id)}
Eliminar >
</Button> Eliminar
</td> </Button>
</tr> </td>
) </tr>
)} )
</tbody> )}
</table> </tbody>
</table>
</div>
</div> </div>
{/* Dialog para eliminar curso */} {/* Dialog para eliminar curso */}
@ -371,9 +389,7 @@ export default function CursosVista() {
<Dialog open={dialogQuitarComp} onOpenChange={setDialogQuitarComp}> <Dialog open={dialogQuitarComp} onOpenChange={setDialogQuitarComp}>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle className="text-black"> <DialogTitle className="text-black">Quitar competencia</DialogTitle>
Quitar competencia
</DialogTitle>
<DialogDescription> <DialogDescription>
¿Estás seguro de que deseas quitar esta competencia del curso? ¿Estás seguro de que deseas quitar esta competencia del curso?
</DialogDescription> </DialogDescription>
@ -411,4 +427,4 @@ export default function CursosVista() {
</Dialog> </Dialog>
</Layout> </Layout>
); );
} }

View File

@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import Layout from "@/components/layout/Layout"; import Layout from "@/components/layout/Layout";
import { supabaseClient } from "@/utils/supabase"; import { supabaseClient } from "@/utils/supabase";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import CrearDiplomaDialog from "@/components/dialogs/crearDiplomaDialog";
import VistaPreviaDiplomaDialog from "@/components/dialogs/vistaPreviaDiplomaDialog"; import VistaPreviaDiplomaDialog from "@/components/dialogs/vistaPreviaDiplomaDialog";
export default function DiplomasVista() { export default function DiplomasVista() {
@ -53,60 +52,51 @@ export default function DiplomasVista() {
return ( return (
<Layout> <Layout>
<div className="w-[80vw] pt-10 flex flex-col items-center text-black"> <div className="w-full pt-10 flex flex-col items-center text-black">
<h1 className="text-2xl font-semibold mb-6">Vista de Diplomas</h1> <h1 className="text-2xl font-semibold mb-6">Vista de Diplomas</h1>
<table className="min-w-full bg-white border"> <div className="overflow-x-auto w-full">
<thead> <table className="w-full bg-white border">
<tr className="bg-gray-100"> <thead>
<th className="py-2 border-b">ID</th> <tr className="bg-gray-100">
<th className="py-2 border-b">Nombre</th> <th className="py-2 border-b">ID</th>
<th className="py-2 border-b">Correo</th> <th className="py-2 border-b">Nombre</th>
<th className="py-2 border-b">Teléfono</th> <th className="py-2 border-b">Correo</th>
<th className="py-2 border-b">Curso</th> <th className="py-2 border-b">Teléfono</th>
<th className="py-2 border-b">Acciones</th> <th className="py-2 border-b">Curso</th>
</tr> <th className="py-2 border-b">Acciones</th>
</thead>
<tbody>
{alumnos.map((alumno) => (
<tr key={alumno.id}>
<td className="py-2 px-4 border-b">{alumno.id}</td>
<td className="py-2 px-4 border-b">{alumno.nombre}</td>
<td className="py-2 px-4 border-b">{alumno.correo}</td>
<td className="py-2 px-4 border-b">{alumno.telefono}</td>
<td className="py-2 px-4 border-b">
{alumno.curso?.nombre || "Sin curso"}
</td>
<td className="py-2 px-4 border-b">
<Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
onClick={() => {
setAlumnoSeleccionado(alumno);
setMostrarDialog(true);
}}
>
Crear Diploma
</Button>
</td>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {alumnos.map((alumno) => (
<tr key={alumno.id}>
<td className="py-2 px-4 border-b">{alumno.id}</td>
<td className="py-2 px-4 border-b">{alumno.nombre}</td>
<td className="py-2 px-4 border-b">{alumno.correo}</td>
<td className="py-2 px-4 border-b">{alumno.telefono}</td>
<td className="py-2 px-4 border-b">
{alumno.curso?.nombre || "Sin curso"}
</td>
<td className="py-2 px-4 border-b">
<Button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
onClick={() => {
setAlumnoSeleccionado(alumno);
setMostrarDialog(true);
}}
>
Crear Diploma
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div> </div>
{/* Dialog para crear diploma y vista previa juntos */} {/* Dialog para crear diploma y vista previa juntos */}
{mostrarDialog && ( {mostrarDialog && (
<div className="fixed inset-0 z-50 flex items-center justify-center"> <div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="flex gap-8 bg-black bg-opacity-30 p-8 rounded"> <div className="flex gap-8 bg-black bg-opacity-30 p-8 rounded">
{/*<CrearDiplomaDialog
open={mostrarDialog}
onOpenChange={handleCloseDialog}
alumno={alumnoSeleccionado}
competencias={competencias}
setCompetencias={setCompetencias}
competenciasAcreditadas={competenciasAcreditadas}
setCompetenciasAcreditadas={setCompetenciasAcreditadas}
fecha={fecha}
setFecha={setFecha}
/>*/}
<VistaPreviaDiplomaDialog <VistaPreviaDiplomaDialog
open={mostrarDialog} open={mostrarDialog}
onOpenChange={handleCloseDialog} onOpenChange={handleCloseDialog}