diff --git a/src/components/PatientPortal/ProfileTab.jsx b/src/components/PatientPortal/ProfileTab.jsx
index 87da01e..171d6f6 100644
--- a/src/components/PatientPortal/ProfileTab.jsx
+++ b/src/components/PatientPortal/ProfileTab.jsx
@@ -1,26 +1,25 @@
import { useState, useEffect } from "react";
-import { User, Mail, Phone, MapPin, Calendar, Heart, Pill, AlertCircle } from "lucide-react";
+import {
+ User, Mail, Phone, MapPin, Calendar, Heart,
+ Pill, AlertCircle, Pencil, X, Save, Loader2,
+} from "lucide-react";
import { API_BASE_URL } from "../../api";
-// Parse comma-separated string into array of trimmed non-empty strings
+/* ── helpers ─────────────────────────────────────────────────────── */
const parseList = (str) => {
if (!str || typeof str !== "string") return [];
return str.split(",").map((s) => s.trim()).filter(Boolean);
};
-// Parse medications: "Name - frequency" or just "Name"
const parseMedications = (str) => {
const items = parseList(str);
return items.map((item) => {
const dash = item.indexOf(" - ");
- if (dash > 0) {
- return { name: item.slice(0, dash).trim(), frequency: item.slice(dash + 3).trim() };
- }
+ if (dash > 0) return { name: item.slice(0, dash).trim(), frequency: item.slice(dash + 3).trim() };
return { name: item, frequency: "" };
});
};
-// Parse past surgeries: "Name (year)" or just "Name"
const parseSurgeries = (str) => {
const items = parseList(str);
return items.map((item) => {
@@ -42,8 +41,7 @@ const calculateAge = (dateOfBirth) => {
const formatPatientId = (id) => {
if (!id) return "N/A";
- const year = new Date().getFullYear();
- return `P-${year}-${String(id).padStart(3, "0")}`;
+ return `P-${new Date().getFullYear()}-${String(id).padStart(3, "0")}`;
};
const formatAddress = (address, city, district, postalCode) => {
@@ -51,253 +49,346 @@ const formatAddress = (address, city, district, postalCode) => {
return parts.length ? parts.join(", ") : "—";
};
+/* ── MedSection: read-only chip/card display ────────────────────── */
+const ChipList = ({ items, color }) =>
+ items.length > 0 ? (
+
+ {items.map((item, i) => (
+
+ {item}
+
+ ))}
+
+ ) : (
+ None recorded
+ );
+
+/* ── main component ─────────────────────────────────────────────── */
const ProfileTab = ({ patientId: propPatientId }) => {
- const [patient, setPatient] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
-
- useEffect(() => {
- const fetchProfile = async () => {
- try {
- const patientIdToFetch = propPatientId || localStorage.getItem("patientId");
- const token = localStorage.getItem("token");
- if (!patientIdToFetch) {
- setError("Please log in again.");
- setLoading(false);
- return;
- }
- const response = await fetch(`${API_BASE_URL}/api/patient/get/${patientIdToFetch}`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- ...(token && { Authorization: `Bearer ${token}` }),
- },
- });
- if (!response.ok) throw new Error("Failed to load profile");
- const data = await response.json();
- setPatient(data);
- setError(null);
- } catch (err) {
- console.error("Profile fetch error:", err);
- setError(err.message || "Failed to load profile");
- } finally {
- setLoading(false);
- }
- };
- fetchProfile();
- }, [propPatientId]);
-
- if (loading) {
- return (
-
-
-
-
- {[1, 2, 3, 4, 5, 6].map((i) => (
-
- ))}
-
-
-
-
-
+ const [patient, setPatient] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ /* edit-mode state */
+ const [editing, setEditing] = useState(false);
+ const [saving, setSaving] = useState(false);
+ const [saveError, setSaveError] = useState("");
+ const [form, setForm] = useState({
+ allergies: "",
+ currentMedications: "",
+ pastSurgeries: "",
+ chronicConditions: "",
+ });
+
+ /* ── fetch profile ── */
+ const fetchProfile = async () => {
+ try {
+ const id = propPatientId || localStorage.getItem("patientId");
+ const token = localStorage.getItem("token");
+ if (!id) { setError("Please log in again."); setLoading(false); return; }
+ const res = await fetch(`${API_BASE_URL}/api/patient/get/${id}`, {
+ headers: { "Content-Type": "application/json", ...(token && { Authorization: `Bearer ${token}` }) },
+ });
+ if (!res.ok) throw new Error("Failed to load profile");
+ const data = await res.json();
+ setPatient(data);
+ setError(null);
+ } catch (err) {
+ setError(err.message || "Failed to load profile");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ useEffect(() => { fetchProfile(); }, [propPatientId]);
+
+ /* ── open edit form ── */
+ const handleEditClick = () => {
+ setForm({
+ allergies: patient?.allergies || "",
+ currentMedications: patient?.currentMedications || "",
+ pastSurgeries: patient?.pastSurgeries || "",
+ chronicConditions: patient?.chronicConditions || "",
+ });
+ setSaveError("");
+ setEditing(true);
+ };
+
+ /* ── save edits ── */
+ const handleSave = async () => {
+ setSaving(true);
+ setSaveError("");
+ try {
+ const id = propPatientId || localStorage.getItem("patientId");
+ const token = localStorage.getItem("token");
+ const res = await fetch(`${API_BASE_URL}/api/patient/update/${id}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ ...(token && { Authorization: `Bearer ${token}` }),
+ },
+ body: JSON.stringify(form),
+ });
+ if (!res.ok) throw new Error("Failed to save changes");
+ const updated = await res.json();
+ setPatient((prev) => ({ ...prev, ...updated }));
+ setEditing(false);
+ } catch (err) {
+ setSaveError(err.message || "Failed to save changes");
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ /* ── loading / error states ── */
+ if (loading) return (
+
+
+
+
+ {[1,2,3,4,5,6].map((i) =>
)}
- );
- }
-
- if (error) {
- return (
-
-
{error}
-
-
- );
- }
+
+ );
+
+ if (error) return (
+
+
{error}
+
+
+ );
if (!patient) return null;
- const age = calculateAge(patient.dateOfBirth);
- const patientIdFormatted = formatPatientId(patient.id || patient.Id);
- const addressFull = formatAddress(patient.address, patient.city, patient.district, patient.postalCode);
- const allergiesList = parseList(patient.allergies);
- const medicationsList = parseMedications(patient.currentMedications);
- const chronicConditionsList = parseList(patient.medicalConditions);
- const pastSurgeriesList = parseSurgeries(patient.pastSurgeries);
+ /* ── derived display values ── */
+ const age = calculateAge(patient.dateOfBirth);
+ const patientIdFmt = formatPatientId(patient.id || patient.Id);
+ const addressFull = formatAddress(patient.address, patient.city, patient.district, patient.postalCode);
+ const allergiesList = parseList(patient.allergies);
+ const medsList = parseMedications(patient.currentMedications);
+ const chronicList = parseList(patient.chronicConditions); // ← correct field
+ const surgeriesList = parseSurgeries(patient.pastSurgeries);
+
+ /* shared textarea style */
+ const taClass = "w-full border border-gray-300 rounded-xl px-3 py-2 text-sm text-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-400 resize-none";
return (
- {/* Personal Information Card */}
+
+ {/* ══════════════ Personal Information ══════════════ */}
Personal Information
+ {/* Left column */}
-
- Full Name
- {patient.name || "—"}
-
-
- Patient ID
- {patientIdFormatted}
-
-
- Age
-
- {age != null ? `${age} years` : "—"}
-
-
-
- Blood Type
- {patient.bloodType || "—"}
-
-
- NIC No
- {patient.nicNo || "—"}
-
-
- Gender
- {patient.gender || "—"}
-
+ {[
+ ["Full Name", patient.name],
+ ["Patient ID", patientIdFmt],
+ ["Age", age != null ? `${age} years` : "—"],
+ ["Blood Type", patient.bloodType],
+ ["NIC No", patient.nicNo],
+ ["Gender", patient.gender],
+ ].map(([label, val]) => (
+
+ {label}
+ {val || "—"}
+
+ ))}
+ {/* Right column */}
-
-
-
- Email
- {patient.email || "—"}
-
-
-
-
-
- Phone
- {patient.contactNo || "—"}
-
-
-
-
-
- Address
- {addressFull}
-
-
-
-
-
-
Emergency Contact
-
{patient.guardiansName || "—"}
- {patient.guardianRelationship && (
-
({patient.guardianRelationship})
- )}
+ {[
+ [Mail, "Email", patient.email, true],
+ [Phone, "Phone", patient.contactNo, false],
+ [MapPin, "Address", addressFull, true],
+ [User, "Emergency Contact", patient.guardiansName, false],
+ [Phone, "Emergency Phone", patient.guardiansContactNo, false],
+ ].map(([Icon, label, val, wrap]) => (
+
+
+
+ {label}
+ {val || "—"}
+ {label === "Emergency Contact" && patient.guardianRelationship && (
+ ({patient.guardianRelationship})
+ )}
+
-
-
-
-
- Emergency Phone
- {patient.guardiansContactNo || "—"}
-
-
+ ))}
- {/* Medical History Card */}
+ {/* ══════════════ Medical History ══════════════ */}
-
Medical History
-
-
-
-
-
- Allergies
-
-
- {allergiesList.length > 0 ? (
- allergiesList.map((allergy, index) => (
-
- {allergy}
-
- ))
- ) : (
-
None recorded
- )}
+ {/* Header + Edit / Save / Cancel buttons */}
+
+
Medical History
+ {!editing ? (
+
+ ) : (
+
+
+
+
+ )}
+
+
+ {saveError && (
+
+ {saveError}
+
+ )}
+
+ {/* ── Read-only view ── */}
+ {!editing && (
+
+ {/* Left column */}
+
+
+
+
+
+ Current Medications
+
+ {medsList.length > 0 ? (
+
+ {medsList.map((med, i) => (
+
+
{med.name}
+ {med.frequency &&
{med.frequency}
}
+
+ ))}
+
+ ) :
None recorded}
-
-
-
- Current Medications
-
-
- {medicationsList.length > 0 ? (
- medicationsList.map((med, index) => (
-
-
{med.name}
- {med.frequency &&
{med.frequency}
}
-
- ))
- ) : (
-
None recorded
- )}
+ {/* Right column */}
+
+
+
+ Chronic Conditions
+
+
+
+
+
+
+ Past Surgeries
+
+ {surgeriesList.length > 0 ? (
+
+ {surgeriesList.map((s, i) => (
+
+
{s.name}
+ {s.date &&
{s.date}
}
+
+ ))}
+
+ ) :
None recorded}
+ )}
-
-
-
-
- Chronic Conditions
-
-
- {chronicConditionsList.length > 0 ? (
- chronicConditionsList.map((condition, index) => (
-
- ))
- ) : (
-
None recorded
- )}
+ {/* ── Edit form ── */}
+ {editing && (
+
+ {/* Left column */}
+
+
+
+
+
+
+
+
-
-
-
- Past Surgeries
-
-
- {pastSurgeriesList.length > 0 ? (
- pastSurgeriesList.map((surgery, index) => (
-
-
{surgery.name}
- {surgery.date &&
{surgery.date}
}
-
- ))
- ) : (
-
None recorded
- )}
+ {/* Right column */}
+
+
+
+
+
+
+
+
+
+
+ Tip: separate multiple items with commas. For medications you can write Name - frequency. For surgeries you can write Name (year).
+
-
+ )}
{patient.emergencyNotes && (
diff --git a/src/pages/Login_Signup/signupPagePatient.jsx b/src/pages/Login_Signup/signupPagePatient.jsx
index 4f30e69..0767808 100644
--- a/src/pages/Login_Signup/signupPagePatient.jsx
+++ b/src/pages/Login_Signup/signupPagePatient.jsx
@@ -24,6 +24,7 @@ export default function SignupPagePatient() {
const [currentAllergies, setCurrentAllergies] = useState("");
const [currentMedications, setCurrentMedications] = useState("");
const [pastSurgeries, setPastSurgeries] = useState("");
+ const [chronicConditions, setChronicConditions] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
@@ -80,6 +81,7 @@ export default function SignupPagePatient() {
currentAllergies,
currentMedications,
pastSurgeries,
+ chronicConditions,
role: "PATIENT",
}),
});
@@ -107,7 +109,11 @@ export default function SignupPagePatient() {
? "Parent Name"
: guardianType === "CHILD"
? "Child Name"
- : "Guardian’s Name";
+ : "Guardian's Name";
+
+ /* ── shared textarea style ── */
+ const textareaClass =
+ "w-full bg-white/35 backdrop-blur-md rounded-2xl px-4 py-2 border border-white/40 text-gray-900 text-sm focus:outline-none focus:ring-2 focus:ring-[#00BAC5]/50";
return (
- {/* LEFT CARD */}
+
+ {/* ═══════════════ LEFT CARD — Personal & Guardian Info ═══════════════ */}
- Patient’s Sign Up
+ Patient's Sign Up
+ {/* ── Personal Info ── */}
-
-
-
-
-
-
-
-
-
-
-
- {/* RIGHT CARD */}
-
- {/* Form content */}
-
+ {/* ── Guardian Info ── */}
setGuardianContactNo(e.target.value)}
required
/>
+
+
+
+ {/* ═══════════════ RIGHT CARD — Credentials, Blood Type & Medical History ═══════════════ */}
+
+ {/* Form content */}
+
+ {/* ── Account Credentials ── */}
+ {/* ── Blood Type ── */}
+
+ {/* ── Optional Medical History ── */}
+
+
+
+
+
+
+
+
+
+
+
{/* Actions */}
@@ -396,4 +399,3 @@ export default function SignupPagePatient() {
);
}
-