style(growth): collapsible WHO+Chart card, form after latest
- Merge WHO Standards and Chart into single collapsible card - Add +Add button shows form below Latest Reading - Collapsible sections with expand/collapse toggle Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a76738689b
commit
38e18c12f3
1 changed files with 107 additions and 96 deletions
|
|
@ -66,6 +66,9 @@ export default function GrowthPage() {
|
|||
const [chartMetric, setChartMetric] = useState<"weight" | "height" | "head">("weight");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saveError, setSaveError] = useState<string | null>(null);
|
||||
const [showWhoStandards, setShowWhoStandards] = useState(true);
|
||||
const [showChart, setShowChart] = useState(true);
|
||||
const [showHistory, setShowHistory] = useState(true);
|
||||
|
||||
// Goals state
|
||||
const [goal, setGoal] = useState<Goal>({});
|
||||
|
|
@ -468,115 +471,123 @@ export default function GrowthPage() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{/* WHO Standards Card - Enhanced with age-wise targets */}
|
||||
{/* WHO Standards + Chart Card - Collapsible */}
|
||||
{child && standard && (
|
||||
<div className="mx-4 mb-4 p-4 bg-white dark:bg-gray-800 rounded-xl hover:shadow-lg transition-shadow">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<div>
|
||||
<div className="font-semibold text-lg">{child.name}</div>
|
||||
<div className="mx-4 mb-4 bg-white dark:bg-gray-800 rounded-xl overflow-hidden">
|
||||
{/* WHO Standards Header - Clickable */}
|
||||
<button
|
||||
onClick={() => setShowWhoStandards(!showWhoStandards)}
|
||||
className="w-full p-4 flex justify-between items-center hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="text-left">
|
||||
<div className="font-semibold text-lg">{child.name} - WHO Standards</div>
|
||||
<div className="text-sm text-gray-500">{ageMonths} months old</div>
|
||||
</div>
|
||||
{savedGoals.weightKg && (
|
||||
<div className="text-right text-sm">
|
||||
<div className="text-gray-500">Goal</div>
|
||||
<div className="font-medium text-rose-500">{savedGoals.weightKg}kg</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Color-coded percentile zones legend */}
|
||||
<div className="flex gap-3 mb-3 text-xs">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-green-500"></span>
|
||||
Normal
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-amber-500"></span>
|
||||
Watch
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-red-500"></span>
|
||||
Alert
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className={`p-3 rounded-lg hover:shadow-md transition-shadow ${latest?.weight_kg && latest.weight_kg < standard.weight.p3 ? "bg-red-100 dark:bg-red-900" : latest?.weight_kg && latest.weight_kg > standard.weight.p85 ? "bg-amber-100 dark:bg-amber-900" : "bg-green-50 dark:bg-green-900"}`}>
|
||||
<div className="text-gray-500 mb-1 text-xs">Weight</div>
|
||||
<div className="font-medium text-lg">{standard.weight.p50} kg</div>
|
||||
<div className="text-xs text-gray-400">{standard.weight.p3}-{standard.weight.p97}</div>
|
||||
{latest?.weight_kg && (
|
||||
<div className={`text-xs font-medium mt-1 ${getPercentileColor(weightPercentile)}`}>
|
||||
{latest.weight_kg}kg ({weightPercentile})
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{savedGoals.weightKg && (
|
||||
<div className="text-sm text-rose-500 mr-2">Goal: {savedGoals.weightKg}kg</div>
|
||||
)}
|
||||
<span className={`transform transition-transform ${showWhoStandards ? "rotate-180" : ""}`}>▼</span>
|
||||
</div>
|
||||
<div className={`p-3 rounded-lg hover:shadow-md transition-shadow ${latest?.height_cm && latest.height_cm < standard.height.p3 ? "bg-red-100 dark:bg-red-900" : latest?.height_cm && latest.height_cm > standard.height.p85 ? "bg-amber-100 dark:bg-amber-900" : "bg-green-50 dark:bg-green-900"}`}>
|
||||
<div className="text-gray-500 mb-1 text-xs">Height</div>
|
||||
<div className="font-medium text-lg">{standard.height.p50} cm</div>
|
||||
<div className="text-xs text-gray-400">{standard.height.p3}-{standard.height.p97}</div>
|
||||
{latest?.height_cm && (
|
||||
<div className={`text-xs font-medium mt-1 ${getPercentileColor(heightPercentile)}`}>
|
||||
{latest.height_cm}cm ({heightPercentile})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={`p-3 rounded-lg hover:shadow-md transition-shadow ${latest?.head_circumference_cm && latest.head_circumference_cm < standard.headCircumference.p3 ? "bg-red-100 dark:bg-red-900" : latest?.head_circumference_cm && latest.head_circumference_cm > standard.headCircumference.p85 ? "bg-amber-100 dark:bg-amber-900" : "bg-green-50 dark:bg-green-900"}`}>
|
||||
<div className="text-gray-500 mb-1 text-xs">Head</div>
|
||||
<div className="font-medium text-lg">{standard.headCircumference.p50} cm</div>
|
||||
<div className="text-xs text-gray-400">{standard.headCircumference.p3}-{standard.headCircumference.p97}</div>
|
||||
{latest?.head_circumference_cm && (
|
||||
<div className={`text-xs font-medium mt-1 ${getPercentileColor(headPercentile)}`}>
|
||||
{latest.head_circumference_cm}cm ({headPercentile})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Growth velocity indicator */}
|
||||
{velocity && (
|
||||
<div className="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span className="text-gray-500">Velocity:</span>
|
||||
<span className={`font-medium ${velocity.direction === "up" ? "text-green-600" : "text-amber-600"}`}>
|
||||
{velocity.weight} kg/month {velocity.direction === "up" ? "↑" : "↓"}
|
||||
{/* WHO Standards Content - Collapsible */}
|
||||
{showWhoStandards && (
|
||||
<div className="px-4 pb-4">
|
||||
{/* Color-coded percentile zones legend */}
|
||||
<div className="flex gap-3 mb-3 text-xs">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-green-500"></span>
|
||||
Normal
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-amber-500"></span>
|
||||
Watch
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-3 h-3 rounded-full bg-red-500"></span>
|
||||
Alert
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* WHO Standards Cards */}
|
||||
<div className="grid grid-cols-3 gap-3 mb-3">
|
||||
<div className={`p-3 rounded-lg hover:shadow-md transition-shadow ${latest?.weight_kg && latest.weight_kg < standard.weight.p3 ? "bg-red-100 dark:bg-red-900" : latest?.weight_kg && latest.weight_kg > standard.weight.p85 ? "bg-amber-100 dark:bg-amber-900" : "bg-green-50 dark:bg-green-900"}`}>
|
||||
<div className="text-gray-500 mb-1 text-xs">Weight</div>
|
||||
<div className="font-medium text-lg">{standard.weight.p50} kg</div>
|
||||
<div className="text-xs text-gray-400">{standard.weight.p3}-{standard.weight.p97}</div>
|
||||
{latest?.weight_kg && (
|
||||
<div className={`text-xs font-medium mt-1 ${getPercentileColor(weightPercentile)}`}>
|
||||
{latest.weight_kg}kg ({weightPercentile})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={`p-3 rounded-lg hover:shadow-md transition-shadow ${latest?.height_cm && latest.height_cm < standard.height.p3 ? "bg-red-100 dark:bg-red-900" : latest?.height_cm && latest.height_cm > standard.height.p85 ? "bg-amber-100 dark:bg-amber-900" : "bg-green-50 dark:bg-green-900"}`}>
|
||||
<div className="text-gray-500 mb-1 text-xs">Height</div>
|
||||
<div className="font-medium text-lg">{standard.height.p50} cm</div>
|
||||
<div className="text-xs text-gray-400">{standard.height.p3}-{standard.height.p97}</div>
|
||||
{latest?.height_cm && (
|
||||
<div className={`text-xs font-medium mt-1 ${getPercentileColor(heightPercentile)}`}>
|
||||
{latest.height_cm}cm ({heightPercentile})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={`p-3 rounded-lg hover:shadow-md transition-shadow ${latest?.head_circumference_cm && latest.head_circumference_cm < standard.headCircumference.p3 ? "bg-red-100 dark:bg-red-900" : latest?.head_circumference_cm && latest.head_circumference_cm > standard.headCircumference.p85 ? "bg-amber-100 dark:bg-amber-900" : "bg-green-50 dark:bg-green-900"}`}>
|
||||
<div className="text-gray-500 mb-1 text-xs">Head</div>
|
||||
<div className="font-medium text-lg">{standard.headCircumference.p50} cm</div>
|
||||
<div className="text-xs text-gray-400">{standard.headCircumference.p3}-{standard.headCircumference.p97}</div>
|
||||
{latest?.head_circumference_cm && (
|
||||
<div className={`text-xs font-medium mt-1 ${getPercentileColor(headPercentile)}`}>
|
||||
{latest.head_circumference_cm}cm ({headPercentile})
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Growth velocity indicator */}
|
||||
{velocity && (
|
||||
<div className="flex items-center gap-2 text-sm mb-3">
|
||||
<span className="text-gray-500">Velocity:</span>
|
||||
<span className={`font-medium ${velocity.direction === "up" ? "text-green-600" : "text-amber-600"}`}>
|
||||
{velocity.weight} kg/month {velocity.direction === "up" ? "↑" : "↓"}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Growth Chart - Inside collapsible card */}
|
||||
{chartData && growthData.length > 0 && (
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 pt-3">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h3 className="font-semibold text-sm">Growth Chart</h3>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => setChartMetric("weight")}
|
||||
className={`px-2 py-1 text-xs rounded ${chartMetric === "weight" ? "bg-rose-400 text-white" : "bg-gray-100 dark:bg-gray-700"}`}
|
||||
>
|
||||
Weight
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setChartMetric("height")}
|
||||
className={`px-2 py-1 text-xs rounded ${chartMetric === "height" ? "bg-rose-400 text-white" : "bg-gray-100 dark:bg-gray-700"}`}
|
||||
>
|
||||
Height
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setChartMetric("head")}
|
||||
className={`px-2 py-1 text-xs rounded ${chartMetric === "head" ? "bg-rose-400 text-white" : "bg-gray-100 dark:bg-gray-700"}`}
|
||||
>
|
||||
Head
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Line data={chartData} options={chartOptions} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Growth Chart */}
|
||||
{chartData && growthData.length > 0 && (
|
||||
<div className="mx-4 mb-4 p-4 bg-white dark:bg-gray-800 rounded-xl">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h2 className="font-semibold">Growth Chart</h2>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => setChartMetric("weight")}
|
||||
className={`px-3 py-1 text-xs rounded ${chartMetric === "weight" ? "bg-rose-400 text-white" : "bg-gray-100 dark:bg-gray-700"}`}
|
||||
>
|
||||
Weight
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setChartMetric("height")}
|
||||
className={`px-3 py-1 text-xs rounded ${chartMetric === "height" ? "bg-rose-400 text-white" : "bg-gray-100 dark:bg-gray-700"}`}
|
||||
>
|
||||
Height
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setChartMetric("head")}
|
||||
className={`px-3 py-1 text-xs rounded ${chartMetric === "head" ? "bg-rose-400 text-white" : "bg-gray-100 dark:bg-gray-700"}`}
|
||||
>
|
||||
Head
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Line data={chartData} options={chartOptions} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Add/Edit Form */}
|
||||
{showAdd && (
|
||||
<div className="mx-4 mb-4 p-4 bg-white dark:bg-gray-800 rounded-xl space-y-3">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue