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 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 = '<span class="spinner-border spinner-border-sm me-1"></span> 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 = '<span class="spinner-border spinner-border-sm me-1"></span> 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 = '<i class="bi bi-check-circle"></i> Sauvegarder';
|
||||
if (saveBtn) {
|
||||
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');
|
||||
|
||||
// Restore button state
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.innerHTML = '<i class="bi bi-check-circle"></i> Sauvegarder';
|
||||
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) {
|
||||
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);
|
||||
})();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue