diff --git a/streetup/interventions/static/interventions/detail/cms_edit_mode.js b/streetup/interventions/static/interventions/detail/cms_edit_mode.js
index f5f5768..b4f2c18 100644
--- a/streetup/interventions/static/interventions/detail/cms_edit_mode.js
+++ b/streetup/interventions/static/interventions/detail/cms_edit_mode.js
@@ -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,96 +232,107 @@
originalContractOptions = null;
}
- async function saveChanges() {
- // Emit event to allow other components to save their changes
- // Collect promises from other components that need to save
- const savePromises = [];
- const saveEvent = new CustomEvent('cms-save-before', {
- detail: {
- addSavePromise: (promise) => savePromises.push(promise)
- }
- });
- document.dispatchEvent(saveEvent);
-
- // Track if other components had changes
- let otherComponentsHadChanges = false;
-
- // Wait for all other components to finish saving
- if (savePromises.length > 0) {
- try {
- const results = await Promise.all(savePromises);
- // Check if any save failed
- const failed = results.some(r => r && r.success === false);
- if (failed) {
- showErrorMessage('Erreur lors de la sauvegarde de certains éléments');
- return;
- }
- // 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;
- }
- }
-
- // Collect changed values
- 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.querySelector('input, select');
-
- if (input) {
- let newValue;
- // Handle checkboxes specially
- if (input.type === 'checkbox') {
- newValue = input.checked ? 'true' : 'false';
- } else {
- newValue = input.value;
- }
- const oldValue = originalValues[fieldName];
-
- if (newValue !== oldValue) {
- changes[fieldName] = newValue;
- hasChanges = true;
- }
- }
- });
+ async function saveChanges(options = {}) {
+ const reloadOnSuccess = options.reloadOnSuccess !== false;
- // Collect note changes if collectNoteChanges function exists
- let noteChanges = null;
- if (typeof window.collectNoteChanges === 'function') {
- 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 (isSavingInProgress) {
+ return false;
}
-
- if (!hasChanges && !otherComponentsHadChanges) {
- // No changes anywhere, just exit edit mode
- exitEditMode();
- return;
- }
-
- if (!hasChanges && otherComponentsHadChanges) {
- // Only other components had changes (e.g., operations), reload to show updated data
- isEditMode = false;
- window.location.reload();
- return;
- }
-
- // Show loading state
- saveBtn.disabled = true;
- saveBtn.innerHTML = ' Enregistrement...';
-
+ isSavingInProgress = true;
+
try {
+ // Emit event to allow other components to save their changes
+ // Collect promises from other components that need to save
+ const savePromises = [];
+ const saveEvent = new CustomEvent('cms-save-before', {
+ detail: {
+ addSavePromise: (promise) => savePromises.push(promise)
+ }
+ });
+ document.dispatchEvent(saveEvent);
+
+ // Track if other components had changes
+ let otherComponentsHadChanges = false;
+
+ // Wait for all other components to finish saving
+ if (savePromises.length > 0) {
+ try {
+ const results = await Promise.all(savePromises);
+ // Check if any save failed
+ const failed = results.some(r => r && r.success === false);
+ if (failed) {
+ showErrorMessage('Erreur lors de la sauvegarde de certains éléments');
+ 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 false;
+ }
+ }
+
+ // Collect changed values
+ 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;
+ // Handle checkboxes specially
+ if (input.type === 'checkbox') {
+ newValue = input.checked ? 'true' : 'false';
+ } else {
+ newValue = input.value;
+ }
+ const oldValue = originalValues[fieldName];
+
+ if (newValue !== oldValue) {
+ changes[fieldName] = newValue;
+ hasChanges = true;
+ }
+ }
+ });
+
+ // Collect note changes if collectNoteChanges function exists
+ let noteChanges = null;
+ if (typeof window.collectNoteChanges === 'function') {
+ 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 && !otherComponentsHadChanges) {
+ // No changes anywhere, just exit edit mode
+ exitEditMode();
+ return true;
+ }
+
+ if (!hasChanges && otherComponentsHadChanges) {
+ // Only other components had changes (e.g., operations)
+ isEditMode = false;
+ if (reloadOnSuccess) {
+ window.location.reload();
+ }
+ return true;
+ }
+
+ // Show loading state
+ if (saveBtn) {
+ saveBtn.disabled = true;
+ saveBtn.innerHTML = ' Enregistrement...';
+ }
+
// Get intervention ID from URL or page
const interventionId = window.location.pathname.match(/\/interventions\/(\d+)\//)?.[1];
if (!interventionId) {
@@ -340,20 +356,26 @@
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
- window.location.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
- saveBtn.disabled = false;
- saveBtn.innerHTML = ' Sauvegarder';
+ if (saveBtn) {
+ saveBtn.disabled = false;
+ saveBtn.innerHTML = ' Sauvegarder';
+ }
+ return false;
+ } finally {
+ isSavingInProgress = false;
}
}
@@ -399,8 +421,10 @@
cancelBtn.classList.add('d-none');
// Restore button state
- saveBtn.disabled = false;
- saveBtn.innerHTML = ' Sauvegarder';
+ if (saveBtn) {
+ saveBtn.disabled = false;
+ saveBtn.innerHTML = ' Sauvegarder';
+ }
originalValues = {};
}
@@ -451,12 +475,114 @@
return cookieValue;
}
- // Warn before leaving page if in edit mode
- window.addEventListener('beforeunload', (e) => {
- if (isEditMode) {
- e.preventDefault();
- e.returnValue = '';
- return '';
+ // 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.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);
})();
+