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 charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>SimpleConnect - Gestion Centralisée des Plannings</title>
|
<title>SimpleConnect - Gestion Centralisée des Plannings</title>
|
||||||
|
<link rel="stylesheet" href="choices.min.css">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -19,9 +20,9 @@
|
|||||||
<form id="loginForm">
|
<form id="loginForm">
|
||||||
<input type="text" id="accessCode" placeholder="Code d'accès" required>
|
<input type="text" id="accessCode" placeholder="Code d'accès" required>
|
||||||
<input type="password" id="password" placeholder="Mot de passe" 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">
|
<select id="terminal" required>
|
||||||
<datalist id="terminalList">
|
<option value="" placeholder>Chargement des postes...</option>
|
||||||
</datalist>
|
</select>
|
||||||
<button type="submit">Se connecter</button>
|
<button type="submit">Se connecter</button>
|
||||||
<div id="loginError" class="error-message"></div>
|
<div id="loginError" class="error-message"></div>
|
||||||
</form>
|
</form>
|
||||||
@@ -113,6 +114,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
|
<script src="choices.min.js"></script>
|
||||||
<script src="renderer.js"></script>
|
<script src="renderer.js"></script>
|
||||||
<script src="cti-simulator.js"></script>
|
<script src="cti-simulator.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
6
main.js
6
main.js
@@ -34,10 +34,10 @@ function createWindow() {
|
|||||||
// Charger l'interface HTML
|
// Charger l'interface HTML
|
||||||
mainWindow.loadFile('index.html');
|
mainWindow.loadFile('index.html');
|
||||||
|
|
||||||
// Ouvrir les DevTools en mode développement
|
// Ouvrir les DevTools en mode développement ou toujours pour debug
|
||||||
if (process.env.NODE_ENV === 'development') {
|
// if (process.env.NODE_ENV === 'development') {
|
||||||
mainWindow.webContents.openDevTools();
|
mainWindow.webContents.openDevTools();
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Gérer la fermeture de la fenêtre
|
// Gérer la fermeture de la fenêtre
|
||||||
mainWindow.on('closed', () => {
|
mainWindow.on('closed', () => {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
"url": "https://github.com/simpleconnect/electron-app.git"
|
"url": "https://github.com/simpleconnect/electron-app.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@microsoft/signalr": "^9.0.6"
|
"@microsoft/signalr": "^9.0.6",
|
||||||
|
"choices.js": "^11.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
149
renderer.js
149
renderer.js
@@ -1,4 +1,5 @@
|
|||||||
const { ipcRenderer } = require('electron');
|
const { ipcRenderer } = require('electron');
|
||||||
|
// Choices est maintenant chargé via CDN dans index.html
|
||||||
|
|
||||||
// Variables globales
|
// Variables globales
|
||||||
let currentAgent = null;
|
let currentAgent = null;
|
||||||
@@ -26,8 +27,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
const initialStatus = await ipcRenderer.invoke('get-signalr-status');
|
const initialStatus = await ipcRenderer.invoke('get-signalr-status');
|
||||||
updateSignalRIndicator(initialStatus);
|
updateSignalRIndicator(initialStatus);
|
||||||
|
|
||||||
// Toujours charger les terminaux (simulation ou réels)
|
// Charger immédiatement les terminaux pour la page de login
|
||||||
loadTerminals();
|
await loadTerminals();
|
||||||
|
|
||||||
// Vérifier si un agent est déjà connecté
|
// Vérifier si un agent est déjà connecté
|
||||||
const agentData = await ipcRenderer.invoke('get-current-agent');
|
const agentData = await ipcRenderer.invoke('get-current-agent');
|
||||||
@@ -438,53 +439,149 @@ async function saveNotes() {
|
|||||||
|
|
||||||
// === GESTION DES TERMINAUX ===
|
// === GESTION DES TERMINAUX ===
|
||||||
let availableTerminals = []; // Stocker les terminaux disponibles
|
let availableTerminals = []; // Stocker les terminaux disponibles
|
||||||
|
let terminalChoices = null; // Instance Choices.js
|
||||||
|
|
||||||
async function loadTerminals() {
|
async function loadTerminals() {
|
||||||
const terminalInput = document.getElementById('terminal');
|
const terminalSelect = document.getElementById('terminal');
|
||||||
const terminalDatalist = document.getElementById('terminalList');
|
if (!terminalSelect) {
|
||||||
if (!terminalInput || !terminalDatalist) return;
|
console.log('Element terminal non trouvé');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Chargement des terminaux...');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Récupérer les terminaux depuis le serveur SignalR
|
// Récupérer les terminaux depuis le serveur SignalR
|
||||||
const terminals = await ipcRenderer.invoke('get-terminal-list');
|
const terminals = await ipcRenderer.invoke('get-terminal-list');
|
||||||
availableTerminals = terminals || [];
|
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) {
|
if (terminals && terminals.length > 0) {
|
||||||
// Vider la datalist
|
// Afficher tous les terminaux sans groupement
|
||||||
terminalDatalist.innerHTML = '';
|
|
||||||
|
|
||||||
// Ajouter chaque terminal comme option
|
|
||||||
terminals.forEach(terminal => {
|
terminals.forEach(terminal => {
|
||||||
const option = document.createElement('option');
|
choicesData.push({
|
||||||
option.value = terminal;
|
value: terminal.toString(),
|
||||||
terminalDatalist.appendChild(option);
|
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
|
// Restaurer la dernière sélection si disponible
|
||||||
const lastTerminal = localStorage.getItem('last-terminal');
|
const lastTerminal = localStorage.getItem('last-terminal');
|
||||||
if (lastTerminal && terminals.includes(lastTerminal)) {
|
if (lastTerminal && availableTerminals.map(t => t.toString()).includes(lastTerminal)) {
|
||||||
terminalInput.value = lastTerminal;
|
terminalChoices.setChoiceByValue(lastTerminal.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activer l'input
|
} catch (choicesError) {
|
||||||
terminalInput.disabled = false;
|
console.error('Erreur lors de l\'initialisation de Choices.js:', choicesError);
|
||||||
terminalInput.placeholder = 'Poste téléphonique (tapez pour filtrer)';
|
console.error('Stack:', choicesError.stack);
|
||||||
} else {
|
// En cas d'erreur, utiliser le select natif
|
||||||
terminalDatalist.innerHTML = '';
|
terminalSelect.innerHTML = '<option value="">Erreur de chargement - voir console</option>';
|
||||||
terminalInput.placeholder = 'Aucun poste disponible';
|
if (terminals && terminals.length > 0) {
|
||||||
terminalInput.disabled = true;
|
terminals.forEach(terminal => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = terminal;
|
||||||
|
option.textContent = `Poste ${terminal}`;
|
||||||
|
terminalSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Erreur chargement des terminaux:', error);
|
console.error('Erreur générale chargement des terminaux:', error);
|
||||||
terminalDatalist.innerHTML = '';
|
// En cas d'erreur, utiliser le select natif
|
||||||
terminalInput.placeholder = 'Erreur de chargement';
|
const terminalSelect = document.getElementById('terminal');
|
||||||
terminalInput.disabled = true;
|
if (terminalSelect) {
|
||||||
|
terminalSelect.innerHTML = '<option value="">Erreur de chargement</option>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Valider que le terminal saisi existe dans la liste
|
// Valider que le terminal saisi existe dans la liste
|
||||||
function validateTerminal(terminal) {
|
function validateTerminal(terminal) {
|
||||||
return availableTerminals.includes(terminal);
|
return availableTerminals.map(t => t.toString()).includes(terminal.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// === GESTION INDICATEUR SIGNALR ===
|
// === 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