diff --git a/.env.development b/.env.development index 4a390ef..f6862db 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,4 @@ VITE_API_URL=http://localhost:8088 -VITE_WS_URL=http://localhost:8080 +VITE_WS_URL=http://localhost:8088 VITE_CHATBOT_URL=http://localhost:8088 VITE_IOT_URL=http://localhost:8088 \ No newline at end of file diff --git a/src/components/AdminPortal/AdminDashboard.jsx b/src/components/AdminPortal/AdminDashboard.jsx index ec64d10..f3010f9 100644 --- a/src/components/AdminPortal/AdminDashboard.jsx +++ b/src/components/AdminPortal/AdminDashboard.jsx @@ -4,7 +4,6 @@ import { useNavigate } from "react-router-dom"; import { Bell, Activity, - Users, UserCircle, TrendingUp, } from "lucide-react"; @@ -17,9 +16,9 @@ import IotDevices from "../../pages/AdminPages/IotDevices"; import Analytics from "../../pages/AdminPages/Analytics"; import SecurityLogs from "../../pages/AdminPages/SecurityLogs"; import SpecialDoctorAdminPage from "../PatientPortal/bookings/SpecialDoctorAdminPage"; +import { API_BASE_URL } from "../../api"; function AdminDashboard() { - const [hasNotification, setHasNotification] = useState(true); const navigate = useNavigate(); const [adminData, setAdminData] = useState({ name: "", role: "" }); @@ -67,11 +66,9 @@ function AdminDashboard() { return (
- {/* === HEADER === */}
- {/* Left */}
- {/* Right */}
- {/* IoT Devices */}
@@ -170,11 +165,9 @@ function AdminDashboard() {

23

-
- {/* === BOTTOM NAVIGATION === */}
@@ -281,9 +274,7 @@ function AdminDashboard() { {/* === TAB CONTENT === */}
{activeTab === "users" && } - {activeTab === "accept" && } {activeTab === "patients" && } - {activeTab === "admins" && } {activeTab === "iot" && }
diff --git a/src/components/PatientPortal/RealtimeGraphs.jsx b/src/components/PatientPortal/RealtimeGraphs.jsx index 906e6e6..844361c 100644 --- a/src/components/PatientPortal/RealtimeGraphs.jsx +++ b/src/components/PatientPortal/RealtimeGraphs.jsx @@ -1,27 +1,61 @@ -import { useEffect, useState, useCallback } from "react"; -import { LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid, ResponsiveContainer } from "recharts"; -import { Activity, Thermometer, Heart, Droplet, Wind, Waves } from "lucide-react"; - -const BACKEND_URL = import.meta.env.VITE_IOT_URL; - -// Metric Card Component -function MetricCard({ icon: Icon, label, value, unit, status, statusColor }) { +import { useCallback, useEffect, useRef, useState } from "react"; +import { + Activity, + Heart, + RefreshCw, + Waves, + WifiOff, + Wind, + Thermometer, + Droplets, +} from "lucide-react"; +import { + CartesianGrid, + Line, + LineChart, + ReferenceLine, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; + +const BACKEND_URL = import.meta.env.VITE_IOT_URL || "http://localhost:8088"; + +function MetricCard({ icon: Icon, label, value, unit, status, statusColor, stale }) { return ( -
-
+
+
- {label} + + {label} + + {stale && ( + + cached + + )}
+
- {value} + + {value ?? 0} + {unit}
+ {status && ( {status} @@ -33,8 +67,7 @@ function MetricCard({ icon: Icon, label, value, unit, status, statusColor }) { ); } -// Graph Card Component -function GraphCard({ data, dataKey, label, icon: Icon, unit }) { +function GraphCard({ data, dataKey, label, icon: Icon, unit, refLines }) { return (
@@ -43,364 +76,313 @@ function GraphCard({ data, dataKey, label, icon: Icon, unit }) {

{label}

+ {data.length > 0 && ( + {data.length} pts + )}
- - - - - - - - - - - - - new Date(v).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) - } - stroke="#9ca3af" - style={{ fontSize: "11px" }} - /> - - - - new Date(v).toLocaleString()} - formatter={(value) => [`${value} ${unit}`, label]} - /> - - - - + {data.length === 0 ? ( +
+ No data yet — waiting for sensor readings… +
+ ) : ( + + + + + new Date(v + "Z").toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + }) + } + stroke="#9ca3af" + style={{ fontSize: "11px" }} + /> + + new Date(v + "Z").toLocaleString()} + formatter={(value) => [`${value} ${unit}`, label]} + /> + {refLines?.map((r) => ( + + ))} + + + + )}
); } -function RealtimeGraphs() { +export default function RealtimeGraphs({ patientId: propPatientId }) { const [history, setHistory] = useState([]); - const [currentData, setCurrentData] = useState(null); + const [current, setCurrent] = useState(null); const [loading, setLoading] = useState(true); + const [isStale, setIsStale] = useState(false); + const [lastUpdate, setLastUpdate] = useState(null); + const [error, setError] = useState(null); + const pollerRef = useRef(null); + + const patientId = propPatientId || localStorage.getItem("patientId"); + + const fetchData = useCallback(async () => { + if (!patientId) { + setError("No patient ID found"); + setLoading(false); + return; + } - const fetchHistory = useCallback(async () => { try { - const res = await fetch(`${BACKEND_URL}/api/sensordata/latest/50`); - if (res.ok) { - const data = await res.json(); - const reversed = Array.isArray(data) ? [...data].reverse() : []; - setHistory(reversed); - if (reversed.length > 0) { - setCurrentData(reversed[reversed.length - 1]); - } + const url = `${BACKEND_URL}/api/sensordata/patient/${patientId}/latest/50`; + console.log("Fetching IoT data from:", url); + + const res = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!res.ok) { + throw new Error(`HTTP ${res.status}`); } - } catch (error) { - console.error("Error fetching data:", error); - } - }, []); - useEffect(() => { - let active = true; + const data = await res.json(); - const loadInitial = async () => { - await fetchHistory(); - if (active) setLoading(false); - }; + if (Array.isArray(data) && data.length > 0) { + const latest = data[0]; + const chartData = [...data].reverse(); - loadInitial(); - const interval = setInterval(fetchHistory, 5000); + setHistory(chartData); + setCurrent(latest); - return () => { - active = false; - clearInterval(interval); - }; - }, [fetchHistory]); + const receivedAtUTC = new Date(latest.receivedAt + "Z"); + setLastUpdate(receivedAtUTC); - if (loading) { - return ( -
-
-
-

Loading patient monitoring...

-
-
- ); - } + const ageSeconds = (Date.now() - receivedAtUTC.getTime()) / 1000; + setIsStale(ageSeconds > 30); + } else { + setHistory([]); + setCurrent(null); + setLastUpdate(null); + setIsStale(false); + } - const getHeartRateStatus = (bpm) => { - if (!bpm || bpm === 0) return { text: "No Data", color: "bg-gray-100 text-gray-600" }; - if (bpm < 60) return { text: "Low", color: "bg-yellow-100 text-yellow-700" }; - if (bpm > 100) return { text: "High", color: "bg-red-100 text-red-700" }; + setError(null); + } catch (e) { + console.error("Fetch error:", e); + setError("Cannot reach IoT service — showing last known patient data"); + } finally { + setLoading(false); + } + }, [patientId]); + + useEffect(() => { + fetchData(); + pollerRef.current = setInterval(fetchData, 5000); + return () => clearInterval(pollerRef.current); + }, [fetchData]); + + const hrStatus = (v) => { + if (!v || v === 0) return { text: "No Data", color: "bg-gray-100 text-gray-600" }; + if (v < 60) return { text: "Low", color: "bg-yellow-100 text-yellow-700" }; + if (v > 100) return { text: "High", color: "bg-red-100 text-red-700" }; return { text: "Normal", color: "bg-teal-100 text-teal-700" }; }; - const getSpo2Status = (spo2) => { - if (!spo2 || spo2 === 0) return { text: "No Data", color: "bg-gray-100 text-gray-600" }; - if (spo2 < 95) return { text: "Low", color: "bg-orange-100 text-orange-700" }; + const spo2Status = (v) => { + if (!v || v === 0) return { text: "No Data", color: "bg-gray-100 text-gray-600" }; + if (v < 95) return { text: "Low", color: "bg-orange-100 text-orange-700" }; return { text: "Normal", color: "bg-teal-100 text-teal-700" }; }; - const getBodyTempStatus = (temp) => { - if (!temp || temp === 0) return { text: "No Data", color: "bg-gray-100 text-gray-600" }; - if (temp < 36.1) return { text: "Low", color: "bg-blue-100 text-blue-700" }; - if (temp > 37.2) return { text: "High", color: "bg-red-100 text-red-700" }; + const tempStatus = (v) => { + if (!v || v === 0) return { text: "No Data", color: "bg-gray-100 text-gray-600" }; + if (v < 36.1) return { text: "Low", color: "bg-blue-100 text-blue-700" }; + if (v > 37.2) return { text: "High", color: "bg-red-100 text-red-700" }; return { text: "Normal", color: "bg-teal-100 text-teal-700" }; }; - const hrStatus = currentData ? getHeartRateStatus(currentData.avgBpm) : { text: "No Data", color: "bg-gray-100 text-gray-600" }; - const spo2Status = currentData ? getSpo2Status(currentData.spo2) : { text: "No Data", color: "bg-gray-100 text-gray-600" }; - const bodyTempStatus = currentData ? getBodyTempStatus(currentData.waterTempC) : { text: "No Data", color: "bg-gray-100 text-gray-600" }; + const roomTempStatus = (v) => { + if (v === null || v === undefined || v === 0) { + return { text: "No Data", color: "bg-gray-100 text-gray-600" }; + } + if (v < 20) return { text: "Cold", color: "bg-blue-100 text-blue-700" }; + if (v > 32) return { text: "Hot", color: "bg-red-100 text-red-700" }; + return { text: "Comfort", color: "bg-teal-100 text-teal-700" }; + }; - return ( -
+ const humidityStatus = (v) => { + if (v === null || v === undefined || v === 0) { + return { text: "No Data", color: "bg-gray-100 text-gray-600" }; + } + if (v < 30) return { text: "Dry", color: "bg-yellow-100 text-yellow-700" }; + if (v > 70) return { text: "Humid", color: "bg-blue-100 text-blue-700" }; + return { text: "Normal", color: "bg-teal-100 text-teal-700" }; + }; + + if (loading) { + return
Loading patient monitoring…
; + } -
+ const hr = hrStatus(current?.avgBpm); + const sp = spo2Status(current?.spo2); + const bt = tempStatus(current?.waterTempC); + const rt = roomTempStatus(current?.roomTemp); + const hm = humidityStatus(current?.humidity); - {/* Patient Vital Signs */} -
-
-
- -
-

Patient Vital Signs

+ return ( +
+
+ {isStale && !error && ( +
+ + Sensor not sending live data — showing last recorded readings from database + {lastUpdate && ( + + Last: {lastUpdate.toLocaleTimeString()} + + )} +
+ )} + + {error && ( +
+ + {error} + {history.length > 0 && ( + Showing {history.length} cached readings + )}
+ )} -
+ {!loading && history.length === 0 && ( +
+ +

No sensor data found for this patient

+

Make sure the device is assigned and sending data

+
+ )} +
+
-
-
- - {/* Environmental Conditions */} -
-
-
- -
-

Environmental Conditions

-
- -
- - -
-
- - {/* Real-Time Monitoring Graphs */} -
-
-
- -
-

Real-Time Monitoring

-
- -
- - - - - -
- -
-
+ + +
+ + + + + + + + +
- - {/* Health Reference Ranges */} -
-

- - Health Reference Ranges -

-
- -
-

- - Heart Rate (BPM) -

-
    -
  • - - Normal: 60-100 -
  • -
  • - - Low: <60 -
  • -
  • - - High: >100 -
  • -
-
- -
-

- - SpO2 (%) -

-
    -
  • - - Normal: 95-100% -
  • -
  • - - Low: 90-94% -
  • -
  • - - Critical: <90% -
  • -
-
- -
-

- - Body Temperature (°C) -

-
    -
  • - - Normal: 36.1-37.2 -
  • -
  • - - Low: <36.1 -
  • -
  • - - High: >37.2 -
  • -
-
- -
-

- - Room Humidity (%) -

-
    -
  • - - Optimal: 30-60% -
  • -
  • - - Dry: <30% -
  • -
  • - - Humid: >60% -
  • -
-
- -
-
- - {/* Status Footer */} -
-
-
-
-
-
-
- 24/7 Monitoring Active -
- {currentData && ( - - Last update: {new Date(currentData.receivedAt).toLocaleTimeString()} - - )} -
-
-
); -} - -export default RealtimeGraphs; \ No newline at end of file +} \ No newline at end of file diff --git a/src/pages/AdminPages/IotDevices.jsx b/src/pages/AdminPages/IotDevices.jsx index 8710554..f18902c 100644 --- a/src/pages/AdminPages/IotDevices.jsx +++ b/src/pages/AdminPages/IotDevices.jsx @@ -1,14 +1,192 @@ -import React from "react"; +import React, { useEffect, useMemo, useState } from "react"; +import { API_BASE_URL } from "../../api"; function IotDevices() { + const [devices, setDevices] = useState([]); + const [loading, setLoading] = useState(true); + const [search, setSearch] = useState(""); + const [error, setError] = useState(""); + + const fetchDevices = async () => { + try { + setLoading(true); + setError(""); + + const token = localStorage.getItem("token"); + + const response = await fetch(`${API_BASE_URL}/api/sensordata/devices`, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + + const text = await response.text(); + let data = []; + + try { + data = text ? JSON.parse(text) : []; + } catch { + data = []; + } + + if (!response.ok) { + throw new Error(data.message || "Failed to fetch devices"); + } + + setDevices(Array.isArray(data) ? data : []); + } catch (err) { + console.error(err); + setError(err.message || "Unable to load IoT devices"); + setDevices([]); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchDevices(); + }, []); + + const filteredDevices = useMemo(() => { + return devices.filter((device) => { + const q = search.toLowerCase(); + + return ( + String(device.deviceId || "").toLowerCase().includes(q) || + String(device.status || "").toLowerCase().includes(q) || + String(device.userId || "").toLowerCase().includes(q) + ); + }); + }, [devices, search]); + + const getStatusStyle = (status) => { + switch ((status || "").toUpperCase()) { + case "ACTIVE": + return "bg-green-100 text-green-700"; + case "INACTIVE": + return "bg-red-100 text-red-700"; + case "UNASSIGNED": + return "bg-yellow-100 text-yellow-700"; + default: + return "bg-gray-100 text-gray-700"; + } + }; + + const maskToken = (token) => { + if (!token) return "-"; + if (token.length <= 6) return token; + return `${token.slice(0, 3)}***${token.slice(-3)}`; + }; + + const totalDevices = devices.length; + const activeDevices = devices.filter( + (d) => String(d.status || "").toUpperCase() === "ACTIVE" + ).length; + const assignedDevices = devices.filter((d) => d.userId !== null && d.userId !== undefined).length; + return (
-

IoT Devices

-

- IoT device list and live status will appear here. -

+
+
+

IoT Devices

+

+ Monitor device assignment and status +

+
+ + +
+ +
+
+

Total Devices

+

{totalDevices}

+
+ +
+

Active Devices

+

{activeDevices}

+
+ +
+

Assigned Devices

+

{assignedDevices}

+
+
+ + setSearch(e.target.value)} + className="w-full mb-4 px-4 py-2 bg-gray-100 rounded-full text-sm outline-none" + /> + + {error && ( +
+ {error} +
+ )} + + {loading &&

Loading devices...

} + + {!loading && filteredDevices.length === 0 && ( +

No IoT devices found

+ )} + + {!loading && filteredDevices.length > 0 && ( +
+ {filteredDevices.map((device) => ( +
+
+

+ {device.deviceId || "-"} +

+

+ Token: {maskToken(device.deviceToken)} +

+
+ +
+
+

Patient ID

+

+ {device.userId ?? "Unassigned"} +

+
+ +
+

Status

+ + {device.status || "UNKNOWN"} + +
+ +
+

Device Row ID

+

{device.id}

+
+
+
+ ))} +
+ )}
); } -export default IotDevices \ No newline at end of file +export default IotDevices; \ No newline at end of file diff --git a/src/pages/AdminPages/PatientManagement.jsx b/src/pages/AdminPages/PatientManagement.jsx index bc62df2..04b784d 100644 --- a/src/pages/AdminPages/PatientManagement.jsx +++ b/src/pages/AdminPages/PatientManagement.jsx @@ -1,109 +1,91 @@ import React, { useEffect, useState } from "react"; -import { fetchPatient, deletePatient, updatePatient } from "./service/patientconnecter"; -import { API_BASE_URL } from "../../api"; +import { + fetchPatient, + deletePatient, + updatePatient, + createPatient, +} from "./service/patientconnecter"; function PatientManagement() { const [patients, setPatient] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); + const [showModal, setShowModal] = useState(false); - const [formData, setFormData] = useState({ - name: "", - dateOfBirth: "", - address: "", - email: "", - nicNo: "", - gender: "", - contactNo: "", - guardiansName: "", - guardiansContactNo: "", - username: "", - password: "", - bloodType: "", - city: "", - district: "", - dispostalCodet: "", - guardianRelationship: "", - guardianEmail: "", - medicalConditions: "", - allergies: "", - currentMedications: "", - pastSurgeries: "", - emergencyNotes: "", - }); - const [error, setError] = useState(""); - const [submitting, setSubmitting] = useState(false); - - const [showEditModal, setShowEditModal] = useState(false); - const [editPatientId, setEditPatientId] = useState(null); - const [editFormData, setEditFormData] = useState({ - name: "", - dateOfBirth: "", - address: "", - email: "", - nicNo: "", - gender: "", - contactNo: "", - guardiansName: "", - guardiansContactNo: "", - username: "", - password: "", - bloodType: "", - city: "", - district: "", - dispostalCodet: "", - guardianRelationship: "", - guardianEmail: "", - medicalConditions: "", - allergies: "", - currentMedications: "", - pastSurgeries: "", - emergencyNotes: "", - }); -//new - const [showViewModal, setShowViewModal] = useState(false); - const [selectedPatient, setSelectedPatient] = useState(null); + const [showEditModal, setShowEditModal] = useState(false); + const [showViewModal, setShowViewModal] = useState(false); + + const [selectedPatient, setSelectedPatient] = useState(null); + const [editPatientId, setEditPatientId] = useState(null); + + const [error, setError] = useState(""); + const [submitting, setSubmitting] = useState(false); + + const initialFormData = { + name: "", + dateOfBirth: "", + address: "", + email: "", + nicNo: "", + gender: "", + contactNo: "", + guardiansName: "", + guardiansContactNo: "", + username: "", + password: "", + bloodType: "", + city: "", + district: "", + postalCode: "", + guardianRelationship: "", + guardianEmail: "", + medicalConditions: "", + allergies: "", + currentMedications: "", + pastSurgeries: "", + emergencyNotes: "", + deviceId: "", + }; + + const [formData, setFormData] = useState(initialFormData); + const [editFormData, setEditFormData] = useState(initialFormData); useEffect(() => { - async function load() { - try { - const data = await fetchPatient(); - setPatient(data || []); - } catch (err) { - console.error(err); - } finally { - setLoading(false); - } - } - load(); + loadPatients(); }, []); - // search bar - const filteredPatients = patients.filter((pt) => - pt.name?.toLowerCase().includes(search.toLowerCase()) - ); + async function loadPatients() { + try { + setLoading(true); + const data = await fetchPatient(); + setPatient(data || []); + } catch (err) { + console.error(err); + setPatient([]); + } finally { + setLoading(false); + } + } - const handleDelete = async (PatientId) => { - if (!window.confirm("Reject this Patient?")) return; - - try { - await deletePatient(PatientId); - - setPatient((prev) => - prev.filter((pt) => pt.Id !== PatientId) - ); - - alert("Patient rejected successfully"); - } catch (error) { - console.error(error); - alert("Failed to reject Patient")} - }; + const filteredPatients = patients.filter((patient) => + patient.name?.toLowerCase().includes(search.toLowerCase()) + ); const handleInputChange = (e) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); }; + const handleEditInputChange = (e) => { + const { name, value } = e.target; + setEditFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleView = (patient) => { + setSelectedPatient(patient); + setShowViewModal(true); + }; + const handleEdit = (patient) => { setEditPatientId(patient.id || patient.Id); @@ -115,132 +97,99 @@ function PatientManagement() { nicNo: patient.nicNo || "", gender: patient.gender || "", contactNo: patient.contactNo || "", + guardiansName: patient.guardiansName || "", + guardiansContactNo: patient.guardiansContactNo || "", + username: patient.username || "", + password: "", + bloodType: patient.bloodType || "", + city: patient.city || "", + district: patient.district || "", + postalCode: patient.postalCode || "", + guardianRelationship: patient.guardianRelationship || "", + guardianEmail: patient.guardianEmail || "", + medicalConditions: patient.medicalConditions || "", + allergies: patient.allergies || "", + currentMedications: patient.currentMedications || "", + pastSurgeries: patient.pastSurgeries || "", + emergencyNotes: patient.emergencyNotes || "", + deviceId: patient.deviceId || "", }); setShowEditModal(true); - }; - //new - const handleView = (patient) => { - setSelectedPatient(patient); - setShowViewModal(true); }; - const handleEditInputChange = (e) => { - const { name, value } = e.target; - setEditFormData((prev) => ({ ...prev, [name]: value })); + const handleDelete = async (patientId) => { + if (!window.confirm("Delete this patient?")) return; + + try { + await deletePatient(patientId); + setPatient((prev) => prev.filter((p) => (p.id || p.Id) !== patientId)); + alert("Patient deleted successfully"); + } catch (error) { + console.error(error); + alert(error.message || "Failed to delete patient"); + } }; const handleCreatePatient = async (e) => { - e.preventDefault(); - setError(""); - - if ( - !formData.name || - !formData.email || - !formData.username || - !formData.password || - !formData.address || - !formData.dateOfBirth || - !formData.allergies || - !formData.nicNo || - !formData.gender || - !formData.contactNo || - !formData.bloodType || - !formData.city || - !formData.currentMedications || - !formData.dispostalCodet || - !formData.district || - !formData.guardianEmail || - !formData.guardianRelationship || - !formData.guardiansContactNo || - !formData.guardiansName || - !formData.medicalConditions - ) { - setError("Please fill in all required fields"); - return; - } - + e.preventDefault(); + setError(""); + + if ( + !formData.name || + !formData.email || + !formData.username || + !formData.password || + !formData.dateOfBirth || + !formData.address || + !formData.nicNo || + !formData.gender || + !formData.contactNo + ) { + setError("Please fill in all required fields"); + return; + } + + try { setSubmitting(true); - try { - const token = localStorage.getItem("token"); - const response = await fetch(`${API_BASE_URL}/api/admin/patient/create`, { - method: "POST", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${token}`, - }, - body: JSON.stringify(formData), - }); - - if (!response.ok) { - const text = await response.text(); - setError(text || "Failed to create Patient"); - setSubmitting(false); - return; - } - - // Reset form and close modal - setFormData({ - name: "", - dateOfBirth: "", - address: "", - email: "", - nicNo: "", - gender: "", - contactNo: "", - guardiansName: "", - guardiansContactNo: "", - username: "", - password: "", - bloodType: "", - city: "", - district: "", - dispostalCodet: "", - guardianRelationship: "", - guardianEmail: "", - medicalConditions: "", - allergies: "", - currentMedications: "", - pastSurgeries: "", - emergencyNotes: "", - createdAt: "", - updatedAt: "", - }); - setShowModal(false); - setError(""); - - const data = await fetchPatient(); - setPatient(response.data || []); - } catch (e) { - setError("Unable to connect to server. Please try again."); - } finally { - setSubmitting(false); - - } - }; - const handleUpdatePatient = async (e) => { - e.preventDefault(); - - try { - await updatePatient(editPatientId, editFormData); - - const data = await fetchPatient(); - setPatient(data || []); - setShowEditModal(false); - - alert("Patient updated successfully"); - } catch (err) { - console.error(err); - alert(err.message || "Failed to update Patient"); - } - }; + await createPatient(formData); + setShowModal(false); + setFormData(initialFormData); + await loadPatients(); + alert("Patient created successfully"); + } catch (err) { + console.error(err); + setError(err.message || "Failed to create patient"); + } finally { + setSubmitting(false); + } + }; + const handleUpdatePatient = async (e) => { + e.preventDefault(); + + try { + await updatePatient(editPatientId, editFormData); + setShowEditModal(false); + await loadPatients(); + alert("Patient updated successfully"); + } catch (err) { + console.error(err); + alert(err.message || "Failed to update patient"); + } + }; + + const resetCreateModal = () => { + setShowModal(false); + setError(""); + setFormData(initialFormData); + }; return (

Patients

- +
))}
- {/* View Patient Modal */} - {showViewModal && selectedPatient && ( -
- -
- - {/* Header */} -
-

- Patient Profile -

- -
- - {/* Scrollable Body */} -
-
- - - - - - + {showViewModal && selectedPatient && ( +
+
+
+

Patient Profile

+ +
- {/* Right column */} - - +
+
+ + + + + + + + + + + + + +
- - - +
- {/* Guardian Details */} - - - - + + + + + + + +
+
+
- {/* Footer */} -
- -
- +
+
- )} +
+ )} - {/* Create Patient Modal */} {showModal && (
-
+

Create New Patient