choices.js pour la liste déroulante

This commit is contained in:
Pierre Marx
2025-09-04 13:37:31 -04:00
parent a22344d664
commit c7c48360fd
8 changed files with 392 additions and 39 deletions

1
choices.min.css vendored Normal file

File diff suppressed because one or more lines are too long

2
choices.min.js vendored Normal file

File diff suppressed because one or more lines are too long

43
docs/changelog.md Normal file
View File

@@ -0,0 +1,43 @@
# Changelog - SimpleConnect Electron
## [1.0.1] - 2024-09-04
### Ajouté
- **Intégration de Choices.js** pour améliorer l'expérience utilisateur sur le champ de sélection des postes téléphoniques
- Liste déroulante moderne avec recherche instantanée
- Support de plus de 100 postes téléphoniques
- Interface utilisateur améliorée avec scrollbar personnalisée
- Recherche en temps réel pour trouver rapidement un poste
### Modifié
- **Page de connexion** : Remplacement du select HTML natif par Choices.js
- Amélioration visuelle avec thème personnalisé (violet #667eea)
- Ajout d'animations fluides à l'ouverture/fermeture
- Indicateurs visuels pour les éléments sélectionnés
- **Chargement des ressources** : Migration des CDN vers des fichiers locaux pour éviter les problèmes CSP
- **Gestion des erreurs** : Amélioration du fallback en cas d'échec de chargement de Choices.js
### Technique
- Installation locale de Choices.js via npm
- Copie des fichiers CSS/JS dans le répertoire racine
- Adaptation du code pour gérer window.Choices dans le contexte Electron
- Suppression du groupement par centaine pour simplifier la navigation
### Corrigé
- Résolution des erreurs Content Security Policy (CSP) avec les ressources CDN
- Correction de l'initialisation de Choices.js dans l'environnement Electron
- Fix des classes CSS pour éviter les erreurs DOMTokenList
## [1.0.0] - 2024-09-01
### Fonctionnalités initiales
- **Connexion agent** avec authentification locale
- **Intégration SignalR** pour la communication avec le serveur CTI
- **Récupération dynamique** des postes téléphoniques depuis le serveur
- **Multi-centres** : Gestion de plusieurs centres médicaux
- **Webviews intégrées** pour afficher les plannings médicaux
- **Simulation d'appels** pour les tests et démos
- **Interface moderne** avec design violet/blanc
- **Statistiques journalières** : Compteurs d'appels et de RDV
- **Notes rapides** : Prise de notes pendant les appels
- **Indicateurs visuels** : États de connexion SignalR et disponibilité agent

View File

@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SimpleConnect - Gestion Centralisée des Plannings</title>
<link rel="stylesheet" href="choices.min.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
@@ -19,9 +20,9 @@
<form id="loginForm">
<input type="text" id="accessCode" placeholder="Code d'accès" required>
<input type="password" id="password" placeholder="Mot de passe" required>
<input type="text" id="terminal" list="terminalList" placeholder="Poste téléphonique" required autocomplete="off">
<datalist id="terminalList">
</datalist>
<select id="terminal" required>
<option value="" placeholder>Chargement des postes...</option>
</select>
<button type="submit">Se connecter</button>
<div id="loginError" class="error-message"></div>
</form>
@@ -113,6 +114,7 @@
</div>
<!-- Scripts -->
<script src="choices.min.js"></script>
<script src="renderer.js"></script>
<script src="cti-simulator.js"></script>
</body>

View File

@@ -34,10 +34,10 @@ function createWindow() {
// Charger l'interface HTML
mainWindow.loadFile('index.html');
// Ouvrir les DevTools en mode développement
if (process.env.NODE_ENV === 'development') {
// Ouvrir les DevTools en mode développement ou toujours pour debug
// if (process.env.NODE_ENV === 'development') {
mainWindow.webContents.openDevTools();
}
// }
// Gérer la fermeture de la fenêtre
mainWindow.on('closed', () => {

View File

@@ -67,6 +67,7 @@
"url": "https://github.com/simpleconnect/electron-app.git"
},
"dependencies": {
"@microsoft/signalr": "^9.0.6"
"@microsoft/signalr": "^9.0.6",
"choices.js": "^11.1.0"
}
}

View File

@@ -1,4 +1,5 @@
const { ipcRenderer } = require('electron');
// Choices est maintenant chargé via CDN dans index.html
// Variables globales
let currentAgent = null;
@@ -26,8 +27,8 @@ document.addEventListener('DOMContentLoaded', async () => {
const initialStatus = await ipcRenderer.invoke('get-signalr-status');
updateSignalRIndicator(initialStatus);
// Toujours charger les terminaux (simulation ou réels)
loadTerminals();
// 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');
@@ -438,53 +439,149 @@ async function saveNotes() {
// === GESTION DES TERMINAUX ===
let availableTerminals = []; // Stocker les terminaux disponibles
let terminalChoices = null; // Instance Choices.js
async function loadTerminals() {
const terminalInput = document.getElementById('terminal');
const terminalDatalist = document.getElementById('terminalList');
if (!terminalInput || !terminalDatalist) return;
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) {
// Vider la datalist
terminalDatalist.innerHTML = '';
// Ajouter chaque terminal comme option
// Afficher tous les terminaux sans groupement
terminals.forEach(terminal => {
const option = document.createElement('option');
option.value = terminal;
terminalDatalist.appendChild(option);
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 = '<option value="">Sélectionner un poste...</option>';
if (terminals && terminals.length > 0) {
terminals.forEach(terminal => {
const option = document.createElement('option');
option.value = terminal;
option.textContent = `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 && terminals.includes(lastTerminal)) {
terminalInput.value = lastTerminal;
if (lastTerminal && availableTerminals.map(t => t.toString()).includes(lastTerminal)) {
terminalChoices.setChoiceByValue(lastTerminal.toString());
}
// Activer l'input
terminalInput.disabled = false;
terminalInput.placeholder = 'Poste téléphonique (tapez pour filtrer)';
} else {
terminalDatalist.innerHTML = '';
terminalInput.placeholder = 'Aucun poste disponible';
terminalInput.disabled = true;
} catch (choicesError) {
console.error('Erreur lors de l\'initialisation de Choices.js:', choicesError);
console.error('Stack:', choicesError.stack);
// En cas d'erreur, utiliser le select natif
terminalSelect.innerHTML = '<option value="">Erreur de chargement - voir console</option>';
if (terminals && terminals.length > 0) {
terminals.forEach(terminal => {
const option = document.createElement('option');
option.value = terminal;
option.textContent = `Poste ${terminal}`;
terminalSelect.appendChild(option);
});
}
}
} catch (error) {
console.error('Erreur chargement des terminaux:', error);
terminalDatalist.innerHTML = '';
terminalInput.placeholder = 'Erreur de chargement';
terminalInput.disabled = true;
console.error('Erreur générale chargement des terminaux:', error);
// En cas d'erreur, utiliser le select natif
const terminalSelect = document.getElementById('terminal');
if (terminalSelect) {
terminalSelect.innerHTML = '<option value="">Erreur de chargement</option>';
}
}
}
// Valider que le terminal saisi existe dans la liste
function validateTerminal(terminal) {
return availableTerminals.includes(terminal);
return availableTerminals.map(t => t.toString()).includes(terminal.toString());
}
// === GESTION INDICATEUR SIGNALR ===

File diff suppressed because one or more lines are too long