From 13684f97de7616a9139561c53ec707fefb78ca2e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Wed, 25 Feb 2026 09:42:34 -0700 Subject: [PATCH] Add DELETE key and Enter-to-confirm to all tables --- .../controllers/AdoptionController.java | 31 +++++ .../controllers/AppointmentController.java | 62 +++++++++- .../controllers/InventoryController.java | 31 +++++ .../controllers/LoginController.java | 77 ++++++++++-- .../controllers/PetController.java | 31 +++++ .../controllers/ProductController.java | 31 +++++ .../ProductSupplierController.java | 31 +++++ .../controllers/PurchaseOrderController.java | 48 +++++++- .../controllers/ServiceController.java | 64 +++++++++- .../controllers/StaffAccountsController.java | 112 ++++++++++++++++++ .../controllers/SupplierController.java | 30 +++++ 11 files changed, 527 insertions(+), 21 deletions(-) create mode 100644 src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java diff --git a/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java b/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java index e5b783eb..5cd29fa5 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java @@ -13,6 +13,7 @@ import javafx.stage.Stage; import org.example.petshopdesktop.controllers.dialogcontrollers.AdoptionDialogController; import org.example.petshopdesktop.database.AdoptionDB; import org.example.petshopdesktop.models.Adoption; +import org.example.petshopdesktop.util.ActivityLogger; import java.io.IOException; import java.sql.SQLException; @@ -80,6 +81,15 @@ public class AdoptionController { txtSearch.textProperty().addListener((observable, oldValue, newValue) -> { displayFilteredAdoptions(newValue); }); + + //EventListener for DELETE key + tvAdoptions.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { + if (tvAdoptions.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + } + }); } @FXML @@ -96,6 +106,7 @@ public class AdoptionController { Alert question = new Alert(Alert.AlertType.CONFIRMATION); question.setHeaderText("Please confirm delete"); question.setContentText("Are you sure you want to delete this adoption record?"); + question.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); Optional result = question.showAndWait(); if (result.isPresent() && result.get() == ButtonType.OK) { @@ -105,12 +116,20 @@ public class AdoptionController { numRows = AdoptionDB.deleteAdoption(adoptionId); } catch (SQLIntegrityConstraintViolationException e) { + ActivityLogger.getInstance().logException( + "AdoptionController.btnDeleteClicked", + e, + "Deleting adoption (integrity constraint violation) with ID: " + adoptionId); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Database Operation Error"); alert.setContentText("Delete failed\nThe selected adoption is being referred in another table"); alert.showAndWait(); return; } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "AdoptionController.btnDeleteClicked", + e, + "Deleting adoption with ID: " + adoptionId); throw new RuntimeException(e); } @@ -152,6 +171,10 @@ public class AdoptionController { tvAdoptions.setItems(data); } } catch (Exception e) { + ActivityLogger.getInstance().logException( + "AdoptionController.displayFilteredAdoptions", + e, + "Filtering adoptions with filter: " + filter); System.out.println("Error while fetching table data: " + e.getMessage()); } } @@ -161,6 +184,10 @@ public class AdoptionController { try { data = AdoptionDB.getAdoptions(); } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "AdoptionController.displayAdoptions", + e, + "Fetching adoption data for table display"); System.out.println("Error while fetching table data: " + e.getMessage()); } tvAdoptions.setItems(data); @@ -173,6 +200,10 @@ public class AdoptionController { try { scene = new Scene(fxmlLoader.load()); } catch (IOException e) { + ActivityLogger.getInstance().logException( + "AdoptionController.openDialog", + e, + "Loading adoption dialog in " + mode + " mode"); throw new RuntimeException(e); } diff --git a/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java b/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java index 4616abcf..54e168b6 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java @@ -2,6 +2,7 @@ package org.example.petshopdesktop.controllers; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; @@ -14,6 +15,7 @@ import javafx.stage.Stage; import org.example.petshopdesktop.DTOs.AppointmentDTO; import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController; import org.example.petshopdesktop.database.AppointmentDB; +import org.example.petshopdesktop.util.ActivityLogger; public class AppointmentController { @@ -33,7 +35,8 @@ public class AppointmentController { @FXML private TextField txtSearch; - private ObservableList data = FXCollections.observableArrayList(); + private final ObservableList appointments = FXCollections.observableArrayList(); + private FilteredList filtered; @FXML public void initialize(){ @@ -46,18 +49,63 @@ public class AppointmentController { colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName")); colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus")); + filtered = new FilteredList<>(appointments, a -> true); + tvAppointments.setItems(filtered); + + if (txtSearch != null) { + txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n)); + } + + //EventListener for DELETE key + tvAppointments.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { + if (tvAppointments.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + } + }); + loadAppointments(); } private void loadAppointments(){ try{ - data = AppointmentDB.getAppointmentDTOs(); - tvAppointments.setItems(data); + appointments.setAll(AppointmentDB.getAppointmentDTOs()); }catch(Exception e){ + ActivityLogger.getInstance().logException( + "AppointmentController.loadAppointments", + e, + "Loading appointments for table display"); e.printStackTrace(); } } + private void applyFilter(String text) { + if (filtered == null) { + return; + } + + String q = text == null ? "" : text.trim().toLowerCase(); + if (q.isEmpty()) { + filtered.setPredicate(a -> true); + return; + } + + filtered.setPredicate(a -> + String.valueOf(a.getAppointmentId()).contains(q) + || safe(a.getPetName()).contains(q) + || safe(a.getServiceName()).contains(q) + || safe(a.getAppointmentDate()).contains(q) + || safe(a.getAppointmentTime()).contains(q) + || safe(a.getCustomerName()).contains(q) + || safe(a.getAppointmentStatus()).contains(q) + ); + } + + private static String safe(String v) { + return v == null ? "" : v.toLowerCase(); + } + @FXML void btnAddClicked(ActionEvent event){ openDialog(null, "Add"); @@ -89,6 +137,10 @@ public class AppointmentController { AppointmentDB.deleteAppointment(selected.getAppointmentId()); loadAppointments(); }catch(Exception e){ + ActivityLogger.getInstance().logException( + "AppointmentController.btnDeleteClicked", + e, + "Deleting appointment with ID: " + selected.getAppointmentId()); e.printStackTrace(); } } @@ -121,6 +173,10 @@ public class AppointmentController { loadAppointments(); }catch(Exception e){ + ActivityLogger.getInstance().logException( + "AppointmentController.openDialog", + e, + "Opening appointment dialog in " + mode + " mode"); e.printStackTrace(); } } diff --git a/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java b/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java index 43000c51..96fa947a 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java @@ -13,6 +13,7 @@ import javafx.stage.Stage; import org.example.petshopdesktop.controllers.dialogcontrollers.InventoryDialogController; import org.example.petshopdesktop.database.InventoryDB; import org.example.petshopdesktop.models.Inventory; +import org.example.petshopdesktop.util.ActivityLogger; import java.io.IOException; import java.sql.SQLException; @@ -79,6 +80,15 @@ public class InventoryController { txtSearch.textProperty().addListener((observable, oldValue, newValue) -> { displayFilteredInventory(newValue); }); + + //EventListener for DELETE key + tvInventory.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { + if (tvInventory.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + } + }); } //Opens dialog in add mode @@ -98,6 +108,7 @@ public class InventoryController { Alert question = new Alert(Alert.AlertType.CONFIRMATION); question.setHeaderText("Please confirm delete"); question.setContentText("Are you sure you want to delete this inventory record?"); + question.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); Optional result = question.showAndWait(); //If user confirms, proceed with trying to delete... @@ -109,6 +120,10 @@ public class InventoryController { } catch (SQLIntegrityConstraintViolationException e) { + ActivityLogger.getInstance().logException( + "InventoryController.btnDeleteClicked", + e, + "Deleting inventory (integrity constraint violation) with ID: " + inventoryId); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Database Operation Error"); alert.setContentText("Delete failed\nThe selected inventory record is being referred in another table"); @@ -117,6 +132,10 @@ public class InventoryController { } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "InventoryController.btnDeleteClicked", + e, + "Deleting inventory with ID: " + inventoryId); throw new RuntimeException(e); } @@ -170,6 +189,10 @@ public class InventoryController { } catch (Exception e) { + ActivityLogger.getInstance().logException( + "InventoryController.displayFilteredInventory", + e, + "Filtering inventory with filter: " + filter); System.out.println("Error while fetching table data: " + e.getMessage()); } } @@ -182,6 +205,10 @@ public class InventoryController { } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "InventoryController.displayInventory", + e, + "Fetching inventory data for table display"); System.out.println("Error while fetching table data: " + e.getMessage()); } tvInventory.setItems(data); @@ -198,6 +225,10 @@ public class InventoryController { } catch (IOException e) { + ActivityLogger.getInstance().logException( + "InventoryController.openDialog", + e, + "Loading inventory dialog in " + mode + " mode"); throw new RuntimeException(e); } diff --git a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java index 440c5686..e39b2ea3 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java @@ -4,20 +4,20 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; +import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; +import javafx.stage.Modality; import javafx.stage.Stage; import org.example.petshopdesktop.auth.UserSession; +import org.example.petshopdesktop.database.ConnectionDB; import org.example.petshopdesktop.database.UserDB; import org.example.petshopdesktop.models.User; +import org.example.petshopdesktop.util.ActivityLogger; import java.sql.SQLException; -/* -Petshop Desktop -Purpose: Authentication controller responsible for validating credentials and initialising the user session. -*/ public class LoginController { @FXML @@ -29,20 +29,34 @@ public class LoginController { @FXML private Label lblError; + @FXML + private Button btnCreateStaff; + + @FXML + public void initialize() { + lblError.setText(""); + try { + ConnectionDB.getConnection().close(); + try { + UserDB.initializeTable(); + } catch (Exception ignored) { + } + } catch (Exception e) { + lblError.setText("Database is not connected. Check Docker and connectionpetstore.properties."); + } + } + @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."); @@ -50,18 +64,55 @@ public class LoginController { return; } - // Session state is stored in memory for use by controllers and UI RBAC. - UserSession.getInstance().login(user.getUsername(), user.getRole()); + UserSession.getInstance().login( + user.getUserId(), + user.getEmployeeId(), + user.getUsername(), + user.getEmployeeFullName(), + user.getRole() + ); openMainLayout(); } catch (SQLException e) { - lblError.setText("Database error: " + e.getMessage()); + ActivityLogger.getInstance().logException( + "LoginController.btnLoginClicked", + e, + "Authentication attempt for username: " + username); + String msg = e.getMessage() == null ? "" : e.getMessage().toLowerCase(); + if (msg.contains("doesn't exist") || msg.contains("unknown database") || msg.contains("access denied")) { + lblError.setText("Database error. Check Docker and connectionpetstore.properties."); + } else { + lblError.setText("Login failed. Check username and password."); + } + } catch (RuntimeException e) { + ActivityLogger.getInstance().logException( + "LoginController.btnLoginClicked", + e, + "Database connection"); + lblError.setText("Database is not connected. Check Docker and connectionpetstore.properties."); + } + } + + @FXML + void btnCreateStaffClicked(ActionEvent event) { + lblError.setText(""); + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-register-dialog-view.fxml")); + Stage dialog = new Stage(); + dialog.initOwner(txtUsername.getScene().getWindow()); + dialog.initModality(Modality.APPLICATION_MODAL); + dialog.setTitle("Create Staff Account"); + dialog.setScene(new Scene(loader.load())); + dialog.setResizable(false); + dialog.showAndWait(); + } catch (Exception e) { + ActivityLogger.getInstance().logException("LoginController.btnCreateStaffClicked", e, "Opening staff register dialog"); + lblError.setText("Could not open staff account creation."); } } 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()); @@ -69,6 +120,10 @@ public class LoginController { stage.setScene(scene); stage.setTitle("Pet Shop Manager"); } catch (Exception e) { + ActivityLogger.getInstance().logException( + "LoginController.openMainLayout", + e, + "Loading main application layout after successful login"); lblError.setText("Error loading application: " + e.getMessage()); e.printStackTrace(); } diff --git a/src/main/java/org/example/petshopdesktop/controllers/PetController.java b/src/main/java/org/example/petshopdesktop/controllers/PetController.java index 7eb55cb8..ee987340 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/PetController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/PetController.java @@ -14,6 +14,7 @@ import org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogControl import org.example.petshopdesktop.database.PetDB; import org.example.petshopdesktop.database.ProductDB; import org.example.petshopdesktop.models.Pet; +import org.example.petshopdesktop.util.ActivityLogger; import java.io.IOException; import java.sql.SQLException; @@ -73,6 +74,7 @@ public class PetController { Alert question = new Alert(Alert.AlertType.CONFIRMATION); question.setHeaderText("Please confirm delete"); question.setContentText("Are you sure you want to delete this pet?"); + question.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); Optional result = question.showAndWait(); //show alert and wait for response //if confirmed,start deletion @@ -84,6 +86,10 @@ public class PetController { numRows = PetDB.deletePet(petId); } catch (SQLIntegrityConstraintViolationException e){ + ActivityLogger.getInstance().logException( + "PetController.btnDeleteClicked", + e, + "Deleting pet (integrity constraint violation) with ID: " + petId); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Database Operation Error"); alert.setContentText("Delete failed\n" + @@ -92,6 +98,10 @@ public class PetController { return; } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "PetController.btnDeleteClicked", + e, + "Deleting pet with ID: " + petId); throw new RuntimeException(e); } @@ -154,6 +164,15 @@ public class PetController { txtSearch.textProperty().addListener((observable, oldValue, newValue) -> { displayFilteredPet(newValue); }); + + //EventListener for DELETE key + tvPets.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { + if (tvPets.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + } + }); } private void displayFilteredPet(String filter) { @@ -167,6 +186,10 @@ public class PetController { tvPets.setItems(data); } } catch (Exception e) { + ActivityLogger.getInstance().logException( + "PetController.displayFilteredPet", + e, + "Filtering pets with filter: " + filter); System.out.println("Error while fetching table data: " + e.getMessage()); } } @@ -178,6 +201,10 @@ public class PetController { data = PetDB.getPets(); } catch(SQLException e){ + ActivityLogger.getInstance().logException( + "PetController.displayPets", + e, + "Fetching pet data for table display"); System.out.println("Error while fetching table data: " + e.getMessage()); } @@ -191,6 +218,10 @@ public class PetController { try{ scene = new Scene(fxmlLoader.load()); } catch (IOException e) { + ActivityLogger.getInstance().logException( + "PetController.openDialog", + e, + "Loading pet dialog in " + mode + " mode"); throw new RuntimeException(e); } PetDialogController dialogController = fxmlLoader.getController(); //controller associated with this view diff --git a/src/main/java/org/example/petshopdesktop/controllers/ProductController.java b/src/main/java/org/example/petshopdesktop/controllers/ProductController.java index 8c0021fb..c39ca788 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/ProductController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/ProductController.java @@ -17,6 +17,7 @@ import org.example.petshopdesktop.database.ProductDB; import org.example.petshopdesktop.database.SupplierDB; import org.example.petshopdesktop.models.Product; import org.example.petshopdesktop.models.Supplier; +import org.example.petshopdesktop.util.ActivityLogger; import java.io.IOException; import java.sql.SQLException; @@ -92,6 +93,15 @@ public class ProductController { displayFilteredProduct(newValue); }); + //EventListener for DELETE key press + tvProducts.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { + if (tvProducts.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + } + }); + } /** @@ -106,6 +116,10 @@ public class ProductController { data = ProductDB.getProductDTO(); } catch (SQLException e) { System.out.println("Error while fetching table data: " + e.getMessage()); + ActivityLogger.getInstance().logException( + "ProductController.displayProduct", + e, + "Fetching product data for table display"); } //put data in the table @@ -136,6 +150,7 @@ public class ProductController { Alert question = new Alert(Alert.AlertType.CONFIRMATION); question.setHeaderText("Please confirm delete"); question.setContentText("Are you sure you want to delete this product?"); + question.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); Optional result = question.showAndWait(); //show alert and wait for response //if confirmed,start deletion @@ -147,6 +162,10 @@ public class ProductController { numRows = ProductDB.deleteProduct(prodId); } catch (SQLIntegrityConstraintViolationException e){ + ActivityLogger.getInstance().logException( + "ProductController.btnDeleteClicked", + e, + String.format("Attempting to delete product ID %d - foreign key constraint", prodId)); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Database Operation Error"); alert.setContentText("Delete failed\n" + @@ -155,6 +174,10 @@ public class ProductController { return; } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "ProductController.btnDeleteClicked", + e, + String.format("Attempting to delete product ID %d", prodId)); throw new RuntimeException(e); } @@ -212,6 +235,10 @@ public class ProductController { } } catch (Exception e) { System.out.println("Error while fetching table data: " + e.getMessage()); + ActivityLogger.getInstance().logException( + "ProductController.displayFilteredProduct", + e, + String.format("Filtering products with keyword: %s", filter)); } } @@ -228,6 +255,10 @@ public class ProductController { try{ scene = new Scene(fxmlLoader.load()); } catch (IOException e) { + ActivityLogger.getInstance().logException( + "ProductController.openDialog", + e, + String.format("Loading product dialog view in %s mode", mode)); throw new RuntimeException(e); } ProductDialogController dialogController = fxmlLoader.getController(); //controller associated with this view diff --git a/src/main/java/org/example/petshopdesktop/controllers/ProductSupplierController.java b/src/main/java/org/example/petshopdesktop/controllers/ProductSupplierController.java index 56080c6e..64d3f513 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/ProductSupplierController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/ProductSupplierController.java @@ -17,6 +17,7 @@ import org.example.petshopdesktop.controllers.dialogcontrollers.ProductSupplierD import org.example.petshopdesktop.database.ProductDB; import org.example.petshopdesktop.database.ProductSupplierDB; import org.example.petshopdesktop.models.ProductSupplier; +import org.example.petshopdesktop.util.ActivityLogger; import java.io.IOException; import java.sql.SQLException; @@ -89,6 +90,15 @@ public class ProductSupplierController { displayFilteredProductSupplier(newValue); }); + //EventListener for DELETE key + tvProductSuppliers.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { + if (tvProductSuppliers.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + } + }); + } /** @@ -102,6 +112,10 @@ public class ProductSupplierController { try{ data = ProductSupplierDB.getProductSupplierDTO(); } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "ProductSupplierController.displayProductSupplier", + e, + "Fetching product-supplier data for table display"); System.out.println("Error while fetching table data: " + e.getMessage()); } @@ -125,6 +139,10 @@ public class ProductSupplierController { tvProductSuppliers.setItems(data); } } catch (Exception e) { + ActivityLogger.getInstance().logException( + "ProductSupplierController.displayFilteredProductSupplier", + e, + "Filtering product-supplier data with filter: " + filter); System.out.println("Error while fetching table data: " + e.getMessage()); } } @@ -153,6 +171,7 @@ public class ProductSupplierController { Alert question = new Alert(Alert.AlertType.CONFIRMATION); question.setHeaderText("Please confirm delete"); question.setContentText("Are you sure you want to delete this product-supplier?"); + question.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); Optional result = question.showAndWait(); //show alert and wait for response //if confirmed,start deletion @@ -165,6 +184,10 @@ public class ProductSupplierController { numRows = ProductSupplierDB.deleteProductSupplier(supId, prodId); } catch (SQLIntegrityConstraintViolationException e){ + ActivityLogger.getInstance().logException( + "ProductSupplierController.btnDeleteClicked", + e, + "Deleting product-supplier (integrity constraint violation) - SupID: " + supId + ", ProdID: " + prodId); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Database Operation Error"); alert.setContentText("Delete failed\n" + @@ -173,6 +196,10 @@ public class ProductSupplierController { return; } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "ProductSupplierController.btnDeleteClicked", + e, + "Deleting product-supplier - SupID: " + supId + ", ProdID: " + prodId); throw new RuntimeException(e); } @@ -222,6 +249,10 @@ public class ProductSupplierController { try{ scene = new Scene(fxmlLoader.load()); } catch (IOException e) { + ActivityLogger.getInstance().logException( + "ProductSupplierController.openDialog", + e, + "Loading product-supplier dialog in " + mode + " mode"); throw new RuntimeException(e); } ProductSupplierDialogController dialogController = fxmlLoader.getController(); //controller associated with this view diff --git a/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java b/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java index 0f0246bd..58082dee 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/PurchaseOrderController.java @@ -1,15 +1,22 @@ package org.example.petshopdesktop.controllers; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; 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; +import org.example.petshopdesktop.util.ActivityLogger; public class PurchaseOrderController { @FXML private Button btnRefresh; + @FXML + private TextField txtSearch; + @FXML private TableView tvPurchaseOrders; @FXML private TableColumn colOrderId; @@ -17,6 +24,9 @@ public class PurchaseOrderController { @FXML private TableColumn colOrderDate; @FXML private TableColumn colStatus; + private final ObservableList purchaseOrders = FXCollections.observableArrayList(); + private FilteredList filtered; + @FXML public void initialize() { @@ -32,21 +42,53 @@ public class PurchaseOrderController { colStatus.setCellValueFactory( new PropertyValueFactory<>("status")); + filtered = new FilteredList<>(purchaseOrders, p -> true); + tvPurchaseOrders.setItems(filtered); + + if (txtSearch != null) { + txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n)); + } + loadPurchaseOrders(); } private void loadPurchaseOrders() { try { - tvPurchaseOrders.setItems( - PurchaseOrderDB.getPurchaseOrders() - ); + purchaseOrders.setAll(PurchaseOrderDB.getPurchaseOrders()); } catch (Exception e) { + ActivityLogger.getInstance().logException( + "PurchaseOrderController.loadPurchaseOrders", + e, + "Loading purchase orders for table display"); e.printStackTrace(); new Alert(Alert.AlertType.ERROR, "Unable to load purchase orders").showAndWait(); } } + private void applyFilter(String text) { + if (filtered == null) { + return; + } + + String q = text == null ? "" : text.trim().toLowerCase(); + if (q.isEmpty()) { + filtered.setPredicate(p -> true); + return; + } + + filtered.setPredicate(p -> + String.valueOf(p.getPurchaseOrderId()).contains(q) + || safe(p.getSupplierName()).contains(q) + || safe(p.getOrderDate()).contains(q) + || safe(p.getStatus()).contains(q) + ); + } + + private static String safe(String v) { + return v == null ? "" : v.toLowerCase(); + } + @FXML void btnRefresh() { loadPurchaseOrders(); diff --git a/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java b/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java index c227fbbc..5d203dfe 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/ServiceController.java @@ -1,18 +1,19 @@ package org.example.petshopdesktop.controllers; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.event.ActionEvent; import javafx.fxml.FXML; 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 org.example.petshopdesktop.util.ActivityLogger; import javafx.stage.Modality; @@ -32,6 +33,9 @@ public class ServiceController { @FXML private TextField txtSearch; + private final ObservableList services = FXCollections.observableArrayList(); + private FilteredList filtered; + @FXML public void initialize() { @@ -41,18 +45,62 @@ public class ServiceController { colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration")); colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice")); + filtered = new FilteredList<>(services, s -> true); + tvServices.setItems(filtered); + + if (txtSearch != null) { + txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n)); + } + + //EventListener for DELETE key + tvServices.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { + if (tvServices.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + } + }); + loadServices(); } private void loadServices() { try { - tvServices.setItems(ServiceDB.getServices()); + services.setAll(ServiceDB.getServices()); } catch (Exception e) { + ActivityLogger.getInstance().logException( + "ServiceController.loadServices", + e, + "Loading services for table display"); showAlert("Database Error", "Unable to load services."); e.printStackTrace(); } } + private void applyFilter(String text) { + if (filtered == null) { + return; + } + + String q = text == null ? "" : text.trim().toLowerCase(); + if (q.isEmpty()) { + filtered.setPredicate(s -> true); + return; + } + + filtered.setPredicate(s -> + String.valueOf(s.getServiceId()).contains(q) + || safe(s.getServiceName()).contains(q) + || safe(s.getServiceDesc()).contains(q) + || String.valueOf(s.getServiceDuration()).contains(q) + || String.valueOf(s.getServicePrice()).contains(q) + ); + } + + private static String safe(String v) { + return v == null ? "" : v.toLowerCase(); + } + @FXML void btnAddClicked(ActionEvent event) { @@ -84,6 +132,10 @@ public class ServiceController { ServiceDB.deleteService(service.getServiceId()); loadServices(); } catch (Exception ex) { + ActivityLogger.getInstance().logException( + "ServiceController.btnDeleteClicked", + ex, + "Deleting service with ID: " + service.getServiceId()); ex.printStackTrace(); } } @@ -111,6 +163,10 @@ public class ServiceController { loadServices(); } catch (Exception e) { + ActivityLogger.getInstance().logException( + "ServiceController.openDialog", + e, + "Opening service dialog in " + mode + " mode"); e.printStackTrace(); } } diff --git a/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java b/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java new file mode 100644 index 00000000..8e92ddb6 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java @@ -0,0 +1,112 @@ +package org.example.petshopdesktop.controllers; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.control.cell.PropertyValueFactory; +import org.example.petshopdesktop.auth.UserSession; +import org.example.petshopdesktop.database.UserDB; +import org.example.petshopdesktop.models.StaffAccount; +import org.example.petshopdesktop.util.ActivityLogger; + +import java.sql.SQLException; + +public class StaffAccountsController { + + @FXML + private TableView tvStaff; + + @FXML + private TableColumn colUsername; + + @FXML + private TableColumn colName; + + @FXML + private TableColumn colEmail; + + @FXML + private TableColumn colPhone; + + @FXML + private TableColumn colStatus; + + @FXML + private TableColumn colCreated; + + @FXML + private TextField txtSearch; + + @FXML + private Label lblError; + + private final ObservableList staffAccounts = FXCollections.observableArrayList(); + private FilteredList filtered; + + @FXML + public void initialize() { + colUsername.setCellValueFactory(new PropertyValueFactory<>("username")); + colName.setCellValueFactory(new PropertyValueFactory<>("fullName")); + colEmail.setCellValueFactory(new PropertyValueFactory<>("email")); + colPhone.setCellValueFactory(new PropertyValueFactory<>("phone")); + colStatus.setCellValueFactory(new PropertyValueFactory<>("status")); + colCreated.setCellValueFactory(new PropertyValueFactory<>("createdAt")); + + filtered = new FilteredList<>(staffAccounts, a -> true); + tvStaff.setItems(filtered); + + txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n)); + + if (!UserSession.getInstance().isAdmin()) { + lblError.setText("Access restricted."); + tvStaff.setDisable(true); + return; + } + + refresh(); + } + + @FXML + void btnRefreshClicked(ActionEvent event) { + refresh(); + } + + private void refresh() { + lblError.setText(""); + try { + staffAccounts.setAll(UserDB.getStaffAccounts()); + } catch (SQLException e) { + ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts"); + lblError.setText("Could not load staff accounts."); + } catch (RuntimeException e) { + ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Database connection"); + lblError.setText("Database is not connected."); + } + } + + private void applyFilter(String text) { + String q = text == null ? "" : text.trim().toLowerCase(); + if (q.isEmpty()) { + filtered.setPredicate(a -> true); + return; + } + + filtered.setPredicate(a -> + safe(a.getUsername()).contains(q) + || safe(a.getFullName()).contains(q) + || safe(a.getEmail()).contains(q) + || safe(a.getPhone()).contains(q) + || safe(a.getStatus()).contains(q) + ); + } + + private static String safe(String v) { + return v == null ? "" : v.toLowerCase(); + } +} diff --git a/src/main/java/org/example/petshopdesktop/controllers/SupplierController.java b/src/main/java/org/example/petshopdesktop/controllers/SupplierController.java index aa909103..a148c8da 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/SupplierController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/SupplierController.java @@ -13,6 +13,7 @@ import javafx.stage.Stage; import org.example.petshopdesktop.controllers.dialogcontrollers.SupplierDialogController; import org.example.petshopdesktop.database.SupplierDB; import org.example.petshopdesktop.models.Supplier; +import org.example.petshopdesktop.util.ActivityLogger; import java.io.IOException; import java.sql.SQLException; @@ -87,6 +88,14 @@ public class SupplierController { displayFilteredSupplier(newValue); }); + //EventListener for DELETE key + tvSuppliers.setOnKeyPressed(event -> { + if (event.getCode() == javafx.scene.input.KeyCode.DELETE) { + if (tvSuppliers.getSelectionModel().getSelectedItem() != null) { + btnDeleteClicked(null); + } + } + }); } @@ -99,6 +108,10 @@ public class SupplierController { try{ data = SupplierDB.getSuppliers(); } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "SupplierController.displaySupplier", + e, + "Fetching supplier data for table display"); System.out.println("Error while fetching table data: " + e.getMessage()); } @@ -121,6 +134,10 @@ public class SupplierController { tvSuppliers.setItems(data); } } catch (Exception e) { + ActivityLogger.getInstance().logException( + "SupplierController.displayFilteredSupplier", + e, + "Filtering suppliers with filter: " + filter); System.out.println("Error while fetching table data: " + e.getMessage()); } } @@ -150,6 +167,7 @@ public class SupplierController { Alert question = new Alert(Alert.AlertType.CONFIRMATION); question.setHeaderText("Please confirm delete"); question.setContentText("Are you sure you want to delete this supplier?"); + question.getDialogPane().lookupButton(ButtonType.OK).requestFocus(); Optional result = question.showAndWait(); //show alert and wait for response //if confirmed, start deletion @@ -161,6 +179,10 @@ public class SupplierController { numRows = SupplierDB.deleteSupplier(supId); } catch (SQLIntegrityConstraintViolationException e){ + ActivityLogger.getInstance().logException( + "SupplierController.btnDeleteClicked", + e, + "Deleting supplier (integrity constraint violation) with ID: " + supId); Alert alert = new Alert(Alert.AlertType.ERROR); alert.setHeaderText("Database Operation Error"); alert.setContentText("Delete failed\n" + @@ -169,6 +191,10 @@ public class SupplierController { return; } catch (SQLException e) { + ActivityLogger.getInstance().logException( + "SupplierController.btnDeleteClicked", + e, + "Deleting supplier with ID: " + supId); throw new RuntimeException(e); } @@ -222,6 +248,10 @@ public class SupplierController { try{ scene = new Scene(fxmlLoader.load()); } catch (IOException e) { + ActivityLogger.getInstance().logException( + "SupplierController.openDialog", + e, + "Loading supplier dialog in " + mode + " mode"); throw new RuntimeException(e); } SupplierDialogController dialogController = fxmlLoader.getController(); //controller associated with this view