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 }; // === GESTION DE LA CONNEXION === document.addEventListener('DOMContentLoaded', async () => { // Initialiser l'indicateur SignalR updateSignalRStatus(); // É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(); } // Gestionnaire du formulaire de connexion const loginForm = document.getElementById('loginForm'); if (loginForm) { loginForm.addEventListener('submit', handleLogin); } // Bouton de déconnexion const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', handleLogout); } // Bouton simulation d'appel const simulateBtn = document.getElementById('simulateCallBtn'); if (simulateBtn) { simulateBtn.addEventListener('click', showCallSimulation); } // Bouton sauvegarder notes const saveNotesBtn = document.getElementById('saveNotesBtn'); if (saveNotesBtn) { saveNotesBtn.addEventListener('click', saveNotes); } // Écouter les appels entrants ipcRenderer.on('incoming-call', (event, callData) => { handleIncomingCall(callData); }); }); // Connexion 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'); // Vérifier que le terminal est sélectionné et valide if (!terminal) { errorDiv.textContent = 'Veuillez sélectionner un poste téléphonique'; return; } // Valider que le terminal existe dans la liste if (!validateTerminal(terminal)) { errorDiv.textContent = 'Poste téléphonique invalide. Veuillez choisir un poste de la liste.'; return; } // Sauvegarder le terminal sélectionné pour la prochaine fois localStorage.setItem('last-terminal', terminal); // Pour l'instant, utiliser l'authentification locale (simulation) // TODO: Intégrer l'authentification SignalR const config = await ipcRenderer.invoke('get-config'); const agent = config.agents.find(a => a.email === `${accessCode}@callcenter.fr` && a.password === password ); if (agent) { currentAgent = agent; currentAgent.terminal = terminal; // Ajouter le terminal sélectionné currentCentres = config.centres.filter(c => agent.centresAssignes.includes(c.id) ); errorDiv.textContent = ''; showMainPage(); } else { errorDiv.textContent = 'Code d\'accès ou mot de passe incorrect'; } } // Déconnexion async function handleLogout() { if (confirm('Voulez-vous vraiment vous déconnecter ?')) { await ipcRenderer.invoke('logout'); currentAgent = null; currentCentres = []; activeCenter = null; webviews = {}; showLoginPage(); } } // === GESTION DES PAGES === function showLoginPage() { document.getElementById('loginPage').classList.add('active'); document.getElementById('mainPage').classList.remove('active'); } 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'); } // === GESTION DES CENTRES === function initializeCenters() { const centersList = document.getElementById('centersList'); const centerTabs = document.getElementById('centerTabs'); const webviewContainer = document.getElementById('webviewContainer'); // Vider les contenus existants centersList.innerHTML = ''; centerTabs.innerHTML = ''; webviewContainer.innerHTML = ''; // Créer la liste des centres et les onglets currentCentres.forEach(centre => { // Élément dans la sidebar const centerItem = document.createElement('div'); centerItem.className = 'center-item'; centerItem.dataset.centerId = centre.id; centerItem.innerHTML = `
${centre.nom}
${centre.telephone}
`; centerItem.addEventListener('click', () => selectCenter(centre.id)); centersList.appendChild(centerItem); // Onglet const tab = document.createElement('div'); tab.className = 'tab'; tab.dataset.centerId = centre.id; tab.style.borderBottomColor = centre.couleur; tab.textContent = centre.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'); // Barre d'outils pour la webview const toolbar = document.createElement('div'); toolbar.className = 'webview-toolbar'; toolbar.innerHTML = ` ${centre.url} `; webviewWrapper.appendChild(toolbar); 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`); document.getElementById(`status-${centre.id}`).style.color = '#4CAF50'; // 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-navigate', (e) => { document.getElementById(`url-${centre.id}`).textContent = e.url; }); webview.addEventListener('did-fail-load', (e) => { console.error(`Erreur chargement ${centre.nom}:`, e); document.getElementById(`status-${centre.id}`).style.color = '#FF5252'; }); }); } // Sélectionner un centre function selectCenter(centerId) { // Mettre à jour le centre actif activeCenter = centerId; // Mettre à jour l'UI document.querySelectorAll('.center-item').forEach(item => { item.classList.toggle('active', item.dataset.centerId === 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'; }); } // Navigation dans les webviews window.navigateWebview = function(centerId, action) { const webview = webviews[centerId]; if (webview) { switch(action) { case 'back': webview.goBack(); break; case 'forward': webview.goForward(); break; case 'reload': webview.reload(); break; } } }; // === 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++; document.getElementById('callCount').textContent = 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++; document.getElementById('appointmentCount').textContent = callStats.appointments; } // === SIMULATION D'APPELS === function showCallSimulation() { const modal = document.getElementById('callSimulationModal'); modal.style.display = 'block'; // Charger les appels simulés loadSimulatedCalls(); // Fermer la modal modal.querySelector('.close').onclick = () => { modal.style.display = 'none'; }; // Appel personnalisé document.getElementById('customCallBtn').onclick = () => { const customCall = { numero: prompt('Numéro de téléphone:'), nom: prompt('Nom du patient:'), centreId: currentCentres[0]?.id }; if (customCall.numero) { ipcRenderer.invoke('simulate-call', customCall); modal.style.display = 'none'; } }; } async function loadSimulatedCalls() { const calls = await ipcRenderer.invoke('get-simulated-calls'); const listDiv = document.getElementById('simulatedCallsList'); listDiv.innerHTML = calls.map(call => `
${call.nom}
${call.numero} - ${call.centreNom}
`).join(''); } window.simulateThisCall = function(callDataStr) { const callData = JSON.parse(callDataStr); ipcRenderer.invoke('simulate-call', callData); document.getElementById('callSimulationModal').style.display = 'none'; }; // === 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); } async function saveNotes() { const notes = document.getElementById('quickNotes').value; if (!notes.trim()) return; const result = await ipcRenderer.invoke('save-notes', { content: notes, centre: activeCenter }); if (result.success) { alert('Notes sauvegardées !'); document.getElementById('quickNotes').value = ''; } } // === 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: `Poste ${terminal}` }); }); } // 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 = `Poste ${terminal}`; terminalSelect.appendChild(option); }); } // Réessayer après un court délai setTimeout(() => loadTerminals(), 500); 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, searchPlaceholderValue: 'Rechercher un poste...', itemSelectText: '', noResultsText: 'Aucun poste trouvé', noChoicesText: 'Aucun poste disponible', shouldSort: false, searchResultLimit: 20, renderChoiceLimit: -1, placeholder: true, placeholderValue: 'Sélectionner 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 = ''; if (terminals && terminals.length > 0) { terminals.forEach(terminal => { const option = document.createElement('option'); option.value = terminal; option.textContent = `Poste ${terminal}`; 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 = ''; } } } // Valider que le terminal saisi existe dans la liste function validateTerminal(terminal) { return availableTerminals.map(t => t.toString()).includes(terminal.toString()); } // === GESTION INDICATEUR SIGNALR === function updateSignalRStatus() { // Fonction appelée au chargement pour initialiser l'interface } 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'; } }