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
This commit is contained in:
Claude committed 2025-11-27 08:43:46 +00:00
1 parent 425b7f36a0
commit 75551be69d
3 files changed
+120 -15

No files matched your search

+21 -7
View File
@@ -1414,9 +1414,14 @@ const Index = () => {
<Checkbox
checked={todo.completed}
onCheckedChange={async () => {
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 };
await handleUpdateNote({ todos: updatedTodos });
setOpenNote({ ...openNote, todos: updatedTodos });
setNotes(prev => prev.map(note => note.id === openNote.id ? { ...note, todos: updatedTodos } : note));
}
}}
/>
<span className={todo.completed ? "line-through text-muted-foreground flex-1 text-sm" : "flex-1 text-sm"}>
@@ -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");
}
}}
>
<Trash2 className="h-3 w-3" />
@@ -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"
+20 -8
View File
@@ -80,28 +80,40 @@ export default function NoteDetailPage() {
};
const handleAddTodo = async () => {
if (!note || !todoText.trim()) return;
const newTodo = { text: todoText, completed: false };
if (!note?.id || !todoText.trim()) return;
const newTodo = await NotesService.addTodo(note.id, todoText.trim());
if (newTodo) {
const updatedTodos = [...(note.todos || []), newTodo];
await updateNote({ todos: updatedTodos });
setNote({ ...note, todos: updatedTodos });
setTodoText("");
setAddTodoModal(false);
showSuccess("Tâche ajoutée");
}
};
const handleToggleTodo = async (index: number) => {
if (!note?.todos) return;
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 };
await updateNote({ todos: updatedTodos });
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 });
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<HTMLInputElement>) => {
+79
View File
@@ -219,6 +219,85 @@ class NotesService {
return false;
}
}
// Todo management methods
async addTodo(noteId: number, text: string): Promise<Todo | null> {
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<Todo>): Promise<Todo | null> {
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<boolean> {
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<boolean> {
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();