feat: add edit note functionality and UI action buttons

This commit is contained in:
kdeterme 2026-09-25 23:36:04 +02:00
parent 205a05cb5b
commit f4460a5323
4 changed files with 130 additions and 106 deletions

View file

@ -146,14 +146,21 @@ body.cms-edit-mode .cms-modal-container {
border: 2px solid #f8e8b6;
}
/* Delete button hidden by default */
.delete-note-btn {
display: none !important;
/* Note action buttons */
.note-actions .delete-note-btn,
.note-actions .edit-note-btn {
opacity: 0.7;
transition: opacity 0.2s ease, transform 0.1s ease;
text-decoration: none;
cursor: pointer;
}
/* Delete button visible in edit mode */
body.cms-edit-mode .delete-note-btn {
display: block !important;
.note-actions .delete-note-btn:hover,
.note-actions .edit-note-btn:hover,
body.cms-edit-mode .note-actions .delete-note-btn,
body.cms-edit-mode .note-actions .edit-note-btn {
opacity: 1;
transform: scale(1.1);
}
/* Editable notes in CMS edit mode */

View file

@ -5,10 +5,12 @@
'use strict';
const interventionId = document.querySelector('[data-intervention-id]')?.dataset.interventionId;
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value;
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value
|| (typeof getCookie === 'function' ? getCookie('csrftoken') : null)
|| (typeof csrftoken !== 'undefined' ? csrftoken : null);
if (!interventionId || !csrfToken) {
console.warn('CMS Notes: Missing intervention ID or CSRF token');
if (!interventionId) {
console.warn('CMS Notes: Missing intervention ID');
return;
}
@ -92,17 +94,110 @@
// Store original note values for cancel
window.cmsNotesOriginalValues = {};
// Show delete buttons in edit mode
document.addEventListener('cms-edit-mode-enter', function () {
document.querySelectorAll('.delete-note-btn').forEach(btn => {
btn.style.display = 'block';
});
});
function startEditingNote(noteItem) {
if (!noteItem) return;
// Editable notes - click to edit in CMS mode
const noteId = noteItem.dataset.noteId;
const textarea = noteItem.querySelector('.edit-note-content');
const select = noteItem.querySelector('.edit-note-type');
const originalContent = decodeHtmlToText(noteItem.dataset.noteContent || '');
const originalType = noteItem.dataset.noteType || '';
if (textarea) textarea.value = originalContent;
if (select) select.value = originalType;
if (!window.cmsNotesOriginalValues[noteId]) {
window.cmsNotesOriginalValues[noteId] = {
content: originalContent,
type: originalType
};
}
noteItem.classList.add('editing');
if (textarea) {
setTimeout(() => {
textarea.focus();
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
}, 100);
}
}
// Notes container click delegation (edit button, delete button, inline edit)
if (notesContainer) {
notesContainer.addEventListener('click', function (e) {
// Only allow editing in CMS edit mode
// Edit note button handler
const editBtn = e.target.closest('.edit-note-btn');
if (editBtn) {
e.preventDefault();
e.stopPropagation();
const noteItem = editBtn.closest('.editable-note');
if (!noteItem) return;
if (!document.body.classList.contains('cms-edit-mode')) {
const cmsEditBtn = document.getElementById('cms-edit-btn');
if (cmsEditBtn) {
cmsEditBtn.click();
}
}
startEditingNote(noteItem);
return;
}
// Delete note button handler
const deleteBtn = e.target.closest('.delete-note-btn');
if (deleteBtn) {
e.preventDefault();
e.stopPropagation();
const noteId = deleteBtn.dataset.noteId;
if (!noteId) return;
const confirmMsg = typeof gettext === 'function' ? gettext('Voulez-vous vraiment supprimer cette note ?') : 'Voulez-vous vraiment supprimer cette note ?';
if (!window.confirm(confirmMsg)) {
return;
}
deleteBtn.disabled = true;
const token = document.querySelector('[name=csrfmiddlewaretoken]')?.value
|| (typeof getCookie === 'function' ? getCookie('csrftoken') : null)
|| (typeof csrftoken !== 'undefined' ? csrftoken : null);
fetch(`/interventions/${interventionId}/notes/${noteId}/delete/`, {
method: 'POST',
headers: {
'X-CSRFToken': token,
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
const successMsg = typeof gettext === 'function' ? gettext('Note supprimée avec succès !') : 'Note supprimée avec succès !';
showAlert('✅ ' + successMsg, 'success');
const noteItem = document.getElementById(`note-container-${noteId}`);
if (noteItem) {
noteItem.remove();
}
} else {
showAlert(`❌ ${data.error || 'Une erreur est survenue.'}`, 'danger');
deleteBtn.disabled = false;
}
})
.catch(error => {
console.error('Error deleting note:', error);
const errorMsg = typeof gettext === 'function' ? gettext('Une erreur est survenue lors de la suppression.') : 'Une erreur est survenue lors de la suppression.';
showAlert('❌ ' + errorMsg, 'danger');
deleteBtn.disabled = false;
});
return;
}
// Click anywhere on note in CMS edit mode to edit
if (!document.body.classList.contains('cms-edit-mode')) {
return;
}
@ -110,98 +205,17 @@
const noteItem = e.target.closest('.editable-note');
if (!noteItem) return;
// Don't trigger if clicking on buttons
if (e.target.closest('.delete-note-btn')) {
return;
}
// Don't toggle if already editing
if (noteItem.classList.contains('editing')) {
return;
}
// Store original values when entering edit mode
const noteId = noteItem.dataset.noteId;
const textarea = noteItem.querySelector('.edit-note-content');
const select = noteItem.querySelector('.edit-note-type');
// Force reset textarea value from data attribute to avoid browser cache
const originalContent = decodeHtmlToText(noteItem.dataset.noteContent || '');
const originalType = noteItem.dataset.noteType || '';
if (textarea) textarea.value = originalContent;
if (select) select.value = originalType;
// Store these values for cancel operation
if (!window.cmsNotesOriginalValues[noteId]) {
window.cmsNotesOriginalValues[noteId] = {
content: originalContent,
type: originalType
};
}
// Enter edit mode for this note
noteItem.classList.add('editing');
// Focus on the textarea
if (textarea) {
setTimeout(() => {
textarea.focus();
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
}, 100);
}
});
// Delete note button
notesContainer.addEventListener('click', function (e) {
const deleteBtn = e.target.closest('.delete-note-btn');
if (!deleteBtn) return;
const noteId = deleteBtn.dataset.noteId;
if (!noteId) return;
if (!window.confirm('Voulez-vous vraiment supprimer cette note ?')) {
return;
}
// Disable button during delete
deleteBtn.disabled = true;
fetch(`/interventions/${interventionId}/notes/${noteId}/delete/`, {
method: 'POST',
headers: {
'X-CSRFToken': csrfToken,
},
})
.then(response => response.json())
.then(data => {
if (data.success) {
showAlert('✅ Note supprimée avec succès !', 'success');
// Remove note from DOM
const noteItem = document.getElementById(`note-container-${noteId}`);
if (noteItem) {
noteItem.remove();
}
} else {
showAlert(`❌ ${data.error || 'Une erreur est survenue.'}`, 'danger');
deleteBtn.disabled = false;
}
})
.catch(error => {
console.error('Error deleting note:', error);
showAlert('❌ Une erreur est survenue lors de la suppression.', 'danger');
deleteBtn.disabled = false;
});
startEditingNote(noteItem);
});
}
// Hide new note editor and restore original values when exiting edit mode
document.addEventListener('cms-edit-mode-exit', function () {
// Hide delete buttons
document.querySelectorAll('.delete-note-btn').forEach(btn => {
btn.style.display = 'none';
});
// Hide and reset new note editor
if (newNoteEditor) {

View file

@ -63,8 +63,11 @@
<p class="mb-0 note-text">{{ note.content|safe|linebreaksbr }}</p>
</div>
{% if note.id in editable_note_ids %}
<div class="note-actions ms-auto">
<button type="button" class="btn btn-link text-danger p-0 delete-note-btn" data-note-id="{{ note.id }}" aria-label="{% translate "Supprimer la note" %}" style="display: none;">
<div class="note-actions ms-auto d-flex align-items-center gap-2 flex-shrink-0">
<button type="button" class="btn btn-link text-primary p-0 edit-note-btn" data-note-id="{{ note.id }}" title="{% translate 'Modifier la note' %}" aria-label="{% translate 'Modifier la note' %}">
<i class="bi bi-pencil fs-6"></i>
</button>
<button type="button" class="btn btn-link text-danger p-0 delete-note-btn" data-note-id="{{ note.id }}" title="{% translate 'Supprimer la note' %}" aria-label="{% translate 'Supprimer la note' %}">
<i class="bi bi-trash fs-6"></i>
</button>
</div>

View file

@ -122,12 +122,12 @@
<p class="mb-0">{{ note.content|linebreaksbr }}</p>
</div>
{% if note.id in editable_note_ids %}
<div class="note-actions cms-note-actions" style="display: none;">
<button type="button" class="btn btn-sm btn-outline-primary btn-edit-note me-1" title="{% translate 'Modifier la note' %}">
<i class="bi bi-pencil"></i>
<div class="note-actions ms-auto d-flex align-items-center gap-2 flex-shrink-0">
<button type="button" class="btn btn-link text-primary p-0 edit-note-btn" data-note-id="{{ note.id }}" title="{% translate 'Modifier la note' %}" aria-label="{% translate 'Modifier la note' %}">
<i class="bi bi-pencil fs-6"></i>
</button>
<button type="button" class="btn btn-sm btn-outline-danger btn-delete-note" title="{% translate 'Supprimer la note' %}">
<i class="bi bi-trash"></i>
<button type="button" class="btn btn-link text-danger p-0 delete-note-btn" data-note-id="{{ note.id }}" title="{% translate 'Supprimer la note' %}" aria-label="{% translate 'Supprimer la note' %}">
<i class="bi bi-trash fs-6"></i>
</button>
</div>
{% endif %}