fix(growth): fix add button and form submission

- Fix + Add button to properly open form (was closing immediately)
- Add error handling and saving state to form
- Add visual feedback (Saving... text, error messages)
- Clear form state properly when opening

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-17 13:58:04 +05:30
parent 318b277e44
commit abbaaf8874

View file

@ -64,6 +64,8 @@ export default function GrowthPage() {
// Chart state // Chart state
const [chartMetric, setChartMetric] = useState<"weight" | "height" | "head">("weight"); const [chartMetric, setChartMetric] = useState<"weight" | "height" | "head">("weight");
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
// Goals state // Goals state
const [goal, setGoal] = useState<Goal>({}); const [goal, setGoal] = useState<Goal>({});
@ -101,8 +103,14 @@ export default function GrowthPage() {
}; };
const handleAdd = async () => { const handleAdd = async () => {
if (!childId || (!weight && !height && !headCircumference)) return; if (!childId || (!weight && !height && !headCircumference)) {
await fetch("/api/growth", { setSaveError("Please enter at least one measurement");
return;
}
setSaving(true);
setSaveError(null);
try {
const res = await fetch("/api/growth", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
@ -113,12 +121,25 @@ export default function GrowthPage() {
headCircumferenceCm: headCircumference ? parseFloat(headCircumference) : null, headCircumferenceCm: headCircumference ? parseFloat(headCircumference) : null,
}), }),
}); });
if (!res.ok) {
const err = await res.json();
setSaveError(err.error || "Failed to save");
return;
}
resetForm(); resetForm();
fetchGrowthData(); fetchGrowthData();
} catch (e: any) {
setSaveError(e.message || "Failed to save");
} finally {
setSaving(false);
}
}; };
const handleEdit = async (id: string) => { const handleEdit = async (id: string) => {
await fetch("/api/growth", { setSaving(true);
setSaveError(null);
try {
const res = await fetch("/api/growth", {
method: "PUT", method: "PUT",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
@ -129,9 +150,19 @@ export default function GrowthPage() {
headCircumferenceCm: headCircumference ? parseFloat(headCircumference) : null, headCircumferenceCm: headCircumference ? parseFloat(headCircumference) : null,
}), }),
}); });
if (!res.ok) {
const err = await res.json();
setSaveError(err.error || "Failed to update");
return;
}
setEditingId(null); setEditingId(null);
resetForm(); resetForm();
fetchGrowthData(); fetchGrowthData();
} catch (e: any) {
setSaveError(e.message || "Failed to update");
} finally {
setSaving(false);
}
}; };
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
@ -330,7 +361,7 @@ export default function GrowthPage() {
<button onClick={() => setShowGoals(!showGoals)} className="p-2 text-sm bg-gray-200 dark:bg-gray-700 rounded-lg" title="Set goals"> <button onClick={() => setShowGoals(!showGoals)} className="p-2 text-sm bg-gray-200 dark:bg-gray-700 rounded-lg" title="Set goals">
🎯 🎯
</button> </button>
<button onClick={() => { setShowAdd(!showAdd); setEditingId(null); resetForm(); }} className="p-2 bg-rose-400 text-white rounded-lg"> <button onClick={() => { setShowAdd(!showAdd); setEditingId(null); setWeight(""); setHeight(""); setHeadCircumference(""); setMeasuredAt(new Date().toISOString().split("T")[0]); }} className="p-2 bg-rose-400 text-white rounded-lg">
+ Add + Add
</button> </button>
</div> </div>
@ -547,6 +578,9 @@ export default function GrowthPage() {
{showAdd && ( {showAdd && (
<div className="mx-4 mb-4 p-4 bg-white dark:bg-gray-800 rounded-xl space-y-3"> <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> <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 <input
type="date" type="date"
value={measuredAt} value={measuredAt}
@ -580,16 +614,16 @@ export default function GrowthPage() {
<div className="flex gap-2"> <div className="flex gap-2">
{editingId ? ( {editingId ? (
<> <>
<button onClick={() => handleEdit(editingId)} className="flex-1 p-3 bg-rose-400 text-white rounded-xl"> <button onClick={() => handleEdit(editingId)} disabled={saving} className="flex-1 p-3 bg-rose-400 text-white rounded-xl disabled:opacity-50">
Update {saving ? "Saving..." : "Update"}
</button> </button>
<button onClick={resetForm} className="flex-1 p-3 bg-gray-200 dark:bg-gray-700 rounded-xl"> <button onClick={resetForm} className="flex-1 p-3 bg-gray-200 dark:bg-gray-700 rounded-xl">
Cancel Cancel
</button> </button>
</> </>
) : ( ) : (
<button onClick={handleAdd} className="w-full p-3 bg-rose-400 text-white rounded-xl"> <button onClick={() => handleAdd()} disabled={saving} className="w-full p-3 bg-rose-400 text-white rounded-xl disabled:opacity-50">
Save {saving ? "Saving..." : "Save"}
</button> </button>
)} )}
</div> </div>