Cambio en diseños

This commit is contained in:
evenegas
2025-07-01 17:15:01 -06:00
parent 3df7ef428b
commit 148c84483a
8 changed files with 197 additions and 92 deletions
+21
View File
@@ -0,0 +1,21 @@
import { jwtDecode } from "jwt-decode";
interface JwtPayload {
sub: string,
email: string,
tipo: string
}
export function getUserFromToken(): JwtPayload | null {
const token = localStorage.getItem("token");
if (!token) return null;
try {
const payload: JwtPayload = jwtDecode(token);
return payload;
} catch (error) {
console.error("Token inválido:", error);
return null;
}
}
+54 -14
View File
@@ -5,12 +5,23 @@ import Button from "./button";
const CargaArchivo: React.FC = () => {
const [archivo, setArchivo] = useState<File | null>(null);
const [errores, setErrores] = useState<string[] | null>(null);
const [loading, setLoading] = useState(false); // ← estado de carga
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) setArchivo(file);
};
const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
};
const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
const file = e.dataTransfer.files?.[0];
if (file) setArchivo(file);
};
const handleUpload = async () => {
if (!archivo) return;
@@ -23,6 +34,8 @@ const CargaArchivo: React.FC = () => {
const formData = new FormData();
formData.append("file", archivo);
setLoading(true); // ← empieza a subir
try {
const res = await fetch("https://venus.acatlan.unam.mx/servicios_pcpuma_test/excel/load", {
method: "POST",
@@ -40,7 +53,7 @@ const CargaArchivo: React.FC = () => {
);
setArchivo(null);
if (data.errors && data.errors.length > 0) {
setErrores(data.errors); // ← Mostrar ventana si hay errores
setErrores(data.errors);
}
} else {
console.error(data);
@@ -48,35 +61,62 @@ const CargaArchivo: React.FC = () => {
}
} catch (err) {
alert(`Error en la solicitud: ${err}`);
} finally {
setLoading(false); // ← termina de subir
}
};
return (
<div className="w-full relative">
<div className="w-full max-w-xl mx-auto">
<input
id="fileInput"
type="file"
accept=".xls,.xlsx"
onChange={handleChange}
className="mb-4 block text-sm"
style={{ display: "none" }}
/>
<Button onClick={handleUpload} disabled={!archivo} variant="azul">
Subir archivo
</Button>
<div
className="rounded p-5 text-center bg-white shadow-lg transition hover:shadow-xl"
style={{
border: "none",
cursor: "pointer",
}}
onDragOver={handleDragOver}
onDrop={handleDrop}
onClick={() => document.getElementById("fileInput")?.click()}
>
<p className="text-gray-600 mb-2">
Arrastra y suelta un archivo aquí o{" "}
<span className="text-blue-600 font-semibold">
haz clic para seleccionarlo
</span>.
</p>
<i className="bi bi-upload fs-1 text-primary"></i>
{archivo && (
<p className="my-0 text-green-600 font-semibold">
Archivo seleccionado: {archivo.name}
</p>
)}
</div>
<div className="mt-4 text-center">
<Button onClick={handleUpload} disabled={!archivo || loading} variant="azul">
{loading ? "Subiendo..." : "Subir archivo"}
</Button>
</div>
{/* Modal de errores */}
{errores && (
<div className="">
<div className="mt-6 p-4 bg-red-100 border border-red-300 rounded">
<div className="flex justify-between items-center mb-2">
<h2 className="text-xl font-semibold mt-6 mb-4">Reporte de Errores</h2>
<Button
variant="danger"
onClick={() => setErrores(null)}
className="text-sm"
>
<h2 className="text-lg font-semibold text-red-800">Errores detectados</h2>
<Button variant="danger" onClick={() => setErrores(null)} className="text-sm">
<i className="fas fa-times"></i>
</Button>
</div>
<ul className="list-disc list-inside text-sm max-h-60 overflow-y-auto">
<ul className="list-disc list-inside text-sm max-h-60 overflow-y-auto text-red-700">
{errores.map((error, index) => (
<li key={index}>{error}</li>
))}
+28 -12
View File
@@ -1,13 +1,22 @@
"use client";
import React from "react";
import Button from "./button";
type FuenteCardProps = {
nombre: string;
fecha: string;
activa: boolean;
id_mov: number;
onValidated?: () => void;
};
const FuenteCard: React.FC<FuenteCardProps> = ({ nombre, fecha, activa, id_mov }) => {
const FuenteCard: React.FC<FuenteCardProps> = ({
nombre,
fecha,
activa,
id_mov,
onValidated,
}) => {
const handleActivar = async () => {
const token = localStorage.getItem("token");
if (!token) {
@@ -16,17 +25,21 @@ const FuenteCard: React.FC<FuenteCardProps> = ({ nombre, fecha, activa, id_mov }
}
try {
const res = await fetch(`https://venus.acatlan.unam.mx/servicios_pcpuma_test/excel/activar-servicios/${id_mov}`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
});
const res = await fetch(
`https://venus.acatlan.unam.mx/servicios_pcpuma_test/excel/activar-servicios/${id_mov}`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
}
);
const data = await res.json();
if (res.ok) {
alert("Usuarios validados correctamente");
onValidated?.(); // ← Recargar fuentes
} else {
alert(`Error: ${data.message || "No se pudo validar"}`);
}
@@ -40,21 +53,24 @@ const FuenteCard: React.FC<FuenteCardProps> = ({ nombre, fecha, activa, id_mov }
<div className="flex items-center justify-between mb-2">
<span className="font-semibold text-sm">{nombre}</span>
<span
className={`w-3 h-3 rounded-full ${activa ? "bg-green-500" : "bg-red-500"}`}
className={`w-3 h-3 rounded-full ${activa ? "bg-green-500" : "bg-red-500"
}`}
/>
</div>
<p className="text-xs text-gray-700 leading-tight">
Fecha de carga:<br />
Fecha de carga:
<br />
{fecha}
</p>
{activa && (
<button
<Button
onClick={handleActivar}
className="mt-3 px-3 py-1 btn btn-success btn-sm rounded-pill"
className="mb-3 px-3 py-1 mt-3"
variant="success"
>
Validar
</button>
</Button>
)}
</div>
);