feat: enhance diploma dialogs with improved state management and user feedback, including PDF generation and course competency handling

This commit is contained in:
SirRobert-1 2025-05-19 20:43:14 -06:00
parent d2c7bddf18
commit bb02cf3830
3 changed files with 178 additions and 48 deletions

View File

@ -27,13 +27,13 @@ export default function CrearDiplomaDialog({
fecha, fecha,
setFecha, setFecha,
}) { }) {
const [diseñoSeleccionado, setDiseñoSeleccionado] = useState(DISEÑOS[0]?.id || null); const [diseñoSeleccionado, setDiseñoSeleccionado] = useState(
DISEÑOS[0]?.id || null
);
const toggleCompetencia = (id) => { const toggleCompetencia = (id) => {
setCompetenciasAcreditadas((prev) => setCompetenciasAcreditadas((prev) =>
prev.includes(id) prev.includes(id) ? prev.filter((cid) => cid !== id) : [...prev, id]
? prev.filter((cid) => cid !== id)
: [...prev, id]
); );
}; };
@ -68,7 +68,9 @@ export default function CrearDiplomaDialog({
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="mb-2"> <div className="mb-2">
<label className="block text-sm font-semibold mb-1">Competencias:</label> <label className="block text-sm font-semibold mb-1">
Competencias:
</label>
{competencias.map((comp) => ( {competencias.map((comp) => (
<div key={comp.id} className="flex items-center mb-1"> <div key={comp.id} className="flex items-center mb-1">
<input <input
@ -87,14 +89,20 @@ export default function CrearDiplomaDialog({
type="date" type="date"
className="border p-1 w-full" className="border p-1 w-full"
value={fecha} value={fecha}
onChange={e => setFecha(e.target.value)} onChange={(e) => setFecha(e.target.value)}
/> />
</div> </div>
<DialogFooter> <DialogFooter>
<Button onClick={handleCrearDiploma} className="bg-green-500 hover:bg-green-700 text-white"> <Button
onClick={handleCrearDiploma}
className="bg-green-500 hover:bg-green-700 text-white"
>
Crear Diploma Crear Diploma
</Button> </Button>
<Button onClick={() => onOpenChange(false)} className="bg-gray-400 hover:bg-gray-600 text-white"> <Button
onClick={() => onOpenChange(false)}
className="bg-gray-400 hover:bg-gray-600 text-white"
>
Cancelar Cancelar
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import { useState, useEffect } from "react";
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -6,57 +6,164 @@ import {
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import Diploma from "@/components/Diploma"; import Diploma from "@/components/Diploma";
import { PDFDownloadLink, PDFViewer } from "@react-pdf/renderer"; import { PDFDownloadLink, PDFViewer, pdf } from "@react-pdf/renderer";
import { Button } from "../ui/button"; import { supabaseClient } from "@/utils/supabase";
function VistaPreviaDiplomaDialog({ function VistaPreviaDiplomaDialog({
open, open,
onOpenChange, onOpenChange,
alumno, alumno,
competencias, competencias: competenciasProp,
fecha, fecha,
competenciasAcreditadas, competenciasAcreditadas,
}) { }) {
const [mostrarVistaPrevia, setMostrarVistaPrevia] = useState(false); const [mostrarVistaPrevia, setMostrarVistaPrevia] = useState(false);
const [enviando, setEnviando] = useState(false);
const [mensaje, setMensaje] = useState("");
const [competencias, setCompetencias] = useState([]);
if (!open || !alumno) return null; useEffect(() => {
if (alumno && alumno.curso?.id) {
supabaseClient
.from("curso_competencia")
.select("competencia(id, descripcion)")
.eq("curso_id", alumno.curso.id)
.then(({ data }) => {
const comps = data?.map((c) => c.competencia).filter(Boolean) || [];
setCompetencias(comps);
});
}
}, [alumno]);
// Filtra solo las competencias seleccionadas si se pasa el array de acreditadas if (!alumno) return null;
const competenciasMostradas = Array.isArray(competenciasAcreditadas)
? (Array.isArray(competencias) ? competencias : []).filter((comp) =>
competenciasAcreditadas.includes(comp.id)
)
: (Array.isArray(competencias) ? competencias : []);
const competenciasParaPDF = const competenciasMostradas = competenciasAcreditadas
Array.isArray(competenciasMostradas) && competenciasMostradas.length > 0 ? competencias.filter((comp) => competenciasAcreditadas.includes(comp.id))
? competenciasMostradas : competencias;
: [];
// Simulación de envío de PDF por correo y WhatsApp
const handleEnviar = async () => {
setEnviando(true);
setMensaje("");
// Simula la generación del PDF
const blob = await pdf(
<Diploma
alumno={alumno}
curso={alumno.curso}
competencias={competenciasMostradas}
fecha={fecha || new Date().toLocaleDateString()}
/>
).toBlob();
// Simula el envío por correo (espera 2 segundos)
await new Promise((resolve) => setTimeout(resolve, 2000));
// Enviar mensaje real por WhatsApp usando la API de WhatsApp (abrirá una nueva ventana)
const telefono = alumno.telefono.replace(/\D/g, ""); // Solo números
const mensajeWhatsapp = encodeURIComponent(
`Hola ${alumno.nombre}, tu diploma ha sido generado y enviado a tu correo (${alumno.correo}). ¡Felicidades!`
);
window.open(`https://wa.me/${telefono}?text=${mensajeWhatsapp}`, "_blank");
setMensaje(
`Diploma enviado por correo a ${alumno.correo} y mensaje enviado por WhatsApp al ${alumno.telefono}.`
);
setEnviando(false);
};
return ( return (
<div className="bg-white border shadow-lg rounded p-8 w-[500px] h-[350px] mx-4 flex flex-col text-black"> <Dialog open={open} onOpenChange={onOpenChange}>
<h2 className="text-2xl font-bold text-center mb-6">Diploma</h2> <DialogContent className="max-w-lg text-black">
<div className="text-lg mb-2"> <DialogHeader>
<b>Alumno:</b> {alumno.nombre} <DialogTitle>Diploma</DialogTitle>
</div> </DialogHeader>
<div className="text-lg mb-2"> <div className="text-lg mb-2">
<b>Curso:</b> {alumno.curso?.nombre || "Sin curso"} <b>Alumno:</b> {alumno.nombre}
</div> </div>
<div className="text-lg mb-2"> <div className="text-lg mb-2">
<b>Competencias Acreditadas:</b> <b>Curso:</b> {alumno.curso?.nombre || "Sin curso"}
<ul className="list-disc ml-6"> </div>
{competenciasMostradas.map((comp) => ( <div className="text-lg mb-2">
<li key={comp.id}>{comp.descripcion}</li> <b>Competencias Acreditadas:</b>
))} <ul className="list-disc ml-6">
</ul> {competenciasMostradas.map((comp) => (
</div> <li key={comp.id}>{comp.descripcion}</li>
<div className="text-lg mb-2"> ))}
<b>Fecha:</b> {fecha || new Date().toLocaleDateString()} </ul>
</div> </div>
<div className="mt-auto text-gray-400 text-xs text-right"> <div className="text-lg mb-2">
Vista previa <b>Fecha:</b> {fecha || new Date().toLocaleDateString()}
</div> </div>
</div> <div className="mt-auto text-gray-400 text-xs text-right">
Vista previa
</div>
<div className="mt-4 flex gap-2 justify-center flex-wrap">
<PDFDownloadLink
document={
<Diploma
alumno={alumno}
curso={alumno.curso}
competencias={competenciasMostradas}
fecha={fecha || new Date().toLocaleDateString()}
/>
}
fileName={`Diploma_${alumno.nombre}.pdf`}
>
{({ loading }) =>
loading ? (
<button className="bg-gray-300 px-4 py-2 rounded" disabled>
Generando PDF...
</button>
) : (
<button className="bg-green-500 hover:bg-green-700 text-white px-4 py-2 rounded">
Descargar PDF
</button>
)
}
</PDFDownloadLink>
<button
className="bg-blue-500 hover:bg-blue-700 text-white px-4 py-2 rounded"
onClick={() => setMostrarVistaPrevia(true)}
>
Ver vista previa PDF
</button>
<button
className="bg-purple-600 hover:bg-purple-800 text-white px-4 py-2 rounded"
onClick={handleEnviar}
disabled={enviando}
>
{enviando ? "Enviando..." : "Enviar por correo y WhatsApp"}
</button>
</div>
{mensaje && (
<div className="mt-4 text-green-700 font-semibold text-center">
{mensaje}
</div>
)}
{mostrarVistaPrevia && (
<div className="fixed inset-0 bg-black bg-opacity-60 flex flex-col items-center justify-center z-50">
<div className="bg-white rounded shadow-lg p-4 flex flex-col items-center">
<div className="w-[80vw] h-[80vh] lg:h-[90vh] mb-4 border">
<PDFViewer width="100%" height="100%">
<Diploma
alumno={alumno}
curso={alumno.curso}
competencias={competenciasMostradas}
fecha={fecha || new Date().toLocaleDateString()}
/>
</PDFViewer>
</div>
<button
className="bg-red-500 hover:bg-red-700 text-white px-4 py-2 rounded"
onClick={() => setMostrarVistaPrevia(false)}
>
Cerrar vista previa
</button>
</div>
</div>
)}
</DialogContent>
</Dialog>
); );
} }

View File

@ -37,6 +37,20 @@ export default function DiplomasVista() {
} }
}; };
useEffect(() => {
if (alumnoSeleccionado && alumnoSeleccionado.curso?.id) {
supabaseClient
.from("curso_competencia")
.select("competencia(id, descripcion)")
.eq("curso_id", alumnoSeleccionado.curso.id)
.then(({ data }) => {
const comps = data?.map((c) => c.competencia).filter(Boolean) || [];
setCompetencias(comps);
setCompetenciasAcreditadas(comps.map((c) => c.id)); // Opcional: selecciona todas por default
});
}
}, [alumnoSeleccionado]);
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">
@ -82,7 +96,7 @@ export default function DiplomasVista() {
{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 {/*<CrearDiplomaDialog
open={mostrarDialog} open={mostrarDialog}
onOpenChange={handleCloseDialog} onOpenChange={handleCloseDialog}
alumno={alumnoSeleccionado} alumno={alumnoSeleccionado}
@ -92,9 +106,10 @@ export default function DiplomasVista() {
setCompetenciasAcreditadas={setCompetenciasAcreditadas} setCompetenciasAcreditadas={setCompetenciasAcreditadas}
fecha={fecha} fecha={fecha}
setFecha={setFecha} setFecha={setFecha}
/> />*/}
<VistaPreviaDiplomaDialog <VistaPreviaDiplomaDialog
open={mostrarDialog} open={mostrarDialog}
onOpenChange={handleCloseDialog}
alumno={alumnoSeleccionado} alumno={alumnoSeleccionado}
competencias={competencias} competencias={competencias}
fecha={fecha} fecha={fecha}