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:
parent
bc08828c18
commit
3e54efaf66
1 changed files with 56 additions and 4 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue