diff --git a/diplomas/src/pages/cursosManual.jsx b/diplomas/src/pages/cursosManual.jsx index 55723b5..6b9b2d4 100644 --- a/diplomas/src/pages/cursosManual.jsx +++ b/diplomas/src/pages/cursosManual.jsx @@ -1,88 +1,209 @@ import React, { useState } from "react"; -import { supabaseClient } from "@/utils/supabase"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { cursosSchema } from "@/schemas/CursosSchema"; import Layout from "@/components/layout/Layout"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; -import { - Select, - SelectTrigger, - SelectValue, - SelectContent, - SelectItem, -} from "@/components/ui/select"; +import { supabaseClient } from "@/utils/supabase"; // Importar el cliente de Supabase export default function CursosManual() { - const [nombre, setNombre] = useState(""); - const [descripcion, setDescripcion] = useState(""); - const [horas, setHoras] = useState(""); - const [competencia, setCompetencia] = useState(""); + const [addCompetencia, setAddCompetencia] = useState(false); + const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]); const [loading, setLoading] = useState(false); - const manejarGuardar = async () => { - setLoading(true); - const { error } = await supabaseClient.from("curso").insert([ - { - nombre, - descripcion, - horas, - competencias: [competencia], // si es una sola, usa string. Si son varias, un array. - }, - ]); - setLoading(false); + const form = useForm({ + resolver: zodResolver(cursosSchema), + defaultValues: { + nombre: "", + descripcion: "", + horas: 0, + nuevaCompetencia: "", + }, + }); - if (error) { - alert("Error al guardar: " + error.message); - } else { - alert("Curso guardado correctamente"); - setNombre(""); - setDescripcion(""); - setHoras(""); - setCompetencia(""); + const { + register, + handleSubmit, + setValue, + getValues, + formState: { errors }, + } = form; + + const handleAddCompetencia = () => { + 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 { + alert("Curso guardado exitosamente"); + 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%]"> + <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> - <Input - type="text" - placeholder="Nombre del curso" - value={nombre} - onChange={(e) => setNombre(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black" - /> - <Textarea - placeholder="Descripción" - value={descripcion} - onChange={(e) => setDescripcion(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24 text-black" - /> - <Input - type="number" - placeholder="Horas del curso" - value={horas} - onChange={(e) => setHoras(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black" - /> - <Select onValueChange={(value) => setCompetencia(value)}> - <SelectTrigger className="w-full px-3 py-2 border border-gray-300 rounded-md mb-4 text-black"> - <SelectValue placeholder="Competencia" /> - </SelectTrigger> - <SelectContent> - <SelectItem value="competencia1">Competencia 1</SelectItem> - <SelectItem value="competencia2">Competencia 2</SelectItem> - </SelectContent> - </Select> - <Button - onClick={manejarGuardar} - className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md text-black" - disabled={loading} - > - {loading ? "Guardando..." : "Guardar"} - </Button> + <form onSubmit={handleSubmit(onSubmit)} className="w-full"> + <Input + type="text" + placeholder="Nombre del curso" + {...register("nombre")} + 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 + placeholder="Descripción" + {...register("descripcion")} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 h-24 text-black" + /> + {errors.descripcion && ( + <p className="text-red-500 text-sm"> + {errors.descripcion.message} + </p> + )} + + <Input + type="number" + placeholder="Horas del curso" + {...register("horas", { valueAsNumber: true })} + className="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 text-black" + /> + {errors.horas && ( + <p className="text-red-500 text-sm">{errors.horas.message}</p> + )} + + <h2 className="text-lg font-semibold mb-3 text-black"> + Competencias + </h2> + + {competenciasGuardadas.length > 0 && ( + <div className="mt-5 w-full flex-wrap"> + {competenciasGuardadas.map((competencia, index) => ( + <div + key={index} + className="w-full flex justify-between items-center px-2 mb-2" + > + <span className="text-black">{competencia}</span> + <Button + type="button" + onClick={() => handleDeleteCompetencia(index)} + className="bg-red-400 hover:bg-red-500 text-white font-bold py-1 px-3 rounded-md" + > + X + </Button> + </div> + ))} + </div> + )} + + {addCompetencia && ( + <div className="w-full flex flex-col md:flex-row mt-5"> + <div className="flex flex-col"> + <Input + type="text" + placeholder="Nueva competencia" + {...register("nuevaCompetencia")} + className="w-80 px-3 py-2 border border-gray-300 rounded-md mb-3 text-black" + /> + {errors.nuevaCompetencia && ( + <p className="text-red-500 text-sm"> + {errors.nuevaCompetencia.message} + </p> + )} + </div> + <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={handleCancel} + 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={handleAddCompetencia} + 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 + type="submit" + className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md" + disabled={loading} + > + {loading ? "Guardando..." : "Guardar curso"} + </Button> + </div> + </form> </div> </div> </Layout> diff --git a/diplomas/src/schemas/CursosSchema.js b/diplomas/src/schemas/CursosSchema.js index f097b88..b163e45 100644 --- a/diplomas/src/schemas/CursosSchema.js +++ b/diplomas/src/schemas/CursosSchema.js @@ -6,8 +6,8 @@ export const cursosSchema = z.object({ .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 + horas: z.number().positive("Las horas deben ser un número positivo").int(), + nuevaCompetencia: z .string() .nonempty("Escribe las competencias") .regex(/^[\p{L}\s]+$/u, "Solo se permiten letras en las competencias"),