refactor: implement save state locking and modularize save logic in cms_edit_mode.js
This commit is contained in:
parent
8590ddb17c
commit
7767856cb5
1 changed files with 231 additions and 105 deletions
|
|
@ -10,13 +10,18 @@
|
||||||
let isEditMode = false;
|
let isEditMode = false;
|
||||||
let originalValues = {};
|
let originalValues = {};
|
||||||
let originalContractOptions = null; // Store original contract options for cancel
|
let originalContractOptions = null; // Store original contract options for cancel
|
||||||
|
let isSavingInProgress = false;
|
||||||
|
let isAutoSavingAndNavigating = false;
|
||||||
|
let isSubmittingAutoSavedForm = false;
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
if (editBtn) {
|
if (editBtn) {
|
||||||
editBtn.addEventListener('click', enterEditMode);
|
editBtn.addEventListener('click', enterEditMode);
|
||||||
}
|
}
|
||||||
if (saveBtn) {
|
if (saveBtn) {
|
||||||
saveBtn.addEventListener('click', saveChanges);
|
saveBtn.addEventListener('click', function() {
|
||||||
|
saveChanges({ reloadOnSuccess: true });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (cancelBtn) {
|
if (cancelBtn) {
|
||||||
cancelBtn.addEventListener('click', cancelChanges);
|
cancelBtn.addEventListener('click', cancelChanges);
|
||||||
|
|
@ -130,7 +135,7 @@
|
||||||
const fieldName = field.dataset.field;
|
const fieldName = field.dataset.field;
|
||||||
const displayDiv = field.querySelector('.field-display');
|
const displayDiv = field.querySelector('.field-display');
|
||||||
const editDiv = field.querySelector('.field-edit');
|
const editDiv = field.querySelector('.field-edit');
|
||||||
const input = editDiv.querySelector('input, select');
|
const input = editDiv ? editDiv.querySelector('input, select') : null;
|
||||||
|
|
||||||
if (input) {
|
if (input) {
|
||||||
// Handle checkboxes specially
|
// Handle checkboxes specially
|
||||||
|
|
@ -195,7 +200,7 @@
|
||||||
const fieldName = field.dataset.field;
|
const fieldName = field.dataset.field;
|
||||||
const displayDiv = field.querySelector('.field-display');
|
const displayDiv = field.querySelector('.field-display');
|
||||||
const editDiv = field.querySelector('.field-edit');
|
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) {
|
if (input && originalValues[fieldName] !== undefined) {
|
||||||
// Handle checkboxes specially
|
// Handle checkboxes specially
|
||||||
|
|
@ -227,96 +232,107 @@
|
||||||
originalContractOptions = null;
|
originalContractOptions = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveChanges() {
|
async function saveChanges(options = {}) {
|
||||||
// Emit event to allow other components to save their changes
|
const reloadOnSuccess = options.reloadOnSuccess !== false;
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Collect note changes if collectNoteChanges function exists
|
if (isSavingInProgress) {
|
||||||
let noteChanges = null;
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
isSavingInProgress = true;
|
||||||
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 = '<span class="spinner-border spinner-border-sm me-1"></span> Enregistrement...';
|
|
||||||
|
|
||||||
try {
|
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 = '<span class="spinner-border spinner-border-sm me-1"></span> Enregistrement...';
|
||||||
|
}
|
||||||
|
|
||||||
// Get intervention ID from URL or page
|
// Get intervention ID from URL or page
|
||||||
const interventionId = window.location.pathname.match(/\/interventions\/(\d+)\//)?.[1];
|
const interventionId = window.location.pathname.match(/\/interventions\/(\d+)\//)?.[1];
|
||||||
if (!interventionId) {
|
if (!interventionId) {
|
||||||
|
|
@ -340,20 +356,26 @@
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
// Exit edit mode to prevent "leave page" warning
|
// Exit edit mode to prevent any warning
|
||||||
isEditMode = false;
|
isEditMode = false;
|
||||||
|
|
||||||
// Reload page immediately to reflect all changes (especially notes)
|
if (reloadOnSuccess) {
|
||||||
// The backend can use Django messages framework to show success message after reload
|
window.location.reload();
|
||||||
window.location.reload();
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Save error:', error);
|
console.error('Save error:', error);
|
||||||
showErrorMessage(error.message || 'Erreur lors de la sauvegarde');
|
showErrorMessage(error.message || 'Erreur lors de la sauvegarde');
|
||||||
|
|
||||||
// Restore button state
|
// Restore button state
|
||||||
saveBtn.disabled = false;
|
if (saveBtn) {
|
||||||
saveBtn.innerHTML = '<i class="bi bi-check-circle"></i> Sauvegarder';
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerHTML = '<i class="bi bi-check-circle"></i> Sauvegarder';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
isSavingInProgress = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -399,8 +421,10 @@
|
||||||
cancelBtn.classList.add('d-none');
|
cancelBtn.classList.add('d-none');
|
||||||
|
|
||||||
// Restore button state
|
// Restore button state
|
||||||
saveBtn.disabled = false;
|
if (saveBtn) {
|
||||||
saveBtn.innerHTML = '<i class="bi bi-check-circle"></i> Sauvegarder';
|
saveBtn.disabled = false;
|
||||||
|
saveBtn.innerHTML = '<i class="bi bi-check-circle"></i> Sauvegarder';
|
||||||
|
}
|
||||||
|
|
||||||
originalValues = {};
|
originalValues = {};
|
||||||
}
|
}
|
||||||
|
|
@ -451,12 +475,114 @@
|
||||||
return cookieValue;
|
return cookieValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn before leaving page if in edit mode
|
// Auto-save before navigation when in edit mode
|
||||||
window.addEventListener('beforeunload', (e) => {
|
document.addEventListener('click', async function(e) {
|
||||||
if (isEditMode) {
|
if (!isEditMode || isAutoSavingAndNavigating) return;
|
||||||
e.preventDefault();
|
|
||||||
e.returnValue = '';
|
const anchor = e.target.closest('a');
|
||||||
return '';
|
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);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue