Feature: Modal de confirmation pour les suppressions et style barré pour les todos

- Ajout modal de confirmation personnalisé avec icône, titre et message
- Remplacement de tous les confirm() natifs par le nouveau modal
- Modal animé avec backdrop et fermeture Escape
- Style barré automatique pour les todos complétées (déjà présent)
- Exposition globale des fonctions toggleTodo et deleteTodo
- Confirmation visuelle pour :
  * Suppression de notes
  * Suppression de todos (note et globaux)
  * Suppression d'images
  * Suppression d'utilisateurs
  * Création d'admin
- Design cohérent avec boutons Annuler/Confirmer
- Animations smooth avec transitions CSS
This commit is contained in:
Claude committed 2025-11-11 12:06:59 +00:00
1 parent c70a6725d2
commit 8c39ead2c6
3 files changed
+190 -11

No files matched your search

+65
View File
@@ -524,3 +524,68 @@
background: #c0392b;
transform: translateY(-1px);
}
/* Modal de confirmation */
.confirm-modal {
max-width: 450px;
text-align: center;
padding: var(--spacing-xl) var(--spacing-lg);
}
.confirm-icon {
font-size: 3rem;
margin-bottom: var(--spacing-md);
}
.confirm-title {
font-size: var(--font-size-xl);
font-weight: 600;
margin-bottom: var(--spacing-md);
color: var(--color-text);
}
.confirm-message {
font-size: var(--font-size-base);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-xl);
line-height: 1.6;
}
.confirm-actions {
display: flex;
gap: var(--spacing-md);
justify-content: center;
}
.btn-cancel,
.btn-confirm {
padding: var(--spacing-md) var(--spacing-xl);
border: none;
border-radius: var(--radius-md);
font-size: var(--font-size-base);
font-weight: 500;
cursor: pointer;
transition: all var(--transition-speed);
min-width: 120px;
}
.btn-cancel {
background: var(--color-background);
color: var(--color-text);
border: 2px solid var(--color-border);
}
.btn-cancel:hover {
background: var(--color-border);
}
.btn-confirm {
background: var(--color-danger);
color: white;
}
.btn-confirm:hover {
background: #c0392b;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(231, 76, 60, 0.3);
}
+13
View File
@@ -146,6 +146,19 @@
</div>
</div>
<!-- Modal de Confirmation -->
<div class="modal-backdrop" id="confirmModal" style="display: none;">
<div class="modal-content confirm-modal">
<div class="confirm-icon" id="confirmIcon">⚠️</div>
<h3 class="confirm-title" id="confirmTitle">Confirmer l'action</h3>
<p class="confirm-message" id="confirmMessage">Êtes-vous sûr de vouloir continuer ?</p>
<div class="confirm-actions">
<button class="btn-cancel" id="confirmCancel">Annuler</button>
<button class="btn-confirm" id="confirmOk">Confirmer</button>
</div>
</div>
</div>
<!-- JavaScript -->
<script src="/js/complete-app.js"></script>
</body>
+112 -11
View File
@@ -38,6 +38,63 @@ const utils = {
}
};
// ==================== MODAL DE CONFIRMATION ====================
const confirmDialog = {
show(options = {}) {
return new Promise((resolve) => {
const modal = document.getElementById('confirmModal');
const icon = document.getElementById('confirmIcon');
const title = document.getElementById('confirmTitle');
const message = document.getElementById('confirmMessage');
const cancelBtn = document.getElementById('confirmCancel');
const okBtn = document.getElementById('confirmOk');
// Configurer le contenu
icon.textContent = options.icon || '⚠️';
title.textContent = options.title || 'Confirmer l\'action';
message.textContent = options.message || 'Êtes-vous sûr de vouloir continuer ?';
cancelBtn.textContent = options.cancelText || 'Annuler';
okBtn.textContent = options.okText || 'Confirmer';
// Gérer les clics
const handleCancel = () => {
cleanup();
resolve(false);
};
const handleConfirm = () => {
cleanup();
resolve(true);
};
const handleEscape = (e) => {
if (e.key === 'Escape') {
handleCancel();
}
};
const cleanup = () => {
modal.classList.remove('active');
setTimeout(() => {
modal.style.display = 'none';
}, 300);
cancelBtn.removeEventListener('click', handleCancel);
okBtn.removeEventListener('click', handleConfirm);
document.removeEventListener('keydown', handleEscape);
};
// Ajouter les écouteurs
cancelBtn.addEventListener('click', handleCancel);
okBtn.addEventListener('click', handleConfirm);
document.addEventListener('keydown', handleEscape);
// Afficher le modal
modal.style.display = 'flex';
setTimeout(() => modal.classList.add('active'), 10);
});
}
};
// ==================== API ====================
const api = {
async request(url, options = {}) {
@@ -401,7 +458,14 @@ async function toggleNoteTodo(todoId, completed) {
}
async function deleteNoteTodo(todoId) {
if (!confirm('Supprimer ce todo ?')) return;
const confirmed = await confirmDialog.show({
icon: '🗑️',
title: 'Supprimer ce todo',
message: 'Êtes-vous sûr de vouloir supprimer ce todo ?',
okText: 'Supprimer'
});
if (!confirmed) return;
try {
await api.delete(`/api/notes/todos/${todoId}`);
@@ -421,7 +485,15 @@ function closeNoteModal() {
async function deleteCurrentNote() {
if (!currentNoteId) return;
if (!confirm('Supprimer cette note ?')) return;
const confirmed = await confirmDialog.show({
icon: '🗑️',
title: 'Supprimer cette note',
message: 'Êtes-vous sûr de vouloir supprimer cette note ?\n\nTous les todos associés seront également supprimés.',
okText: 'Supprimer'
});
if (!confirmed) return;
try {
await api.delete(`/api/notes/${currentNoteId}`);
@@ -456,7 +528,15 @@ async function handleImageUpload(event) {
async function removeNoteImage() {
if (!currentNoteId) return;
if (!confirm('Supprimer l\'image ?')) return;
const confirmed = await confirmDialog.show({
icon: '🖼️',
title: 'Supprimer l\'image',
message: 'Êtes-vous sûr de vouloir supprimer cette image ?',
okText: 'Supprimer'
});
if (!confirmed) return;
try {
await api.delete(`/api/notes/${currentNoteId}/image`);
@@ -519,23 +599,33 @@ async function addTodo() {
}
}
async function toggleTodo(id, completed) {
// Exposer globalement pour les event handlers inline
window.toggleTodo = async function(id, completed) {
try {
await api.put(`/api/todos/${id}`, { completed });
await loadTodos();
} catch (error) {
console.error('Erreur toggle todo:', error);
}
}
};
window.deleteTodo = async function(id) {
const confirmed = await confirmDialog.show({
icon: '🗑️',
title: 'Supprimer cette tâche',
message: 'Êtes-vous sûr de vouloir supprimer cette tâche ?',
okText: 'Supprimer'
});
if (!confirmed) return;
async function deleteTodo(id) {
try {
await api.delete(`/api/todos/${id}`);
await loadTodos();
} catch (error) {
console.error('Erreur suppression todo:', error);
}
}
};
function setTodoFilter(filter) {
state.filter = filter;
@@ -647,7 +737,13 @@ async function createUser() {
return;
}
const isAdmin = confirm('Créer cet utilisateur en tant qu\'administrateur?');
const isAdmin = await confirmDialog.show({
icon: '👤',
title: 'Droits administrateur',
message: `Créer "${username}" en tant qu'administrateur ?`,
okText: 'Oui, admin',
cancelText: 'Non, utilisateur'
});
try {
await api.post('/api/users', {
@@ -665,9 +761,14 @@ async function createUser() {
// Fonction globale pour la suppression d'utilisateur (appelée depuis onclick)
window.deleteUser = async function(id, username) {
if (!confirm(`Êtes-vous sûr de vouloir supprimer l'utilisateur "${username}"?\n\nToutes ses notes et todos seront également supprimés.`)) {
return;
}
const confirmed = await confirmDialog.show({
icon: '⚠️',
title: 'Supprimer cet utilisateur',
message: `Êtes-vous sûr de vouloir supprimer l'utilisateur "${username}" ?\n\nToutes ses notes et todos seront également supprimés définitivement.`,
okText: 'Supprimer'
});
if (!confirmed) return;
try {
await api.delete(`/api/users/${id}`);