Merge branch 'benito' of https://git.gumoio.com/benito.rodriguez/SIDAC into roberto
This commit is contained in:
commit
0718f041a0
File diff suppressed because it is too large
Load Diff
|
@ -21,8 +21,11 @@
|
||||||
"@supabase/supabase-js": "^2.49.4",
|
"@supabase/supabase-js": "^2.49.4",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"diplomas": "file:",
|
"diplomas": "file:",
|
||||||
|
"express": "^5.1.0",
|
||||||
"lucide-react": "^0.488.0",
|
"lucide-react": "^0.488.0",
|
||||||
|
"mysql2": "^3.14.1",
|
||||||
"next": "15.3.0",
|
"next": "15.3.0",
|
||||||
"papaparse": "^5.5.2",
|
"papaparse": "^5.5.2",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
|
|
|
@ -52,6 +52,15 @@ const data = {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Diplomas",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Creación de diplomas",
|
||||||
|
url: "/diplomasVista",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,75 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Papa from "papaparse";
|
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";
|
||||||
|
|
||||||
export default function AlumnosArchivo() {
|
export default function AlumnosArchivo() {
|
||||||
const [archivo, setArchivo] = useState(null);
|
const [archivo, setArchivo] = useState(null);
|
||||||
const [datos, setDatos] = useState([]);
|
const [datos, setDatos] = useState([]);
|
||||||
|
const [dialogoAbierto, setDialogoAbierto] = useState(false);
|
||||||
|
const [mensajeDialogo, setMensajeDialogo] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (archivo) extraerContenido();
|
||||||
|
}, [archivo]);
|
||||||
|
|
||||||
|
const registrarAlumnos = async () => {
|
||||||
|
if (datos.length === 0) return;
|
||||||
|
|
||||||
|
const errores = [];
|
||||||
|
|
||||||
|
for (const alumno of datos) {
|
||||||
|
const res = await fetch("/api/alumno", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
nombre: alumno.nombre,
|
||||||
|
correo: alumno.correo,
|
||||||
|
nombreCurso: alumno.nombreCurso,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultado = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
errores.push({ alumno, error: resultado.error || "Error desconocido" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errores.length > 0) {
|
||||||
|
setMensajeDialogo(`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`);
|
||||||
|
} else {
|
||||||
|
setMensajeDialogo("Todos los alumnos fueron registrados correctamente.");
|
||||||
|
setArchivo(null);
|
||||||
|
setDatos([]);
|
||||||
|
}
|
||||||
|
setDialogoAbierto(true);
|
||||||
|
};
|
||||||
|
|
||||||
const manejarArchivo = (e) => {
|
const manejarArchivo = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (file && (file.name.endsWith(".csv") || file.name.endsWith(".xlsx"))) {
|
if (validarArchivo(file)) setArchivo(file);
|
||||||
setArchivo(file);
|
|
||||||
} else {
|
|
||||||
alert("Solo se permiten archivos .csv o .xlsx");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const manejarSoltar = (e) => {
|
const manejarSoltar = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const file = e.dataTransfer.files[0];
|
const file = e.dataTransfer.files[0];
|
||||||
if (file && (file.name.endsWith(".csv") || file.name.endsWith(".xlsx"))) {
|
if (validarArchivo(file)) setArchivo(file);
|
||||||
setArchivo(file);
|
|
||||||
} else {
|
|
||||||
alert("Solo se permiten archivos .csv o .xlsx");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const manejarArrastrar = (e) => e.preventDefault();
|
const manejarArrastrar = (e) => e.preventDefault();
|
||||||
|
|
||||||
|
const validarArchivo = (file) => {
|
||||||
|
if (file && (file.name.endsWith(".csv") || file.name.endsWith(".xlsx"))) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
setMensajeDialogo("Solo se permiten archivos .csv o .xlsx");
|
||||||
|
setDialogoAbierto(true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const extraerContenido = () => {
|
const extraerContenido = () => {
|
||||||
if (!archivo) return;
|
if (!archivo) return;
|
||||||
|
|
||||||
|
@ -39,7 +80,6 @@ export default function AlumnosArchivo() {
|
||||||
header: true,
|
header: true,
|
||||||
skipEmptyLines: true,
|
skipEmptyLines: true,
|
||||||
complete: (result) => {
|
complete: (result) => {
|
||||||
console.log("Contenido CSV:", result.data);
|
|
||||||
setDatos(result.data);
|
setDatos(result.data);
|
||||||
},
|
},
|
||||||
error: (error) => {
|
error: (error) => {
|
||||||
|
@ -55,7 +95,6 @@ export default function AlumnosArchivo() {
|
||||||
const contenido = XLSX.utils.sheet_to_json(workbook.Sheets[hoja], {
|
const contenido = XLSX.utils.sheet_to_json(workbook.Sheets[hoja], {
|
||||||
defval: "",
|
defval: "",
|
||||||
});
|
});
|
||||||
console.log("Contenido XLSX:", contenido);
|
|
||||||
setDatos(contenido);
|
setDatos(contenido);
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(archivo);
|
reader.readAsArrayBuffer(archivo);
|
||||||
|
@ -64,7 +103,7 @@ export default function AlumnosArchivo() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center">
|
<div className="w-[60vw] pt-10 flex flex-col items-end 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 p-8 font-sans text-center w-[70%] 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
|
||||||
|
@ -90,22 +129,47 @@ export default function AlumnosArchivo() {
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={extraerContenido}
|
onClick={registrarAlumnos}
|
||||||
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md"
|
className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-md mt-4"
|
||||||
>
|
>
|
||||||
Extraer contenido
|
Registrar alumnos
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{datos.length > 0 && (
|
{datos.length > 0 && (
|
||||||
<div className="mt-6 text-left w-full max-w-md">
|
<div className="mt-6 text-left w-full overflow-auto">
|
||||||
<h3 className="font-bold mb-2">Contenido extraído:</h3>
|
<h3 className="font-bold mb-2">Vista previa del archivo:</h3>
|
||||||
<pre className="bg-gray-100 p-2 rounded overflow-x-auto text-sm">
|
<table className="min-w-full bg-white border border-gray-300 text-sm">
|
||||||
{JSON.stringify(datos, null, 2)}
|
<thead className="bg-gray-100 text-gray-700">
|
||||||
</pre>
|
<tr>
|
||||||
|
{Object.keys(datos[0]).map((columna, index) => (
|
||||||
|
<th key={index} className="border px-4 py-2">{columna}</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{datos.map((fila, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
{Object.values(fila).map((valor, i) => (
|
||||||
|
<td key={i} className="border px-4 py-1">{valor}</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Dialog Component */}
|
||||||
|
<Dialog open={dialogoAbierto} onOpenChange={setDialogoAbierto}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-black">Información</DialogTitle>
|
||||||
|
<DialogDescription>{mensajeDialogo}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -20,7 +20,6 @@ import {
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
|
||||||
|
|
||||||
export default function AlumnosVista() {
|
export default function AlumnosVista() {
|
||||||
const [alumnos, setAlumnos] = useState([]);
|
const [alumnos, setAlumnos] = useState([]);
|
||||||
const [alumnoEditando, setAlumnoEditando] = useState(null);
|
const [alumnoEditando, setAlumnoEditando] = useState(null);
|
||||||
|
@ -28,10 +27,13 @@ export default function AlumnosVista() {
|
||||||
const [nuevoCorreo, setNuevoCorreo] = useState("");
|
const [nuevoCorreo, setNuevoCorreo] = useState("");
|
||||||
const [mostrarModal, setMostrarModal] = useState(false);
|
const [mostrarModal, setMostrarModal] = useState(false);
|
||||||
const [modalMensaje, setModalMensaje] = useState("");
|
const [modalMensaje, setModalMensaje] = useState("");
|
||||||
const [nuevoCurso, setNuevoCurso] = useState("");
|
const [nuevoCurso, setNuevoCurso] = useState("");
|
||||||
|
|
||||||
const [cursos, setCursos] = useState([]);
|
const [cursos, setCursos] = useState([]);
|
||||||
|
|
||||||
|
// Estado para confirmación de eliminación
|
||||||
|
const [confirmarEliminar, setConfirmarEliminar] = useState(false);
|
||||||
|
const [alumnoAEliminar, setAlumnoAEliminar] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cargarAlumnos();
|
cargarAlumnos();
|
||||||
cargarCursos();
|
cargarCursos();
|
||||||
|
@ -60,7 +62,7 @@ export default function AlumnosVista() {
|
||||||
setAlumnoEditando(alumno.id);
|
setAlumnoEditando(alumno.id);
|
||||||
setNuevoNombre(alumno.nombre);
|
setNuevoNombre(alumno.nombre);
|
||||||
setNuevoCorreo(alumno.correo);
|
setNuevoCorreo(alumno.correo);
|
||||||
setNuevoCurso(alumno.nombreCurso); // Asumiendo que tienes un campo cursoId en el alumno
|
setNuevoCurso(alumno.nombreCurso);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cancelar edición
|
// Cancelar edición
|
||||||
|
@ -78,7 +80,7 @@ export default function AlumnosVista() {
|
||||||
.update({
|
.update({
|
||||||
nombre: nuevoNombre,
|
nombre: nuevoNombre,
|
||||||
correo: nuevoCorreo,
|
correo: nuevoCorreo,
|
||||||
nombreCurso: nuevoCurso, // Asumiendo que tienes un campo cursoId en el alumno
|
nombreCurso: nuevoCurso,
|
||||||
})
|
})
|
||||||
.eq("id", id);
|
.eq("id", id);
|
||||||
|
|
||||||
|
@ -93,9 +95,15 @@ export default function AlumnosVista() {
|
||||||
setMostrarModal(true);
|
setMostrarModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Confirmar eliminación
|
||||||
|
const confirmarEliminacion = (id) => {
|
||||||
|
setAlumnoAEliminar(id);
|
||||||
|
setConfirmarEliminar(true);
|
||||||
|
};
|
||||||
|
|
||||||
// Eliminar alumno
|
// Eliminar alumno
|
||||||
const eliminarAlumno = async (id) => {
|
const eliminarAlumno = async () => {
|
||||||
const { error } = await supabaseClient.from("alumno").delete().eq("id", id);
|
const { error } = await supabaseClient.from("alumno").delete().eq("id", alumnoAEliminar);
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error eliminando alumno:", error.message);
|
console.error("Error eliminando alumno:", error.message);
|
||||||
setModalMensaje("Error al eliminar el alumno");
|
setModalMensaje("Error al eliminar el alumno");
|
||||||
|
@ -103,6 +111,7 @@ export default function AlumnosVista() {
|
||||||
setModalMensaje("Alumno eliminado exitosamente");
|
setModalMensaje("Alumno eliminado exitosamente");
|
||||||
await cargarAlumnos();
|
await cargarAlumnos();
|
||||||
}
|
}
|
||||||
|
setConfirmarEliminar(false);
|
||||||
setMostrarModal(true);
|
setMostrarModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -191,7 +200,7 @@ export default function AlumnosVista() {
|
||||||
|
|
||||||
<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 rounded"
|
||||||
onClick={() => eliminarAlumno(alumno.id)}
|
onClick={() => confirmarEliminacion(alumno.id)}
|
||||||
>
|
>
|
||||||
Eliminar
|
Eliminar
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -203,7 +212,33 @@ export default function AlumnosVista() {
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal de confirmación */}
|
||||||
|
<Dialog open={confirmarEliminar} onOpenChange={setConfirmarEliminar}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-black">Confirmar eliminación</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
¿Estás seguro de que deseas eliminar este alumno? Esta acción no se puede deshacer.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
className="bg-red-500 hover:bg-red-700 text-white"
|
||||||
|
onClick={eliminarAlumno}
|
||||||
|
>
|
||||||
|
Eliminar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="bg-gray-400 hover:bg-gray-600 text-white"
|
||||||
|
onClick={() => setConfirmarEliminar(false)}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Modal de resultado */}
|
||||||
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
|
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
@ -219,4 +254,4 @@ export default function AlumnosVista() {
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// pages/api/alumno.js
|
||||||
|
import { createClient } from "@/utils/supabase";
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
return res.status(405).json({ error: "Método no permitido" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const supabase = createClient({ req, res });
|
||||||
|
const { nombre, correo, nombreCurso } = req.body;
|
||||||
|
|
||||||
|
if (!nombre || !correo || !nombreCurso) {
|
||||||
|
return res.status(400).json({ error: "Faltan datos del alumno" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await supabase.from("alumno").insert([
|
||||||
|
{ nombre, correo, nombreCurso },
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return res.status(500).json({ error: "Error al insertar en Supabase", detalles: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ mensaje: "Alumno registrado", data });
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(500).json({ error: "Error interno del servidor", detalles: err.message });
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
// pages/api/curso.js
|
||||||
|
import { createClient } from "@/utils/supabase";
|
||||||
|
|
||||||
|
export default async function handler(req, res) {
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
return res.status(405).json({ error: "Método no permitido" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const supabase = createClient({ req, res });
|
||||||
|
const { nombre, horas, descripcion, competencias } = req.body;
|
||||||
|
|
||||||
|
if (!nombre || !horas || !descripcion || !competencias) {
|
||||||
|
return res.status(400).json({ error: "Faltan datos del curso" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from("curso")
|
||||||
|
.insert([{ nombre, horas, descripcion, competencias }]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return res.status(500).json({ error: "Error al insertar en Supabase", detalles: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({ mensaje: "Curso registrado", data });
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(500).json({ error: "Error interno del servidor", detalles: err.message });
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,80 +1,178 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import Papa from "papaparse";
|
import Papa from "papaparse";
|
||||||
|
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";
|
||||||
|
|
||||||
export default function CursosArchivo() {
|
export default function cursosArchivo() {
|
||||||
const [archivo, setArchivo] = useState(null);
|
const [archivo, setArchivo] = useState(null);
|
||||||
const [datosCSV, setDatosCSV] = useState([]);
|
const [datos, setDatos] = useState([]);
|
||||||
|
const [dialogoAbierto, setDialogoAbierto] = useState(false);
|
||||||
|
const [mensajeDialogo, setMensajeDialogo] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (archivo) extraerContenido();
|
||||||
|
}, [archivo]);
|
||||||
|
|
||||||
|
const registrarCursos = async () => {
|
||||||
|
if (datos.length === 0) return;
|
||||||
|
|
||||||
|
const errores = [];
|
||||||
|
|
||||||
|
for (const curso of datos) {
|
||||||
|
const res = await fetch("/api/curso", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
nombre: curso.nombre,
|
||||||
|
horas: curso.horas,
|
||||||
|
descripcion: curso.descripcion,
|
||||||
|
competencias: curso.competencias
|
||||||
|
? curso.competencias.split(",").map(c => c.trim())
|
||||||
|
: [],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const resultado = await res.json();
|
||||||
|
if (!res.ok) {
|
||||||
|
errores.push({ curso, error: resultado.error || "Error desconocido" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errores.length > 0) {
|
||||||
|
setMensajeDialogo(`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`);
|
||||||
|
} else {
|
||||||
|
setMensajeDialogo("Todos los cursos fueron registrados correctamente.");
|
||||||
|
setArchivo(null);
|
||||||
|
setDatos([]);
|
||||||
|
}
|
||||||
|
setDialogoAbierto(true);
|
||||||
|
};
|
||||||
|
|
||||||
const manejarArchivo = (e) => {
|
const manejarArchivo = (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
setArchivo(file);
|
if (validarArchivo(file)) setArchivo(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
const manejarSoltar = (e) => {
|
const manejarSoltar = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const file = e.dataTransfer.files[0];
|
const file = e.dataTransfer.files[0];
|
||||||
setArchivo(file);
|
if (validarArchivo(file)) setArchivo(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
const manejarArrastrar = (e) => {
|
const manejarArrastrar = (e) => e.preventDefault();
|
||||||
e.preventDefault();
|
|
||||||
|
const validarArchivo = (file) => {
|
||||||
|
if (file && (file.name.endsWith(".csv") || file.name.endsWith(".xlsx"))) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
setMensajeDialogo("Solo se permiten archivos .csv o .xlsx");
|
||||||
|
setDialogoAbierto(true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const extraerContenido = () => {
|
const extraerContenido = () => {
|
||||||
if (!archivo) return;
|
if (!archivo) return;
|
||||||
|
|
||||||
Papa.parse(archivo, {
|
const extension = archivo.name.split(".").pop().toLowerCase();
|
||||||
header: true,
|
|
||||||
skipEmptyLines: true,
|
if (extension === "csv") {
|
||||||
complete: (result) => {
|
Papa.parse(archivo, {
|
||||||
console.log("Contenido CSV:", result.data);
|
header: true,
|
||||||
setDatosCSV(result.data);
|
skipEmptyLines: true,
|
||||||
},
|
complete: (result) => {
|
||||||
error: (error) => {
|
setDatos(result.data);
|
||||||
console.error("Error al leer el CSV:", error.message);
|
},
|
||||||
},
|
error: (error) => {
|
||||||
});
|
console.error("Error al leer el CSV:", error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (extension === "xlsx") {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const data = new Uint8Array(e.target.result);
|
||||||
|
const workbook = XLSX.read(data, { type: "array" });
|
||||||
|
const hoja = workbook.SheetNames[0];
|
||||||
|
const contenido = XLSX.utils.sheet_to_json(workbook.Sheets[hoja], {
|
||||||
|
defval: "",
|
||||||
|
});
|
||||||
|
setDatos(contenido);
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(archivo);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<div className="w-[60vw] pt-10 flex flex-col items-end justify-center">
|
<div className="w-[60vw] pt-10 flex flex-col items-end 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 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>
|
<h1 className="text-xl font-semibold mb-4 text-black">
|
||||||
<h1
|
Nuevo curso
|
||||||
|
</h1>
|
||||||
|
<label
|
||||||
htmlFor="archivo"
|
htmlFor="archivo"
|
||||||
onDrop={manejarSoltar}
|
onDrop={manejarSoltar}
|
||||||
onDragOver={manejarArrastrar}
|
onDragOver={manejarArrastrar}
|
||||||
className="border-2 border-gray-300 rounded-md p-8 text-gray-600 cursor-pointer w-80 text-center mb-4"
|
className="border-2 border-gray-300 rounded-md p-8 text-gray-600 cursor-pointer w-80 text-center mb-4"
|
||||||
>
|
>
|
||||||
Arrastra y suelta un archivo o busca un archivo
|
{archivo ? (
|
||||||
|
<span className="text-black font-medium">{archivo.name}</span>
|
||||||
|
) : (
|
||||||
|
<span>Arrastra y suelta un archivo o haz clic para seleccionarlo</span>
|
||||||
|
)}
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
id="archivo"
|
id="archivo"
|
||||||
accept=".csv"
|
accept=".csv, .xlsx"
|
||||||
onChange={manejarArchivo}
|
onChange={manejarArchivo}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
/>
|
/>
|
||||||
</h1>
|
</label>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={extraerContenido}
|
onClick={registrarCursos}
|
||||||
className="bg-green-400 hover:bg-green-500 text-white font-bold py-2 px-4 rounded-md"
|
className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-md mt-4"
|
||||||
>
|
>
|
||||||
Extraer contenido
|
Registrar cursos
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{datosCSV.length > 0 && (
|
{datos.length > 0 && (
|
||||||
<div className="mt-6 text-left w-full max-w-md">
|
<div className="mt-6 text-left w-full overflow-auto">
|
||||||
<h3 className="font-bold mb-2">Contenido extraído:</h3>
|
<h3 className="font-bold mb-2">Vista previa del archivo:</h3>
|
||||||
<pre className="bg-gray-100 p-2 rounded overflow-x-auto text-sm">
|
<table className="min-w-full bg-white border border-gray-300 text-sm">
|
||||||
{JSON.stringify(datosCSV, null, 2)}
|
<thead className="bg-gray-100 text-gray-700">
|
||||||
</pre>
|
<tr>
|
||||||
|
{Object.keys(datos[0]).map((columna, index) => (
|
||||||
|
<th key={index} className="border px-4 py-2">{columna}</th>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{datos.map((fila, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
{Object.values(fila).map((valor, i) => (
|
||||||
|
<td key={i} className="border px-4 py-1">{valor}</td>
|
||||||
|
))}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Dialog Component */}
|
||||||
|
<Dialog open={dialogoAbierto} onOpenChange={setDialogoAbierto}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-black">Información</DialogTitle>
|
||||||
|
<DialogDescription>{mensajeDialogo}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -21,6 +21,10 @@ export default function CursosVista() {
|
||||||
const [mostrarModal, setMostrarModal] = useState(false);
|
const [mostrarModal, setMostrarModal] = useState(false);
|
||||||
const [modalMensaje, setModalMensaje] = useState("");
|
const [modalMensaje, setModalMensaje] = useState("");
|
||||||
|
|
||||||
|
// Estado para confirmación de eliminación
|
||||||
|
const [confirmarEliminar, setConfirmarEliminar] = useState(false);
|
||||||
|
const [cursoAEliminar, setCursoAEliminar] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
cargarCursos();
|
cargarCursos();
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -69,8 +73,13 @@ export default function CursosVista() {
|
||||||
setMostrarModal(true);
|
setMostrarModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const eliminarCurso = async (id) => {
|
const confirmarEliminacion = (id) => {
|
||||||
const { error } = await supabaseClient.from("curso").delete().eq("id", id);
|
setCursoAEliminar(id);
|
||||||
|
setConfirmarEliminar(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const eliminarCurso = async () => {
|
||||||
|
const { error } = await supabaseClient.from("curso").delete().eq("id", cursoAEliminar);
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error eliminando curso:", error.message);
|
console.error("Error eliminando curso:", error.message);
|
||||||
setModalMensaje("Error al eliminar el curso");
|
setModalMensaje("Error al eliminar el curso");
|
||||||
|
@ -78,6 +87,7 @@ export default function CursosVista() {
|
||||||
setModalMensaje("Curso eliminado exitosamente");
|
setModalMensaje("Curso eliminado exitosamente");
|
||||||
await cargarCursos();
|
await cargarCursos();
|
||||||
}
|
}
|
||||||
|
setConfirmarEliminar(false);
|
||||||
setMostrarModal(true);
|
setMostrarModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -149,7 +159,7 @@ export default function CursosVista() {
|
||||||
</Button>
|
</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 rounded"
|
||||||
onClick={() => eliminarCurso(curso.id)}
|
onClick={() => confirmarEliminacion(curso.id)}
|
||||||
>
|
>
|
||||||
Eliminar
|
Eliminar
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -161,7 +171,33 @@ export default function CursosVista() {
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal de confirmación */}
|
||||||
|
<Dialog open={confirmarEliminar} onOpenChange={setConfirmarEliminar}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-black">Confirmar eliminación</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
¿Estás seguro de que deseas eliminar este curso? Esta acción no se puede deshacer.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
className="bg-red-500 hover:bg-red-700 text-white"
|
||||||
|
onClick={eliminarCurso}
|
||||||
|
>
|
||||||
|
Eliminar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="bg-gray-400 hover:bg-gray-600 text-white"
|
||||||
|
onClick={() => setConfirmarEliminar(false)}
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Modal de resultado */}
|
||||||
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
|
<Dialog open={mostrarModal} onOpenChange={setMostrarModal}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
@ -177,4 +213,4 @@ export default function CursosVista() {
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
Reference in New Issue