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 () => { // 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 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) { 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); // 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'; } } // 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: 20px; 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: `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); }); } // 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, 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 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'; } }