diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 7459e876..d1f07dd9 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -8,7 +8,9 @@ module org.example.petshopdesktop { opens org.example.petshopdesktop to javafx.fxml; opens org.example.petshopdesktop.controllers.dialogcontrollers to javafx.fxml; opens org.example.petshopdesktop.controllers to javafx.fxml; + opens org.example.petshopdesktop.auth to javafx.fxml; exports org.example.petshopdesktop; exports org.example.petshopdesktop.controllers; + exports org.example.petshopdesktop.auth; } \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java b/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java new file mode 100644 index 00000000..f749b5b5 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java @@ -0,0 +1,59 @@ +package org.example.petshopdesktop.DTOs; + +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; + +public class AppointmentDTO { + + private SimpleIntegerProperty appointmentId; + + private SimpleIntegerProperty customerId; + private SimpleStringProperty customerName; + + private SimpleIntegerProperty petId; + private SimpleStringProperty petName; + + private SimpleIntegerProperty serviceId; + private SimpleStringProperty serviceName; + + private SimpleStringProperty appointmentDate; + private SimpleStringProperty appointmentTime; + private SimpleStringProperty appointmentStatus; + + // Constructor + public AppointmentDTO(int appointmentId, + int customerId, String customerName, + int petId, String petName, + int serviceId, String serviceName, + String appointmentDate, + String appointmentTime, + String appointmentStatus) { + + this.appointmentId = new SimpleIntegerProperty(appointmentId); + this.customerId = new SimpleIntegerProperty(customerId); + this.customerName = new SimpleStringProperty(customerName); + this.petId = new SimpleIntegerProperty(petId); + this.petName = new SimpleStringProperty(petName); + this.serviceId = new SimpleIntegerProperty(serviceId); + this.serviceName = new SimpleStringProperty(serviceName); + this.appointmentDate = new SimpleStringProperty(appointmentDate); + this.appointmentTime = new SimpleStringProperty(appointmentTime); + this.appointmentStatus = new SimpleStringProperty(appointmentStatus); + } + + // Getters + public int getAppointmentId() { return appointmentId.get(); } + + public int getCustomerId() { return customerId.get(); } + public String getCustomerName() { return customerName.get(); } + + public int getPetId() { return petId.get(); } + public String getPetName() { return petName.get(); } + + public int getServiceId() { return serviceId.get(); } + public String getServiceName() { return serviceName.get(); } + + public String getAppointmentDate() { return appointmentDate.get(); } + public String getAppointmentTime() { return appointmentTime.get(); } + public String getAppointmentStatus() { return appointmentStatus.get(); } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/DTOs/PurchaseOrderDTO.java b/src/main/java/org/example/petshopdesktop/DTOs/PurchaseOrderDTO.java new file mode 100644 index 00000000..11c5d330 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/DTOs/PurchaseOrderDTO.java @@ -0,0 +1,25 @@ +package org.example.petshopdesktop.DTOs; + +import javafx.beans.property.*; + +public class PurchaseOrderDTO { + + private IntegerProperty purchaseOrderId; + private StringProperty supplierName; + private StringProperty orderDate; + private StringProperty status; + + public PurchaseOrderDTO(int id, String supplierName, + String orderDate, String status) { + + this.purchaseOrderId = new SimpleIntegerProperty(id); + this.supplierName = new SimpleStringProperty(supplierName); + this.orderDate = new SimpleStringProperty(orderDate); + this.status = new SimpleStringProperty(status); + } + + public int getPurchaseOrderId() { return purchaseOrderId.get(); } + public String getSupplierName() { return supplierName.get(); } + public String getOrderDate() { return orderDate.get(); } + public String getStatus() { return status.get(); } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/DTOs/ServiceDTO.java b/src/main/java/org/example/petshopdesktop/DTOs/ServiceDTO.java new file mode 100644 index 00000000..c876cd3f --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/DTOs/ServiceDTO.java @@ -0,0 +1,110 @@ +package org.example.petshopdesktop.DTOs; + +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; +import org.example.petshopdesktop.models.Service; + +/** + * The class for ServiceDTO, all service data stored here + */ +public class ServiceDTO { + + private SimpleIntegerProperty serviceId; + private SimpleStringProperty serviceName; + private SimpleStringProperty serviceDesc; + private SimpleIntegerProperty serviceDuration; + private SimpleDoubleProperty servicePrice; + + // constructor + public ServiceDTO(int serviceId, + String serviceName, + String serviceDesc, + int serviceDuration, + double servicePrice) { + + this.serviceId = new SimpleIntegerProperty(serviceId); + this.serviceName = new SimpleStringProperty(serviceName); + this.serviceDesc = new SimpleStringProperty(serviceDesc); + this.serviceDuration = new SimpleIntegerProperty(serviceDuration); + this.servicePrice = new SimpleDoubleProperty(servicePrice); + } + + // getters & setters + + public int getServiceId() { + return serviceId.get(); + } + + public SimpleIntegerProperty serviceIdProperty() { + return serviceId; + } + + public void setServiceId(int serviceId) { + this.serviceId.set(serviceId); + } + + public String getServiceName() { + return serviceName.get(); + } + + public SimpleStringProperty serviceNameProperty() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName.set(serviceName); + } + + public String getServiceDesc() { + return serviceDesc.get(); + } + + public SimpleStringProperty serviceDescProperty() { + return serviceDesc; + } + + public void setServiceDesc(String serviceDesc) { + this.serviceDesc.set(serviceDesc); + } + + public int getServiceDuration() { + return serviceDuration.get(); + } + + public SimpleIntegerProperty serviceDurationProperty() { + return serviceDuration; + } + + public void setServiceDuration(int serviceDuration) { + this.serviceDuration.set(serviceDuration); + } + + public double getServicePrice() { + return servicePrice.get(); + } + + public SimpleDoubleProperty servicePriceProperty() { + return servicePrice; + } + + public void setServicePrice(double servicePrice) { + this.servicePrice.set(servicePrice); + } + + /** + * Converts DTO into Service model (for edit/delete) + */ + public Service toService() { + + Service service = new Service( + getServiceId(), + getServiceName(), + getServiceDesc(), + getServiceDuration(), + getServicePrice() + ); + + return service; + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/PetShopApplication.java b/src/main/java/org/example/petshopdesktop/PetShopApplication.java index 8a8fcd65..06def37b 100644 --- a/src/main/java/org/example/petshopdesktop/PetShopApplication.java +++ b/src/main/java/org/example/petshopdesktop/PetShopApplication.java @@ -4,15 +4,21 @@ import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.stage.Stage; +import org.example.petshopdesktop.database.UserDB; import java.io.IOException; public class PetShopApplication extends Application { @Override public void start(Stage stage) throws IOException { - FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("main-layout-view.fxml")); + try { + UserDB.initializeTable(); + } catch (Exception e) { + System.err.println("Warning: could not initialize users table: " + e.getMessage()); + } + FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("login-view.fxml")); Scene scene = new Scene(fxmlLoader.load()); - stage.setTitle("Pet Shop Manager"); + stage.setTitle("Pet Shop Manager - Login"); stage.setScene(scene); stage.show(); } diff --git a/src/main/java/org/example/petshopdesktop/auth/Role.java b/src/main/java/org/example/petshopdesktop/auth/Role.java new file mode 100644 index 00000000..e631ddf7 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/auth/Role.java @@ -0,0 +1,13 @@ +package org.example.petshopdesktop.auth; + +/* +Petshop Desktop +Purpose: Application role definitions used by session state and role based access control. +*/ +public enum Role { + // Administrative access, includes system management screens. + ADMIN, + + // Staff access, limited to day to day operational screens. + STAFF +} diff --git a/src/main/java/org/example/petshopdesktop/auth/UserSession.java b/src/main/java/org/example/petshopdesktop/auth/UserSession.java new file mode 100644 index 00000000..2cc4538a --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/auth/UserSession.java @@ -0,0 +1,59 @@ +package org.example.petshopdesktop.auth; + +/* +Petshop Desktop +Purpose: In memory session state for the authenticated user. +Notes: Session is process local and cleared on logout or application restart. +*/ +public class UserSession { + + // Singleton instance used to share session state across controllers. + private static UserSession instance; + + // Current authenticated username, null when logged out. + private String username; + + // Current authenticated role, null when logged out. + private Role role; + + private UserSession() {} + + // Lazily initialised singleton accessor. + public static UserSession getInstance() { + if (instance == null) { + instance = new UserSession(); + } + return instance; + } + + // Stores identity and role for the active session. + public void login(String username, Role role) { + this.username = username; + this.role = role; + } + + // Clears session state and returns the application to an unauthenticated state. + public void logout() { + this.username = null; + this.role = null; + } + + public String getUsername() { + return username; + } + + public Role getRole() { + return role; + } + + // Convenience check for administrative privileges. + // Role.ADMIN.equals(role) remains safe when role is null. + public boolean isAdmin() { + return Role.ADMIN.equals(role); + } + + // Session is considered active only when both username and role are set. + public boolean isLoggedIn() { + return username != null && role != null; + } +} diff --git a/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java b/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java index 9eb6dfce..4616abcf 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java @@ -1,63 +1,135 @@ package org.example.petshopdesktop.controllers; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TextField; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.stage.Modality; +import javafx.stage.Stage; + +import org.example.petshopdesktop.DTOs.AppointmentDTO; +import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController; +import org.example.petshopdesktop.database.AppointmentDB; public class AppointmentController { - @FXML - private Button btnAdd; + @FXML private TableView tvAppointments; + + @FXML private TableColumn colAppointmentId; + @FXML private TableColumn colPetName; + @FXML private TableColumn colServiceName; + @FXML private TableColumn colAppointmentDate; + @FXML private TableColumn colAppointmentTime; + @FXML private TableColumn colCustomerName; + @FXML private TableColumn colAppointmentStatus; + + @FXML private Button btnAdd; + @FXML private Button btnEdit; + @FXML private Button btnDelete; + + @FXML private TextField txtSearch; + + private ObservableList data = FXCollections.observableArrayList(); @FXML - private Button btnDelete; + public void initialize(){ - @FXML - private Button btnEdit; + colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId")); + colPetName.setCellValueFactory(new PropertyValueFactory<>("petName")); + colServiceName.setCellValueFactory(new PropertyValueFactory<>("serviceName")); + colAppointmentDate.setCellValueFactory(new PropertyValueFactory<>("appointmentDate")); + colAppointmentTime.setCellValueFactory(new PropertyValueFactory<>("appointmentTime")); + colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName")); + colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus")); - @FXML - private TableColumn colAppointmentDate; - - @FXML - private TableColumn colAppointmentId; - - @FXML - private TableColumn colAppointmentStatus; - - @FXML - private TableColumn colAppointmentTime; - - @FXML - private TableColumn colCustomerName; - - @FXML - private TableColumn colPetName; - - @FXML - private TableColumn colServiceName; - - @FXML - private TableView tvAppointments; - - @FXML - private TextField txtSearch; - - @FXML - void btnAddClicked(ActionEvent event) { + loadAppointments(); + } + private void loadAppointments(){ + try{ + data = AppointmentDB.getAppointmentDTOs(); + tvAppointments.setItems(data); + }catch(Exception e){ + e.printStackTrace(); + } } @FXML - void btnDeleteClicked(ActionEvent event) { - + void btnAddClicked(ActionEvent event){ + openDialog(null, "Add"); } @FXML - void btnEditClicked(ActionEvent event) { + void btnEditClicked(ActionEvent event){ + AppointmentDTO selected = + tvAppointments.getSelectionModel().getSelectedItem(); + + if(selected == null){ + showAlert("Select Appointment", "Please select appointment to edit."); + return; + } + + openDialog(selected, "Edit"); } + @FXML + void btnDeleteClicked(ActionEvent event){ + + AppointmentDTO selected = + tvAppointments.getSelectionModel().getSelectedItem(); + + if(selected == null) return; + + try{ + AppointmentDB.deleteAppointment(selected.getAppointmentId()); + loadAppointments(); + }catch(Exception e){ + e.printStackTrace(); + } + } + + private void openDialog(AppointmentDTO appt, String mode){ + + try{ + FXMLLoader loader = new FXMLLoader( + getClass().getResource( + "/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml" + ) + ); + + Scene scene = new Scene(loader.load()); + + AppointmentDialogController controller = + loader.getController(); + + controller.setMode(mode); + + if(mode.equals("Edit")){ + controller.displayAppointmentDetails(appt); + } + + Stage stage = new Stage(); + stage.initModality(Modality.APPLICATION_MODAL); + stage.setScene(scene); + stage.showAndWait(); + + loadAppointments(); + + }catch(Exception e){ + e.printStackTrace(); + } + } + + private void showAlert(String title, String msg){ + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(msg); + alert.showAndWait(); + } } diff --git a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java new file mode 100644 index 00000000..440c5686 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java @@ -0,0 +1,76 @@ +package org.example.petshopdesktop.controllers; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.stage.Stage; +import org.example.petshopdesktop.auth.UserSession; +import org.example.petshopdesktop.database.UserDB; +import org.example.petshopdesktop.models.User; + +import java.sql.SQLException; + +/* +Petshop Desktop +Purpose: Authentication controller responsible for validating credentials and initialising the user session. +*/ +public class LoginController { + + @FXML + private TextField txtUsername; + + @FXML + private PasswordField txtPassword; + + @FXML + private Label lblError; + + @FXML + void btnLoginClicked(ActionEvent event) { + // Input normalisation keeps authentication behaviour consistent. + String username = txtUsername.getText().trim(); + String password = txtPassword.getText(); + + // Basic validation to avoid unnecessary database calls. + if (username.isEmpty() || password.isEmpty()) { + lblError.setText("Please enter username and password."); + return; + } + + try { + // Credential verification returns a fully populated User on success. + User user = UserDB.authenticate(username, password); + if (user == null) { + lblError.setText("Invalid username or password."); + txtPassword.clear(); + return; + } + + // Session state is stored in memory for use by controllers and UI RBAC. + UserSession.getInstance().login(user.getUsername(), user.getRole()); + openMainLayout(); + + } catch (SQLException e) { + lblError.setText("Database error: " + e.getMessage()); + } + } + + private void openMainLayout() { + try { + // View transition into the post login application shell. + FXMLLoader loader = new FXMLLoader( + getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml")); + Scene scene = new Scene(loader.load()); + Stage stage = (Stage) txtUsername.getScene().getWindow(); + stage.setScene(scene); + stage.setTitle("Pet Shop Manager"); + } catch (Exception e) { + lblError.setText("Error loading application: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java b/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java index 431d38d4..8198d517 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java @@ -4,9 +4,17 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; +import javafx.scene.Scene; import javafx.scene.control.Button; +import javafx.scene.control.Label; import javafx.scene.layout.StackPane; +import javafx.stage.Stage; +import org.example.petshopdesktop.auth.UserSession; +/* +Petshop Desktop +Purpose: Main application shell controller, includes navigation and UI level role based access control. +*/ public class MainLayoutController { @FXML @@ -18,6 +26,9 @@ public class MainLayoutController { @FXML private Button btnInventory; + @FXML + private Button btnLogout; + @FXML private Button btnPets; @@ -30,12 +41,21 @@ public class MainLayoutController { @FXML private Button btnSalesHistory; + @FXML + private Button btnPurchaseOrders; + @FXML private Button btnServices; @FXML private Button btnSuppliers; + @FXML + private Label lblUsername; + + @FXML + private Label lblRole; + @FXML private StackPane spContentArea; @@ -81,6 +101,12 @@ public class MainLayoutController { updateButtons(btnSalesHistory); } + @FXML + void btnPurchaseOrdersClicked() { + loadView("purchase-order-view.fxml"); + updateButtons(btnPurchaseOrders); + } + @FXML void btnServicesClicked(ActionEvent event) { loadView("service-view.fxml"); @@ -91,13 +117,57 @@ public class MainLayoutController { void btnSuppliersClicked(ActionEvent event) { loadView("supplier-view.fxml"); updateButtons(btnSuppliers); + + } + + + + @FXML + void btnLogoutClicked(ActionEvent event) { + // Logout clears session state before returning to the login view. + UserSession.getInstance().logout(); + try { + FXMLLoader loader = new FXMLLoader( + getClass().getResource("/org/example/petshopdesktop/login-view.fxml")); + Scene scene = new Scene(loader.load()); + Stage stage = (Stage) btnLogout.getScene().getWindow(); + stage.setScene(scene); + stage.setTitle("Pet Shop Manager - Login"); + } catch (Exception e) { + System.err.println("Error loading login view: " + e.getMessage()); + e.printStackTrace(); + } } @FXML public void initialize() { + // RBAC state is applied once during initial layout load. + applyRBAC(); + + // Default landing view after successful authentication. loadView("pet-view.fxml"); } + private void applyRBAC() { + UserSession session = UserSession.getInstance(); + + // Session identity is displayed in the header for clarity and auditing. + lblUsername.setText(session.getUsername()); + lblRole.setText(session.getRole().toString()); + + // UI level RBAC hides admin only navigation entries for non admin users. + // setManaged(false) removes the node from layout calculations to avoid empty spacing. + boolean isAdmin = session.isAdmin(); + btnInventory.setVisible(isAdmin); + btnInventory.setManaged(isAdmin); + btnSuppliers.setVisible(isAdmin); + btnSuppliers.setManaged(isAdmin); + btnProductSuppliers.setVisible(isAdmin); + btnProductSuppliers.setManaged(isAdmin); + + // Privileged operations should still be enforced within the relevant controllers and database methods. + } + /** * Load a view when a button is clicked on the navigation * @param fxmlFile the fxmlFile name to be loaded diff --git a/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java b/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java new file mode 100644 index 00000000..0f0246bd --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java @@ -0,0 +1,54 @@ +package org.example.petshopdesktop.controllers; + +import javafx.fxml.FXML; +import javafx.scene.control.*; +import javafx.scene.control.cell.PropertyValueFactory; +import org.example.petshopdesktop.DTOs.PurchaseOrderDTO; +import org.example.petshopdesktop.database.PurchaseOrderDB; + +public class PurchaseOrderController { + + @FXML private Button btnRefresh; + + @FXML private TableView tvPurchaseOrders; + + @FXML private TableColumn colOrderId; + @FXML private TableColumn colSupplier; + @FXML private TableColumn colOrderDate; + @FXML private TableColumn colStatus; + + @FXML + public void initialize() { + + colOrderId.setCellValueFactory( + new PropertyValueFactory<>("purchaseOrderId")); + + colSupplier.setCellValueFactory( + new PropertyValueFactory<>("supplierName")); + + colOrderDate.setCellValueFactory( + new PropertyValueFactory<>("orderDate")); + + colStatus.setCellValueFactory( + new PropertyValueFactory<>("status")); + + loadPurchaseOrders(); + } + + private void loadPurchaseOrders() { + try { + tvPurchaseOrders.setItems( + PurchaseOrderDB.getPurchaseOrders() + ); + } catch (Exception e) { + e.printStackTrace(); + new Alert(Alert.AlertType.ERROR, + "Unable to load purchase orders").showAndWait(); + } + } + + @FXML + void btnRefresh() { + loadPurchaseOrders(); + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java b/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java index 65f73c7d..c227fbbc 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java @@ -2,56 +2,123 @@ package org.example.petshopdesktop.controllers; import javafx.event.ActionEvent; import javafx.fxml.FXML; -import javafx.scene.control.Button; -import javafx.scene.control.TableColumn; -import javafx.scene.control.TableView; -import javafx.scene.control.TextField; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.*; +import javafx.scene.input.MouseEvent; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.stage.Stage; +import org.example.petshopdesktop.database.ServiceDB; +import org.example.petshopdesktop.models.Service; +import org.example.petshopdesktop.controllers.dialogcontrollers.ServiceDialogController; +import javafx.stage.Modality; + public class ServiceController { - @FXML - private Button btnAdd; + @FXML private Button btnAdd; + @FXML private Button btnDelete; + @FXML private Button btnEdit; + + @FXML private TableColumn colServiceId; + @FXML private TableColumn colServiceName; + @FXML private TableColumn colServiceDesc; + @FXML private TableColumn colServiceDuration; + @FXML private TableColumn colServicePrice; + + @FXML private TableView tvServices; + + @FXML private TextField txtSearch; @FXML - private Button btnDelete; + public void initialize() { - @FXML - private Button btnEdit; + colServiceId.setCellValueFactory(new PropertyValueFactory<>("serviceId")); + colServiceName.setCellValueFactory(new PropertyValueFactory<>("serviceName")); + colServiceDesc.setCellValueFactory(new PropertyValueFactory<>("serviceDesc")); + colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration")); + colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice")); - @FXML - private TableColumn colServiceDesc; + loadServices(); + } - @FXML - private TableColumn colServiceDuration; + private void loadServices() { + try { + tvServices.setItems(ServiceDB.getServices()); + } catch (Exception e) { + showAlert("Database Error", "Unable to load services."); + e.printStackTrace(); + } + } - @FXML - private TableColumn colServiceId; - - @FXML - private TableColumn colServiceName; - - @FXML - private TableColumn colServicePrice; - - @FXML - private TableView tvServices; - - @FXML - private TextField txtSearch; @FXML void btnAddClicked(ActionEvent event) { - - } - - @FXML - void btnDeleteClicked(ActionEvent event) { - + openDialog(null, "Add"); + loadServices(); } @FXML void btnEditClicked(ActionEvent event) { + Service selected = tvServices.getSelectionModel().getSelectedItem(); + + if (selected == null) { + showAlert("Select Service", "Please select a service to edit."); + return; + } + + openDialog(selected, "Edit"); + loadServices(); } -} + @FXML + void btnDeleteClicked(ActionEvent e) { + + Service service = tvServices.getSelectionModel().getSelectedItem(); + if (service == null) return; + + try { + ServiceDB.deleteService(service.getServiceId()); + loadServices(); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private void openDialog(Service service, String mode) { + + try { + FXMLLoader loader = new FXMLLoader( + getClass().getResource("/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml") + ); + + Stage stage = new Stage(); + stage.setScene(new Scene(loader.load())); + + ServiceDialogController controller = loader.getController(); + controller.setMode(mode); + + if (mode.equals("Edit")) { + controller.setService(service); + } + + stage.initModality(Modality.APPLICATION_MODAL); + stage.showAndWait(); + + loadServices(); + + } catch (Exception e) { + e.printStackTrace(); + } + } + private void showAlert(String title, String msg) { + Alert alert = new Alert(Alert.AlertType.INFORMATION); + alert.setTitle(title); + alert.setHeaderText(null); + alert.setContentText(msg); + alert.showAndWait(); + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java b/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java new file mode 100644 index 00000000..61fa04e8 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java @@ -0,0 +1,209 @@ +package org.example.petshopdesktop.controllers.dialogcontrollers; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.*; +import javafx.scene.input.MouseEvent; +import javafx.stage.Stage; +import javafx.scene.control.ListCell; + +import org.example.petshopdesktop.DTOs.AppointmentDTO; +import org.example.petshopdesktop.database.*; +import org.example.petshopdesktop.models.*; + +import java.sql.Time; + +public class AppointmentDialogController { + + // ============================ + // FXML + // ============================ + + @FXML private Button btnCancel; + @FXML private Button btnSave; + + @FXML private ComboBox cbService; + @FXML private ComboBox cbCustomer; + @FXML private ComboBox cbPet; + + @FXML private ComboBox cbHour; + @FXML private ComboBox cbMinute; + + @FXML private ComboBox cbAppointmentStatus; + @FXML private DatePicker dpAppointmentDate; + + @FXML private Label lblAppointmentId; + @FXML private Label lblMode; + + // ============================ + // DATA + // ============================ + + private String mode = null; // Add | Edit + private AppointmentDTO selectedAppointment = null; + + private ObservableList statusList = + FXCollections.observableArrayList( + "Booked", "Completed", "Cancelled" + ); + + // + // MODE + // + + public void setMode(String mode) { + this.mode = mode; + lblMode.setText(mode + " Appointment"); + lblAppointmentId.setVisible(!mode.equals("Add")); + } + + // + // INITIALIZE + // + + @FXML + public void initialize() { + + try { + cbService.setItems(ServiceDB.getServices()); + cbCustomer.setItems(CustomerDB.getCustomers()); + cbPet.setItems(PetDB.getPets()); + } catch (Exception e) { + e.printStackTrace(); + } + + cbAppointmentStatus.setItems(statusList); + + // Hours 9 AM - 5 PM + for (int i = 9; i <= 17; i++) { + cbHour.getItems().add(i); + } + + cbMinute.getItems().addAll(0, 15, 30, 45); + + // Show pet name + cbPet.setCellFactory(param -> new ListCell<>() { + @Override + protected void updateItem(Pet pet, boolean empty) { + super.updateItem(pet, empty); + setText(empty || pet == null ? null : pet.getPetName()); + } + }); + + cbPet.setButtonCell(new ListCell<>() { + @Override + protected void updateItem(Pet pet, boolean empty) { + super.updateItem(pet, empty); + setText(empty || pet == null ? null : pet.getPetName()); + } + }); + + btnSave.setOnMouseClicked(this::buttonSaveClicked); + btnCancel.setOnMouseClicked(this::closeStage); + } + + // + // DISPLAY FOR EDIT + // + + public void displayAppointmentDetails(AppointmentDTO appt) { + + selectedAppointment = appt; + lblAppointmentId.setText("ID: " + appt.getAppointmentId()); + + dpAppointmentDate.setValue( + java.time.LocalDate.parse(appt.getAppointmentDate()) + ); + + cbAppointmentStatus.setValue(appt.getAppointmentStatus()); + + Time time = Time.valueOf(appt.getAppointmentTime()); + cbHour.setValue(time.toLocalTime().getHour()); + cbMinute.setValue(time.toLocalTime().getMinute()); + + cbService.getItems().forEach(s -> { + if (s.getServiceId() == appt.getServiceId()) cbService.setValue(s); + }); + + cbCustomer.getItems().forEach(c -> { + if (c.getCustomerId() == appt.getCustomerId()) cbCustomer.setValue(c); + }); + + cbPet.getItems().forEach(p -> { + if (p.getPetId() == appt.getPetId()) cbPet.setValue(p); + }); + } + + // + // SAVE + // + + private void buttonSaveClicked(MouseEvent e) { + + if (cbService.getValue() == null || + cbCustomer.getValue() == null || + cbPet.getValue() == null || + dpAppointmentDate.getValue() == null || + cbHour.getValue() == null || + cbMinute.getValue() == null || + cbAppointmentStatus.getValue() == null) { + + showError("All fields are required"); + return; + } + + Time appointmentTime = + Time.valueOf(String.format( + "%02d:%02d:00", + cbHour.getValue(), + cbMinute.getValue() + )); + + Appointment appt = new Appointment( + selectedAppointment == null ? 0 : selectedAppointment.getAppointmentId(), + cbService.getValue().getServiceId(), + cbCustomer.getValue().getCustomerId(), + dpAppointmentDate.getValue().toString(), + appointmentTime.toString(), + cbAppointmentStatus.getValue() + ); + + try { + + if (mode.equals("Add")) { + int newId = AppointmentDB.insertAppointment(appt); + AppointmentDB.insertAppointmentPet(newId, cbPet.getValue().getPetId()); + } else { + AppointmentDB.updateAppointment( + selectedAppointment.getAppointmentId(), + appt, + cbPet.getValue().getPetId() + ); + } + + closeStage(e); + + } catch (Exception ex) { + ex.printStackTrace(); + showError("Error saving appointment"); + } + } + + // + // UTIL + // + + private void closeStage(MouseEvent e) { + Stage stage = (Stage) ((Node) e.getSource()).getScene().getWindow(); + stage.close(); + } + + private void showError(String msg) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setHeaderText("Input Error"); + alert.setContentText(msg); + alert.showAndWait(); + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ServiceDialogController.java b/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ServiceDialogController.java new file mode 100644 index 00000000..4d878324 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/ServiceDialogController.java @@ -0,0 +1,153 @@ +package org.example.petshopdesktop.controllers.dialogcontrollers; + + +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.input.MouseEvent; +import javafx.stage.Stage; +import org.example.petshopdesktop.database.ServiceDB; +import org.example.petshopdesktop.models.Service; +import javafx.scene.control.Alert; +import javafx.scene.control.ComboBox; + + +public class ServiceDialogController { + + @FXML + private Button btnCancel; + + @FXML + private Button btnSave; + + @FXML + private Label lblMode; + + @FXML + private Label lblServiceId; + + @FXML + private TextField txtServiceDesc; + + @FXML + private TextField txtServiceName; + + @FXML + private TextField txtServicePrice; + + @FXML + private ComboBox cbHours; + + @FXML + private ComboBox cbMinutes; + + private String mode; + private Service selectedService; + + + + @FXML + public void initialize() { + cbHours.getItems().addAll(0, 1, 2, 3, 4); + cbMinutes.getItems().addAll(0, 15, 30, 45); + btnSave.setOnAction(e -> saveService()); + btnCancel.setOnAction(e -> close()); + } + + public void setMode(String mode) { + this.mode = mode; + lblMode.setText(mode + " Service"); + + if (mode.equals("Add")) { + lblServiceId.setVisible(false); + } else { + lblServiceId.setVisible(true); + } + } + + public void setService(Service service) { + this.selectedService = service; + + lblServiceId.setText("ID: " + service.getServiceId()); + txtServiceName.setText(service.getServiceName()); + txtServiceDesc.setText(service.getServiceDesc()); + int totalMinutes = service.getServiceDuration(); + cbHours.setValue(totalMinutes / 60); + cbMinutes.setValue(totalMinutes % 60); + txtServicePrice.setText(String.valueOf(service.getServicePrice())); + } + + private void saveService() { + + String name = txtServiceName.getText(); + String desc = txtServiceDesc.getText(); + String priceText = txtServicePrice.getText(); + + Integer hours = cbHours.getValue(); + Integer minutes = cbMinutes.getValue(); + + // -------- VALIDATION -------- + if (name == null || name.isBlank()) { + showError("Service name is required."); + return; + } + + if (priceText == null || priceText.isBlank()) { + showError("Price is required."); + return; + } + + if (hours == null || minutes == null) { + showError("Please select duration."); + return; + } + + double price; + + try { + price = Double.parseDouble(priceText); + } catch (NumberFormatException e) { + showError("Price must be numeric."); + return; + } + + int duration = (hours * 60) + minutes; + + Service service = new Service( + selectedService == null ? 0 : selectedService.getServiceId(), + name, + desc, + duration, + price + ); + + try { + + if (mode.equals("Add")) { + ServiceDB.insertService(service); + } else { + ServiceDB.updateService(selectedService.getServiceId(), service); + } + + close(); + + } catch (Exception e) { + e.printStackTrace(); + showError("Database error while saving service."); + } + } + + private void showError(String msg) { + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setHeaderText("Invalid Input"); + alert.setContentText(msg); + alert.showAndWait(); + } + + private void close() { + Stage stage = (Stage) btnSave.getScene().getWindow(); + stage.close(); + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/database/AppointmentDB.java b/src/main/java/org/example/petshopdesktop/database/AppointmentDB.java new file mode 100644 index 00000000..773957ed --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/database/AppointmentDB.java @@ -0,0 +1,193 @@ +package org.example.petshopdesktop.database; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import org.example.petshopdesktop.DTOs.AppointmentDTO; +import org.example.petshopdesktop.models.Appointment; + +import java.sql.*; + +public class AppointmentDB { + + // ============================ + // GET ALL APPOINTMENTS + // ============================ + public static ObservableList getAppointmentDTOs() + throws SQLException { + + ObservableList list = + FXCollections.observableArrayList(); + + Connection conn = ConnectionDB.getConnection(); + + String sql = """ + SELECT a.appointmentId, + c.customerId, + CONCAT(c.firstName,' ',c.lastName) AS customerName, + p.petId, + p.petName, + s.serviceId, + s.serviceName, + a.appointmentDate, + a.appointmentTime, + a.appointmentStatus + FROM appointment a + JOIN customer c ON a.customerId = c.customerId + JOIN appointmentPet ap ON a.appointmentId = ap.appointmentId + JOIN pet p ON ap.petId = p.petId + JOIN service s ON a.serviceId = s.serviceId + """; + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + + while (rs.next()) { + + AppointmentDTO dto = new AppointmentDTO( + rs.getInt("appointmentId"), + + rs.getInt("customerId"), + rs.getString("customerName"), + + rs.getInt("petId"), + rs.getString("petName"), + + rs.getInt("serviceId"), + rs.getString("serviceName"), + + rs.getString("appointmentDate"), + rs.getString("appointmentTime"), + rs.getString("appointmentStatus") + ); + + list.add(dto); + } + + conn.close(); + return list; + } + + // ============================ + // INSERT APPOINTMENT + // ============================ + public static int insertAppointment(Appointment appt) + throws SQLException { + + Connection conn = ConnectionDB.getConnection(); + + String sql = """ + INSERT INTO appointment + (serviceId, customerId, appointmentDate, + appointmentTime, appointmentStatus) + VALUES (?,?,?,?,?) + """; + + PreparedStatement ps = + conn.prepareStatement(sql, + Statement.RETURN_GENERATED_KEYS); + + ps.setInt(1, appt.getServiceId()); + ps.setInt(2, appt.getCustomerId()); + ps.setString(3, appt.getAppointmentDate()); + ps.setString(4, appt.getAppointmentTime()); + ps.setString(5, appt.getAppointmentStatus()); + + ps.executeUpdate(); + + ResultSet keys = ps.getGeneratedKeys(); + int newId = 0; + + if (keys.next()) { + newId = keys.getInt(1); + } + + conn.close(); + return newId; + } + + // + // LINK PET TO APPOINTMENT + // + public static void insertAppointmentPet(int appointmentId, + int petId) + throws SQLException { + + Connection conn = ConnectionDB.getConnection(); + + String sql = + "INSERT INTO appointmentPet (appointmentId, petId) VALUES (?,?)"; + + PreparedStatement ps = conn.prepareStatement(sql); + ps.setInt(1, appointmentId); + ps.setInt(2, petId); + ps.executeUpdate(); + + conn.close(); + } + + // + // UPDATE APPOINTMENT + // + public static int updateAppointment(int id, + Appointment appt, + int petId) + throws SQLException { + + Connection conn = ConnectionDB.getConnection(); + + String sql = + "UPDATE appointment SET serviceId=?, customerId=?, " + + "appointmentDate=?, appointmentTime=?, appointmentStatus=? " + + "WHERE appointmentId=?"; + + PreparedStatement ps = conn.prepareStatement(sql); + + ps.setInt(1, appt.getServiceId()); + ps.setInt(2, appt.getCustomerId()); + ps.setString(3, appt.getAppointmentDate()); + ps.setString(4, appt.getAppointmentTime()); + ps.setString(5, appt.getAppointmentStatus()); + ps.setInt(6, id); + + ps.executeUpdate(); + + String sql2 = + "UPDATE appointmentPet SET petId=? WHERE appointmentId=?"; + + PreparedStatement ps2 = conn.prepareStatement(sql2); + ps2.setInt(1, petId); + ps2.setInt(2, id); + ps2.executeUpdate(); + + conn.close(); + return 1; + } + + // + // DELETE APPOINTMENT + // + public static int deleteAppointment(int id) + throws SQLException { + + Connection conn = ConnectionDB.getConnection(); + + PreparedStatement ps1 = + conn.prepareStatement( + "DELETE FROM appointmentPet WHERE appointmentId=?" + ); + ps1.setInt(1, id); + ps1.executeUpdate(); + + PreparedStatement ps2 = + conn.prepareStatement( + "DELETE FROM appointment WHERE appointmentId=?" + ); + ps2.setInt(1, id); + + int rows = ps2.executeUpdate(); + + conn.close(); + return rows; + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/database/CustomerDB.java b/src/main/java/org/example/petshopdesktop/database/CustomerDB.java new file mode 100644 index 00000000..abe47004 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/database/CustomerDB.java @@ -0,0 +1,43 @@ +package org.example.petshopdesktop.database; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.example.petshopdesktop.models.Customer; + +import java.sql.*; + +public class CustomerDB { + + // + // GET ALL CUSTOMERS + // + public static ObservableList getCustomers() + throws SQLException { + + ObservableList list = + FXCollections.observableArrayList(); + + Connection conn = ConnectionDB.getConnection(); + + String sql = "SELECT * FROM customer"; + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + + while(rs.next()) { + + Customer c = new Customer( + rs.getInt("customerId"), + rs.getString("firstName"), + rs.getString("lastName"), + rs.getString("email"), + rs.getString("phone") + ); + + list.add(c); + } + + conn.close(); + return list; + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/database/PurchaseOrderDB.java b/src/main/java/org/example/petshopdesktop/database/PurchaseOrderDB.java new file mode 100644 index 00000000..2dea4de4 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/database/PurchaseOrderDB.java @@ -0,0 +1,45 @@ +package org.example.petshopdesktop.database; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.example.petshopdesktop.DTOs.PurchaseOrderDTO; + +import java.sql.*; + +public class PurchaseOrderDB { + + public static ObservableList getPurchaseOrders() + throws SQLException { + + ObservableList list = + FXCollections.observableArrayList(); + + Connection conn = ConnectionDB.getConnection(); + + String sql = """ + SELECT po.purchaseOrderId, + s.supCompany, + po.orderDate, + po.status + FROM purchaseOrder po + JOIN supplier s ON po.supId = s.supId + ORDER BY po.purchaseOrderId + """; + + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + + while (rs.next()) { + + list.add(new PurchaseOrderDTO( + rs.getInt("purchaseOrderId"), + rs.getString("supCompany"), + rs.getString("orderDate"), + rs.getString("status") + )); + } + + conn.close(); + return list; + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/database/ServiceDB.java b/src/main/java/org/example/petshopdesktop/database/ServiceDB.java new file mode 100644 index 00000000..c5ca9ff7 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/database/ServiceDB.java @@ -0,0 +1,107 @@ +package org.example.petshopdesktop.database; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import org.example.petshopdesktop.models.Service; + +import java.sql.*; + +public class ServiceDB { + + // + // GET ALL SERVICES + // + public static ObservableList getServices() throws SQLException { + + ObservableList list = FXCollections.observableArrayList(); + Connection conn = ConnectionDB.getConnection(); + + String sql = "SELECT * FROM service"; + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql); + + while (rs.next()) { + + Service service = new Service( + rs.getInt("serviceId"), + rs.getString("serviceName"), + rs.getString("serviceDesc"), + rs.getInt("serviceDuration"), + rs.getDouble("servicePrice") + ); + + list.add(service); + } + + conn.close(); + return list; + } + + // + // INSERT SERVICE + // + public static int insertService(Service service) throws SQLException { + + Connection conn = ConnectionDB.getConnection(); + + String sql = + "INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) " + + "VALUES (?, ?, ?, ?)"; + + PreparedStatement stmt = conn.prepareStatement(sql); + + stmt.setString(1, service.getServiceName()); + stmt.setString(2, service.getServiceDesc()); + stmt.setInt(3, service.getServiceDuration()); + stmt.setDouble(4, service.getServicePrice()); + + int rows = stmt.executeUpdate(); + conn.close(); + + return rows; + } + + // + // UPDATE SERVICE + // + public static int updateService(int id, Service service) throws SQLException { + + Connection conn = ConnectionDB.getConnection(); + + String sql = + "UPDATE service SET " + + "serviceName=?, serviceDesc=?, serviceDuration=?, servicePrice=? " + + "WHERE serviceId=?"; + + PreparedStatement stmt = conn.prepareStatement(sql); + + stmt.setString(1, service.getServiceName()); + stmt.setString(2, service.getServiceDesc()); + stmt.setInt(3, service.getServiceDuration()); + stmt.setDouble(4, service.getServicePrice()); + stmt.setInt(5, id); + + int rows = stmt.executeUpdate(); + conn.close(); + + return rows; + } + + // + // DELETE SERVICE + // + public static int deleteService(int id) throws SQLException { + + Connection conn = ConnectionDB.getConnection(); + + String sql = "DELETE FROM service WHERE serviceId=?"; + + PreparedStatement stmt = conn.prepareStatement(sql); + stmt.setInt(1, id); + + int rows = stmt.executeUpdate(); + conn.close(); + + return rows; + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/database/UserDB.java b/src/main/java/org/example/petshopdesktop/database/UserDB.java new file mode 100644 index 00000000..3b5f78b4 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/database/UserDB.java @@ -0,0 +1,80 @@ +package org.example.petshopdesktop.database; + +import org.example.petshopdesktop.auth.Role; +import org.example.petshopdesktop.models.User; + +import java.sql.*; + +/* +Petshop Desktop +Purpose: User authentication and role lookup against the users table. +*/ +public class UserDB { + + /** + * Authenticate a user by username and password. + * Passwords are stored as SHA-256 hex digests in the database. + * + * @param username the username to authenticate + * @param password the plaintext password + * @return the User if credentials are valid, or null if authentication fails + */ + public static User authenticate(String username, String password) throws SQLException { + String sql = "SELECT user_id, username, role FROM users " + + "WHERE username = ? AND password_hash = SHA2(?, 256)"; + + try (Connection conn = ConnectionDB.getConnection(); + PreparedStatement ps = conn.prepareStatement(sql)) { + + ps.setString(1, username); + ps.setString(2, password); + + try (ResultSet rs = ps.executeQuery()) { + if (rs.next()) { + int userId = rs.getInt("user_id"); + String uname = rs.getString("username"); + + // Role values are stored in the database as strings and normalised to match the enum. + // Table constraints limit role values, Role.valueOf is expected to be safe under normal operation. + Role role = Role.valueOf(rs.getString("role").toUpperCase()); + + return new User(userId, uname, role); + } + } + } + return null; + } + + /** + * Create the users table and seed default admin/staff accounts if they do not exist. + * Passwords are stored as SHA2-256 hashes. + */ + public static void initializeTable() throws SQLException { + String createTable = """ + CREATE TABLE IF NOT EXISTS users ( + user_id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(100) NOT NULL UNIQUE, + password_hash CHAR(64) NOT NULL, + role ENUM('ADMIN','STAFF') NOT NULL + ) + """; + + // Default accounts support initial development and testing, credentials should be rotated or removed for deployment. + String seedAdmin = """ + INSERT IGNORE INTO users (username, password_hash, role) + VALUES ('admin', SHA2('admin123', 256), 'ADMIN') + """; + + String seedStaff = """ + INSERT IGNORE INTO users (username, password_hash, role) + VALUES ('staff', SHA2('staff123', 256), 'STAFF') + """; + + try (Connection conn = ConnectionDB.getConnection(); + Statement st = conn.createStatement()) { + st.executeUpdate(createTable); + st.executeUpdate(seedAdmin); + st.executeUpdate(seedStaff); + } + } +} diff --git a/src/main/java/org/example/petshopdesktop/models/Appointment.java b/src/main/java/org/example/petshopdesktop/models/Appointment.java new file mode 100644 index 00000000..7ab54292 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/models/Appointment.java @@ -0,0 +1,46 @@ +package org.example.petshopdesktop.models; + +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; + +public class Appointment { + + private SimpleIntegerProperty appointmentId; + private SimpleIntegerProperty serviceId; + private SimpleIntegerProperty customerId; + private SimpleStringProperty appointmentDate; + private SimpleStringProperty appointmentTime; + private SimpleStringProperty appointmentStatus; + + // Constructor + public Appointment(int appointmentId, + int serviceId, + int customerId, + String appointmentDate, + String appointmentTime, + String appointmentStatus) { + + this.appointmentId = new SimpleIntegerProperty(appointmentId); + this.serviceId = new SimpleIntegerProperty(serviceId); + this.customerId = new SimpleIntegerProperty(customerId); + this.appointmentDate = new SimpleStringProperty(appointmentDate); + this.appointmentTime = new SimpleStringProperty(appointmentTime); + this.appointmentStatus = new SimpleStringProperty(appointmentStatus); + } + + // Getters + public int getAppointmentId() { return appointmentId.get(); } + public int getServiceId() { return serviceId.get(); } + public int getCustomerId() { return customerId.get(); } + public String getAppointmentDate() { return appointmentDate.get(); } + public String getAppointmentTime() { return appointmentTime.get(); } + public String getAppointmentStatus() { return appointmentStatus.get(); } + + // Properties + public SimpleIntegerProperty appointmentIdProperty() { return appointmentId; } + public SimpleIntegerProperty serviceIdProperty() { return serviceId; } + public SimpleIntegerProperty customerIdProperty() { return customerId; } + public SimpleStringProperty appointmentDateProperty() { return appointmentDate; } + public SimpleStringProperty appointmentTimeProperty() { return appointmentTime; } + public SimpleStringProperty appointmentStatusProperty() { return appointmentStatus; } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/models/Customer.java b/src/main/java/org/example/petshopdesktop/models/Customer.java index e2ccd005..66602694 100644 --- a/src/main/java/org/example/petshopdesktop/models/Customer.java +++ b/src/main/java/org/example/petshopdesktop/models/Customer.java @@ -1,35 +1,75 @@ package org.example.petshopdesktop.models; -public class Customer { - private int customerId; - private String firstName; - private String lastName; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; - //Constructor - public Customer(int customerId, String firstName, String lastName) { - this.customerId = customerId; - this.firstName = firstName; - this.lastName = lastName; +public class Customer { + + private SimpleIntegerProperty customerId; + private SimpleStringProperty firstName; + private SimpleStringProperty lastName; + private SimpleStringProperty email; + private SimpleStringProperty phone; + + // Constructor + public Customer(int customerId, + String firstName, + String lastName, + String email, + String phone) { + + this.customerId = new SimpleIntegerProperty(customerId); + this.firstName = new SimpleStringProperty(firstName); + this.lastName = new SimpleStringProperty(lastName); + this.email = new SimpleStringProperty(email); + this.phone = new SimpleStringProperty(phone); } - //Returns customer ID + // Getters public int getCustomerId() { + return customerId.get(); + } + + public String getFirstName() { + return firstName.get(); + } + + public String getLastName() { + return lastName.get(); + } + + public String getEmail() { + return email.get(); + } + + public String getPhone() { + return phone.get(); + } + + // Properties (optional but useful later) + public SimpleIntegerProperty customerIdProperty() { return customerId; } - //Returns first name - public String getFirstName() { + public SimpleStringProperty firstNameProperty() { return firstName; } - //Returns last name - public String getLastName() { + public SimpleStringProperty lastNameProperty() { return lastName; } - //Returns first + last name + public SimpleStringProperty emailProperty() { + return email; + } + + public SimpleStringProperty phoneProperty() { + return phone; + } + + // This controls how customer appears in ComboBox @Override public String toString() { - return firstName + " " + lastName; + return getFirstName() + " " + getLastName(); } } diff --git a/src/main/java/org/example/petshopdesktop/models/PurchaseOrder.java b/src/main/java/org/example/petshopdesktop/models/PurchaseOrder.java new file mode 100644 index 00000000..3b1cf838 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/models/PurchaseOrder.java @@ -0,0 +1,35 @@ +package org.example.petshopdesktop.models; + +public class PurchaseOrder { + + private int purchaseOrderId; + private int supId; + private String orderDate; + private String status; + + public PurchaseOrder(int purchaseOrderId, + int supId, + String orderDate, + String status) { + this.purchaseOrderId = purchaseOrderId; + this.supId = supId; + this.orderDate = orderDate; + this.status = status; + } + + public int getPurchaseOrderId() { + return purchaseOrderId; + } + + public int getSupId() { + return supId; + } + + public String getOrderDate() { + return orderDate; + } + + public String getStatus() { + return status; + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/models/Service.java b/src/main/java/org/example/petshopdesktop/models/Service.java new file mode 100644 index 00000000..b259fc21 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/models/Service.java @@ -0,0 +1,98 @@ +package org.example.petshopdesktop.models; + +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleStringProperty; + +/** + * The class for the entity of services (contains all data relating to services) + */ +public class Service { + + private SimpleIntegerProperty serviceId; + private SimpleStringProperty serviceName; + private SimpleStringProperty serviceDesc; + private SimpleIntegerProperty serviceDuration; + private SimpleDoubleProperty servicePrice; + + // constructor + public Service(int serviceId, + String serviceName, + String serviceDesc, + int serviceDuration, + double servicePrice) { + + this.serviceId = new SimpleIntegerProperty(serviceId); + this.serviceName = new SimpleStringProperty(serviceName); + this.serviceDesc = new SimpleStringProperty(serviceDesc); + this.serviceDuration = new SimpleIntegerProperty(serviceDuration); + this.servicePrice = new SimpleDoubleProperty(servicePrice); + } + + // getters & setters + + public int getServiceId() { + return serviceId.get(); + } + + public SimpleIntegerProperty serviceIdProperty() { + return serviceId; + } + + public void setServiceId(int serviceId) { + this.serviceId.set(serviceId); + } + + public String getServiceName() { + return serviceName.get(); + } + + public SimpleStringProperty serviceNameProperty() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName.set(serviceName); + } + + public String getServiceDesc() { + return serviceDesc.get(); + } + + public SimpleStringProperty serviceDescProperty() { + return serviceDesc; + } + + public void setServiceDesc(String serviceDesc) { + this.serviceDesc.set(serviceDesc); + } + + public int getServiceDuration() { + return serviceDuration.get(); + } + + public SimpleIntegerProperty serviceDurationProperty() { + return serviceDuration; + } + + public void setServiceDuration(int serviceDuration) { + this.serviceDuration.set(serviceDuration); + } + + public double getServicePrice() { + return servicePrice.get(); + } + + public SimpleDoubleProperty servicePriceProperty() { + return servicePrice; + } + + public void setServicePrice(double servicePrice) { + this.servicePrice.set(servicePrice); + } + + @Override + public String toString() { + return getServiceName(); + } +} \ No newline at end of file diff --git a/src/main/java/org/example/petshopdesktop/models/User.java b/src/main/java/org/example/petshopdesktop/models/User.java new file mode 100644 index 00000000..02089be4 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/models/User.java @@ -0,0 +1,27 @@ +package org.example.petshopdesktop.models; + +import org.example.petshopdesktop.auth.Role; + +public class User { + private int userId; + private String username; + private Role role; + + public User(int userId, String username, Role role) { + this.userId = userId; + this.username = username; + this.role = role; + } + + public int getUserId() { + return userId; + } + + public String getUsername() { + return username; + } + + public Role getRole() { + return role; + } +} diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml new file mode 100644 index 00000000..b12bc777 --- /dev/null +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml new file mode 100644 index 00000000..327ce2d0 --- /dev/null +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/example/petshopdesktop/login-view.fxml b/src/main/resources/org/example/petshopdesktop/login-view.fxml new file mode 100644 index 00000000..65199c95 --- /dev/null +++ b/src/main/resources/org/example/petshopdesktop/login-view.fxml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml b/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml index 9579dff6..a1471b98 100644 --- a/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml @@ -16,12 +16,12 @@ - diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml index ec7e6cce..4bfd3029 100644 --- a/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml +++ b/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml @@ -66,15 +66,15 @@ - - - + + + - + diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml new file mode 100644 index 00000000..e4c3a488 --- /dev/null +++ b/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + +