From 6ffa2dd875bdfb5a32875940553472c42fd297fc Mon Sep 17 00:00:00 2001 From: Mannu Date: Sun, 10 May 2026 16:10:54 +0530 Subject: [PATCH] Add Activity page with timeline view - Timeline view showing logs grouped by day - Filter by log type (feed/sleep/diaper) - Toggle between timeline and calendar views - Calendar view placeholder for future implementation Co-Authored-By: Claude Opus 4.7 --- src/app/activity/page.tsx | 167 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/app/activity/page.tsx diff --git a/src/app/activity/page.tsx b/src/app/activity/page.tsx new file mode 100644 index 0000000..19f0212 --- /dev/null +++ b/src/app/activity/page.tsx @@ -0,0 +1,167 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Link from "next/link"; + +type ViewMode = "timeline" | "calendar"; +type LogType = "feed" | "sleep" | "diaper"; + +interface Log { + id: string; + type: LogType; + subType?: string; + amount?: number; + notes?: string; + loggedAt: string; +} + +interface DayLogs { + date: string; + logs: Log[]; +} + +export default function ActivityPage() { + const [view, setView] = useState("timeline"); + const [logs, setLogs] = useState([]); + const [loading, setLoading] = useState(true); + const [filter, setFilter] = useState("all"); + const childId = "default"; + + useEffect(() => { + fetchLogs(); + }, []); + + const fetchLogs = async () => { + try { + const res = await fetch(`/api/logs?childId=${childId}&limit=100`); + const data = await res.json(); + setLogs(data.entries || []); + } catch (err) { + console.error("Failed to fetch:", err); + } + setLoading(false); + }; + + 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] }); + } + return acc; + }, []); + + const getIcon = (type: LogType) => { + switch (type) { + case "feed": return "๐Ÿผ"; + case "sleep": return "๐Ÿ˜ด"; + case "diaper": return "๐Ÿ‘ถ"; + default: return "๐Ÿ“"; + } + }; + + return ( +
+ {/* Header */} +
+
+ โ† +

Activity

+
+ {/* View Toggle */} +
+ + +
+
+ + {/* Filter */} +
+ {(["all", "feed", "sleep", "diaper"] as const).map((f) => ( + + ))} +
+ + {/* 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" })} +
+
+ ))} +
+
+ ))} +
+ ) + ) : ( + /* Calendar View */ +
+
+
๐Ÿ“…
+

Calendar view coming soon

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