From 75551be69d764e21261ee24804cfa3120626b934 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Nov 2025 08:43:46 +0000 Subject: [PATCH] fix: resolve note todos synchronization issue between mobile and desktop Fixed critical bug where todos added on mobile or desktop were being silently lost. Root cause: - Mobile and desktop were sending todos via the generic PUT /api/notes/:id endpoint - Backend ignored the 'todos' field in this endpoint (only processes title and content) - Users saw success messages but todos were never saved to database - On reload, todos disappeared Solution: - Added dedicated todo management methods to NotesService: * addTodo() - POST /api/notes/:id/todos * updateTodo() - PUT /api/notes/todos/:todoId * toggleTodo() - PUT /api/notes/todos/:todoId * deleteTodo() - DELETE /api/notes/todos/:todoId - Updated NoteDetailPage.tsx (mobile) to use proper API methods - Updated Index.tsx (desktop) to use proper API methods - Both versions now correctly persist todos to the database Files modified: - src/services/NotesService.ts - Added todo management methods - src/pages/mobile/NoteDetailPage.tsx - Fixed todo handlers - src/pages/Index.tsx - Fixed todo handlers in note detail view --- src/pages/Index.tsx | 32 ++++++++---- src/pages/mobile/NoteDetailPage.tsx | 40 ++++++++++----- src/services/NotesService.ts | 79 +++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 23 deletions(-) diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 9a2eacf..62f9c7e 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1414,9 +1414,14 @@ const Index = () => { { - const updatedTodos = [...(openNote.todos || [])]; - updatedTodos[index] = { ...todo, completed: !todo.completed }; - await handleUpdateNote({ todos: updatedTodos }); + if (!todo.id) return; + const success = await NotesService.toggleTodo(todo.id, !todo.completed); + if (success) { + const updatedTodos = [...(openNote.todos || [])]; + updatedTodos[index] = { ...todo, completed: !todo.completed }; + setOpenNote({ ...openNote, todos: updatedTodos }); + setNotes(prev => prev.map(note => note.id === openNote.id ? { ...note, todos: updatedTodos } : note)); + } }} /> @@ -1427,9 +1432,14 @@ const Index = () => { size="icon" className="h-6 w-6" onClick={async () => { - const updatedTodos = [...(openNote.todos || [])]; - updatedTodos.splice(index, 1); - await handleUpdateNote({ todos: updatedTodos }); + if (!todo.id) return; + const success = await NotesService.deleteTodo(todo.id); + if (success) { + const updatedTodos = openNote.todos.filter((_, i) => i !== index); + setOpenNote({ ...openNote, todos: updatedTodos }); + setNotes(prev => prev.map(note => note.id === openNote.id ? { ...note, todos: updatedTodos } : note)); + showSuccess("Tâche supprimée"); + } }} > @@ -2215,9 +2225,13 @@ const Index = () => { label="Tâche" placeholder="Entrez la tâche..." onConfirm={async (text) => { - if (openNote) { - const updatedTodos = [...(openNote.todos || []), { text, completed: false }]; - await handleUpdateNote({ todos: updatedTodos }); + if (openNote?.id) { + const newTodo = await NotesService.addTodo(openNote.id, text); + if (newTodo) { + const updatedTodos = [...(openNote.todos || []), newTodo]; + setOpenNote({ ...openNote, todos: updatedTodos }); + setNotes(prev => prev.map(note => note.id === openNote.id ? { ...note, todos: updatedTodos } : note)); + } } }} confirmText="Ajouter" diff --git a/src/pages/mobile/NoteDetailPage.tsx b/src/pages/mobile/NoteDetailPage.tsx index 15bf8cd..b525f1a 100644 --- a/src/pages/mobile/NoteDetailPage.tsx +++ b/src/pages/mobile/NoteDetailPage.tsx @@ -80,28 +80,40 @@ export default function NoteDetailPage() { }; const handleAddTodo = async () => { - if (!note || !todoText.trim()) return; - const newTodo = { text: todoText, completed: false }; - const updatedTodos = [...(note.todos || []), newTodo]; - await updateNote({ todos: updatedTodos }); - setTodoText(""); - setAddTodoModal(false); - showSuccess("Tâche ajoutée"); + if (!note?.id || !todoText.trim()) return; + const newTodo = await NotesService.addTodo(note.id, todoText.trim()); + if (newTodo) { + const updatedTodos = [...(note.todos || []), newTodo]; + setNote({ ...note, todos: updatedTodos }); + setTodoText(""); + setAddTodoModal(false); + } }; const handleToggleTodo = async (index: number) => { if (!note?.todos) return; - const updatedTodos = [...note.todos]; - updatedTodos[index] = { ...updatedTodos[index], completed: !updatedTodos[index].completed }; - await updateNote({ todos: updatedTodos }); + const todo = note.todos[index]; + if (!todo.id) return; + + const success = await NotesService.toggleTodo(todo.id, !todo.completed); + if (success) { + const updatedTodos = [...note.todos]; + updatedTodos[index] = { ...updatedTodos[index], completed: !updatedTodos[index].completed }; + setNote({ ...note, todos: updatedTodos }); + } }; const handleDeleteTodo = async (index: number) => { if (!note?.todos) return; - const updatedTodos = [...note.todos]; - updatedTodos.splice(index, 1); - await updateNote({ todos: updatedTodos }); - showSuccess("Tâche supprimée"); + const todo = note.todos[index]; + if (!todo.id) return; + + const success = await NotesService.deleteTodo(todo.id); + if (success) { + const updatedTodos = note.todos.filter((_, i) => i !== index); + setNote({ ...note, todos: updatedTodos }); + showSuccess("Tâche supprimée"); + } }; const handleImageUpload = async (e: React.ChangeEvent) => { diff --git a/src/services/NotesService.ts b/src/services/NotesService.ts index c4d338a..d308bf3 100644 --- a/src/services/NotesService.ts +++ b/src/services/NotesService.ts @@ -219,6 +219,85 @@ class NotesService { return false; } } + + // Todo management methods + async addTodo(noteId: number, text: string): Promise { + try { + const response = await fetch(`/api/notes/${noteId}/todos`, { + method: "POST", + headers: AuthService.getHeaders(), + body: JSON.stringify({ text }) + }); + + if (!response.ok) { + throw new Error("Erreur lors de l'ajout de la tâche"); + } + + const result = await response.json(); + showSuccess("Tâche ajoutée"); + return result.todo || result; + } catch (error) { + showError(error instanceof Error ? error.message : "Erreur serveur"); + return null; + } + } + + async updateTodo(todoId: number, updates: Partial): Promise { + try { + const response = await fetch(`/api/notes/todos/${todoId}`, { + method: "PUT", + headers: AuthService.getHeaders(), + body: JSON.stringify(updates) + }); + + if (!response.ok) { + throw new Error("Erreur lors de la mise à jour de la tâche"); + } + + const result = await response.json(); + return result.todo || result; + } catch (error) { + showError(error instanceof Error ? error.message : "Erreur serveur"); + return null; + } + } + + async toggleTodo(todoId: number, completed: boolean): Promise { + try { + const response = await fetch(`/api/notes/todos/${todoId}`, { + method: "PUT", + headers: AuthService.getHeaders(), + body: JSON.stringify({ completed }) + }); + + if (!response.ok) { + throw new Error("Erreur lors de la mise à jour de la tâche"); + } + + return true; + } catch (error) { + showError(error instanceof Error ? error.message : "Erreur serveur"); + return false; + } + } + + async deleteTodo(todoId: number): Promise { + try { + const response = await fetch(`/api/notes/todos/${todoId}`, { + method: "DELETE", + headers: AuthService.getHeaders() + }); + + if (!response.ok) { + throw new Error("Erreur lors de la suppression de la tâche"); + } + + return true; + } catch (error) { + showError(error instanceof Error ? error.message : "Erreur serveur"); + return false; + } + } } export default new NotesService(); \ No newline at end of file