From 06b4e2819d7fc2568c7dca9b5a135fbe411968ad Mon Sep 17 00:00:00 2001 From: Pierre Marx Date: Thu, 4 Sep 2025 16:49:07 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20Syst=C3=A8me=20de=20persistance=20des?= =?UTF-8?q?=20notes=20am=C3=A9lior=C3=A9=20avec=20fichier=20unique=20par?= =?UTF-8?q?=20agent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Un seul fichier notes_{agentId}.json par agent (plus d'accumulation) - Auto-save après 2 secondes d'inactivité - Restauration automatique au démarrage depuis fichier ou localStorage - Historique des 50 dernières versions intégré dans le fichier - Synchronisation transparente fichier/localStorage - Notifications visuelles lors de la restauration --- docs/changelog.md | 30 ++++++++++++ main.js | 60 +++++++++++++++++++++--- renderer.js | 117 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 199 insertions(+), 8 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 4728772..4f91de3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,35 @@ # Changelog - SimpleConnect Electron +## [1.2.14] - 2025-09-04 + +### Ajouté +- **Système de persistance des notes amélioré** : Sauvegarde et restauration automatiques + - Auto-save après 2 secondes d'inactivité + - Restauration automatique des notes au démarrage + - Synchronisation localStorage + fichier serveur + - Notification visuelle lors de la restauration + - Historique local des 20 dernières notes dans localStorage + +### Modifié +- **Gestion des fichiers de notes** : Un seul fichier par agent au lieu de multiples + - Format `notes_{agentId}.json` unique par agent + - Mise à jour du même fichier à chaque sauvegarde + - Historique des 50 dernières versions intégré dans le fichier + - Plus d'accumulation de fichiers datés + +### Amélioré +- **Expérience utilisateur des notes** : Persistance transparente + - Chargement prioritaire depuis le fichier serveur + - Fallback sur localStorage si fichier absent + - Bouton "Effacer" vide aussi localStorage + - Messages de confirmation et notifications + +### Technique +- Nouvelle fonction `loadSavedNotes()` asynchrone +- Handler IPC `get-notes` pour récupérer depuis le serveur +- Auto-save avec debouncing de 2 secondes +- Structure JSON avec note courante + historique + ## [1.2.13] - 2025-09-04 ### Corrigé diff --git a/main.js b/main.js index e8be425..1389834 100644 --- a/main.js +++ b/main.js @@ -471,25 +471,73 @@ ipcMain.handle('get-simulated-calls', () => { }); }); -// Sauvegarder les notes de l'agent +// Sauvegarder les notes de l'agent (un seul fichier par agent) ipcMain.handle('save-notes', (event, noteData) => { const notesDir = path.join(__dirname, 'notes'); if (!fs.existsSync(notesDir)) { fs.mkdirSync(notesDir); } - const fileName = `notes_${currentAgent.id}_${Date.now()}.json`; + // Un seul fichier par agent, mis à jour à chaque sauvegarde + const fileName = `notes_${currentAgent.id}.json`; const filePath = path.join(notesDir, fileName); - fs.writeFileSync(filePath, JSON.stringify({ + // Lire l'historique existant si le fichier existe + let notesData = { agent: currentAgent.id, - timestamp: new Date().toISOString(), - ...noteData - }, null, 2)); + agentName: currentAgent.name, + currentNote: noteData.content, + lastModified: new Date().toISOString(), + centre: noteData.centre, + history: [] + }; + + // Si le fichier existe, préserver l'historique + if (fs.existsSync(filePath)) { + try { + const existingData = JSON.parse(fs.readFileSync(filePath, 'utf8')); + // Ajouter l'ancienne note à l'historique si elle a changé + if (existingData.currentNote && existingData.currentNote !== noteData.content) { + notesData.history = existingData.history || []; + notesData.history.unshift({ + content: existingData.currentNote, + date: existingData.lastModified, + centre: existingData.centre + }); + // Limiter l'historique à 50 entrées + notesData.history = notesData.history.slice(0, 50); + } + } catch (error) { + console.error('Erreur lecture notes existantes:', error); + } + } + + fs.writeFileSync(filePath, JSON.stringify(notesData, null, 2)); return { success: true, file: fileName }; }); +// Récupérer les notes de l'agent +ipcMain.handle('get-notes', () => { + if (!currentAgent) return null; + + const notesDir = path.join(__dirname, 'notes'); + const fileName = `notes_${currentAgent.id}.json`; + const filePath = path.join(notesDir, fileName); + + if (fs.existsSync(filePath)) { + try { + const data = fs.readFileSync(filePath, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Erreur lecture notes:', error); + return null; + } + } + + return null; +}); + // Obtenir l'historique des appels ipcMain.handle('get-call-history', () => { const historyFile = path.join(__dirname, 'call_history.json'); diff --git a/renderer.js b/renderer.js index 3062f52..92f8f26 100644 --- a/renderer.js +++ b/renderer.js @@ -40,6 +40,7 @@ document.addEventListener('DOMContentLoaded', async () => { 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(); @@ -437,6 +438,12 @@ function showMainPage() { }); selectCenter(sortedCentres[0].id); } + + // Charger les notes sauvegardées APRÈS que la page soit affichée + setTimeout(() => { + loadSavedNotes(); + setupAutoSave(); + }, 100); } // === GESTION DES CENTRES === @@ -692,6 +699,11 @@ async function saveNotes() { 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 @@ -699,11 +711,89 @@ async function saveNotes() { if (result.success) { showNotification('Notes sauvegardées avec succès !', 'success'); - // Optionnel : garder les notes ou les effacer - // document.getElementById('quickNotes').value = ''; + // 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 @@ -931,7 +1021,30 @@ 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é + }); } }