- Nouveau bouton "Quitter" sous le bouton "Se connecter"
- Permet de fermer proprement l'application sans se connecter
- Style gris secondaire pour différencier du bouton principal
- Appel à ipcRenderer.invoke('quit-app') pour fermer l'application
Fichiers modifiés :
- index.html : ajout du bouton avec id quitLoginBtn
- renderer.js : gestionnaire handleQuitFromLogin
- styles-modern.css : style .btn-quit avec couleur grise
1272 lines
45 KiB
JavaScript
1272 lines
45 KiB
JavaScript
const { ipcRenderer } = require('electron');
|
|
// Choices est maintenant chargé via CDN dans index.html
|
|
|
|
// Variables globales
|
|
let currentAgent = null;
|
|
let currentCentres = [];
|
|
let activeCenter = null;
|
|
let webviews = {};
|
|
let callStats = {
|
|
calls: 0,
|
|
appointments: 0
|
|
};
|
|
|
|
// Variables pour le redimensionnement des notes
|
|
let isResizingNotes = false;
|
|
let notesStartWidth = 380;
|
|
let notesStartX = 0;
|
|
|
|
// === GESTION DE LA CONNEXION ===
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
// Afficher la version de l'application
|
|
const appVersion = await ipcRenderer.invoke('get-app-version');
|
|
const versionElement = document.getElementById('appVersion');
|
|
const versionLoginElement = document.getElementById('appVersionLogin');
|
|
if (versionElement && appVersion) {
|
|
versionElement.textContent = `v${appVersion}`;
|
|
}
|
|
if (versionLoginElement && appVersion) {
|
|
versionLoginElement.textContent = `v${appVersion}`;
|
|
}
|
|
|
|
// Initialiser l'indicateur SignalR
|
|
|
|
// Écouter les changements de statut SignalR
|
|
ipcRenderer.on('signalr-status', (event, status) => {
|
|
updateSignalRIndicator(status);
|
|
// Recharger les terminaux à chaque changement de statut
|
|
loadTerminals();
|
|
});
|
|
|
|
// Obtenir le statut initial SignalR
|
|
const initialStatus = await ipcRenderer.invoke('get-signalr-status');
|
|
updateSignalRIndicator(initialStatus);
|
|
|
|
// Charger immédiatement les terminaux pour la page de login
|
|
await loadTerminals();
|
|
|
|
// Vérifier si un agent est déjà connecté
|
|
const agentData = await ipcRenderer.invoke('get-current-agent');
|
|
if (agentData) {
|
|
currentAgent = agentData.agent;
|
|
currentCentres = agentData.centres;
|
|
showMainPage();
|
|
// Les notes seront chargées dans showMainPage()
|
|
} else {
|
|
// S'assurer que le formulaire est propre au démarrage
|
|
resetLoginForm();
|
|
// Mettre le focus sur le champ code d'accès
|
|
const accessCodeInput = document.getElementById('accessCode');
|
|
if (accessCodeInput) {
|
|
accessCodeInput.focus();
|
|
}
|
|
}
|
|
|
|
// Gestionnaire du formulaire de connexion
|
|
const loginForm = document.getElementById('loginForm');
|
|
if (loginForm) {
|
|
loginForm.addEventListener('submit', handleLogin);
|
|
}
|
|
|
|
// Bouton quitter sur la page de login
|
|
const quitLoginBtn = document.getElementById('quitLoginBtn');
|
|
if (quitLoginBtn) {
|
|
quitLoginBtn.addEventListener('click', handleQuitFromLogin);
|
|
}
|
|
|
|
// Bouton de déconnexion
|
|
const logoutBtn = document.getElementById('logoutBtn');
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener('click', handleLogout);
|
|
}
|
|
|
|
// Bouton sauvegarder notes
|
|
const saveNotesBtn = document.getElementById('saveNotesBtn');
|
|
if (saveNotesBtn) {
|
|
saveNotesBtn.addEventListener('click', saveNotes);
|
|
}
|
|
|
|
// Bouton effacer notes
|
|
const clearNotesBtn = document.getElementById('clearNotesBtn');
|
|
if (clearNotesBtn) {
|
|
clearNotesBtn.addEventListener('click', clearNotes);
|
|
}
|
|
|
|
// Bouton toggle notes
|
|
const toggleNotesBtn = document.getElementById('toggleNotesBtn');
|
|
if (toggleNotesBtn) {
|
|
toggleNotesBtn.addEventListener('click', toggleNotes);
|
|
}
|
|
|
|
// Bouton fermer notes
|
|
const closeNotesBtn = document.getElementById('closeNotesBtn');
|
|
if (closeNotesBtn) {
|
|
closeNotesBtn.addEventListener('click', hideNotes);
|
|
}
|
|
|
|
// Bouton rafraîchir
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
if (refreshBtn) {
|
|
refreshBtn.addEventListener('click', refreshCurrentWebview);
|
|
}
|
|
|
|
// Charger les préférences utilisateur
|
|
loadUserPreferences();
|
|
|
|
// Initialiser le redimensionnement des notes
|
|
initNotesResize();
|
|
|
|
|
|
// Gérer les boutons de la modal de déconnexion (si on est sur la page principale)
|
|
const cancelLogoutBtn = document.getElementById('cancelLogoutBtn');
|
|
const confirmLogoutBtn = document.getElementById('confirmLogoutBtn');
|
|
if (cancelLogoutBtn && confirmLogoutBtn) {
|
|
// Les event listeners sont gérés dans showLogoutModal()
|
|
}
|
|
|
|
// Écouter les appels entrants
|
|
ipcRenderer.on('incoming-call', (event, callData) => {
|
|
handleIncomingCall(callData);
|
|
});
|
|
|
|
// Écouter les événements SignalR de basculement de centre
|
|
ipcRenderer.on('switch-to-center', (event, data) => {
|
|
console.log('Basculement vers le centre:', data.centreName);
|
|
|
|
// Trouver le centre et basculer automatiquement
|
|
const centre = currentCentres.find(c => c.id === data.centreId);
|
|
if (centre) {
|
|
selectCenter(data.centreId);
|
|
|
|
// Afficher une notification
|
|
showNotification(`Appel entrant sur ${data.centreName}`, 'info');
|
|
|
|
// Mettre à jour le statut
|
|
updateAgentStatus('EN APPEL');
|
|
}
|
|
});
|
|
|
|
// Écouter la libération de centre après raccrochage
|
|
ipcRenderer.on('release-center', (event, data) => {
|
|
console.log('Libération de la file:', data.queueName);
|
|
|
|
// Mettre à jour le statut
|
|
updateAgentStatus('DISPONIBLE');
|
|
|
|
// Incrementer le compteur d'appels
|
|
callStats.calls++;
|
|
updateCallStats();
|
|
|
|
// Afficher une notification
|
|
showNotification('Appel terminé', 'success');
|
|
});
|
|
});
|
|
|
|
// Connexion via SignalR
|
|
async function handleLogin(e) {
|
|
e.preventDefault();
|
|
|
|
const accessCode = document.getElementById('accessCode').value;
|
|
const password = document.getElementById('password').value;
|
|
const terminal = document.getElementById('terminal').value;
|
|
const forceDisconnect = document.getElementById('forceDisconnect').checked;
|
|
const errorDiv = document.getElementById('loginError');
|
|
const loginBtn = document.querySelector('#loginForm button[type="submit"]');
|
|
|
|
// Vérifier que le terminal est sélectionné et valide
|
|
if (!terminal || terminal.trim() === '') {
|
|
errorDiv.textContent = 'Veuillez sélectionner ou saisir un poste téléphonique';
|
|
return;
|
|
}
|
|
|
|
// Valider le format du terminal (doit être numérique)
|
|
if (!/^\d+$/.test(terminal)) {
|
|
errorDiv.textContent = 'Le poste doit être un numéro (ex: 3001)';
|
|
return;
|
|
}
|
|
|
|
// Avertir si le poste n'est pas dans la liste officielle
|
|
if (!validateTerminal(terminal)) {
|
|
console.log(`⚠️ Avertissement : Le poste ${terminal} n'est pas dans la liste officielle`);
|
|
|
|
// Afficher un avertissement visuel temporaire
|
|
const warningDiv = document.createElement('div');
|
|
warningDiv.className = 'warning-message';
|
|
warningDiv.style.cssText = 'color: #ff9800; font-size: 0.9em; margin-top: 5px;';
|
|
warningDiv.textContent = `⚠️ Attention : Le poste ${terminal} n'est pas dans la liste officielle`;
|
|
|
|
// Insérer l'avertissement avant le message d'erreur
|
|
errorDiv.parentNode.insertBefore(warningDiv, errorDiv);
|
|
|
|
// Supprimer l'avertissement après 5 secondes
|
|
setTimeout(() => {
|
|
if (warningDiv.parentNode) {
|
|
warningDiv.remove();
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
// Sauvegarder le terminal sélectionné pour la prochaine fois
|
|
localStorage.setItem('last-terminal', terminal);
|
|
|
|
// Afficher la modal de progression de connexion
|
|
showLoginProgress(forceDisconnect);
|
|
|
|
// Désactiver le bouton pendant la connexion
|
|
loginBtn.disabled = true;
|
|
loginBtn.textContent = forceDisconnect ? 'Reconnexion...' : 'Connexion en cours...';
|
|
errorDiv.textContent = '';
|
|
|
|
// Attendre un peu pour que l'animation soit visible
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
try {
|
|
// Préparer les credentials pour SignalR
|
|
const credentials = {
|
|
email: accessCode, // Utiliser directement le code agent comme email
|
|
password: password,
|
|
terminal: terminal,
|
|
forceDisconnect: forceDisconnect // Ajouter l'option de déconnexion forcée
|
|
};
|
|
|
|
// Appeler l'authentification SignalR
|
|
const result = await ipcRenderer.invoke('login-agent', credentials);
|
|
|
|
if (result.success) {
|
|
currentAgent = result.agent;
|
|
currentCentres = result.centres;
|
|
errorDiv.textContent = '';
|
|
console.log('Connexion réussie:', currentAgent.name, 'sur le poste', terminal);
|
|
|
|
// Mettre à jour le message de progression
|
|
updateLoginProgress('Chargement de vos centres...', 'Préparation de l\'interface...');
|
|
|
|
// Attendre encore un peu pour une transition fluide
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
// Masquer la modal et afficher la page principale
|
|
hideLoginProgress();
|
|
showMainPage();
|
|
} else {
|
|
// Masquer la modal en cas d'échec
|
|
hideLoginProgress();
|
|
errorDiv.textContent = result.message || 'Identifiants incorrects';
|
|
loginBtn.disabled = false;
|
|
loginBtn.textContent = 'Se connecter';
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la connexion:', error);
|
|
// Masquer la modal en cas d'erreur
|
|
hideLoginProgress();
|
|
errorDiv.textContent = 'Erreur de connexion. Veuillez réessayer.';
|
|
loginBtn.disabled = false;
|
|
loginBtn.textContent = 'Se connecter';
|
|
}
|
|
}
|
|
|
|
// Quitter depuis la page de login
|
|
function handleQuitFromLogin() {
|
|
ipcRenderer.invoke('quit-app');
|
|
}
|
|
|
|
// Déconnexion
|
|
function handleLogout() {
|
|
showLogoutModal();
|
|
}
|
|
|
|
// Afficher la modal de déconnexion
|
|
function showLogoutModal() {
|
|
const modal = document.getElementById('logoutModal');
|
|
modal.classList.add('active');
|
|
|
|
// Bouton annuler
|
|
const cancelBtn = document.getElementById('cancelLogoutBtn');
|
|
const confirmBtn = document.getElementById('confirmLogoutBtn');
|
|
|
|
// Gérer les clics
|
|
const handleCancel = () => {
|
|
modal.classList.remove('active');
|
|
cancelBtn.removeEventListener('click', handleCancel);
|
|
confirmBtn.removeEventListener('click', handleConfirm);
|
|
};
|
|
|
|
const handleConfirm = async () => {
|
|
// Afficher l'animation de déconnexion
|
|
showLogoutProgress();
|
|
|
|
// Attendre un peu pour que l'animation soit visible
|
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
|
|
// Effectuer la déconnexion
|
|
await ipcRenderer.invoke('logout');
|
|
|
|
// Attendre encore un peu pour une transition fluide
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
// Fermer l'application proprement
|
|
await ipcRenderer.invoke('quit-app');
|
|
|
|
cancelBtn.removeEventListener('click', handleCancel);
|
|
confirmBtn.removeEventListener('click', handleConfirm);
|
|
};
|
|
|
|
cancelBtn.addEventListener('click', handleCancel);
|
|
confirmBtn.addEventListener('click', handleConfirm);
|
|
|
|
// Fermer la modal en cliquant en dehors
|
|
modal.addEventListener('click', (e) => {
|
|
if (e.target === modal) {
|
|
handleCancel();
|
|
}
|
|
});
|
|
|
|
// Fermer avec Escape
|
|
const handleEscape = (e) => {
|
|
if (e.key === 'Escape') {
|
|
handleCancel();
|
|
document.removeEventListener('keydown', handleEscape);
|
|
}
|
|
};
|
|
document.addEventListener('keydown', handleEscape);
|
|
}
|
|
|
|
// Afficher l'animation de progression de déconnexion
|
|
function showLogoutProgress() {
|
|
const modal = document.querySelector('.logout-modal');
|
|
const icon = document.getElementById('logoutIcon');
|
|
const spinner = document.getElementById('logoutSpinner');
|
|
const title = document.getElementById('logoutTitle');
|
|
const text = document.getElementById('logoutText');
|
|
const subtitle = document.getElementById('logoutSubtitle');
|
|
const buttons = document.getElementById('logoutButtons');
|
|
|
|
// Masquer l'icône et afficher le spinner
|
|
icon.style.display = 'none';
|
|
spinner.style.display = 'block';
|
|
|
|
// Mettre à jour les textes
|
|
title.textContent = 'Déconnexion en cours...';
|
|
text.textContent = 'Veuillez patienter';
|
|
subtitle.textContent = 'Fermeture de votre session...';
|
|
|
|
// Masquer les boutons
|
|
buttons.style.display = 'none';
|
|
}
|
|
|
|
// Masquer l'animation de progression de déconnexion
|
|
function hideLogoutProgress() {
|
|
const icon = document.getElementById('logoutIcon');
|
|
const spinner = document.getElementById('logoutSpinner');
|
|
const title = document.getElementById('logoutTitle');
|
|
const text = document.getElementById('logoutText');
|
|
const subtitle = document.getElementById('logoutSubtitle');
|
|
const buttons = document.getElementById('logoutButtons');
|
|
|
|
// Restaurer l'état initial
|
|
icon.style.display = 'flex';
|
|
spinner.style.display = 'none';
|
|
title.textContent = 'Déconnexion';
|
|
text.textContent = 'Êtes-vous sûr de vouloir vous déconnecter ?';
|
|
subtitle.textContent = 'Votre session sera fermée et vous devrez vous reconnecter.';
|
|
buttons.style.display = 'flex';
|
|
}
|
|
|
|
// Afficher l'animation de progression de connexion
|
|
function showLoginProgress(isForceDisconnect = false) {
|
|
const modal = document.getElementById('loginModal');
|
|
const title = document.getElementById('loginTitle');
|
|
const text = document.getElementById('loginText');
|
|
const subtitle = document.getElementById('loginSubtitle');
|
|
|
|
// Afficher la modal
|
|
modal.classList.add('active');
|
|
|
|
// Utiliser les mêmes textes peu importe le type de connexion
|
|
title.textContent = 'Connexion en cours...';
|
|
text.textContent = 'Authentification auprès du serveur';
|
|
subtitle.textContent = 'Veuillez patienter...';
|
|
}
|
|
|
|
// Mettre à jour le texte de progression de connexion
|
|
function updateLoginProgress(text, subtitle) {
|
|
const textElement = document.getElementById('loginText');
|
|
const subtitleElement = document.getElementById('loginSubtitle');
|
|
|
|
if (textElement) textElement.textContent = text;
|
|
if (subtitleElement) subtitleElement.textContent = subtitle;
|
|
}
|
|
|
|
// Masquer l'animation de progression de connexion
|
|
function hideLoginProgress() {
|
|
const modal = document.getElementById('loginModal');
|
|
modal.classList.remove('active');
|
|
}
|
|
|
|
// === GESTION DES PAGES ===
|
|
function showLoginPage() {
|
|
document.getElementById('loginPage').classList.add('active');
|
|
document.getElementById('mainPage').classList.remove('active');
|
|
|
|
// Mettre le focus sur le champ code d'accès après un court délai
|
|
setTimeout(() => {
|
|
const accessCodeInput = document.getElementById('accessCode');
|
|
if (accessCodeInput) {
|
|
accessCodeInput.focus();
|
|
accessCodeInput.select(); // Sélectionner le contenu s'il y en a
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
// Réinitialiser le formulaire de connexion
|
|
function resetLoginForm() {
|
|
// Vider les champs
|
|
const accessCode = document.getElementById('accessCode');
|
|
const password = document.getElementById('password');
|
|
const terminal = document.getElementById('terminal');
|
|
const forceDisconnect = document.getElementById('forceDisconnect');
|
|
const loginError = document.getElementById('loginError');
|
|
const loginBtn = document.querySelector('#loginForm button[type="submit"]');
|
|
|
|
if (accessCode) accessCode.value = '';
|
|
if (password) password.value = '';
|
|
|
|
// Réinitialiser le terminal (garder la dernière sélection si elle existe)
|
|
// Ne pas réinitialiser le terminal pour garder la préférence
|
|
|
|
// Décocher la checkbox de déblocage
|
|
if (forceDisconnect) forceDisconnect.checked = false;
|
|
|
|
// Vider les messages d'erreur
|
|
if (loginError) loginError.textContent = '';
|
|
|
|
// Réactiver le bouton et remettre le texte par défaut
|
|
if (loginBtn) {
|
|
loginBtn.disabled = false;
|
|
loginBtn.textContent = 'Se connecter';
|
|
}
|
|
}
|
|
|
|
function showMainPage() {
|
|
document.getElementById('loginPage').classList.remove('active');
|
|
document.getElementById('mainPage').classList.add('active');
|
|
|
|
// Afficher le nom de l'agent
|
|
document.getElementById('agentName').textContent = currentAgent.name;
|
|
|
|
// Initialiser l'interface
|
|
initializeCenters();
|
|
updateStatus('available');
|
|
|
|
// Sélectionner automatiquement le premier centre/onglet (après tri alphabétique)
|
|
if (currentCentres.length > 0) {
|
|
// Trier pour obtenir le premier alphabétiquement
|
|
const sortedCentres = [...currentCentres].sort((a, b) => {
|
|
return a.id.localeCompare(b.id, 'fr', { numeric: true });
|
|
});
|
|
selectCenter(sortedCentres[0].id);
|
|
}
|
|
|
|
// Charger les notes sauvegardées APRÈS que la page soit affichée
|
|
setTimeout(() => {
|
|
loadSavedNotes();
|
|
setupAutoSave();
|
|
}, 100);
|
|
}
|
|
|
|
// === GESTION DES CENTRES ===
|
|
function initializeCenters() {
|
|
const centerTabs = document.getElementById('centerTabs');
|
|
const webviewContainer = document.getElementById('webviewContainer');
|
|
|
|
// Vider les contenus existants
|
|
centerTabs.innerHTML = '';
|
|
webviewContainer.innerHTML = '';
|
|
|
|
// Trier les centres par ordre alphabétique du code (id)
|
|
const sortedCentres = [...currentCentres].sort((a, b) => {
|
|
return a.id.localeCompare(b.id, 'fr', { numeric: true });
|
|
});
|
|
|
|
// Créer les onglets et webviews
|
|
sortedCentres.forEach(centre => {
|
|
|
|
// Onglet
|
|
const tab = document.createElement('div');
|
|
tab.className = 'tab';
|
|
tab.dataset.centerId = centre.id;
|
|
tab.style.borderBottomColor = centre.couleur;
|
|
tab.textContent = centre.id; // Afficher le code du client au lieu du nom
|
|
tab.addEventListener('click', () => selectCenter(centre.id));
|
|
centerTabs.appendChild(tab);
|
|
|
|
// Créer la webview
|
|
const webviewWrapper = document.createElement('div');
|
|
webviewWrapper.className = 'webview-wrapper';
|
|
webviewWrapper.dataset.centerId = centre.id;
|
|
webviewWrapper.style.display = 'none';
|
|
|
|
const webview = document.createElement('webview');
|
|
webview.id = `webview-${centre.id}`;
|
|
webview.src = centre.url;
|
|
webview.className = 'planning-webview';
|
|
webview.setAttribute('partition', `persist:${centre.id}`);
|
|
webview.setAttribute('useragent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36');
|
|
|
|
webviewWrapper.appendChild(webview);
|
|
webviewContainer.appendChild(webviewWrapper);
|
|
|
|
// Stocker la référence de la webview
|
|
webviews[centre.id] = webview;
|
|
|
|
// Gérer les événements de la webview
|
|
webview.addEventListener('dom-ready', () => {
|
|
console.log(`Webview ${centre.nom} prête`);
|
|
|
|
// Auto-connexion si credentials disponibles
|
|
if (centre.credentials && centre.credentials.username) {
|
|
// Injecter le script de connexion automatique (à adapter selon le site)
|
|
const loginScript = `
|
|
// Script générique de connexion - à adapter selon le planning
|
|
console.log('Tentative de connexion automatique pour ${centre.nom}');
|
|
`;
|
|
webview.executeJavaScript(loginScript);
|
|
}
|
|
});
|
|
|
|
webview.addEventListener('did-fail-load', (e) => {
|
|
console.error(`Erreur chargement ${centre.nom}:`, e);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Sélectionner un centre
|
|
function selectCenter(centerId) {
|
|
// Mettre à jour le centre actif
|
|
activeCenter = centerId;
|
|
|
|
document.querySelectorAll('.tab').forEach(tab => {
|
|
tab.classList.toggle('active', tab.dataset.centerId === centerId);
|
|
});
|
|
|
|
// Afficher la bonne webview
|
|
document.querySelectorAll('.webview-wrapper').forEach(wrapper => {
|
|
wrapper.style.display = wrapper.dataset.centerId === centerId ? 'flex' : 'none';
|
|
});
|
|
}
|
|
|
|
|
|
// === GESTION DES APPELS ===
|
|
function handleIncomingCall(callData) {
|
|
const centre = currentCentres.find(c => c.id === callData.centreId);
|
|
if (!centre) return;
|
|
|
|
// Afficher l'alerte
|
|
const alert = document.getElementById('incomingCallAlert');
|
|
alert.classList.add('active');
|
|
|
|
document.getElementById('callCenterName').textContent = centre.nom;
|
|
document.getElementById('callPatientInfo').textContent =
|
|
`${callData.nom || 'Patient'} - ${callData.numero}`;
|
|
|
|
// Jouer un son (à implémenter)
|
|
playNotificationSound();
|
|
|
|
// Gérer l'acceptation de l'appel
|
|
const acceptBtn = document.getElementById('acceptCallBtn');
|
|
acceptBtn.onclick = () => {
|
|
acceptCall(callData, centre);
|
|
};
|
|
|
|
// Auto-accepter après 3 secondes en mode démo
|
|
setTimeout(() => {
|
|
if (alert.classList.contains('active')) {
|
|
acceptCall(callData, centre);
|
|
}
|
|
}, 3000);
|
|
}
|
|
|
|
function acceptCall(callData, centre) {
|
|
// Masquer l'alerte
|
|
document.getElementById('incomingCallAlert').classList.remove('active');
|
|
|
|
// Sélectionner automatiquement le bon centre
|
|
selectCenter(centre.id);
|
|
|
|
// Mettre à jour le statut
|
|
updateStatus('incall', centre.nom);
|
|
|
|
// Incrémenter les stats
|
|
callStats.calls++;
|
|
|
|
// Sauvegarder l'appel dans l'historique
|
|
ipcRenderer.invoke('save-call-history', {
|
|
...callData,
|
|
centreId: centre.id,
|
|
centreName: centre.nom,
|
|
status: 'answered'
|
|
});
|
|
|
|
// Simuler la fin de l'appel après 30 secondes
|
|
setTimeout(() => {
|
|
endCall();
|
|
}, 30000);
|
|
}
|
|
|
|
function endCall() {
|
|
updateStatus('available');
|
|
callStats.appointments++;
|
|
}
|
|
|
|
|
|
// === UTILITAIRES ===
|
|
function updateStatus(status, details = '') {
|
|
const indicator = document.getElementById('statusIndicator');
|
|
const text = document.getElementById('statusText');
|
|
|
|
switch(status) {
|
|
case 'available':
|
|
indicator.className = 'status-indicator available';
|
|
text.textContent = 'Disponible';
|
|
break;
|
|
case 'incall':
|
|
indicator.className = 'status-indicator busy';
|
|
text.textContent = details ? `En appel - ${details}` : 'En appel';
|
|
break;
|
|
case 'offline':
|
|
indicator.className = 'status-indicator offline';
|
|
text.textContent = 'Hors ligne';
|
|
break;
|
|
}
|
|
}
|
|
|
|
function playNotificationSound() {
|
|
// Créer un bip simple avec l'API Web Audio
|
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
const oscillator = audioContext.createOscillator();
|
|
const gainNode = audioContext.createGain();
|
|
|
|
oscillator.connect(gainNode);
|
|
gainNode.connect(audioContext.destination);
|
|
|
|
oscillator.frequency.value = 800;
|
|
oscillator.type = 'sine';
|
|
gainNode.gain.value = 0.3;
|
|
|
|
oscillator.start();
|
|
oscillator.stop(audioContext.currentTime + 0.2);
|
|
}
|
|
|
|
// Fonction pour mettre à jour le statut de l'agent
|
|
function updateAgentStatus(status) {
|
|
const statusElement = document.getElementById('statusText');
|
|
const indicatorElement = document.getElementById('statusIndicator');
|
|
|
|
if (statusElement && indicatorElement) {
|
|
switch(status) {
|
|
case 'DISPONIBLE':
|
|
updateStatus('available');
|
|
break;
|
|
case 'EN APPEL':
|
|
updateStatus('incall');
|
|
break;
|
|
case 'HORS LIGNE':
|
|
updateStatus('offline');
|
|
break;
|
|
default:
|
|
statusElement.textContent = status;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fonction pour afficher une notification
|
|
function showNotification(message, type = 'info') {
|
|
// Créer un élément de notification temporaire
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification notification-${type}`;
|
|
notification.textContent = message;
|
|
|
|
// Styles de base pour la notification
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 70px;
|
|
right: 20px;
|
|
padding: 15px 20px;
|
|
background: ${type === 'success' ? '#4caf50' : type === 'error' ? '#f44336' : '#2196f3'};
|
|
color: white;
|
|
border-radius: 5px;
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|
z-index: 10000;
|
|
animation: slideIn 0.3s ease;
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
// Retirer la notification après 3 secondes
|
|
setTimeout(() => {
|
|
notification.style.animation = 'slideOut 0.3s ease';
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 3000);
|
|
|
|
// Jouer un son si c'est une notification d'appel
|
|
if (type === 'info' && message.includes('Appel entrant')) {
|
|
playNotificationSound();
|
|
}
|
|
}
|
|
|
|
// Fonction pour mettre à jour les statistiques d'appels
|
|
function updateCallStats() {
|
|
// Statistiques supprimées de l'interface
|
|
console.log('Stats:', { calls: callStats.calls, appointments: callStats.appointments });
|
|
}
|
|
|
|
async function saveNotes() {
|
|
const notes = document.getElementById('quickNotes').value;
|
|
if (!notes.trim()) {
|
|
showNotification('Aucune note à sauvegarder', 'error');
|
|
return;
|
|
}
|
|
|
|
// Sauvegarder dans localStorage pour persistance immédiate
|
|
localStorage.setItem('currentNotes', notes);
|
|
localStorage.setItem('currentNotesDate', new Date().toISOString());
|
|
|
|
// Sauvegarder aussi dans un fichier pour historique
|
|
const result = await ipcRenderer.invoke('save-notes', {
|
|
content: notes,
|
|
centre: activeCenter
|
|
});
|
|
|
|
if (result.success) {
|
|
showNotification('Notes sauvegardées avec succès !', 'success');
|
|
// Ajouter à l'historique local
|
|
addToNotesHistory(notes, activeCenter);
|
|
}
|
|
}
|
|
|
|
// Charger les notes sauvegardées au démarrage
|
|
async function loadSavedNotes() {
|
|
const textarea = document.getElementById('quickNotes');
|
|
if (!textarea) return;
|
|
|
|
// D'abord essayer de charger depuis le fichier serveur
|
|
try {
|
|
const serverNotes = await ipcRenderer.invoke('get-notes');
|
|
if (serverNotes && serverNotes.currentNote) {
|
|
textarea.value = serverNotes.currentNote;
|
|
|
|
// Sauvegarder aussi dans localStorage pour synchronisation
|
|
localStorage.setItem('currentNotes', serverNotes.currentNote);
|
|
localStorage.setItem('currentNotesDate', serverNotes.lastModified);
|
|
|
|
const date = new Date(serverNotes.lastModified);
|
|
const formattedDate = date.toLocaleString('fr-FR', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
|
|
showNotification(`Notes restaurées (${formattedDate})`, 'info');
|
|
console.log(`Notes chargées depuis le serveur (${formattedDate})`);
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur chargement notes serveur:', error);
|
|
}
|
|
|
|
// Sinon, charger depuis localStorage
|
|
const savedNotes = localStorage.getItem('currentNotes');
|
|
const savedDate = localStorage.getItem('currentNotesDate');
|
|
|
|
if (savedNotes) {
|
|
textarea.value = savedNotes;
|
|
|
|
if (savedDate) {
|
|
const date = new Date(savedDate);
|
|
const formattedDate = date.toLocaleString('fr-FR', {
|
|
day: '2-digit',
|
|
month: '2-digit',
|
|
year: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
console.log(`Notes restaurées depuis localStorage (${formattedDate})`);
|
|
}
|
|
} else {
|
|
textarea.value = '';
|
|
console.log('Aucune note sauvegardée à restaurer');
|
|
}
|
|
}
|
|
|
|
// Ajouter à l'historique des notes
|
|
function addToNotesHistory(content, centre) {
|
|
let history = JSON.parse(localStorage.getItem('notesHistory') || '[]');
|
|
|
|
history.unshift({
|
|
content: content,
|
|
centre: centre,
|
|
date: new Date().toISOString(),
|
|
id: Date.now()
|
|
});
|
|
|
|
// Garder seulement les 20 dernières notes
|
|
history = history.slice(0, 20);
|
|
|
|
localStorage.setItem('notesHistory', JSON.stringify(history));
|
|
}
|
|
|
|
// Récupérer l'historique des notes
|
|
function getNotesHistory() {
|
|
return JSON.parse(localStorage.getItem('notesHistory') || '[]');
|
|
}
|
|
|
|
// === GESTION DES TERMINAUX ===
|
|
let availableTerminals = []; // Stocker les terminaux disponibles
|
|
let terminalChoices = null; // Instance Choices.js
|
|
|
|
async function loadTerminals() {
|
|
const terminalSelect = document.getElementById('terminal');
|
|
if (!terminalSelect) {
|
|
console.log('Element terminal non trouvé');
|
|
return;
|
|
}
|
|
|
|
console.log('Chargement des terminaux...');
|
|
|
|
try {
|
|
// Récupérer les terminaux depuis le serveur SignalR
|
|
const terminals = await ipcRenderer.invoke('get-terminal-list');
|
|
availableTerminals = terminals || [];
|
|
console.log(`${terminals.length} terminaux récupérés`);
|
|
|
|
// Préparer les options pour Choices.js
|
|
const choicesData = [];
|
|
if (terminals && terminals.length > 0) {
|
|
// Afficher tous les terminaux sans groupement
|
|
terminals.forEach(terminal => {
|
|
choicesData.push({
|
|
value: terminal.toString(),
|
|
label: terminal.toString() // Affichage simple du numéro sans préfixe
|
|
});
|
|
});
|
|
}
|
|
|
|
// Détruire l'instance existante si elle existe
|
|
if (terminalChoices) {
|
|
terminalChoices.destroy();
|
|
terminalChoices = null;
|
|
}
|
|
|
|
// Vider le select avant d'initialiser Choices
|
|
terminalSelect.innerHTML = '';
|
|
|
|
// Attendre que Choices soit disponible (chargé via CDN)
|
|
if (typeof window.Choices === 'undefined') {
|
|
console.log('Choices.js pas encore chargé, utilisation du select natif');
|
|
// Fallback : utiliser le select natif
|
|
terminalSelect.innerHTML = '<option value="">Sélectionner un poste...</option>';
|
|
if (terminals && terminals.length > 0) {
|
|
terminals.forEach(terminal => {
|
|
const option = document.createElement('option');
|
|
option.value = terminal;
|
|
option.textContent = terminal.toString(); // Affichage simple du numéro sans préfixe
|
|
terminalSelect.appendChild(option);
|
|
});
|
|
}
|
|
// Pas de retry automatique pour éviter une boucle infinie
|
|
return;
|
|
}
|
|
|
|
console.log('Initialisation de Choices.js avec', choicesData.length, 'options');
|
|
|
|
try {
|
|
// Créer une nouvelle instance Choices.js
|
|
terminalChoices = new window.Choices(terminalSelect, {
|
|
searchEnabled: true,
|
|
addItems: true, // Permet l'ajout d'items
|
|
addChoices: true, // IMPORTANT: Pour les <select>, permet d'ajouter des choices personnalisés
|
|
searchPlaceholderValue: 'Rechercher ou saisir un poste...',
|
|
itemSelectText: 'Appuyez sur Entrée pour sélectionner',
|
|
noResultsText: 'Aucun poste trouvé. Appuyez sur Entrée pour utiliser ce numéro.',
|
|
noChoicesText: 'Aucun poste disponible',
|
|
addItemText: (value) => {
|
|
return `Utiliser le poste personnalisé : <b>${value}</b>`;
|
|
},
|
|
shouldSort: false,
|
|
searchResultLimit: 20,
|
|
renderChoiceLimit: -1,
|
|
placeholder: true,
|
|
placeholderValue: 'Sélectionner ou saisir un poste téléphonique',
|
|
choices: choicesData.length > 0 ? choicesData : [{value: '', label: 'Aucun poste disponible', disabled: true}],
|
|
searchFields: ['label', 'value'],
|
|
fuseOptions: {
|
|
includeScore: true,
|
|
threshold: 0.3
|
|
},
|
|
classNames: {
|
|
containerOuter: ['choices', 'choices-terminal'],
|
|
containerInner: 'choices__inner',
|
|
input: 'choices__input',
|
|
inputCloned: 'choices__input--cloned',
|
|
list: 'choices__list',
|
|
listItems: 'choices__list--multiple',
|
|
listSingle: 'choices__list--single',
|
|
listDropdown: 'choices__list--dropdown',
|
|
item: 'choices__item',
|
|
itemSelectable: 'choices__item--selectable',
|
|
itemDisabled: 'choices__item--disabled',
|
|
itemChoice: 'choices__item--choice',
|
|
placeholder: 'choices__placeholder',
|
|
group: 'choices__group',
|
|
groupHeading: 'choices__heading',
|
|
button: 'choices__button',
|
|
activeState: 'is-active',
|
|
focusState: 'is-focused',
|
|
openState: 'is-open',
|
|
disabledState: 'is-disabled',
|
|
highlightedState: 'is-highlighted',
|
|
hiddenState: 'is-hidden',
|
|
flippedState: 'is-flipped',
|
|
loadingState: 'is-loading',
|
|
noResults: 'has-no-results',
|
|
noChoices: 'has-no-choices'
|
|
}
|
|
});
|
|
|
|
console.log('Choices.js initialisé avec succès');
|
|
|
|
// Restaurer la dernière sélection si disponible
|
|
const lastTerminal = localStorage.getItem('last-terminal');
|
|
if (lastTerminal && availableTerminals.map(t => t.toString()).includes(lastTerminal)) {
|
|
terminalChoices.setChoiceByValue(lastTerminal.toString());
|
|
}
|
|
|
|
} catch (choicesError) {
|
|
console.error('Erreur lors de l\'initialisation de Choices.js:', choicesError);
|
|
console.error('Stack:', choicesError.stack);
|
|
// En cas d'erreur, utiliser le select natif
|
|
terminalSelect.innerHTML = '<option value="">Erreur de chargement - voir console</option>';
|
|
if (terminals && terminals.length > 0) {
|
|
terminals.forEach(terminal => {
|
|
const option = document.createElement('option');
|
|
option.value = terminal;
|
|
option.textContent = terminal.toString(); // Affichage simple du numéro sans préfixe
|
|
terminalSelect.appendChild(option);
|
|
});
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Erreur générale chargement des terminaux:', error);
|
|
// En cas d'erreur, utiliser le select natif
|
|
const terminalSelect = document.getElementById('terminal');
|
|
if (terminalSelect) {
|
|
terminalSelect.innerHTML = '<option value="">Erreur de chargement</option>';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Valider que le terminal saisi existe dans la liste
|
|
function validateTerminal(terminal) {
|
|
return availableTerminals.map(t => t.toString()).includes(terminal.toString());
|
|
}
|
|
|
|
// === GESTION DES NOTES DYNAMIQUES ===
|
|
function toggleNotes() {
|
|
const notesSection = document.getElementById('notesSection');
|
|
const toggleBtn = document.getElementById('toggleNotesBtn');
|
|
const webviewContainer = document.getElementById('webviewContainer');
|
|
|
|
if (notesSection.classList.contains('visible')) {
|
|
hideNotes();
|
|
} else {
|
|
showNotes();
|
|
}
|
|
}
|
|
|
|
function showNotes() {
|
|
const notesSection = document.getElementById('notesSection');
|
|
const toggleBtn = document.getElementById('toggleNotesBtn');
|
|
const webviewContainer = document.getElementById('webviewContainer');
|
|
|
|
// Restaurer la largeur sauvegardée
|
|
const savedWidth = localStorage.getItem('notesWidth');
|
|
if (savedWidth && notesSection) {
|
|
const width = parseInt(savedWidth);
|
|
if (width >= 280 && width <= 600) {
|
|
notesSection.style.width = `${width}px`;
|
|
document.documentElement.style.setProperty('--notes-width', `${width}px`);
|
|
}
|
|
}
|
|
|
|
// Réinitialiser la position pour l'affichage
|
|
if (notesSection) {
|
|
notesSection.style.right = '0';
|
|
}
|
|
|
|
notesSection.classList.add('visible');
|
|
toggleBtn.classList.add('active');
|
|
|
|
// Ajuster la largeur de la webview pour faire de la place aux notes
|
|
if (webviewContainer) {
|
|
webviewContainer.classList.add('notes-open');
|
|
}
|
|
|
|
// Sauvegarder la préférence
|
|
localStorage.setItem('notesVisible', 'true');
|
|
|
|
// Focus sur le textarea avec un délai pour l'animation
|
|
setTimeout(() => {
|
|
const textarea = document.getElementById('quickNotes');
|
|
if (textarea) {
|
|
textarea.focus();
|
|
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
|
}
|
|
}, 300);
|
|
}
|
|
|
|
function hideNotes() {
|
|
const notesSection = document.getElementById('notesSection');
|
|
const toggleBtn = document.getElementById('toggleNotesBtn');
|
|
const webviewContainer = document.getElementById('webviewContainer');
|
|
|
|
notesSection.classList.remove('visible');
|
|
toggleBtn.classList.remove('active');
|
|
|
|
// Restaurer la largeur normale de la webview
|
|
if (webviewContainer) {
|
|
webviewContainer.classList.remove('notes-open');
|
|
}
|
|
|
|
// S'assurer que le panneau est complètement caché
|
|
if (notesSection) {
|
|
const currentWidth = notesSection.offsetWidth || 380;
|
|
notesSection.style.right = `-${currentWidth + 20}px`;
|
|
}
|
|
|
|
// Sauvegarder la préférence
|
|
localStorage.setItem('notesVisible', 'false');
|
|
}
|
|
|
|
function clearNotes() {
|
|
const textarea = document.getElementById('quickNotes');
|
|
if (textarea && confirm('Êtes-vous sûr de vouloir effacer toutes les notes ?')) {
|
|
textarea.value = '';
|
|
// Effacer aussi de localStorage
|
|
localStorage.removeItem('currentNotes');
|
|
localStorage.removeItem('currentNotesDate');
|
|
textarea.focus();
|
|
showNotification('Notes effacées', 'info');
|
|
}
|
|
}
|
|
|
|
// Sauvegarder automatiquement les notes en cours de frappe (debounced)
|
|
let autoSaveTimeout;
|
|
function setupAutoSave() {
|
|
const textarea = document.getElementById('quickNotes');
|
|
if (textarea) {
|
|
textarea.addEventListener('input', () => {
|
|
clearTimeout(autoSaveTimeout);
|
|
autoSaveTimeout = setTimeout(() => {
|
|
const notes = textarea.value;
|
|
if (notes.trim()) {
|
|
localStorage.setItem('currentNotes', notes);
|
|
localStorage.setItem('currentNotesDate', new Date().toISOString());
|
|
console.log('Notes auto-sauvegardées');
|
|
}
|
|
}, 2000); // Sauvegarder après 2 secondes d'inactivité
|
|
});
|
|
}
|
|
}
|
|
|
|
// === GESTION DES PRÉFÉRENCES UTILISATEUR ===
|
|
function loadUserPreferences() {
|
|
// Charger l'état des notes
|
|
const notesVisible = localStorage.getItem('notesVisible');
|
|
if (notesVisible === 'true') {
|
|
// On n'affiche pas automatiquement les notes au démarrage
|
|
// L'utilisateur devra cliquer pour les afficher
|
|
}
|
|
|
|
// Charger la largeur sauvegardée des notes
|
|
const savedNotesWidth = localStorage.getItem('notesWidth');
|
|
if (savedNotesWidth) {
|
|
const width = parseInt(savedNotesWidth);
|
|
if (width >= 280 && width <= 600) {
|
|
document.documentElement.style.setProperty('--notes-width', `${width}px`);
|
|
const notesSection = document.getElementById('notesSection');
|
|
if (notesSection) {
|
|
notesSection.style.width = `${width}px`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Charger d'autres préférences si nécessaire
|
|
const lastTheme = localStorage.getItem('theme');
|
|
if (lastTheme) {
|
|
document.body.setAttribute('data-theme', lastTheme);
|
|
}
|
|
}
|
|
|
|
function saveUserPreferences() {
|
|
// Cette fonction peut être étendue pour sauvegarder d'autres préférences
|
|
const notesSection = document.getElementById('notesSection');
|
|
const preferences = {
|
|
notesVisible: notesSection.classList.contains('visible'),
|
|
notesWidth: notesSection ? notesSection.offsetWidth : 380,
|
|
theme: document.body.getAttribute('data-theme') || 'light'
|
|
};
|
|
|
|
Object.keys(preferences).forEach(key => {
|
|
localStorage.setItem(key, preferences[key]);
|
|
});
|
|
}
|
|
|
|
// === GESTION DU REDIMENSIONNEMENT DES NOTES ===
|
|
function initNotesResize() {
|
|
const resizeHandle = document.getElementById('notesResizeHandle');
|
|
const notesSection = document.getElementById('notesSection');
|
|
const webviewContainer = document.getElementById('webviewContainer');
|
|
|
|
if (!resizeHandle || !notesSection) return;
|
|
|
|
let rafId = null;
|
|
|
|
resizeHandle.addEventListener('mousedown', (e) => {
|
|
isResizingNotes = true;
|
|
notesStartX = e.clientX;
|
|
notesStartWidth = notesSection.offsetWidth;
|
|
|
|
// Ajouter les classes pour désactiver les transitions
|
|
notesSection.classList.add('resizing');
|
|
if (webviewContainer) {
|
|
webviewContainer.classList.add('resizing');
|
|
}
|
|
|
|
// Empêcher la sélection de texte pendant le redimensionnement
|
|
document.body.style.userSelect = 'none';
|
|
document.body.style.cursor = 'col-resize';
|
|
|
|
// Ajouter un overlay pour capturer tous les mouvements de souris
|
|
const overlay = document.createElement('div');
|
|
overlay.id = 'resize-overlay';
|
|
overlay.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; cursor: col-resize;';
|
|
document.body.appendChild(overlay);
|
|
|
|
e.preventDefault();
|
|
});
|
|
|
|
const handleMouseMove = (e) => {
|
|
if (!isResizingNotes) return;
|
|
|
|
// Utiliser requestAnimationFrame pour la fluidité
|
|
if (rafId) cancelAnimationFrame(rafId);
|
|
|
|
rafId = requestAnimationFrame(() => {
|
|
// Calculer la nouvelle largeur (inversé car le panneau est à droite)
|
|
const deltaX = notesStartX - e.clientX;
|
|
const newWidth = notesStartWidth + deltaX;
|
|
|
|
// Appliquer les limites min/max
|
|
const clampedWidth = Math.min(Math.max(newWidth, 280), 600);
|
|
|
|
// Mettre à jour la largeur avec transform pour plus de fluidité
|
|
notesSection.style.width = `${clampedWidth}px`;
|
|
|
|
// Mettre à jour la variable CSS pour la webview
|
|
document.documentElement.style.setProperty('--notes-width', `${clampedWidth}px`);
|
|
});
|
|
};
|
|
|
|
const handleMouseUp = () => {
|
|
if (!isResizingNotes) return;
|
|
|
|
isResizingNotes = false;
|
|
|
|
// Annuler l'animation frame en cours
|
|
if (rafId) cancelAnimationFrame(rafId);
|
|
|
|
// Retirer l'overlay
|
|
const overlay = document.getElementById('resize-overlay');
|
|
if (overlay) overlay.remove();
|
|
|
|
// Retirer les classes de redimensionnement
|
|
const notesSection = document.getElementById('notesSection');
|
|
const webviewContainer = document.getElementById('webviewContainer');
|
|
|
|
if (notesSection) {
|
|
notesSection.classList.remove('resizing');
|
|
}
|
|
if (webviewContainer) {
|
|
webviewContainer.classList.remove('resizing');
|
|
}
|
|
|
|
// Réactiver la sélection de texte
|
|
document.body.style.userSelect = '';
|
|
document.body.style.cursor = '';
|
|
|
|
// Sauvegarder la nouvelle largeur
|
|
if (notesSection) {
|
|
localStorage.setItem('notesWidth', notesSection.offsetWidth);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('mousemove', handleMouseMove);
|
|
document.addEventListener('mouseup', handleMouseUp);
|
|
}
|
|
|
|
// === FONCTION DE RAFRAÎCHISSEMENT ===
|
|
function refreshCurrentWebview() {
|
|
if (activeCenter && webviews[activeCenter]) {
|
|
webviews[activeCenter].reload();
|
|
console.log(`Rafraîchissement de la webview ${activeCenter}`);
|
|
|
|
// Animation visuelle du bouton
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
if (refreshBtn) {
|
|
refreshBtn.classList.add('rotating');
|
|
setTimeout(() => {
|
|
refreshBtn.classList.remove('rotating');
|
|
}, 1000);
|
|
}
|
|
} else {
|
|
console.log('Aucune webview active à rafraîchir');
|
|
}
|
|
}
|
|
|
|
// === GESTION INDICATEUR SIGNALR ===
|
|
function updateSignalRIndicator(status) {
|
|
const indicator = document.getElementById('signalrIndicator');
|
|
const text = document.getElementById('signalrText');
|
|
|
|
if (!indicator || !text) return;
|
|
|
|
// Réinitialiser les classes
|
|
indicator.className = 'signalr-indicator';
|
|
|
|
switch(status) {
|
|
case 'connected':
|
|
indicator.classList.add('connected');
|
|
text.textContent = 'Connecté au serveur';
|
|
break;
|
|
case 'connecting':
|
|
indicator.classList.add('connecting');
|
|
text.textContent = 'Connexion en cours...';
|
|
break;
|
|
case 'disconnected':
|
|
indicator.classList.add('disconnected');
|
|
text.textContent = 'Serveur déconnecté';
|
|
break;
|
|
case 'error':
|
|
indicator.classList.add('error');
|
|
text.textContent = 'Erreur de connexion';
|
|
break;
|
|
case 'disabled':
|
|
indicator.classList.add('disabled');
|
|
text.textContent = 'SignalR désactivé';
|
|
break;
|
|
default:
|
|
text.textContent = 'État inconnu';
|
|
}
|
|
} |