From 75551be69d764e21261ee24804cfa3120626b934 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Nov 2025 08:43:46 +0000 Subject: [PATCH 1/2] 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 From 2b7d824c223e1ecad30be048f8a21fed8e6a8851 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Nov 2025 08:44:34 +0000 Subject: [PATCH 2/2] chore: update package-lock.json after npm install --- package-lock.json | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 15c3881..c3b5d09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -138,7 +138,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -3682,7 +3681,6 @@ "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3693,7 +3691,6 @@ "integrity": "sha512-k5dJVszUiNr1DSe8Cs+knKR6IrqhqdhpUwzqhkS8ecQTSf3THNtbfIp/umqHMpX2bv+9dkx3fwDv/86LcSfvSg==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3704,7 +3701,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3761,7 +3757,6 @@ "integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.4", "@typescript-eslint/types": "8.46.4", @@ -4047,7 +4042,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4482,7 +4476,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -5335,8 +5328,7 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -5537,7 +5529,6 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6992,7 +6983,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -8226,7 +8216,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.9.1", "pg-pool": "^3.10.1", @@ -8366,7 +8355,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -8757,7 +8745,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8798,7 +8785,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -9851,7 +9837,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -10056,7 +10041,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -10185,7 +10169,6 @@ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -10433,7 +10416,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -10525,7 +10507,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" },