322 lines
9.8 KiB
JavaScript
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);
|
|
}}
|
|
>
|
|
Sí, continuar
|
|
</Button>
|
|
<Button
|
|
className="bg-gray-400 hover:bg-gray-600 text-white"
|
|
onClick={() => setDialogoAdvertencia(false)}
|
|
>
|
|
Cancelar
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</Layout>
|
|
);
|
|
}
|