refactor: implement save state locking and modularize save logic in cms_edit_mode.js

This commit is contained in:
kdeterme 2026-08-12 08:58:55 +02:00
parent 8590ddb17c
commit 7767856cb5

View file

@ -10,13 +10,18 @@
let isEditMode = false;
let originalValues = {};
let originalContractOptions = null; // Store original contract options for cancel
let isSavingInProgress = false;
let isAutoSavingAndNavigating = false;
let isSubmittingAutoSavedForm = false;
// Initialize
if (editBtn) {
editBtn.addEventListener('click', enterEditMode);
}
if (saveBtn) {
saveBtn.addEventListener('click', saveChanges);
saveBtn.addEventListener('click', function() {
saveChanges({ reloadOnSuccess: true });
});
}
if (cancelBtn) {
cancelBtn.addEventListener('click', cancelChanges);
@ -130,7 +135,7 @@
const fieldName = field.dataset.field;
const displayDiv = field.querySelector('.field-display');
const editDiv = field.querySelector('.field-edit');
const input = editDiv.querySelector('input, select');
const input = editDiv ? editDiv.querySelector('input, select') : null;
if (input) {
// Handle checkboxes specially
@ -195,7 +200,7 @@
const fieldName = field.dataset.field;
const displayDiv = field.querySelector('.field-display');
const editDiv = field.querySelector('.field-edit');
const input = editDiv.querySelector('input, select');
const input = editDiv ? editDiv.querySelector('input, select') : null;
if (input && originalValues[fieldName] !== undefined) {
// Handle checkboxes specially
@ -227,7 +232,15 @@
originalContractOptions = null;
}
async function saveChanges() {
async function saveChanges(options = {}) {
const reloadOnSuccess = options.reloadOnSuccess !== false;
if (isSavingInProgress) {
return false;
}
isSavingInProgress = true;
try {
// Emit event to allow other components to save their changes
// Collect promises from other components that need to save
const savePromises = [];
@ -249,14 +262,14 @@
const failed = results.some(r => r && r.success === false);
if (failed) {
showErrorMessage('Erreur lors de la sauvegarde de certains éléments');
return;
return false;
}
// Check if any component had changes
otherComponentsHadChanges = results.some(r => r && r.saved_count > 0);
} catch (error) {
console.error('Error saving components:', error);
showErrorMessage('Erreur lors de la sauvegarde');
return;
return false;
}
}
@ -270,7 +283,7 @@
const fieldName = field.dataset.field;
const editDiv = field.querySelector('.field-edit');
const input = editDiv.querySelector('input, select');
const input = editDiv ? editDiv.querySelector('input, select') : null;
if (input) {
let newValue;
@ -302,21 +315,24 @@
if (!hasChanges && !otherComponentsHadChanges) {
// No changes anywhere, just exit edit mode
exitEditMode();
return;
return true;
}
if (!hasChanges && otherComponentsHadChanges) {
// Only other components had changes (e.g., operations), reload to show updated data
// Only other components had changes (e.g., operations)
isEditMode = false;
if (reloadOnSuccess) {
window.location.reload();
return;
}
return true;
}
// Show loading state
if (saveBtn) {
saveBtn.disabled = true;
saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span> Enregistrement...';
}
try {
// Get intervention ID from URL or page
const interventionId = window.location.pathname.match(/\/interventions\/(\d+)\//)?.[1];
if (!interventionId) {
@ -340,21 +356,27 @@
const result = await response.json();
// Exit edit mode to prevent "leave page" warning
// Exit edit mode to prevent any warning
isEditMode = false;
// Reload page immediately to reflect all changes (especially notes)
// The backend can use Django messages framework to show success message after reload
if (reloadOnSuccess) {
window.location.reload();
}
return true;
} catch (error) {
console.error('Save error:', error);
showErrorMessage(error.message || 'Erreur lors de la sauvegarde');
// Restore button state
if (saveBtn) {
saveBtn.disabled = false;
saveBtn.innerHTML = '<i class="bi bi-check-circle"></i> Sauvegarder';
}
return false;
} finally {
isSavingInProgress = false;
}
}
function exitEditMode() {
@ -399,8 +421,10 @@
cancelBtn.classList.add('d-none');
// Restore button state
if (saveBtn) {
saveBtn.disabled = false;
saveBtn.innerHTML = '<i class="bi bi-check-circle"></i> Sauvegarder';
}
originalValues = {};
}
@ -451,12 +475,114 @@
return cookieValue;
}
// Warn before leaving page if in edit mode
window.addEventListener('beforeunload', (e) => {
if (isEditMode) {
// Auto-save before navigation when in edit mode
document.addEventListener('click', async function(e) {
if (!isEditMode || isAutoSavingAndNavigating) return;
const anchor = e.target.closest('a');
if (!anchor) return;
const href = anchor.getAttribute('href');
if (!href || href === '#' || href.startsWith('javascript:') || href.startsWith('#')) return;
if (anchor.getAttribute('target') === '_blank') return;
if (anchor.hasAttribute('data-bs-toggle') || anchor.hasAttribute('data-bs-dismiss') || anchor.hasAttribute('data-bs-target')) return;
if (e.ctrlKey || e.metaKey || e.shiftKey || e.altKey) return;
e.preventDefault();
e.returnValue = '';
return '';
e.stopPropagation();
isAutoSavingAndNavigating = true;
const targetUrl = anchor.href;
try {
const success = await saveChanges({ reloadOnSuccess: false });
if (success !== false) {
window.location.href = targetUrl;
} else {
isAutoSavingAndNavigating = false;
}
} catch (err) {
console.error('Auto-save navigation error:', err);
isAutoSavingAndNavigating = false;
}
}, true);
// Intercept form submissions to auto-save before submitting
document.addEventListener('submit', async function(e) {
if (!isEditMode || isSubmittingAutoSavedForm || isAutoSavingAndNavigating) return;
const form = e.target;
if (form.getAttribute('target') === '_blank') return;
e.preventDefault();
e.stopPropagation();
isAutoSavingAndNavigating = true;
try {
const success = await saveChanges({ reloadOnSuccess: false });
if (success !== false) {
isSubmittingAutoSavedForm = true;
form.submit();
} else {
isAutoSavingAndNavigating = false;
}
} catch (err) {
console.error('Auto-save form submit error:', err);
isAutoSavingAndNavigating = false;
}
}, true);
// Background auto-save on tab close / browser reload without showing alert
function sendKeepAliveSave() {
if (!isEditMode) return;
const changes = {};
let hasChanges = false;
editableFields.forEach(field => {
const canEdit = field.dataset.canEdit === 'true';
if (!canEdit) return;
const fieldName = field.dataset.field;
const editDiv = field.querySelector('.field-edit');
const input = editDiv ? editDiv.querySelector('input, select') : null;
if (input) {
let newValue = input.type === 'checkbox' ? (input.checked ? 'true' : 'false') : input.value;
const oldValue = originalValues[fieldName];
if (newValue !== oldValue) {
changes[fieldName] = newValue;
hasChanges = true;
}
}
});
if (typeof window.collectNoteChanges === 'function') {
const noteChanges = window.collectNoteChanges();
if (noteChanges.new_notes.length > 0 || noteChanges.updated_notes.length > 0 || noteChanges.deleted_notes.length > 0) {
hasChanges = true;
changes.notes = noteChanges;
}
}
if (hasChanges) {
const interventionId = window.location.pathname.match(/\/interventions\/(\d+)\//)?.[1];
if (interventionId) {
fetch(`/interventions/${interventionId}/update/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify(changes),
keepalive: true
});
}
}
}
window.addEventListener('pagehide', sendKeepAliveSave);
window.addEventListener('beforeunload', sendKeepAliveSave);
})();