fix(growth): move add form to top, fix empty-state bug, clean up UI

- Add Measurement form now appears directly below the header (not buried
  after WHO card) so it's immediately visible when + Add is tapped
- Removed `&& latest` guard — form now works even with zero records
- Goals and Add are mutually exclusive: opening one closes the other
- 3-column grid for measurement inputs (weight / height / head on one row)
- Sticky header with backdrop-blur, smaller title (text-sm), icon-style
  export and goals buttons
- All cards use rounded-2xl + shadow-sm for consistent look
- "Add First Measurement" in empty state scrolls to top and opens the form

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-18 22:49:30 +05:30
parent 9c0afa2054
commit acd2adde7e

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import Link from "next/link";
import { useFamily } from "../FamilyProvider"; import { useFamily } from "../FamilyProvider";
import { Button, Card, Input, ConfirmDialog } from "@/components/ui"; import { Button, Card, Input, ConfirmDialog } from "@/components/ui";
import { WHO_BOY_WEIGHT, WHO_GIRL_WEIGHT, getAgeInMonthsFromBirth, getPercentile } from "@/lib/growth-standards"; import { WHO_BOY_WEIGHT, WHO_GIRL_WEIGHT, getAgeInMonthsFromBirth, getPercentile } from "@/lib/growth-standards";
@ -337,15 +338,38 @@ export default function GrowthPage() {
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 pb-20"> <div className="min-h-screen bg-gradient-to-br from-rose-50 to-amber-50 dark:from-gray-900 dark:to-gray-800 pb-20">
<div className="p-4 flex justify-between items-center"> {/* Header */}
<div className="flex items-center gap-4"> <div className="sticky top-0 z-10 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm border-b border-gray-100 dark:border-gray-800 px-4 py-3 flex justify-between items-center">
<a href="/menu" className="p-2"></a> <div className="flex items-center gap-3">
<h1 className="text-xl font-bold">Growth 📈</h1> <Link href="/menu" className="text-gray-500 dark:text-gray-400 p-1"></Link>
<h1 className="text-sm font-semibold dark:text-white">Growth 📈</h1>
</div> </div>
<div className="flex gap-2"> <div className="flex items-center gap-2">
<Button variant="secondary" size="sm" onClick={exportCSV} title="Export CSV">📥</Button> <button
<Button variant="secondary" size="sm" onClick={() => setShowGoals(!showGoals)} title="Set goals">🎯</Button> onClick={exportCSV}
<Button variant="primary" size="sm" onClick={() => { setShowAdd(!showAdd); setEditingId(null); setWeight(""); setHeight(""); setHeadCircumference(""); setMeasuredAt(new Date().toISOString().split("T")[0]); }}> title="Export CSV"
className="p-2 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors text-sm"
>
📥
</button>
<button
title="Set goals"
onClick={() => { setShowGoals(g => !g); setShowAdd(false); setEditingId(null); }}
className={`p-2 rounded-lg text-sm transition-colors ${showGoals ? "bg-amber-100 dark:bg-amber-900 text-amber-600" : "text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"}`}
>
🎯
</button>
<Button
variant="primary"
size="sm"
onClick={() => {
setShowAdd(a => !a);
setShowGoals(false);
setEditingId(null);
setWeight(""); setHeight(""); setHeadCircumference("");
setMeasuredAt(new Date().toISOString().split("T")[0]);
}}
>
+ Add + Add
</Button> </Button>
</div> </div>
@ -353,9 +377,8 @@ export default function GrowthPage() {
{/* Goals Card */} {/* Goals Card */}
{showGoals && ( {showGoals && (
<Card className="mx-4 mb-4"> <div className="mx-4 mt-4 mb-2 p-4 bg-white dark:bg-gray-800 rounded-2xl shadow-sm space-y-3">
<h3 className="font-semibold mb-3">Set Growth Goals</h3> <h3 className="font-semibold text-sm dark:text-white">🎯 Growth Goals</h3>
<div className="space-y-3">
<Input label="Target Weight (kg)" type="number" step="0.1" placeholder="e.g., 10" <Input label="Target Weight (kg)" type="number" step="0.1" placeholder="e.g., 10"
value={goal.weightKg || ""} value={goal.weightKg || ""}
onChange={e => setGoal({ ...goal, weightKg: parseFloat(e.target.value) || undefined })} onChange={e => setGoal({ ...goal, weightKg: parseFloat(e.target.value) || undefined })}
@ -369,12 +392,40 @@ export default function GrowthPage() {
<Button variant="secondary" fullWidth onClick={() => setShowGoals(false)}>Cancel</Button> <Button variant="secondary" fullWidth onClick={() => setShowGoals(false)}>Cancel</Button>
</div> </div>
</div> </div>
</Card> )}
{/* Add / Edit Measurement — always at the top, right below the header */}
{showAdd && (
<div className="mx-4 mt-4 mb-2 p-4 bg-white dark:bg-gray-800 rounded-2xl shadow-sm space-y-3">
<h3 className="font-semibold text-sm dark:text-white">{editingId ? "✏️ Edit Record" : "📏 New Measurement"}</h3>
{saveError && (
<div className="p-2 bg-red-50 dark:bg-red-900/40 text-red-600 dark:text-red-300 rounded-lg text-xs">{saveError}</div>
)}
<Input type="date" value={measuredAt} onChange={e => setMeasuredAt(e.target.value)} />
<div className="grid grid-cols-3 gap-2">
<Input type="number" step="0.01" placeholder="Weight kg" value={weight} onChange={e => setWeight(e.target.value)} />
<Input type="number" step="0.1" placeholder="Height cm" value={height} onChange={e => setHeight(e.target.value)} />
<Input type="number" step="0.1" placeholder="Head cm" value={headCircumference} onChange={e => setHeadCircumference(e.target.value)} />
</div>
<div className="flex gap-2">
{editingId ? (
<>
<Button fullWidth loading={saving} onClick={() => handleEdit(editingId)}>Update</Button>
<Button variant="secondary" fullWidth onClick={resetForm}>Cancel</Button>
</>
) : (
<>
<Button fullWidth loading={saving} onClick={() => handleAdd()}>Save</Button>
<Button variant="secondary" fullWidth onClick={resetForm}>Cancel</Button>
</>
)}
</div>
</div>
)} )}
{/* Latest Reading Card */} {/* Latest Reading Card */}
{latest && ( {latest && (
<div className="mx-4 mb-4 p-4 bg-gradient-to-r from-rose-50 to-pink-50 dark:from-rose-900 dark:to-pink-900 rounded-xl hover:shadow-lg transition-shadow cursor-pointer"> <div className="mx-4 mt-4 mb-2 p-4 bg-gradient-to-r from-rose-50 to-pink-50 dark:from-rose-900/40 dark:to-pink-900/40 rounded-2xl shadow-sm">
<div className="flex justify-between items-center mb-3"> <div className="flex justify-between items-center mb-3">
<div> <div>
<div className="font-semibold text-rose-600 dark:text-rose-300">Latest Reading</div> <div className="font-semibold text-rose-600 dark:text-rose-300">Latest Reading</div>
@ -438,7 +489,7 @@ export default function GrowthPage() {
{/* WHO Standards + Chart Card - Collapsible */} {/* WHO Standards + Chart Card - Collapsible */}
{child && standard && ( {child && standard && (
<div className="mx-4 mb-4 bg-white dark:bg-gray-800 rounded-xl overflow-hidden"> <div className="mx-4 mt-4 mb-2 bg-white dark:bg-gray-800 rounded-2xl shadow-sm overflow-hidden">
{/* WHO Standards Header - Clickable */} {/* WHO Standards Header - Clickable */}
<button <button
onClick={() => setShowWhoStandards(!showWhoStandards)} onClick={() => setShowWhoStandards(!showWhoStandards)}
@ -560,32 +611,6 @@ export default function GrowthPage() {
</div> </div>
)} )}
{/* Add Measurement - Same pattern as Goals */}
{showAdd && latest && (
<div className="mx-4 mb-4 p-4 bg-white dark:bg-gray-800 rounded-xl space-y-3">
<h3 className="font-semibold">{editingId ? "Edit Record" : "Add New Measurement"}</h3>
{saveError && (
<div className="p-2 bg-red-100 dark:bg-red-900 text-red-600 rounded-lg text-sm">{saveError}</div>
)}
<Input type="date" value={measuredAt} onChange={e => setMeasuredAt(e.target.value)} />
<Input type="number" step="0.01" placeholder="Weight (kg)" value={weight} onChange={e => setWeight(e.target.value)} />
<Input type="number" step="0.1" placeholder="Height (cm)" value={height} onChange={e => setHeight(e.target.value)} />
<Input type="number" step="0.1" placeholder="Head circumference (cm)" value={headCircumference} onChange={e => setHeadCircumference(e.target.value)} />
<div className="flex gap-2">
{editingId ? (
<>
<Button fullWidth loading={saving} onClick={() => handleEdit(editingId)}>Update</Button>
<Button variant="secondary" fullWidth onClick={resetForm}>Cancel</Button>
</>
) : (
<>
<Button fullWidth loading={saving} onClick={() => handleAdd()}>Save</Button>
<Button variant="secondary" fullWidth onClick={() => { setShowAdd(false); setWeight(""); setHeight(""); setHeadCircumference(""); }}>Cancel</Button>
</>
)}
</div>
</div>
)}
{/* Empty State */} {/* Empty State */}
{loading ? ( {loading ? (
@ -593,19 +618,19 @@ export default function GrowthPage() {
<p>Loading...</p> <p>Loading...</p>
</div> </div>
) : growthData.length === 0 ? ( ) : growthData.length === 0 ? (
<Card className="mx-4"> <div className="mx-4 mt-4 mb-4 p-6 bg-white dark:bg-gray-800 rounded-2xl shadow-sm text-center">
<div className="text-center py-4"> <div className="text-4xl mb-3">📏</div>
<div className="text-4xl mb-4">📏</div> <h3 className="text-base font-semibold mb-1 dark:text-white">Track {child?.name}&apos;s Growth</h3>
<h3 className="text-lg font-semibold mb-2">Track {child?.name}&apos;s Growth</h3> <p className="text-gray-500 dark:text-gray-400 mb-4 text-sm">
<p className="text-gray-500 mb-4 text-sm"> Log weight, height, and head measurements to see how {child?.name} compares to WHO standards.
Start logging weight, height, and head measurements to see how {child?.name} is growing compared to WHO standards.
</p> </p>
<Button onClick={() => setShowAdd(true)}>Add First Measurement</Button> <Button onClick={() => { setShowAdd(true); setShowGoals(false); window.scrollTo({ top: 0, behavior: "smooth" }); }}>
Add First Measurement
</Button>
</div> </div>
</Card>
) : ( ) : (
/* History List - Collapsible */ /* History List - Collapsible */
<div className="mx-4 mb-4 bg-white dark:bg-gray-800 rounded-xl overflow-hidden"> <div className="mx-4 mt-4 mb-4 bg-white dark:bg-gray-800 rounded-2xl shadow-sm overflow-hidden">
<button <button
onClick={() => setShowHistory(!showHistory)} onClick={() => setShowHistory(!showHistory)}
className="w-full p-4 flex justify-between items-center hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" className="w-full p-4 flex justify-between items-center hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"