From 66609ab25d83d348bcc678bc2f862b8c7a4e77af Mon Sep 17 00:00:00 2001 From: John Ahlroos Date: Sun, 9 Nov 2025 17:33:37 +0100 Subject: [PATCH] Fully working app --- .../java/com/project/back_end/DTO/Login.java | 33 ++--- .../back_end/controllers/AdminController.java | 43 +++--- .../controllers/AppointmentController.java | 106 +++++++++----- .../controllers/DoctorController.java | 134 ++++++++++++------ .../controllers/PatientController.java | 108 ++++++++------ .../controllers/PrescriptionController.java | 60 ++++---- .../com/project/back_end/models/Admin.java | 21 +-- .../project/back_end/models/Appointment.java | 2 - .../com/project/back_end/models/Doctor.java | 43 ++---- .../com/project/back_end/models/Patient.java | 35 +---- .../project/back_end/models/Prescription.java | 28 ++-- .../back_end/mvc/DashboardController.java | 7 +- .../back_end/repo/AppointmentRepository.java | 1 + .../back_end/services/AppointmentService.java | 105 ++++++++------ .../back_end/services/DoctorService.java | 90 +++++++----- .../back_end/services/PatientService.java | 22 +-- .../services/PrescriptionService.java | 9 +- .../project/back_end/services/Service.java | 22 +-- .../back_end/services/TokenService.java | 13 +- .../project/back_end/utils/PasswordUtil.java | 18 +++ .../static/assets/css/adminDashboard.css | 3 +- .../static/assets/css/doctorDashboard.css | 3 +- .../resources/static/assets/css/index.css | 3 +- .../app/src/main/resources/static/index.html | 10 +- .../resources/static/js/adminDashboard.js | 48 +++---- .../static/js/components/doctorCard.js | 8 +- .../resources/static/js/components/header.js | 15 +- .../resources/static/js/components/modals.js | 33 +++-- .../static/js/components/patientRows.js | 2 +- .../resources/static/js/doctorDashboard.js | 86 +++-------- .../main/resources/static/js/loggedPatient.js | 21 +-- .../resources/static/js/patientDashboard.js | 33 ++--- .../src/main/resources/static/js/render.js | 26 ++-- .../js/services/appointmentRecordService.js | 19 ++- .../static/js/services/doctorServices.js | 16 ++- .../resources/static/js/services/index.js | 12 +- .../static/js/services/patientServices.js | 2 + .../static/pages/loggedPatientDashboard.html | 6 +- .../static/pages/patientAppointments.html | 18 +-- .../static/pages/patientDashboard.html | 3 +- .../templates/admin/adminDashboard.html | 44 ++++-- .../templates/doctor/doctorDashboard.html | 19 +-- 42 files changed, 710 insertions(+), 620 deletions(-) create mode 100644 SmartClinicManagementSystem/app/src/main/java/com/project/back_end/utils/PasswordUtil.java diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/DTO/Login.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/DTO/Login.java index cac8921..0582e47 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/DTO/Login.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/DTO/Login.java @@ -1,17 +1,24 @@ package com.project.back_end.DTO; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.project.back_end.utils.PasswordUtil; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; public class Login { + @NotNull + @Size(min = 3) + @JsonAlias({"email","username"}) private String identifier; + + @NotNull + @Size(min = 3) private String password; public Login(String identifier, String password) { this.identifier = identifier; - this.password = hashPassword(password); + this.password = PasswordUtil.hashPassword(password); } public String getIdentifier() { @@ -21,22 +28,4 @@ public class Login { public String getPassword() { return password; } - - public void setIdentifier(String identifier) { - this.identifier = identifier; - } - - public void setPassword(String password) { - this.password = password; - } - - private static String hashPassword(String rawPassword) { - try { - return new String(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } - - } \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/AdminController.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/AdminController.java index d63ba40..12a7a4c 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/AdminController.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/AdminController.java @@ -1,27 +1,30 @@ package com.project.back_end.controllers; +import com.project.back_end.DTO.Login; +import com.project.back_end.models.Admin; +import com.project.back_end.services.Service; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("${api.path}" + "admin") public class AdminController { -// 1. Set Up the Controller Class: -// - Annotate the class with `@RestController` to indicate that it's a REST controller, used to handle web requests and return JSON responses. -// - Use `@RequestMapping("${api.path}admin")` to define a base path for all endpoints in this controller. -// - This allows the use of an external property (`api.path`) for flexible configuration of endpoint paths. + private final Service service; + public AdminController(Service service) { + this.service = service; + } -// 2. Autowire Service Dependency: -// - Use constructor injection to autowire the `Service` class. -// - The service handles core logic related to admin validation and token checking. -// - This promotes cleaner code and separation of concerns between the controller and business logic layer. - - -// 3. Define the `adminLogin` Method: -// - Handles HTTP POST requests for admin login functionality. -// - Accepts an `Admin` object in the request body, which contains login credentials. -// - Delegates authentication logic to the `validateAdmin` method in the service layer. -// - Returns a `ResponseEntity` with a `Map` containing login status or messages. - - - -} - + @PostMapping + public ResponseEntity> adminLogin(@Valid @RequestBody Login login) { + return service.validateAdminLogin(login); + } +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/AppointmentController.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/AppointmentController.java index 4706eee..ce82a80 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/AppointmentController.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/AppointmentController.java @@ -1,48 +1,78 @@ package com.project.back_end.controllers; +import com.project.back_end.models.Appointment; +import com.project.back_end.services.AppointmentService; +import com.project.back_end.services.Service; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; +import java.util.Map; + +import static org.springframework.http.ResponseEntity.*; + +@RestController +@RequestMapping("/appointments") public class AppointmentController { -// 1. Set Up the Controller Class: -// - Annotate the class with `@RestController` to define it as a REST API controller. -// - Use `@RequestMapping("/appointments")` to set a base path for all appointment-related endpoints. -// - This centralizes all routes that deal with booking, updating, retrieving, and canceling appointments. + private final AppointmentService appointmentService; + private final Service service; + public AppointmentController(AppointmentService appointmentService, Service service) { + this.appointmentService = appointmentService; + this.service = service; + } -// 2. Autowire Dependencies: -// - Inject `AppointmentService` for handling the business logic specific to appointments. -// - Inject the general `Service` class, which provides shared functionality like token validation and appointment checks. + @GetMapping("/{date}/{patientName}/{token}") + public ResponseEntity> getAppointments(@PathVariable LocalDate date, @PathVariable String patientName, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "doctor"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + return ResponseEntity.ok(appointmentService.getAppointment(patientName.equals("null") ? null : patientName, date, token)); + } + @PostMapping("/{token}") + public ResponseEntity> bookAppointment(@Valid @RequestBody Appointment appointment, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "patient"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + int validationResult = service.validateAppointment(appointment); + return switch (validationResult) { + case -1 -> badRequest().body(Map.of("success", false,"message", "Doctor not found")); + case 0 -> badRequest().body(Map.of("success", false, "message", "Time has already been reserved")); + case 1 -> { + int saveResult = appointmentService.bookAppointment(appointment); + if (saveResult == 0) { + yield internalServerError().body(Map.of("success", false,"message", "Failed to save appointment!")); + } else if (saveResult == 1){ + yield status(HttpStatus.CREATED).body(Map.of("success", true,"message", "Appointment booked!")); + } else { + yield internalServerError().body(Map.of("success", false,"message", "Internal Server error")); + } + } + default -> internalServerError().body(Map.of("success", false,"message", "Internal Server error")); + }; + } -// 3. Define the `getAppointments` Method: -// - Handles HTTP GET requests to fetch appointments based on date and patient name. -// - Takes the appointment date, patient name, and token as path variables. -// - First validates the token for role `"doctor"` using the `Service`. -// - If the token is valid, returns appointments for the given patient on the specified date. -// - If the token is invalid or expired, responds with the appropriate message and status code. + @PutMapping("/{token}") + public ResponseEntity> updateAppointment(@Valid @RequestBody Appointment appointment, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "patient"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + return appointmentService.updateAppointment(appointment); + } - -// 4. Define the `bookAppointment` Method: -// - Handles HTTP POST requests to create a new appointment. -// - Accepts a validated `Appointment` object in the request body and a token as a path variable. -// - Validates the token for the `"patient"` role. -// - Uses service logic to validate the appointment data (e.g., check for doctor availability and time conflicts). -// - Returns success if booked, or appropriate error messages if the doctor ID is invalid or the slot is already taken. - - -// 5. Define the `updateAppointment` Method: -// - Handles HTTP PUT requests to modify an existing appointment. -// - Accepts a validated `Appointment` object and a token as input. -// - Validates the token for `"patient"` role. -// - Delegates the update logic to the `AppointmentService`. -// - Returns an appropriate success or failure response based on the update result. - - -// 6. Define the `cancelAppointment` Method: -// - Handles HTTP DELETE requests to cancel a specific appointment. -// - Accepts the appointment ID and a token as path variables. -// - Validates the token for `"patient"` role to ensure the user is authorized to cancel the appointment. -// - Calls `AppointmentService` to handle the cancellation process and returns the result. - - -} + @DeleteMapping("/{id}/{token}") + public ResponseEntity> cancelAppointment(@PathVariable long id, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "patient"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + return appointmentService.cancelAppointment(id, token); + } +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/DoctorController.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/DoctorController.java index 1c76fe5..62286b5 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/DoctorController.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/DoctorController.java @@ -1,61 +1,103 @@ package com.project.back_end.controllers; +import com.project.back_end.DTO.Login; +import com.project.back_end.models.Doctor; +import com.project.back_end.services.DoctorService; +import com.project.back_end.services.Service; +import jakarta.validation.Valid; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("${api.path}doctor") public class DoctorController { -// 1. Set Up the Controller Class: -// - Annotate the class with `@RestController` to define it as a REST controller that serves JSON responses. -// - Use `@RequestMapping("${api.path}doctor")` to prefix all endpoints with a configurable API path followed by "doctor". -// - This class manages doctor-related functionalities such as registration, login, updates, and availability. + private final DoctorService doctorService; + private final Service service; + public DoctorController(DoctorService doctorService, Service service) { + this.doctorService = doctorService; + this.service = service; + } -// 2. Autowire Dependencies: -// - Inject `DoctorService` for handling the core logic related to doctors (e.g., CRUD operations, authentication). -// - Inject the shared `Service` class for general-purpose features like token validation and filtering. + @GetMapping("/availability/{user}/{doctorId}/{date}/{token}") + public ResponseEntity> getDoctorAvailability(@PathVariable String user, @PathVariable long doctorId, @PathVariable LocalDate date, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, user); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + List doctorAvailability = doctorService.getDoctorAvailability(doctorId, date); + return ResponseEntity.ok(Map.of("availability", doctorAvailability)); + } -// 3. Define the `getDoctorAvailability` Method: -// - Handles HTTP GET requests to check a specific doctor’s availability on a given date. -// - Requires `user` type, `doctorId`, `date`, and `token` as path variables. -// - First validates the token against the user type. -// - If the token is invalid, returns an error response; otherwise, returns the availability status for the doctor. + @GetMapping + public ResponseEntity> getDoctor( + @RequestParam(required = false) String name, + @RequestParam(required = false) String time, + @RequestParam(required = false) String specialty) { + return ResponseEntity.ok(service.filterDoctor(name, specialty, time)); + } + @PostMapping("/{token}") + public ResponseEntity> saveDoctor(@Valid @RequestBody Doctor doctor, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "admin"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + int result = doctorService.saveDoctor(doctor); + return switch (result) { + case -1 -> ResponseEntity.badRequest().body(Map.of("message", "Doctor email already exists")); + case 0 -> ResponseEntity.internalServerError().body(Map.of("message", "Failed to save doctor")); + case 1 -> ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "Doctor saved successfully")); + default -> ResponseEntity.internalServerError().build(); + }; + } -// 4. Define the `getDoctor` Method: -// - Handles HTTP GET requests to retrieve a list of all doctors. -// - Returns the list within a response map under the key `"doctors"` with HTTP 200 OK status. + @PostMapping("/login") + public ResponseEntity> doctorLogin(@Valid @RequestBody Login login) { + return doctorService.validateDoctor(login); + } + @PutMapping("/{token}") + public ResponseEntity> updateDoctor(@Valid @RequestBody Doctor doctor, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "admin"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } -// 5. Define the `saveDoctor` Method: -// - Handles HTTP POST requests to register a new doctor. -// - Accepts a validated `Doctor` object in the request body and a token for authorization. -// - Validates the token for the `"admin"` role before proceeding. -// - If the doctor already exists, returns a conflict response; otherwise, adds the doctor and returns a success message. + int result = doctorService.updateDoctor(doctor); + return switch (result) { + case -1 -> ResponseEntity.badRequest().body(Map.of("message", "Doctor not found")); + case 0 -> ResponseEntity.internalServerError().body(Map.of("message", "Failed to save doctor")); + case 1 -> ResponseEntity.ok(Map.of("message", "Doctor updated successfully")); + default -> ResponseEntity.internalServerError().build(); + }; + } + @DeleteMapping("/{id}/{token}") + public ResponseEntity> deleteDoctor(@PathVariable long id, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "admin"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + int result = doctorService.deleteDoctor(id); + return switch (result) { + case -1 -> ResponseEntity.badRequest().body(Map.of("message", "Doctor not found")); + case 0 -> ResponseEntity.internalServerError().body(Map.of("message", "Failed to delete doctor")); + case 1 -> ResponseEntity.ok(Map.of("message", "Doctor deleted successfully")); + default -> ResponseEntity.internalServerError().build(); + }; + } -// 6. Define the `doctorLogin` Method: -// - Handles HTTP POST requests for doctor login. -// - Accepts a validated `Login` DTO containing credentials. -// - Delegates authentication to the `DoctorService` and returns login status and token information. - - -// 7. Define the `updateDoctor` Method: -// - Handles HTTP PUT requests to update an existing doctor's information. -// - Accepts a validated `Doctor` object and a token for authorization. -// - Token must belong to an `"admin"`. -// - If the doctor exists, updates the record and returns success; otherwise, returns not found or error messages. - - -// 8. Define the `deleteDoctor` Method: -// - Handles HTTP DELETE requests to remove a doctor by ID. -// - Requires both doctor ID and an admin token as path variables. -// - If the doctor exists, deletes the record and returns a success message; otherwise, responds with a not found or error message. - - -// 9. Define the `filter` Method: -// - Handles HTTP GET requests to filter doctors based on name, time, and specialty. -// - Accepts `name`, `time`, and `speciality` as path variables. -// - Calls the shared `Service` to perform filtering logic and returns matching doctors in the response. - - -} + @GetMapping("/filter/{name}/{time}/{specialty}") + public ResponseEntity> filter(@PathVariable String name, @PathVariable String time, @PathVariable String specialty) { + return ResponseEntity.ok(service.filterDoctor(name, specialty, time)); + } +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/PatientController.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/PatientController.java index 9cbba5f..161b7dd 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/PatientController.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/PatientController.java @@ -1,52 +1,80 @@ package com.project.back_end.controllers; +import com.project.back_end.DTO.Login; +import com.project.back_end.models.Patient; +import com.project.back_end.services.PatientService; +import com.project.back_end.services.Service; +import com.project.back_end.services.TokenService; +import jakarta.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/patient") public class PatientController { -// 1. Set Up the Controller Class: -// - Annotate the class with `@RestController` to define it as a REST API controller for patient-related operations. -// - Use `@RequestMapping("/patient")` to prefix all endpoints with `/patient`, grouping all patient functionalities under a common route. + private static final Logger log = LoggerFactory.getLogger(PatientController.class); + private final PatientService patientService; + private final Service service; + private final TokenService tokenService; + public PatientController(PatientService patientService, Service service, TokenService tokenService) { + this.patientService = patientService; + this.service = service; + this.tokenService = tokenService; + } -// 2. Autowire Dependencies: -// - Inject `PatientService` to handle patient-specific logic such as creation, retrieval, and appointments. -// - Inject the shared `Service` class for tasks like token validation and login authentication. + @GetMapping("/{token}") + public ResponseEntity> getPatient(@PathVariable String token) { + return patientService.getPatientDetails(token); + } + @PostMapping + public ResponseEntity> createPatient(@Valid @RequestBody Patient patient) { + try { + boolean validated = service.validatePatient(patient); + if (!validated) { + return ResponseEntity.ok(Map.of("message", "Patient with email id or phone no already exist")); + } + int result = patientService.createPatient(patient); + return switch (result) { + case 0 -> ResponseEntity.internalServerError().body(Map.of("success", false, "message", "Internal server error")); + case 1 -> ResponseEntity.status(HttpStatus.CREATED).body(Map.of("success", false, "message", "Signup successful")); + default -> ResponseEntity.internalServerError().body(Map.of("success", false, "message", "Unknown result")); + }; + } catch (Exception e) { + log.error("Failed to save patient", e); + return ResponseEntity.internalServerError().body(Map.of("success", false, "message", "Internal server error")); + } + } -// 3. Define the `getPatient` Method: -// - Handles HTTP GET requests to retrieve patient details using a token. -// - Validates the token for the `"patient"` role using the shared service. -// - If the token is valid, returns patient information; otherwise, returns an appropriate error message. + @PostMapping("/login") + public ResponseEntity> login(@Valid @RequestBody Login login) { + return service.validatePatientLogin(login); + } + @GetMapping("/{id}/{token}") + public ResponseEntity> getPatientAppointment(@PathVariable long id, @PathVariable String token) { + return patientService.getPatientAppointment(id, token); + } -// 4. Define the `createPatient` Method: -// - Handles HTTP POST requests for patient registration. -// - Accepts a validated `Patient` object in the request body. -// - First checks if the patient already exists using the shared service. -// - If validation passes, attempts to create the patient and returns success or error messages based on the outcome. - - -// 5. Define the `login` Method: -// - Handles HTTP POST requests for patient login. -// - Accepts a `Login` DTO containing email/username and password. -// - Delegates authentication to the `validatePatientLogin` method in the shared service. -// - Returns a response with a token or an error message depending on login success. - - -// 6. Define the `getPatientAppointment` Method: -// - Handles HTTP GET requests to fetch appointment details for a specific patient. -// - Requires the patient ID, token, and user role as path variables. -// - Validates the token using the shared service. -// - If valid, retrieves the patient's appointment data from `PatientService`; otherwise, returns a validation error. - - -// 7. Define the `filterPatientAppointment` Method: -// - Handles HTTP GET requests to filter a patient's appointments based on specific conditions. -// - Accepts filtering parameters: `condition`, `name`, and a token. -// - Token must be valid for a `"patient"` role. -// - If valid, delegates filtering logic to the shared service and returns the filtered result. - - - -} + @GetMapping("/{id}/{user}/{token}") + public ResponseEntity> getPatientAppointmentUser(@PathVariable long id, @PathVariable String user, @PathVariable String token) { + boolean validated = tokenService.validateToken(token, user); + if (!validated) { + return ResponseEntity.badRequest().body(Map.of("success", false, "message", "Invalid token")); + } + return patientService.getPatientAppointment(id, token); + } + @GetMapping("/filter/{condition}/{name}/{token}") + public ResponseEntity> filterPatientAppointment(@PathVariable String condition, @PathVariable String name, @PathVariable String token) { + return service.filterPatient(condition.equals("null") ? null : condition, name.equals("null") ? null : name, token); + } +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/PrescriptionController.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/PrescriptionController.java index 8d8b94b..3bd05df 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/PrescriptionController.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/controllers/PrescriptionController.java @@ -1,33 +1,41 @@ package com.project.back_end.controllers; +import com.project.back_end.models.Prescription; +import com.project.back_end.services.PrescriptionService; +import com.project.back_end.services.Service; +import jakarta.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("${api.path}prescription") public class PrescriptionController { - -// 1. Set Up the Controller Class: -// - Annotate the class with `@RestController` to define it as a REST API controller. -// - Use `@RequestMapping("${api.path}prescription")` to set the base path for all prescription-related endpoints. -// - This controller manages creating and retrieving prescriptions tied to appointments. + private final PrescriptionService prescriptionService; + private final Service service; -// 2. Autowire Dependencies: -// - Inject `PrescriptionService` to handle logic related to saving and fetching prescriptions. -// - Inject the shared `Service` class for token validation and role-based access control. -// - Inject `AppointmentService` to update appointment status after a prescription is issued. + public PrescriptionController(PrescriptionService prescriptionService, Service service) { + this.prescriptionService = prescriptionService; + this.service = service; + } + @PostMapping("/{token}") + public ResponseEntity> savePrescription(@Valid @RequestBody Prescription prescription, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "doctor"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + return prescriptionService.savePrescription(prescription); + } -// 3. Define the `savePrescription` Method: -// - Handles HTTP POST requests to save a new prescription for a given appointment. -// - Accepts a validated `Prescription` object in the request body and a doctor’s token as a path variable. -// - Validates the token for the `"doctor"` role. -// - If the token is valid, updates the status of the corresponding appointment to reflect that a prescription has been added. -// - Delegates the saving logic to `PrescriptionService` and returns a response indicating success or failure. - - -// 4. Define the `getPrescription` Method: -// - Handles HTTP GET requests to retrieve a prescription by its associated appointment ID. -// - Accepts the appointment ID and a doctor’s token as path variables. -// - Validates the token for the `"doctor"` role using the shared service. -// - If the token is valid, fetches the prescription using the `PrescriptionService`. -// - Returns the prescription details or an appropriate error message if validation fails. - - -} + @GetMapping("/{appointmentId}/{token}") + public ResponseEntity> getPrescription(@PathVariable long appointmentId, @PathVariable String token) { + ResponseEntity> validation = service.validateToken(token, "doctor"); + if (!validation.getStatusCode().is2xxSuccessful()) { + return validation; + } + return prescriptionService.getPrescription(appointmentId); + } +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Admin.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Admin.java index 52b9fcc..bcc3a69 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Admin.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Admin.java @@ -1,14 +1,11 @@ package com.project.back_end.models; import com.fasterxml.jackson.annotation.JsonProperty; +import com.project.back_end.utils.PasswordUtil; import jakarta.persistence.*; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - @Entity @Table(name = "admins") public class Admin { @@ -23,12 +20,14 @@ public class Admin { @NotNull @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - @Size(min = 256, max = 256) + @Size(min = 3, max = 256) private String password; + public Admin() {} + public Admin(String username, String password) { this.username = username; - this.password = hashPassword(password); + this.password = PasswordUtil.hashPassword(password); } public String getUsername() { @@ -44,14 +43,6 @@ public class Admin { } public void setPassword(String password) { - this.password = password; - } - - private static String hashPassword(String rawPassword) { - try { - return new String(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } + this.password = PasswordUtil.hashPassword(password); } } \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Appointment.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Appointment.java index 98fe1a3..77f2945 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Appointment.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Appointment.java @@ -22,12 +22,10 @@ public class Appointment { @NotNull @ManyToOne - @JsonIgnore private Doctor doctor; @NotNull @ManyToOne - @JsonIgnore private Patient patient; @NotNull diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Doctor.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Doctor.java index 82201a5..d0bc7ec 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Doctor.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Doctor.java @@ -1,16 +1,13 @@ package com.project.back_end.models; import com.fasterxml.jackson.annotation.JsonProperty; +import com.project.back_end.utils.PasswordUtil; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.List; import java.util.Set; @Entity @@ -21,32 +18,28 @@ public class Doctor { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @NotNull + @NotNull (message = "Name must not be empty") @Size(min = 3, max = 100) - @Column(unique=true) private String name; - @NotNull - @Email + @NotNull(message = "Email must not be empty") + @Email() @Column(unique=true) private String email; @NotNull @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - @Size(min = 256,max = 256) + @Size(min = 4,max = 256, message = "Password must be at least for characters") private String password; - @NotNull + @NotNull (message = "Speciality must not be empty") @Size(min = 3, max = 50) private String specialty; - @NotNull + @NotNull (message = "Phone must not be empty") @Pattern(regexp = "^[0-9]{10}$") private String phone; - @OneToMany(mappedBy = "doctor", fetch = FetchType.LAZY) - private List appointments; - @ElementCollection(fetch = FetchType.EAGER) @CollectionTable(name = "doctors_available_times", uniqueConstraints = @UniqueConstraint(columnNames = {"element_value", "doctor_id"})) @Column(name = "available_times") @@ -58,7 +51,7 @@ public class Doctor { public Doctor(String name, String email, String password) { this.name = name; this.email = email; - this.password = hashPassword(password); + this.password = PasswordUtil.hashPassword(password); } public Long getId() { @@ -77,10 +70,6 @@ public class Doctor { this.specialty = specialty; } - public void setPassword(String password) { - this.password = hashPassword(password); - } - public String getEmail() { return email; } @@ -105,12 +94,8 @@ public class Doctor { this.phone = phone; } - public List getAppointments() { - return appointments; - } - - public void setAppointments(List appointments) { - this.appointments = appointments; + public void setPassword(String password) { + this.password = PasswordUtil.hashPassword(password); } public Set getAvailableTimes() { @@ -120,12 +105,4 @@ public class Doctor { public void setAvailableTimes(Set availableTimes) { this.availableTimes = availableTimes; } - - private static String hashPassword(String rawPassword) { - try { - return new String(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } } \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Patient.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Patient.java index 109ca74..429c868 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Patient.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Patient.java @@ -1,15 +1,13 @@ package com.project.back_end.models; import com.fasterxml.jackson.annotation.JsonProperty; +import com.project.back_end.utils.PasswordUtil; import jakarta.persistence.*; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Size; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.List; @Entity @@ -22,7 +20,7 @@ public class Patient { @NotNull @Size(min = 3, max = 100) - @Column(unique=true) + @Column private String name; @NotNull @@ -32,20 +30,17 @@ public class Patient { @NotNull @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - @Size(min = 256,max = 256) + @Size(min = 4, max = 256, message = "Password must be at least 4 characters") private String password; @NotNull - @Pattern(regexp = "^[0-9]{10}$") + @Pattern(regexp = "^[0-9]{10}$", message = "Phone number must be 10 numbers") private String phone; @NotNull @Size(max = 255) private String address; - @OneToMany(mappedBy = "patient") - private List appointments; - public Patient(){} public Patient(String name, String email, String password, String phone, String address) { @@ -53,11 +48,7 @@ public class Patient { this.email = email; this.phone = phone; this.address = address; - this.password = hashPassword(password); - } - - public void setPassword(String password) { - this.password = hashPassword(password); + this.password = PasswordUtil.hashPassword(password); } public String getPassword() { @@ -80,8 +71,8 @@ public class Patient { this.address = address; } - public void setAppointments(List appointments) { - this.appointments = appointments; + public void setPassword(String password) { + this.password = PasswordUtil.hashPassword(password); } public Long getId() { @@ -103,16 +94,4 @@ public class Patient { public String getAddress() { return address; } - - public List getAppointments() { - return appointments; - } - - private static String hashPassword(String rawPassword) { - try { - return new String(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - throw new RuntimeException(e); - } - } } \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Prescription.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Prescription.java index 797be4b..6772246 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Prescription.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/models/Prescription.java @@ -1,32 +1,30 @@ package com.project.back_end.models; -import jakarta.persistence.Column; import jakarta.persistence.Id; -import jakarta.persistence.ManyToOne; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import org.springframework.data.mongodb.core.mapping.Document; import static java.util.Objects.requireNonNull; -@Document(collation = "prescriptions") +@Document(collection = "prescriptions") public class Prescription { @Id private String id; @NotNull - @Size(min = 3, max = 100) + @Size(min = 3, max = 100, message = "Patient name must be at least 3 characters") private String patientName; - @ManyToOne - private Appointment appointment; + @NotNull(message = "Appointment id must be set") + private Long appointmentId; - @NotNull - @Size(min = 3, max = 100) + @NotNull(message = "Medication cannot be null") + @Size(min = 3, max = 100, message = "Medication has to be more than 3 characters") private String medication; - @NotNull + @NotNull(message = "Dosage cannot be null") private String dosage; @Size(max = 200) @@ -34,8 +32,8 @@ public class Prescription { public Prescription() {} - public Prescription(Patient patient, Appointment appointment, String medication, String dosage, String doctorNotes) { - this.appointment = appointment; + public Prescription(Patient patient, Long appointmentId, String medication, String dosage, String doctorNotes) { + this.appointmentId = appointmentId; this.medication = medication; this.dosage = dosage; this.doctorNotes = doctorNotes; @@ -70,12 +68,12 @@ public class Prescription { this.doctorNotes = doctorNotes; } - public Appointment getAppointment() { - return appointment; + public Long getAppointmentId() { + return appointmentId; } - public void setAppointment(Appointment appointment) { - this.appointment = appointment; + public void setAppointmentId(Long appointmentId) { + this.appointmentId = appointmentId; } public String getPatientName() { diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/mvc/DashboardController.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/mvc/DashboardController.java index 2045409..ac126e9 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/mvc/DashboardController.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/mvc/DashboardController.java @@ -1,7 +1,10 @@ package com.project.back_end.mvc; +import com.project.back_end.services.Service; import com.project.back_end.services.TokenService; +import org.hibernate.annotations.View; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -19,7 +22,7 @@ public class DashboardController { if (!tokenService.validateToken(token, "admin")) { return "redirect:/"; } - return "redirect:admin/adminDashboard"; + return "admin/adminDashboard"; } @GetMapping("/doctorDashboard/{token}") @@ -27,6 +30,6 @@ public class DashboardController { if (!tokenService.validateToken(token, "doctor")) { return "redirect:/"; } - return "redirect:admin/doctorDashboard"; + return "doctor/doctorDashboard"; } } \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/repo/AppointmentRepository.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/repo/AppointmentRepository.java index f50300c..79a69f7 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/repo/AppointmentRepository.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/repo/AppointmentRepository.java @@ -16,6 +16,7 @@ public interface AppointmentRepository extends JpaRepository @Query(""" FROM Appointment a LEFT JOIN FETCH Doctor d ON a.doctor = d + LEFT JOIN FETCH Patient p on a.patient = p WHERE d.id = :doctorId AND a.appointmentTime BETWEEN :start and :end """) diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/AppointmentService.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/AppointmentService.java index 855f44d..753280e 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/AppointmentService.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/AppointmentService.java @@ -4,9 +4,11 @@ import com.project.back_end.models.Appointment; import com.project.back_end.models.Doctor; import com.project.back_end.repo.AppointmentRepository; import com.project.back_end.repo.DoctorRepository; -import com.project.back_end.repo.PatientRepository; import jakarta.transaction.Transactional; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -14,88 +16,109 @@ import org.springframework.stereotype.Service; import java.time.LocalDate; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import static java.util.Objects.requireNonNull; + @Service public class AppointmentService { + private static final Logger log = LoggerFactory.getLogger(AppointmentService.class); + private final AppointmentRepository appointmentRepository; - private final PatientRepository patientRepository; private final DoctorRepository doctorRepository; private final TokenService tokenService; private final com.project.back_end.services.Service service; - public AppointmentService(AppointmentRepository appointmentRepository, PatientRepository patientRepository, DoctorRepository doctorRepository, TokenService tokenService, com.project.back_end.services.Service service) { + public AppointmentService(AppointmentRepository appointmentRepository, + DoctorRepository doctorRepository, TokenService tokenService, + com.project.back_end.services.Service service) { this.appointmentRepository = appointmentRepository; - this.patientRepository = patientRepository; this.doctorRepository = doctorRepository; this.tokenService = tokenService; this.service = service; } @Transactional - public int bookAppointment(@Valid Appointment appointment) { + public int bookAppointment(@Valid @NotNull Appointment appointment) { try { + requireNonNull(appointment, "Appointment cannot be null"); appointmentRepository.save(appointment); return 1; } catch (Exception e) { + log.error(e.getMessage(), e); return 0; } } @Transactional - public ResponseEntity> updateAppointment(@Valid Appointment appointment) { - - if (appointmentRepository.findById(appointment.getId()).isEmpty()) { - return ResponseEntity.noContent().build(); - } - - if (service.validateAppointment(appointment) < 1) { - return ResponseEntity.badRequest().build(); - } - + public ResponseEntity> updateAppointment(@Valid @NotNull Appointment appointment) { try { + requireNonNull(appointment, "Appointment cannot be null"); + if (appointmentRepository.findById(appointment.getId()).isEmpty()) { + return ResponseEntity.noContent().build(); + } + + if (service.validateAppointment(appointment) < 1) { + return ResponseEntity.badRequest().build(); + } appointmentRepository.save(appointment); return ResponseEntity.ok(Map.of("success", "true", "message", "Updated successfully!")); } catch (Exception e) { - return ResponseEntity.ok(Map.of("success", "false", "message", e.getMessage())); + log.error(e.getMessage(), e); + return ResponseEntity.ok(Map.of("success", "false", "message", "Internal error")); } } @Transactional - public ResponseEntity> cancelAppointment(@Valid Appointment appointment, String token) { - - if (!tokenService.validateToken(token, "DOCTOR")) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - } - - if (appointmentRepository.findById(appointment.getId()).isEmpty()) { - return ResponseEntity.noContent().build(); - } - + public ResponseEntity> cancelAppointment(long id, @NotNull String token) { try { - appointmentRepository.delete(appointment); + requireNonNull(token, "Token cannot be null"); + + if (!tokenService.validateToken(token, "DOCTOR")) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } + + Optional found = appointmentRepository.findById(id); + if (found.isEmpty()) { + return ResponseEntity.badRequest().body(Map.of("success", "false", "message", "Appointment not found")); + } + + appointmentRepository.delete(found.get()); return ResponseEntity.ok(Map.of("success", "true", "message", "Removed successfully!")); } catch (Exception e) { - return ResponseEntity.ok(Map.of("success", "false", "message", e.getMessage())); + log.error(e.getMessage(), e); + return ResponseEntity.internalServerError().body(Map.of("success", "false", "message", "Internal error")); } } @Transactional - public Map getAppointment(String pname, LocalDate date, String token) { - String doctorEmail = tokenService.extractIdentifier(token); - Optional found = doctorRepository.findByEmail(doctorEmail); - if (found.isEmpty()) { - return Map.of("appointments", List.of()); - } + public Map getAppointment(String pname, @NotNull LocalDate date, @NotNull String token) { + try { + requireNonNull(date, "Date cannot be null"); + requireNonNull(token, "Token cannot be null"); + String doctorEmail = tokenService.extractIdentifier(token); + Optional found = doctorRepository.findByEmail(doctorEmail); + if (found.isEmpty()) { + return Map.of("appointments", List.of()); + } - Long doctorId = found.get().getId(); - List appointments = appointmentRepository.findByDoctorIdAndAppointmentTimeBetween( - doctorId, date.atStartOfDay(), date.plusDays(1).atStartOfDay()); - if (pname != null) { - appointments = appointments.stream().filter(a -> a.getPatientName().contains(pname)).toList(); - } + Long doctorId = found.get().getId(); - return Map.of("appointments", appointments); + List appointments; + if (pname !=null) { + appointments = appointmentRepository.findByDoctorIdAndPatient_NameContainingIgnoreCaseAndAppointmentTimeBetween( + doctorId, pname, date.atStartOfDay(), date.plusDays(1).atStartOfDay()); + } else { + appointments = appointmentRepository.findByDoctorIdAndAppointmentTimeBetween( + doctorId, date.atStartOfDay(), date.plusDays(1).atStartOfDay()); + } + return Map.of("success", "true", "appointments", appointments); + + } catch (Exception e) { + log.error(e.getMessage(), e); + return Map.of("success", "false", "message", "Internal error"); + } } } \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/DoctorService.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/DoctorService.java index c358d56..d03baed 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/DoctorService.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/DoctorService.java @@ -7,6 +7,9 @@ import com.project.back_end.repo.AppointmentRepository; import com.project.back_end.repo.DoctorRepository; import jakarta.transaction.Transactional; import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -14,15 +17,16 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport; import java.time.LocalDate; import java.time.LocalTime; -import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; import java.util.Optional; +import static java.util.Objects.requireNonNull; + @Service public class DoctorService { - private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); + private static final Logger log = LoggerFactory.getLogger(DoctorService.class); private final DoctorRepository doctorRepository; private final AppointmentRepository appointmentRepository; @@ -35,33 +39,38 @@ public class DoctorService { } @Transactional - public List getDoctorAvailability(Long doctorId, LocalDate date) { + public List getDoctorAvailability(long doctorId, @NotNull LocalDate date) { + requireNonNull(date, "Date cannot be null"); Doctor doctor = doctorRepository.findById(doctorId).orElseThrow(() -> new IllegalArgumentException("Doctor not found")); return getAvailableTimes(doctor, date); } @Transactional - public int saveDoctor(@Valid Doctor doctor) { - if (doctorRepository.findByEmail(doctor.getEmail()).isPresent()) { - return -1; - } + public int saveDoctor(@Valid @NotNull Doctor doctor) { try { + requireNonNull(doctor, "Doctor cannot be null"); + if (doctorRepository.findByEmail(doctor.getEmail()).isPresent()) { + return -1; + } doctorRepository.save(doctor); return 1; } catch (Exception e) { + log.error(e.getMessage(), e); return 0; } } @Transactional - public int updateDoctor(@Valid Doctor doctor) { - if (doctorRepository.findById(doctor.getId()).isEmpty()) { - return -1; - } + public int updateDoctor(@Valid @NotNull Doctor doctor) { try { + requireNonNull(doctor, "Doctor cannot be null"); + if (doctorRepository.findById(doctor.getId()).isEmpty()) { + return -1; + } doctorRepository.save(doctor); return 1; } catch (Exception e) { + log.error(e.getMessage(), e); return 0; } } @@ -72,13 +81,14 @@ public class DoctorService { } @Transactional - public int deleteDoctor(Doctor doctor) { - if (doctorRepository.findById(doctor.getId()).isEmpty()) { - return -1; - } + public int deleteDoctor(long id) { try { - appointmentRepository.deleteAllByDoctorId(doctor.getId()); - doctorRepository.delete(doctor); + Optional found = doctorRepository.findById(id); + if (found.isEmpty()) { + return -1; + } + appointmentRepository.deleteAllByDoctorId(id); + doctorRepository.delete(found.get()); return 1; } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); @@ -87,7 +97,7 @@ public class DoctorService { } @Transactional - public ResponseEntity> validateDoctor(Login login) { + public ResponseEntity> validateDoctor(@Valid @NotNull Login login) { Optional found = doctorRepository.findByEmail(login.getIdentifier()); if (found.isEmpty()) { return ResponseEntity.status(HttpStatus.FORBIDDEN).build(); @@ -104,12 +114,16 @@ public class DoctorService { } @Transactional - public List findDoctorByName(String name) { - return doctorRepository.findByNameLike(name); + public List findDoctorByName(@NotNull String name) { + return doctorRepository.findByNameLike(requireNonNull(name, "Name cannot be null")); } @Transactional - public Map filterDoctorsByNameSpecialityAndTime(String name, String specialty, String amOrPm) { + public Map filterDoctorsByNameSpecialityAndTime(@NotNull String name, @NotNull String specialty, @NotNull String amOrPm) { + requireNonNull(name, "Name cannot be null"); + requireNonNull(specialty, "Speciality cannot be null"); + requireNonNull(amOrPm, "Time cannot be null"); + if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) { throw new IllegalArgumentException("AM/PM only accepted"); } @@ -121,10 +135,12 @@ public class DoctorService { } @Transactional - public Map filterDoctorByTime(String amOrPm) { + public Map filterDoctorByTime(@NotNull String amOrPm) { + requireNonNull(amOrPm, "Time cannot be null"); if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) { throw new IllegalArgumentException("AM/PM only accepted"); } + List doctors = doctorRepository.findAll() .stream() .filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm))) @@ -133,10 +149,14 @@ public class DoctorService { } @Transactional - public Map filterDoctorByNameAndTime(String name, String amOrPm) { + public Map filterDoctorByNameAndTime(@NotNull String name, @NotNull String amOrPm) { + requireNonNull(name, "Name cannot be null"); + requireNonNull(amOrPm, "Time cannot be null"); + if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) { throw new IllegalArgumentException("AM/PM only accepted"); } + List doctors = doctorRepository.findByNameLike(name) .stream() .filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm))) @@ -145,13 +165,19 @@ public class DoctorService { } @Transactional - public Map filterDoctorByNameAndSpeciality(String name, String speciality) { + public Map filterDoctorByNameAndSpeciality(@NotNull String name, @NotNull String speciality) { + requireNonNull(name, "Name cannot be null"); + requireNonNull(speciality, "Speciality cannot be null"); + List doctors = doctorRepository.findByNameContainingIgnoreCaseAndSpecialtyIgnoreCase(name,speciality); return Map.of("doctors", doctors); } @Transactional - public Map filterDoctorByTimeAndSpeciality(String speciality, String amOrPm) { + public Map filterDoctorByTimeAndSpeciality(@NotNull String speciality, @NotNull String amOrPm) { + requireNonNull(speciality, "Speciality cannot be null"); + requireNonNull(amOrPm, "Time cannot be null"); + if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) { throw new IllegalArgumentException("AM/PM only accepted"); } @@ -163,22 +189,12 @@ public class DoctorService { } @Transactional - public Map filterDoctorBySpeciality(String speciality) { + public Map filterDoctorBySpeciality(@NotNull String speciality) { + requireNonNull(speciality, "Speciality cannot be null"); List doctors = doctorRepository.findBySpecialtyIgnoreCase(speciality); return Map.of("doctors", doctors); } - @Transactional - public List filterDoctorsByTime(String amOrPm) { - if (!"AM".equalsIgnoreCase(amOrPm) && !"PM".equalsIgnoreCase(amOrPm)) { - throw new IllegalArgumentException("AM/PM only accepted"); - } - return doctorRepository.findAll() - .stream() - .filter(d -> d.getAvailableTimes().stream().anyMatch(t -> matchesAMPM(t, amOrPm))) - .toList(); - } - private static boolean matchesAMPM(String time, String amOrPm) { LocalTime start = LocalTime.parse(time.substring(0, time.indexOf("-"))); return switch (amOrPm.toUpperCase()) { diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/PatientService.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/PatientService.java index 07186ed..f65caf8 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/PatientService.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/PatientService.java @@ -7,6 +7,8 @@ import com.project.back_end.repo.AppointmentRepository; import com.project.back_end.repo.PatientRepository; import jakarta.transaction.Transactional; import jakarta.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -19,6 +21,7 @@ import java.util.Optional; @Service public class PatientService { + private static final Logger log = LoggerFactory.getLogger(PatientService.class); private final PatientRepository patientRepository; private final AppointmentRepository appointmentRepository; private final TokenService tokenService; @@ -35,6 +38,7 @@ public class PatientService { patientRepository.save(patient); return 1; } catch (Exception e) { + log.error("Failed to create patient", e); return 0; } } @@ -42,7 +46,7 @@ public class PatientService { @Transactional public ResponseEntity> getPatientAppointment(Long patientId, String token) { try { - if (!tokenService.validateToken(token, "PATIENT")) { + if (!tokenService.validateToken(token, "patient")) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } @@ -57,8 +61,6 @@ public class PatientService { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } - - List appointments = appointmentRepository.findByPatientId(patientId) .stream() .map(AppointmentDTO::new) @@ -71,8 +73,8 @@ public class PatientService { @Transactional public ResponseEntity> filterByCondition(String condition, Long patientId) { - if (!"PAST".equalsIgnoreCase(condition) && !"PRESENT".equalsIgnoreCase(condition)) { - return ResponseEntity.badRequest().build(); + if (!"past".equalsIgnoreCase(condition) && !"future".equalsIgnoreCase(condition)) { + return ResponseEntity.badRequest().body(Map.of("success", false, "message","Condition must be past or future")); } try { List appointments = appointmentRepository.findByPatientId(patientId) @@ -101,7 +103,7 @@ public class PatientService { @Transactional public ResponseEntity> filterByDoctorAndCondition(String condition, String name, long patientId) { - if (!"PAST".equalsIgnoreCase(condition) && !"PRESENT".equalsIgnoreCase(condition)) { + if (!"past".equalsIgnoreCase(condition) && !"future".equalsIgnoreCase(condition)) { return ResponseEntity.badRequest().build(); } try { @@ -119,7 +121,7 @@ public class PatientService { @Transactional public ResponseEntity> getPatientDetails(String token) { try { - if (!tokenService.validateToken(token, "PATIENT")) { + if (!tokenService.validateToken(token, "patient")) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } @@ -138,9 +140,9 @@ public class PatientService { } private static boolean matchesCondition(Appointment appointment, String condition) { - return switch (condition.toUpperCase()) { - case "PAST" -> appointment.getAppointmentTime().isBefore(LocalDateTime.now()); - case "PRESENT" -> appointment.getAppointmentTime().isAfter(LocalDateTime.now()); + return switch (condition.toLowerCase()) { + case "past" -> appointment.getAppointmentTime().isBefore(LocalDateTime.now()); + case "future" -> appointment.getAppointmentTime().isAfter(LocalDateTime.now()); default -> false; }; } diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/PrescriptionService.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/PrescriptionService.java index a746c16..ec2e62a 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/PrescriptionService.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/PrescriptionService.java @@ -4,6 +4,8 @@ import com.project.back_end.models.Prescription; import com.project.back_end.repo.PrescriptionRepository; import jakarta.transaction.Transactional; import jakarta.validation.Valid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; @@ -14,6 +16,7 @@ import java.util.Map; @Service public class PrescriptionService { + private static final Logger log = LoggerFactory.getLogger(PrescriptionService.class); private final PrescriptionRepository prescriptionRepository; public PrescriptionService(PrescriptionRepository prescriptionRepository) { @@ -21,14 +24,15 @@ public class PrescriptionService { } @Transactional - public ResponseEntity> savePrescription(@Valid Prescription prescription) { + public ResponseEntity> savePrescription(@Valid Prescription prescription) { try { - if (!prescriptionRepository.findByAppointmentId(prescription.getAppointment().getId()).isEmpty()) { + if (!prescriptionRepository.findByAppointmentId(prescription.getAppointmentId()).isEmpty()) { return ResponseEntity.badRequest().body(Map.of("message", "Prescription already exists")); } prescriptionRepository.save(prescription); return ResponseEntity.status(HttpStatus.CREATED).body(Map.of("message", "Prescription saved")); } catch (Exception e) { + log.error("Failed to save prescription", e); return ResponseEntity.internalServerError().body(Map.of("message", "Internal Error")); } } @@ -39,6 +43,7 @@ public class PrescriptionService { List prescriptions = prescriptionRepository.findByAppointmentId(appointmentId); return ResponseEntity.ok(Map.of("prescriptions", prescriptions)); } catch (Exception e) { + log.error("Failed to get prescription", e); return ResponseEntity.internalServerError().body(Map.of("message", "Internal Error")); } } diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/Service.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/Service.java index 7d9447c..6df0eda 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/Service.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/Service.java @@ -37,28 +37,30 @@ public class Service { } @Transactional - public ResponseEntity> validateToken(String token, String user) { - if (tokenService.validateToken(token, user)) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + public ResponseEntity> validateToken(String token, String user) { + if (!tokenService.validateToken(token, user)) { + return ResponseEntity + .status(HttpStatus.UNAUTHORIZED) + .body(Map.of("success", false, "message", "User is not authorized!")); } - return ResponseEntity.ok().body(Map.of("message", "User is valid")); + return ResponseEntity.ok().body(Map.of("success", false, "message", "User is valid")); } @Transactional - public ResponseEntity> validateAdmin(Admin receivedAdmin) { + public ResponseEntity> validateAdminLogin(Login login) { try { - Optional found = adminRepository.findByUsername(receivedAdmin.getUsername()); + Optional found = adminRepository.findByUsername(login.getIdentifier()); if (found.isEmpty()) { return ResponseEntity.noContent().build(); } Admin admin = found.get(); - if (!receivedAdmin.getPassword().equals(admin.getPassword())) { + if (!login.getPassword().equals(admin.getPassword())) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } - String token = tokenService.generateToken(receivedAdmin.getUsername()); + String token = tokenService.generateToken(login.getIdentifier()); return ResponseEntity.ok(Map.of("token", token)); } catch (Exception e) { @@ -105,7 +107,7 @@ public class Service { } @Transactional - public ResponseEntity> validatePatientLogin(Login login) { + public ResponseEntity> validatePatientLogin(Login login) { try { Optional found = patientRepository.findByEmail(login.getIdentifier()); if (found.isEmpty()) { @@ -148,7 +150,7 @@ public class Service { } } catch (Exception e) { - return ResponseEntity.internalServerError().build(); + return ResponseEntity.internalServerError().body(Map.of("success", false, "message", "Internal Server Error")); } } } \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/TokenService.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/TokenService.java index ba97849..84d3942 100644 --- a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/TokenService.java +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/services/TokenService.java @@ -6,6 +6,8 @@ import com.project.back_end.repo.PatientRepository; import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -17,6 +19,7 @@ import java.util.Date; @Component public class TokenService { + private static final Logger log = LoggerFactory.getLogger(TokenService.class); private final AdminRepository adminRepository; private final DoctorRepository doctorRepository; private final PatientRepository patientRepository; @@ -47,7 +50,8 @@ public class TokenService { try { return jwtParser.parseSignedClaims(token).getPayload().getSubject(); } catch (Exception e) { - return null; + log.error("Failed to parse token claims", e); + return null; } } @@ -55,15 +59,20 @@ public class TokenService { try { String identifier = extractIdentifier(token); if (identifier == null) { + log.error("Failed to extract identifier from token {}", token); return false; } return switch (role.toUpperCase()) { case "ADMIN" -> adminRepository.findByUsername(identifier).isPresent(); case "DOCTOR" -> doctorRepository.findByEmail(identifier).isPresent(); case "PATIENT" -> patientRepository.findByEmail(identifier).isPresent(); - default -> false; + default -> { + log.error("Role {} unknown", role); + yield false; + } }; } catch (Exception e) { + log.error("Failed to validate token.", e); return false; } } diff --git a/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/utils/PasswordUtil.java b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/utils/PasswordUtil.java new file mode 100644 index 0000000..db6033e --- /dev/null +++ b/SmartClinicManagementSystem/app/src/main/java/com/project/back_end/utils/PasswordUtil.java @@ -0,0 +1,18 @@ +package com.project.back_end.utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HexFormat; + +public class PasswordUtil { + + private PasswordUtil(){} + + public static String hashPassword(String rawPassword) { + try { + return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(rawPassword.getBytes())); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/adminDashboard.css b/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/adminDashboard.css index b9f5d1c..972f8bc 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/adminDashboard.css +++ b/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/adminDashboard.css @@ -25,9 +25,8 @@ h2 { .main-content { flex-grow: 1; padding: 40px; - display: flex; text-align: center; - background-image: url("index.png"); + background-image: url("/assets/images/defineRole/index.png"); background-size: cover; background-position: center; background-repeat: no-repeat; diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/doctorDashboard.css b/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/doctorDashboard.css index 7aeb761..f5526e9 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/doctorDashboard.css +++ b/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/doctorDashboard.css @@ -18,9 +18,8 @@ html, body { .main-content { flex-grow: 1; padding: 40px; - display: flex; text-align: center; - background-image: url("index.png"); + background-image: url("/assets/images/defineRole/index.png"); background-size: cover; background-position: center; background-repeat: no-repeat; diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/index.css b/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/index.css index 3f7571c..afdf13b 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/index.css +++ b/SmartClinicManagementSystem/app/src/main/resources/static/assets/css/index.css @@ -24,9 +24,10 @@ h2 { } .main-content { + flex-grow: 1; padding: 40px; text-align: center; - background-image: url("index.png"); + background-image: url("/assets/images/defineRole/index.png"); background-size: cover; background-position: center; background-repeat: no-repeat; diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/index.html b/SmartClinicManagementSystem/app/src/main/resources/static/index.html index 22d36db..283a9f4 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/index.html +++ b/SmartClinicManagementSystem/app/src/main/resources/static/index.html @@ -11,7 +11,7 @@ - +
@@ -19,14 +19,16 @@

Select Your Role:

- +
diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/adminDashboard.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/adminDashboard.js index d5cd48d..3e64dd3 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/adminDashboard.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/adminDashboard.js @@ -1,17 +1,12 @@ -import { openModal } from "../components/modals.js"; +import { openModal } from "./components/modals.js"; import { getDoctors, filterDoctors, saveDoctor } from "./services/doctorServices.js"; import { createDoctorCard } from "./components/doctorCard.js"; window.onload = function () { - document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange); - document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange); - document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange); - document.getElementById('addDocBtn').addEventListener('click', () => { openModal('addDoctor'); }); - loadDoctorCards(); }; -export async function loadDoctorCards() { +async function loadDoctorCards() { try { const doctors = await getDoctors(); renderDoctorCards(doctors); @@ -20,37 +15,30 @@ export async function loadDoctorCards() { } } -export async function filterDoctorsOnChange() { - const name = document.getElementById("searchBar").value; - const time = document.getElementById("filterTime").value; - const specialty = document.getElementById("filterSpecialty").value; - const doctors = filterDoctors(name, time, specialty); - renderDoctorCards(doctors); -} - -export async function renderDoctorCards(doctors) { - const contentDiv = document.getElementById("content"); - contentDiv.innerHTML = ""; - for (doctor : doctors) { - const card = createDoctorCard(doctor); - contentDiv.appendChild(card); - } -} - -export async function adminAddDoctor() { +window.adminAddDoctor = async function() { const doctor = { "name": document.getElementById("doctorName").value, "email": document.getElementById("doctorEmail").value, "password": document.getElementById("doctorPassword").value, - "speciality": document.getElementById("specialization").value, + "specialty": document.getElementById("specialization").value, "phone": document.getElementById("doctorPhone").value, - "availability": document.querySelectorAll('input[name=availability]:checked').map(e => e.value) + "availableTimes": Array.from( document.querySelectorAll('input[name=availability]:checked').values()).map(input => input.value) } - const token = localStorage.getItem("TOKEN"); + const token = localStorage.getItem("token"); if (token == 'undefined') { throw Error("No Authentication token found!"); } - - saveDoctor(doctor, token); + try { + const { success, message } = await saveDoctor(doctor, token); + if (success) { + alert(message); + document.getElementById("modal").style.display = "none"; + window.location.reload(); + } + else alert(message); + } catch(error) { + console.error("Registration failed:", error); + alert("❌ An error occurred while registering up."); + } } \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/components/doctorCard.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/components/doctorCard.js index 6cff9d9..b1357b0 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/components/doctorCard.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/components/doctorCard.js @@ -21,7 +21,7 @@ export function createDoctorCard(doctor) { email.textContent = doctor.email; const availability = document.createElement("h3"); - availability.textContent = doctor.availability.join(", "); + availability.textContent = doctor.availableTimes.join(", "); infoDiv.appendChild(name); infoDiv.appendChild(specialization); @@ -35,8 +35,8 @@ export function createDoctorCard(doctor) { const removeBtn = document.createElement("button"); removeBtn.textContent = "Delete"; removeBtn.addEventListener("click", async () => { - if (confirm("Arey you sure?") == true) { - const token = localStorage.getItem("TOKEN"); + if (confirm("Are you sure?") == true) { + const token = localStorage.getItem("token"); const result = await deleteDoctor(doctor.id, token); if (result.success) { card.remove(); @@ -57,7 +57,7 @@ export function createDoctorCard(doctor) { const bookNow = document.createElement("button"); bookNow.textContent = "Book Now"; bookNow.addEventListener("click", async (e) => { - const token = localStorage.getItem("TOKEN"); + const token = localStorage.getItem("token"); const patientData = await getPatientData(token); showBookingOverlay(e, doctor, patientData); }); diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/components/header.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/components/header.js index 8eee486..6e555b4 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/components/header.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/components/header.js @@ -14,7 +14,7 @@ function renderHeader() { } const role = localStorage.getItem("userRole"); - const token = localStorage.getItem("TOKEN"); + const token = localStorage.getItem("token"); let headerContent = `
@@ -30,7 +30,7 @@ function renderHeader() { return; } else if (role === "admin") { headerContent += ` - + Logout`; } else if (role === "doctor") { headerContent += ` @@ -67,12 +67,15 @@ function attachHeaderButtonListeners() { function logout() { localStorage.removeItem("userRole"); - localStorage.removeItem("TOKEN"); + localStorage.removeItem("token"); window.location.href = "/"; } function logoutPatient() { localStorage.removeItem("userRole"); - selectRole('patient'); - window.location.href='/pages/loggedPatientDashboard.html'; -} \ No newline at end of file + localStorage.removeItem("token"); + setRole('patient') + window.location.href='/pages/patientDashboard.html'; +} + +renderHeader(); \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/components/modals.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/components/modals.js index 78ee11d..bfc9e43 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/components/modals.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/components/modals.js @@ -6,22 +6,21 @@ export function openModal(type) {

Add Doctor

@@ -98,4 +97,4 @@ export function openModal(type) { if (type === 'doctorLogin') { document.getElementById('doctorLoginBtn').addEventListener('click', doctorLoginHandler); } -} +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/components/patientRows.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/components/patientRows.js index 7729fca..88806af 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/components/patientRows.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/components/patientRows.js @@ -20,4 +20,4 @@ export function createPatientRow(patient, appointmentId, doctorId) { }); return tr; -} +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/doctorDashboard.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/doctorDashboard.js index cb49a18..3061845 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/doctorDashboard.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/doctorDashboard.js @@ -2,23 +2,26 @@ import { getAllAppointments } from "./services/appointmentRecordService.js"; import { createPatientRow } from "./components/patientRows.js"; const patientTable = document.getElementById("patientTableBody"); -const token = localStorage.getItem("TOKEN"); +const token = localStorage.getItem("token"); -var selectedDate = new Date(); +var selectedDate = new Date().toISOString().split('T')[0]; //YYYY-MM-DD var patientName = null; window.onload = function () { document.getElementById("searchBar").addEventListener("input", filterPatientsOnChange); + document.getElementById('todayButton').addEventListener('click', () => { - selectedDate = new Date(); + selectedDate = new Date().toISOString().split('T')[0]; //YYYY-MM-DD document.getElementById('datePicker').value = selectedDate; loadAppointments(); }); - document.getElementById('datePicker').addEventListener('click', () => { + + document.getElementById('datePicker').addEventListener('change', () => { selectedDate = document.getElementById('datePicker').value; loadAppointments(); }); -} + loadAppointments(); +}; export async function filterPatientsOnChange() { patientName = document.getElementById("searchBar").value.trim(); @@ -28,75 +31,22 @@ export async function filterPatientsOnChange() { loadAppointments(); } -export async loadAppointments() { +export async function loadAppointments() { try { const appointments = await getAllAppointments(selectedDate, patientName, token); + console.log(appointments); if (appointments.length == 0) { - patientTable.innerHTML = "No Appointments found for today”; + patientTable.innerHTML = "No Patients found for the selected day"; return; } patientTable.innerHTML = ""; - for (appointment : appointments) { - const row = createPatientRow(appointment.patient, appointment.id, appointment.doctorId); - patientTable.appendChild(row); - } + appointments.forEach(appointment => { + const row = createPatientRow(appointment.patient, appointment.id, appointment.doctorId); + patientTable.appendChild(row); + }); + } catch (error) { console.error("Error: ", error); - patientTable.innerHTML = "Failed to load appointments”; + patientTable.innerHTML = "Error loading patients. Try again later."; } -} - -/* - Import getAllAppointments to fetch appointments from the backend - Import createPatientRow to generate a table row for each patient appointment - - - Get the table body where patient rows will be added - Initialize selectedDate with today's date in 'YYYY-MM-DD' format - Get the saved token from localStorage (used for authenticated API calls) - Initialize patientName to null (used for filtering by name) - - - Add an 'input' event listener to the search bar - On each keystroke: - - Trim and check the input value - - If not empty, use it as the patientName for filtering - - Else, reset patientName to "null" (as expected by backend) - - Reload the appointments list with the updated filter - - - Add a click listener to the "Today" button - When clicked: - - Set selectedDate to today's date - - Update the date picker UI to match - - Reload the appointments for today - - - Add a change event listener to the date picker - When the date changes: - - Update selectedDate with the new value - - Reload the appointments for that specific date - - - Function: loadAppointments - Purpose: Fetch and display appointments based on selected date and optional patient name - - Step 1: Call getAllAppointments with selectedDate, patientName, and token - Step 2: Clear the table body content before rendering new rows - - Step 3: If no appointments are returned: - - Display a message row: "No Appointments found for today." - - Step 4: If appointments exist: - - Loop through each appointment and construct a 'patient' object with id, name, phone, and email - - Call createPatientRow to generate a table row for the appointment - - Append each row to the table body - - Step 5: Catch and handle any errors during fetch: - - Show a message row: "Error loading appointments. Try again later." - - - When the page is fully loaded (DOMContentLoaded): - - Call renderContent() (assumes it sets up the UI layout) - - Call loadAppointments() to display today's appointments by default -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/loggedPatient.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/loggedPatient.js index 774587a..23d5937 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/loggedPatient.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/loggedPatient.js @@ -4,9 +4,11 @@ import { createDoctorCard } from './components/doctorCard.js'; import { filterDoctors } from './services/doctorServices.js'; import { bookAppointment } from './services/appointmentRecordService.js'; - document.addEventListener("DOMContentLoaded", () => { - loadDoctorCards(); + loadDoctorCards(); + document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange); + document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange); + document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange); }); function loadDoctorCards() { @@ -84,16 +86,8 @@ export function showBookingOverlay(e, doctor, patient) { }); } - - -// Filter Input -document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange); -document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange); -document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange); - - - function filterDoctorsOnChange() { + const searchBar = document.getElementById("searchBar").value.trim(); const filterTime = document.getElementById("filterTime").value; const filterSpecialty = document.getElementById("filterSpecialty").value; @@ -104,8 +98,7 @@ function filterDoctorsOnChange() { const specialty = filterSpecialty.length > 0 ? filterSpecialty : null; filterDoctors(name, time, specialty) - .then(response => { - const doctors = response.doctors; + .then(doctors => { const contentDiv = document.getElementById("content"); contentDiv.innerHTML = ""; @@ -135,4 +128,4 @@ export function renderDoctorCards(doctors) { contentDiv.appendChild(card); }); -} +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/patientDashboard.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/patientDashboard.js index 3ac97f1..bf9e813 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/patientDashboard.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/patientDashboard.js @@ -6,7 +6,6 @@ import { filterDoctors } from './services/doctorServices.js';//call the same fun import { patientSignup, patientLogin } from './services/patientServices.js'; - document.addEventListener("DOMContentLoaded", () => { loadDoctorCards(); }); @@ -27,9 +26,13 @@ document.addEventListener("DOMContentLoaded", () => { } }) +document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange); +document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange); +document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange); + function loadDoctorCards() { - getDoctors() - .then(doctors => { + const doctors = getDoctors().then(doctors => { + const contentDiv = document.getElementById("content"); contentDiv.innerHTML = ""; @@ -37,16 +40,8 @@ function loadDoctorCards() { const card = createDoctorCard(doctor); contentDiv.appendChild(card); }); - }) - .catch(error => { - console.error("Failed to load doctors:", error); - }); + }) } -// Filter Input -document.getElementById("searchBar").addEventListener("input", filterDoctorsOnChange); -document.getElementById("filterTime").addEventListener("change", filterDoctorsOnChange); -document.getElementById("filterSpecialty").addEventListener("change", filterDoctorsOnChange); - function filterDoctorsOnChange() { @@ -54,14 +49,11 @@ function filterDoctorsOnChange() { const filterTime = document.getElementById("filterTime").value; const filterSpecialty = document.getElementById("filterSpecialty").value; - const name = searchBar.length > 0 ? searchBar : null; const time = filterTime.length > 0 ? filterTime : null; const specialty = filterSpecialty.length > 0 ? filterSpecialty : null; - filterDoctors(name, time, specialty) - .then(response => { - const doctors = response.doctors; + filterDoctors(name, time, specialty).then(doctors => { const contentDiv = document.getElementById("content"); contentDiv.innerHTML = ""; @@ -75,11 +67,8 @@ function filterDoctorsOnChange() { contentDiv.innerHTML = "

No doctors found with the given filters.

"; console.log("Nothing"); } - }) - .catch(error => { - console.error("Failed to filter doctors:", error); - alert("❌ An error occurred while filtering doctors."); - }); + }) + } window.signupPatient = async function () { @@ -133,4 +122,4 @@ window.loginPatient = async function () { } -} +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/render.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/render.js index ac98111..22ebda7 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/render.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/render.js @@ -3,26 +3,28 @@ function selectRole(role) { setRole(role); const token = localStorage.getItem('token'); - if (role === "admin") { - if (token) { + if (role === "admin" && token) { window.location.href = `/adminDashboard/${token}`; - } - } if (role === "patient") { + + } else if (role === "loggedPatient" && token) { + window.location.href = "/pages/loggedPatientDashboard.html"; + + } else if (role === "patient") { window.location.href = "/pages/patientDashboard.html"; - } else if (role === "doctor") { - if (token) { - window.location.href = `/doctorDashboard/${token}`; - } else if (role === "loggedPatient") { - window.location.href = "loggedPatientDashboard.html"; - } + + } else if (role === "doctor" && token) { + window.location.href = `/doctorDashboard/${token}`; + + } else { + window.location.href = "/"; } } - function renderContent() { const role = getRole(); if (!role) { + console.error("No role set!") window.location.href = "/"; // if no role, send to role selection page return; } -} +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/services/appointmentRecordService.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/services/appointmentRecordService.js index e3b212c..06cdfd7 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/services/appointmentRecordService.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/services/appointmentRecordService.js @@ -5,12 +5,17 @@ const APPOINTMENT_API = `${API_BASE_URL}/appointments`; //This is for the doctor to get all the patient Appointments export async function getAllAppointments(date, patientName, token) { - const response = await fetch(`${APPOINTMENT_API}/${date}/${patientName}/${token}`); - if (!response.ok) { - throw new Error("Failed to fetch appointments"); - } - - return await response.json(); + try { + const response = await fetch(`${APPOINTMENT_API}/${date}/${patientName}/${token}`); + if (!response.ok) { + throw new Error("Failed to fetch appointments"); + } + const data = await response.json(); + return data.appointments; + } catch (error) { + console.error("Error while fetching appointments:", error); + return []; + } } export async function bookAppointment(appointment, token) { @@ -59,4 +64,4 @@ export async function updateAppointment(appointment, token) { message: "Network error. Please try again later." }; } -} +} \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/services/doctorServices.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/services/doctorServices.js index 143377d..dc27976 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/services/doctorServices.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/services/doctorServices.js @@ -12,7 +12,7 @@ export async function getDoctors() { if (!response.ok) { throw new Error(result.message); } - return response.doctors; + return result.doctors; } catch (error) { console.log("Error fetching doctors:", error); return []; @@ -21,7 +21,7 @@ export async function getDoctors() { export async function deleteDoctor(id, token) { try { - const response = await fetch(`${DOCTOR_API}/${id}/{token}`, { + const response = await fetch(`${DOCTOR_API}/${id}/${token}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' } }); @@ -54,9 +54,15 @@ export async function saveDoctor(doctor, token) { } } -export async function filterDoctors(name ,time ,specialty) { +export async function filterDoctors(name, time, specialty) { try { - const response = await fetch(`${DOCTOR_API}?name=${name}&time=${time}&specialty=${specialty}`, { + const url = new URL(DOCTOR_API); + + if (name) url.searchParams.append("name", name); + if (time) url.searchParams.append("time", time); + if (specialty) url.searchParams.append("specialty", specialty); + + const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }) @@ -64,7 +70,7 @@ export async function filterDoctors(name ,time ,specialty) { if (!response.ok) { throw new Error(result.message); } - return response.doctors; + return result.doctors; } catch (error) { console.error("Error :: filterDoctors :: ", error) return []; diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/services/index.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/services/index.js index 857973f..6a8dcb6 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/services/index.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/services/index.js @@ -19,6 +19,14 @@ window.onload = function () { openModal('doctorLogin'); }); } + + const patientBtn = document.getElementById('patientLogin'); + if (patientBtn) { + patientBtn.addEventListener('click', () => { + selectRole('patient'); + window.location.href = '/pages/patientDashboard.html'; + }); + } } window.adminLoginHandler = async function() { @@ -35,7 +43,7 @@ window.adminLoginHandler = async function() { if (!response.ok) { throw new Error(result.message); } - localStorage.setItem("TOKEN", result) + localStorage.setItem("token", result.token) selectRole('admin'); } catch (error) { alert("Invalid credentials!"); @@ -56,7 +64,7 @@ window.doctorLoginHandler = async function() { if (!response.ok) { throw new Error(result.message); } - localStorage.setItem("TOKEN", result) + localStorage.setItem("token", result.token) selectRole('doctor'); } catch (error) { alert("Invalid credentials!"); diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/js/services/patientServices.js b/SmartClinicManagementSystem/app/src/main/resources/static/js/services/patientServices.js index 711cf7c..7b0c79b 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/js/services/patientServices.js +++ b/SmartClinicManagementSystem/app/src/main/resources/static/js/services/patientServices.js @@ -1,5 +1,7 @@ // patientServices import { API_BASE_URL } from "../config/config.js"; + + const PATIENT_API = API_BASE_URL + '/patient' diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/pages/loggedPatientDashboard.html b/SmartClinicManagementSystem/app/src/main/resources/static/pages/loggedPatientDashboard.html index aaa93e2..19b1a92 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/pages/loggedPatientDashboard.html +++ b/SmartClinicManagementSystem/app/src/main/resources/static/pages/loggedPatientDashboard.html @@ -19,7 +19,7 @@
- +
-
+
- + \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/pages/patientAppointments.html b/SmartClinicManagementSystem/app/src/main/resources/static/pages/patientAppointments.html index 58e8ccb..ea53eb5 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/pages/patientAppointments.html +++ b/SmartClinicManagementSystem/app/src/main/resources/static/pages/patientAppointments.html @@ -22,14 +22,14 @@

Patient Appointment

- -
- -
+ +
+ +
@@ -49,4 +49,4 @@ - + \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/static/pages/patientDashboard.html b/SmartClinicManagementSystem/app/src/main/resources/static/pages/patientDashboard.html index 23dedb9..f330228 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/static/pages/patientDashboard.html +++ b/SmartClinicManagementSystem/app/src/main/resources/static/pages/patientDashboard.html @@ -13,7 +13,6 @@ - @@ -21,7 +20,7 @@
- +
- - -
+ +
+ + +
+
- - + + \ No newline at end of file diff --git a/SmartClinicManagementSystem/app/src/main/resources/templates/doctor/doctorDashboard.html b/SmartClinicManagementSystem/app/src/main/resources/templates/doctor/doctorDashboard.html index a3f9361..33ef46e 100644 --- a/SmartClinicManagementSystem/app/src/main/resources/templates/doctor/doctorDashboard.html +++ b/SmartClinicManagementSystem/app/src/main/resources/templates/doctor/doctorDashboard.html @@ -6,14 +6,11 @@ + - - - - @@ -21,11 +18,13 @@
- - - + +
+ + +
- + @@ -40,6 +39,10 @@ + + + + \ No newline at end of file
Patient ID Name