feat: running timers, symptom log, search, PDF export

- dashboard: ActiveTimers widget — live h:mm:ss for any in-progress timed event (breastfeed, nap, sleep, walk…), Terminer button patches endedAt
- dashboard: reorganised quick-log groups; Santé group (Température, Traitement, Symptôme)
- events: SYMPTOM type — tag chips (Fièvre, Reflux, Colique, Éruption, Toux, Congestion, Diarrhée, Vomissement, Irritabilité, Pleurs excessifs), stored in metadata.tags
- prisma: migration adding SYMPTOM to EventType enum
- search: GET /api/search (events by notes ILIKE, journal by content ILIKE), /search page with debounced input + keyword highlight
- nav: Recherche link added to LINKS + More drawer
- growth: PDF export button — generates formatted table with all measurements, zebra rows, downloads croissance-{name}.pdf

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 17:27:01 +02:00
parent cd677d9244
commit 009090b381
14 changed files with 549 additions and 10 deletions
+64 -8
View File
@@ -11,7 +11,7 @@ import {
import { useBaby } from "@/contexts/baby-context";
import { format, subDays } from "date-fns";
import { fr } from "date-fns/locale";
import { Plus, Pencil, Trash2 } from "lucide-react";
import { Plus, Pencil, Trash2, FileDown } from "lucide-react";
interface GrowthLog {
id: string;
@@ -241,6 +241,53 @@ export default function GrowthPage() {
qc.invalidateQueries({ queryKey: ["growth"] });
}
function exportPDF() {
import("jspdf").then(({ default: jsPDF }) => {
const doc = new jsPDF();
const pageW = doc.internal.pageSize.getWidth();
doc.setFontSize(18);
doc.setTextColor(30, 41, 59);
doc.text("Courbe de croissance", 14, 20);
doc.setFontSize(11);
doc.setTextColor(100, 116, 139);
doc.text(`${baby!.name} · né${baby!.gender === "F" ? "e" : ""} le ${format(new Date(baby!.birthDate), "d MMMM yyyy", { locale: fr })}`, 14, 28);
doc.text(`Généré le ${format(new Date(), "d MMMM yyyy", { locale: fr })}`, 14, 34);
// Table header
const COL = [14, 55, 95, 130, 155];
const headers = ["Date", "Poids (kg)", "Taille (cm)", "PC (cm)", "Notes"];
let y = 46;
doc.setFillColor(238, 242, 255);
doc.rect(14, y - 5, pageW - 28, 9, "F");
doc.setFontSize(9);
doc.setTextColor(79, 70, 229);
headers.forEach((h, i) => doc.text(h, COL[i], y));
y += 8;
doc.setFontSize(9);
const sorted = [...logs].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
sorted.forEach((log, idx) => {
if (y > 270) { doc.addPage(); y = 20; }
if (idx % 2 === 0) {
doc.setFillColor(248, 250, 252);
doc.rect(14, y - 4, pageW - 28, 7, "F");
}
doc.setTextColor(30, 41, 59);
doc.text(format(new Date(log.date), "d MMM yyyy", { locale: fr }), COL[0], y);
doc.text(log.weight ? (log.weight / 1000).toFixed(3) : "—", COL[1], y);
doc.text(log.height ? String(log.height) : "—", COL[2], y);
doc.text(log.headCirc ? String(log.headCirc) : "—", COL[3], y);
if (log.notes) doc.text(log.notes.slice(0, 30), COL[4], y);
y += 7;
});
doc.save(`croissance-${baby!.name.toLowerCase()}.pdf`);
});
}
const latest = logs.at(-1);
const rangeOptions: { label: string; value: TimeRange }[] = [
@@ -257,13 +304,22 @@ export default function GrowthPage() {
<h1 className="text-2xl font-bold text-slate-900 dark:text-slate-100">Courbes de croissance</h1>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-0.5">{baby?.name}</p>
</div>
<button
onClick={() => showForm ? setShowForm(false) : openAddForm()}
className="flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition"
>
<Plus className="w-4 h-4" />
Mesure
</button>
<div className="flex gap-2">
{logs.length > 0 && (
<button onClick={exportPDF}
className="flex items-center gap-2 px-3 py-2 border border-slate-200 dark:border-slate-700 text-slate-600 dark:text-slate-300 text-sm font-medium rounded-lg hover:bg-slate-50 dark:hover:bg-slate-700 transition">
<FileDown className="w-4 h-4" />
PDF
</button>
)}
<button
onClick={() => showForm ? setShowForm(false) : openAddForm()}
className="flex items-center gap-2 px-4 py-2 bg-indigo-600 text-white text-sm font-medium rounded-lg hover:bg-indigo-700 transition"
>
<Plus className="w-4 h-4" />
Mesure
</button>
</div>
</div>
{/* Add measurement form */}