Files
Projet_Symfony_IA_Quentin/public/js/inline-editing.js

362 lines
12 KiB
JavaScript
Raw Permalink Normal View History

2025-10-24 16:13:37 +02:00
/**
* 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}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
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();
}
});