tia/src/components/medical/VisitTab.tsx
Mannu 96d28cbadc refactor: full codebase sweep — shared types, utilities, component splits
New foundations:
- src/types/index.ts — shared domain types (Child, Log, GrowthRecord, Medicine,
  Dose, Allergy, Visit, Illness, Vaccination, AIChat, ChatSession, Goal)
- src/lib/formatting.ts — calculateAge, formatAge, formatTimeAgo (eliminates
  3 duplicate implementations spread across page.tsx and growth/page.tsx)
- src/lib/api.ts — typed fetch helpers (api.get/post/patch/delete) with
  consistent error handling; replaces manual fetch boilerplate

New shared components:
- src/components/PageHeader.tsx — reusable back-link + title header
- src/components/TabBar.tsx — horizontal pill tab bar
- src/components/CalendarView.tsx — extracted from activity/page.tsx (was ~170 inline lines)
- src/components/medical/ — medical page split into 5 focused tab components:
  VaccineTab, MedicineTab, AllergyTab, VisitTab, IllnessTab

Pages updated:
- medical/page.tsx: 1029 → 42 lines (thin shell wiring the 5 tab components)
- activity/page.tsx: uses CalendarView + shared Log type + api.ts
- growth/page.tsx: uses shared GrowthRecord/Goal types + formatAge; fixes
  `any` catch clauses; fixes undefined → null in Chart.js dataset values
- page.tsx (home): uses shared Log/AIChat/ChatSession types + formatTimeAgo/
  calculateAge from formatting.ts; removes inline type definitions
- ai/page.tsx: uses shared AIChat/ChatSession types
- FamilyProvider.tsx: uses shared Child type; fixes `c: any` mapping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 21:37:39 +05:30

113 lines
4 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import { Button, Input } from "@/components/ui";
import { api } from "@/lib/api";
import type { Visit } from "@/types";
interface Props { childId: string }
export function VisitTab({ childId }: Props) {
const [visits, setVisits] = useState<Visit[]>([]);
const [editingVisit, setEditingVisit] = useState<Visit | null>(null);
const [showAdd, setShowAdd] = useState(false);
const [doctor, setDoctor] = useState("");
const [reason, setReason] = useState("");
const [date, setDate] = useState("");
const [notes, setNotes] = useState("");
useEffect(() => { fetchVisits(); }, [childId]);
const fetchVisits = () =>
api.get<{ visits: Visit[] }>(`/api/visits?childId=${childId}`)
.then(d => setVisits(d.visits || []))
.catch(console.error);
const save = async () => {
if (!doctor) return;
try {
if (editingVisit) {
await api.patch("/api/visits", { id: editingVisit.id, doctorName: doctor, reason, date, notes });
} else {
await api.post("/api/visits", { childId, doctorName: doctor, reason, date, notes });
}
fetchVisits();
} catch (err) { console.error("Failed to save visit:", err); }
reset();
};
const remove = async (id: string) => {
try {
await api.delete(`/api/visits?id=${id}`);
fetchVisits();
} catch (err) { console.error("Failed to delete visit:", err); }
};
const edit = (v: Visit) => {
setEditingVisit(v);
setDoctor(v.doctorName);
setReason(v.reason);
setDate(v.date);
setNotes(v.notes);
setShowAdd(true);
};
const reset = () => {
setEditingVisit(null);
setDoctor("");
setReason("");
setDate("");
setNotes("");
setShowAdd(false);
};
return (
<div className="space-y-2">
<h2 className="font-semibold mb-3">Doctor Visits</h2>
{showAdd && (
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl space-y-3">
<Input placeholder="Doctor name" value={doctor} onChange={e => setDoctor(e.target.value)} />
<Input placeholder="Reason (e.g., Checkup, Fever)" value={reason} onChange={e => setReason(e.target.value)} />
<Input type="date" value={date} onChange={e => setDate(e.target.value)} />
<Input placeholder="Notes" value={notes} onChange={e => setNotes(e.target.value)} />
<div className="flex gap-2">
<Button fullWidth onClick={save}>{editingVisit ? "Update" : "Add"}</Button>
<Button variant="secondary" fullWidth onClick={reset}>Cancel</Button>
</div>
</div>
)}
{visits.length === 0 && !showAdd ? (
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl">
<p className="text-gray-500 dark:text-gray-400">No visits recorded</p>
</div>
) : (
visits.map(v => (
<div key={v.id} className="p-4 bg-white dark:bg-gray-800 rounded-xl">
<div className="flex items-center justify-between">
<div className="flex-1">
<div className="font-medium">{v.doctorName}</div>
<div className="text-sm text-gray-500 dark:text-gray-400">
{new Date(v.date).toLocaleDateString()}
{v.reason && ` · ${v.reason}`}
{v.notes && ` · ${v.notes}`}
</div>
</div>
<div className="flex gap-2">
<button onClick={() => edit(v)} className="p-2 text-gray-400"></button>
<button onClick={() => remove(v.id)} className="p-2 text-red-400">🗑</button>
</div>
</div>
</div>
))
)}
{!showAdd && (
<button onClick={() => setShowAdd(true)} className="w-full p-4 border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-xl text-gray-500 dark:text-gray-400">
+ Add Visit
</button>
)}
</div>
);
}