diff --git a/FEATURES.md b/FEATURES.md index 4acd3a0..0159cec 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -56,14 +56,16 @@ 🔲 Offline support / PWA install prompt — service worker for offline event logging 🔲 Multi-language UI (FR/EN toggle) — see also pending Bilingual UI -🔲 Weekly summary push notification (Sunday digest: feedings, sleep, weight change) +✅ Weekly summary cron — POST /api/cron/weekly-summary, 7-day digest per baby (feedings, sleep, diapers, weight), Pushover to family; auth via Bearer cron_secret +✅ Baby activities log — BATH, WALK, TUMMY_TIME event types (timer + duration) +✅ Pinned family note — shared editable note on dashboard; /api/family/pinned-note +✅ Event templates — named quick-log presets; create/delete from dashboard; /api/event-templates +✅ Maintenance mode banner — layout reads SystemConfig; amber banner for users, indicator for superadmin 🔲 Growth alert: no weight log in 30 days → push notification 🔲 Feeding pattern AI insight (detect sleep regression, feeding cluster, etc.) 🔲 Contacts / care team directory (pediatrician, midwife, family contact numbers) -🔲 Baby activities log (bath, walk, tummy time) — lightweight custom event types 🔲 Sleep log chart — visual night timeline (bar per night, wake markers) 🔲 Bottle feed from milk stock — direct link: log BOTTLE → decrement milk stock -🔲 Shared family notes / pinned message (daily handoff summary visible to all members) -🔲 Event templates / quick presets (e.g. "usual nap 13:00–14:30") 🔲 Data retention policy — auto-archive events older than N months -🔲 Maintenance mode banner in layout (config key exists, layout not wired yet) +🔲 Offline support / PWA install prompt +🔲 Multi-language UI (FR/EN toggle) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f046874..f23ab56 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -85,12 +85,27 @@ model VerificationToken { } model Family { - id String @id @default(cuid()) - name String - inviteCode String @unique @default(cuid()) - users User[] - babies Baby[] - createdAt DateTime @default(now()) + id String @id @default(cuid()) + name String + inviteCode String @unique @default(cuid()) + users User[] + babies Baby[] + eventTemplates EventTemplate[] + pinnedNote String? + pinnedNoteUpdatedAt DateTime? + pinnedNoteAuthor String? + createdAt DateTime @default(now()) +} + +model EventTemplate { + id String @id @default(cuid()) + familyId String + family Family @relation(fields: [familyId], references: [id], onDelete: Cascade) + label String + type EventType + metadata Json? + sortOrder Int @default(0) + createdAt DateTime @default(now()) } model Baby { @@ -136,6 +151,9 @@ enum EventType { NIGHT_SLEEP MEDICATION TEMPERATURE + BATH + WALK + TUMMY_TIME } model GrowthLog { diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index f75b500..5c150f0 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { EVENT_CONFIG, EventType, formatTimeAgo, formatDuration } from "@/lib/event-config"; import { EventModal } from "@/components/event-modal"; @@ -9,7 +9,7 @@ import { useBaby } from "@/contexts/baby-context"; import { checkFeedAlert, getThresholds } from "@/lib/notifications"; import { format, isToday } from "date-fns"; import { fr } from "date-fns/locale"; -import { AlertTriangle, Clock, CheckCircle2 } from "lucide-react"; +import { AlertTriangle, Clock, CheckCircle2, Pin, PinOff, Pencil, Check, X, Plus, Trash2 } from "lucide-react"; const QUICK_LOG_TYPES: EventType[] = [ "BREASTFEED", @@ -21,6 +21,9 @@ const QUICK_LOG_TYPES: EventType[] = [ "NIGHT_SLEEP", "MEDICATION", "TEMPERATURE", + "BATH", + "WALK", + "TUMMY_TIME", ]; interface Event { @@ -33,6 +36,13 @@ interface Event { user: { id: string; name: string }; } +interface EventTemplate { + id: string; + label: string; + type: EventType; + metadata?: Record | null; +} + function getBabyAge(birthDate: string): string { const birth = new Date(birthDate); const now = new Date(); @@ -63,6 +73,210 @@ function getEventSummary(event: Event): string { return ""; } +function PinnedNoteWidget() { + const qc = useQueryClient(); + const [editing, setEditing] = useState(false); + const [draft, setDraft] = useState(""); + const [saving, setSaving] = useState(false); + const textareaRef = useRef(null); + + const { data } = useQuery<{ note: string | null; updatedAt: string | null; author: string | null }>({ + queryKey: ["pinned-note"], + queryFn: () => fetch("/api/family/pinned-note").then((r) => r.json()), + }); + + function startEdit() { + setDraft(data?.note ?? ""); + setEditing(true); + setTimeout(() => textareaRef.current?.focus(), 50); + } + + async function save() { + setSaving(true); + await fetch("/api/family/pinned-note", { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ note: draft }), + }); + qc.invalidateQueries({ queryKey: ["pinned-note"] }); + setEditing(false); + setSaving(false); + } + + const hasNote = !!data?.note; + + return ( +
+
+
+ + Note épinglée +
+ {!editing && ( + + )} +
+ + {editing ? ( +
+