Add activity scroller to home page

- Add time-based activity scroller showing lastFeed, lastSleep, lastDiaper times
- Scrolling marquee animation below vaccine reminders
- Fix diaper icon to 🧷
- Link baby profile card to growth page
- Add loading state for recent logs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-17 16:15:20 +05:30
parent b10bfb0e2f
commit 9e258d74b4
2 changed files with 65 additions and 11 deletions

View file

@ -19,6 +19,15 @@
--font-mono: var(--font-geist-mono);
}
@keyframes marquee {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
.animate-marquee {
animation: marquee 20s linear infinite;
}
body {
background: var(--background);
color: var(--foreground);

View file

@ -55,7 +55,6 @@ export async function processOfflineQueue() {
localStorage.setItem(OFFLINE_QUEUE_KEY, JSON.stringify(failed));
}
// --- AI Session Functions (using database) ---
async function getSessions(cid: string): Promise<ChatSession[]> {
try {
const res = await fetch(`/api/chat?childId=${cid}`);
@ -134,6 +133,48 @@ function getGreeting() {
return "Good evening";
}
function formatTimeAgo(dateStr: string | null | undefined) {
if (!dateStr) return null;
const date = new Date(dateStr);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
const diffHours = Math.floor(diffMins / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffMins < 1) return "just now";
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
return `${diffDays}d ago`;
}
function ActivityScroller({ lastLogs }: { lastLogs: any[] }) {
const feedLog = lastLogs.find(l => l?.type === "feed");
const sleepLog = lastLogs.find(l => l?.type === "sleep");
const diaperLog = lastLogs.find(l => l?.type === "diaper");
const items = [
{ icon: "🍼", label: "Fed", time: feedLog?.logged_at },
{ icon: "😴", label: "Sleep", time: sleepLog?.logged_at },
{ icon: "🧷", label: "Diaper", time: diaperLog?.logged_at },
].filter(item => item.time);
if (items.length === 0) return null;
return (
<div className="mx-4 mb-4 overflow-hidden">
<div className="flex animate-marquee gap-4">
{[...items, ...items].map((item, i) => (
<div key={i} className="flex-shrink-0 flex items-center gap-2 px-3 py-2 bg-white dark:bg-gray-800 rounded-full shadow-sm">
<span className="text-lg">{item.icon}</span>
<span className="text-sm dark:text-gray-300">{item.label}:</span>
<span className="text-sm font-medium text-rose-500">{formatTimeAgo(item.time)}</span>
</div>
))}
</div>
</div>
);
}
const QUICK_QUESTIONS = ["How much should my baby eat?", "When should baby sleep?", "Is fever normal?", "How to increase milk supply?", "Baby won't sleep", "Starting solids?"];
export default function HomePage() {
@ -145,6 +186,7 @@ export default function HomePage() {
const [homeSessionId, setHomeSessionId] = useState<string | null>(null);
const [pendingCount, setPendingCount] = useState(0);
const [lastLogs, setLastLogs] = useState<any[]>([]);
const [logsLoading, setLogsLoading] = useState(true);
const [vaccineReminders, setVaccineReminders] = useState<any[]>([]);
const { theme, toggle: toggleTheme } = useTheme();
const { childId, child, familyId, loading } = useFamily();
@ -173,18 +215,21 @@ export default function HomePage() {
useEffect(() => {
if (!childId) return;
setLogsLoading(true);
Promise.all([
fetch(`/api/logs?type=feed&childId=${childId}&limit=1`).then(r => r.json()),
fetch(`/api/logs?type=sleep&childId=${childId}&limit=1`).then(r => r.json()),
fetch(`/api/logs?type=diaper&childId=${childId}&limit=1`).then(r => r.json()),
]).then(([feed, sleep, diaper]) => setLastLogs([feed.entries?.[0], sleep.entries?.[0], diaper.entries?.[0]].filter(Boolean)));
]).then(([feed, sleep, diaper]) => {
setLastLogs([feed.entries?.[0], sleep.entries?.[0], diaper.entries?.[0]].filter(Boolean));
setLogsLoading(false);
}).catch(() => setLogsLoading(false));
}, [childId]);
if (loading) {
return <div className="min-h-screen flex items-center justify-center">Loading...</div>;
}
// Not logged in - redirect to login
if (!familyId) {
if (typeof window !== "undefined") {
window.location.href = "/login";
@ -200,7 +245,6 @@ export default function HomePage() {
toggleTheme();
};
// Unified AI chat — creates a fresh session per modal open, reuses it for follow-ups
const handleAiChat = async (question?: string) => {
const q = question || aiInput;
if (!q.trim() || aiLoading) return;
@ -210,7 +254,6 @@ export default function HomePage() {
const inputVal = q.trim();
if (!question) setAiInput("");
// Reuse session within same modal open, or create fresh one
let sessionId = homeSessionId;
if (!sessionId) {
const newSession = await createSession(childId);
@ -220,7 +263,6 @@ export default function HomePage() {
setAiChats([]);
}
// Optimistically show user message
const userMsg: AIChat = { id: "tmp-" + Date.now(), role: "user", content: inputVal, createdAt: new Date().toISOString() };
setAiChats(prev => [...prev, userMsg]);
@ -264,12 +306,13 @@ export default function HomePage() {
<p className="text-gray-600 dark:text-gray-300">How is {child?.name || "your baby"} doing today?</p>
</div>
<div className="mx-4 mb-4 p-4 bg-white dark:bg-gray-800 rounded-2xl shadow-md">
<Link href="/growth" className="mx-4 mb-4 p-4 bg-white dark:bg-gray-800 rounded-2xl shadow-md block">
<div className="flex items-center gap-4">
<div className="w-16 h-16 bg-rose-100 dark:bg-rose-900 rounded-full flex items-center justify-center text-2xl">👶</div>
<div><div className="text-lg font-semibold">{child?.name || "Baby"}</div><div className="text-gray-500 dark:text-gray-400">{calculateAge(child?.birthDate || "")}</div></div>
<div className="flex-1"><div className="text-lg font-semibold">{child?.name || "Baby"}</div><div className="text-gray-500 dark:text-gray-400">{calculateAge(child?.birthDate || "")}</div></div>
<div className="text-2xl"></div>
</div>
</div>
</Link>
{pendingCount > 0 && <div className="mx-4 mb-4 bg-amber-100 text-amber-800 px-4 py-2 rounded-xl text-center">{pendingCount} pending log{pendingCount > 1 ? "s" : ""}</div>}
@ -289,12 +332,14 @@ export default function HomePage() {
</div>
)}
<ActivityScroller lastLogs={lastLogs} />
<div className="px-4 mb-4">
<h2 className="font-semibold mb-3 ml-1">Quick Log</h2>
<div className="grid grid-cols-4 gap-2">
<button onClick={() => setModalType("feed")} className="flex flex-col items-center p-4 bg-white dark:bg-gray-800 rounded-xl shadow-sm"><span className="text-3xl">🍼</span><span className="text-sm mt-1">Feed</span></button>
<button onClick={() => setModalType("sleep")} className="flex flex-col items-center p-4 bg-white dark:bg-gray-800 rounded-xl shadow-sm"><span className="text-3xl">😴</span><span className="text-sm mt-1">Sleep</span></button>
<button onClick={() => setModalType("diaper")} className="flex flex-col items-center p-4 bg-white dark:bg-gray-800 rounded-xl shadow-sm"><span className="text-3xl">👶</span><span className="text-sm mt-1">Diaper</span></button>
<button onClick={() => setModalType("diaper")} className="flex flex-col items-center p-4 bg-white dark:bg-gray-800 rounded-xl shadow-sm"><span className="text-3xl">🧷</span><span className="text-sm mt-1">Diaper</span></button>
<Link href="/medical" className="flex flex-col items-center p-4 bg-white dark:bg-gray-800 rounded-xl shadow-sm"><span className="text-3xl">💊</span><span className="text-sm mt-1">Medical</span></Link>
</div>
</div>
@ -318,7 +363,7 @@ export default function HomePage() {
<div className="px-4 mt-4">
<h2 className="font-semibold mb-3 ml-1">Recent Activity</h2>
<div className="space-y-2">
{lastLogs.length === 0 ? <p className="text-gray-400 text-sm">No logs yet today</p> : lastLogs.filter(Boolean).map((log: any, i: number) => (
{logsLoading ? <p className="text-gray-400 text-sm">Loading...</p> : lastLogs.length === 0 ? <p className="text-gray-400 text-sm">No logs yet today</p> : lastLogs.filter(Boolean).map((log: any, i: number) => (
<div key={i} className="flex items-center justify-between p-3 bg-white dark:bg-gray-800 rounded-xl">
<div className="flex items-center gap-3">
<span className="text-xl">{log.type === "feed" && "🍼"}{log.type === "sleep" && "😴"}{log.type === "diaper" && "👶"}</span>