connexion avec signalR

This commit is contained in:
Pierre Marx
2025-09-04 14:18:36 -04:00
parent d6f89ed686
commit 6069a7238b
3 changed files with 389 additions and 48 deletions

249
main.js
View File

@@ -6,8 +6,10 @@ const signalR = require('@microsoft/signalr');
let mainWindow; let mainWindow;
let config; let config;
let currentAgent = null; let currentAgent = null;
let currentTerminal = null; // Terminal téléphonique de l'agent connecté
let signalRConnection = null; let signalRConnection = null;
let signalRStatus = 'disconnected'; // disconnected, connecting, connected, error let signalRStatus = 'disconnected'; // disconnected, connecting, connected, error
let agentConnectionInfo = null; // Informations complètes retournées par SignalR
// Charger la configuration // Charger la configuration
function loadConfig() { function loadConfig() {
@@ -97,10 +99,70 @@ function initializeSignalR() {
function setupSignalRMethods() { function setupSignalRMethods() {
// Écouter les événements IPBX // Écouter les événements IPBX
signalRConnection.on('IpbxEvent', (name, args) => { signalRConnection.on('IpbxEvent', (name, args) => {
if (!args) return; if (!args || !agentConnectionInfo) return;
const event = args[0]; const event = args[0];
console.log('Événement IPBX reçu:', event); console.log('Événement IPBX reçu:', {
// TODO: Gérer les événements d'appel eventCode: event.eventCode,
terminal: event.terminal,
queueName: event.queueName
});
// Vérifier que l'événement est pour notre terminal
if (event.terminal !== currentTerminal) {
console.log('Événement ignoré - Terminal différent:', event.terminal, '!==', currentTerminal);
return;
}
// Gérer les différents types d'événements
switch(event.eventCode) {
case 1: // Appel décroché
handleCallPickedUp(event);
break;
case 2: // Appel raccroché
handleCallHungUp(event);
break;
default:
console.log('Code événement non géré:', event.eventCode);
}
});
}
// Gérer un appel entrant
function handleCallPickedUp(event) {
if (!mainWindow || !agentConnectionInfo) return;
// Identifier le centre correspondant à la file
const centres = processApplicationUrls(agentConnectionInfo.connList);
const centre = centres.find(c => c.queueName === event.queueName);
if (centre) {
console.log('Basculement vers le centre:', centre.nom);
// Envoyer l'instruction de basculement à la fenêtre
mainWindow.webContents.send('switch-to-center', {
centreId: centre.id,
centreName: centre.nom,
queueName: event.queueName,
terminal: event.terminal,
eventType: 'call_pickup'
});
} else {
console.warn('Aucun centre trouvé pour la file:', event.queueName);
}
}
// Gérer la fin d'un appel
function handleCallHungUp(event) {
if (!mainWindow) return;
console.log('Fin d\'appel sur la file:', event.queueName);
// Envoyer l'instruction de libération à la fenêtre
mainWindow.webContents.send('release-center', {
queueName: event.queueName,
terminal: event.terminal,
eventType: 'call_hangup'
}); });
} }
@@ -159,7 +221,22 @@ app.whenReady().then(() => {
}); });
// Quitter quand toutes les fenêtres sont fermées // Quitter quand toutes les fenêtres sont fermées
app.on('window-all-closed', () => { app.on('window-all-closed', async () => {
// Déconnexion propre avant de quitter
if (currentAgent && signalRConnection && signalRStatus === 'connected') {
try {
await signalRConnection.invoke('AgentLogoff', currentAgent.accessCode);
console.log('Déconnexion agent avant fermeture');
} catch (error) {
console.error('Erreur déconnexion:', error);
}
}
// Arrêter SignalR
if (signalRConnection) {
await signalRConnection.stop();
}
if (process.platform !== 'darwin') { if (process.platform !== 'darwin') {
app.quit(); app.quit();
} }
@@ -208,46 +285,158 @@ ipcMain.handle('get-terminal-list', async () => {
} }
}); });
// Connexion agent // Connexion agent via SignalR
ipcMain.handle('login-agent', (event, credentials) => { ipcMain.handle('login-agent', async (event, credentials) => {
const agent = config.agents.find(a => // Vérifier que SignalR est connecté
a.email === credentials.email && if (!signalRConnection || signalRStatus !== 'connected') {
a.password === credentials.password return {
); success: false,
message: 'Connexion au serveur SignalR non établie. Veuillez réessayer.'
if (agent) { };
currentAgent = agent; }
// Retourner l'agent avec ses centres assignés
const centresAssignes = config.centres.filter(c => try {
agent.centresAssignes.includes(c.id) console.log('Tentative de connexion agent:', credentials.email, 'Terminal:', credentials.terminal);
);
return { // Appel SignalR pour l'authentification
success: true, const result = await signalRConnection.invoke('AgentLogin',
agent: agent, credentials.email,
centres: centresAssignes credentials.password,
credentials.terminal
);
if (result) {
console.log('Connexion réussie:', result);
// Stocker les informations de connexion
agentConnectionInfo = result;
currentTerminal = credentials.terminal;
// Créer l'objet agent pour compatibilité
currentAgent = {
id: result.accessCode,
accessCode: result.accessCode,
name: `${result.firstName} ${result.lastName}`,
email: credentials.email,
firstName: result.firstName,
lastName: result.lastName,
terminal: credentials.terminal
};
// Traiter les URLs des applications et créer les centres
const centres = processApplicationUrls(result.connList);
// Mettre à jour le titre de la fenêtre
if (mainWindow) {
mainWindow.setTitle(
`SimpleConnect - Agent: ${currentAgent.accessCode} (${result.firstName} ${result.lastName}) - Tel: ${credentials.terminal}`
);
}
return {
success: true,
agent: currentAgent,
centres: centres
};
}
return { success: false, message: 'Échec de l\'authentification' };
} catch (error) {
console.error('Erreur lors de la connexion agent:', error);
return {
success: false,
message: error.message || 'Erreur de connexion au serveur'
}; };
} }
return { success: false, message: 'Email ou mot de passe incorrect' };
}); });
// Déconnexion // Traiter les URLs des applications avec les placeholders
ipcMain.handle('logout', () => { function processApplicationUrls(connList) {
if (!connList || connList.length === 0) return [];
return connList.map((conn, index) => {
let url = conn.applicationName;
// Remplacer les placeholders
if (url.includes('#CA#')) {
url = url.replace('#CA#', conn.accessCode || '');
}
if (url.includes('#MP#')) {
url = url.replace('#MP#', conn.password || '');
}
// Gérer les cas spécifiques des plateformes connues
if (url === 'pro.mondocteur.fr' || url.includes('mondocteur.fr')) {
if (!url.startsWith('http')) {
url = 'https://pro.mondocteur.fr/backoffice.do';
}
} else if (url === 'pro.doctolib.fr' || url.includes('doctolib.fr')) {
if (!url.startsWith('http')) {
url = 'https://pro.doctolib.fr/signin';
}
} else if (!url.startsWith('http')) {
// Ajouter https:// par défaut si pas de protocole
url = `https://${url}`;
}
// Créer l'objet centre compatible avec l'interface
return {
id: conn.code || `centre${index + 1}`,
nom: conn.queueName || conn.code || `Centre ${index + 1}`,
url: url,
couleur: getColorForIndex(index),
credentials: {
username: conn.accessCode,
password: conn.password
},
queueName: conn.queueName // Garder pour le mapping avec les événements IPBX
};
});
}
// Fonction helper pour attribuer des couleurs aux centres
function getColorForIndex(index) {
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#DDA0DD', '#FFD93D', '#6BCB77'];
return colors[index % colors.length];
}
// Déconnexion agent via SignalR
ipcMain.handle('logout', async () => {
if (currentAgent && signalRConnection && signalRStatus === 'connected') {
try {
// Appeler SignalR pour la déconnexion
await signalRConnection.invoke('AgentLogoff', currentAgent.accessCode);
console.log('Agent déconnecté du serveur SignalR');
} catch (error) {
console.error('Erreur lors de la déconnexion SignalR:', error);
}
}
// Réinitialiser les variables locales
currentAgent = null; currentAgent = null;
currentTerminal = null;
agentConnectionInfo = null;
// Réinitialiser le titre de la fenêtre
if (mainWindow) {
mainWindow.setTitle('SimpleConnect - Gestion Centralisée des Plannings');
}
return { success: true }; return { success: true };
}); });
// Obtenir l'agent actuel // Obtenir l'agent actuel
ipcMain.handle('get-current-agent', () => { ipcMain.handle('get-current-agent', () => {
if (!currentAgent) return null; if (!currentAgent || !agentConnectionInfo) return null;
const centresAssignes = config.centres.filter(c => // Retourner les centres traités depuis SignalR
currentAgent.centresAssignes.includes(c.id) const centres = processApplicationUrls(agentConnectionInfo.connList);
);
return { return {
agent: currentAgent, agent: currentAgent,
centres: centresAssignes centres: centres,
terminal: currentTerminal
}; };
}); });

View File

@@ -65,9 +65,41 @@ document.addEventListener('DOMContentLoaded', async () => {
ipcRenderer.on('incoming-call', (event, callData) => { ipcRenderer.on('incoming-call', (event, callData) => {
handleIncomingCall(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 // Connexion via SignalR
async function handleLogin(e) { async function handleLogin(e) {
e.preventDefault(); e.preventDefault();
@@ -75,6 +107,7 @@ async function handleLogin(e) {
const password = document.getElementById('password').value; const password = document.getElementById('password').value;
const terminal = document.getElementById('terminal').value; const terminal = document.getElementById('terminal').value;
const errorDiv = document.getElementById('loginError'); const errorDiv = document.getElementById('loginError');
const loginBtn = document.querySelector('#loginForm button[type="submit"]');
// Vérifier que le terminal est sélectionné et valide // Vérifier que le terminal est sélectionné et valide
if (!terminal) { if (!terminal) {
@@ -91,24 +124,38 @@ async function handleLogin(e) {
// Sauvegarder le terminal sélectionné pour la prochaine fois // Sauvegarder le terminal sélectionné pour la prochaine fois
localStorage.setItem('last-terminal', terminal); localStorage.setItem('last-terminal', terminal);
// Pour l'instant, utiliser l'authentification locale (simulation) // Désactiver le bouton pendant la connexion
// TODO: Intégrer l'authentification SignalR loginBtn.disabled = true;
const config = await ipcRenderer.invoke('get-config'); loginBtn.textContent = 'Connexion en cours...';
const agent = config.agents.find(a => errorDiv.textContent = '';
a.email === `${accessCode}@callcenter.fr` &&
a.password === password
);
if (agent) { try {
currentAgent = agent; // Préparer les credentials pour SignalR
currentAgent.terminal = terminal; // Ajouter le terminal sélectionné const credentials = {
currentCentres = config.centres.filter(c => email: accessCode, // Utiliser directement le code agent comme email
agent.centresAssignes.includes(c.id) password: password,
); terminal: terminal
errorDiv.textContent = ''; };
showMainPage();
} else { // Appeler l'authentification SignalR
errorDiv.textContent = 'Code d\'accès ou mot de passe incorrect'; 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);
showMainPage();
} else {
errorDiv.textContent = result.message || 'Identifiants incorrects';
loginBtn.disabled = false;
loginBtn.textContent = 'Se connecter';
}
} catch (error) {
console.error('Erreur lors de la connexion:', error);
errorDiv.textContent = 'Erreur de connexion. Veuillez réessayer.';
loginBtn.disabled = false;
loginBtn.textContent = 'Se connecter';
} }
} }
@@ -421,6 +468,76 @@ function playNotificationSound() {
oscillator.stop(audioContext.currentTime + 0.2); 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() {
const callCountElement = document.getElementById('callCount');
const appointmentCountElement = document.getElementById('appointmentCount');
if (callCountElement) {
callCountElement.textContent = callStats.calls;
}
if (appointmentCountElement) {
appointmentCountElement.textContent = callStats.appointments;
}
}
async function saveNotes() { async function saveNotes() {
const notes = document.getElementById('quickNotes').value; const notes = document.getElementById('quickNotes').value;
if (!notes.trim()) return; if (!notes.trim()) return;

View File

@@ -13,6 +13,41 @@ body {
overflow: hidden; overflow: hidden;
} }
/* === ANIMATIONS === */
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(102, 126, 234, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0);
}
}
/* === PAGES === */ /* === PAGES === */
.page { .page {
display: none; display: none;