diff --git a/src/app/activity/page.tsx b/src/app/activity/page.tsx index 86d4a13..e43643c 100644 --- a/src/app/activity/page.tsx +++ b/src/app/activity/page.tsx @@ -1,8 +1,9 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import Link from "next/link"; import { useFamily } from "../FamilyProvider"; +import { getGuideline, getAgeInMonths } from "@/lib/guidelines"; type ViewMode = "timeline" | "calendar"; type LogType = "feed" | "sleep" | "diaper"; @@ -16,39 +17,218 @@ interface Log { loggedAt: string; } -interface Child { - id: string; - name: string; - birthDate: string; -} - -import { getGuideline, getAgeInMonths, guidelines } from "@/lib/guidelines"; - interface DayLogs { date: string; logs: Log[]; } +const TYPE_COLORS: Record = { + feed: "bg-rose-400", + sleep: "bg-blue-400", + diaper: "bg-amber-400", +}; + +function getIcon(type: LogType) { + if (type === "feed") return "🍼"; + if (type === "sleep") return "😴"; + if (type === "diaper") return "πŸ‘Ά"; + return "πŸ“"; +} + +// ─── Calendar view ──────────────────────────────────────────────────────────── + +function CalendarView({ logs, filter }: { logs: Log[]; filter: LogType | "all" }) { + const now = new Date(); + const [calMonth, setCalMonth] = useState(new Date(now.getFullYear(), now.getMonth(), 1)); + const [selectedDay, setSelectedDay] = useState( + now.toISOString().slice(0, 10) + ); + + const year = calMonth.getFullYear(); + const month = calMonth.getMonth(); + const today = now.toISOString().slice(0, 10); + const isCurrentMonth = year === now.getFullYear() && month === now.getMonth(); + + const filteredLogs = filter === "all" ? logs : logs.filter(l => l.type === filter); + + // Group logs by YYYY-MM-DD + const logsByDate = useMemo(() => { + const map: Record = {}; + filteredLogs.forEach(log => { + const key = new Date(log.loggedAt).toISOString().slice(0, 10); + if (!map[key]) map[key] = []; + map[key].push(log); + }); + return map; + }, [filteredLogs]); + + // Build grid: leading nulls for DOW offset, then day numbers + const firstDow = new Date(year, month, 1).getDay(); + const daysInMonth = new Date(year, month + 1, 0).getDate(); + const cells: (number | null)[] = [ + ...Array(firstDow).fill(null), + ...Array.from({ length: daysInMonth }, (_, i) => i + 1), + ]; + + const dayKey = (d: number) => + `${year}-${String(month + 1).padStart(2, "0")}-${String(d).padStart(2, "0")}`; + + const selectedLogs = useMemo( + () => (selectedDay ? (logsByDate[selectedDay] || []) + .slice() + .sort((a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()) + : []), + [selectedDay, logsByDate] + ); + + return ( +
+ {/* Month grid */} +
+ {/* Month nav */} +
+ + + {calMonth.toLocaleDateString("en-US", { month: "long", year: "numeric" })} + + +
+ + {/* DOW headers */} +
+ {["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map(d => ( +
{d}
+ ))} +
+ + {/* Day cells */} +
+ {cells.map((day, i) => { + if (!day) return
; + const key = dayKey(day); + const dayLogs = logsByDate[key] || []; + const isToday = key === today; + const isSel = key === selectedDay; + const types = [...new Set(dayLogs.map(l => l.type))] as LogType[]; + + return ( + + ); + })} +
+ + {/* Legend */} +
+ {(["feed", "sleep", "diaper"] as LogType[]).map(t => ( +
+ + {t} +
+ ))} +
+
+ + {/* Selected day detail */} + {selectedDay && ( +
+
+

+ {new Date(selectedDay + "T12:00:00").toLocaleDateString("en-US", { + weekday: "long", month: "short", day: "numeric", + })} +

+ + {selectedLogs.length} {selectedLogs.length === 1 ? "entry" : "entries"} + +
+ + {selectedLogs.length === 0 ? ( +

No activity logged

+ ) : ( +
+ {selectedLogs.map(log => ( +
+ {getIcon(log.type)} +
+
{log.type}
+
+ {[ + log.subType?.replace(/_/g, " "), + log.amount ? `${log.amount}ml` : null, + log.notes, + ].filter(Boolean).join(" Β· ")} +
+
+
+ {new Date(log.loggedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} +
+
+ ))} +
+ )} +
+ )} +
+ ); +} + +// ─── Main page ──────────────────────────────────────────────────────────────── + export default function ActivityPage() { - const { child, childId: providerChildId, familyId } = useFamily(); - const [view, setView] = useState("timeline"); - const [logs, setLogs] = useState([]); - const [loading, setLoading] = useState(true); - const [filter, setFilter] = useState("all"); + const { child, childId: providerChildId } = useFamily(); + const [view, setView] = useState("timeline"); + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [filter, setFilter] = useState("all"); const [showSuggested, setShowSuggested] = useState(true); const [generating, setGenerating] = useState(false); - const childId = providerChildId || "default"; + const childId = providerChildId || ""; useEffect(() => { - if (providerChildId) { - fetchLogs(); - } + if (providerChildId) fetchLogs(); }, [providerChildId]); const fetchLogs = async () => { if (!childId) return; try { - const res = await fetch(`/api/logs?childId=${childId}&limit=100`); + const res = await fetch(`/api/logs?childId=${childId}&limit=200`); const data = await res.json(); setLogs(data.entries || []); } catch (err) { @@ -61,87 +241,75 @@ export default function ActivityPage() { if (!child) return; setGenerating(true); try { - const res = await fetch("/api/history", { - method: "POST", + const res = await fetch("/api/history", { + method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ childId: child.id, birthDate: child.birthDate }), + body: JSON.stringify({ childId: child.id, birthDate: child.birthDate }), }); const data = await res.json(); - if (data.success) { - fetchLogs(); - } + if (data.success) fetchLogs(); } catch (err) { console.error("Failed to generate:", err); } setGenerating(false); }; - const filteredLogs = filter === "all" ? logs : logs.filter((l) => l.type === filter); + const filteredLogs = filter === "all" ? logs : logs.filter(l => l.type === filter); const groupedByDay = filteredLogs.reduce((acc: DayLogs[], log) => { - const date = new Date(log.loggedAt).toDateString(); - const existing = acc.find((d) => d.date === date); - if (existing) { - existing.logs.push(log); - } else { - acc.push({ date, logs: [log] }); - } + const date = new Date(log.loggedAt).toDateString(); + const existing = acc.find(d => d.date === date); + if (existing) existing.logs.push(log); + else acc.push({ date, logs: [log] }); return acc; }, []); - const getIcon = (type: LogType) => { - switch (type) { - case "feed": return "🍼"; - case "sleep": return "😴"; - case "diaper": return "πŸ‘Ά"; - default: return "πŸ“"; - } - }; - return (
{/* Header */}
-
+
←

Activity

- {child && logs.length === 0 && ( + {child && logs.length === 0 && !loading && ( )}
- {/* View Toggle */} + {/* View toggle */}
- {/* Filter */} + {/* Filter pills */}
- {(["all", "feed", "sleep", "diaper"] as const).map((f) => ( + {(["all", "feed", "sleep", "diaper"] as const).map(f => (
- {/* Guidelines Card */} + {/* Guidelines card */} {child && showSuggested && (
{(() => { - const guide = getGuideline(child.birthDate); + const guide = getGuideline(child.birthDate); const ageMonths = getAgeInMonths(child.birthDate); return (
@@ -161,7 +329,7 @@ export default function ActivityPage() {
{child.name} Β· {ageMonths} months old
- +
@@ -186,59 +354,50 @@ export default function ActivityPage() { {/* Content */}
{loading ? ( -
Loading...
- ) : view === "timeline" ? ( - /* Timeline View */ - groupedByDay.length === 0 ? ( -
-
πŸ“Š
-

No activity yet

-
- ) : ( -
- {groupedByDay.map((day) => ( -
-
- {new Date(day.date).toLocaleDateString("en-US", { - weekday: "long", - month: "short", - day: "numeric", - })} -
-
- {day.logs - .sort((a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()) - .map((log) => ( -
- {getIcon(log.type)} -
-
{log.type}
-
- {log.subType && `${log.subType} Β· `} - {log.amount && `${log.amount}ml`} - {log.notes && ` Β· ${log.notes}`} -
-
-
- {new Date(log.loggedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} +
Loading…
+ ) : view === "calendar" ? ( + + ) : groupedByDay.length === 0 ? ( +
+
πŸ“Š
+

No activity yet

+
+ ) : ( +
+ {groupedByDay.map(day => ( +
+
+ {new Date(day.date).toLocaleDateString("en-US", { + weekday: "long", month: "short", day: "numeric", + })} +
+
+ {day.logs + .sort((a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()) + .map(log => ( +
+ {getIcon(log.type)} +
+
{log.type}
+
+ {[ + log.subType?.replace(/_/g, " "), + log.amount ? `${log.amount}ml` : null, + log.notes, + ].filter(Boolean).join(" Β· ")}
- ))} -
+
+ {new Date(log.loggedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })} +
+
+ ))}
- ))} -
- ) - ) : ( - /* Calendar View */ -
-
-
πŸ“…
-

Calendar view coming soon

-
+
+ ))}
)}
); -} \ No newline at end of file +}