├── .gitignore ├── public ├── assets │ ├── logo.png │ ├── blood_group.svg │ ├── height.svg │ ├── weight.svg │ └── patient.svg ├── js │ ├── script.js │ ├── appointmentActions.js │ ├── verifyotp.js │ ├── login.js │ ├── signup.js │ ├── bookapp.js │ ├── editProfile.js │ └── dashboard.js ├── verifyotp.html ├── dashboard.php ├── login.html ├── ContactUs.html ├── signup.html ├── editProfile.php ├── bookappointment.php └── userProfile.php ├── composer.json ├── backend ├── user_logout.php ├── includes │ └── is_loggedin.php ├── doctorapi.php ├── database │ └── connectDB.php ├── approve_app.php ├── reject_app.php ├── mark_as_completed.php ├── get_user.php ├── ai_api.php ├── verify_email.php ├── user_login.php ├── verify_otp.php ├── profileapi.php ├── book_appointment.php ├── send_otp.php ├── emergencymail.php └── views │ ├── doctor_dashboard.php │ └── patient_dashboard.php ├── README.md ├── package.json └── eslint.config.mjs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | db_config.php 3 | vendor/ 4 | composer.lock 5 | backend/.env 6 | logs/ -------------------------------------------------------------------------------- /public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cmd-anurag/ca2-project/HEAD/public/assets/logo.png -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "phpmailer/phpmailer": "^6.9", 4 | "vlucas/phpdotenv": "^5.6" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /backend/user_logout.php: -------------------------------------------------------------------------------- 1 | true, "message" => "Logout successful"]); 7 | exit(); 8 | ?> -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Problem Statement 2 | "A healthcare assistant providing appointment booking and emergency alerts with an Android UI. It integrates notifications, background scheduling, and secure data storage for seamless patient care." 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@tailwindcss/cli": "^4.0.4", 4 | "tailwindcss": "^4.0.4" 5 | }, 6 | "devDependencies": { 7 | "@eslint/js": "^9.20.0", 8 | "eslint": "^9.20.1", 9 | "globals": "^15.15.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | 4 | 5 | /** @type {import('eslint').Linter.Config[]} */ 6 | export default [ 7 | {files: ["**/*.js"], languageOptions: {sourceType: "script"}}, 8 | {languageOptions: { globals: globals.browser }}, 9 | pluginJs.configs.recommended, 10 | ]; -------------------------------------------------------------------------------- /public/js/script.js: -------------------------------------------------------------------------------- 1 | const menuBtn = document.getElementById("menu-btn"); 2 | const closeBtn = document.getElementById("close-btn"); 3 | const mobileMenu = document.getElementById("mobile-menu"); 4 | 5 | menuBtn.addEventListener("click", () => { 6 | mobileMenu.classList.remove("-translate-y-full"); 7 | }); 8 | 9 | closeBtn.addEventListener("click", () => { 10 | mobileMenu.classList.add("-translate-y-full"); 11 | }); 12 | -------------------------------------------------------------------------------- /backend/includes/is_loggedin.php: -------------------------------------------------------------------------------- 1 | '; 4 | echo "

You are not logged in. You will be redirected to the login page in a few seconds.

"; 5 | echo ""; 10 | exit(); 11 | } 12 | -------------------------------------------------------------------------------- /backend/doctorapi.php: -------------------------------------------------------------------------------- 1 | prepare($query); 14 | 15 | if (!$statement) { 16 | echo json_encode(["success" => false, "message" => "Error updating profile"]); 17 | die(); 18 | } 19 | 20 | $statement->bind_param("sis", $specialization, $experience_years, $hospital_name); 21 | 22 | 23 | if ($statement->execute()) { 24 | echo json_encode(["success" =>true, "message" => "Profile Updated Successfully"]); 25 | } else { 26 | echo json_encode(["success" => false, "message" => "Error updating profile"]); 27 | } 28 | $statement->close(); 29 | 30 | ?> -------------------------------------------------------------------------------- /backend/database/connectDB.php: -------------------------------------------------------------------------------- 1 | connect_error) { 15 | http_response_code(500); 16 | echo json_encode(["success" => false, "message" => "DB connection failed"]); 17 | die(); 18 | } 19 | else { 20 | // Write to custom log file 21 | $log_file = __DIR__ . '/../logs/dblog.log'; 22 | $log_message = sprintf( 23 | "[%s] Database Connection: Success\nHost: %s\nDatabase: %s\nUser: %s\n\n", 24 | date('Y-m-d H:i:s'), 25 | $config['DB_HOST'], 26 | $config['DB_NAME'], 27 | $config['DB_USERNAME'] 28 | ); 29 | error_log($log_message, 3, $log_file); 30 | } 31 | ?> 32 | -------------------------------------------------------------------------------- /backend/approve_app.php: -------------------------------------------------------------------------------- 1 | false, "message" => "Appointment ID is required."]); 8 | exit(); 9 | } 10 | 11 | if (!filter_var($appointment_id, FILTER_VALIDATE_INT)) { 12 | echo json_encode(["success" => false, "message" => "Invalid appointment ID. It must be an integer."]); 13 | exit(); 14 | } 15 | 16 | $query = "UPDATE appointments SET status='approved' WHERE id=?"; 17 | $stmt = $conn->prepare($query); 18 | 19 | $stmt->bind_param("i", $appointment_id); 20 | 21 | if (!$stmt) { 22 | echo json_encode(["success" => false, "message" => "Error approving appointment"]); 23 | die(); 24 | } 25 | 26 | if ($stmt->execute()) { 27 | echo json_encode(["success" => true, "message" => "Appointment approved"]); 28 | } else { 29 | echo json_encode(["success" => false, "message" => "Error approving appointment"]); 30 | } 31 | 32 | $stmt->close(); 33 | $conn->close(); 34 | 35 | ?> -------------------------------------------------------------------------------- /backend/reject_app.php: -------------------------------------------------------------------------------- 1 | false, "message" => "Appointment ID is required."]); 8 | exit(); 9 | } 10 | 11 | if (!filter_var($appointment_id, FILTER_VALIDATE_INT)) { 12 | echo json_encode(["success" => false, "message" => "Invalid appointment ID. It must be an integer."]); 13 | exit(); 14 | } 15 | 16 | $query = "UPDATE appointments SET STATUS ='rejected' WHERE id=?"; 17 | $stmt = $conn->prepare($query); 18 | 19 | $stmt->bind_param("i", $appointment_id); 20 | 21 | 22 | if (!$stmt) { 23 | echo json_encode(["success" => false, "message" => "Error in appointment rejection"]); 24 | die(); 25 | } 26 | 27 | if ($stmt->execute()) { 28 | echo json_encode(["success" => true, "message" => "Appointment rejected"]); 29 | } else { 30 | echo json_encode(["success" => false, "message" => "Error in appointment rejection"]); 31 | } 32 | 33 | 34 | $stmt->close(); 35 | $conn->close(); 36 | 37 | ?> -------------------------------------------------------------------------------- /backend/mark_as_completed.php: -------------------------------------------------------------------------------- 1 | false, "message" => "Appointment ID is required."]); 12 | exit(); 13 | } 14 | 15 | if (!filter_var($appointment_id, FILTER_VALIDATE_INT)) { 16 | echo json_encode(["success" => false, "message" => "Invalid appointment ID. It must be an integer."]); 17 | exit(); 18 | } 19 | 20 | $query = "UPDATE appointments SET status = 'completed' WHERE id = ?"; 21 | $stmt = $conn->prepare($query); 22 | 23 | 24 | if (!$stmt) { 25 | echo json_encode(["success" => false, "message" => "Error preparing the SQL query."]); 26 | exit(); 27 | } 28 | 29 | $stmt->bind_param("i", $appointment_id); 30 | 31 | if ($stmt->execute()) { 32 | echo json_encode(["success" => true, "message" => "Appointment marked as complete successfully."]); 33 | } else { 34 | echo json_encode(["success" => false, "message" => "Error executing the SQL query."]); 35 | } 36 | 37 | 38 | $stmt->close(); 39 | $conn->close(); 40 | 41 | ?> -------------------------------------------------------------------------------- /backend/get_user.php: -------------------------------------------------------------------------------- 1 | false, "message" => "Invalid email format"]; 8 | } 9 | 10 | require __DIR__ . "/database/connectDB.php"; 11 | 12 | $query = "SELECT u.name, u.phone, u.address, p.date_of_birth, p.gender, p.medical_history, p.height, p.weight, p.blood_type, p.emergency_contact 13 | FROM users u 14 | LEFT JOIN patients p ON p.id = u.id 15 | WHERE u.email = ?"; 16 | 17 | $stmt = $conn->prepare($query); 18 | 19 | if(!$stmt) { 20 | return ["success" => false, "message" => "Internal Server Error: " . $conn->error]; 21 | } 22 | 23 | $stmt->bind_param('s', $email); 24 | 25 | if(!$stmt->execute()) { 26 | return ["success" => false, "message" => "Internal Server Error: " . $stmt->error]; 27 | } 28 | 29 | $result = $stmt->get_result()->fetch_assoc(); 30 | $stmt->close(); 31 | 32 | return ["success" => true, "data" => $result]; 33 | } 34 | 35 | // Handle direct API calls via POST 36 | if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST["email"])) { 37 | $email = $_POST["email"]; 38 | $result = getUserData($email); 39 | 40 | if (!$result["success"]) { 41 | http_response_code(500); 42 | } 43 | 44 | echo json_encode($result); 45 | } 46 | ?> -------------------------------------------------------------------------------- /backend/ai_api.php: -------------------------------------------------------------------------------- 1 | false, "message" => "Bad Request Method"]); 6 | die(); 7 | } 8 | if(!isset($_POST["userinput"])) { 9 | http_response_code(400); 10 | echo json_encode(["sucess" => false, "message" => "missing body"]); 11 | die(); 12 | } 13 | 14 | // LOAD FROM .ENV 15 | require '../vendor/autoload.php'; 16 | $dotenv = Dotenv\Dotenv::createImmutable(__DIR__); 17 | $dotenv->load(); 18 | $apikey = $_ENV["API_KEY"]; 19 | 20 | 21 | $userInput = $_POST["userinput"]; 22 | 23 | 24 | // Gemini api's json structure is nasty bruh 25 | $requestBody = json_encode([ 26 | "contents" => [ 27 | [ 28 | "role" => "user", 29 | "parts" => [["text" => "You are a helpful healthcare assistant. Avoid very long responses. Do not format your responses and answer in plaintext."]] 30 | ], 31 | [ 32 | "role" => "user", 33 | "parts" => [["text" => $userInput]] 34 | ] 35 | ] 36 | ]); 37 | 38 | 39 | $url = "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$apikey"; 40 | $ch = curl_init(); 41 | 42 | curl_setopt($ch, CURLOPT_URL, $url); 43 | curl_setopt($ch, CURLOPT_POST, 1); 44 | curl_setopt($ch, CURLOPT_POSTFIELDS, $requestBody); 45 | curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]); 46 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 47 | 48 | $response = curl_exec($ch); 49 | curl_close($ch); 50 | 51 | 52 | echo $response; -------------------------------------------------------------------------------- /backend/verify_email.php: -------------------------------------------------------------------------------- 1 | false, "message" => "DB Error"]); 17 | die(); 18 | } 19 | 20 | if (!isset($_POST["email"])) { 21 | http_response_code(400); 22 | echo json_encode(["success" => false, "message" => "No email provided."]); 23 | die(); 24 | } 25 | 26 | try { 27 | // Sanitize the email input 28 | $email = filter_var($_POST["email"], FILTER_SANITIZE_EMAIL); 29 | 30 | $stmt = $conn->prepare("SELECT * FROM users WHERE email = ?"); 31 | if (!$stmt) { 32 | http_response_code(500); 33 | echo json_encode(["success" => false, "message" => "Database error: " . $conn->error]); 34 | die(); 35 | } 36 | 37 | $stmt->bind_param("s", $email); 38 | $stmt->execute(); 39 | $result = $stmt->get_result(); 40 | 41 | if ($result && $result->num_rows > 0) { 42 | echo json_encode(["success" => false, "message" => "Email already exists"]); 43 | } else { 44 | echo json_encode(["success" => true, "message" => "Email is available"]); 45 | } 46 | 47 | $stmt->close(); 48 | } catch(Exception $error) { 49 | http_response_code(500); 50 | echo json_encode(["success" => false, "message" => $error->getMessage()]); 51 | } 52 | ?> 53 | -------------------------------------------------------------------------------- /public/assets/blood_group.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | 12 | 13 | 21 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /backend/user_login.php: -------------------------------------------------------------------------------- 1 | false, "message" => "Bad Request Method"]); 13 | die(); 14 | } 15 | 16 | if (!isset($_POST["email"]) || !isset($_POST["password"])) { 17 | http_response_code(400); 18 | echo json_encode(["success" => false, "message" => "Missing credentials"]); 19 | die(); 20 | } 21 | 22 | require "./database/connectDB.php"; 23 | 24 | $email = filter_var($_POST["email"], FILTER_SANITIZE_EMAIL); 25 | 26 | $stmt = $conn->prepare("SELECT * FROM users WHERE email = ?"); 27 | if (!$stmt) { 28 | http_response_code(500); 29 | echo json_encode(["success" => false, "message" => "Database error: " . $conn->error]); 30 | die(); 31 | } 32 | 33 | $stmt->bind_param("s", $email); 34 | $stmt->execute(); 35 | $result = $stmt->get_result(); 36 | 37 | if (!$result) { 38 | http_response_code(500); 39 | echo json_encode(["success" => false, "message" => "Database query failed"]); 40 | die(); 41 | } 42 | 43 | if ($result->num_rows === 0) { 44 | http_response_code(400); 45 | echo json_encode(["success" => false, "message" => "User does not exist."]); 46 | die(); 47 | } 48 | 49 | $row = $result->fetch_assoc(); 50 | $entered_password = $_POST["password"]; 51 | $hashed_password = $row["password"]; 52 | 53 | if (password_verify($entered_password, $hashed_password)) { 54 | $_SESSION["user"] = ["email" => $email]; 55 | echo json_encode(["success" => true, "message" => "Login Successful"]); 56 | } else { 57 | http_response_code(401); 58 | echo json_encode(["success" => false, "message" => "Incorrect Password"]); 59 | } 60 | 61 | $stmt->close(); 62 | } catch (Exception $e) { 63 | http_response_code(500); 64 | echo json_encode(["success" => false, "message" => $e->getMessage()]); 65 | } 66 | ?> 67 | -------------------------------------------------------------------------------- /public/js/appointmentActions.js: -------------------------------------------------------------------------------- 1 | document.querySelectorAll(".approve-btn").forEach((button) => { 2 | button.addEventListener("click", ()=> { 3 | if(button.hasAttribute('disabled')) return; 4 | 5 | const appointmentId = button.getAttribute('data-appointment-id'); 6 | updateAppointmentStatus(appointmentId, 'approved'); 7 | }) 8 | }) 9 | 10 | document.querySelectorAll(".reject-btn").forEach((button) => { 11 | button.addEventListener("click", () => { 12 | if(button.hasAttribute('disabled')) return; 13 | 14 | const appointmentId = button.getAttribute('data-appointment-id'); 15 | updateAppointmentStatus(appointmentId, 'rejected'); 16 | }) 17 | }) 18 | 19 | document.querySelectorAll(".complete-btn").forEach((button) => { 20 | button.addEventListener('click', () => { 21 | if(button.hasAttribute('disabled')) return; 22 | 23 | const appointmentId = button.getAttribute('data-appointment-id'); 24 | updateAppointmentStatus(appointmentId, 'completed'); 25 | }) 26 | }) 27 | 28 | let loader = document.getElementById('loader-element'); 29 | 30 | const updateAppointmentStatus = async (id, status) => { 31 | 32 | let url = ""; 33 | 34 | if(status === "approved") { 35 | url = "http://localhost/ca2-project/backend/approve_app.php"; 36 | } 37 | else if(status === "rejected") { 38 | url = "http://localhost/ca2-project/backend/reject_app.php"; 39 | } 40 | else if(status === "completed") { 41 | url = "http://localhost/ca2-project/backend/mark_as_completed.php"; 42 | } 43 | 44 | const formData = new FormData(); 45 | formData.append("appointment_id", id); 46 | 47 | try { 48 | loader.classList.remove('hidden'); 49 | 50 | let response = await fetch(url, { 51 | method: 'POST', 52 | credentials: 'include', 53 | body: formData 54 | }); 55 | 56 | let result = await response.json(); 57 | 58 | if(result.success) { 59 | window.location.reload(); 60 | } 61 | else { 62 | alert(result.message); 63 | } 64 | } 65 | catch(e) { 66 | console.log(e); 67 | } 68 | finally { 69 | loader.classList.add('hidden'); 70 | } 71 | 72 | } -------------------------------------------------------------------------------- /public/js/verifyotp.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | document.getElementById("otpinp1").focus(); 3 | } 4 | 5 | const form = document.getElementById("otp-form"); 6 | 7 | 8 | // Event listener for filling 9 | form.addEventListener("input", function(event) { 10 | const otpinputs = [...form.getElementsByClassName("otp-input")]; 11 | const currentInput = event.target; 12 | const currentIndex = otpinputs.indexOf(currentInput); 13 | 14 | if(currentIndex !== -1 && currentInput.value.length > 0) { 15 | const nextInput = otpinputs[currentIndex + 1]; 16 | if(nextInput) { 17 | nextInput.focus(); 18 | } 19 | } 20 | }) 21 | 22 | // Event listener for deleting 23 | form.addEventListener("keydown", function(event) { 24 | if(event.key == "Backspace") { 25 | const otpinputs = [...form.getElementsByClassName("otp-input")]; 26 | const currentInput = event.target; 27 | const currentIndex = otpinputs.indexOf(currentInput); 28 | 29 | if(currentIndex > 0 && currentInput.value == "") { 30 | otpinputs[currentIndex-1].focus(); 31 | } 32 | } 33 | }) 34 | 35 | let verifybtn = document.getElementById("verifybtn"); 36 | verifybtn.addEventListener("click", async () => { 37 | 38 | const otpinputs = [...form.getElementsByClassName("otp-input")]; 39 | 40 | for (let i = 0; i < otpinputs.length; i++) { 41 | const element = otpinputs[i]; 42 | if (element.value === "") { 43 | alert("Incomplete OTP"); 44 | return; 45 | } 46 | } 47 | 48 | const formdata = new FormData(); 49 | 50 | for (let i = 0; i < otpinputs.length; ++i) { 51 | const element = otpinputs[i]; 52 | 53 | formdata.append(i , element.value); 54 | } 55 | 56 | let response; 57 | try { 58 | response = await fetch("http://localhost/ca2-project/backend/verify_otp.php", { 59 | method: "POST", 60 | credentials: "include", 61 | body: formdata 62 | }) 63 | 64 | const result = await response.json(); 65 | 66 | if(result.success) { 67 | // redirect to dashboard 68 | setTimeout(() => { 69 | window.location.href = "dashboard.php"; 70 | }, 3000); 71 | } 72 | else { 73 | alert(result.message); 74 | } 75 | } 76 | catch(exception) { 77 | console.log(exception); 78 | } 79 | 80 | }) -------------------------------------------------------------------------------- /public/verifyotp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | OTP Verification | SwiftHealth 7 | 8 | 9 | 10 | 11 |
12 |

OTP Verification

13 |

A verification code has been sent to your email. Please enter the 6-digit OTP below.

14 | 15 |
16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 |
28 | 31 |
32 | 33 | 34 |
35 |

36 | Didn't receive the code? Resend OTP 37 |

38 |
39 |
40 |
41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/js/login.js: -------------------------------------------------------------------------------- 1 | let loginbutton = document.getElementById("loginbutton"); 2 | 3 | let emailField = document.getElementById("emailinput"); 4 | let passwordField = document.getElementById("passwordinput"); 5 | 6 | let loader = document.getElementById("loader-element"); 7 | 8 | let emailError = document.getElementById("emailError"); 9 | let passwordError = document.getElementById("passwordError") 10 | 11 | // a basic email vaidation function 12 | function isValidEmail(email) { 13 | const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 14 | return emailPattern.test(email); 15 | } 16 | 17 | loginbutton.addEventListener("click", async () => { 18 | 19 | if(emailField.value === "" || !isValidEmail(emailField.value)) { 20 | emailError.classList.add("max-h-20"); 21 | emailError.classList.add("mt-1"); 22 | return; 23 | } 24 | 25 | if(passwordField.value === "") { 26 | passwordError.classList.add('max-h-20'); 27 | passwordError.classList.add('mt-1'); 28 | return; 29 | } 30 | // validated 31 | loader.classList.remove("hidden"); 32 | const formdata = new FormData(); 33 | 34 | formdata.append("email", emailField.value); 35 | formdata.append("password", passwordField.value); 36 | 37 | let response; 38 | try { 39 | response = await fetch("http://localhost/ca2-project/backend/user_login.php", { 40 | method: "POST", 41 | credentials: "include", 42 | body: formdata 43 | }) 44 | 45 | let result = await response.json(); 46 | 47 | if(result.success) { 48 | window.location.href = "dashboard.php"; 49 | // loader.classList.add('hidden'); 50 | } 51 | else { 52 | loader.classList.add('hidden'); 53 | alert(result.message); 54 | } 55 | } 56 | catch(exception) { 57 | loader.classList.add('hidden'); 58 | console.log(exception); 59 | } 60 | 61 | }) 62 | 63 | emailField.addEventListener('focus', () => { 64 | emailError.classList.remove("max-h-20"); 65 | emailError.classList.remove("mt-1"); 66 | }) 67 | 68 | passwordField.addEventListener('focus', () => { 69 | passwordError.classList.remove('max-h-20'); 70 | passwordError.classList.remove('mt-1'); 71 | }) 72 | 73 | document.getElementById("togglePassword").addEventListener("click", function () { 74 | const passwordInput = document.getElementById("passwordinput"); 75 | const eyeIcon = document.getElementById("eyeIcon"); 76 | 77 | if (passwordInput.type === "password") { 78 | passwordInput.type = "text"; 79 | eyeIcon.classList.remove("fa-eye"); 80 | eyeIcon.classList.add("fa-eye-slash"); 81 | } else { 82 | passwordInput.type = "password"; 83 | eyeIcon.classList.remove("fa-eye-slash"); 84 | eyeIcon.classList.add("fa-eye"); 85 | } 86 | }); 87 | -------------------------------------------------------------------------------- /public/dashboard.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dashboard | SwiftHealth 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | prepare("SELECT * FROM users WHERE email = ?"); 27 | if (!$stmt) { 28 | echo json_encode([ 29 | "success" => false, 30 | "message" => "DB Error: " . $conn->error 31 | ]); 32 | exit(); 33 | } 34 | $stmt->bind_param("s", $userEmail); 35 | if (!$stmt->execute()) { 36 | echo json_encode([ 37 | "success" => false, 38 | "message" => "Error fetching details: " . $stmt->error 39 | ]); 40 | exit(); 41 | } 42 | $result = $stmt->get_result()->fetch_assoc(); 43 | $stmt->close(); 44 | 45 | if (!isset($result['name']) || !isset($result['role'])) { 46 | echo "

Unable to fetch user details.

"; 47 | die(); 48 | } 49 | $user_id = $result['id']; 50 | $user_name = $result['name']; 51 | $user_role = $result['role']; 52 | 53 | // saving the id in session for future use. 54 | $_SESSION['user']['id'] = $user_id; 55 | $_SESSION['user']['role'] = $user_role; 56 | $_SESSION['user']['name'] = $user_name; 57 | ?> 58 | 59 | 60 |
61 |
62 |
63 | SwiftHealth 64 |
65 | 72 |
73 | 74 | 75 |
76 |
77 |
78 | 79 | 80 |
81 | 82 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /backend/verify_otp.php: -------------------------------------------------------------------------------- 1 | false, 24 | "message" => "OTP was not sent successfully. Please try again later." 25 | ]); 26 | die(); 27 | } 28 | 29 | if ($_SESSION['creds']['code'] != $received_otp) { 30 | echo json_encode([ 31 | "success" => false, 32 | "message" => "Incorrect OTP." 33 | ]); 34 | die(); 35 | } 36 | 37 | 38 | $config = require __DIR__ . '/../config/db_config.php'; 39 | $dsn = sprintf( 40 | 'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', 41 | $config['DB_HOST'], 42 | $config['DB_PORT'], 43 | $config['DB_NAME'] 44 | ); 45 | $pdo = new PDO($dsn, $config['DB_USERNAME'], $config['DB_PASSWORD'], [ 46 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 47 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 48 | ]); 49 | 50 | $name = $_SESSION['creds']['name']; 51 | $email = $_SESSION['creds']['email']; 52 | $password = $_SESSION['creds']['password']; 53 | 54 | // -------------------------------------------- TRANSACTION BEGINS --------------------------------------------------- 55 | $pdo->beginTransaction(); 56 | 57 | $insertQuery = "INSERT INTO users (name, email, password) VALUES (?, ?, ?)"; 58 | $stmt = $pdo->prepare($insertQuery); 59 | if (!$stmt) { 60 | echo json_encode([ 61 | "success" => false, 62 | "message" => "Error preparing user statement." 63 | ]); 64 | $pdo->rollBack(); 65 | die(); 66 | } 67 | if (!$stmt->execute([$name, $email, $password])) { 68 | echo json_encode([ 69 | "success" => false, 70 | "message" => "Error creating user." 71 | ]); 72 | $pdo->rollBack(); 73 | die(); 74 | } 75 | $user_id = $pdo->lastInsertId(); 76 | 77 | $stmt2 = $pdo->prepare("INSERT INTO patients (id) VALUES (?)"); 78 | if (!$stmt2) { 79 | echo json_encode([ 80 | "success" => false, 81 | "message" => "Error preparing patient statement." 82 | ]); 83 | $pdo->rollBack(); 84 | die(); 85 | } 86 | if (!$stmt2->execute([$user_id])) { 87 | echo json_encode([ 88 | "success" => false, 89 | "message" => "Error creating patient." 90 | ]); 91 | $pdo->rollBack(); 92 | die(); 93 | } 94 | 95 | unset($_SESSION['creds']); 96 | $_SESSION['user'] = ["email" => $email]; 97 | http_response_code(201); 98 | echo json_encode([ 99 | "success" => true, 100 | "message" => "Successfully registered the user." 101 | ]); 102 | $pdo->commit(); 103 | // -------------------------------------- TRANSACTION ENDS ----------------------------------------------------------- 104 | 105 | } catch (Exception $e) { 106 | http_response_code(500); 107 | echo json_encode([ 108 | "success" => false, 109 | "message" => $e->getMessage(), 110 | // "file" => $e->getFile(), 111 | // "line" => $e->getLine(), 112 | // "trace" => $e->getTraceAsString() 113 | // debug 114 | ]); 115 | } 116 | -------------------------------------------------------------------------------- /public/js/signup.js: -------------------------------------------------------------------------------- 1 | let signupbtn = document.getElementById("signupbtn"); 2 | 3 | const nameField = document.getElementById("name-input"); 4 | const emailField = document.getElementById("email-input"); 5 | const passwordField = document.getElementById("signupPassword"); 6 | 7 | const invalidName = document.getElementById("invalidname"); 8 | const invalidEmail = document.getElementById("invalidemail"); 9 | const invalidPassword = document.getElementById("invalidpass"); 10 | const registeredEmail = document.getElementById("registered-email"); 11 | 12 | 13 | let loader = document.getElementById("loader-element"); 14 | 15 | function isValidEmail(email) { 16 | const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 17 | return emailPattern.test(email); 18 | } 19 | 20 | // SIGNUP BUTTON CLICK LISTENER 21 | signupbtn.addEventListener("click", async () => { 22 | if (!nameField.value) { 23 | invalidName.classList.remove("hidden"); 24 | return; 25 | } 26 | if (!emailField.value || !isValidEmail(emailField.value)) { 27 | invalidEmail.classList.remove("hidden"); 28 | return; 29 | } 30 | 31 | if (!passwordField.value || passwordField.value.length < 8) { 32 | invalidPassword.classList.remove("hidden"); 33 | return; 34 | } 35 | 36 | loader.classList.remove("hidden"); 37 | 38 | // check if the email already exists 39 | 40 | let emailres = false; 41 | 42 | try { 43 | const formdata = new FormData(); 44 | formdata.append("email", emailField.value); 45 | emailres = await fetch( 46 | "http://localhost/ca2-project/backend/verify_email.php", 47 | { 48 | method: "POST", 49 | credentials: "include", 50 | body: formdata, 51 | } 52 | ); 53 | 54 | const result = await emailres.json(); 55 | 56 | if (!result.success) { 57 | registeredEmail.classList.remove('hidden'); 58 | loader.classList.add("hidden"); 59 | return; 60 | } 61 | } catch (error) { 62 | loader.classList.add("hidden"); 63 | console.log(error); 64 | } 65 | 66 | let response; 67 | try { 68 | const formdata = new FormData(); 69 | formdata.append("name", nameField.value); 70 | formdata.append("email", emailField.value); 71 | formdata.append("password", passwordField.value); 72 | 73 | response = await fetch( 74 | "http://localhost/ca2-project/backend/send_otp.php", 75 | { 76 | method: "POST", 77 | credentials: "include", 78 | body: formdata, 79 | } 80 | ); 81 | const result = await response.json(); 82 | 83 | if (result.success) { 84 | console.log("sent otp"); 85 | window.location.href = result.redirect; 86 | } else { 87 | console.error("some error occured"); 88 | } 89 | } catch (error) { 90 | console.log(error); 91 | } finally { 92 | loader.classList.add("hidden"); 93 | } 94 | }); 95 | 96 | 97 | nameField.addEventListener("focus", () => { 98 | invalidName.classList.add("hidden"); 99 | }); 100 | emailField.addEventListener("focus", () => { 101 | invalidEmail.classList.add('hidden'); 102 | registeredEmail.classList.add('hidden'); 103 | }); 104 | passwordField.addEventListener("focus", () => { 105 | invalidPassword.classList.add('hidden'); 106 | }); 107 | 108 | 109 | const toggleBtns = document.querySelectorAll(".password-toggle"); 110 | toggleBtns.forEach((btn) => { 111 | btn.addEventListener("click", function () { 112 | const input = btn.previousElementSibling; 113 | const icon = btn.querySelector("i"); 114 | 115 | if (input.type === "password") { 116 | input.type = "text"; 117 | icon.classList.remove("fa-eye"); 118 | icon.classList.add("fa-eye-slash"); 119 | } else { 120 | input.type = "password"; 121 | icon.classList.remove("fa-eye-slash"); 122 | icon.classList.add("fa-eye"); 123 | } 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /backend/profileapi.php: -------------------------------------------------------------------------------- 1 | false, "message" => "All fields are required"]); 23 | exit(); 24 | } 25 | 26 | // Validation 27 | if (!preg_match("/^\d{4}-\d{2}-\d{2}$/", $dob)) { 28 | echo json_encode(["success" => false, "message" => "Invalid date of birth format. Please use YYYY-MM-DD."]); 29 | exit(); 30 | } 31 | 32 | if (!preg_match("/^\d{10}$/", $phone)) { 33 | echo json_encode(["success" => false, "message" => "Invalid phone number. Please use a 10-digit number."]); 34 | exit(); 35 | } 36 | 37 | if (!is_numeric($height)) { 38 | echo json_encode(["success" => false, "message" => "Height must be numeric value."]); 39 | exit(); 40 | } 41 | 42 | if (!is_numeric($weight)) { 43 | echo json_encode(["success" => false, "message" => "Weight must be numeric value."]); 44 | exit(); 45 | } 46 | 47 | 48 | $checkQuery = "SELECT id FROM patients WHERE id = ?"; 49 | $checkStmt = $conn->prepare($checkQuery); 50 | if (!$checkStmt) { 51 | echo json_encode(["success" => false, "message" => "Database error: " . $conn->error]); 52 | exit(); 53 | } 54 | 55 | $checkStmt->bind_param("i", $id); 56 | $checkStmt->execute(); 57 | $result = $checkStmt->get_result(); 58 | $patientExists = $result->num_rows > 0; 59 | $checkStmt->close(); 60 | 61 | 62 | if (!$patientExists) { 63 | echo json_encode(["success" => false, "message" => "Patient record not found. Please contact support."]); 64 | exit(); 65 | } 66 | 67 | // phone and address in users table 68 | $usersQuery = "UPDATE users SET phone = ?, address = ? WHERE id = ?"; 69 | $usersStmt = $conn->prepare($usersQuery); 70 | 71 | if (!$usersStmt) { 72 | echo json_encode(["success" => false, "message" => "Error updating user profile: " . $conn->error]); 73 | exit(); 74 | } 75 | 76 | $usersStmt->bind_param("ssi", $phone, $address, $id); 77 | $usersSuccess = $usersStmt->execute(); 78 | $usersStmt->close(); 79 | 80 | if (!$usersSuccess) { 81 | echo json_encode(["success" => false, "message" => "Error updating contact information"]); 82 | exit(); 83 | } 84 | 85 | $patientsQuery = "UPDATE patients SET 86 | date_of_birth = ?, 87 | gender = ?, 88 | medical_history = ?, 89 | blood_type = ?, 90 | height = ?, 91 | weight = ?, 92 | emergency_contact = ? 93 | WHERE id = ?"; 94 | 95 | $patientsStmt = $conn->prepare($patientsQuery); 96 | if (!$patientsStmt) { 97 | echo json_encode(["success" => false, "message" => "Error updating medical information: " . $conn->error]); 98 | exit(); 99 | } 100 | 101 | $patientsStmt->bind_param("sssssssi", $dob, $gender, $medical_history, $blood_type, $height, $weight, $emergency_contact, $id); 102 | $patientsSuccess = $patientsStmt->execute(); 103 | $patientsStmt->close(); 104 | 105 | if ($patientsSuccess) { 106 | echo json_encode(["success" => true, "message" => "Profile Updated Successfully"]); 107 | } else { 108 | echo json_encode(["success" => false, "message" => "Error updating medical information: " . $conn->error]); 109 | } 110 | 111 | ?> 112 | -------------------------------------------------------------------------------- /public/js/bookapp.js: -------------------------------------------------------------------------------- 1 | const modalBackdrop = document.getElementById("modal-backdrop"); 2 | const modalContainer = document.getElementById("modal-container"); 3 | const modalTitle = document.getElementById("modal-title"); 4 | const modalMessage = document.getElementById("modal-message"); 5 | const successIcon = document.getElementById("success-icon"); 6 | const errorIcon = document.getElementById("error-icon"); 7 | const closeModal = document.getElementById("close-modal"); 8 | const modalCloseBtn = document.getElementById("modal-close-btn"); 9 | const loader = document.getElementById("loader-element"); 10 | 11 | //show modal 12 | function showModal(success, message) { 13 | loader.classList.add("hidden"); 14 | modalMessage.textContent = message; 15 | 16 | if (success) { 17 | modalTitle.textContent = "Appointment Booked"; 18 | successIcon.classList.remove("hidden"); 19 | errorIcon.classList.add("hidden"); 20 | modalTitle.classList.remove("text-red-600"); 21 | modalTitle.classList.add("text-green-600"); 22 | } else { 23 | modalTitle.textContent = "Booking Failed"; 24 | errorIcon.classList.remove("hidden"); 25 | successIcon.classList.add("hidden"); 26 | modalTitle.classList.remove("text-green-600"); 27 | modalTitle.classList.add("text-red-600"); 28 | } 29 | 30 | modalBackdrop.classList.remove("hidden"); 31 | setTimeout(() => { 32 | modalBackdrop.classList.remove("opacity-0"); 33 | modalContainer.classList.remove("scale-95"); 34 | modalContainer.classList.add("scale-100"); 35 | }, 10); 36 | } 37 | 38 | //hide modal 39 | function hideModal() { 40 | modalBackdrop.classList.add("opacity-0"); 41 | modalContainer.classList.remove("scale-100"); 42 | modalContainer.classList.add("scale-95"); 43 | setTimeout(() => { 44 | modalBackdrop.classList.add("hidden"); 45 | }, 300); 46 | } 47 | 48 | // Event listeners to close modal 49 | closeModal.addEventListener("click", hideModal); 50 | modalCloseBtn.addEventListener("click", hideModal); 51 | modalBackdrop.addEventListener("click", (e) => { 52 | if (e.target === modalBackdrop) { 53 | hideModal(); 54 | } 55 | }); 56 | 57 | let dateInput = document.getElementById("appdate"); 58 | let specializationInput = document.getElementById("specializationInput"); 59 | let remarksInput = document.getElementById("remarksInput"); 60 | 61 | let bookButton = document.getElementById("book-button"); 62 | 63 | bookButton.addEventListener("click", async () => { 64 | let dateValue = dateInput.value; 65 | let specializationValue = specializationInput.value; 66 | let remarksValue = remarksInput.value; 67 | 68 | if (!dateValue) { 69 | dateInput.classList.add("ring-2", "ring-red-500"); 70 | return; 71 | } 72 | 73 | if (specializationValue === "Select a specialization") { 74 | specializationInput.classList.add("ring-2", "ring-red-500"); 75 | return; 76 | } 77 | loader.classList.remove("hidden"); 78 | 79 | const formdata = new FormData(); 80 | formdata.append("date", dateValue); 81 | formdata.append("specialization", specializationValue); 82 | formdata.append("remarks", remarksValue); 83 | 84 | try { 85 | const res = await fetch( 86 | "http://localhost/ca2-project/backend/book_appointment.php", 87 | { 88 | method: "POST", 89 | credentials: "include", 90 | body: formdata, 91 | } 92 | ); 93 | 94 | const result = await res.json(); 95 | showModal(result.success, result.message); 96 | 97 | } catch (error) { 98 | showModal(false, "An unexpected error occurred. Please try again."); 99 | console.error("Error:", error); 100 | } 101 | }); 102 | 103 | dateInput.addEventListener("focus", () => { 104 | dateInput.classList.remove("ring-2"); 105 | }); 106 | 107 | specializationInput.addEventListener("focus", () => { 108 | specializationInput.classList.remove("ring-2"); 109 | }); 110 | -------------------------------------------------------------------------------- /public/assets/height.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 35 | 37 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /public/js/editProfile.js: -------------------------------------------------------------------------------- 1 | const profileForm = document.getElementById("profileForm"); 2 | const statusMessage = document.getElementById("statusMessage"); 3 | 4 | 5 | 6 | function isValidEmail(email) { 7 | const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 8 | return emailPattern.test(email); 9 | } 10 | 11 | profileForm.addEventListener("submit", function (e) { 12 | e.preventDefault(); 13 | 14 | // phone number validation 15 | const phoneInput = document.getElementById("phone"); 16 | // regex just looks ugly oof 17 | if (!/^\d{10}$/.test(phoneInput.value)) { 18 | statusMessage.innerHTML = 19 | ' Phone number must be 10 digits'; 20 | statusMessage.classList.remove( 21 | "hidden", 22 | "bg-green-100", 23 | "text-green-700", 24 | "border-green-500" 25 | ); 26 | statusMessage.classList.add("bg-red-100", "text-red-700", "border-red-500"); 27 | statusMessage.scrollIntoView({ behavior: "smooth", block: "center" }); 28 | return; 29 | } 30 | 31 | // Emergency contact email validation 32 | const emergencyContactInput = document.getElementById("emergency_contact"); 33 | if (!isValidEmail(emergencyContactInput.value)) { 34 | statusMessage.innerHTML = 35 | ' Emergency contact must be a valid email address'; 36 | statusMessage.classList.remove( 37 | "hidden", 38 | "bg-green-100", 39 | "text-green-700", 40 | "border-green-500" 41 | ); 42 | statusMessage.classList.add("bg-red-100", "text-red-700", "border-red-500"); 43 | statusMessage.scrollIntoView({ behavior: "smooth", block: "center" }); 44 | return; 45 | } 46 | 47 | // loading state 48 | const submitButton = profileForm.querySelector('button[type="submit"]'); 49 | const originalButtonText = submitButton.innerHTML; 50 | submitButton.innerHTML = 51 | ' Saving...'; 52 | submitButton.disabled = true; 53 | 54 | // form data 55 | const formData = new FormData(profileForm); 56 | 57 | // submit using fetch 58 | fetch("../backend/profileapi.php", { 59 | method: "POST", 60 | body: formData, 61 | }) 62 | .then((response) => response.json()) 63 | .then((data) => { 64 | statusMessage.innerHTML = data.success 65 | ? ' ' + data.message 66 | : ' ' + data.message; 67 | 68 | statusMessage.classList.remove( 69 | "hidden", 70 | "bg-red-100", 71 | "text-red-700", 72 | "bg-green-100", 73 | "text-green-700", 74 | "border-red-500", 75 | "border-green-500" 76 | ); 77 | 78 | if (data.success) { 79 | statusMessage.classList.add( 80 | "bg-green-100", 81 | "text-green-700", 82 | "border-green-500" 83 | ); 84 | } else { 85 | statusMessage.classList.add( 86 | "bg-red-100", 87 | "text-red-700", 88 | "border-red-500" 89 | ); 90 | } 91 | 92 | submitButton.innerHTML = originalButtonText; 93 | submitButton.disabled = false; 94 | 95 | 96 | statusMessage.scrollIntoView({ behavior: "smooth", block: "center" }); 97 | 98 | if (data.success) { 99 | setTimeout(() => { 100 | statusMessage.classList.add("hidden"); 101 | }, 5000); 102 | } 103 | }) 104 | .catch((error) => { 105 | console.error("Error:", error); 106 | statusMessage.innerHTML = 107 | ' An error occurred. Please try again.'; 108 | statusMessage.classList.remove( 109 | "hidden", 110 | "bg-green-100", 111 | "text-green-700", 112 | "border-green-500" 113 | ); 114 | statusMessage.classList.add( 115 | "bg-red-100", 116 | "text-red-700", 117 | "border-red-500" 118 | ); 119 | 120 | 121 | submitButton.innerHTML = originalButtonText; 122 | submitButton.disabled = false; 123 | }); 124 | }); 125 | 126 | // Logout button 127 | document.getElementById("logout-btn").addEventListener("click", function () { 128 | window.location.href = "logout.php"; 129 | }); 130 | -------------------------------------------------------------------------------- /public/assets/weight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 13 | 19 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 58 | 59 | 62 | 63 | -------------------------------------------------------------------------------- /backend/book_appointment.php: -------------------------------------------------------------------------------- 1 | false, "message" => "Missing required fields"]); 14 | exit(); 15 | } 16 | 17 | $date = $_POST['date']; 18 | $specialization = $_POST['specialization']; 19 | $remarks = $_POST['remarks']; 20 | 21 | $day = strtolower(date('l', strtotime($date))); // e.g., "Monday" 22 | $formatted_date = date("Y-m-d", strtotime($date)); // e.g., "2025-03-19" 23 | 24 | // -------------------- TRANSACTION BEGINS ------------------------- 25 | $config = require __DIR__ . '/../config/db_config.php'; 26 | $dsn = sprintf( 27 | 'mysql:host=%s;port=%s;dbname=%s;charset=utf8mb4', 28 | $config['DB_HOST'], 29 | $config['DB_PORT'], 30 | $config['DB_NAME'] 31 | ); 32 | $pdo = new PDO($dsn, $config['DB_USERNAME'], $config['DB_PASSWORD'], [ 33 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, 34 | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, 35 | ]); 36 | $pdo->exec("SET session wait_timeout=600"); 37 | $pdo->exec("SET session interactive_timeout=600"); 38 | $pdo->exec("SET SESSION net_read_timeout=600"); 39 | $pdo->beginTransaction(); 40 | 41 | try { 42 | // this is a basic doctor finding algorithm on the basis of availabiltiy, it won't be inefficient if the slots per day are less. 43 | // i'll maybe optimize it later by normalizing the scheduling process and mainting a seperate table for booking times. 44 | 45 | // Query1: get all doctors with the given specialization and with available slots on $day 46 | $query1 = "SELECT id, specialization, available_slots FROM doctors 47 | WHERE specialization = ? 48 | AND JSON_CONTAINS_PATH(available_slots, 'one', CONCAT('$.', ?)) 49 | ORDER BY id"; 50 | $stmt1 = $pdo->prepare($query1); 51 | $stmt1->execute([$specialization, $day]); 52 | $results = $stmt1->fetchAll(); 53 | $stmt1 = null; 54 | if (count($results) === 0) { 55 | $pdo->rollBack(); 56 | http_response_code(404); 57 | echo json_encode(["success" => false, "message" => "No doctors available on that date"]); 58 | exit(); 59 | } 60 | $doctor_found = false; 61 | $doctor_id = null; 62 | $free_time = null; 63 | foreach ($results as $doctor) { 64 | $available_slots_json = $doctor['available_slots']; 65 | $available_slots = json_decode($available_slots_json, true); 66 | if (!isset($available_slots[$day]) || empty($available_slots[$day])) { 67 | continue; 68 | } 69 | $potential_slots = $available_slots[$day]; 70 | $query_appts = "SELECT appointment_time FROM appointments 71 | WHERE doctor_id = ? AND DATE(appointment_time) = ?"; 72 | $stmt_appts = $pdo->prepare($query_appts); 73 | $stmt_appts->execute([$doctor['id'], $formatted_date]); 74 | $booked_result = $stmt_appts->fetchAll(); 75 | $stmt_appts = null; 76 | $booked_slots = []; 77 | foreach ($booked_result as $row) { 78 | $booked_slots[] = date("H:i", strtotime($row['appointment_time'])); 79 | } 80 | $free_slots = array_diff($potential_slots, $booked_slots); 81 | if (!empty($free_slots)) { 82 | $doctor_found = true; 83 | $doctor_id = $doctor['id']; 84 | $free_slots = array_values($free_slots); 85 | $free_time = $free_slots[0]; 86 | break; 87 | } 88 | } 89 | if (!$doctor_found) { 90 | $pdo->rollBack(); 91 | http_response_code(404); 92 | echo json_encode(["success" => false, "message" => "No free slots available for any doctor with that specialization on that date"]); 93 | exit(); 94 | } 95 | } catch (Exception $e) { 96 | $pdo->rollBack(); 97 | http_response_code(500); 98 | echo json_encode(["success" => false, "message" => "Internal Server Error. Failed to find available doctor: " . $e->getMessage()]); 99 | exit(); 100 | } 101 | 102 | try { 103 | $appointment_time = $formatted_date . ' ' . $free_time; 104 | $query3 = "INSERT INTO appointments (patient_id, doctor_id, appointment_time, remarks) VALUES (?, ?, ?, ?)"; 105 | $stmt3 = $pdo->prepare($query3); 106 | if (!$stmt3->execute([$userid, $doctor_id, $appointment_time, $remarks])) { 107 | $pdo->rollBack(); 108 | http_response_code(500); 109 | echo json_encode(["success" => false, "message" => "Failed to create appointment."]); 110 | exit(); 111 | } 112 | $stmt3 = null; 113 | $pdo->commit(); 114 | echo json_encode(["success" => true, "message" => "Appointment booked successfully.", "appointment_time" => $appointment_time]); 115 | } catch (Exception $e) { 116 | $pdo->rollBack(); 117 | http_response_code(500); 118 | echo json_encode(["success" => false, "message" => "Internal Server Error. Failed to create an appointment: " . $e->getMessage()]); 119 | exit(); 120 | } 121 | // --------------------- TRANSACTION ENDS -------------------------- 122 | ?> 123 | -------------------------------------------------------------------------------- /backend/send_otp.php: -------------------------------------------------------------------------------- 1 | load(); 33 | 34 | $sender = $_ENV["SENDER_MAIL"]; 35 | $sender_pass = $_ENV["SENDER_PASSWORD"]; 36 | 37 | 38 | if (!$sender || !$sender_pass) { 39 | die("Environment variables for email not set."); 40 | } 41 | 42 | $mail = new PHPMailer(true); 43 | 44 | $email = filter_var($_POST["email"], FILTER_SANITIZE_EMAIL); 45 | $name = filter_var($_POST["name"], FILTER_SANITIZE_FULL_SPECIAL_CHARS); 46 | $password = filter_var($_POST["password"], FILTER_SANITIZE_FULL_SPECIAL_CHARS); 47 | 48 | // hash the password 49 | $hashed_password = password_hash($password, PASSWORD_BCRYPT); 50 | 51 | $otp = random_int(100000, 999999); 52 | $_SESSION['creds'] = [ 53 | "name" => $name, 54 | "email" => $email, 55 | "code" => $otp, 56 | "password" => $hashed_password 57 | ]; 58 | 59 | $emailBody = " 60 | 61 | 109 | 110 | 111 |
112 |

Your OTP Code for SwiftHealth

113 |

Dear User,

114 |

Thank you for signing up with SwiftHealth. To complete your registration and activate your account, please use the One-Time Password (OTP) below:

115 |
$otp
116 |

This OTP is confidential. Please do not share it with anyone to protect your account.

117 |

If you did not request this, please ignore this message.

118 |
119 |

Thank you for using SwiftHealth!

120 |

For any queries, contact support.

121 |
122 |
123 | 124 | "; 125 | 126 | try { 127 | $mail->isSMTP(); 128 | $mail->Host = 'smtp.gmail.com'; 129 | $mail->SMTPAuth = true; 130 | $mail->Username = $sender; 131 | $mail->Password = $sender_pass; 132 | $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; 133 | $mail->Port = 587; 134 | 135 | $mail->setFrom($sender, 'SwiftHealth'); 136 | $mail->addAddress($email); 137 | $mail->isHTML(true); 138 | $mail->Subject = 'Your OTP is here.'; 139 | $mail->Body = $emailBody; 140 | 141 | $mail->send(); 142 | echo json_encode(["success" => true, "message" => "OTP Sent Successfully", "redirect" => "verifyotp.html"]); 143 | } catch (Exception $e) { 144 | echo json_encode(["success" => false, "message" => $e->getMessage(), "redirect" => false]); 145 | unset($_SESSION['otp']); 146 | } 147 | -------------------------------------------------------------------------------- /public/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Login | SwiftHealth 7 | 8 | 9 | 10 | 11 | 12 | 22 | 23 | 24 | 25 | 26 | 50 | 51 |
52 | 53 | 54 |
55 |
56 |

57 | SwiftHealth 58 |

59 |

Your Complete Healthcare Solution

60 |
61 | 62 |
63 |

Welcome Back!

64 |

Access your health dashboard securely and efficiently.

65 | 66 | 67 |
68 |
69 | 70 | Secure access to your health records 71 |
72 |
73 | 74 | View upcoming appointments 75 |
76 |
77 | 78 | Get personalized health notifications 79 |
80 |
81 |
82 |
83 | 84 | 85 |
86 |

Login

87 |

Welcome back! Please login to your account.

88 | 89 |
90 | 91 |
92 |
93 | 94 | 95 |
96 |

Please enter a valid email.

97 |
98 | 99 | 100 |
101 | 102 |
103 | 104 | 105 | 108 |
109 |

A password is required.

110 |
111 | 112 |
113 | 114 | Forgot Password? 115 |
116 | 117 | 118 | 119 |
120 | 121 | 122 |

Don't have an account? 123 | Sign Up 124 |

125 |
126 |
127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /backend/emergencymail.php: -------------------------------------------------------------------------------- 1 | load(); 28 | 29 | $sender = $_ENV["ALERT_SENDER_MAIL"]; 30 | $sender_pass = $_ENV["ALERT_SENDER_PASSWORD"]; 31 | 32 | require "./database/connectDB.php"; 33 | 34 | if (!$sender || !$sender_pass) { 35 | http_response_code(500); 36 | die(json_encode(["success" => false, "message" => "Server configuration error (missing email credentials)."])); 37 | } 38 | 39 | // Check if user is logged in 40 | if (!isset($_SESSION['user']['id']) || !isset($_SESSION['user']['name'])) { 41 | http_response_code(401); 42 | die(json_encode(["success" => false, "message" => "User not logged in or session expired."])); 43 | } 44 | 45 | $user_id = $_SESSION['user']['id']; 46 | $patient_name = $_SESSION['user']['name']; 47 | 48 | 49 | $input = json_decode(file_get_contents('php://input'), true); 50 | $latitude = isset($input['latitude']) ? filter_var($input['latitude'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : null; 51 | $longitude = isset($input['longitude']) ? filter_var($input['longitude'], FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION) : null; 52 | $location_info = ""; 53 | 54 | if ($latitude !== null && $longitude !== null) { 55 | $googleMapsLink = "https://www.google.com/maps?q=" . urlencode($latitude . "," . $longitude); 56 | $location_info = "

Patient's Last Known Location:

" 57 | . "

Latitude: $latitude
Longitude: $longitude

" 58 | . "

View on Google Maps

"; 59 | } else { 60 | $location_info = "

Patient's Location: Not available.

"; 61 | } 62 | 63 | 64 | 65 | 66 | $stmt = $conn->prepare("SELECT emergency_contact FROM patients WHERE id = ?"); 67 | if (!$stmt) { 68 | http_response_code(500); 69 | die(json_encode(["success" => false, "message" => "Database prepare statement failed: " . $conn->error])); 70 | } 71 | $stmt->bind_param("i", $user_id); 72 | $stmt->execute(); 73 | $result = $stmt->get_result(); 74 | 75 | if ($result->num_rows === 0) { 76 | $stmt->close(); 77 | $conn->close(); 78 | http_response_code(404); 79 | die(json_encode(["success" => false, "message" => "Patient record not found."])); 80 | } 81 | 82 | $patient = $result->fetch_assoc(); 83 | $emergency_contact = $patient['emergency_contact']; 84 | $stmt->close(); 85 | 86 | 87 | 88 | if (!$emergency_contact || !filter_var($emergency_contact, FILTER_VALIDATE_EMAIL)) { 89 | http_response_code(400); 90 | die(json_encode(["success" => false, "message" => "Invalid or missing emergency contact email in patient profile."])); 91 | } 92 | 93 | $mail = new PHPMailer(true); 94 | 95 | 96 | $emailBody = " 97 | 98 | 99 | 111 | 112 | 113 |
114 |

Emergency Alert

115 |

Urgent: Immediate Attention Required!

116 | 117 |
118 |

Patient Name: $patient_name

119 |

This patient has triggered an emergency alert using the SwiftHealth system. Please respond immediately.

120 |
121 | 122 |
123 | $location_info 124 |
125 | 126 |

Time is critical, and your prompt response could be life-saving. Please take necessary action right away.

127 | 128 | 132 |
133 | 134 | 135 | "; 136 | 137 | 138 | try { 139 | // --- PHPMailer Setup --- 140 | $mail->isSMTP(); 141 | $mail->Host = 'smtp.gmail.com'; 142 | $mail->SMTPAuth = true; 143 | $mail->Username = $sender; 144 | $mail->Password = $sender_pass; 145 | $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; 146 | $mail->Port = 587; 147 | 148 | $mail->setFrom($sender, 'SwiftHealth Emergency Alert'); 149 | $mail->addAddress($emergency_contact); 150 | $mail->isHTML(true); 151 | $mail->Subject = 'Emergency Alert - Immediate Attention Required for ' . $patient_name; 152 | $mail->Body = $emailBody; 153 | // this was optional but i added it anyway 154 | $mail->AltBody = "Emergency Alert!\n\nPatient Name: $patient_name\nThis patient has triggered an emergency alert. Please respond immediately.\n\n" . strip_tags(str_replace('
', "\n", $location_info)) . "\n\nTime is critical."; 155 | 156 | 157 | $mail->send(); 158 | echo json_encode(["success" => true, "message" => "Emergency email sent successfully to $emergency_contact."]); 159 | 160 | } catch (Exception $e) { 161 | error_log("Mailer Error: " . $mail->ErrorInfo); 162 | http_response_code(500); 163 | echo json_encode(["success" => false, "message" => "Failed to send email. Please try again later or contact support."]); 164 | } 165 | ?> 166 | -------------------------------------------------------------------------------- /public/js/dashboard.js: -------------------------------------------------------------------------------- 1 | let logoutbtn = document.getElementById("logout-btn"); 2 | logoutbtn.addEventListener("click", logoutUser); 3 | 4 | async function logoutUser() { 5 | let response = await fetch( 6 | "http://localhost/ca2-project/backend/user_logout.php", 7 | { 8 | method: "POST", 9 | credentials: "include", 10 | } 11 | ); 12 | 13 | let result = await response.json(); 14 | 15 | if (result.success) { 16 | window.location.href = "login.html"; 17 | } else { 18 | alert("An error occured."); 19 | } 20 | } 21 | 22 | const chatPopup = document.getElementById("chat-popup"); 23 | const chatToggleBtn = document.getElementById("chat-toggle-btn"); 24 | const chatCloseBtn = document.getElementById("chat-close-btn"); 25 | const chatSendBtn = document.getElementById("chat-send-btn"); 26 | const chatInputField = document.getElementById("chat-input-field"); 27 | const chatMessages = document.getElementById("chat-messages"); 28 | const typingIndicator = document.getElementById("typing-indicator"); 29 | const username = document.getElementById("user_name")?.innerText || "You"; 30 | 31 | 32 | chatToggleBtn.addEventListener("click", () => { 33 | chatPopup.classList.toggle("hidden"); 34 | if (!chatPopup.classList.contains("hidden")) { 35 | chatInputField.focus(); 36 | } 37 | }); 38 | 39 | 40 | chatCloseBtn.addEventListener("click", () => { 41 | chatPopup.classList.add("hidden"); 42 | }); 43 | 44 | // new messg 45 | function addMessage(sender, message, isAI = false) { 46 | const messageElem = document.createElement("div"); 47 | 48 | if (isAI) { 49 | // AI message 50 | messageElem.className = "flex items-start mb-4"; 51 | messageElem.innerHTML = ` 52 |
53 | 54 |
55 |
56 |

${message}

57 |
58 | `; 59 | } else { 60 | // user message 61 | messageElem.className = "flex items-start justify-end mb-4"; 62 | messageElem.innerHTML = ` 63 |
64 |

${message}

65 |
66 |
67 | ${username 68 | .charAt(0) 69 | .toUpperCase()} 70 |
71 | `; 72 | } 73 | 74 | chatMessages.appendChild(messageElem); 75 | chatMessages.scrollTop = chatMessages.scrollHeight; 76 | } 77 | 78 | async function sendMessage() { 79 | const userInput = chatInputField.value.trim(); 80 | if (userInput === "") return; 81 | 82 | // Display user message 83 | addMessage(username, userInput, false); 84 | chatInputField.value = ""; 85 | 86 | // Show typing indicator 87 | typingIndicator.classList.remove("hidden"); 88 | chatMessages.scrollTop = chatMessages.scrollHeight; 89 | 90 | const formData = new FormData(); 91 | formData.append("userinput", userInput); 92 | 93 | try { 94 | const response = await fetch( 95 | "http://localhost/ca2-project/backend/ai_api.php", 96 | { 97 | method: "POST", 98 | body: formData, 99 | } 100 | ); 101 | 102 | // Hide typing indicator 103 | typingIndicator.classList.add("hidden"); 104 | 105 | const data = await response.json(); 106 | const reply = data.candidates[0].content.parts[0].text; 107 | 108 | // Display AI message 109 | addMessage("AI", reply, true); 110 | } catch (error) { 111 | // Hide typing indicator 112 | typingIndicator.classList.add("hidden"); 113 | 114 | console.error("Error:", error); 115 | addMessage( 116 | "AI", 117 | "Sorry, I'm having trouble processing your request. Please try again later.", 118 | true 119 | ); 120 | } 121 | } 122 | 123 | // Event listeners for sending messages 124 | chatSendBtn.addEventListener("click", sendMessage); 125 | chatInputField.addEventListener("keypress", function (e) { 126 | if (e.key === "Enter") sendMessage(); 127 | }); 128 | 129 | 130 | const emergencyBtn = document.getElementById("emergency-btn"); 131 | const emergencyModal = document.getElementById("emergency-modal"); 132 | const confirmEmergencyBtn = document.getElementById("confirm-emergency"); 133 | const cancelEmergencyBtn = document.getElementById("cancel-emergency"); 134 | const emergencyStatusModal = document.getElementById("emergency-status-modal"); 135 | const statusContent = document.getElementById("status-content"); 136 | const closeStatusBtn = document.getElementById("close-status"); 137 | 138 | emergencyBtn.addEventListener("click", function () { 139 | emergencyModal.classList.remove("hidden"); 140 | }); 141 | 142 | cancelEmergencyBtn.addEventListener("click", function () { 143 | emergencyModal.classList.add("hidden"); 144 | }); 145 | 146 | closeStatusBtn.addEventListener("click", function () { 147 | emergencyStatusModal.classList.add("hidden"); 148 | }); 149 | 150 | confirmEmergencyBtn.addEventListener("click", function () { 151 | emergencyModal.classList.add("hidden"); 152 | 153 | // initial status 154 | statusContent.innerHTML = ` 155 |
156 | 157 |
158 |

Getting Location & Sending Alert

159 |

Please wait...

160 | `; 161 | emergencyStatusModal.classList.remove("hidden"); 162 | 163 | // Try to get location, 164 | if ("geolocation" in navigator) { 165 | navigator.geolocation.getCurrentPosition(sendAlert, () => sendAlert(), { 166 | timeout: 5000, 167 | maximumAge: 0, 168 | }); 169 | } else { 170 | sendAlert(); 171 | } 172 | 173 | async function sendAlert(position = null) { 174 | const requestBody = {}; 175 | if (position) { 176 | requestBody.latitude = position.coords.latitude; 177 | requestBody.longitude = position.coords.longitude; 178 | } 179 | 180 | try { 181 | const response = await fetch("../backend/emergencymail.php", { 182 | method: "POST", 183 | headers: { "Content-Type": "application/json" }, 184 | credentials: "include", 185 | body: JSON.stringify(requestBody), 186 | }); 187 | 188 | const data = await response.json(); 189 | 190 | if (data.success) { 191 | statusContent.innerHTML = ` 192 |
193 | 194 |
195 |

Alert Sent Successfully

196 |

${data.message}

197 | `; 198 | } else { 199 | statusContent.innerHTML = ` 200 |
201 | 202 |
203 |

Alert Failed

204 |

${ 205 | data.message || "Could not send emergency alert." 206 | }

207 | `; 208 | } 209 | } catch (error) { 210 | console.error("Emergency alert error:", error); 211 | statusContent.innerHTML = ` 212 |
213 | 214 |
215 |

Network Error

216 |

Could not connect to server. Please try again later.

217 | `; 218 | } 219 | } 220 | }); 221 | -------------------------------------------------------------------------------- /public/assets/patient.svg: -------------------------------------------------------------------------------- 1 | Created with getavataaars.com -------------------------------------------------------------------------------- /public/ContactUs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Contact Us | SwiftHealth 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 |
Working Hour: 08:00 AM to 09:00 PM | Email: info@domainname.com
18 |
19 | 20 | | Contact: +1 234 567 890 21 |
22 |
23 | 24 | 25 | 49 | 50 | 51 |
53 | 54 | Home 55 | About Us 56 | Services 57 | Contact Us 58 | Sign Up / Log In 59 |
60 | Book Appointment 61 |
62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 |
70 |
71 |

Any Queries? Contact Us

72 |

Reach out to us for appointments, emergency alerts, and queries.

73 | 74 |
75 |
76 | 77 | 78 |
79 |
80 | 81 | 82 |
83 |
84 | 85 | 86 |
87 | 88 |
89 | 90 |
91 |

For emergency support, call +x xxx xxx xxxx

92 |

Email us at xxxxx@gmail.com

93 | 94 |
95 |
96 |
97 | 98 | 99 | 100 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /public/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sign Up | SwiftHealth 8 | 9 | 10 | 11 | 12 | 15 | 25 | 26 | 27 | 28 | 29 | 53 | 54 |
55 |
56 | 57 |
58 | 59 |
60 |
61 | 62 | SwiftHealth 63 |
64 |

Your Complete Healthcare Solution

65 |
66 | 67 | 68 |
69 |

Your Healthcare Assistant Awaits!

70 |

Join thousands of users managing their health effortlessly.

71 | 72 | 73 |
74 |
75 |
76 | 77 |
78 | Easy appointment scheduling 79 |
80 |
81 |
82 | 83 |
84 | Connect with top specialists 85 |
86 |
87 |
88 | 89 |
90 | Secure health record management 91 |
92 |
93 |
94 | 95 | 96 |
97 |
98 |
99 |
100 | 101 | 102 |
103 |
104 |

Create your account

105 |

Start your journey to better health management

106 | 107 | 108 |
109 | 110 |
111 |
112 | 113 | 114 |
115 | 116 |
117 | 118 | 119 |
120 |
121 | 122 | 123 |
124 | 125 | 126 |
127 | 128 | 129 |
130 |
131 | 132 | 133 | 136 |
137 | 138 |
139 | 140 | 141 |
142 | 146 |
147 | 148 | 149 |
150 |

Already have an account? 151 | 152 | Sign in 153 | 154 |

155 |
156 | 157 | 158 |
159 |
160 |
OR
161 |
162 |
163 | 164 | 165 |
166 | 170 |
171 |
172 |
173 |
174 |
175 |
176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /backend/views/doctor_dashboard.php: -------------------------------------------------------------------------------- 1 | 'text-green-500 border-green-500 bg-green-50', 11 | 'pending' => 'text-yellow-500 border-yellow-500 bg-yellow-50', 12 | 'rejected' => 'text-red-500 border-red-500 bg-red-50', 13 | 'completed' => 'text-blue-500 border-blue-500 bg-blue-50', 14 | 'cancelled' => 'text-gray-500 border-gray-500 bg-gray-50' 15 | ]; 16 | 17 | $statusIcons = [ 18 | 'approved' => '', 19 | 'pending' => '', 20 | 'rejected' => '', 21 | 'completed' => '', 22 | 'cancelled' => '' 23 | ]; 24 | 25 | $appointmentID = e($appointment['id']); 26 | $status = e($appointment['status']); 27 | $patientName = e($appointment['patient_name']); 28 | $patientEmail = e($appointment['patient_email']); 29 | 30 | $time = date("F j, Y - g:i A", strtotime($appointment['appointment_time'])); 31 | $remarks = !empty($appointment['remarks']) ? e($appointment['remarks']) : "No remarks provided."; 32 | 33 | $borderClass = $statusColors[$status] ?? 'border-gray-500 bg-gray-50'; 34 | $statusIcon = $statusIcons[$status] ?? ''; 35 | 36 | if($status == "approved") { 37 | $completed = ""; 38 | } 39 | else { 40 | $completed = "disabled"; 41 | } 42 | 43 | if($status != "pending") { 44 | $actionbuttons = "disabled"; 45 | } 46 | else { 47 | $actionbuttons = ""; 48 | } 49 | 50 | return << 52 |
53 |

54 | 55 | Patient: 56 | $patientName 57 |

58 |

59 | $statusIcon $status 60 |

61 |
62 | 63 |
64 |

65 | 66 | Email: 67 | $patientEmail 68 |

69 | 70 |

71 | 72 | Time: 73 | $time 74 |

75 |
76 | 77 |
78 |

79 | 80 | 81 | Remarks: 82 | $remarks 83 | 84 |

85 |
86 | 87 |
88 | 91 | 94 | 97 |
98 | 99 | HTML; 100 | } 101 | 102 | $app_query = "SELECT a.id, a.remarks, a.appointment_time, a.status, 103 | p.name AS patient_name, p.email AS patient_email, 104 | u.name AS doctor_name, u.email AS doctor_email 105 | FROM appointments AS a 106 | JOIN users AS u ON u.id = a.doctor_id 107 | JOIN users AS p ON p.id = a.patient_id 108 | WHERE u.email = ? AND a.appointment_time > NOW() AND a.status != 'completed' 109 | ORDER BY a.appointment_time ASC 110 | "; 111 | 112 | $doc_stmt = $conn->prepare($app_query); 113 | if (!$doc_stmt) { 114 | echo '

Error Fetching Details. Try again later.

'; 115 | die(); 116 | } 117 | 118 | $doc_stmt->bind_param('s', $userEmail); 119 | if (!$doc_stmt->execute()) { 120 | echo '

Error Fetching Details. Try again later.

'; 121 | die(); 122 | } 123 | 124 | $appointments = $doc_stmt->get_result()->fetch_all(MYSQLI_ASSOC); 125 | $doc_stmt->close(); 126 | 127 | $past_query = "SELECT a.id, a.remarks, a.appointment_time, a.status, 128 | p.name AS patient_name, p.email AS patient_email, 129 | u.name AS doctor_name, u.email AS doctor_email 130 | FROM appointments AS a 131 | JOIN users AS u ON u.id = a.doctor_id 132 | JOIN users AS p ON p.id = a.patient_id 133 | WHERE u.email = ? AND (a.appointment_time <= NOW() OR a.status = 'completed') 134 | ORDER BY a.appointment_time DESC 135 | LIMIT 10;"; 136 | $doc_stmt2 = $conn->prepare($past_query); 137 | if (!$doc_stmt2) { 138 | echo '

Error Fetching Details. Try again later.

'; 139 | die(); 140 | } 141 | $doc_stmt2->bind_param('s', $userEmail); 142 | 143 | if (!$doc_stmt2->execute()) { 144 | echo '

Error Fetching Details. Try again later.

'; 145 | die(); 146 | } 147 | 148 | $past_appointments = $doc_stmt2->get_result()->fetch_all(MYSQLI_ASSOC); 149 | $doc_stmt2->close(); 150 | 151 | // Count appointments by status 152 | $pending_count = 0; 153 | $approved_count = 0; 154 | $completed_count = 0; 155 | 156 | foreach($appointments as $appointment) { 157 | if($appointment['status'] == 'pending') $pending_count++; 158 | if($appointment['status'] == 'approved') $approved_count++; 159 | } 160 | 161 | foreach($past_appointments as $appointment) { 162 | if($appointment['status'] == 'completed') $completed_count++; 163 | } 164 | 165 | ?> 166 | 167 | 168 |
169 |
170 | 171 |
172 |
173 |

174 | 175 | Doctor Dashboard 176 |

177 |

Welcome back

178 |
179 | 180 |
181 | 185 |
186 | 187 |
188 |
189 |
190 | 191 | 192 |
193 |
194 |
195 |
196 | 197 |
198 |
199 |

Pending

200 |

Appointments

201 |
202 |
203 |
204 | 205 |
206 |
207 |
208 | 209 |
210 |
211 |

Approved

212 |

Appointments

213 |
214 |
215 |
216 | 217 |
218 |
219 |
220 | 221 |
222 |
223 |

Completed

224 |

Appointments

225 |
226 |
227 |
228 |
229 | 230 |
231 |
232 |

233 | 234 | Upcoming Appointments 235 |

236 |
237 |
    238 | 239 | 242 | 243 |

    No upcoming appointments

    244 |

    You currently have no upcoming appointments

    245 |
'; 246 | } 247 | else { 248 | foreach ($appointments as $appointment) { 249 | echo renderAppointment($appointment); 250 | } 251 | } 252 | ?> 253 | 254 | 255 | 256 | 257 |
258 |
259 |

260 | 261 | Past / Completed Appointments 262 |

263 | 267 |
268 |
'; 276 | } 277 | else { 278 | foreach ($past_appointments as $appointment) { 279 | echo renderAppointment($appointment); 280 | } 281 | } 282 | ?> 283 | 284 | 285 | 286 | 287 | 288 | 312 | 313 | 323 | 324 | 325 | 326 | -------------------------------------------------------------------------------- /public/editProfile.php: -------------------------------------------------------------------------------- 1 | $userId, 15 | 'name' => $userName, 16 | 'email' => $userEmail, 17 | 'date_of_birth' => '', 18 | 'gender' => '', 19 | 'medical_history' => '', 20 | 'blood_type' => '', 21 | 'phone' => '', 22 | 'address' => '', 23 | 'height' => '', 24 | 'weight' => '', 25 | 'emergency_contact' => '' 26 | ]; 27 | 28 | // Fetch froim get_user.php 29 | require __DIR__ . "/../backend/get_user.php"; 30 | $response = getUserData($userEmail); 31 | 32 | if ($response["success"] && isset($response["data"]) && is_array($response["data"])) { 33 | $userData = array_merge($userData, $response["data"]); 34 | } 35 | 36 | // Get initials for avatar 37 | $initials = strtoupper(substr($userName, 0, 1)); 38 | ?> 39 | 40 | 41 | 42 | 43 | 44 | 45 | User Profile - SwiftHealth 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 |
57 |
58 | SwiftHealth 59 |
60 |
61 | 62 | Dashboard 63 | 64 |
65 | 66 | 67 |
68 | 71 |
72 |
73 |
74 | 75 | 76 |
77 | 78 |
79 |
80 | 81 |
82 |
83 |

's Profile

84 |

85 |
86 |
87 | 88 | 89 | 90 | 91 | 92 |
93 |
94 | 95 | 96 | 97 |
98 |
99 | 100 |

Personal Information

101 |
102 | 103 |
104 |
105 | 106 |
107 |
108 | 109 |
110 | 112 |
113 |
114 | 115 |
116 | 117 |
118 |
119 | 120 |
121 | 123 |
124 |
125 | 126 |
127 | 128 |
129 |
130 | 131 |
132 | 134 |
135 |
136 | 137 |
138 | 139 |
140 |
141 | 142 |
143 | 150 |
151 |
152 | 153 |
154 | 155 |
156 |
157 | 158 |
159 | 162 |
163 |
164 | 165 |
166 | 167 |
168 |
169 | 170 |
171 | 173 |
174 |
175 |
176 |
177 | 178 | 179 |
180 |
181 | 182 |

Medical Information

183 |
184 | 185 |
186 |
187 | 188 |
189 |
190 | 191 |
192 | 204 |
205 |
206 | 207 |
208 | 209 |
210 |
211 | 212 |
213 | 215 |
216 |
217 | 218 |
219 | 220 |
221 |
222 | 223 |
224 | 226 |
227 |
228 | 229 |
230 | 231 |
232 |
233 | 234 |
235 | 237 |
238 |
239 |
240 | 241 |
242 | 243 |
244 |
245 | 246 |
247 | 249 |
250 |
251 |
252 | 253 | 254 |
255 | 258 |
259 |
260 |
261 |
262 | 263 | 265 | 266 | -------------------------------------------------------------------------------- /backend/views/patient_dashboard.php: -------------------------------------------------------------------------------- 1 | 'text-green-500 border-green-500 bg-green-50', 16 | 'pending' => 'text-yellow-500 border-yellow-500 bg-yellow-50', 17 | 'rejected' => 'text-red-500 border-red-500 bg-red-50', 18 | 'completed' => 'text-blue-500 border-blue-500 bg-blue-50', 19 | 'cancelled' => 'text-gray-500 border-gray-500 bg-gray-50' 20 | ]; 21 | 22 | $statusIcons = [ 23 | 'approved' => '', 24 | 'pending' => '', 25 | 'rejected' => '', 26 | 'completed' => '', 27 | 'cancelled' => '' 28 | ]; 29 | 30 | $status = e($appointment['status']); 31 | $doctorName = e($appointment['doctor_name']); 32 | $specialization = e($appointment['specialization']); 33 | $hospital = e($appointment['hospital_name']); 34 | $time = date("F j, Y - g:i A", strtotime($appointment['appointment_time'])); 35 | $remarks = !empty($appointment['remarks']) ? e($appointment['remarks']) : "No remarks provided."; 36 | 37 | $borderClass = $statusColors[$status] ?? 'border-gray-500 bg-gray-50'; 38 | $statusIcon = $statusIcons[$status] ?? ''; 39 | 40 | return << 42 |
43 |

44 | 45 | $doctorName 46 |

47 |

48 | $statusIcon $status 49 |

50 |
51 | 52 |
53 |

54 | 55 | Hospital: 56 | $hospital 57 |

58 | 59 |

60 | 61 | Time: 62 | $time 63 |

64 |
65 | 66 |

67 | 68 | Specialization: 69 | $specialization 70 |

71 | 72 |
73 |

74 | 75 | 76 | Remarks: 77 | $remarks 78 | 79 |

80 |
81 | 82 | HTML; 83 | } 84 | 85 | // fetch the list of upcoming appointments. 86 | $app_query = "SELECT 87 | a.id, 88 | a.remarks, 89 | a.appointment_time, 90 | a.status, 91 | d.specialization, 92 | d.hospital_name, 93 | u_patient.name AS patient_name, 94 | u_doctor.name AS doctor_name 95 | FROM appointments AS a 96 | JOIN users AS u_patient ON a.patient_id = u_patient.id 97 | LEFT JOIN doctors AS d ON a.doctor_id = d.id 98 | LEFT JOIN users AS u_doctor ON d.id = u_doctor.id 99 | WHERE u_patient.email = ? AND a.appointment_time > NOW() AND a.status != 'completed' 100 | ORDER BY a.appointment_time ASC 101 | ;"; 102 | 103 | $stmt2 = $conn->prepare($app_query); 104 | 105 | if (!$stmt2) { 106 | echo '

Error fetching details, please try again later

'; 107 | exit(); 108 | } 109 | $stmt2->bind_param("s", $userEmail); 110 | if (!$stmt2->execute()) { 111 | echo '

Error executing query, please try again later

'; 112 | exit(); 113 | } 114 | 115 | $appointments = $stmt2->get_result()->fetch_all(MYSQLI_ASSOC); 116 | $stmt2->close(); 117 | 118 | $past_query = "SELECT 119 | a.id, 120 | a.remarks, 121 | a.appointment_time, 122 | a.status, 123 | d.specialization, 124 | d.hospital_name, 125 | u_doctor.name as doctor_name 126 | FROM appointments as a 127 | JOIN users as u_patient on a.patient_id = u_patient.id 128 | LEFT JOIN doctors as d on a.doctor_id = d.id 129 | LEFT JOIN users as u_doctor on d.id = u_doctor.id 130 | WHERE u_patient.email = ? AND (a.appointment_time <= NOW() OR a.status = 'completed') 131 | ORDER BY a.appointment_time DESC LIMIT 10; 132 | "; 133 | 134 | $past_stmt = $conn->prepare($past_query); 135 | 136 | if(!$past_stmt) { 137 | echo '

Error fetching details, please try again later

'; 138 | exit(); 139 | } 140 | 141 | $past_stmt->bind_param('s', $userEmail); 142 | 143 | if(!$past_stmt->execute()) { 144 | echo '

Error executing query, please try again later

'; 145 | exit(); 146 | } 147 | 148 | $past_appointments = $past_stmt->get_result()->fetch_all(MYSQLI_ASSOC); 149 | $past_stmt->close(); 150 | ?> 151 | 152 | 153 |
154 |
155 | 156 |
157 |
158 |

159 | 160 | Patient Dashboard 161 |

162 |

Welcome back to SwiftHealth

163 |
164 | 165 |
166 | 170 |
171 | 172 |
173 |
174 |
175 | 176 | 177 |
178 |
179 |
180 |
181 | 182 |
183 |
184 |

Upcoming

185 |

Appointments

186 |
187 |
188 |
189 | 190 |
191 |
192 |
193 | 194 |
195 |
196 |

Past

197 |

Appointments

198 |
199 |
200 |
201 | 202 |
203 |
204 |
205 | 206 |
207 |
208 |

Health Status

209 |

Good

210 |
211 |
212 |
213 |
214 | 215 |
216 |
217 |

218 | 219 | Upcoming Appointments 220 |

221 | 222 | Book Appointment 223 | 224 |
225 |
    226 | 227 | 230 | 231 |

    No upcoming appointments

    232 |

    Schedule your next appointment to see it here

    233 |
'; 234 | } 235 | else { 236 | foreach ($appointments as $appointment) { 237 | echo renderAppointment($appointment); 238 | } 239 | } 240 | ?> 241 | 242 | 243 | 244 | 245 |
246 |
247 |

248 | 249 | Past / Completed Appointments 250 |

251 | 255 |
256 |
'; 264 | } 265 | else { 266 | foreach ($past_appointments as $appointment) { 267 | echo renderAppointment($appointment); 268 | } 269 | } 270 | ?> 271 | 272 | 273 | 274 | 275 |
276 |
277 |
278 |

279 | Emergency Access 280 |

281 |

Need immediate medical attention? Click the emergency button below.

282 | 286 | 287 | 288 |
289 |
290 | 291 |
292 |
293 |

294 | Health Tips 295 |

296 |
297 |
298 |
299 | 300 |
301 |
302 |

Regular Check-ups

303 |

Schedule a check-up at least once a year to maintain good health.

304 |
305 |
306 |
307 |
308 | 309 |
310 |
311 |

Balanced Diet

312 |

Maintain a balanced diet rich in fruits, vegetables, and whole grains.

313 |
314 |
315 |
316 |
317 | 318 |
319 |
320 |

Physical Activity

321 |

Aim for at least 30 minutes of moderate exercise daily.

322 |
323 |
324 |
325 |
326 |
327 |
328 | 329 | 330 | 331 | 332 | 356 | 357 | 358 | 370 | 371 | 372 | 375 | 376 | 377 | 432 | 433 | 434 | 435 | 436 | -------------------------------------------------------------------------------- /public/bookappointment.php: -------------------------------------------------------------------------------- 1 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Book Appointment | SwiftHealth 44 | 45 | 46 | 47 | 48 | 49 | 59 | 60 | 61 | 62 | 63 | 64 |
65 |
Working Hour: 08:00 AM to 09:00 PM | Email: info@swifthealth.com
66 |
67 | 68 | | Contact: +1 234 567 890 69 |
70 |
71 | 72 | 73 | 97 | 98 | 99 |
101 | 102 | Home 103 | My Dashboard 104 | About Us 105 | Services 106 | Contact Us 107 |
108 | Book Appointment 109 |
110 |
111 | 112 | 113 | 114 | 115 | 121 | 122 | 123 | 124 |
125 |
126 |
127 | 128 | 129 |
130 | 131 | 132 |
133 | 134 | 184 |
185 | 186 | 187 |
188 | 189 |
190 |

Make an appointment

191 |

Schedule your healthcare service with ease. Choose a date and specialist that works best for you.

192 | 193 |
194 |
195 |
196 |
197 |
198 |

Customer Services

199 |

+1 (555) 123-4567

200 |
201 |
202 | 203 |
204 |
205 |
206 |
207 |
208 |

Opening Hours

209 |

Mon - Sat (09:00 - 21:00)
Sunday (Closed)

210 |
211 |
212 | 213 |
214 |
215 |
216 |
217 |
218 |

Our Location

219 |

123 Health Avenue, Medical District
New York, NY 10001

220 |
221 |
222 |
223 |
224 | 225 | 226 | 227 |
228 |
229 |
How We Work
230 |
We work to achieve better health outcomes
231 |
We are committed to improving health outcomes through personalized care, innovative treatments, and a focus on prevention.
232 |
233 |
234 |
235 | Create Account 236 |
237 | 1 238 |

Create Account

239 |

Join our community by creating an account today.

240 |
241 |
242 | 243 |
244 | Book Appointment 245 |
246 | 2 247 |

Book Appointment

248 |

Effortlessly book an appointment according to your needs.

249 |
250 |
251 | 252 |
253 | Schedule Appointment 254 |
255 | 3 256 |

Schedule Appointment

257 |

Our scheduling algorithm will assign a doctor to you.

258 |
259 |
260 | 261 |
262 | Start Consultation 263 |
264 | 4 265 |

Start Consultation

266 |

Consult the doctor after approval.

267 |
268 |
269 |
270 |
271 | 272 | 273 | 274 | 348 | 349 | 350 | 351 | 393 | 394 | 395 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /public/userProfile.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | SwiftHealth - Patient Dashboard 18 | 19 | 20 | 21 | 22 | 23 | 38 | 39 | 168 | 169 | 170 |
171 | 172 | 196 | 197 | 198 |
200 | 201 | Home 202 | Hospitals 203 | About Us 204 | Services 205 | Contact Us 206 | Sign Up / Log In 207 |
208 | Book Appointment 209 |
210 |
211 | 212 | 213 | 214 | 215 |
216 |
217 |

My Health Dashboard

218 |

Manage your health profile and monitor your medical information

219 |
220 |
221 | 222 |
223 |
224 | 225 |
226 | 227 |
228 |
229 |
230 |

Personal Profile

231 | 232 | 233 | 234 |
235 |
236 |
237 | 238 |
239 |
240 |

241 |

242 | 243 | Bangalore, India 244 |

245 |
246 |
247 |
248 | 249 |
250 |
251 |
252 |
253 | 254 |
255 |
256 |

Phone Number

257 |

258 |
259 |
260 | 261 |
262 |
263 | 264 |
265 |
266 |

Email Address

267 |

268 |
269 |
270 | 271 |
272 |
273 | 274 |
275 |
276 |

Date of Birth

277 |

278 |
279 |
280 | 281 |
282 |
283 | 284 |
285 |
286 |

Gender

287 |

288 |
289 |
290 | 291 |
292 |
293 | 294 |
295 |
296 |

Emergency Contact

297 |

298 |
299 |
300 | 301 |
302 |
303 | 304 |
305 |
306 |

Complete Address

307 |

308 |
309 |
310 |
311 | 312 | 313 | 318 |
319 |
320 |
321 | 322 | 323 |
324 | 325 |
326 |
327 |
328 |

Health Metrics

329 | Last updated: 02 Apr 2025 330 |
331 | 332 |
333 |
334 |
335 | 336 |
337 | HEIGHT 338 | 339 |
340 | 341 |
342 |
343 | 344 |
345 | WEIGHT 346 | 347 |
348 | 349 |
350 |
351 | 352 |
353 | BLOOD GROUP 354 | 355 |
356 |
357 | 358 |
359 |
360 |
361 | Body Mass Index (BMI) 362 | Normal 363 |
364 | 22.4 365 |
366 |
367 |
368 |
369 |
370 | Underweight 371 | Normal 372 | Overweight 373 | Obese 374 |
375 |
376 |
377 |
378 | 379 | 380 |
381 |
382 |
383 |

Medical History

384 |
385 | 386 |
    387 | 388 |
389 |
390 |
391 |
392 |
393 |
394 | 395 | 396 | 430 |
431 | 432 | 444 | 445 | --------------------------------------------------------------------------------