Añadir frontend/src/components/IndicadorActualizacion.jsx
This commit is contained in:
97
frontend/src/components/IndicadorActualizacion.jsx
Normal file
97
frontend/src/components/IndicadorActualizacion.jsx
Normal file
@@ -0,0 +1,97 @@
|
||||
// src/components/IndicadorActualizacion.jsx
|
||||
// Indicador (arriba-derecha) con la hora de la última actualización + botón de
|
||||
// recarga. Al pulsar: re-lee las bases para la vista actual y recachea el resto.
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { api } from "../lib/api";
|
||||
import { getVistaActual, emitirRefresco } from "../lib/vistaActual";
|
||||
|
||||
function formatearHora(iso) {
|
||||
if (!iso) return "—";
|
||||
try {
|
||||
const d = new Date(iso);
|
||||
return d.toLocaleString("es-PE", {
|
||||
day: "2-digit", month: "2-digit",
|
||||
hour: "2-digit", minute: "2-digit",
|
||||
});
|
||||
} catch {
|
||||
return "—";
|
||||
}
|
||||
}
|
||||
|
||||
export default function IndicadorActualizacion() {
|
||||
const [hora, setHora] = useState(null);
|
||||
const [cargando, setCargando] = useState(false);
|
||||
|
||||
const consultar = useCallback(async () => {
|
||||
try {
|
||||
const r = await api.ultimaActualizacion();
|
||||
setHora(r?.hora || null);
|
||||
} catch { /* silencioso */ }
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
consultar();
|
||||
const id = setInterval(consultar, 60000);
|
||||
return () => clearInterval(id);
|
||||
}, [consultar]);
|
||||
|
||||
async function recargar() {
|
||||
if (cargando) return;
|
||||
setCargando(true);
|
||||
try {
|
||||
const r = await api.cacheRefreshManual(getVistaActual());
|
||||
setHora(r?.hora || null);
|
||||
emitirRefresco();
|
||||
} catch { /* silencioso */ }
|
||||
finally { setCargando(false); }
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<style>{`
|
||||
@keyframes spin-actualiza { to { transform: rotate(360deg); } }
|
||||
.ind-actualiza-btn { transition: color .15s, transform .15s; }
|
||||
.ind-actualiza-btn:hover:not(:disabled) { color:#7dd3fc; transform: scale(1.15); }
|
||||
.ind-actualiza-spin { animation: spin-actualiza .9s linear infinite; }
|
||||
`}</style>
|
||||
<div
|
||||
style={{
|
||||
position: "fixed", right: 16, top: 14, zIndex: 60,
|
||||
display: "flex", alignItems: "center", gap: 8,
|
||||
background: "rgba(15,23,42,0.9)",
|
||||
color: cargando ? "#7dd3fc" : "#cbd5e1",
|
||||
border: "1px solid " + (cargando ? "#0ea5e9" : "#334155"),
|
||||
borderRadius: 20, padding: "6px 12px",
|
||||
fontSize: 12, lineHeight: 1, fontWeight: 500,
|
||||
boxShadow: "0 2px 8px rgba(0,0,0,0.28)",
|
||||
backdropFilter: "blur(4px)",
|
||||
userSelect: "none",
|
||||
}}
|
||||
title="Última actualización de las bases de datos"
|
||||
>
|
||||
<span style={{ opacity: cargando ? 1 : 0.85 }}>
|
||||
{cargando ? "Actualizando…" : formatearHora(hora)}
|
||||
</span>
|
||||
<button
|
||||
onClick={recargar}
|
||||
disabled={cargando}
|
||||
className={"ind-actualiza-btn" + (cargando ? " ind-actualiza-spin" : "")}
|
||||
title={cargando ? "Actualizando datos…" : "Actualizar ahora"}
|
||||
style={{
|
||||
background: "transparent", border: "none",
|
||||
cursor: cargando ? "default" : "pointer",
|
||||
color: "#38bdf8", fontSize: 15, padding: 0, lineHeight: 1,
|
||||
display: "flex", alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2.4"
|
||||
strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M21 12a9 9 0 1 1-2.64-6.36" />
|
||||
<path d="M21 3v6h-6" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user