// components/NotesSection.tsx "use client"; import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Loader2, StickyNote } from "lucide-react"; import { Card, CardHeader, CardTitle } from "@/components/ui/card"; export type Note = { id: string; content: string; author?: string; source?: string; created_at?: string; source_created_at?: string; }; function formatDateTimeFR(iso?: string) { if (!iso) return ""; const d = new Date(iso); return d.toLocaleString("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", }); } function useContratNotes(contractId: string) { return useQuery({ queryKey: ["contrat", contractId, "notes"], queryFn: async () => { const res = await fetch(`/api/contrats/${contractId}/notes`, { credentials: "include" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const json = await res.json(); return Array.isArray(json?.items) ? json.items : []; }, staleTime: 10_000, }); } function useAddNote(contractId: string, source: string = "Client") { const qc = useQueryClient(); return useMutation({ mutationFn: async (content: string) => { const res = await fetch(`/api/contrats/${contractId}/notes`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ content, source }), }); if (!res.ok) { const t = await res.text().catch(() => ""); throw new Error(t || `HTTP ${res.status}`); } return true; }, onSuccess: () => qc.invalidateQueries({ queryKey: ["contrat", contractId, "notes"] }), }); } export function NotesSection({ contractId, contractRef, title = "Notes", showAddButton = true, compact = false, source = "Client", }: { contractId: string; contractRef?: string; title?: string; showAddButton?: boolean; compact?: boolean; source?: string; }) { const { data: notes, isLoading, isError, error } = useContratNotes(contractId); const addNote = useAddNote(contractId, source); const [open, setOpen] = useState(false); const [text, setText] = useState(""); const [localErr, setLocalErr] = useState(undefined); const submit = async () => { if (!text.trim()) { setLocalErr("La note est vide."); return; } setLocalErr(undefined); try { await addNote.mutateAsync(text.trim()); setText(""); setOpen(false); } catch (e: any) { setLocalErr(e?.message || "Échec de l'envoi"); } }; return ( {title}
{showAddButton && (
)} {isLoading && (
Chargement des notes…
)} {isError && (
Impossible de charger les notes{error ? ` : ${(error as any)?.message ?? ""}` : ""}
)} {!isLoading && !isError && (
{notes && notes.length > 0 ? ( notes.map((n) => (
{n.author ? ( {n.author} ) : null} {/* Source badge */} {n.source ? ( {n.source} ) : null} {((n.source_created_at && n.source_created_at.length) || (n.created_at && n.created_at.length)) ? ( {formatDateTimeFR(n.source_created_at || n.created_at)} ) : null}
{n.content}
)) ) : (
Aucune note pour l'instant.
)}
)}
{/* Modal ajout de note */} {open && (
{/* overlay */}
!addNote.isPending && setOpen(false)} /> {/* card */}
Ajout de note
Contrat {contractRef || contractId}