diff --git a/prisma/migrations/20250115130000_add_task_to_notes/migration.sql b/prisma/migrations/20250115130000_add_task_to_notes/migration.sql new file mode 100644 index 0000000..85dd269 --- /dev/null +++ b/prisma/migrations/20250115130000_add_task_to_notes/migration.sql @@ -0,0 +1,6 @@ +-- AlterTable +ALTER TABLE "Note" ADD COLUMN "taskId" TEXT; + +-- AddForeignKey +ALTER TABLE "Note" ADD CONSTRAINT "Note_taskId_fkey" FOREIGN KEY ("taskId") REFERENCES "tasks"("id") ON DELETE SET NULL ON UPDATE CASCADE; + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b21c42e..448bb23 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -51,6 +51,7 @@ model Task { primaryTag Tag? @relation("PrimaryTag", fields: [primaryTagId], references: [id]) dailyCheckboxes DailyCheckbox[] taskTags TaskTag[] + notes Note[] // Notes associées à cette tâche @@unique([source, sourceId]) @@map("tasks") @@ -129,9 +130,11 @@ model Note { title String content String // Markdown content userId String + taskId String? // Tâche associée à la note createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) + task Task? @relation(fields: [taskId], references: [id]) noteTags NoteTag[] } diff --git a/src/app/api/notes/[id]/route.ts b/src/app/api/notes/[id]/route.ts index 43aa4bf..99957aa 100644 --- a/src/app/api/notes/[id]/route.ts +++ b/src/app/api/notes/[id]/route.ts @@ -52,7 +52,7 @@ export async function PUT( } const body = await request.json(); - const { title, content, tags } = body; + const { title, content, taskId, tags } = body; const resolvedParams = await params; const note = await notesService.updateNote( @@ -61,6 +61,7 @@ export async function PUT( { title, content, + taskId, tags, } ); diff --git a/src/app/api/notes/route.ts b/src/app/api/notes/route.ts index 5dcfecc..13b9f4d 100644 --- a/src/app/api/notes/route.ts +++ b/src/app/api/notes/route.ts @@ -46,7 +46,7 @@ export async function POST(request: Request) { } const body = await request.json(); - const { title, content, tags } = body; + const { title, content, taskId, tags } = body; if (!title || !content) { return NextResponse.json( @@ -59,6 +59,7 @@ export async function POST(request: Request) { title, content, userId: session.user.id, + taskId, tags, }); diff --git a/src/app/notes/NotesPageClient.tsx b/src/app/notes/NotesPageClient.tsx index 68dacbc..e719161 100644 --- a/src/app/notes/NotesPageClient.tsx +++ b/src/app/notes/NotesPageClient.tsx @@ -2,6 +2,7 @@ import { useState, useEffect, useCallback } from 'react'; import { Note } from '@/services/notes'; +import { Task } from '@/lib/types'; import { notesClient } from '@/clients/notes'; import { NotesList } from '@/components/notes/NotesList'; import { MarkdownEditor } from '@/components/notes/MarkdownEditor'; @@ -118,6 +119,7 @@ function NotesPageContent({ initialNotes }: { initialNotes: Note[] }) { const updatedNote = await notesClient.updateNote(selectedNote.id, { content: selectedNote.content, tags: selectedNote.tags, + taskId: selectedNote.taskId, }); // Mettre à jour la liste des notes mais pas selectedNote pour éviter la perte de focus @@ -144,6 +146,18 @@ function NotesPageContent({ initialNotes }: { initialNotes: Note[] }) { [selectedNote] ); + const handleTaskChange = useCallback( + (task: Task | null) => { + if (!selectedNote) return; + + setSelectedNote((prev) => + prev ? { ...prev, taskId: task?.id, task } : null + ); + setHasUnsavedChanges(true); + }, + [selectedNote] + ); + // Auto-save quand les tags changent useEffect(() => { if (hasUnsavedChanges && selectedNote) { @@ -258,6 +272,9 @@ function NotesPageContent({ initialNotes }: { initialNotes: Note[] }) { tags={selectedNote.tags} onTagsChange={handleTagsChange} availableTags={availableTags} + selectedTaskId={selectedNote.taskId} + selectedTask={selectedNote.task} + onTaskChange={handleTaskChange} onCreateNote={handleCreateNote} onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed) diff --git a/src/clients/notes.ts b/src/clients/notes.ts index f29ff74..5a9a87f 100644 --- a/src/clients/notes.ts +++ b/src/clients/notes.ts @@ -4,12 +4,14 @@ import { Note } from '@/services/notes'; export interface CreateNoteData { title: string; content: string; + taskId?: string; // Tâche associée à la note tags?: string[]; } export interface UpdateNoteData { title?: string; content?: string; + taskId?: string; // Tâche associée à la note tags?: string[]; } diff --git a/src/components/notes/MarkdownEditor.tsx b/src/components/notes/MarkdownEditor.tsx index 057b065..7451e27 100644 --- a/src/components/notes/MarkdownEditor.tsx +++ b/src/components/notes/MarkdownEditor.tsx @@ -5,10 +5,11 @@ import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import rehypeHighlight from 'rehype-highlight'; import rehypeSanitize from 'rehype-sanitize'; -import { Eye, EyeOff, Edit3, X } from 'lucide-react'; +import { Eye, EyeOff, Edit3, X, CheckSquare2 } from 'lucide-react'; import { TagInput } from '@/components/ui/TagInput'; import { TagDisplay } from '@/components/ui/TagDisplay'; -import { Tag } from '@/lib/types'; +import { TaskSelector } from '@/components/ui/TaskSelector'; +import { Tag, Task } from '@/lib/types'; interface MarkdownEditorProps { value: string; @@ -20,6 +21,9 @@ interface MarkdownEditorProps { tags?: string[]; onTagsChange?: (tags: string[]) => void; availableTags?: Tag[]; + selectedTaskId?: string; + selectedTask?: Task | null; // Objet Task complet pour l'affichage + onTaskChange?: (task: Task | null) => void; onCreateNote?: () => void; onToggleSidebar?: () => void; } @@ -34,6 +38,9 @@ export function MarkdownEditor({ tags = [], onTagsChange, availableTags = [], + selectedTaskId, + selectedTask, + onTaskChange, onCreateNote, onToggleSidebar, }: MarkdownEditorProps) { @@ -333,20 +340,40 @@ export function MarkdownEditor({