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 de statut serveur // Ecouter les changements de statut serveur let previousServerStatus = null; ipcRenderer.on('server-status', (event, status) => { updateServerIndicator(status); // Recharger les terminaux uniquement quand la connexion (re)monte if (status === 'connected' && previousServerStatus !== 'connected') { loadTerminals(); } previousServerStatus = status; }); // Obtenir le statut initial const initialStatus = await ipcRenderer.invoke('get-server-status'); updateServerIndicator(initialStatus); if (initialStatus === 'connected') { loadTerminals(); } previousServerStatus = initialStatus; // 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); }); // Ecouter les evenements 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.queue_name); // Mettre à jour le statut updateAgentStatus('DISPONIBLE'); // Incrementer le compteur d'appels callStats.calls++; updateCallStats(); // Afficher une notification showNotification('Appel terminé', 'success'); }); }); // Connexion agent 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 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(); // Désactiver le bouton pendant la connexion loginBtn.disabled = true; loginBtn.textContent = 'Connexion en cours...'; errorDiv.textContent = ''; // Attendre un peu pour que l'animation soit visible await new Promise(resolve => setTimeout(resolve, 300)); try { // Preparer les credentials const credentials = { email: accessCode, // Utiliser directement le code agent comme email password: password, terminal: terminal, }; // Appeler l'authentification 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 loginError = document.getElementById('loginError'); const loginBtn = document.querySelector('#loginForm button[type="submit"]'); if (accessCode) accessCode.value = ''; if (password) password.value = ''; // Vider les messages d'erreur if (loginError) loginError.textContent = ''; // Remettre le texte par défaut — l'état disabled depend du statut serveur if (loginBtn) { 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 { // Recuperer les terminaux depuis le serveur 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 = ''; 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