feat: add form validation schemas for alumnos and cursos, enhance UI with modals and input components
This commit is contained in:
parent
749798da29
commit
d5140cbec2
diplomas
|
@ -8,6 +8,7 @@
|
||||||
"name": "diplomas",
|
"name": "diplomas",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.11",
|
"@radix-ui/react-dialog": "^1.1.11",
|
||||||
"@radix-ui/react-label": "^2.1.4",
|
"@radix-ui/react-label": "^2.1.4",
|
||||||
"@radix-ui/react-select": "^2.1.7",
|
"@radix-ui/react-select": "^2.1.7",
|
||||||
|
@ -25,8 +26,10 @@
|
||||||
"papaparse": "^5.5.2",
|
"papaparse": "^5.5.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.56.2",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tw-animate-css": "^1.2.5"
|
"tw-animate-css": "^1.2.5",
|
||||||
|
"zod": "^3.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
@ -252,6 +255,17 @@
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
|
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@hookform/resolvers": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/utils": "^0.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-hook-form": "^7.55.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
|
@ -1961,6 +1975,11 @@
|
||||||
"integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
|
"integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@standard-schema/utils": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="
|
||||||
|
},
|
||||||
"node_modules/@supabase/auth-js": {
|
"node_modules/@supabase/auth-js": {
|
||||||
"version": "2.69.1",
|
"version": "2.69.1",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz",
|
||||||
|
@ -5769,6 +5788,21 @@
|
||||||
"react": "^19.1.0"
|
"react": "^19.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hook-form": {
|
||||||
|
"version": "7.56.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.2.tgz",
|
||||||
|
"integrity": "sha512-vpfuHuQMF/L6GpuQ4c3ZDo+pRYxIi40gQqsCmmfUBwm+oqvBhKhwghCuj2o00YCgSfU6bR9KC/xnQGWm3Gr08A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/react-hook-form"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "16.13.1",
|
"version": "16.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
|
@ -6881,6 +6915,14 @@
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.24.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz",
|
||||||
|
"integrity": "sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.11",
|
"@radix-ui/react-dialog": "^1.1.11",
|
||||||
"@radix-ui/react-label": "^2.1.4",
|
"@radix-ui/react-label": "^2.1.4",
|
||||||
"@radix-ui/react-select": "^2.1.7",
|
"@radix-ui/react-select": "^2.1.7",
|
||||||
|
@ -26,8 +27,10 @@
|
||||||
"papaparse": "^5.5.2",
|
"papaparse": "^5.5.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.56.2",
|
||||||
"tailwind-merge": "^3.2.0",
|
"tailwind-merge": "^3.2.0",
|
||||||
"tw-animate-css": "^1.2.5"
|
"tw-animate-css": "^1.2.5",
|
||||||
|
"zod": "^3.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
|
|
@ -81,7 +81,7 @@ export function AppSidebar({ ...props }) {
|
||||||
{data.navMain.map((item) => (
|
{data.navMain.map((item) => (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem key={item.title}>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<a href={item.url} className="font-medium">
|
<a href={item.url} className="text-xl font-medium">
|
||||||
{item.title}
|
{item.title}
|
||||||
</a>
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
|
|
@ -9,7 +9,10 @@ import {
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { supabaseClient } from "@/util/supabase";
|
import { supabaseClient } from "@/utils/supabase";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { alumnoSchema } from "@/schemas/AlumnosSchema";
|
||||||
|
|
||||||
export default function AlumnosManual() {
|
export default function AlumnosManual() {
|
||||||
const [nombre, setNombre] = useState("");
|
const [nombre, setNombre] = useState("");
|
||||||
|
@ -96,7 +99,7 @@ export default function AlumnosManual() {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={manejarGuardar}
|
onClick={manejarGuardar}
|
||||||
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md text-black"
|
className="bg-green-400 hover:bg-green-500 font-bold py-2 px-4 rounded-md text-black"
|
||||||
>
|
>
|
||||||
Guardar
|
Guardar
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,9 +1,25 @@
|
||||||
import React, { useEffect, useState } from "react";
|
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 {
|
||||||
|
Dialog,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
export default function AlumnosVista() {
|
export default function AlumnosVista() {
|
||||||
const [alumnos, setAlumnos] = useState([]);
|
const [alumnos, setAlumnos] = useState([]);
|
||||||
|
const [alumnoEditando, setAlumnoEditando] = useState(null);
|
||||||
|
const [nuevoNombre, setNuevoNombre] = useState("");
|
||||||
|
const [nuevoCorreo, setNuevoCorreo] = useState("");
|
||||||
|
const [mostrarModal, setMostrarModal] = useState(false);
|
||||||
|
const [modalMensaje, setModalMensaje] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cargarAlumnos = async () => {
|
const cargarAlumnos = async () => {
|
||||||
|
@ -17,6 +33,43 @@ export default function AlumnosVista() {
|
||||||
cargarAlumnos();
|
cargarAlumnos();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Iniciar edición
|
||||||
|
const iniciarEdicion = (alumno) => {
|
||||||
|
setAlumnoEditando(alumno.id);
|
||||||
|
setNuevoNombre(alumno.nombre);
|
||||||
|
setNuevoCorreo(alumno.correo);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cancelar edición
|
||||||
|
const cancelarEdicion = () => {
|
||||||
|
setAlumnoEditando(null);
|
||||||
|
setNuevoNombre("");
|
||||||
|
setNuevoCorreo("");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Guardar cambios
|
||||||
|
const guardarEdicion = async (id) => {
|
||||||
|
const { error } = await supabaseClient
|
||||||
|
.from("alumno")
|
||||||
|
.update({
|
||||||
|
nombre: nuevoNombre,
|
||||||
|
correo: nuevoCorreo,
|
||||||
|
})
|
||||||
|
.eq("id", id);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error("Error actualizando alumno:", error.message);
|
||||||
|
setModalMensaje("Error al actualizar el alumno");
|
||||||
|
} else {
|
||||||
|
setModalMensaje("Alumno actualizado exitosamente");
|
||||||
|
// Recargar alumnos
|
||||||
|
const { data } = await supabaseClient.from("alumno").select("*");
|
||||||
|
setAlumnos(data);
|
||||||
|
cancelarEdicion();
|
||||||
|
}
|
||||||
|
setMostrarModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
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">
|
||||||
|
@ -26,22 +79,84 @@ export default function AlumnosVista() {
|
||||||
<table className="min-w-full bg-white border">
|
<table className="min-w-full bg-white border">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="py-2 px-4 border-b">ID</th>
|
<th className="py-2 border-b">ID</th>
|
||||||
<th className="py-2 px-4 border-b">Nombre</th>
|
<th className="py-2 border-b">Nombre</th>
|
||||||
<th className="py-2 px-4 border-b">Correo</th>
|
<th className="py-2 border-b">Correo</th>
|
||||||
|
<th className="py-2 border-b">Acciones</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{alumnos.map((alumno) => (
|
{alumnos.map((alumno) =>
|
||||||
<tr key={alumno.id}>
|
alumnoEditando === alumno.id ? (
|
||||||
<td className="py-2 px-4 border-b text-center">{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 text-center">
|
||||||
<td className="py-2 px-4 border-b">{alumno.correo}</td>
|
{alumno.id}
|
||||||
</tr>
|
</td>
|
||||||
))}
|
<td className="py-2 px-4 border-b">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={nuevoNombre}
|
||||||
|
onChange={(e) => setNuevoNombre(e.target.value)}
|
||||||
|
className="border rounded px-2 py-1 w-full"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className="py-2 px-4 border-b">
|
||||||
|
<Input
|
||||||
|
type="email"
|
||||||
|
value={nuevoCorreo}
|
||||||
|
onChange={(e) => setNuevoCorreo(e.target.value)}
|
||||||
|
className="border rounded px-2 py-1 w-full"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className="py-2 px-4 border-b flex justify-center">
|
||||||
|
<Button
|
||||||
|
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 m-2 rounded"
|
||||||
|
onClick={() => guardarEdicion(alumno.id)}
|
||||||
|
>
|
||||||
|
Guardar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 m-2 rounded"
|
||||||
|
onClick={cancelarEdicion}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
<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">
|
||||||
|
<Button
|
||||||
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
|
||||||
|
onClick={() => iniciarEdicion(alumno)}
|
||||||
|
>
|
||||||
|
Editar
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Modal */}
|
||||||
|
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-black">
|
||||||
|
Resultado de la operación
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>{modalMensaje}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={() => setMostrarModal(false)}>Cerrar</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,17 @@
|
||||||
import React, { useEffect, useState } from "react";
|
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 {
|
||||||
|
Dialog,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
export default function CursosVista() {
|
export default function CursosVista() {
|
||||||
const [cursos, setCursos] = useState([]);
|
const [cursos, setCursos] = useState([]);
|
||||||
|
@ -8,6 +19,8 @@ export default function CursosVista() {
|
||||||
const [nuevoNombre, setNuevoNombre] = useState("");
|
const [nuevoNombre, setNuevoNombre] = useState("");
|
||||||
const [nuevaDescripcion, setNuevaDescripcion] = useState("");
|
const [nuevaDescripcion, setNuevaDescripcion] = useState("");
|
||||||
const [nuevaHoras, setNuevaHoras] = useState("");
|
const [nuevaHoras, setNuevaHoras] = useState("");
|
||||||
|
const [mostrarModal, setMostrarModal] = useState(false);
|
||||||
|
const [modalMensaje, setModalMensaje] = useState("");
|
||||||
|
|
||||||
// Cargar cursos al iniciar
|
// Cargar cursos al iniciar
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -40,7 +53,7 @@ export default function CursosVista() {
|
||||||
|
|
||||||
// Guardar cambios
|
// Guardar cambios
|
||||||
const guardarEdicion = async (id) => {
|
const guardarEdicion = async (id) => {
|
||||||
const { data, error } = await supabaseClient
|
const { error } = await supabaseClient
|
||||||
.from("curso")
|
.from("curso")
|
||||||
.update({
|
.update({
|
||||||
nombre: nuevoNombre,
|
nombre: nuevoNombre,
|
||||||
|
@ -51,14 +64,15 @@ export default function CursosVista() {
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error actualizando curso:", error.message);
|
console.error("Error actualizando curso:", error.message);
|
||||||
alert("Error al actualizar el curso");
|
setModalMensaje("Error al actualizar el curso");
|
||||||
} else {
|
} else {
|
||||||
alert("Curso actualizado exitosamente");
|
setModalMensaje("Curso actualizado exitosamente");
|
||||||
// Recargar cursos
|
// Recargar cursos
|
||||||
const { data } = await supabaseClient.from("curso").select("*");
|
const { data } = await supabaseClient.from("curso").select("*");
|
||||||
setCursos(data);
|
setCursos(data);
|
||||||
cancelarEdicion();
|
cancelarEdicion();
|
||||||
}
|
}
|
||||||
|
setMostrarModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -86,7 +100,7 @@ export default function CursosVista() {
|
||||||
{curso.id}
|
{curso.id}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 px-4 border-b">
|
<td className="py-2 px-4 border-b">
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={nuevoNombre}
|
value={nuevoNombre}
|
||||||
onChange={(e) => setNuevoNombre(e.target.value)}
|
onChange={(e) => setNuevoNombre(e.target.value)}
|
||||||
|
@ -94,7 +108,7 @@ export default function CursosVista() {
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 px-4 border-b">
|
<td className="py-2 px-4 border-b">
|
||||||
<input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={nuevaDescripcion}
|
value={nuevaDescripcion}
|
||||||
onChange={(e) => setNuevaDescripcion(e.target.value)}
|
onChange={(e) => setNuevaDescripcion(e.target.value)}
|
||||||
|
@ -110,18 +124,18 @@ export default function CursosVista() {
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2 px-4 border-b flex gap-2 justify-center">
|
<td className="py-2 px-4 border-b flex gap-2 justify-center">
|
||||||
<button
|
<Button
|
||||||
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 rounded"
|
className="bg-green-500 hover:bg-green-700 text-white font-bold py-1 px-3 rounded"
|
||||||
onClick={() => guardarEdicion(curso.id)}
|
onClick={() => guardarEdicion(curso.id)}
|
||||||
>
|
>
|
||||||
Guardar
|
Guardar
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button
|
||||||
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 rounded"
|
className="bg-gray-400 hover:bg-gray-600 text-white font-bold py-1 px-3 rounded"
|
||||||
onClick={cancelarEdicion}
|
onClick={cancelarEdicion}
|
||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
|
@ -133,12 +147,12 @@ export default function CursosVista() {
|
||||||
<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 text-center">
|
<td className="py-2 px-4 border-b text-center">
|
||||||
<button
|
<Button
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
|
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-3 rounded"
|
||||||
onClick={() => iniciarEdicion(curso)}
|
onClick={() => iniciarEdicion(curso)}
|
||||||
>
|
>
|
||||||
Editar
|
Editar
|
||||||
</button>
|
</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
)
|
||||||
|
@ -147,6 +161,21 @@ export default function CursosVista() {
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Modal */}
|
||||||
|
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-black">
|
||||||
|
Resultado de la operación
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>{modalMensaje}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={() => setMostrarModal(false)}>Cerrar</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const alumnoSchema = z.object({
|
||||||
|
nombre: z
|
||||||
|
.string()
|
||||||
|
.nonempty("Escribe el nombre")
|
||||||
|
.regex(/^[\p{L}\s]+$/u, "Solo se permiten letras en el nombre"),
|
||||||
|
correo: z.string().email("Escribe un correo válido"),
|
||||||
|
cursoSeleccionado: z.string().nonempty("Selecciona un curso"),
|
||||||
|
});
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const cursosSchema = z.object({
|
||||||
|
nombre: z
|
||||||
|
.string()
|
||||||
|
.nonempty("Escribe el nombre del curso")
|
||||||
|
.regex(/^[\p{L}\s]+$/u, "Solo se permiten letras en el nombre"),
|
||||||
|
descripcion: z.string().nonempty("Escribe una descripción"),
|
||||||
|
horas: z.number().positive("Las horas deben ser un número positivo"),
|
||||||
|
competencias: z
|
||||||
|
.string()
|
||||||
|
.nonempty("Escribe las competencias")
|
||||||
|
.regex(/^[\p{L}\s]+$/u, "Solo se permiten letras en las competencias"),
|
||||||
|
});
|
Loading…
Reference in New Issue