feat: Phase 4 — module Notes complet

Backend :
- schemas/notes.py : NoteCreate/Update/Response + AttachmentResponse
- api/notes.py : CRUD + FTS français (plainto_tsquery) + filtres rapides
  (has_photo/audio/gps/tag/category) + pièces jointes (image/audio)
- main.py : enregistrement /api/notes

Frontend :
- api/notes.ts : fetchNotes/create/update/delete + add/deleteAttachment
- NoteForm.tsx : titre, contenu, catégorie, tags CSV, GPS
- NotesPage.tsx : liste mobile (chronologique) + grille laptop, FAB +,
  enregistrement audio inline (MediaRecorder), upload photo, filtres rapides

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 06:49:46 +02:00
parent c4634b5a27
commit fdeb747f38
6 changed files with 835 additions and 4 deletions
+91
View File
@@ -0,0 +1,91 @@
export interface NoteAttachment {
id: string
file_path: string | null
thumbnail_path: string | null
file_type: 'image' | 'audio' | null
original_name: string | null
created_at: string
}
export interface Note {
id: string
title: string | null
content: string
category: string | null
tags: string[]
gps_lat: number | null
gps_lon: number | null
created_at: string
attachments: NoteAttachment[]
}
export interface NoteCreate {
title?: string
content: string
category?: string
tags?: string[]
gps_lat?: number
gps_lon?: number
}
export interface NoteFilters {
q?: string
category?: string
tag?: string
has_photo?: boolean
has_audio?: boolean
has_gps?: boolean
}
const BASE = '/api/notes'
export async function fetchNotes(filters: NoteFilters = {}): Promise<Note[]> {
const params = new URLSearchParams()
if (filters.q) params.set('q', filters.q)
if (filters.category) params.set('category', filters.category)
if (filters.tag) params.set('tag', filters.tag)
if (filters.has_photo !== undefined) params.set('has_photo', String(filters.has_photo))
if (filters.has_audio !== undefined) params.set('has_audio', String(filters.has_audio))
if (filters.has_gps !== undefined) params.set('has_gps', String(filters.has_gps))
const res = await fetch(`${BASE}/?${params}`)
if (!res.ok) throw new Error('Erreur chargement notes')
return res.json() as Promise<Note[]>
}
export async function createNote(data: NoteCreate): Promise<Note> {
const res = await fetch(`${BASE}/`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!res.ok) throw new Error('Erreur création note')
return res.json() as Promise<Note>
}
export async function updateNote(id: string, data: Partial<NoteCreate>): Promise<Note> {
const res = await fetch(`${BASE}/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!res.ok) throw new Error('Erreur mise à jour note')
return res.json() as Promise<Note>
}
export async function deleteNote(id: string): Promise<void> {
const res = await fetch(`${BASE}/${id}`, { method: 'DELETE' })
if (!res.ok) throw new Error('Erreur suppression note')
}
export async function addAttachment(noteId: string, file: File): Promise<Note> {
const fd = new FormData()
fd.append('file', file)
const res = await fetch(`${BASE}/${noteId}/attachments`, { method: 'POST', body: fd })
if (!res.ok) throw new Error('Erreur upload pièce jointe')
return res.json() as Promise<Note>
}
export async function deleteAttachment(noteId: string, attId: string): Promise<void> {
const res = await fetch(`${BASE}/${noteId}/attachments/${attId}`, { method: 'DELETE' })
if (!res.ok) throw new Error('Erreur suppression pièce jointe')
}