test
This commit is contained in:
parent
667d3199c9
commit
36a741ab14
|
|
@ -1,132 +1,245 @@
|
||||||
// Enregistrement du Service Worker
|
// Enregistrement du Service Worker amélioré
|
||||||
if ('serviceWorker' in navigator) {
|
(function() {
|
||||||
window.addEventListener('load', function() {
|
// Vérifier si le Service Worker est supporté
|
||||||
// S'assurer que nous sommes sur HTTPS en production
|
if (!('serviceWorker' in navigator)) {
|
||||||
if (location.protocol === 'https:' || location.hostname === 'localhost') {
|
console.warn('[App] Service Worker non supporté');
|
||||||
navigator.serviceWorker.register('/service-worker.js')
|
return;
|
||||||
.then(function(registration) {
|
}
|
||||||
console.log('[Service Worker] Enregistré avec succès:', registration.scope);
|
|
||||||
|
// Vérifier si nous sommes en HTTPS ou localhost
|
||||||
|
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
|
||||||
|
console.warn('[App] Service Worker nécessite HTTPS en production');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction principale d'enregistrement
|
||||||
|
function registerServiceWorker() {
|
||||||
|
const swUrl = '/service-worker.js?v=1.1';
|
||||||
|
|
||||||
|
navigator.serviceWorker.register(swUrl)
|
||||||
|
.then(function(registration) {
|
||||||
|
console.log('[Service Worker] Enregistré avec succès:', registration.scope);
|
||||||
|
|
||||||
|
// Vérifier si une mise à jour est disponible
|
||||||
|
registration.addEventListener('updatefound', function() {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
console.log('[Service Worker] Mise à jour trouvée:', installingWorker.state);
|
||||||
|
|
||||||
// Vérifier les mises à jour
|
installingWorker.addEventListener('statechange', function() {
|
||||||
registration.addEventListener('updatefound', () => {
|
console.log('[Service Worker] Nouvel état:', this.state);
|
||||||
const newWorker = registration.installing;
|
|
||||||
console.log('[Service Worker] Mise à jour trouvée:', newWorker.state);
|
|
||||||
|
|
||||||
newWorker.addEventListener('statechange', () => {
|
if (this.state === 'installed' && navigator.serviceWorker.controller) {
|
||||||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
// Nouvelle version disponible
|
||||||
// Nouvelle version disponible
|
showUpdateNotification();
|
||||||
console.log('[Service Worker] Nouvelle version disponible');
|
}
|
||||||
this.showUpdateNotification();
|
|
||||||
}
|
if (this.state === 'activated') {
|
||||||
});
|
console.log('[Service Worker] Nouveau Service Worker activé');
|
||||||
|
// Notifier tous les clients
|
||||||
|
sendMessageToSW({ type: 'NEW_VERSION_ACTIVATED' });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
console.error('[Service Worker] Échec de l\'enregistrement:', error);
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
// Vérifier l'état du Service Worker périodiquement
|
||||||
|
setInterval(() => {
|
||||||
// Gestion hors ligne
|
registration.update().catch(err => {
|
||||||
window.addEventListener('online', () => {
|
console.debug('[SW] Pas de mise à jour disponible:', err);
|
||||||
document.documentElement.classList.remove('offline');
|
});
|
||||||
console.log('[App] Connexion rétablie');
|
}, 60 * 60 * 1000); // Toutes les heures
|
||||||
this.showOnlineNotification();
|
|
||||||
});
|
return registration;
|
||||||
|
})
|
||||||
window.addEventListener('offline', () => {
|
.catch(function(error) {
|
||||||
document.documentElement.classList.add('offline');
|
console.error('[Service Worker] Erreur d\'enregistrement:', error);
|
||||||
console.log('[App] Mode hors ligne');
|
|
||||||
this.showOfflineNotification();
|
// Tentative de récupération
|
||||||
});
|
if (error.name === 'SecurityError') {
|
||||||
}
|
console.warn('[SW] Erreur de sécurité - Vérifiez HTTPS');
|
||||||
|
} else if (error.name === 'TypeError') {
|
||||||
// Notification de mise à jour
|
console.warn('[SW] Erreur de type - Vérifiez le chemin du SW');
|
||||||
function showUpdateNotification() {
|
}
|
||||||
if (window.appConfig?.isAnglophone) {
|
|
||||||
if (confirm('A new version is available. Reload the page?')) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (confirm('Une nouvelle version est disponible. Recharger la page?')) {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification en ligne
|
|
||||||
function showOnlineNotification() {
|
|
||||||
// Créer une notification toast
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.className = 'position-fixed top-0 end-0 p-3';
|
|
||||||
toast.style.zIndex = '9999';
|
|
||||||
|
|
||||||
toast.innerHTML = `
|
|
||||||
<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
|
|
||||||
<div class="toast-header bg-success text-white">
|
|
||||||
<i class="bi bi-wifi me-2"></i>
|
|
||||||
<strong class="me-auto">${window.appConfig?.isAnglophone ? 'Online' : 'En ligne'}</strong>
|
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body">
|
|
||||||
${window.appConfig?.isAnglophone
|
|
||||||
? 'Connection restored. Synchronization in progress...'
|
|
||||||
: 'Connexion rétablie. Synchronisation en cours...'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
// Supprimer après 3 secondes
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.remove();
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notification hors ligne
|
|
||||||
function showOfflineNotification() {
|
|
||||||
// Créer une notification toast
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.className = 'position-fixed top-0 end-0 p-3';
|
|
||||||
toast.style.zIndex = '9999';
|
|
||||||
|
|
||||||
toast.innerHTML = `
|
|
||||||
<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
|
|
||||||
<div class="toast-header bg-warning text-dark">
|
|
||||||
<i class="bi bi-wifi-off me-2"></i>
|
|
||||||
<strong class="me-auto">${window.appConfig?.isAnglophone ? 'Offline' : 'Hors ligne'}</strong>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body">
|
|
||||||
${window.appConfig?.isAnglophone
|
|
||||||
? 'No internet connection. Working in offline mode.'
|
|
||||||
: 'Pas de connexion Internet. Mode hors ligne actif.'}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
// Supprimer après 5 secondes
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.remove();
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fonction pour forcer la mise à jour du Service Worker
|
|
||||||
function updateServiceWorker() {
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
navigator.serviceWorker.ready.then(registration => {
|
|
||||||
registration.update().then(() => {
|
|
||||||
console.log('[Service Worker] Mise à jour forcée');
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Gestion de l'état de connexion
|
||||||
// Exporter pour une utilisation externe
|
function setupConnectionHandlers() {
|
||||||
window.serviceWorker = {
|
window.addEventListener('online', function() {
|
||||||
update: updateServiceWorker,
|
console.log('[App] Connexion rétablie');
|
||||||
isSupported: 'serviceWorker' in navigator
|
document.documentElement.classList.remove('offline');
|
||||||
};
|
showOnlineNotification();
|
||||||
|
|
||||||
|
// Synchroniser les données
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
sendMessageToSW({ type: 'SYNC_DATA' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('offline', function() {
|
||||||
|
console.log('[App] Mode hors ligne');
|
||||||
|
document.documentElement.classList.add('offline');
|
||||||
|
showOfflineNotification();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vérifier l'état initial
|
||||||
|
if (!navigator.onLine) {
|
||||||
|
document.documentElement.classList.add('offline');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification de mise à jour
|
||||||
|
function showUpdateNotification() {
|
||||||
|
const isAnglophone = window.appConfig?.isAnglophone || false;
|
||||||
|
|
||||||
|
// Utiliser SweetAlert2 si disponible
|
||||||
|
if (typeof Swal !== 'undefined') {
|
||||||
|
Swal.fire({
|
||||||
|
title: isAnglophone ? 'Update Available' : 'Mise à jour disponible',
|
||||||
|
text: isAnglophone
|
||||||
|
? 'A new version is available. Reload to update?'
|
||||||
|
: 'Une nouvelle version est disponible. Recharger pour mettre à jour ?',
|
||||||
|
icon: 'info',
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonText: isAnglophone ? 'Reload' : 'Recharger',
|
||||||
|
cancelButtonText: isAnglophone ? 'Later' : 'Plus tard',
|
||||||
|
allowOutsideClick: false
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback simple
|
||||||
|
if (confirm(isAnglophone
|
||||||
|
? 'New version available. Reload?'
|
||||||
|
: 'Nouvelle version disponible. Recharger ?')) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification en ligne
|
||||||
|
function showOnlineNotification() {
|
||||||
|
// Créer un toast temporaire
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'position-fixed top-0 end-0 p-3';
|
||||||
|
toast.style.zIndex = '9999';
|
||||||
|
|
||||||
|
const isAnglophone = window.appConfig?.isAnglophone || false;
|
||||||
|
|
||||||
|
toast.innerHTML = `
|
||||||
|
<div class="toast show" role="alert" aria-live="polite" aria-atomic="true">
|
||||||
|
<div class="toast-header bg-success text-white">
|
||||||
|
<i class="bi bi-wifi me-2"></i>
|
||||||
|
<strong class="me-auto">${isAnglophone ? 'Online' : 'En ligne'}</strong>
|
||||||
|
<button type="button" class="btn-close btn-close-white ms-2"
|
||||||
|
onclick="this.closest('.toast').remove()"
|
||||||
|
aria-label="${isAnglophone ? 'Close' : 'Fermer'}"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
${isAnglophone
|
||||||
|
? 'Connection restored. Data will be synchronized.'
|
||||||
|
: 'Connexion rétablie. Les données seront synchronisées.'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// Auto-remove après 3 secondes
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification hors ligne
|
||||||
|
function showOfflineNotification() {
|
||||||
|
// Créer un toast temporaire
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'position-fixed top-0 end-0 p-3';
|
||||||
|
toast.style.zIndex = '9999';
|
||||||
|
|
||||||
|
const isAnglophone = window.appConfig?.isAnglophone || false;
|
||||||
|
|
||||||
|
toast.innerHTML = `
|
||||||
|
<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
|
<div class="toast-header bg-warning text-dark">
|
||||||
|
<i class="bi bi-wifi-off me-2"></i>
|
||||||
|
<strong class="me-auto">${isAnglophone ? 'Offline' : 'Hors ligne'}</strong>
|
||||||
|
<button type="button" class="btn-close ms-2"
|
||||||
|
onclick="this.closest('.toast').remove()"
|
||||||
|
aria-label="${isAnglophone ? 'Close' : 'Fermer'}"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
${isAnglophone
|
||||||
|
? 'No internet connection. Working in offline mode.'
|
||||||
|
: 'Pas de connexion Internet. Mode hors ligne actif.'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// Auto-remove après 5 secondes
|
||||||
|
setTimeout(() => {
|
||||||
|
if (toast.parentNode) {
|
||||||
|
toast.remove();
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envoyer un message au Service Worker
|
||||||
|
function sendMessageToSW(message) {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
navigator.serviceWorker.controller.postMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obtenir l'état du cache
|
||||||
|
function getCacheStatus() {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
const messageChannel = new MessageChannel();
|
||||||
|
|
||||||
|
messageChannel.port1.onmessage = function(event) {
|
||||||
|
console.log('[SW] Cache status:', event.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
navigator.serviceWorker.controller.postMessage(
|
||||||
|
{ type: 'GET_CACHE_STATUS' },
|
||||||
|
[messageChannel.port2]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction publique pour forcer la mise à jour
|
||||||
|
function forceUpdate() {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialiser quand la page est prête
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
registerServiceWorker();
|
||||||
|
setupConnectionHandlers();
|
||||||
|
|
||||||
|
// Ajouter les écouteurs de visibilité pour les rafraîchissements
|
||||||
|
document.addEventListener('visibilitychange', function() {
|
||||||
|
if (!document.hidden && navigator.serviceWorker.controller) {
|
||||||
|
navigator.serviceWorker.controller.postMessage({ type: 'PAGE_VISIBLE' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Exposer les fonctions publiques
|
||||||
|
window.serviceWorkerManager = {
|
||||||
|
forceUpdate: forceUpdate,
|
||||||
|
getCacheStatus: getCacheStatus,
|
||||||
|
isSupported: 'serviceWorker' in navigator,
|
||||||
|
sendMessage: sendMessageToSW
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('[App] Service Worker Manager initialisé');
|
||||||
|
})();
|
||||||
|
|
@ -35,7 +35,7 @@ foreach ($menus as $key0 => $menuParent) {
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<base href="<?= $racineWeb ?>">
|
<base href="<?= $racineWeb ?>">
|
||||||
|
|
||||||
<title><?= htmlspecialchars($_SESSION['vue'] ?? 'INTER SANTÉ') ?> | Portail Santé</title>
|
<title><?= htmlspecialchars($_SESSION['vue'] ?? 'INTER SANTE') ?> | Portail Santé RH</title>
|
||||||
|
|
||||||
<!-- Meta pour UX améliorée -->
|
<!-- Meta pour UX améliorée -->
|
||||||
<meta name="description" content="Portail professionnel de gestion santé Inter Santé">
|
<meta name="description" content="Portail professionnel de gestion santé Inter Santé">
|
||||||
|
|
@ -69,9 +69,24 @@ foreach ($menus as $key0 => $menuParent) {
|
||||||
<!-- Animate.css -->
|
<!-- Animate.css -->
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css">
|
||||||
|
|
||||||
<!-- Progressive Web App -->
|
<!-- PWA Meta Tags CORRIGÉS -->
|
||||||
<link rel="manifest" href="/manifest.json">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<link rel="apple-touch-icon" href="Bootstrap_new/images/new/favicon.png">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
|
<meta name="apple-mobile-web-app-title" content="INTER-SANTÉ">
|
||||||
|
|
||||||
|
<!-- Open Graph pour le partage -->
|
||||||
|
<meta property="og:title" content="INTER-SANTÉ Portail RH">
|
||||||
|
<meta property="og:description" content="Portail professionnel de gestion santé">
|
||||||
|
<meta property="og:image" content="<?= $racineWeb ?>Bootstrap_new/images/new/favicon.png">
|
||||||
|
<meta property="og:url" content="<?= $racineWeb ?>">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
|
||||||
|
<!-- Twitter Cards -->
|
||||||
|
<meta name="twitter:card" content="summary">
|
||||||
|
<meta name="twitter:title" content="INTER-SANTÉ Portail RH">
|
||||||
|
<meta name="twitter:description" content="Portail professionnel de gestion santé">
|
||||||
|
<meta name="twitter:image" content="<?= $racineWeb ?>Bootstrap_new/images/new/favicon.png">
|
||||||
|
|
||||||
<!-- jQuery UI (pour Datepicker) -->
|
<!-- jQuery UI (pour Datepicker) -->
|
||||||
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
|
<link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
|
||||||
|
|
@ -82,7 +97,7 @@ foreach ($menus as $key0 => $menuParent) {
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Mode développeur
|
// Mode développeur
|
||||||
const modeDev = <?= $_SESSION['modeDev_C'] ?? 0 ?>;
|
const modeDev = 1; /*<?= $_SESSION['modeDev_C'] ?? 0 ?>;*/
|
||||||
if (modeDev !== 1) {
|
if (modeDev !== 1) {
|
||||||
document.addEventListener('contextmenu', e => e.preventDefault());
|
document.addEventListener('contextmenu', e => e.preventDefault());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,61 @@
|
||||||
{
|
{
|
||||||
"name": "Portail RH Inter Santé",
|
"name": "INTER-SANTÉ Portail RH",
|
||||||
"short_name": "InterSanté RH",
|
"short_name": "InterSanté",
|
||||||
"description": "Portail RH professionnel avec design Office/PowerPoint",
|
"description": "Portail RH professionnel de gestion santé",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
|
"scope": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#f3f2f1",
|
"background_color": "#f3f2f1",
|
||||||
"theme_color": "#b7472a",
|
"theme_color": "#b7472a",
|
||||||
"orientation": "portrait-primary",
|
"orientation": "portrait-primary",
|
||||||
|
"lang": "fr",
|
||||||
|
"dir": "ltr",
|
||||||
|
"categories": ["business", "productivity", "medical"],
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/icons/icon-72x72.png",
|
"src": "/Bootstrap_new/images/new/favicon.png",
|
||||||
"sizes": "72x72",
|
"sizes": "72x72 96x96 128x128 144x144 152x152 192x192 384x384 512x512",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/icons/icon-96x96.png",
|
"src": "/Bootstrap_new/images/new/favicon-192.png",
|
||||||
"sizes": "96x96",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/icons/icon-128x128.png",
|
|
||||||
"sizes": "128x128",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/icons/icon-144x144.png",
|
|
||||||
"sizes": "144x144",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/icons/icon-152x152.png",
|
|
||||||
"sizes": "152x152",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/icons/icon-192x192.png",
|
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/icons/icon-384x384.png",
|
"src": "/Bootstrap_new/images/new/favicon-512.png",
|
||||||
"sizes": "384x384",
|
|
||||||
"type": "image/png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/icons/icon-512x512.png",
|
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
|
"purpose": "any maskable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"categories": ["business", "productivity", "utilities"],
|
"screenshots": [
|
||||||
"dir": "ltr",
|
{
|
||||||
"lang": "fr-FR",
|
"src": "/Bootstrap_new/images/screenshot-desktop.png",
|
||||||
|
"sizes": "1280x720",
|
||||||
|
"type": "image/png",
|
||||||
|
"form_factor": "wide",
|
||||||
|
"label": "Desktop view"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"shortcuts": [
|
||||||
|
{
|
||||||
|
"name": "Tableau de bord",
|
||||||
|
"short_name": "Dashboard",
|
||||||
|
"description": "Accéder au tableau de bord",
|
||||||
|
"url": "/Accueil",
|
||||||
|
"icons": [{ "src": "/Bootstrap_new/images/new/favicon.png", "sizes": "96x96" }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Messagerie",
|
||||||
|
"short_name": "Messages",
|
||||||
|
"description": "Voir les messages",
|
||||||
|
"url": "/Messages",
|
||||||
|
"icons": [{ "src": "/Bootstrap_new/images/icon-message.png", "sizes": "96x96" }]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"related_applications": [],
|
||||||
"prefer_related_applications": false
|
"prefer_related_applications": false
|
||||||
}
|
}
|
||||||
86
offline.html
Normal file
86
offline.html
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mode hors ligne - INTER-SANTÉ</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', sans-serif;
|
||||||
|
background: linear-gradient(135deg, #f3f2f1 0%, #e6e6e6 100%);
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.offline-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 40px;
|
||||||
|
text-align: center;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.offline-icon {
|
||||||
|
font-size: 64px;
|
||||||
|
color: #b7472a;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #b7472a;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: #b7472a;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #a53e24;
|
||||||
|
}
|
||||||
|
button.secondary {
|
||||||
|
background: #2b579a;
|
||||||
|
}
|
||||||
|
button.secondary:hover {
|
||||||
|
background: #1e4a8b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="offline-container">
|
||||||
|
<div class="offline-icon">📶</div>
|
||||||
|
<h1>Mode hors ligne</h1>
|
||||||
|
<p>Vous n'êtes pas connecté à Internet. Certaines fonctionnalités peuvent être limitées.</p>
|
||||||
|
<p>Vous pouvez continuer à utiliser les fonctionnalités disponibles hors ligne.</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button onclick="location.reload()">Réessayer</button>
|
||||||
|
<button class="secondary" onclick="history.back()">Retour</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 30px; font-size: 14px; color: #999;">
|
||||||
|
<p>INTER-SANTÉ Portail RH • Version hors ligne</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
// Service Worker pour Portail RH Inter Santé
|
// Service Worker pour Portail RH Inter Santé
|
||||||
const CACHE_NAME = 'inter-sante-portal-v1.0';
|
const CACHE_NAME = 'inter-sante-portal-v1.1';
|
||||||
const OFFLINE_URL = '/offline.html';
|
const OFFLINE_URL = '/offline.html';
|
||||||
|
|
||||||
// Ressources à mettre en cache immédiatement
|
// Ressources ESSENTIELLES à mettre en cache (vérifier l'existence)
|
||||||
const PRECACHE_URLS = [
|
const PRECACHE_URLS = [
|
||||||
'/',
|
'/',
|
||||||
'/style_office.css',
|
'/Bootstrap_new/css/style_office.css',
|
||||||
'/ux_enhancements.css',
|
'/Bootstrap_new/css/ux_enhancements.css',
|
||||||
'/ux-manager.js',
|
'/Bootstrap_new/js/ux-manager.js',
|
||||||
|
'/Js/fonctions.js',
|
||||||
'/manifest.json',
|
'/manifest.json',
|
||||||
'/icons/icon-192x192.png',
|
'/Bootstrap_new/images/new/favicon.png'
|
||||||
'/icons/icon-512x512.png'
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Installation - Pré-cache des ressources essentielles
|
// Installation - Pré-cache des ressources essentielles
|
||||||
|
|
@ -19,12 +19,32 @@ self.addEventListener('install', event => {
|
||||||
caches.open(CACHE_NAME)
|
caches.open(CACHE_NAME)
|
||||||
.then(cache => {
|
.then(cache => {
|
||||||
console.log('[Service Worker] Pré-cache des ressources');
|
console.log('[Service Worker] Pré-cache des ressources');
|
||||||
return cache.addAll(PRECACHE_URLS);
|
|
||||||
|
// Cache only resources that exist
|
||||||
|
const cachePromises = PRECACHE_URLS.map(url => {
|
||||||
|
return fetch(url, { mode: 'no-cors' })
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok || response.type === 'opaque') {
|
||||||
|
return cache.put(url, response);
|
||||||
|
}
|
||||||
|
console.warn(`[SW] Resource not found: ${url}`);
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.warn(`[SW] Failed to cache ${url}:`, error);
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(cachePromises);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('[Service Worker] Installation terminée');
|
console.log('[Service Worker] Installation terminée');
|
||||||
return self.skipWaiting();
|
return self.skipWaiting();
|
||||||
})
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('[Service Worker] Erreur installation:', error);
|
||||||
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -47,78 +67,154 @@ self.addEventListener('activate', event => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Stratégie de cache: Network First, fallback Cache
|
// Stratégie de cache: Stale-While-Revalidate
|
||||||
self.addEventListener('fetch', event => {
|
self.addEventListener('fetch', event => {
|
||||||
// Ignorer les requêtes non-GET et les requêtes cross-origin
|
// Ignorer les requêtes non-GET
|
||||||
if (event.request.method !== 'GET' ||
|
if (event.request.method !== 'GET') {
|
||||||
!event.request.url.startsWith(self.location.origin)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pour les pages HTML: Network First
|
// Ignorer les requêtes chrome-extension
|
||||||
|
if (event.request.url.includes('chrome-extension')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pour les pages HTML: Network First avec fallback cache
|
||||||
if (event.request.headers.get('accept').includes('text/html')) {
|
if (event.request.headers.get('accept').includes('text/html')) {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
fetch(event.request)
|
fetch(event.request)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
// Mettre à jour le cache avec la nouvelle version
|
// Vérifier si la réponse est valide
|
||||||
const responseClone = response.clone();
|
if (!response || response.status !== 200 || response.type === 'error') {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cloner la réponse pour le cache
|
||||||
|
const responseToCache = response.clone();
|
||||||
caches.open(CACHE_NAME)
|
caches.open(CACHE_NAME)
|
||||||
.then(cache => cache.put(event.request, responseClone));
|
.then(cache => {
|
||||||
|
cache.put(event.request, responseToCache);
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// Fallback au cache ou page offline
|
// Fallback au cache
|
||||||
return caches.match(event.request)
|
return caches.match(event.request)
|
||||||
.then(cachedResponse => {
|
.then(cachedResponse => {
|
||||||
return cachedResponse || caches.match(OFFLINE_URL);
|
return cachedResponse || caches.match('/');
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pour les autres ressources: Cache First
|
// Pour les autres ressources: Cache First, fallback Network
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.match(event.request)
|
caches.match(event.request)
|
||||||
.then(cachedResponse => {
|
.then(cachedResponse => {
|
||||||
if (cachedResponse) {
|
if (cachedResponse) {
|
||||||
|
// Toujours rafraîchir le cache en arrière-plan
|
||||||
|
fetch(event.request)
|
||||||
|
.then(response => {
|
||||||
|
if (response && response.ok) {
|
||||||
|
caches.open(CACHE_NAME)
|
||||||
|
.then(cache => cache.put(event.request, response));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {}); // Ignorer les erreurs de mise à jour
|
||||||
|
|
||||||
return cachedResponse;
|
return cachedResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pas dans le cache, aller au réseau
|
||||||
return fetch(event.request)
|
return fetch(event.request)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
// Ne pas mettre en cache les erreurs
|
// Vérifier si nous avons reçu une réponse valide
|
||||||
if (!response || response.status !== 200 || response.type !== 'basic') {
|
if (!response || !response.ok) {
|
||||||
return response;
|
return response; // Retourner la réponse même si elle a échoué
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mettre en cache pour la prochaine fois
|
// Mettre en cache la réponse pour la prochaine fois
|
||||||
const responseToCache = response.clone();
|
const responseToCache = response.clone();
|
||||||
caches.open(CACHE_NAME)
|
caches.open(CACHE_NAME)
|
||||||
.then(cache => cache.put(event.request, responseToCache));
|
.then(cache => {
|
||||||
|
cache.put(event.request, responseToCache);
|
||||||
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(error => {
|
||||||
// Pour les CSS/JS, on peut retourner une réponse vide
|
console.error('[SW] Fetch failed:', error);
|
||||||
if (event.request.url.includes('.css')) {
|
|
||||||
return new Response('/* Ressource non disponible hors ligne */', {
|
// Pour les CSS/JS, retourner des réponses de secours
|
||||||
|
const url = event.request.url;
|
||||||
|
if (url.includes('.css')) {
|
||||||
|
return new Response('/* Ressource temporairement indisponible */', {
|
||||||
headers: { 'Content-Type': 'text/css' }
|
headers: { 'Content-Type': 'text/css' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (event.request.url.includes('.js')) {
|
if (url.includes('.js')) {
|
||||||
return new Response('// Ressource non disponible hors ligne', {
|
return new Response('// Ressource temporairement indisponible', {
|
||||||
headers: { 'Content-Type': 'application/javascript' }
|
headers: { 'Content-Type': 'application/javascript' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pour les images, retourner une image de secours
|
||||||
|
if (url.includes('.png') || url.includes('.jpg') || url.includes('.svg')) {
|
||||||
|
return fetch('/Bootstrap_new/images/new/favicon.png')
|
||||||
|
.catch(() => {
|
||||||
|
// Si même l'icône de secours échoue, retourner une réponse vide
|
||||||
|
return new Response('', { status: 404 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response('Ressource non disponible hors ligne', {
|
||||||
|
headers: { 'Content-Type': 'text/plain' }
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gestion des messages
|
// Gestion des messages entre l'app et le Service Worker
|
||||||
self.addEventListener('message', event => {
|
self.addEventListener('message', event => {
|
||||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.data && event.data.type === 'GET_CACHE_STATUS') {
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
cacheName: CACHE_NAME,
|
||||||
|
status: 'active'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gestion des notifications push (optionnel)
|
||||||
|
self.addEventListener('push', event => {
|
||||||
|
if (!event.data) return;
|
||||||
|
|
||||||
|
const data = event.data.json();
|
||||||
|
const options = {
|
||||||
|
body: data.body || 'Nouvelle notification',
|
||||||
|
icon: '/Bootstrap_new/images/new/favicon.png',
|
||||||
|
badge: '/Bootstrap_new/images/new/favicon.png',
|
||||||
|
vibrate: [200, 100, 200],
|
||||||
|
data: {
|
||||||
|
url: data.url || '/'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
self.registration.showNotification(data.title || 'Inter Santé', options)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('notificationclick', event => {
|
||||||
|
event.notification.close();
|
||||||
|
|
||||||
|
event.waitUntil(
|
||||||
|
clients.openWindow(event.notification.data.url || '/')
|
||||||
|
);
|
||||||
});
|
});
|
||||||
Loading…
Reference in New Issue
Block a user