// ============================================================ // notifications.jsx — Campana de notificaciones (v2.5) // ============================================================ // Antes la campana solo marcaba todo como leído, sin enseñar nada: el // usuario que seguía una sección nunca veía los avisos de subidas, y se // perdían los de "Nuevo acceso detectado" y las alertas de descargas // masivas para los administradores. Además el contador solo se calculaba // al entrar. // // La usan el Acervo (dashboard.jsx) y Administración (admin.jsx): // // ============================================================ const NOTIF_POLL_MS = 120000; // contador: cada 2 min const NOTIF_ACTIVIDAD_MS = 10 * 60 * 1000; // …solo si hubo actividad en los últimos 10 min function fechaNotificacion(valor) { if (!valor) return ""; const d = new Date(String(valor).replace(" ", "T")); if (isNaN(d.getTime())) return ""; return d.toLocaleString("es-MX", { day: "2-digit", month: "short", hour: "2-digit", minute: "2-digit" }); } function NotificationsBell() { const [abierto, setAbierto] = React.useState(false); const [noLeidas, setNoLeidas] = React.useState(0); const [lista, setLista] = React.useState(null); // null = sin cargar const [cargando, setCargando] = React.useState(false); const [error, setError] = React.useState(null); const caja = React.useRef(null); const contar = React.useCallback(function () { return API.Notifications.list(true) .then(function (r) { setNoLeidas((r && r.unread_count) || 0); }) .catch(function () {}); }, []); // Contador: al montar, al volver a la pestaña y cada 2 minutos. Sin // actividad reciente no se consulta: cada consulta mantiene viva la sesión // en el servidor y no debe impedir el cierre por inactividad. React.useEffect(() => { contar(); const id = setInterval(function () { if (document.visibilityState !== "visible") return; let ultima = 0; try { ultima = parseInt(localStorage.getItem("bib_last_activity") || "0", 10) || 0; } catch (e) {} if (Date.now() - ultima > NOTIF_ACTIVIDAD_MS) return; contar(); }, NOTIF_POLL_MS); const alVolver = function () { if (document.visibilityState === "visible") contar(); }; document.addEventListener("visibilitychange", alVolver); return function () { clearInterval(id); document.removeEventListener("visibilitychange", alVolver); }; }, [contar]); const cargar = function () { setCargando(true); setError(null); API.Notifications.list(false) .then(function (r) { setLista((r && r.data) || []); setNoLeidas((r && r.unread_count) || 0); }) .catch(function (e) { setError((e && e.message) || "No se pudieron cargar las notificaciones."); }) .finally(function () { setCargando(false); }); }; const alternar = function () { const nuevo = !abierto; setAbierto(nuevo); if (nuevo) cargar(); }; // Cerrar con un clic fuera o con Escape React.useEffect(() => { if (!abierto) return; const fuera = function (e) { if (caja.current && !caja.current.contains(e.target)) setAbierto(false); }; const esc = function (e) { if (e.key === "Escape") setAbierto(false); }; document.addEventListener("mousedown", fuera); document.addEventListener("keydown", esc); return function () { document.removeEventListener("mousedown", fuera); document.removeEventListener("keydown", esc); }; }, [abierto]); const marcarLeida = function (n) { if (n.read) return; setLista(function (prev) { return (prev || []).map(function (x) { return x.id === n.id ? Object.assign({}, x, { read: true }) : x; }); }); setNoLeidas(function (c) { return Math.max(0, c - 1); }); API.Notifications.read(n.id).catch(function () { cargar(); }); }; const marcarTodas = function () { setLista(function (prev) { return (prev || []).map(function (x) { return Object.assign({}, x, { read: true }); }); }); setNoLeidas(0); API.Notifications.readAll().catch(function () { cargar(); }); }; const titulo = noLeidas > 0 ? noLeidas + (noLeidas === 1 ? " notificación sin leer" : " notificaciones sin leer") : "Notificaciones"; return (
{abierto && (
Notificaciones {noLeidas > 0 && ( )}
{cargando && lista === null && (
Cargando…
)} {error && (
{error}
)} {!error && lista && lista.length === 0 && (
No tienes notificaciones.
)} {!error && lista && lista.map(function (n) { return ( ); })}
{lista && lista.length >= 50 && (
Se muestran las 50 más recientes.
)}
)}
); } window.NotificationsBell = NotificationsBell;