feat(home): full age breakdown and dark mode fixes

Age now shows years, months and days (e.g. "1 year, 3 months, 5 days").
Added dark: variants to all white cards, inputs, selects, and text on the
home page so dark mode no longer shows white boxes on a dark background.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-17 13:11:03 +05:30
parent 75afc177ce
commit bc2eb9a50f

View file

@ -96,27 +96,35 @@ function LogModal({ type, childId, onClose }: { type: "feed" | "diaper" | "sleep
return ( return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white rounded-2xl p-6 w-full max-w-sm mx-4"> <div className="bg-white dark:bg-gray-800 rounded-2xl p-6 w-full max-w-sm mx-4">
<h2 className="text-xl font-bold mb-4">{type === "feed" && "Log Feed"}{type === "diaper" && "Log Diaper"}{type === "sleep" && "Log Sleep"}</h2> <h2 className="text-xl font-bold mb-4">{type === "feed" && "Log Feed"}{type === "diaper" && "Log Diaper"}{type === "sleep" && "Log Sleep"}</h2>
{type === "feed" && (<><select value={subType} onChange={e => setSubType(e.target.value)} className="w-full p-3 border rounded-xl mb-3"><option value="breast_milk">Breast Milk</option><option value="formula">Formula</option><option value="solid">Solid Food</option><option value="water">Water</option></select><input type="number" placeholder="Amount (ml)" value={amountMl} onChange={e => setAmountMl(e.target.value)} className="w-full p-3 border rounded-xl mb-3" /></>)} {type === "feed" && (<><select value={subType} onChange={e => setSubType(e.target.value)} className="w-full p-3 border dark:border-gray-600 rounded-xl mb-3 bg-white dark:bg-gray-700 dark:text-white"><option value="breast_milk">Breast Milk</option><option value="formula">Formula</option><option value="solid">Solid Food</option><option value="water">Water</option></select><input type="number" placeholder="Amount (ml)" value={amountMl} onChange={e => setAmountMl(e.target.value)} className="w-full p-3 border dark:border-gray-600 rounded-xl mb-3 bg-white dark:bg-gray-700 dark:text-white dark:placeholder-gray-400" /></>)}
{type === "diaper" && (<select value={subType} onChange={e => setSubType(e.target.value)} className="w-full p-3 border rounded-xl mb-3"><option value="wet">Wet</option><option value="dirty">Dirty</option><option value="both">Both</option></select>)} {type === "diaper" && (<select value={subType} onChange={e => setSubType(e.target.value)} className="w-full p-3 border dark:border-gray-600 rounded-xl mb-3 bg-white dark:bg-gray-700 dark:text-white"><option value="wet">Wet</option><option value="dirty">Dirty</option><option value="both">Both</option></select>)}
{type === "sleep" && (<select value={subType} onChange={e => setSubType(e.target.value)} className="w-full p-3 border rounded-xl mb-3"><option value="nap">Nap</option><option value="night">Night Sleep</option></select>)} {type === "sleep" && (<select value={subType} onChange={e => setSubType(e.target.value)} className="w-full p-3 border dark:border-gray-600 rounded-xl mb-3 bg-white dark:bg-gray-700 dark:text-white"><option value="nap">Nap</option><option value="night">Night Sleep</option></select>)}
<input type="text" placeholder="Notes (optional)" value={notes} onChange={e => setNotes(e.target.value)} className="w-full p-3 border rounded-xl mb-4" /> <input type="text" placeholder="Notes (optional)" value={notes} onChange={e => setNotes(e.target.value)} className="w-full p-3 border dark:border-gray-600 rounded-xl mb-4 bg-white dark:bg-gray-700 dark:text-white dark:placeholder-gray-400" />
<div className="flex gap-3"><button onClick={onClose} className="flex-1 p-3 border rounded-xl">Cancel</button><button onClick={handleSubmit} disabled={loading} className="flex-1 p-3 bg-rose-400 text-white rounded-xl">{loading ? "Saving..." : "Save"}</button></div> <div className="flex gap-3"><button onClick={onClose} className="flex-1 p-3 border dark:border-gray-600 rounded-xl dark:text-white">Cancel</button><button onClick={handleSubmit} disabled={loading} className="flex-1 p-3 bg-rose-400 text-white rounded-xl">{loading ? "Saving..." : "Save"}</button></div>
</div> </div>
</div> </div>
); );
} }
function calculateAge(birthDate: string) { function calculateAge(birthDate: string) {
if (!birthDate) return "";
const birth = new Date(birthDate); const birth = new Date(birthDate);
const now = new Date(); const now = new Date();
const days = Math.floor((now.getTime() - birth.getTime()) / (1000 * 60 * 60 * 24)); let years = now.getFullYear() - birth.getFullYear();
const months = Math.floor(days / 30); let months = now.getMonth() - birth.getMonth();
const years = Math.floor(months / 12); let days = now.getDate() - birth.getDate();
if (years > 0) return `${years} year${years > 1 ? "s" : ""} old`; if (days < 0) {
if (months > 0) return `${months} month${months > 1 ? "s" : ""} old`; months--;
return `${days} day${days > 1 ? "s" : ""} old`; days += new Date(now.getFullYear(), now.getMonth(), 0).getDate();
}
if (months < 0) { years--; months += 12; }
const parts = [];
if (years > 0) parts.push(`${years} year${years > 1 ? "s" : ""}`);
if (months > 0) parts.push(`${months} month${months > 1 ? "s" : ""}`);
if (days > 0) parts.push(`${days} day${days > 1 ? "s" : ""}`);
return parts.length > 0 ? parts.join(", ") : "Newborn";
} }
function getGreeting() { function getGreeting() {
@ -256,13 +264,13 @@ export default function HomePage() {
<div className="px-6 pb-4"> <div className="px-6 pb-4">
<h1 className="text-2xl font-bold">{getGreeting()} 👋</h1> <h1 className="text-2xl font-bold">{getGreeting()} 👋</h1>
<p className="text-gray-600">How is {child?.name || "your baby"} doing today?</p> <p className="text-gray-600 dark:text-gray-300">How is {child?.name || "your baby"} doing today?</p>
</div> </div>
<div className="mx-4 mb-4 p-4 bg-white rounded-2xl shadow-md"> <div className="mx-4 mb-4 p-4 bg-white dark:bg-gray-800 rounded-2xl shadow-md">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-16 h-16 bg-rose-100 rounded-full flex items-center justify-center text-2xl">👶</div> <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">{calculateAge(child?.birthDate || "")}</div></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> </div>
</div> </div>
@ -287,10 +295,10 @@ export default function HomePage() {
<div className="px-4 mb-4"> <div className="px-4 mb-4">
<h2 className="font-semibold mb-3 ml-1">Quick Log</h2> <h2 className="font-semibold mb-3 ml-1">Quick Log</h2>
<div className="grid grid-cols-4 gap-2"> <div className="grid grid-cols-4 gap-2">
<button onClick={() => setModalType("feed")} className="flex flex-col items-center p-4 bg-white rounded-xl shadow-sm"><span className="text-3xl">🍼</span><span className="text-sm mt-1">Feed</span></button> <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 rounded-xl shadow-sm"><span className="text-3xl">😴</span><span className="text-sm mt-1">Sleep</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 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 rounded-xl shadow-sm"><span className="text-3xl">💊</span><span className="text-sm mt-1">Medical</span></Link> <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>
</div> </div>
@ -299,13 +307,13 @@ export default function HomePage() {
<h2 className="font-semibold ml-1">Ask AI</h2> <h2 className="font-semibold ml-1">Ask AI</h2>
<Link href="/ai" className="text-rose-500 text-sm">View all chats </Link> <Link href="/ai" className="text-rose-500 text-sm">View all chats </Link>
</div> </div>
<div className="p-4 bg-white rounded-2xl shadow-md"> <div className="p-4 bg-white dark:bg-gray-800 rounded-2xl shadow-md">
<div className="flex gap-2 mb-3"> <div className="flex gap-2 mb-3">
<input type="text" value={aiInput} onChange={e => setAiInput(e.target.value)} onKeyDown={e => e.key === "Enter" && handleAiChat()} placeholder="Ask anything..." className="flex-1 p-2 border rounded-xl text-sm" disabled={aiLoading} /> <input type="text" value={aiInput} onChange={e => setAiInput(e.target.value)} onKeyDown={e => e.key === "Enter" && handleAiChat()} placeholder="Ask anything..." className="flex-1 p-2 border dark:border-gray-600 rounded-xl text-sm bg-white dark:bg-gray-700 dark:text-white dark:placeholder-gray-400" disabled={aiLoading} />
<button onClick={() => handleAiChat()} disabled={aiLoading || !aiInput.trim()} className="px-4 bg-rose-400 text-white rounded-xl text-sm">{aiLoading ? "..." : "Ask"}</button> <button onClick={() => handleAiChat()} disabled={aiLoading || !aiInput.trim()} className="px-4 bg-rose-400 text-white rounded-xl text-sm">{aiLoading ? "..." : "Ask"}</button>
</div> </div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{QUICK_QUESTIONS.map((q, i) => <button key={i} onClick={() => handleAiChat(q)} className="px-3 py-1 bg-rose-50 text-rose-600 rounded-full text-sm">{q}</button>)} {QUICK_QUESTIONS.map((q, i) => <button key={i} onClick={() => handleAiChat(q)} className="px-3 py-1 bg-rose-50 dark:bg-gray-700 text-rose-600 dark:text-rose-400 rounded-full text-sm">{q}</button>)}
</div> </div>
</div> </div>
</div> </div>
@ -314,12 +322,12 @@ export default function HomePage() {
<h2 className="font-semibold mb-3 ml-1">Recent Activity</h2> <h2 className="font-semibold mb-3 ml-1">Recent Activity</h2>
<div className="space-y-2"> <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) => ( {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 rounded-xl"> <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"> <div className="flex items-center gap-3">
<span className="text-xl">{log.type === "feed" && "🍼"}{log.type === "sleep" && "😴"}{log.type === "diaper" && "👶"}</span> <span className="text-xl">{log.type === "feed" && "🍼"}{log.type === "sleep" && "😴"}{log.type === "diaper" && "👶"}</span>
<div><div className="font-medium capitalize">{log.type}</div><div className="text-xs text-gray-500">{new Date(log.logged_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</div></div> <div><div className="font-medium capitalize">{log.type}</div><div className="text-xs text-gray-500 dark:text-gray-400">{new Date(log.logged_at).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}</div></div>
</div> </div>
{log.amount_ml && <span className="text-sm text-gray-500">{log.amount_ml}ml</span>} {log.amount_ml && <span className="text-sm text-gray-500 dark:text-gray-400">{log.amount_ml}ml</span>}
</div> </div>
))} ))}
</div> </div>
@ -336,7 +344,7 @@ export default function HomePage() {
{aiLoading && <div className="bg-rose-50 dark:bg-gray-700 p-3 rounded-xl text-sm animate-pulse">Thinking...</div>} {aiLoading && <div className="bg-rose-50 dark:bg-gray-700 p-3 rounded-xl text-sm animate-pulse">Thinking...</div>}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<input type="text" value={aiInput} onChange={e => setAiInput(e.target.value)} onKeyDown={e => e.key === "Enter" && handleAiChat()} placeholder="Ask a question..." className="flex-1 p-2 border rounded-xl text-sm" disabled={aiLoading} /> <input type="text" value={aiInput} onChange={e => setAiInput(e.target.value)} onKeyDown={e => e.key === "Enter" && handleAiChat()} placeholder="Ask a question..." className="flex-1 p-2 border dark:border-gray-600 rounded-xl text-sm dark:bg-gray-700 dark:text-white dark:placeholder-gray-400" disabled={aiLoading} />
<button onClick={() => handleAiChat()} disabled={aiLoading || !aiInput.trim()} className="px-3 bg-rose-400 text-white rounded-xl"></button> <button onClick={() => handleAiChat()} disabled={aiLoading || !aiInput.trim()} className="px-3 bg-rose-400 text-white rounded-xl"></button>
</div> </div>
</div> </div>