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 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-10 16:10:54 +05:30
parent 38a773f882
commit 6ffa2dd875

167
src/app/activity/page.tsx Normal file
View file

@ -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<ViewMode>("timeline");
const [logs, setLogs] = useState<Log[]>([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState<LogType | "all">("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 (
<div className="min-h-screen bg-gradient-to-br from-rose-50 to-amber-50 dark:from-gray-900 dark:to-gray-800">
{/* Header */}
<div className="p-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/menu" className="p-2"></Link>
<h1 className="text-xl font-bold">Activity</h1>
</div>
{/* View Toggle */}
<div className="flex bg-white dark:bg-gray-800 rounded-lg p-1">
<button
onClick={() => setView("timeline")}
className={`px-3 py-1 rounded-md text-sm ${view === "timeline" ? "bg-rose-400 text-white" : ""}`}
>
Timeline
</button>
<button
onClick={() => setView("calendar")}
className={`px-3 py-1 rounded-md text-sm ${view === "calendar" ? "bg-rose-400 text-white" : ""}`}
>
Calendar
</button>
</div>
</div>
{/* Filter */}
<div className="px-4 mb-4 flex gap-2 overflow-x-auto">
{(["all", "feed", "sleep", "diaper"] as const).map((f) => (
<button
key={f}
onClick={() => setFilter(f)}
className={`px-4 py-2 rounded-full text-sm whitespace-nowrap ${
filter === f
? "bg-rose-400 text-white"
: "bg-white dark:bg-gray-800"
}`}
>
{f === "all" ? "All" : f.charAt(0).toUpperCase() + f.slice(1)}
</button>
))}
</div>
{/* Content */}
<div className="px-4 pb-20">
{loading ? (
<div className="text-center py-20 text-gray-400">Loading...</div>
) : view === "timeline" ? (
/* Timeline View */
groupedByDay.length === 0 ? (
<div className="text-center py-20 text-gray-400">
<div className="text-6xl mb-4">📊</div>
<p>No activity yet</p>
</div>
) : (
<div className="space-y-6">
{groupedByDay.map((day) => (
<div key={day.date}>
<div className="text-sm font-medium text-gray-500 mb-2">
{new Date(day.date).toLocaleDateString("en-US", {
weekday: "long",
month: "short",
day: "numeric",
})}
</div>
<div className="space-y-2">
{day.logs
.sort((a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime())
.map((log) => (
<div key={log.id} className="flex items-center gap-3 p-3 bg-white dark:bg-gray-800 rounded-xl">
<span className="text-2xl">{getIcon(log.type)}</span>
<div className="flex-1">
<div className="font-medium capitalize">{log.type}</div>
<div className="text-sm text-gray-500">
{log.subType && `${log.subType} · `}
{log.amount && `${log.amount}ml`}
{log.notes && ` · ${log.notes}`}
</div>
</div>
<div className="text-sm text-gray-400">
{new Date(log.loggedAt).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
</div>
</div>
))}
</div>
</div>
))}
</div>
)
) : (
/* Calendar View */
<div className="bg-white dark:bg-gray-800 rounded-xl p-4">
<div className="text-center text-gray-400 py-20">
<div className="text-6xl mb-4">📅</div>
<p>Calendar view coming soon</p>
</div>
</div>
)}
</div>
</div>
);
}