SIDAC/diplomas/src/pages/inyeccionesArchivo.jsx

322 lines
9.8 KiB
JavaScript

import React, { useState, useEffect } from "react";
import Papa from "papaparse";
import * as XLSX from "xlsx";
import Layout from "@/components/layout/Layout";
import { Button } from "@/components/ui/button";
import { supabaseClient } from "@/utils/supabase";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import { useRouter } from "next/router";
export default function InyeccionesArchivo() {
const [archivo, setArchivo] = useState(null);
const [datos, setDatos] = useState([]);
const [dialogoAbierto, setDialogoAbierto] = useState(false);
const [mensajeDialogo, setMensajeDialogo] = useState("");
const [dialogoCargando, setDialogoCargando] = useState(false);
const [dialogoAdvertencia, setDialogoAdvertencia] = useState(false);
const [rutaPendiente, setRutaPendiente] = useState(null);
const router = useRouter();
useEffect(() => {
if (archivo) extraerContenido();
// eslint-disable-next-line
}, [archivo]);
useEffect(() => {
const handleRouteChange = (url) => {
if (archivo && datos.length > 0) {
setDialogoAdvertencia(true);
setRutaPendiente(url);
throw "Bloqueo de navegación por archivo pendiente";
}
};
router.events.on("routeChangeStart", handleRouteChange);
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, [archivo, datos, router]);
const registrarInyecciones = async () => {
if (datos.length === 0) return;
setDialogoCargando(true);
const errores = [];
for (const inyeccion of datos) {
// 1. Procesar competencias (si existen)
let competenciasIds = [];
if (inyeccion.competencias) {
const competenciasArr = inyeccion.competencias
.split(",")
.map((c) => c.trim())
.filter(Boolean);
for (const desc of competenciasArr) {
// Buscar si ya existe la competencia
let { data: existente } = await supabaseClient
.from("competencia_inyeccion")
.select("id")
.eq("descripcion", desc)
.maybeSingle();
let compId = existente?.id;
if (!compId) {
// Insertar si no existe
const { data: insertada, error: errorInsert } = await supabaseClient
.from("competencia_inyeccion")
.insert([{ descripcion: desc }])
.select("id")
.single();
if (errorInsert) {
errores.push({
inyeccion,
error: `Error insertando competencia "${desc}": ${errorInsert.message}`,
});
continue;
}
compId = insertada.id;
}
competenciasIds.push(compId);
}
}
// 2. Insertar la inyección
const { data: inyeccionInsertada, error: errorIny } = await supabaseClient
.from("inyeccion")
.insert([
{
nombre: inyeccion.nombre,
horas: inyeccion.horas,
descripcion: inyeccion.descripcion,
},
])
.select("id")
.single();
if (errorIny) {
errores.push({
inyeccion,
error: errorIny.message || "Error desconocido al insertar inyección",
});
continue;
}
// 3. Relacionar competencias con la inyección
if (inyeccionInsertada && competenciasIds.length > 0) {
const relaciones = competenciasIds.map((cid) => ({
inyeccion_id: inyeccionInsertada.id,
competencia_inyeccion_id: cid,
}));
const { error: errorRel } = await supabaseClient
.from("inyeccion_competencia_inyeccion")
.insert(relaciones);
if (errorRel) {
errores.push({
inyeccion,
error: `Error relacionando competencias: ${errorRel.message}`,
});
}
}
}
setDialogoCargando(false);
if (errores.length > 0) {
setMensajeDialogo(
`Se registraron algunos errores:\n${JSON.stringify(errores, null, 2)}`
);
} else {
setMensajeDialogo(
"Todas las inyecciones fueron registradas correctamente."
);
setArchivo(null);
setDatos([]);
}
setDialogoAbierto(true);
};
const manejarArchivo = (e) => {
const file = e.target.files[0];
if (validarArchivo(file)) setArchivo(file);
};
const manejarSoltar = (e) => {
e.preventDefault();
const file = e.dataTransfer.files[0];
if (validarArchivo(file)) setArchivo(file);
};
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 = () => {
if (!archivo) return;
const extension = archivo.name.split(".").pop().toLowerCase();
if (extension === "csv") {
Papa.parse(archivo, {
header: true,
skipEmptyLines: true,
complete: (result) => {
setDatos(result.data);
},
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 (
<Layout>
<div className="w-full pt-10 flex flex-col items-start md:items-center md:justify-center text-black">
<div className="bg-white font-sans text-center w-full flex flex-col items-center">
<h1 className="text-xl font-semibold mb-4 text-black">
Nueva inyección (archivo)
</h1>
<label
htmlFor="archivo"
onDrop={manejarSoltar}
onDragOver={manejarArrastrar}
className="border-2 border-gray-300 rounded-md p-8 text-gray-600 cursor-pointer w-80 text-center mb-4"
>
{archivo ? (
<span className="text-black font-medium">{archivo.name}</span>
) : (
<span>
Arrastra y suelta un archivo o haz clic para seleccionarlo
</span>
)}
<input
type="file"
id="archivo"
accept=".csv, .xlsx"
onChange={manejarArchivo}
className="hidden"
/>
</label>
<Button
onClick={registrarInyecciones}
className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-md mt-4"
>
Registrar inyecciones
</Button>
{datos.length > 0 && (
<div className="mt-6 text-left w-full overflow-auto">
<h3 className="font-bold mb-2">Vista previa del archivo:</h3>
<table className="min-w-full bg-white border border-gray-300 text-sm">
<thead className="bg-gray-100 text-gray-700">
<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>
{/* Dialog Component */}
<Dialog open={dialogoAbierto} onOpenChange={setDialogoAbierto}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Información</DialogTitle>
<DialogDescription>{mensajeDialogo}</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
<Dialog open={dialogoCargando}>
<DialogContent>
<DialogHeader>
<DialogTitle className="text-black">Cargando...</DialogTitle>
<DialogDescription>
Por favor espera, se están registrando las inyecciones.
</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>
);
}