Lm : plus de AJAX, plus de soucis de token, mais loader pas fou, du a l'app en elle meme
This commit is contained in:
parent
d6f06dfd7b
commit
0125e2ae69
|
|
@ -511,4 +511,50 @@ a.grille-garanties:hover{
|
||||||
|
|
||||||
@keyframes l13 {
|
@keyframes l13 {
|
||||||
100% { transform: rotate(1turn); }
|
100% { transform: rotate(1turn); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Message d'erreur du loader */
|
||||||
|
#error-message {
|
||||||
|
display: none;
|
||||||
|
color: #ff4444;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
white-space: pre-line;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Message de timeout du loader */
|
||||||
|
#timeout-message {
|
||||||
|
display: none;
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: center;
|
||||||
|
color: #d0d0d0;
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 420px;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 0 20px;
|
||||||
|
animation: fadeIn 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lien cliquable pour annuler le chargement */
|
||||||
|
#cancel-loading-link {
|
||||||
|
color: #66B2FF;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#cancel-loading-link:hover {
|
||||||
|
color: #90CAF9;
|
||||||
|
text-decoration-thickness: 2px;
|
||||||
|
text-shadow: 0 0 10px rgba(102, 178, 255, 0.6);
|
||||||
}
|
}
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
/* Overlay loader (css pris du site https://css-loaders.com/)*/
|
|
||||||
#loader-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0; left: 0;
|
|
||||||
width: 100%; height: 100%;
|
|
||||||
background: linear-gradient(
|
|
||||||
rgba(10, 20, 60, 0.2),
|
|
||||||
rgba(0, 0, 0, 0.4)
|
|
||||||
);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 9999;
|
|
||||||
font-family: 'Roboto', sans-serif;
|
|
||||||
opacity: 0;
|
|
||||||
backdrop-filter: blur(0px);
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 0.5s ease, backdrop-filter 0.5s ease;
|
|
||||||
}
|
|
||||||
#loader-overlay.active {
|
|
||||||
opacity: 1;
|
|
||||||
backdrop-filter: blur(3px);
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
#loader-overlay.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Spinner wrapper (fade/slide) */
|
|
||||||
.loader-spin-wrap {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(10px);
|
|
||||||
transition: opacity 0.5s ease, transform 0.5s ease;
|
|
||||||
transition-delay: 0.5s; /* apparaît après 0.5s */
|
|
||||||
}
|
|
||||||
#loader-overlay.active .loader-spin-wrap {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Spinner circulaire */
|
|
||||||
.loader-spin {
|
|
||||||
width: 50px;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
border-radius: 50%;
|
|
||||||
mask:1;
|
|
||||||
background:
|
|
||||||
radial-gradient(farthest-side,darkblue 94%,transparent) top/8px 8px no-repeat,
|
|
||||||
conic-gradient(transparent 30%,darkblue);
|
|
||||||
-webkit-mask: radial-gradient(farthest-side,transparent calc(100% - 8px),#000 0);
|
|
||||||
animation: l13 1s infinite linear;
|
|
||||||
}
|
|
||||||
@keyframes l13 {
|
|
||||||
100% { transform: rotate(1turn); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Erreur */
|
|
||||||
#error-message {
|
|
||||||
display: none;
|
|
||||||
color: red;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
|
|
@ -107,17 +107,38 @@ function isNullOrUndefined(value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonction pour éviter la duplication du code fetch
|
// Fonction pour éviter la duplication du code fetch
|
||||||
async function fetchWithJson(url, method, body = null) {
|
async function fetchWithJson(url, method, body = null, useLoader = true) {
|
||||||
const options = {
|
let loaderShown = false;
|
||||||
method: method,
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
if (useLoader && typeof showLoader === 'function') {
|
||||||
};
|
showLoader();
|
||||||
if (body) options.body = JSON.stringify(body);
|
loaderShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(url, options);
|
try {
|
||||||
if (!response.ok) throw new Error('Réseau ou erreur serveur');
|
const options = {
|
||||||
|
method: method,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
};
|
||||||
|
if (body) options.body = JSON.stringify(body);
|
||||||
|
|
||||||
return await response.json();
|
const response = await fetch(url, options);
|
||||||
|
if (!response.ok) throw new Error('Réseau ou erreur serveur');
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (loaderShown && typeof hideLoader === 'function') {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
// Toujours cacher le loader en cas d'erreur
|
||||||
|
if (loaderShown && typeof hideLoader === 'function') {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fonction de load du parcours en session storage
|
// Fonction de load du parcours en session storage
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,129 @@
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const loader = document.getElementById("loader-overlay"); // déjà présent dans le layout.ejs
|
|
||||||
const errorMessage = document.getElementById("error-message");
|
|
||||||
|
|
||||||
let activateTimeout = null;
|
let activateTimeout = null;
|
||||||
|
let timeoutWarning = null;
|
||||||
|
let currentPageLoad = null;
|
||||||
|
let safetyTimeout = null; // Timeout de sécurité pour éviter que le loader reste bloqué
|
||||||
|
|
||||||
//activer le loader et le montrer a l'écran
|
/**
|
||||||
|
* Active le loader et le rend visible
|
||||||
|
* Affiche un message d'annulation après 8 secondes
|
||||||
|
*/
|
||||||
window.showLoader = function() {
|
window.showLoader = function() {
|
||||||
|
const loader = document.getElementById("loader-overlay");
|
||||||
|
if (!loader) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||||
|
|
||||||
|
const timeoutMessage = document.getElementById("timeout-message");
|
||||||
|
const errorMessage = document.getElementById("error-message");
|
||||||
|
|
||||||
|
// Nettoyer les timeouts précédents
|
||||||
clearTimeout(activateTimeout);
|
clearTimeout(activateTimeout);
|
||||||
errorMessage.style.display = "none";
|
clearTimeout(timeoutWarning);
|
||||||
|
|
||||||
|
// Réinitialiser l'affichage
|
||||||
|
if (errorMessage) errorMessage.style.display = "none";
|
||||||
|
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||||
loader.classList.remove("hidden");
|
loader.classList.remove("hidden");
|
||||||
|
|
||||||
|
// Afficher le loader après 500ms (évite le flash sur les chargements rapides)
|
||||||
activateTimeout = setTimeout(() => {
|
activateTimeout = setTimeout(() => {
|
||||||
loader.classList.add("active");
|
if (loader && !loader.classList.contains("hidden")) {
|
||||||
|
loader.classList.add("active");
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Afficher le message de timeout après 8 secondes
|
||||||
|
timeoutWarning = setTimeout(() => {
|
||||||
|
if (loader && loader.classList.contains("active") && !loader.classList.contains("hidden")) {
|
||||||
|
if (timeoutMessage) {
|
||||||
|
timeoutMessage.style.display = "block";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 8000);
|
||||||
|
|
||||||
|
// Protection : cacher automatiquement le loader après 30 secondes maximum
|
||||||
|
// Au cas où hideLoader ne serait jamais appelé (page qui ne charge pas, erreur, etc.)
|
||||||
|
if (safetyTimeout) clearTimeout(safetyTimeout);
|
||||||
|
safetyTimeout = setTimeout(() => {
|
||||||
|
if (loader && !loader.classList.contains("hidden")) {
|
||||||
|
hideLoader();
|
||||||
|
}
|
||||||
|
}, 30000); // 30 secondes max
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Désactive et cache le loader
|
||||||
|
*/
|
||||||
|
window.hideLoader = function() {
|
||||||
|
const loader = document.getElementById("loader-overlay");
|
||||||
|
if (!loader) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||||
|
|
||||||
|
const timeoutMessage = document.getElementById("timeout-message");
|
||||||
|
|
||||||
|
clearTimeout(activateTimeout);
|
||||||
|
clearTimeout(timeoutWarning);
|
||||||
|
if (safetyTimeout) {
|
||||||
|
clearTimeout(safetyTimeout);
|
||||||
|
safetyTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
loader.classList.remove("active");
|
||||||
|
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (loader) {
|
||||||
|
loader.classList.add("hidden");
|
||||||
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
//enlever le loader et le faire disparaitre
|
/**
|
||||||
window.hideLoader = function() {
|
* Affiche un message d'erreur dans le loader
|
||||||
clearTimeout(activateTimeout);
|
* @param {string} msg - Message d'erreur à afficher
|
||||||
loader.classList.remove("active");
|
*/
|
||||||
setTimeout(() => loader.classList.add("hidden"), 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
//cas d'erreur
|
|
||||||
window.showError = function(msg) {
|
window.showError = function(msg) {
|
||||||
|
const errorMessage = document.getElementById("error-message");
|
||||||
|
const timeoutMessage = document.getElementById("timeout-message");
|
||||||
|
|
||||||
|
if (!errorMessage) return; // Protection : si l'élément n'existe pas, on ne fait rien
|
||||||
|
|
||||||
clearTimeout(activateTimeout);
|
clearTimeout(activateTimeout);
|
||||||
|
clearTimeout(timeoutWarning);
|
||||||
|
|
||||||
|
if (timeoutMessage) timeoutMessage.style.display = "none";
|
||||||
|
|
||||||
errorMessage.textContent = msg;
|
errorMessage.textContent = msg;
|
||||||
errorMessage.style.display = "block";
|
errorMessage.style.display = "block";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annule le chargement en cours et cache le loader
|
||||||
|
*/
|
||||||
|
const cancelLoadingLink = document.getElementById("cancel-loading-link");
|
||||||
|
if (cancelLoadingLink) {
|
||||||
|
cancelLoadingLink.addEventListener("click", () => {
|
||||||
|
// Annuler le chargement de la page en cours si possible
|
||||||
|
if (currentPageLoad && currentPageLoad.abort) {
|
||||||
|
currentPageLoad.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoader();
|
||||||
|
|
||||||
|
// Afficher une notification discrète
|
||||||
|
if (typeof M !== 'undefined' && M.toast) {
|
||||||
|
M.toast({
|
||||||
|
html: 'Chargement annulé',
|
||||||
|
classes: 'rounded grey darken-3',
|
||||||
|
displayLength: 2500
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permet de stocker l'abort controller du chargement en cours
|
||||||
|
* @param {AbortController} controller - Controller de la requête fetch
|
||||||
|
*/
|
||||||
|
window.setCurrentPageLoad = function(controller) {
|
||||||
|
currentPageLoad = controller;
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Affiche le loader lors des navigations entre pages (localhost)
|
||||||
|
* Comme on est en localhost, on peut se permettre d'afficher le loader
|
||||||
|
* au clic sur un lien, il disparaîtra automatiquement au chargement de la nouvelle page
|
||||||
|
*/
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const container = document.querySelector(".container");
|
|
||||||
|
// Intercepter les clics sur les liens internes pour afficher le loader
|
||||||
async function loadPage(url, push = true) {
|
|
||||||
showLoader();
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
const res = await fetch(url, { headers: { "X-Requested-With": "fetch" }});
|
|
||||||
const html = await res.text();
|
|
||||||
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const doc = parser.parseFromString(html, "text/html");
|
|
||||||
const newContent = doc.querySelector(".container").innerHTML;
|
|
||||||
|
|
||||||
container.innerHTML = newContent;
|
|
||||||
|
|
||||||
if (push) history.pushState({}, "", url);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (err) {
|
|
||||||
showError("Impossible de charger la page. Vérifiez votre connexion.");
|
|
||||||
}
|
|
||||||
|
|
||||||
finally {
|
|
||||||
hideLoader();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intercepter les clics sur les liens internes
|
|
||||||
document.body.addEventListener("click", (e) => {
|
document.body.addEventListener("click", (e) => {
|
||||||
const link = e.target.closest("a");
|
const link = e.target.closest("a");
|
||||||
|
|
||||||
if (link && link.getAttribute("href").startsWith("/")) {
|
// Si c'est un lien interne (commence par /) et pas un lien spécial
|
||||||
|
if (link &&
|
||||||
e.preventDefault();
|
link.getAttribute("href") &&
|
||||||
loadPage(link.href);
|
link.getAttribute("href").startsWith("/") &&
|
||||||
|
!link.classList.contains("modal-close") &&
|
||||||
|
!link.getAttribute("href").startsWith("/#")) {
|
||||||
|
|
||||||
|
// Afficher le loader avant la navigation
|
||||||
|
if (typeof showLoader === 'function') {
|
||||||
|
showLoader();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gérer le bouton retour
|
// Afficher le loader lors du rechargement (F5, Cmd+R)
|
||||||
window.addEventListener("popstate", () => {
|
window.addEventListener("beforeunload", () => {
|
||||||
|
if (typeof showLoader === 'function') {
|
||||||
|
showLoader();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
loadPage(location.pathname, false);
|
// Cacher le loader quand la page est complètement chargée
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
if (typeof hideLoader === 'function') {
|
||||||
|
// Petit délai pour éviter le flash
|
||||||
|
setTimeout(hideLoader, 100);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -25,8 +25,6 @@
|
||||||
<link rel="stylesheet" href="/css/global.css">
|
<link rel="stylesheet" href="/css/global.css">
|
||||||
<!-- DataTables CSS -->
|
<!-- DataTables CSS -->
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.8/css/dataTables.dataTables.css">
|
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/2.0.8/css/dataTables.dataTables.css">
|
||||||
<!-- Loader CSS : style pour l'affichage du loader -->
|
|
||||||
<link rel="stylesheet" href="/css/loader.css">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|
@ -37,7 +35,10 @@
|
||||||
<div class="loader-spin-wrap">
|
<div class="loader-spin-wrap">
|
||||||
<div class="loader-spin"></div>
|
<div class="loader-spin"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="error-message" class="helper-text error"></div>
|
<div id="error-message"></div>
|
||||||
|
<div id="timeout-message">
|
||||||
|
Si le chargement ne finit pas, <span id="cancel-loading-link">cliquez ici</span> pour annuler et vérifier votre connexion.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navbar -->
|
<!-- Navbar -->
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue