feat: version 2.16.0 -- contador de cierre en Contraseñas, cierre por inactividad y ajustes de Equipos

Contraseñas: el cierre automatico ahora es por inactividad (no saca al usuario a medio escribir),
token de desbloqueo renovable y contador visible junto al titulo. Equipos: GUID como componente.
Sube la version para que el panel de Actualizaciones ofrezca los cambios 2.15.0 -> 2.16.0
(migraciones 025 y 026, instalador del agente y pestaña Compartir).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Milton Gonzalez
2026-10-20 08:52:46 -05:00
parent d60b4b1d77
commit 4a305df73e
10 changed files with 88 additions and 23 deletions
@@ -0,0 +1,43 @@
import { useEffect, useRef, useState } from 'react';
import { Timer } from 'lucide-react';
import { Badge } from './Badge.jsx';
// Cuenta regresiva de cierre por INACTIVIDAD: cada tecla/clic/toque/rueda la reinicia y, al llegar
// a 0, llama onExpire (ej. salir del menu). Aislado en su propio componente para que el tic de cada
// segundo no vuelva a renderizar la pantalla que lo contiene.
const ACTIVITY_EVENTS = ['keydown', 'input', 'mousedown', 'touchstart', 'wheel'];
export function AutoLockCountdown({ ms, onExpire, title }) {
const deadline = useRef(Date.now() + ms);
const expired = useRef(false);
const onExpireRef = useRef(onExpire);
onExpireRef.current = onExpire;
const [left, setLeft] = useState(ms);
useEffect(() => {
const reset = () => { deadline.current = Date.now() + ms; };
ACTIVITY_EVENTS.forEach((e) => window.addEventListener(e, reset, true));
const tick = setInterval(() => {
const remaining = deadline.current - Date.now();
setLeft(Math.max(0, remaining));
if (remaining <= 0 && !expired.current) {
expired.current = true;
onExpireRef.current();
}
}, 1000);
return () => {
clearInterval(tick);
ACTIVITY_EVENTS.forEach((e) => window.removeEventListener(e, reset, true));
};
}, [ms]);
const secs = Math.ceil(left / 1000);
const label = `${Math.floor(secs / 60)}:${String(secs % 60).padStart(2, '0')}`;
return (
<span title={title} style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
<Badge color={secs <= 30 ? 'warning' : 'muted'}>
<Timer size={12} style={{ verticalAlign: '-2px', marginRight: 4 }} />{label}
</Badge>
</span>
);
}