choices.js pour la liste déroulante
This commit is contained in:
1
choices.min.css
vendored
Normal file
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
2
choices.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
43
docs/changelog.md
Normal file
43
docs/changelog.md
Normal 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
|
||||
@@ -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>
|
||||
|
||||
6
main.js
6
main.js
@@ -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', () => {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
143
renderer.js
143
renderer.js
@@ -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 = '';
|
||||
// Afficher tous les terminaux sans groupement
|
||||
terminals.forEach(terminal => {
|
||||
choicesData.push({
|
||||
value: terminal.toString(),
|
||||
label: `Poste ${terminal}`
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Ajouter chaque terminal comme option
|
||||
// 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;
|
||||
terminalDatalist.appendChild(option);
|
||||
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 ===
|
||||
|
||||
219
styles.css
219
styles.css
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user