Bug de rafraichissement de données
This commit is contained in:
+13
-1
@@ -23,8 +23,20 @@ server {
|
||||
client_max_body_size 15M;
|
||||
}
|
||||
|
||||
# SPA fallback
|
||||
# site.webmanifest — jamais mis en cache (sinon iOS garde l'ancienne
|
||||
# config "Add to Home Screen" après un déploiement)
|
||||
location = /site.webmanifest {
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
}
|
||||
|
||||
# SPA fallback — index.html ne doit JAMAIS être mis en cache : la webapp
|
||||
# installée sur l'écran d'accueil iPad (mode standalone) n'a ni bouton
|
||||
# reload ni pull-to-refresh, donc si index.html reste en cache le
|
||||
# WKWebView continue de charger l'ancien bundle après un déploiement.
|
||||
location / {
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
add_header Pragma "no-cache";
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,26 @@ export default function App() {
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
|
||||
// Webapp installée sur l'écran d'accueil (iPad) : mode standalone, donc pas
|
||||
// de bouton reload ni de pull-to-refresh natif. Si l'app est restée en
|
||||
// arrière-plan un moment (cas typique : on la rouvre depuis l'icône des
|
||||
// heures/jours après un déploiement serveur), on force un reload complet
|
||||
// au retour au premier plan pour repartir sur un bundle et des données à jour.
|
||||
useEffect(() => {
|
||||
let hiddenAt = null;
|
||||
const onVisibility = () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
hiddenAt = Date.now();
|
||||
} else if (document.visibilityState === 'visible' && hiddenAt) {
|
||||
const awayMs = Date.now() - hiddenAt;
|
||||
hiddenAt = null;
|
||||
if (awayMs > 2 * 60 * 1000) window.location.reload();
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', onVisibility);
|
||||
return () => document.removeEventListener('visibilitychange', onVisibility);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/login" element={<Login />} />
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ export const api = {
|
||||
const qs = params ? '?' + new URLSearchParams(
|
||||
Object.entries(params).filter(([, v]) => v !== undefined && v !== null && v !== '')
|
||||
).toString() : '';
|
||||
return fetch(BASE + path + qs, { headers: authHeaders() }).then(handle);
|
||||
return fetch(BASE + path + qs, { headers: authHeaders(), cache: 'no-store' }).then(handle);
|
||||
},
|
||||
post: (path, body) =>
|
||||
fetch(BASE + path, {
|
||||
@@ -65,7 +65,7 @@ export const api = {
|
||||
postForm: (path, formData) =>
|
||||
fetch(BASE + path, { method: 'POST', body: formData, headers: authHeaders() }).then(handle),
|
||||
blob: (path) =>
|
||||
fetch(BASE + path, { headers: authHeaders() }).then(async res => {
|
||||
fetch(BASE + path, { headers: authHeaders(), cache: 'no-store' }).then(async res => {
|
||||
if (!res.ok) { const t = await res.text(); throw new Error(t || res.statusText); }
|
||||
return res.blob();
|
||||
}),
|
||||
|
||||
@@ -25,6 +25,9 @@ function IconAide() {
|
||||
function IconComm() {
|
||||
return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>;
|
||||
}
|
||||
function IconRefresh() {
|
||||
return <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>;
|
||||
}
|
||||
function IconChevronRight() {
|
||||
return <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M9 18l6-6-6-6"/></svg>;
|
||||
}
|
||||
@@ -192,6 +195,17 @@ export default function UserMenu() {
|
||||
const go = (path) => { closeMenu(); navigate(path); };
|
||||
const handleLogout = () => { closeMenu(); logout(); navigate('/login'); };
|
||||
|
||||
// En mode standalone (webapp ajoutée à l'écran d'accueil iPad), il n'y a
|
||||
// ni bouton reload ni pull-to-refresh natif. On force donc une vraie
|
||||
// navigation réseau (pas juste reload()) avec un paramètre anti-cache,
|
||||
// pour être certain de récupérer l'index.html et les données à jour.
|
||||
const handleForceRefresh = () => {
|
||||
closeMenu();
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('_r', Date.now().toString());
|
||||
window.location.replace(url.toString());
|
||||
};
|
||||
|
||||
const selectView = (v) => {
|
||||
setActiveView(v);
|
||||
closeMenu();
|
||||
@@ -268,6 +282,9 @@ export default function UserMenu() {
|
||||
<button className="user-menu-item" role="menuitem" onClick={() => go('/aide')}>
|
||||
<IconAide /> Aide
|
||||
</button>
|
||||
<button className="user-menu-item" role="menuitem" onClick={handleForceRefresh}>
|
||||
<IconRefresh /> Recharger l'application
|
||||
</button>
|
||||
|
||||
<div className="user-menu-sep" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user