Added dark: variants to all cards, inputs, selects, text, and buttons throughout the medical page. Added whitespace-nowrap + flex-shrink-0 to all tab buttons to prevent labels like "Doctor Visit" from wrapping. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
873 lines
No EOL
35 KiB
TypeScript
873 lines
No EOL
35 KiB
TypeScript
"use client";
|
||
|
||
import { useState, useEffect } from "react";
|
||
import { useFamily } from "../FamilyProvider";
|
||
|
||
interface Medicine {
|
||
id: string;
|
||
name: string;
|
||
dose: string;
|
||
notes: string;
|
||
reminderTime?: string;
|
||
}
|
||
|
||
interface Allergy {
|
||
id: string;
|
||
name: string;
|
||
severity: string;
|
||
notes: string;
|
||
}
|
||
|
||
interface Visit {
|
||
id: string;
|
||
doctorName: string;
|
||
reason: string;
|
||
date: string;
|
||
notes: string;
|
||
}
|
||
|
||
interface Illness {
|
||
id: string;
|
||
name: string;
|
||
startDate: string;
|
||
endDate?: string;
|
||
notes: string;
|
||
}
|
||
|
||
// IAP Vaccination Schedule (India)
|
||
const IAP_SCHEDULE = [
|
||
{ name: "BCG", weeks: 0 },
|
||
{ name: "OPV-0", weeks: 0 },
|
||
{ name: "HepB-1", weeks: 0 },
|
||
{ name: "OPV-1", weeks: 6 },
|
||
{ name: "Pentavalent-1", weeks: 6 },
|
||
{ name: "PCV-1", weeks: 6 },
|
||
{ name: "Rota-1", weeks: 6 },
|
||
{ name: "OPV-2", weeks: 10 },
|
||
{ name: "Pentavalent-2", weeks: 10 },
|
||
{ name: "PCV-2", weeks: 10 },
|
||
{ name: "Rota-2", weeks: 10 },
|
||
{ name: "OPV-3", weeks: 14 },
|
||
{ name: "Pentavalent-3", weeks: 14 },
|
||
{ name: "PCV-3", weeks: 14 },
|
||
{ name: "Rota-3", weeks: 14 },
|
||
{ name: "MR-1", weeks: 48 }, // 9 months
|
||
{ name: "JE-1", weeks: 48 },
|
||
{ name: "Vitamin A-1", weeks: 48 },
|
||
{ name: "OPV-4", weeks: 48 },
|
||
{ name: "MR-2", weeks: 96 }, // 18 months
|
||
{ name: "JE-2", weeks: 96 },
|
||
{ name: "DPT-Booster-1", weeks: 96 },
|
||
{ name: "Vitamin A-2", weeks: 96 },
|
||
{ name: "OPV-5", weeks: 96 },
|
||
{ name: "DPT-Booster-2", weeks: 208 }, // 4 years
|
||
{ name: "Tetanus and adult diphtheria (Td)", weeks: 208 },
|
||
];
|
||
|
||
function calculateDueDate(birthDate: string, weeks: number): string {
|
||
const birth = new Date(birthDate);
|
||
birth.setDate(birth.getDate() + weeks * 7);
|
||
return birth.toISOString().split("T")[0];
|
||
}
|
||
|
||
export default function MedicalPage() {
|
||
const [vaccinations, setVaccinations] = useState<any[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const { childId: sessionChildId, child, loading: loadingChild } = useFamily();
|
||
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 [givenDate, setGivenDate] = useState("");
|
||
|
||
// CRUD state for medicine, allergies, visits, illness
|
||
const [medicines, setMedicines] = useState<Medicine[]>([]);
|
||
const [allergies, setAllergies] = useState<Allergy[]>([]);
|
||
const [visits, setVisits] = useState<Visit[]>([]);
|
||
const [illnesses, setIllnesses] = useState<Illness[]>([]);
|
||
|
||
// Add/Edit mode
|
||
const [editingMed, setEditingMed] = useState<Medicine | null>(null);
|
||
const [editingAllergy, setEditingAllergy] = useState<Allergy | null>(null);
|
||
const [editingVisit, setEditingVisit] = useState<Visit | null>(null);
|
||
const [editingIllness, setEditingIllness] = useState<Illness | null>(null);
|
||
const [showAddMed, setShowAddMed] = useState(false);
|
||
const [showAddAllergy, setShowAddAllergy] = useState(false);
|
||
const [showAddVisit, setShowAddVisit] = useState(false);
|
||
const [showAddIllness, setShowAddIllness] = useState(false);
|
||
|
||
// Form state for new items
|
||
const [newMedName, setNewMedName] = useState("");
|
||
const [newMedDose, setNewMedDose] = useState("");
|
||
const [newMedNotes, setNewMedNotes] = useState("");
|
||
const [newAllergyName, setNewAllergyName] = useState("");
|
||
const [newAllergySeverity, setNewAllergySeverity] = useState("mild");
|
||
const [newAllergyNotes, setNewAllergyNotes] = useState("");
|
||
const [newVisitDoctor, setNewVisitDoctor] = useState("");
|
||
const [newVisitReason, setNewVisitReason] = useState("");
|
||
const [newVisitDate, setNewVisitDate] = useState("");
|
||
const [newVisitNotes, setNewVisitNotes] = useState("");
|
||
const [newIllnessName, setNewIllnessName] = useState("");
|
||
const [newIllnessStart, setNewIllnessStart] = useState("");
|
||
const [newIllnessEnd, setNewIllnessEnd] = useState("");
|
||
const [newIllnessNotes, setNewIllnessNotes] = useState("");
|
||
|
||
// Load data from database on mount
|
||
useEffect(() => {
|
||
fetchMedicines();
|
||
fetchAllergies();
|
||
fetchVisits();
|
||
fetchIllnesses();
|
||
}, []);
|
||
|
||
const fetchMedicines = async () => {
|
||
try {
|
||
const res = await fetch(`/api/medicines?childId=${childId}`);
|
||
const data = await res.json();
|
||
setMedicines(data.medicines || []);
|
||
} catch (err) {
|
||
console.error("Failed to fetch medicines:", err);
|
||
}
|
||
};
|
||
|
||
const fetchAllergies = async () => {
|
||
try {
|
||
const res = await fetch(`/api/allergies?childId=${childId}`);
|
||
const data = await res.json();
|
||
setAllergies(data.allergies || []);
|
||
} catch (err) {
|
||
console.error("Failed to fetch allergies:", err);
|
||
}
|
||
};
|
||
|
||
const fetchVisits = async () => {
|
||
try {
|
||
const res = await fetch(`/api/visits?childId=${childId}`);
|
||
const data = await res.json();
|
||
setVisits(data.visits || []);
|
||
} catch (err) {
|
||
console.error("Failed to fetch visits:", err);
|
||
}
|
||
};
|
||
|
||
const fetchIllnesses = async () => {
|
||
try {
|
||
const res = await fetch(`/api/illnesses?childId=${childId}`);
|
||
const data = await res.json();
|
||
setIllnesses(data.illnesses || []);
|
||
} catch (err) {
|
||
console.error("Failed to fetch illnesses:", err);
|
||
}
|
||
};
|
||
|
||
// Medicine CRUD - now using database
|
||
const saveMedicine = async () => {
|
||
if (!newMedName) return;
|
||
try {
|
||
if (editingMed) {
|
||
await fetch(`/api/medicines`, {
|
||
method: "PATCH",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ id: editingMed.id, name: newMedName, dose: newMedDose, notes: newMedNotes }),
|
||
});
|
||
} else {
|
||
await fetch("/api/medicines", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ childId, name: newMedName, dose: newMedDose, notes: newMedNotes }),
|
||
});
|
||
}
|
||
fetchMedicines();
|
||
} catch (err) {
|
||
console.error("Failed to save:", err);
|
||
}
|
||
resetMedForm();
|
||
};
|
||
|
||
const deleteMedicine = async (id: string) => {
|
||
try {
|
||
await fetch(`/api/medicines?id=${id}`, { method: "DELETE" });
|
||
fetchMedicines();
|
||
} catch (err) {
|
||
console.error("Failed to delete:", err);
|
||
}
|
||
};
|
||
|
||
const editMedicine = (med: Medicine) => {
|
||
setEditingMed(med);
|
||
setNewMedName(med.name);
|
||
setNewMedDose(med.dose);
|
||
setNewMedNotes(med.notes);
|
||
setShowAddMed(true);
|
||
};
|
||
|
||
const resetMedForm = () => {
|
||
setEditingMed(null);
|
||
setNewMedName("");
|
||
setNewMedDose("");
|
||
setNewMedNotes("");
|
||
setShowAddMed(false);
|
||
};
|
||
|
||
// Allergy CRUD - now using database
|
||
const saveAllergy = async () => {
|
||
if (!newAllergyName) return;
|
||
try {
|
||
if (editingAllergy) {
|
||
await fetch(`/api/allergies`, {
|
||
method: "PATCH",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ id: editingAllergy.id, name: newAllergyName, severity: newAllergySeverity, notes: newAllergyNotes }),
|
||
});
|
||
} else {
|
||
await fetch("/api/allergies", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ childId, name: newAllergyName, severity: newAllergySeverity, notes: newAllergyNotes }),
|
||
});
|
||
}
|
||
fetchAllergies();
|
||
} catch (err) {
|
||
console.error("Failed to save:", err);
|
||
}
|
||
resetAllergyForm();
|
||
};
|
||
|
||
const deleteAllergy = async (id: string) => {
|
||
try {
|
||
await fetch(`/api/allergies?id=${id}`, { method: "DELETE" });
|
||
fetchAllergies();
|
||
} catch (err) {
|
||
console.error("Failed to delete:", err);
|
||
}
|
||
};
|
||
|
||
const editAllergy = (allergy: Allergy) => {
|
||
setEditingAllergy(allergy);
|
||
setNewAllergyName(allergy.name);
|
||
setNewAllergySeverity(allergy.severity);
|
||
setNewAllergyNotes(allergy.notes);
|
||
setShowAddAllergy(true);
|
||
};
|
||
|
||
const resetAllergyForm = () => {
|
||
setEditingAllergy(null);
|
||
setNewAllergyName("");
|
||
setNewAllergySeverity("mild");
|
||
setNewAllergyNotes("");
|
||
setShowAddAllergy(false);
|
||
};
|
||
|
||
// Visit CRUD - now using database
|
||
const saveVisit = async () => {
|
||
if (!newVisitDoctor) return;
|
||
try {
|
||
if (editingVisit) {
|
||
await fetch(`/api/visits`, {
|
||
method: "PATCH",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ id: editingVisit.id, doctorName: newVisitDoctor, reason: newVisitReason, date: newVisitDate, notes: newVisitNotes }),
|
||
});
|
||
} else {
|
||
await fetch("/api/visits", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ childId, doctorName: newVisitDoctor, reason: newVisitReason, date: newVisitDate, notes: newVisitNotes }),
|
||
});
|
||
}
|
||
fetchVisits();
|
||
} catch (err) {
|
||
console.error("Failed to save:", err);
|
||
}
|
||
resetVisitForm();
|
||
};
|
||
|
||
const deleteVisit = async (id: string) => {
|
||
try {
|
||
await fetch(`/api/visits?id=${id}`, { method: "DELETE" });
|
||
fetchVisits();
|
||
} catch (err) {
|
||
console.error("Failed to delete:", err);
|
||
}
|
||
};
|
||
|
||
const editVisit = (visit: Visit) => {
|
||
setEditingVisit(visit);
|
||
setNewVisitDoctor(visit.doctorName);
|
||
setNewVisitReason(visit.reason);
|
||
setNewVisitDate(visit.date);
|
||
setNewVisitNotes(visit.notes);
|
||
setShowAddVisit(true);
|
||
};
|
||
|
||
const resetVisitForm = () => {
|
||
setEditingVisit(null);
|
||
setNewVisitDoctor("");
|
||
setNewVisitReason("");
|
||
setNewVisitDate("");
|
||
setNewVisitNotes("");
|
||
setShowAddVisit(false);
|
||
};
|
||
|
||
// Illness CRUD - now using database
|
||
const saveIllness = async () => {
|
||
if (!newIllnessName) return;
|
||
try {
|
||
if (editingIllness) {
|
||
await fetch(`/api/illnesses`, {
|
||
method: "PATCH",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ id: editingIllness.id, name: newIllnessName, startDate: newIllnessStart, endDate: newIllnessEnd, notes: newIllnessNotes }),
|
||
});
|
||
} else {
|
||
await fetch("/api/illnesses", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ childId, name: newIllnessName, startDate: newIllnessStart, endDate: newIllnessEnd, notes: newIllnessNotes }),
|
||
});
|
||
}
|
||
fetchIllnesses();
|
||
} catch (err) {
|
||
console.error("Failed to save:", err);
|
||
}
|
||
resetIllnessForm();
|
||
};
|
||
|
||
const deleteIllness = async (id: string) => {
|
||
try {
|
||
await fetch(`/api/illnesses?id=${id}`, { method: "DELETE" });
|
||
fetchIllnesses();
|
||
} catch (err) {
|
||
console.error("Failed to delete:", err);
|
||
}
|
||
};
|
||
|
||
const editIllness = (illness: Illness) => {
|
||
setEditingIllness(illness);
|
||
setNewIllnessName(illness.name);
|
||
setNewIllnessStart(illness.startDate);
|
||
setNewIllnessEnd(illness.endDate || "");
|
||
setNewIllnessNotes(illness.notes);
|
||
setShowAddIllness(true);
|
||
};
|
||
|
||
const resetIllnessForm = () => {
|
||
setEditingIllness(null);
|
||
setNewIllnessName("");
|
||
setNewIllnessStart("");
|
||
setNewIllnessEnd("");
|
||
setNewIllnessNotes("");
|
||
setShowAddIllness(false);
|
||
};
|
||
|
||
const childId = sessionChildId;
|
||
const birthDate = child?.birthDate || "2024-01-15";
|
||
|
||
// Common supplements for babies
|
||
const SUPPLEMENTS = [
|
||
{ name: "Vitamin D3", dose: "400 IU daily", notes: "For bone health" },
|
||
{ name: "Iron", dose: "1 mg/kg daily", notes: "As prescribed" },
|
||
{ name: "Calcium", dose: "500 mg daily", notes: "With food" },
|
||
{ name: "Zinc", dose: "5 mg daily", notes: "Immune support" },
|
||
{ name: "Omega-3", dose: "DHA 100mg", notes: "Brain development" },
|
||
{ name: "Probiotics", dose: "1 shot daily", notes: "Gut health" },
|
||
{ name: "Multivitamin", dose: "As directed", notes: "Daily vitamin" },
|
||
];
|
||
|
||
useEffect(() => {
|
||
fetch(`/api/vaccinations?childId=${childId}`)
|
||
.then((res) => res.json())
|
||
.then((data) => {
|
||
setVaccinations(data.vaccinations || []);
|
||
setLoading(false);
|
||
})
|
||
.catch(() => setLoading(false));
|
||
}, [childId]);
|
||
|
||
const handleMarkGiven = async (vaccineName: string) => {
|
||
const dueDate = calculateDueDate(birthDate, IAP_SCHEDULE.find((v) => v.name === vaccineName)?.weeks || 0);
|
||
const dateToSave = givenDate || new Date().toISOString().split("T")[0];
|
||
|
||
await fetch("/api/vaccinations", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
childId,
|
||
vaccineName,
|
||
scheduledDate: dueDate,
|
||
givenDate: dateToSave,
|
||
status: "given",
|
||
}),
|
||
});
|
||
|
||
setVaccinations((prev) => [...prev, { vaccine_name: vaccineName, given_date: dateToSave, status: "given" }]);
|
||
setShowAddDate(null);
|
||
setGivenDate("");
|
||
};
|
||
|
||
const isGiven = (name: string) => vaccinations.some((v) => v.vaccine_name === name && v.status === "given");
|
||
const getGivenDate = (name: string) => vaccinations.find((v) => v.vaccine_name === name && v.status === "given")?.given_date;
|
||
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 (
|
||
<div className="min-h-screen bg-gradient-to-br from-rose-50 to-amber-50 dark:from-gray-900 dark:to-gray-800">
|
||
<div className="p-4">
|
||
<div className="flex justify-between items-center">
|
||
<a href="/menu" className="p-2">←</a>
|
||
<h1 className="text-xl font-bold">Medical</h1>
|
||
</div>
|
||
|
||
<div className="flex gap-2 mb-6 overflow-x-auto">
|
||
<button
|
||
onClick={() => setTab("vaccinations")}
|
||
className={`px-4 p-3 rounded-xl whitespace-nowrap flex-shrink-0 ${tab === "vaccinations" ? "bg-rose-400 text-white" : "bg-white dark:bg-gray-800 dark:text-white"}`}
|
||
>
|
||
Vaccines
|
||
</button>
|
||
<button
|
||
onClick={() => setTab("medicine")}
|
||
className={`px-4 p-3 rounded-xl whitespace-nowrap flex-shrink-0 ${tab === "medicine" ? "bg-rose-400 text-white" : "bg-white dark:bg-gray-800 dark:text-white"}`}
|
||
>
|
||
Medicine
|
||
</button>
|
||
<button
|
||
onClick={() => setTab("allergies")}
|
||
className={`px-4 p-3 rounded-xl whitespace-nowrap flex-shrink-0 ${tab === "allergies" ? "bg-rose-400 text-white" : "bg-white dark:bg-gray-800 dark:text-white"}`}
|
||
>
|
||
Allergies
|
||
</button>
|
||
<button
|
||
onClick={() => setTab("visits")}
|
||
className={`px-4 p-3 rounded-xl whitespace-nowrap flex-shrink-0 ${tab === "visits" ? "bg-rose-400 text-white" : "bg-white dark:bg-gray-800 dark:text-white"}`}
|
||
>
|
||
Doctor Visit
|
||
</button>
|
||
<button
|
||
onClick={() => setTab("illness")}
|
||
className={`px-4 p-3 rounded-xl whitespace-nowrap flex-shrink-0 ${tab === "illness" ? "bg-rose-400 text-white" : "bg-white dark:bg-gray-800 dark:text-white"}`}
|
||
>
|
||
Illness
|
||
</button>
|
||
</div>
|
||
|
||
{tab === "vaccinations" && (
|
||
<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 flex-shrink-0 ${vaccineTab === "upcoming" ? "bg-rose-400 text-white" : "bg-white dark:bg-gray-800 dark:text-white"}`}
|
||
>
|
||
Upcoming ({getVaccinesByStatus("upcoming").length})
|
||
</button>
|
||
<button
|
||
onClick={() => setVaccineTab("completed")}
|
||
className={`px-4 py-2 rounded-xl whitespace-nowrap flex-shrink-0 ${vaccineTab === "completed" ? "bg-rose-400 text-white" : "bg-white dark:bg-gray-800 dark:text-white"}`}
|
||
>
|
||
Completed ({getVaccinesByStatus("completed").length})
|
||
</button>
|
||
<button
|
||
onClick={() => setVaccineTab("overdue")}
|
||
className={`px-4 py-2 rounded-xl whitespace-nowrap flex-shrink-0 ${vaccineTab === "overdue" ? "bg-red-500 text-white" : "bg-white dark:bg-gray-800 dark:text-white"}`}
|
||
>
|
||
Overdue ({getVaccinesByStatus("overdue").length})
|
||
</button>
|
||
</div>
|
||
|
||
<h2 className="font-semibold mb-3">IAP Schedule</h2>
|
||
{loading ? (
|
||
<p className="text-gray-500">Loading...</p>
|
||
) : (
|
||
getVaccinesByStatus(vaccineTab).map((vaccine) => {
|
||
const given = isGiven(vaccine.name);
|
||
const actualDate = getGivenDate(vaccine.name);
|
||
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 (
|
||
<div
|
||
key={vaccine.name}
|
||
className={`p-4 bg-white dark:bg-gray-800 rounded-xl ${status === "completed" ? "opacity-60" : ""} ${status === "overdue" ? "border-l-4 border-red-500" : ""}`}
|
||
>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex-1">
|
||
<div className="font-medium">{vaccine.name}</div>
|
||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||
Due: {new Date(dueDate).toLocaleDateString()}
|
||
{actualDate && ` · Given: ${new Date(actualDate).toLocaleDateString()}`}
|
||
</div>
|
||
{status === "overdue" && (
|
||
<div className="text-red-500 text-sm font-medium">{daysOverdue} days overdue</div>
|
||
)}
|
||
</div>
|
||
{given ? (
|
||
<span className="text-green-500">✓</span>
|
||
) : showAddDate === vaccine.name ? (
|
||
<div className="flex gap-2">
|
||
<input
|
||
type="date"
|
||
value={givenDate}
|
||
onChange={(e) => setGivenDate(e.target.value)}
|
||
className="p-1 text-sm border dark:border-gray-600 rounded dark:bg-gray-700 dark:text-white"
|
||
placeholder="Date given"
|
||
/>
|
||
<button
|
||
onClick={() => handleMarkGiven(vaccine.name)}
|
||
className="px-3 py-1 bg-rose-400 text-white rounded-lg text-sm"
|
||
>
|
||
✓
|
||
</button>
|
||
<button
|
||
onClick={() => { setShowAddDate(null); setGivenDate(""); }}
|
||
className="px-2 py-1 text-gray-400"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<button
|
||
onClick={() => setShowAddDate(vaccine.name)}
|
||
className="px-4 py-2 bg-rose-400 text-white rounded-lg text-sm"
|
||
>
|
||
Mark Given
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
})
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{tab === "medicine" && (
|
||
<div className="space-y-2">
|
||
<h2 className="font-semibold mb-3">Medicine & Supplements</h2>
|
||
|
||
{/* Add Form */}
|
||
{showAddMed && (
|
||
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl space-y-3">
|
||
<input
|
||
type="text"
|
||
value={newMedName}
|
||
onChange={(e) => setNewMedName(e.target.value)}
|
||
placeholder="Medicine name"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={newMedDose}
|
||
onChange={(e) => setNewMedDose(e.target.value)}
|
||
placeholder="Dose (e.g., 5ml, 1 tablet)"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={newMedNotes}
|
||
onChange={(e) => setNewMedNotes(e.target.value)}
|
||
placeholder="Notes"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<div className="flex gap-2">
|
||
<button onClick={saveMedicine} className="flex-1 py-2 bg-rose-400 text-white rounded-lg">
|
||
{editingMed ? "Update" : "Add"}
|
||
</button>
|
||
<button onClick={resetMedForm} className="flex-1 py-2 bg-gray-200 dark:bg-gray-600 dark:text-white rounded-lg">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{medicines.length === 0 && !showAddMed ? (
|
||
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl">
|
||
<p className="text-gray-500 dark:text-gray-400">No medicines added</p>
|
||
</div>
|
||
) : (
|
||
medicines.map((med) => (
|
||
<div key={med.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">{med.name}</div>
|
||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||
{med.dose} {med.notes && `· ${med.notes}`}
|
||
{med.reminderTime && ` · ⏰ ${med.reminderTime}`}
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<button onClick={() => editMedicine(med)} className="p-2 text-gray-400">✏️</button>
|
||
<button onClick={() => deleteMedicine(med.id)} className="p-2 text-red-400">🗑️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))
|
||
)}
|
||
|
||
{!showAddMed && (
|
||
<button onClick={() => setShowAddMed(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 Medicine
|
||
</button>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{tab === "allergies" && (
|
||
<div className="space-y-2">
|
||
<h2 className="font-semibold mb-3">Known Allergies</h2>
|
||
|
||
{showAddAllergy && (
|
||
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl space-y-3">
|
||
<input
|
||
type="text"
|
||
value={newAllergyName}
|
||
onChange={(e) => setNewAllergyName(e.target.value)}
|
||
placeholder="Allergy name (e.g., Peanut, Milk)"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<select
|
||
value={newAllergySeverity}
|
||
onChange={(e) => setNewAllergySeverity(e.target.value)}
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white"
|
||
>
|
||
<option value="mild">Mild</option>
|
||
<option value="moderate">Moderate</option>
|
||
<option value="severe">Severe</option>
|
||
</select>
|
||
<input
|
||
type="text"
|
||
value={newAllergyNotes}
|
||
onChange={(e) => setNewAllergyNotes(e.target.value)}
|
||
placeholder="Notes"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<div className="flex gap-2">
|
||
<button onClick={saveAllergy} className="flex-1 py-2 bg-rose-400 text-white rounded-lg">
|
||
{editingAllergy ? "Update" : "Add"}
|
||
</button>
|
||
<button onClick={resetAllergyForm} className="flex-1 py-2 bg-gray-200 dark:bg-gray-600 dark:text-white rounded-lg">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{allergies.length === 0 && !showAddAllergy ? (
|
||
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl">
|
||
<p className="text-gray-500 dark:text-gray-400">No allergies recorded</p>
|
||
</div>
|
||
) : (
|
||
allergies.map((allergy) => (
|
||
<div key={allergy.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">{allergy.name}</div>
|
||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||
<span className={allergy.severity === "severe" ? "text-red-500" : "text-orange-500"}>
|
||
{allergy.severity.toUpperCase()}
|
||
</span>
|
||
{allergy.notes && ` · ${allergy.notes}`}
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<button onClick={() => editAllergy(allergy)} className="p-2 text-gray-400">✏️</button>
|
||
<button onClick={() => deleteAllergy(allergy.id)} className="p-2 text-red-400">🗑️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))
|
||
)}
|
||
|
||
{!showAddAllergy && (
|
||
<button onClick={() => setShowAddAllergy(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 Allergy
|
||
</button>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{tab === "visits" && (
|
||
<div className="space-y-2">
|
||
<h2 className="font-semibold mb-3">Doctor Visits</h2>
|
||
|
||
{showAddVisit && (
|
||
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl space-y-3">
|
||
<input
|
||
type="text"
|
||
value={newVisitDoctor}
|
||
onChange={(e) => setNewVisitDoctor(e.target.value)}
|
||
placeholder="Doctor name"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={newVisitReason}
|
||
onChange={(e) => setNewVisitReason(e.target.value)}
|
||
placeholder="Reason (e.g., Checkup, Fever)"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<input
|
||
type="date"
|
||
value={newVisitDate}
|
||
onChange={(e) => setNewVisitDate(e.target.value)}
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white"
|
||
/>
|
||
<input
|
||
type="text"
|
||
value={newVisitNotes}
|
||
onChange={(e) => setNewVisitNotes(e.target.value)}
|
||
placeholder="Notes"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<div className="flex gap-2">
|
||
<button onClick={saveVisit} className="flex-1 py-2 bg-rose-400 text-white rounded-lg">
|
||
{editingVisit ? "Update" : "Add"}
|
||
</button>
|
||
<button onClick={resetVisitForm} className="flex-1 py-2 bg-gray-200 dark:bg-gray-600 dark:text-white rounded-lg">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{visits.length === 0 && !showAddVisit ? (
|
||
<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((visit) => (
|
||
<div key={visit.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">{visit.doctorName}</div>
|
||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||
{new Date(visit.date).toLocaleDateString()}
|
||
{visit.reason && ` · ${visit.reason}`}
|
||
{visit.notes && ` · ${visit.notes}`}
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<button onClick={() => editVisit(visit)} className="p-2 text-gray-400">✏️</button>
|
||
<button onClick={() => deleteVisit(visit.id)} className="p-2 text-red-400">🗑️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))
|
||
)}
|
||
|
||
{!showAddVisit && (
|
||
<button onClick={() => setShowAddVisit(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>
|
||
)}
|
||
|
||
{tab === "illness" && (
|
||
<div className="space-y-2">
|
||
<h2 className="font-semibold mb-3">Illness Log</h2>
|
||
|
||
{showAddIllness && (
|
||
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl space-y-3">
|
||
<input
|
||
type="text"
|
||
value={newIllnessName}
|
||
onChange={(e) => setNewIllnessName(e.target.value)}
|
||
placeholder="Illness (e.g., Cold, Fever, Flu)"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<div className="grid grid-cols-2 gap-2">
|
||
<input
|
||
type="date"
|
||
value={newIllnessStart}
|
||
onChange={(e) => setNewIllnessStart(e.target.value)}
|
||
placeholder="Start date"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white"
|
||
/>
|
||
<input
|
||
type="date"
|
||
value={newIllnessEnd}
|
||
onChange={(e) => setNewIllnessEnd(e.target.value)}
|
||
placeholder="End date"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white"
|
||
/>
|
||
</div>
|
||
<input
|
||
type="text"
|
||
value={newIllnessNotes}
|
||
onChange={(e) => setNewIllnessNotes(e.target.value)}
|
||
placeholder="Notes"
|
||
className="w-full p-2 border dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
||
/>
|
||
<div className="flex gap-2">
|
||
<button onClick={saveIllness} className="flex-1 py-2 bg-rose-400 text-white rounded-lg">
|
||
{editingIllness ? "Update" : "Add"}
|
||
</button>
|
||
<button onClick={resetIllnessForm} className="flex-1 py-2 bg-gray-200 dark:bg-gray-600 dark:text-white rounded-lg">
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{illnesses.length === 0 && !showAddIllness ? (
|
||
<div className="p-4 bg-white dark:bg-gray-800 rounded-xl">
|
||
<p className="text-gray-500 dark:text-gray-400">No illnesses recorded</p>
|
||
</div>
|
||
) : (
|
||
illnesses.map((illness) => (
|
||
<div key={illness.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">{illness.name}</div>
|
||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||
{new Date(illness.startDate).toLocaleDateString()}
|
||
{illness.endDate && ` - ${new Date(illness.endDate).toLocaleDateString()}`}
|
||
{illness.notes && ` · ${illness.notes}`}
|
||
</div>
|
||
</div>
|
||
<div className="flex gap-2">
|
||
<button onClick={() => editIllness(illness)} className="p-2 text-gray-400">✏️</button>
|
||
<button onClick={() => deleteIllness(illness.id)} className="p-2 text-red-400">🗑️</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))
|
||
)}
|
||
|
||
{!showAddIllness && (
|
||
<button onClick={() => setShowAddIllness(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">
|
||
+ Log Illness
|
||
</button>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
} |