feat(snapshots): overlay plein page pendant action async + beforeunload guard

- Overlay 'busy-overlay' plein ecran (z-index 9999) avec spinner anime
- Bloque toute interaction (clics) pendant recherche/suppression
- setBusy active l'overlay + change le texte du bouton
- clearBusy retire l'overlay
- beforeunload: avertit l'utilisateur s'il tente de quitter la page
  pendant une requete en vol (close tab, navigation, refresh)
This commit is contained in:
Pierre & Lumière 2026-05-07 20:40:13 +02:00
parent 77e884d620
commit 0a3fde36b7

View File

@ -18,8 +18,32 @@
.badge-age-warn { background: rgba(245,158,11,.20); color: #f59e0b; border: 1px solid #f59e0b; }
.badge-age-fresh { background: rgba(34,197,94,.20); color: #22c55e; border: 1px solid #22c55e; }
.filters-card label { display: flex; align-items: center; gap: 0.5rem; }
/* Overlay plein page pendant les actions async */
#busy-overlay {
position: fixed; inset: 0; z-index: 9999;
background: rgba(10,14,23,0.75); backdrop-filter: blur(2px);
display: none; align-items: center; justify-content: center;
flex-direction: column; gap: 1rem;
}
#busy-overlay.active { display: flex; }
.spinner {
width: 64px; height: 64px;
border: 4px solid rgba(245,158,11,0.2);
border-top-color: #f59e0b;
border-radius: 50%;
animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
#busy-text { color: #f59e0b; font-size: 1.05rem; font-weight: 600; text-shadow: 0 0 12px rgba(245,158,11,0.6); }
#busy-sub { color: #9ca3af; font-size: 0.85rem; }
</style>
<div id="busy-overlay">
<div class="spinner"></div>
<div id="busy-text">Action en cours…</div>
<div id="busy-sub">Ne ferme pas la page, ne navigue pas ailleurs.</div>
</div>
<div class="card p-4 mb-4 filters-card">
<div class="grid grid-cols-12 gap-3 items-end">
<div class="col-span-3">
@ -199,8 +223,19 @@
btnDelete.textContent = n > 0 ? `✕ SUPPRIMER ${n} SNAPSHOT${n>1?'S':''}` : '✕ SUPPRIMER LA SÉLECTION';
}
function setBusy(msg, btn) {
// Overlay plein page : bloque toute interaction pendant les actions async.
// Empêche aussi la navigation accidentelle via beforeunload.
const overlay = document.getElementById('busy-overlay');
const busyText = document.getElementById('busy-text');
const busySub = document.getElementById('busy-sub');
let busyActive = false;
function setBusy(msg, btn, sub) {
status.innerHTML = '<span class="text-cyber-yellow">⏳ ' + escapeHTML(msg) + '</span>';
busyText.textContent = '⏳ ' + msg;
busySub.textContent = sub || 'Ne ferme pas la page, ne navigue pas ailleurs.';
overlay.classList.add('active');
busyActive = true;
if (btn) {
btn.disabled = true;
btn._origText = btn._origText || btn.textContent;
@ -208,11 +243,21 @@
}
}
function clearBusy(btn) {
overlay.classList.remove('active');
busyActive = false;
if (btn && btn._origText) {
btn.disabled = false;
btn.textContent = btn._origText;
}
}
// Avertit si l'utilisateur tente de quitter la page pendant une action
window.addEventListener('beforeunload', (e) => {
if (busyActive) {
e.preventDefault();
e.returnValue = 'Une action est en cours. Quitter maintenant peut interrompre la requête.';
return e.returnValue;
}
});
btnRefresh.addEventListener('click', async () => {
setBusy('Recherche en cours… (peut prendre 10-30 s selon les vCenters)', btnRefresh);