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:
parent
9c0afa2054
commit
acd2adde7e
1 changed files with 88 additions and 63 deletions
|
|
@ -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,28 +377,55 @@ 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 })}
|
/>
|
||||||
/>
|
<Input label="Target Height (cm)" type="number" step="0.1" placeholder="e.g., 80"
|
||||||
<Input label="Target Height (cm)" type="number" step="0.1" placeholder="e.g., 80"
|
value={goal.heightCm || ""}
|
||||||
value={goal.heightCm || ""}
|
onChange={e => setGoal({ ...goal, heightCm: parseFloat(e.target.value) || undefined })}
|
||||||
onChange={e => setGoal({ ...goal, heightCm: parseFloat(e.target.value) || undefined })}
|
/>
|
||||||
/>
|
<div className="flex gap-2">
|
||||||
<div className="flex gap-2">
|
<Button fullWidth onClick={saveGoal}>Save Goal</Button>
|
||||||
<Button fullWidth onClick={saveGoal}>Save Goal</Button>
|
<Button variant="secondary" fullWidth onClick={() => setShowGoals(false)}>Cancel</Button>
|
||||||
<Button variant="secondary" fullWidth onClick={() => setShowGoals(false)}>Cancel</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 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}'s Growth</h3>
|
||||||
<h3 className="text-lg font-semibold mb-2">Track {child?.name}'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); setShowGoals(false); window.scrollTo({ top: 0, behavior: "smooth" }); }}>
|
||||||
<Button onClick={() => setShowAdd(true)}>Add First Measurement</Button>
|
Add First Measurement
|
||||||
</div>
|
</Button>
|
||||||
</Card>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
/* 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"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue