Add vaccine tabs: Completed/Upcoming/Overdue with days overdue display

- Calculate schedule from child's DOB
- Add tab navigation for vaccines
- Show days overdue for missed vaccines
- Visual indicators (opacity for completed, red border for overdue)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Manohar Gupta 2026-05-16 16:29:33 +05:30
parent bc08828c18
commit 3e54efaf66

View file

@ -75,6 +75,7 @@ export default function MedicalPage() {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const { childId: sessionChildId, child, loading: loadingChild } = useFamily(); const { childId: sessionChildId, child, loading: loadingChild } = useFamily();
const [tab, setTab] = useState<"vaccinations" | "medicine" | "allergies" | "visits" | "illness">("vaccinations"); const [tab, setTab] = useState<"vaccinations" | "medicine" | "allergies" | "visits" | "illness">("vaccinations");
const [vaccineTab, setVaccineTab] = useState<"upcoming" | "completed" | "overdue">("upcoming");
const [showAddDate, setShowAddDate] = useState<string | null>(null); const [showAddDate, setShowAddDate] = useState<string | null>(null);
const [givenDate, setGivenDate] = useState(""); const [givenDate, setGivenDate] = useState("");
@ -407,6 +408,33 @@ const SUPPLEMENTS = [
const getGivenDate = (name: string) => vaccinations.find((v) => v.vaccine_name === name && v.status === "given")?.given_date; const getGivenDate = (name: string) => vaccinations.find((v) => v.vaccine_name === name && v.status === "given")?.given_date;
const isPending = (name: string) => !isGiven(name); const isPending = (name: string) => !isGiven(name);
// Get vaccines by status
const getVaccinesByStatus = (status: "upcoming" | "completed" | "overdue") => {
const today = new Date();
const todayStr = today.toISOString().split("T")[0];
return IAP_SCHEDULE.filter((v) => {
const dueDate = calculateDueDate(birthDate, v.weeks);
const given = isGiven(v.name);
if (status === "completed") return given;
if (status === "overdue") return !given && dueDate < todayStr;
if (status === "upcoming") return !given && dueDate >= todayStr;
return false;
}).sort((a, b) => a.weeks - b.weeks);
};
const getVaccineStatus = (name: string) => {
const dueDate = calculateDueDate(birthDate, IAP_SCHEDULE.find((v) => v.name === name)?.weeks || 0);
const today = new Date();
const todayStr = today.toISOString().split("T")[0];
const given = isGiven(name);
if (given) return "completed";
if (dueDate < todayStr) return "overdue";
return "upcoming";
};
return ( return (
<div className="min-h-screen bg-gradient-to-br from-rose-50 to-amber-50"> <div className="min-h-screen bg-gradient-to-br from-rose-50 to-amber-50">
<div className="p-4"> <div className="p-4">
@ -450,21 +478,42 @@ const SUPPLEMENTS = [
{tab === "vaccinations" && ( {tab === "vaccinations" && (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex gap-2 mb-4 overflow-x-auto">
<button
onClick={() => setVaccineTab("upcoming")}
className={`px-4 py-2 rounded-xl whitespace-nowrap ${vaccineTab === "upcoming" ? "bg-rose-400 text-white" : "bg-white"}`}
>
Upcoming ({getVaccinesByStatus("upcoming").length})
</button>
<button
onClick={() => setVaccineTab("completed")}
className={`px-4 py-2 rounded-xl whitespace-nowrap ${vaccineTab === "completed" ? "bg-rose-400 text-white" : "bg-white"}`}
>
Completed ({getVaccinesByStatus("completed").length})
</button>
<button
onClick={() => setVaccineTab("overdue")}
className={`px-4 py-2 rounded-xl whitespace-nowrap ${vaccineTab === "overdue" ? "bg-red-500 text-white" : "bg-white"}`}
>
Overdue ({getVaccinesByStatus("overdue").length})
</button>
</div>
<h2 className="font-semibold mb-3">IAP Schedule</h2> <h2 className="font-semibold mb-3">IAP Schedule</h2>
{loading ? ( {loading ? (
<p className="text-gray-500">Loading...</p> <p className="text-gray-500">Loading...</p>
) : ( ) : (
IAP_SCHEDULE.filter((v) => isPending(v.name) || isGiven(v.name)) getVaccinesByStatus(vaccineTab).map((vaccine) => {
.sort((a, b) => a.weeks - b.weeks)
.map((vaccine) => {
const given = isGiven(vaccine.name); const given = isGiven(vaccine.name);
const actualDate = getGivenDate(vaccine.name); const actualDate = getGivenDate(vaccine.name);
const dueDate = calculateDueDate(birthDate, vaccine.weeks); const dueDate = calculateDueDate(birthDate, vaccine.weeks);
const status = getVaccineStatus(vaccine.name);
const daysOverdue = status === "overdue" ? Math.floor((new Date().getTime() - new Date(dueDate).getTime()) / (1000 * 60 * 60 * 24)) : 0;
return ( return (
<div <div
key={vaccine.name} key={vaccine.name}
className={`p-4 bg-white rounded-xl ${given ? "opacity-60" : ""}`} className={`p-4 bg-white rounded-xl ${status === "completed" ? "opacity-60" : ""} ${status === "overdue" ? "border-l-4 border-red-500" : ""}`}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex-1"> <div className="flex-1">
@ -473,6 +522,9 @@ const SUPPLEMENTS = [
Due: {new Date(dueDate).toLocaleDateString()} Due: {new Date(dueDate).toLocaleDateString()}
{actualDate && ` · Given: ${new Date(actualDate).toLocaleDateString()}`} {actualDate && ` · Given: ${new Date(actualDate).toLocaleDateString()}`}
</div> </div>
{status === "overdue" && (
<div className="text-red-500 text-sm font-medium">{daysOverdue} days overdue</div>
)}
</div> </div>
{given ? ( {given ? (
<span className="text-green-500"></span> <span className="text-green-500"></span>