Add DELETE key and Enter-to-confirm to all tables

This commit is contained in:
2026-02-25 09:42:34 -07:00
parent 19593af688
commit 13684f97de
11 changed files with 527 additions and 21 deletions

View File

@@ -13,6 +13,7 @@ import javafx.stage.Stage;
import org.example.petshopdesktop.controllers.dialogcontrollers.AdoptionDialogController; import org.example.petshopdesktop.controllers.dialogcontrollers.AdoptionDialogController;
import org.example.petshopdesktop.database.AdoptionDB; import org.example.petshopdesktop.database.AdoptionDB;
import org.example.petshopdesktop.models.Adoption; import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -80,6 +81,15 @@ public class AdoptionController {
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> { txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredAdoptions(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 @FXML
@@ -96,6 +106,7 @@ public class AdoptionController {
Alert question = new Alert(Alert.AlertType.CONFIRMATION); Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete"); question.setHeaderText("Please confirm delete");
question.setContentText("Are you sure you want to delete this adoption record?"); question.setContentText("Are you sure you want to delete this adoption record?");
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait(); Optional<ButtonType> result = question.showAndWait();
if (result.isPresent() && result.get() == ButtonType.OK) { if (result.isPresent() && result.get() == ButtonType.OK) {
@@ -105,12 +116,20 @@ public class AdoptionController {
numRows = AdoptionDB.deleteAdoption(adoptionId); numRows = AdoptionDB.deleteAdoption(adoptionId);
} }
catch (SQLIntegrityConstraintViolationException e) { 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 alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error"); alert.setHeaderText("Database Operation Error");
alert.setContentText("Delete failed\nThe selected adoption is being referred in another table"); alert.setContentText("Delete failed\nThe selected adoption is being referred in another table");
alert.showAndWait(); alert.showAndWait();
return; return;
} catch (SQLException e) { } catch (SQLException e) {
ActivityLogger.getInstance().logException(
"AdoptionController.btnDeleteClicked",
e,
"Deleting adoption with ID: " + adoptionId);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -152,6 +171,10 @@ public class AdoptionController {
tvAdoptions.setItems(data); tvAdoptions.setItems(data);
} }
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionController.displayFilteredAdoptions",
e,
"Filtering adoptions with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
} }
@@ -161,6 +184,10 @@ public class AdoptionController {
try { try {
data = AdoptionDB.getAdoptions(); data = AdoptionDB.getAdoptions();
} catch (SQLException e) { } 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()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
tvAdoptions.setItems(data); tvAdoptions.setItems(data);
@@ -173,6 +200,10 @@ public class AdoptionController {
try { try {
scene = new Scene(fxmlLoader.load()); scene = new Scene(fxmlLoader.load());
} catch (IOException e) { } catch (IOException e) {
ActivityLogger.getInstance().logException(
"AdoptionController.openDialog",
e,
"Loading adoption dialog in " + mode + " mode");
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -2,6 +2,7 @@ package org.example.petshopdesktop.controllers;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
@@ -14,6 +15,7 @@ import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.AppointmentDTO; import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController; import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController;
import org.example.petshopdesktop.database.AppointmentDB; import org.example.petshopdesktop.database.AppointmentDB;
import org.example.petshopdesktop.util.ActivityLogger;
public class AppointmentController { public class AppointmentController {
@@ -33,7 +35,8 @@ public class AppointmentController {
@FXML private TextField txtSearch; @FXML private TextField txtSearch;
private ObservableList<AppointmentDTO> data = FXCollections.observableArrayList(); private final ObservableList<AppointmentDTO> appointments = FXCollections.observableArrayList();
private FilteredList<AppointmentDTO> filtered;
@FXML @FXML
public void initialize(){ public void initialize(){
@@ -46,18 +49,63 @@ public class AppointmentController {
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName")); colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus")); 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(); loadAppointments();
} }
private void loadAppointments(){ private void loadAppointments(){
try{ try{
data = AppointmentDB.getAppointmentDTOs(); appointments.setAll(AppointmentDB.getAppointmentDTOs());
tvAppointments.setItems(data);
}catch(Exception e){ }catch(Exception e){
ActivityLogger.getInstance().logException(
"AppointmentController.loadAppointments",
e,
"Loading appointments for table display");
e.printStackTrace(); 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 @FXML
void btnAddClicked(ActionEvent event){ void btnAddClicked(ActionEvent event){
openDialog(null, "Add"); openDialog(null, "Add");
@@ -89,6 +137,10 @@ public class AppointmentController {
AppointmentDB.deleteAppointment(selected.getAppointmentId()); AppointmentDB.deleteAppointment(selected.getAppointmentId());
loadAppointments(); loadAppointments();
}catch(Exception e){ }catch(Exception e){
ActivityLogger.getInstance().logException(
"AppointmentController.btnDeleteClicked",
e,
"Deleting appointment with ID: " + selected.getAppointmentId());
e.printStackTrace(); e.printStackTrace();
} }
} }
@@ -121,6 +173,10 @@ public class AppointmentController {
loadAppointments(); loadAppointments();
}catch(Exception e){ }catch(Exception e){
ActivityLogger.getInstance().logException(
"AppointmentController.openDialog",
e,
"Opening appointment dialog in " + mode + " mode");
e.printStackTrace(); e.printStackTrace();
} }
} }

View File

@@ -13,6 +13,7 @@ import javafx.stage.Stage;
import org.example.petshopdesktop.controllers.dialogcontrollers.InventoryDialogController; import org.example.petshopdesktop.controllers.dialogcontrollers.InventoryDialogController;
import org.example.petshopdesktop.database.InventoryDB; import org.example.petshopdesktop.database.InventoryDB;
import org.example.petshopdesktop.models.Inventory; import org.example.petshopdesktop.models.Inventory;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -79,6 +80,15 @@ public class InventoryController {
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> { txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredInventory(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 //Opens dialog in add mode
@@ -98,6 +108,7 @@ public class InventoryController {
Alert question = new Alert(Alert.AlertType.CONFIRMATION); Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete"); question.setHeaderText("Please confirm delete");
question.setContentText("Are you sure you want to delete this inventory record?"); question.setContentText("Are you sure you want to delete this inventory record?");
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait(); Optional<ButtonType> result = question.showAndWait();
//If user confirms, proceed with trying to delete... //If user confirms, proceed with trying to delete...
@@ -109,6 +120,10 @@ public class InventoryController {
} }
catch (SQLIntegrityConstraintViolationException e) { 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 alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error"); alert.setHeaderText("Database Operation Error");
alert.setContentText("Delete failed\nThe selected inventory record is being referred in another table"); alert.setContentText("Delete failed\nThe selected inventory record is being referred in another table");
@@ -117,6 +132,10 @@ public class InventoryController {
} }
catch (SQLException e) { catch (SQLException e) {
ActivityLogger.getInstance().logException(
"InventoryController.btnDeleteClicked",
e,
"Deleting inventory with ID: " + inventoryId);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -170,6 +189,10 @@ public class InventoryController {
} }
catch (Exception e) { catch (Exception e) {
ActivityLogger.getInstance().logException(
"InventoryController.displayFilteredInventory",
e,
"Filtering inventory with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
} }
@@ -182,6 +205,10 @@ public class InventoryController {
} }
catch (SQLException e) { 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()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
tvInventory.setItems(data); tvInventory.setItems(data);
@@ -198,6 +225,10 @@ public class InventoryController {
} }
catch (IOException e) { catch (IOException e) {
ActivityLogger.getInstance().logException(
"InventoryController.openDialog",
e,
"Loading inventory dialog in " + mode + " mode");
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -4,20 +4,20 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.PasswordField; import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.ConnectionDB;
import org.example.petshopdesktop.database.UserDB; import org.example.petshopdesktop.database.UserDB;
import org.example.petshopdesktop.models.User; import org.example.petshopdesktop.models.User;
import org.example.petshopdesktop.util.ActivityLogger;
import java.sql.SQLException; import java.sql.SQLException;
/*
Petshop Desktop
Purpose: Authentication controller responsible for validating credentials and initialising the user session.
*/
public class LoginController { public class LoginController {
@FXML @FXML
@@ -29,20 +29,34 @@ public class LoginController {
@FXML @FXML
private Label lblError; 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 @FXML
void btnLoginClicked(ActionEvent event) { void btnLoginClicked(ActionEvent event) {
// Input normalisation keeps authentication behaviour consistent.
String username = txtUsername.getText().trim(); String username = txtUsername.getText().trim();
String password = txtPassword.getText(); String password = txtPassword.getText();
// Basic validation to avoid unnecessary database calls.
if (username.isEmpty() || password.isEmpty()) { if (username.isEmpty() || password.isEmpty()) {
lblError.setText("Please enter username and password."); lblError.setText("Please enter username and password.");
return; return;
} }
try { try {
// Credential verification returns a fully populated User on success.
User user = UserDB.authenticate(username, password); User user = UserDB.authenticate(username, password);
if (user == null) { if (user == null) {
lblError.setText("Invalid username or password."); lblError.setText("Invalid username or password.");
@@ -50,18 +64,55 @@ public class LoginController {
return; return;
} }
// Session state is stored in memory for use by controllers and UI RBAC. UserSession.getInstance().login(
UserSession.getInstance().login(user.getUsername(), user.getRole()); user.getUserId(),
user.getEmployeeId(),
user.getUsername(),
user.getEmployeeFullName(),
user.getRole()
);
openMainLayout(); openMainLayout();
} catch (SQLException e) { } 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() { private void openMainLayout() {
try { try {
// View transition into the post login application shell.
FXMLLoader loader = new FXMLLoader( FXMLLoader loader = new FXMLLoader(
getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml")); getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml"));
Scene scene = new Scene(loader.load()); Scene scene = new Scene(loader.load());
@@ -69,6 +120,10 @@ public class LoginController {
stage.setScene(scene); stage.setScene(scene);
stage.setTitle("Pet Shop Manager"); stage.setTitle("Pet Shop Manager");
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException(
"LoginController.openMainLayout",
e,
"Loading main application layout after successful login");
lblError.setText("Error loading application: " + e.getMessage()); lblError.setText("Error loading application: " + e.getMessage());
e.printStackTrace(); e.printStackTrace();
} }

View File

@@ -14,6 +14,7 @@ import org.example.petshopdesktop.controllers.dialogcontrollers.PetDialogControl
import org.example.petshopdesktop.database.PetDB; import org.example.petshopdesktop.database.PetDB;
import org.example.petshopdesktop.database.ProductDB; import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.models.Pet; import org.example.petshopdesktop.models.Pet;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -73,6 +74,7 @@ public class PetController {
Alert question = new Alert(Alert.AlertType.CONFIRMATION); Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete"); question.setHeaderText("Please confirm delete");
question.setContentText("Are you sure you want to delete this pet?"); question.setContentText("Are you sure you want to delete this pet?");
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response
//if confirmed,start deletion //if confirmed,start deletion
@@ -84,6 +86,10 @@ public class PetController {
numRows = PetDB.deletePet(petId); numRows = PetDB.deletePet(petId);
} }
catch (SQLIntegrityConstraintViolationException e){ 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 alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error"); alert.setHeaderText("Database Operation Error");
alert.setContentText("Delete failed\n" + alert.setContentText("Delete failed\n" +
@@ -92,6 +98,10 @@ public class PetController {
return; return;
} }
catch (SQLException e) { catch (SQLException e) {
ActivityLogger.getInstance().logException(
"PetController.btnDeleteClicked",
e,
"Deleting pet with ID: " + petId);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -154,6 +164,15 @@ public class PetController {
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> { txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
displayFilteredPet(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) { private void displayFilteredPet(String filter) {
@@ -167,6 +186,10 @@ public class PetController {
tvPets.setItems(data); tvPets.setItems(data);
} }
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException(
"PetController.displayFilteredPet",
e,
"Filtering pets with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
} }
@@ -178,6 +201,10 @@ public class PetController {
data = PetDB.getPets(); data = PetDB.getPets();
} }
catch(SQLException e){ 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()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
@@ -191,6 +218,10 @@ public class PetController {
try{ try{
scene = new Scene(fxmlLoader.load()); scene = new Scene(fxmlLoader.load());
} catch (IOException e) { } catch (IOException e) {
ActivityLogger.getInstance().logException(
"PetController.openDialog",
e,
"Loading pet dialog in " + mode + " mode");
throw new RuntimeException(e); throw new RuntimeException(e);
} }
PetDialogController dialogController = fxmlLoader.getController(); //controller associated with this view PetDialogController dialogController = fxmlLoader.getController(); //controller associated with this view

View File

@@ -17,6 +17,7 @@ import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.SupplierDB; import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.models.Product; import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.models.Supplier; import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -92,6 +93,15 @@ public class ProductController {
displayFilteredProduct(newValue); 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(); data = ProductDB.getProductDTO();
} catch (SQLException e) { } catch (SQLException e) {
System.out.println("Error while fetching table data: " + e.getMessage()); 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 //put data in the table
@@ -136,6 +150,7 @@ public class ProductController {
Alert question = new Alert(Alert.AlertType.CONFIRMATION); Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete"); question.setHeaderText("Please confirm delete");
question.setContentText("Are you sure you want to delete this product?"); question.setContentText("Are you sure you want to delete this product?");
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response
//if confirmed,start deletion //if confirmed,start deletion
@@ -147,6 +162,10 @@ public class ProductController {
numRows = ProductDB.deleteProduct(prodId); numRows = ProductDB.deleteProduct(prodId);
} }
catch (SQLIntegrityConstraintViolationException e){ 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 alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error"); alert.setHeaderText("Database Operation Error");
alert.setContentText("Delete failed\n" + alert.setContentText("Delete failed\n" +
@@ -155,6 +174,10 @@ public class ProductController {
return; return;
} }
catch (SQLException e) { catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductController.btnDeleteClicked",
e,
String.format("Attempting to delete product ID %d", prodId));
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -212,6 +235,10 @@ public class ProductController {
} }
} catch (Exception e) { } catch (Exception e) {
System.out.println("Error while fetching table data: " + e.getMessage()); 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{ try{
scene = new Scene(fxmlLoader.load()); scene = new Scene(fxmlLoader.load());
} catch (IOException e) { } catch (IOException e) {
ActivityLogger.getInstance().logException(
"ProductController.openDialog",
e,
String.format("Loading product dialog view in %s mode", mode));
throw new RuntimeException(e); throw new RuntimeException(e);
} }
ProductDialogController dialogController = fxmlLoader.getController(); //controller associated with this view ProductDialogController dialogController = fxmlLoader.getController(); //controller associated with this view

View File

@@ -17,6 +17,7 @@ import org.example.petshopdesktop.controllers.dialogcontrollers.ProductSupplierD
import org.example.petshopdesktop.database.ProductDB; import org.example.petshopdesktop.database.ProductDB;
import org.example.petshopdesktop.database.ProductSupplierDB; import org.example.petshopdesktop.database.ProductSupplierDB;
import org.example.petshopdesktop.models.ProductSupplier; import org.example.petshopdesktop.models.ProductSupplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -89,6 +90,15 @@ public class ProductSupplierController {
displayFilteredProductSupplier(newValue); 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{ try{
data = ProductSupplierDB.getProductSupplierDTO(); data = ProductSupplierDB.getProductSupplierDTO();
} catch (SQLException e) { } 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()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
@@ -125,6 +139,10 @@ public class ProductSupplierController {
tvProductSuppliers.setItems(data); tvProductSuppliers.setItems(data);
} }
} catch (Exception e) { } 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()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
} }
@@ -153,6 +171,7 @@ public class ProductSupplierController {
Alert question = new Alert(Alert.AlertType.CONFIRMATION); Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete"); question.setHeaderText("Please confirm delete");
question.setContentText("Are you sure you want to delete this product-supplier?"); question.setContentText("Are you sure you want to delete this product-supplier?");
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response
//if confirmed,start deletion //if confirmed,start deletion
@@ -165,6 +184,10 @@ public class ProductSupplierController {
numRows = ProductSupplierDB.deleteProductSupplier(supId, prodId); numRows = ProductSupplierDB.deleteProductSupplier(supId, prodId);
} }
catch (SQLIntegrityConstraintViolationException e){ 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 alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error"); alert.setHeaderText("Database Operation Error");
alert.setContentText("Delete failed\n" + alert.setContentText("Delete failed\n" +
@@ -173,6 +196,10 @@ public class ProductSupplierController {
return; return;
} }
catch (SQLException e) { catch (SQLException e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.btnDeleteClicked",
e,
"Deleting product-supplier - SupID: " + supId + ", ProdID: " + prodId);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -222,6 +249,10 @@ public class ProductSupplierController {
try{ try{
scene = new Scene(fxmlLoader.load()); scene = new Scene(fxmlLoader.load());
} catch (IOException e) { } catch (IOException e) {
ActivityLogger.getInstance().logException(
"ProductSupplierController.openDialog",
e,
"Loading product-supplier dialog in " + mode + " mode");
throw new RuntimeException(e); throw new RuntimeException(e);
} }
ProductSupplierDialogController dialogController = fxmlLoader.getController(); //controller associated with this view ProductSupplierDialogController dialogController = fxmlLoader.getController(); //controller associated with this view

View File

@@ -1,15 +1,22 @@
package org.example.petshopdesktop.controllers; package org.example.petshopdesktop.controllers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import org.example.petshopdesktop.DTOs.PurchaseOrderDTO; import org.example.petshopdesktop.DTOs.PurchaseOrderDTO;
import org.example.petshopdesktop.database.PurchaseOrderDB; import org.example.petshopdesktop.database.PurchaseOrderDB;
import org.example.petshopdesktop.util.ActivityLogger;
public class PurchaseOrderController { public class PurchaseOrderController {
@FXML private Button btnRefresh; @FXML private Button btnRefresh;
@FXML
private TextField txtSearch;
@FXML private TableView<PurchaseOrderDTO> tvPurchaseOrders; @FXML private TableView<PurchaseOrderDTO> tvPurchaseOrders;
@FXML private TableColumn<PurchaseOrderDTO,Integer> colOrderId; @FXML private TableColumn<PurchaseOrderDTO,Integer> colOrderId;
@@ -17,6 +24,9 @@ public class PurchaseOrderController {
@FXML private TableColumn<PurchaseOrderDTO,String> colOrderDate; @FXML private TableColumn<PurchaseOrderDTO,String> colOrderDate;
@FXML private TableColumn<PurchaseOrderDTO,String> colStatus; @FXML private TableColumn<PurchaseOrderDTO,String> colStatus;
private final ObservableList<PurchaseOrderDTO> purchaseOrders = FXCollections.observableArrayList();
private FilteredList<PurchaseOrderDTO> filtered;
@FXML @FXML
public void initialize() { public void initialize() {
@@ -32,21 +42,53 @@ public class PurchaseOrderController {
colStatus.setCellValueFactory( colStatus.setCellValueFactory(
new PropertyValueFactory<>("status")); new PropertyValueFactory<>("status"));
filtered = new FilteredList<>(purchaseOrders, p -> true);
tvPurchaseOrders.setItems(filtered);
if (txtSearch != null) {
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
}
loadPurchaseOrders(); loadPurchaseOrders();
} }
private void loadPurchaseOrders() { private void loadPurchaseOrders() {
try { try {
tvPurchaseOrders.setItems( purchaseOrders.setAll(PurchaseOrderDB.getPurchaseOrders());
PurchaseOrderDB.getPurchaseOrders()
);
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException(
"PurchaseOrderController.loadPurchaseOrders",
e,
"Loading purchase orders for table display");
e.printStackTrace(); e.printStackTrace();
new Alert(Alert.AlertType.ERROR, new Alert(Alert.AlertType.ERROR,
"Unable to load purchase orders").showAndWait(); "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 @FXML
void btnRefresh() { void btnRefresh() {
loadPurchaseOrders(); loadPurchaseOrders();

View File

@@ -1,18 +1,19 @@
package org.example.petshopdesktop.controllers; package org.example.petshopdesktop.controllers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Node;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB; import org.example.petshopdesktop.database.ServiceDB;
import org.example.petshopdesktop.models.Service; import org.example.petshopdesktop.models.Service;
import org.example.petshopdesktop.controllers.dialogcontrollers.ServiceDialogController; import org.example.petshopdesktop.controllers.dialogcontrollers.ServiceDialogController;
import org.example.petshopdesktop.util.ActivityLogger;
import javafx.stage.Modality; import javafx.stage.Modality;
@@ -32,6 +33,9 @@ public class ServiceController {
@FXML private TextField txtSearch; @FXML private TextField txtSearch;
private final ObservableList<Service> services = FXCollections.observableArrayList();
private FilteredList<Service> filtered;
@FXML @FXML
public void initialize() { public void initialize() {
@@ -41,18 +45,62 @@ public class ServiceController {
colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration")); colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration"));
colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice")); 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(); loadServices();
} }
private void loadServices() { private void loadServices() {
try { try {
tvServices.setItems(ServiceDB.getServices()); services.setAll(ServiceDB.getServices());
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException(
"ServiceController.loadServices",
e,
"Loading services for table display");
showAlert("Database Error", "Unable to load services."); showAlert("Database Error", "Unable to load services.");
e.printStackTrace(); 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 @FXML
void btnAddClicked(ActionEvent event) { void btnAddClicked(ActionEvent event) {
@@ -84,6 +132,10 @@ public class ServiceController {
ServiceDB.deleteService(service.getServiceId()); ServiceDB.deleteService(service.getServiceId());
loadServices(); loadServices();
} catch (Exception ex) { } catch (Exception ex) {
ActivityLogger.getInstance().logException(
"ServiceController.btnDeleteClicked",
ex,
"Deleting service with ID: " + service.getServiceId());
ex.printStackTrace(); ex.printStackTrace();
} }
} }
@@ -111,6 +163,10 @@ public class ServiceController {
loadServices(); loadServices();
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException(
"ServiceController.openDialog",
e,
"Opening service dialog in " + mode + " mode");
e.printStackTrace(); e.printStackTrace();
} }
} }

View File

@@ -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<StaffAccount> tvStaff;
@FXML
private TableColumn<StaffAccount, String> colUsername;
@FXML
private TableColumn<StaffAccount, String> colName;
@FXML
private TableColumn<StaffAccount, String> colEmail;
@FXML
private TableColumn<StaffAccount, String> colPhone;
@FXML
private TableColumn<StaffAccount, String> colStatus;
@FXML
private TableColumn<StaffAccount, java.sql.Timestamp> colCreated;
@FXML
private TextField txtSearch;
@FXML
private Label lblError;
private final ObservableList<StaffAccount> staffAccounts = FXCollections.observableArrayList();
private FilteredList<StaffAccount> 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();
}
}

View File

@@ -13,6 +13,7 @@ import javafx.stage.Stage;
import org.example.petshopdesktop.controllers.dialogcontrollers.SupplierDialogController; import org.example.petshopdesktop.controllers.dialogcontrollers.SupplierDialogController;
import org.example.petshopdesktop.database.SupplierDB; import org.example.petshopdesktop.database.SupplierDB;
import org.example.petshopdesktop.models.Supplier; import org.example.petshopdesktop.models.Supplier;
import org.example.petshopdesktop.util.ActivityLogger;
import java.io.IOException; import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
@@ -87,6 +88,14 @@ public class SupplierController {
displayFilteredSupplier(newValue); 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{ try{
data = SupplierDB.getSuppliers(); data = SupplierDB.getSuppliers();
} catch (SQLException e) { } 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()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
@@ -121,6 +134,10 @@ public class SupplierController {
tvSuppliers.setItems(data); tvSuppliers.setItems(data);
} }
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException(
"SupplierController.displayFilteredSupplier",
e,
"Filtering suppliers with filter: " + filter);
System.out.println("Error while fetching table data: " + e.getMessage()); System.out.println("Error while fetching table data: " + e.getMessage());
} }
} }
@@ -150,6 +167,7 @@ public class SupplierController {
Alert question = new Alert(Alert.AlertType.CONFIRMATION); Alert question = new Alert(Alert.AlertType.CONFIRMATION);
question.setHeaderText("Please confirm delete"); question.setHeaderText("Please confirm delete");
question.setContentText("Are you sure you want to delete this supplier?"); question.setContentText("Are you sure you want to delete this supplier?");
question.getDialogPane().lookupButton(ButtonType.OK).requestFocus();
Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response
//if confirmed, start deletion //if confirmed, start deletion
@@ -161,6 +179,10 @@ public class SupplierController {
numRows = SupplierDB.deleteSupplier(supId); numRows = SupplierDB.deleteSupplier(supId);
} }
catch (SQLIntegrityConstraintViolationException e){ 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 alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error"); alert.setHeaderText("Database Operation Error");
alert.setContentText("Delete failed\n" + alert.setContentText("Delete failed\n" +
@@ -169,6 +191,10 @@ public class SupplierController {
return; return;
} }
catch (SQLException e) { catch (SQLException e) {
ActivityLogger.getInstance().logException(
"SupplierController.btnDeleteClicked",
e,
"Deleting supplier with ID: " + supId);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -222,6 +248,10 @@ public class SupplierController {
try{ try{
scene = new Scene(fxmlLoader.load()); scene = new Scene(fxmlLoader.load());
} catch (IOException e) { } catch (IOException e) {
ActivityLogger.getInstance().logException(
"SupplierController.openDialog",
e,
"Loading supplier dialog in " + mode + " mode");
throw new RuntimeException(e); throw new RuntimeException(e);
} }
SupplierDialogController dialogController = fxmlLoader.getController(); //controller associated with this view SupplierDialogController dialogController = fxmlLoader.getController(); //controller associated with this view