Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.development
Original file line number Diff line number Diff line change
@@ -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
VITE_IOT_URL=http://localhost:8088
2 changes: 1 addition & 1 deletion .env.production
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VITE_API_URL=https://apigatewaynew.mangobush-8de88b36.southeastasia.azurecontainerapps.io
VITE_WS_URL=https://apigatewaynew.mangobush-8de88b36.southeastasia.azurecontainerapps.io
VITE_CHATBOT_URL=https://apigatewaynew.mangobush-8de88b36.southeastasia.azurecontainerapps.io
VITE_IOT_URL=https://apigatewaynew.mangobush-8de88b36.southeastasia.azurecontainerapps.io
VITE_IOT_URL=https://apigatewaynew.mangobush-8de88b36.southeastasia.azurecontainerapps.io
3 changes: 2 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import DocDashboard from "./pages/DoctorsPage/DocDashboard";
import DocViewPatient from "./pages/DoctorViewPatient/DoctorPatientView";

import AdminConfirmAppointments from "./components/PatientPortal/bookings/AdminConfirmAppointments";

import PaymentSuccess from "./components/PatientPortal/bookings/PaymentSuccess";

function HomePage() {
const stats = [
Expand Down Expand Up @@ -126,6 +126,7 @@ function AppContent() {
path="/AdminDashboard/confirm-appointments"
element={<AdminConfirmAppointments />}
/>
<Route path="/payment-success" element={<PaymentSuccess />} />
</Routes>
</>
);
Expand Down
20 changes: 20 additions & 0 deletions src/api/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,16 @@ export const getUserAppointments = async () => {
return [];
}
};
export const getDoctorAppointments = async (doctorId) => {
try {
if (!doctorId) return [];
const res = await API.get(`/doctor/appointments/${doctorId}`);
return res.data;
} catch (err) {
console.error("Error fetching doctor appointments:", err);
return [];
}
};

/* =========================
ADMIN – APPOINTMENTS
Expand All @@ -116,4 +126,14 @@ export const confirmAppointment = async (appointmentId, params) => {
return res.data;
};

// POST /api/doctor/appointments/confirm/{id}?physicalLocation=... OR ?zoomLink=...
export const confirmAppointmentDoctor = async (appointmentId, params) => {
const res = await API.post(
`/doctor/appointments/confirm/${appointmentId}`,
null,
{ params } // axios will convert this to query string
);
return res.data;
};

export default API;
193 changes: 108 additions & 85 deletions src/components/AdminPortal/AdminDashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

import {
Bell,
Activity,
Users,
UserCircle,
TrendingUp,
} from "lucide-react";
Expand All @@ -17,25 +15,28 @@ 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: "" });
const [activeTab, setActiveTab] = useState("users");
const [doctorCount, setDoctorCount] = useState(null);
const [patientCount, setPatientCount] = useState(null);
const [deviceCount, setDeviceCount] = useState(null);

// ✅ FIX: read admin info from localStorage (set during login)
const [adminData, setAdminData] = useState({ name: "Admin" });

useEffect(() => {
const storedUser = localStorage.getItem("user");
if (storedUser) {
try {
const parsed = JSON.parse(storedUser);
setAdminData({
name: parsed.name || parsed.username || parsed.fullName || "Admin",
role: parsed.role || "Administrator",
});
} catch (e) {
console.error("Failed to parse user from localStorage", e);
try {
const stored = localStorage.getItem("user");
if (stored) {
const parsed = JSON.parse(stored);
setAdminData(parsed);
}
} catch (e) {
console.warn("Could not parse user from localStorage", e);
}
}, []);

Expand All @@ -45,33 +46,60 @@ function AdminDashboard() {
navigate("/");
};

const [activeTab, setActiveTab] = useState("users");
const [doctorCount, setDoctorCount] = useState(0);
const [pendingdoctorCount, setPendingDoctorCount] = useState(0);
const [patientCount, setPatientCount] = useState(0);

useEffect(() => {
fetch("http://localhost:8080/api/dashboard/counts")
.then((res) => res.json())
.then((data) => {
console.log(data);
const token = localStorage.getItem("token");

setDoctorCount(data.doctorCount);
setPatientCount(data.patientCount);
setPendingDoctorCount(data.pendingDoctorCount);
})
.catch((error) =>
console.error("Error fetching dashboard counts:", error)
);
}, []);
async function loadDashboardCounts() {
try {
const response = await fetch(`${API_BASE_URL}/api/dashboard/counts`, {
headers: {
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
throw new Error("Failed to fetch dashboard counts");
}

const data = await response.json();
setDoctorCount(data?.doctorCount ?? 0);
setPatientCount(data?.patientCount ?? 0);
} catch (error) {
console.error("Error fetching dashboard counts:", error);
setDoctorCount(0);
setPatientCount(0);
}
}

async function loadDeviceCount() {
try {
const response = await fetch(`${API_BASE_URL}/api/sensordata/devices`, {
headers: {
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
throw new Error("Failed to fetch IoT devices");
}

const data = await response.json();
setDeviceCount(Array.isArray(data) ? data.length : 0);
} catch (error) {
console.error("Error fetching IoT devices:", error);
setDeviceCount(0);
}
}

loadDashboardCounts();
loadDeviceCount();
}, []);

return (
<div className="min-h-screen bg-[#F0F6FF] transition-colors">
{/* === HEADER === */}
<header className="bg-white shadow-sm w-full">
<div className="max-w-full px-4 sm:px-6 md:px-8 py-3 sm:py-4">
<div className="flex items-center justify-between">
{/* Left */}
<div className="flex items-center gap-3">
<div
className="rounded-lg p-2.5 shadow-xl flex items-center justify-center"
Expand All @@ -94,13 +122,12 @@ function AdminDashboard() {
<p className="text-sm text-gray-600">
Welcome,{" "}
<span className="font-semibold">
{adminData.name || "..."}
{adminData?.name || "Admin"}
</span>
</p>
</div>
</div>

{/* Right */}
<div className="flex items-center gap-3">
<button
onClick={handleLogout}
Expand All @@ -117,11 +144,8 @@ function AdminDashboard() {
</div>
</header>

{/* === STATS CARDS === */}
<div className="px-6 mt-6">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">

{/* Active Doctors */}
<div className="bg-[#E9FBF6] rounded-2xl p-5 shadow-md flex justify-between items-center">
<div>
<div className="w-10 h-10 bg-teal-500 rounded-lg flex items-center justify-center mb-3">
Expand All @@ -130,24 +154,10 @@ function AdminDashboard() {
<p className="text-sm text-gray-600">Active Doctors</p>
</div>
<h2 className="text-3xl font-bold text-gray-800">
{doctorCount === 0 ? "..." : doctorCount}
{doctorCount ?? "..."}
</h2>
</div>

{/* Special Doctors
<div className="bg-[#F5F0FF] rounded-2xl p-5 shadow-md flex justify-between items-center">
<div>
<div className="w-10 h-10 bg-purple-500 rounded-lg flex items-center justify-center mb-3">
<Users className="text-white w-5 h-5" />
</div>
<p className="text-sm text-gray-600">Special Doctors</p>
</div>
<h2 className="text-3xl font-bold text-gray-800">
{specialdoctorCount === 0 ? "..." : specialdoctorCount}
</h2>
</div> */}

{/* Active Patients */}
<div className="bg-[#F5F0FF] rounded-2xl p-5 shadow-md flex justify-between items-center">
<div>
<div className="w-10 h-10 bg-purple-500 rounded-lg flex items-center justify-center mb-3">
Expand All @@ -156,28 +166,26 @@ function AdminDashboard() {
<p className="text-sm text-gray-600">Active Patients</p>
</div>
<h2 className="text-3xl font-bold text-gray-800">
{patientCount === 0 ? "..." : patientCount}
{patientCount ?? "..."}
</h2>
</div>

{/* IoT Devices */}
<div className="bg-[#E9FBF6] rounded-2xl p-5 shadow-md flex justify-between items-center">
<div>
<div className="w-10 h-10 bg-teal-500 rounded-lg flex items-center justify-center mb-3">
<TrendingUp className="text-white w-5 h-5" />
</div>
<p className="text-sm text-gray-600">IOT Devices</p>
</div>
<h2 className="text-3xl font-bold text-gray-800">23</h2>
<h2 className="text-3xl font-bold text-gray-800">
{deviceCount ?? "..."}
</h2>
</div>

</div>
</div>

{/* === BOTTOM NAVIGATION === */}
<div className="px-6 mt-8">
<div className="bg-white rounded-3xl shadow-md p-2 flex gap-3 w-fit">

<button
onClick={() => setActiveTab("users")}
className={`px-4 py-2 rounded-3xl text-sm font-medium transition-all ${
Expand All @@ -194,23 +202,6 @@ function AdminDashboard() {
Doctor Management
</button>

{/*
<button
onClick={() => setActiveTab("logs")}
className={`px-4 py-2 rounded-3xl text-sm font-medium transition-all ${
activeTab === "logs"
? "text-white shadow-md"
: "text-gray-600 hover:bg-gray-100"
}`}
style={
activeTab === "logs"
? { background: "linear-gradient(45deg, #007CFC 0%, #11C2BA 100%)" }
: {}
}
>
Special Doctors
</button> */}

<button
onClick={() => setActiveTab("patients")}
className={`px-4 py-2 rounded-3xl text-sm font-medium transition-all ${
Expand Down Expand Up @@ -244,19 +235,19 @@ function AdminDashboard() {
</button>

<button
onClick={() => setActiveTab("booking")}
onClick={() => setActiveTab("accept")}
className={`px-4 py-2 rounded-3xl text-sm font-medium transition-all ${
activeTab === "booking"
activeTab === "accept"
? "text-white shadow-md"
: "text-gray-600 hover:bg-gray-100"
}`}
style={
activeTab === "booking"
activeTab === "accept"
? { background: "linear-gradient(45deg, #007CFC 0%, #11C2BA 100%)" }
: {}
}
>
Booking
Pending Doctors
</button>

<button
Expand All @@ -275,17 +266,49 @@ function AdminDashboard() {
Admin Management
</button>

<button
onClick={() => setActiveTab("security")}
className={`px-4 py-2 rounded-3xl text-sm font-medium transition-all ${
activeTab === "security"
? "text-white shadow-md"
: "text-gray-600 hover:bg-gray-100"
}`}
style={
activeTab === "security"
? { background: "linear-gradient(45deg, #007CFC 0%, #11C2BA 100%)" }
: {}
}
>
Security Logs
</button>

<button
onClick={() => setActiveTab("booking")}
className={`px-4 py-2 rounded-3xl text-sm font-medium transition-all ${
activeTab === "booking"
? "text-white shadow-md"
: "text-gray-600 hover:bg-gray-100"
}`}
style={
activeTab === "booking"
? { background: "linear-gradient(45deg, #007CFC 0%, #11C2BA 100%)" }
: {}
}
>
Booking
</button>
</div>
</div>

{/* === TAB CONTENT === */}
<div className="px-6 mt-6">
<div className="px-6 py-8">
{activeTab === "users" && <UserManagement />}
{activeTab === "accept" && <PendingDoctors />}
{activeTab === "patients" && <PatientManagement />}
{activeTab === "admins" && <AdminManagement />}
{activeTab === "iot" && <IotDevices />}

{activeTab === "accept" && <PendingDoctors />}
{activeTab === "analytics" && <Analytics />}
{activeTab === "admins" && <AdminManagement />}
{activeTab === "security" && <SecurityLogs />}
{activeTab === "booking" && <SpecialDoctorAdminPage />}
</div>
</div>
);
Expand Down
Loading
Loading