/** * Système d'édition inline avec verrouillage - Version corrigée */ class InlineEditing { constructor() { this.locks = new Map(); this.lockCheckInterval = null; this.init(); console.log('InlineEditing initialisé'); } init() { this.setupEventListeners(); this.startLockCheck(); } setupEventListeners() { console.log('Configuration des événements...'); // Détecter les clics sur les cellules éditables document.addEventListener('click', (e) => { console.log('Clic détecté sur:', e.target); if (e.target.classList.contains('editable-cell')) { console.log('Cellule éditables cliquée'); this.startEditing(e.target); } }); // Détecter les clics en dehors pour sauvegarder document.addEventListener('click', (e) => { if (!e.target.closest('.editable-cell, .inline-edit-input')) { this.saveAllPending(); } }); // Détecter les touches pour sauvegarder document.addEventListener('keydown', (e) => { if (e.key === 'Enter') { this.saveCurrentEditing(); } else if (e.key === 'Escape') { this.cancelCurrentEditing(); } }); } startEditing(cell) { console.log('Début de l\'édition pour:', cell); if (cell.classList.contains('editing')) { console.log('Déjà en cours d\'édition'); return; } const entityType = cell.dataset.entityType; const entityId = cell.dataset.entityId; const field = cell.dataset.field; const currentValue = cell.textContent.trim(); console.log('Données:', { entityType, entityId, field, currentValue }); // Vérifier si déjà en cours d'édition if (this.isEditing(entityType, entityId, field)) { console.log('Déjà en cours d\'édition pour ce champ'); return; } // Acquérir le verrou this.acquireLock(entityType, entityId).then((success) => { console.log('Résultat de l\'acquisition du verrou:', success); if (success) { this.createEditInput(cell, entityType, entityId, field, currentValue); } else { this.showLockMessage(cell); } }).catch(error => { console.error('Erreur lors de l\'acquisition du verrou:', error); this.showErrorMessage('Erreur lors de l\'acquisition du verrou'); }); } createEditInput(cell, entityType, entityId, field, currentValue) { console.log('Création de l\'input pour:', { entityType, entityId, field, currentValue }); cell.classList.add('editing'); const input = document.createElement('input'); input.type = 'text'; input.value = currentValue; input.className = 'inline-edit-input form-control'; input.dataset.entityType = entityType; input.dataset.entityId = entityId; input.dataset.field = field; // Remplacer le contenu cell.innerHTML = ''; cell.appendChild(input); input.focus(); input.select(); console.log('Input créé et ajouté'); // Sauvegarder automatiquement après 3 secondes d'inactivité let saveTimeout; input.addEventListener('input', () => { console.log('Changement détecté dans l\'input'); clearTimeout(saveTimeout); saveTimeout = setTimeout(() => { console.log('Sauvegarde automatique déclenchée'); this.saveField(entityType, entityId, field, input.value); }, 3000); }); } async acquireLock(entityType, entityId) { console.log('Tentative d\'acquisition du verrou pour:', entityType, entityId); try { const url = `/api/${entityType.toLowerCase()}/${entityId}/lock`; console.log('URL de verrouillage:', url); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', } }); console.log('Réponse du serveur:', response.status); const data = await response.json(); console.log('Données reçues:', data); if (response.ok) { this.locks.set(`${entityType}_${entityId}`, { entityType, entityId, acquiredAt: new Date(), expiresAt: new Date(data.expiresAt) }); console.log('Verrou acquis avec succès'); return true; } else { console.error('Erreur lors de l\'acquisition du verrou:', data.error); return false; } } catch (error) { console.error('Erreur réseau lors de l\'acquisition du verrou:', error); return false; } } async releaseLock(entityType, entityId) { console.log('Libération du verrou pour:', entityType, entityId); try { const url = `/api/${entityType.toLowerCase()}/${entityId}/unlock`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', } }); console.log('Réponse de libération:', response.status); this.locks.delete(`${entityType}_${entityId}`); } catch (error) { console.error('Erreur lors de la libération du verrou:', error); } } async extendLock(entityType, entityId) { try { const url = `/api/${entityType.toLowerCase()}/${entityId}/extend-lock`; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', } }); if (response.ok) { const data = await response.json(); const lock = this.locks.get(`${entityType}_${entityId}`); if (lock) { lock.expiresAt = new Date(data.expiresAt); } } } catch (error) { console.error('Erreur lors de la prolongation du verrou:', error); } } async saveField(entityType, entityId, field, value) { console.log('Sauvegarde du champ:', { entityType, entityId, field, value }); try { const url = `/api/${entityType.toLowerCase()}/${entityId}/update-field`; console.log('URL de sauvegarde:', url); const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ field: field, value: value }) }); console.log('Réponse de sauvegarde:', response.status); const data = await response.json(); console.log('Données de sauvegarde:', data); if (response.ok) { // Mettre à jour l'affichage this.updateCellDisplay(entityType, entityId, field, value); this.showSuccessMessage(`Champ ${field} mis à jour`); console.log('Sauvegarde réussie'); } else { console.error('Erreur de sauvegarde:', data.error); this.showErrorMessage(data.error || 'Erreur lors de la sauvegarde'); } } catch (error) { console.error('Erreur réseau lors de la sauvegarde:', error); this.showErrorMessage('Erreur réseau lors de la sauvegarde'); } } updateCellDisplay(entityType, entityId, field, value) { console.log('Mise à jour de l\'affichage:', { entityType, entityId, field, value }); const cell = document.querySelector(`[data-entity-type="${entityType}"][data-entity-id="${entityId}"][data-field="${field}"]`); if (cell) { cell.textContent = value; cell.classList.remove('editing'); console.log('Affichage mis à jour'); } else { console.error('Cellule non trouvée pour la mise à jour'); } } isEditing(entityType, entityId, field) { const cell = document.querySelector(`[data-entity-type="${entityType}"][data-entity-id="${entityId}"][data-field="${field}"]`); return cell && cell.classList.contains('editing'); } saveCurrentEditing() { const editingInput = document.querySelector('.inline-edit-input'); if (editingInput) { const entityType = editingInput.dataset.entityType; const entityId = editingInput.dataset.entityId; const field = editingInput.dataset.field; const value = editingInput.value; this.saveField(entityType, entityId, field, value); } } cancelCurrentEditing() { const editingInput = document.querySelector('.inline-edit-input'); if (editingInput) { const cell = editingInput.parentElement; const originalValue = cell.dataset.originalValue || ''; cell.innerHTML = originalValue; cell.classList.remove('editing'); } } saveAllPending() { const editingInputs = document.querySelectorAll('.inline-edit-input'); editingInputs.forEach(input => { const entityType = input.dataset.entityType; const entityId = input.dataset.entityId; const field = input.dataset.field; const value = input.value; this.saveField(entityType, entityId, field, value); }); } startLockCheck() { // Vérifier les verrous toutes les 30 secondes this.lockCheckInterval = setInterval(() => { this.checkLocks(); }, 30000); } async checkLocks() { for (const [key, lock] of this.locks) { if (new Date() > lock.expiresAt) { // Verrou expiré, le libérer this.locks.delete(key); } else { // Prolonger le verrou await this.extendLock(lock.entityType, lock.entityId); } } } showLockMessage(cell) { const message = document.createElement('div'); message.className = 'alert alert-warning lock-message'; message.textContent = 'Cet élément est en cours de modification par un autre utilisateur'; cell.appendChild(message); setTimeout(() => { message.remove(); }, 3000); } showSuccessMessage(message) { this.showMessage(message, 'success'); } showErrorMessage(message) { this.showMessage(message, 'danger'); } showMessage(message, type) { const alert = document.createElement('div'); alert.className = `alert alert-${type} alert-dismissible fade show position-fixed`; alert.style.top = '20px'; alert.style.right = '20px'; alert.style.zIndex = '9999'; alert.innerHTML = ` ${message} `; document.body.appendChild(alert); setTimeout(() => { alert.remove(); }, 5000); } destroy() { if (this.lockCheckInterval) { clearInterval(this.lockCheckInterval); } // Libérer tous les verrous for (const [key, lock] of this.locks) { this.releaseLock(lock.entityType, lock.entityId); } } } // Initialiser l'édition inline quand le DOM est prêt document.addEventListener('DOMContentLoaded', () => { console.log('DOM chargé, initialisation de l\'édition inline...'); window.inlineEditing = new InlineEditing(); }); // Nettoyer à la fermeture de la page window.addEventListener('beforeunload', () => { if (window.inlineEditing) { window.inlineEditing.destroy(); } });