"use client"; import { useEffect, useState, useMemo } from "react"; import { useFamily } from "@/app/FamilyProvider"; import { useStageCheck } from "@/hooks/useStageCheck"; import { MILESTONES, type MilestoneDef } from "@/lib/milestones"; type Category = "all" | "social" | "motor" | "language" | "cognitive"; type Filter = "all" | "achieved" | "upcoming"; interface MilestoneWithStatus extends MilestoneDef { achieved: boolean; achievedAt: string | null; notes: string | null; } const CATEGORY_COLORS: Record = { social: "bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300", motor: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300", language: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300", cognitive: "bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300", }; export default function MilestonesPage() { const { child } = useFamily(); const stage = useStageCheck(child?.birthDate ?? null); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [filter, setFilter] = useState("all"); const [category, setCategory] = useState("all"); // date picker state per milestone key const [pendingKey, setPendingKey] = useState(null); const [pendingDate, setPendingDate] = useState( new Date().toISOString().slice(0, 10) ); useEffect(() => { if (!child?.id) return; setLoading(true); fetch(`/api/milestones?childId=${child.id}`) .then(r => r.json()) .then(d => { setItems(d.items || []); setLoading(false); }) .catch(() => setLoading(false)); }, [child?.id]); async function toggleMilestone(m: MilestoneWithStatus) { if (m.achieved) { // un-mark await fetch(`/api/milestones/${m.key}?childId=${child!.id}`, { method: "DELETE" }); setItems(prev => prev.map(x => x.key === m.key ? { ...x, achieved: false, achievedAt: null } : x)); if (pendingKey === m.key) setPendingKey(null); } else { setPendingKey(m.key); setPendingDate(new Date().toISOString().slice(0, 10)); } } async function confirmAchieved(key: string) { await fetch("/api/milestones", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ childId: child!.id, milestoneKey: key, achievedAt: pendingDate }), }); setItems(prev => prev.map(x => x.key === key ? { ...x, achieved: true, achievedAt: pendingDate } : x)); setPendingKey(null); } const filtered = useMemo(() => { const ageMonths = stage?.ageMonths ?? 0; return items.filter(m => { if (category !== "all" && m.category !== category) return false; if (filter === "achieved") return m.achieved; if (filter === "upcoming") return !m.achieved && m.ageMonths <= ageMonths + 6; return true; }); }, [items, filter, category, stage]); const achievedCount = items.filter(m => m.achieved).length; const nowItems = useMemo(() => { if (!stage) return new Set(); const lo = stage.ageMonths - 3, hi = stage.ageMonths + 3; return new Set(items.filter(m => m.ageMonths >= lo && m.ageMonths <= hi).map(m => m.key)); }, [items, stage]); if (!child) return (

Select a child to view milestones.

); return (
{/* Header */}

Milestones

{child.name}

{stage && ( {stage.emoji} {stage.label} )}
{/* Progress bar */}
{achievedCount} of {items.length} milestones {Math.round(achievedCount / Math.max(items.length, 1) * 100)}%
{/* Filter tabs */}
{(["all", "achieved", "upcoming"] as Filter[]).map(f => ( ))}
{/* Category chips */}
{(["all", "social", "motor", "language", "cognitive"] as Category[]).map(c => ( ))}
{/* Milestone grid */} {loading ? (
{Array.from({ length: 6 }).map((_, i) => (
))}
) : filtered.length === 0 ? (

No milestones match this filter.

) : (
{filtered.map(m => (
{/* Inline date picker */} {pendingKey === m.key && (

When did this happen?

setPendingDate(e.target.value)} className="w-full text-sm border dark:border-gray-600 rounded-lg px-2 py-1.5 dark:bg-gray-700 dark:text-white mb-2" />
)}
))}
)}
); }