feat: add loading and warning dialogs in cursosArchivo, enhance user feedback during course registration

This commit is contained in:
BenitoBB 2025-05-18 15:12:12 -06:00
parent 9510e9a1a1
commit 6a4a44bdbc
2 changed files with 123 additions and 7 deletions

View File

@ -3,21 +3,45 @@ 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 } from "@/components/ui/dialog"; import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog";
import { useRouter } from "next/router";
export default function cursosArchivo() { export default function cursosArchivo() {
const [archivo, setArchivo] = useState(null); const [archivo, setArchivo] = useState(null);
const [datos, setDatos] = useState([]); const [datos, setDatos] = useState([]);
const [dialogoAbierto, setDialogoAbierto] = useState(false); const [dialogoAbierto, setDialogoAbierto] = useState(false);
const [mensajeDialogo, setMensajeDialogo] = useState(""); const [mensajeDialogo, setMensajeDialogo] = useState("");
const [dialogoCargando, setDialogoCargando] = useState(false);
const [dialogoAdvertencia, setDialogoAdvertencia] = useState(false);
const [rutaPendiente, setRutaPendiente] = useState(null);
const router = useRouter();
useEffect(() => { useEffect(() => {
if (archivo) extraerContenido(); if (archivo) extraerContenido();
}, [archivo]); }, [archivo]);
useEffect(() => {
const handleRouteChange = (url) => {
if (archivo && datos.length > 0) {
setDialogoAdvertencia(true);
setRutaPendiente(url);
// Cancelar navegación
throw "Bloqueo de navegación por archivo pendiente";
}
};
router.events.on("routeChangeStart", handleRouteChange);
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, [archivo, datos, router]);
const registrarCursos = async () => { const registrarCursos = async () => {
if (datos.length === 0) return; if (datos.length === 0) return;
setDialogoCargando(true); // Mostrar dialogo de carga
const errores = []; const errores = [];
for (const curso of datos) { for (const curso of datos) {
@ -40,6 +64,8 @@ export default function cursosArchivo() {
} }
} }
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 {
@ -173,6 +199,47 @@ export default function cursosArchivo() {
</DialogHeader> </DialogHeader>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<Dialog open={dialogoCargando}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Cargando...</DialogTitle>
<DialogDescription>
Por favor espera, se están registrando los cursos.
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
<Dialog open={dialogoAdvertencia} onOpenChange={setDialogoAdvertencia}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Advertencia</DialogTitle>
<DialogDescription>
Si cambias de ventana perderás la subida del archivo. ¿Deseas continuar?
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button
className="bg-red-500 hover:bg-red-700 text-white"
onClick={() => {
setDialogoAdvertencia(false);
setArchivo(null);
setDatos([]);
if (rutaPendiente) router.push(rutaPendiente);
}}
>
, continuar
</Button>
<Button
className="bg-gray-400 hover:bg-gray-600 text-white"
onClick={() => setDialogoAdvertencia(false)}
>
Cancelar
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Layout> </Layout>
); );
} }

View File

@ -7,11 +7,21 @@ 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"; // Importar el cliente de Supabase
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
export default function CursosManual() { export default function CursosManual() {
const [addCompetencia, setAddCompetencia] = useState(false); const [addCompetencia, setAddCompetencia] = useState(false);
const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]); const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [mostrarDialog, setMostrarDialog] = useState(false);
const [mensajeDialog, setMensajeDialog] = useState("");
const form = useForm({ const form = useForm({
resolver: zodResolver(cursosSchema), resolver: zodResolver(cursosSchema),
@ -79,7 +89,8 @@ export default function CursosManual() {
console.error("Error al guardar en Supabase:", error.message); console.error("Error al guardar en Supabase:", error.message);
alert("Error al guardar el curso: " + error.message); alert("Error al guardar el curso: " + error.message);
} else { } else {
alert("Curso guardado exitosamente"); setMensajeDialog("Curso guardado exitosamente");
setMostrarDialog(true);
form.reset(); // Reiniciar el formulario form.reset(); // Reiniciar el formulario
setCompetenciasGuardadas([]); // Limpiar competencias guardadas setCompetenciasGuardadas([]); // Limpiar competencias guardadas
} }
@ -107,6 +118,11 @@ export function CursosManualForm({ nombreSugerido = "" }) {
const [addCompetencia, setAddCompetencia] = useState(false); const [addCompetencia, setAddCompetencia] = useState(false);
const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]); // [{id, descripcion}] const [competenciasGuardadas, setCompetenciasGuardadas] = useState([]); // [{id, descripcion}]
const [loading, setLoading] = useState(false); 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({ const form = useForm({
resolver: zodResolver(cursosSchema), resolver: zodResolver(cursosSchema),
@ -126,14 +142,14 @@ export function CursosManualForm({ nombreSugerido = "" }) {
formState: { errors }, formState: { errors },
} = form; } = form;
// Cambia para insertar la competencia en la tabla competencia // Cambia handleSaveCompetencia para mostrar el dialog
const handleSaveCompetencia = async (e) => { const handleSaveCompetencia = async (e) => {
e.preventDefault(); e.preventDefault();
const nuevaCompetencia = getValues("nuevaCompetencia").trim(); const nuevaCompetencia = getValues("nuevaCompetencia").trim();
if (!nuevaCompetencia) return; if (!nuevaCompetencia) return;
// Verifica si ya existe en el estado // Verifica si ya existe en el estado
if (competenciasGuardadas.some(c => c.descripcion === nuevaCompetencia)) { if (competenciasGuardadas.some((c) => c.descripcion === nuevaCompetencia)) {
alert("La competencia ya fue agregada."); alert("La competencia ya fue agregada.");
return; return;
} }
@ -167,6 +183,7 @@ export function CursosManualForm({ nombreSugerido = "" }) {
]); ]);
setAddCompetencia(false); setAddCompetencia(false);
setValue("nuevaCompetencia", ""); setValue("nuevaCompetencia", "");
setMostrarDialogCompetencia(true); // Mostrar dialog de éxito
} catch (err) { } catch (err) {
alert("Error al guardar la competencia: " + (err.message || err)); alert("Error al guardar la competencia: " + (err.message || err));
} }
@ -194,7 +211,8 @@ export function CursosManualForm({ nombreSugerido = "" }) {
.single(); .single();
if (errorCurso) { if (errorCurso) {
alert("Error al guardar el curso: " + errorCurso.message); setMensajeDialog("Error al guardar el curso: " + errorCurso.message);
setMostrarDialog(true);
setLoading(false); setLoading(false);
return; return;
} }
@ -211,13 +229,15 @@ export function CursosManualForm({ nombreSugerido = "" }) {
.from("curso_competencia") .from("curso_competencia")
.insert(relaciones); .insert(relaciones);
if (errorPivote) { if (errorPivote) {
alert("Error al asociar competencias: " + errorPivote.message); setMensajeDialog("Error al asociar competencias: " + errorPivote.message);
setMostrarDialog(true);
setLoading(false); setLoading(false);
return; return;
} }
} }
alert("Curso guardado exitosamente"); setMensajeDialog("Curso guardado exitosamente");
setMostrarDialog(true);
form.reset(); form.reset();
setCompetenciasGuardadas([]); setCompetenciasGuardadas([]);
} catch (err) { } catch (err) {
@ -259,6 +279,9 @@ export function CursosManualForm({ nombreSugerido = "" }) {
)} )}
<h2 className="text-lg font-semibold mb-3 text-black">Competencias</h2> <h2 className="text-lg font-semibold mb-3 text-black">Competencias</h2>
<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 && ( {competenciasGuardadas.length > 0 && (
<div className="mt-5 w-full flex-wrap"> <div className="mt-5 w-full flex-wrap">
@ -334,6 +357,32 @@ export function CursosManualForm({ nombreSugerido = "" }) {
{loading ? "Guardando..." : "Guardar curso"} {loading ? "Guardando..." : "Guardar curso"}
</Button> </Button>
</div> </div>
<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> </form>
); );
} }