diff --git a/Petstoredata.sql b/Petstoredata.sql
index 1b968e23..4d50c46a 100644
--- a/Petstoredata.sql
+++ b/Petstoredata.sql
@@ -146,6 +146,14 @@ CREATE TABLE saleItem (
FOREIGN KEY (prodId) REFERENCES product(prodId)
);
+CREATE TABLE purchaseOrder (
+ purchaseOrderId INT AUTO_INCREMENT PRIMARY KEY,
+ supId INT NOT NULL,
+ orderDate DATE NOT NULL,
+ status VARCHAR(50) NOT NULL,
+ FOREIGN KEY (supId) REFERENCES supplier(supId)
+);
+
CREATE TABLE activityLog (
logId INT AUTO_INCREMENT PRIMARY KEY,
employeeId INT NOT NULL,
@@ -281,20 +289,97 @@ VALUES
INSERT INTO sale (saleDate, totalAmount, paymentMethod, employeeId, storeId)
VALUES
-(NOW(), 60.00, 'Card', 2, 1),
-('2026-01-28 10:30:00', 50.00, 'Cash', 1, 1),
-('2026-01-29 14:15:00', 120.00, 'Card', 4, 3),
-('2026-01-30 16:45:00', 80.00, 'Card', 2, 2),
-('2026-02-01 11:20:00', 150.00, 'Cash', 5, 4);
+-- January Sales
+('2026-01-05 09:15:00', 125.00, 'Card', 1, 1), -- Sale 1: Dog food + treats
+('2026-01-08 11:30:00', 200.00, 'Card', 2, 1), -- Sale 2: Bird cage + fish tank
+('2026-01-12 14:20:00', 60.00, 'Cash', 3, 2), -- Sale 3: Cat toys + hamster wheel
+('2026-01-15 10:45:00', 150.00, 'Debit', 1, 1), -- Sale 4: Dog food (bulk purchase)
+('2026-01-18 16:30:00', 80.00, 'Card', 4, 3), -- Sale 5: Fish tank
+('2026-01-22 13:15:00', 95.00, 'Cash', 2, 2), -- Sale 6: Mixed items
+('2026-01-25 15:40:00', 240.00, 'Card', 5, 4), -- Sale 7: Two bird cages
+('2026-01-28 10:30:00', 80.00, 'Cash', 1, 1), -- Sale 8: Dog food + cat toys
+-- February Sales
+('2026-02-01 09:00:00', 175.00, 'Card', 3, 3), -- Sale 9: Dog food + treats (bulk)
+('2026-02-03 11:20:00', 120.00, 'Card', 2, 1), -- Sale 10: Bird cage
+('2026-02-05 14:50:00', 45.00, 'Cash', 4, 2), -- Sale 11: Hamster wheel + cat toys
+('2026-02-08 16:15:00', 160.00, 'Debit', 1, 1), -- Sale 12: Fish tank + accessories
+('2026-02-10 10:25:00', 100.00, 'Card', 5, 4), -- Sale 13: Dog treats (bulk)
+('2026-02-12 13:45:00', 50.00, 'Cash', 2, 2), -- Sale 14: Dog food
+('2026-02-15 15:30:00', 85.00, 'Card', 3, 3), -- Sale 15: Mixed pet supplies
+('2026-02-18 11:10:00', 200.00, 'Card', 1, 1), -- Sale 16: Bird cage + hamster wheel
+('2026-02-20 14:35:00', 155.00, 'Debit', 4, 3), -- Sale 17: Fish tank + cat toys
+('2026-02-22 16:50:00', 75.00, 'Cash', 2, 1), -- Sale 18: Dog treats + toys
+('2026-02-24 10:15:00', 140.00, 'Card', 5, 4), -- Sale 19: Dog food + treats
+(NOW(), 95.00, 'Card', 1, 1); -- Sale 20: Recent sale (current timestamp)
INSERT INTO saleItem (saleId, prodId, quantity, unitPrice)
VALUES
-(1, 2, 2, 10.00),
-(2, 1, 1, 50.00),
-(3, 3, 1, 120.00),
-(4, 4, 1, 80.00),
-(5, 6, 6, 25.00),
-(2, 2, 3, 10.00);
+-- Sale 1 items (Dog food + treats)
+(1, 1, 2, 50.00), -- 2x Premium Dog Food
+(1, 6, 1, 25.00), -- 1x Organic Dog Treats
+-- Sale 2 items (Bird cage + fish tank)
+(2, 3, 1, 120.00), -- 1x Bird Cage Large
+(2, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon
+-- Sale 3 items (Cat toys + hamster wheel)
+(3, 2, 3, 10.00), -- 3x Cat Toy Ball
+(3, 5, 2, 15.00), -- 2x Hamster Wheel
+-- Sale 4 items (Dog food bulk)
+(4, 1, 3, 50.00), -- 3x Premium Dog Food
+-- Sale 5 items (Fish tank)
+(5, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon
+-- Sale 6 items (Mixed)
+(6, 2, 4, 10.00), -- 4x Cat Toy Ball
+(6, 5, 1, 15.00), -- 1x Hamster Wheel
+(6, 6, 1, 25.00), -- 1x Organic Dog Treats
+(6, 1, 1, 50.00), -- 1x Premium Dog Food (partial - discount applied)
+-- Sale 7 items (Two bird cages)
+(7, 3, 2, 120.00), -- 2x Bird Cage Large
+-- Sale 8 items (Dog food + cat toys)
+(8, 1, 1, 50.00), -- 1x Premium Dog Food
+(8, 2, 3, 10.00), -- 3x Cat Toy Ball
+-- Sale 9 items (Dog food + treats bulk)
+(9, 1, 3, 50.00), -- 3x Premium Dog Food
+(9, 6, 1, 25.00), -- 1x Organic Dog Treats
+-- Sale 10 items (Bird cage)
+(10, 3, 1, 120.00), -- 1x Bird Cage Large
+-- Sale 11 items (Hamster wheel + cat toys)
+(11, 5, 1, 15.00), -- 1x Hamster Wheel
+(11, 2, 3, 10.00), -- 3x Cat Toy Ball
+-- Sale 12 items (Fish tank + accessories)
+(12, 4, 2, 80.00), -- 2x Fish Tank 20 Gallon
+-- Sale 13 items (Dog treats bulk)
+(13, 6, 4, 25.00), -- 4x Organic Dog Treats
+-- Sale 14 items (Dog food)
+(14, 1, 1, 50.00), -- 1x Premium Dog Food
+-- Sale 15 items (Mixed supplies)
+(15, 2, 2, 10.00), -- 2x Cat Toy Ball
+(15, 5, 1, 15.00), -- 1x Hamster Wheel
+(15, 6, 2, 25.00), -- 2x Organic Dog Treats
+-- Sale 16 items (Bird cage + hamster wheel)
+(16, 3, 1, 120.00), -- 1x Bird Cage Large
+(16, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon
+-- Sale 17 items (Fish tank + cat toys)
+(17, 4, 1, 80.00), -- 1x Fish Tank 20 Gallon
+(17, 1, 1, 50.00), -- 1x Premium Dog Food
+(17, 6, 1, 25.00), -- 1x Organic Dog Treats
+-- Sale 18 items (Dog treats + toys)
+(18, 6, 2, 25.00), -- 2x Organic Dog Treats
+(18, 2, 2, 10.00), -- 2x Cat Toy Ball
+(18, 5, 1, 15.00), -- 1x Hamster Wheel
+-- Sale 19 items (Dog food + treats)
+(19, 1, 2, 50.00), -- 2x Premium Dog Food
+(19, 6, 2, 25.00), -- 2x Organic Dog Treats (discount applied)
+-- Sale 20 items (Recent sale)
+(20, 2, 5, 10.00), -- 5x Cat Toy Ball
+(20, 5, 3, 15.00); -- 3x Hamster Wheel
+
+INSERT INTO purchaseOrder (supId, orderDate, status)
+VALUES
+(1, '2025-01-15', 'Delivered'),
+(2, '2025-01-20', 'Pending'),
+(3, '2025-02-01', 'Delivered'),
+(4, '2025-02-10', 'In Transit'),
+(1, '2025-02-15', 'Pending');
INSERT INTO activityLog (employeeId, activity)
VALUES
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..b525f4b2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,48 @@
+# Pet Shop Desktop (JavaFX)
+
+Desktop pet shop management app built with JavaFX and MySQL.
+
+Made by **Group 2**, Shiv, Nikitha, Alex, Harkamal.
+
+## Requirements
+
+- IntelliJ IDEA (Community or Ultimate)
+- Java 17+
+- Maven (handled through IntelliJ)
+- Docker and Docker Compose (for the local MySQL container)
+
+## Database setup (IntelliJ)
+
+1. Open the project in IntelliJ.
+2. Open **View → Tool Windows → Services**.
+3. Add a Docker connection if needed, then open the **Docker** section in Services.
+4. Start the Compose stack from `docker-compose.yml` (Compose Up, or Start).
+5. Confirm the `mysql` service is running.
+
+The container uses `mysql:8.4`, creates the `Petstoredb` database, and imports `Petstoredata.sql`.
+
+## App configuration
+
+An example connection file is provided at `connectionpetstore.properties.example`. Copy it to `connectionpetstore.properties` and edit the values to match the local database setup.
+
+## Run the app (IntelliJ, Maven)
+
+1. Open **View → Tool Windows → Maven**.
+2. Click **Reload All Maven Projects** if the dependencies have not loaded yet.
+3. In the Maven tool window, expand **Plugins → javafx**.
+4. Double click **javafx:run**.
+
+Optional, run a clean first:
+- In the Maven tool window, expand **Lifecycle** and run **clean**.
+
+## Default accounts
+
+On first run, the app creates a `users` table (if missing) and seeds two accounts:
+
+- Admin: `admin` / `admin123`
+- Staff: `staff` / `staff123`
+
+## Notes
+
+- `connectionpetstore.properties` is gitignored so credentials are not committed.
+- If the app cannot connect to MySQL, confirm the Compose stack is running and MySQL is available.
diff --git a/pom.xml b/pom.xml
index 6da4edce..3c683c9e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
PetShopDesktop
UTF-8
- 25.0.2
+ 17.0.10
5.12.1
@@ -53,8 +53,7 @@
maven-compiler-plugin
3.13.0
- 23
- 23
+ 17
diff --git a/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java b/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java
new file mode 100644
index 00000000..a20bf5fd
--- /dev/null
+++ b/src/main/java/org/example/petshopdesktop/DTOs/SaleDTO.java
@@ -0,0 +1,93 @@
+package org.example.petshopdesktop.DTOs;
+
+import javafx.beans.property.*;
+
+public class SaleDTO {
+
+ private IntegerProperty saleId;
+ private StringProperty saleDate;
+ private StringProperty employeeName;
+ private StringProperty productName;
+ private IntegerProperty quantity;
+ private DoubleProperty unitPrice;
+ private DoubleProperty total;
+ private StringProperty paymentMethod;
+
+ public SaleDTO(int saleId, String saleDate, String employeeName, String productName,
+ int quantity, double unitPrice, double total, String paymentMethod) {
+ this.saleId = new SimpleIntegerProperty(saleId);
+ this.saleDate = new SimpleStringProperty(saleDate);
+ this.employeeName = new SimpleStringProperty(employeeName);
+ this.productName = new SimpleStringProperty(productName);
+ this.quantity = new SimpleIntegerProperty(quantity);
+ this.unitPrice = new SimpleDoubleProperty(unitPrice);
+ this.total = new SimpleDoubleProperty(total);
+ this.paymentMethod = new SimpleStringProperty(paymentMethod);
+ }
+
+ // Getters
+ public int getSaleId() {
+ return saleId.get();
+ }
+
+ public String getSaleDate() {
+ return saleDate.get();
+ }
+
+ public String getEmployeeName() {
+ return employeeName.get();
+ }
+
+ public String getProductName() {
+ return productName.get();
+ }
+
+ public int getQuantity() {
+ return quantity.get();
+ }
+
+ public double getUnitPrice() {
+ return unitPrice.get();
+ }
+
+ public double getTotal() {
+ return total.get();
+ }
+
+ public String getPaymentMethod() {
+ return paymentMethod.get();
+ }
+
+ // Properties
+ public IntegerProperty saleIdProperty() {
+ return saleId;
+ }
+
+ public StringProperty saleDateProperty() {
+ return saleDate;
+ }
+
+ public StringProperty employeeNameProperty() {
+ return employeeName;
+ }
+
+ public StringProperty productNameProperty() {
+ return productName;
+ }
+
+ public IntegerProperty quantityProperty() {
+ return quantity;
+ }
+
+ public DoubleProperty unitPriceProperty() {
+ return unitPrice;
+ }
+
+ public DoubleProperty totalProperty() {
+ return total;
+ }
+
+ public StringProperty paymentMethodProperty() {
+ return paymentMethod;
+ }
+}
diff --git a/src/main/java/org/example/petshopdesktop/Launcher.java b/src/main/java/org/example/petshopdesktop/Launcher.java
index 8b91cd9c..70456482 100644
--- a/src/main/java/org/example/petshopdesktop/Launcher.java
+++ b/src/main/java/org/example/petshopdesktop/Launcher.java
@@ -1,5 +1,3 @@
-//Initial commmit
-
package org.example.petshopdesktop;
import javafx.application.Application;
diff --git a/src/main/java/org/example/petshopdesktop/Validator.class b/src/main/java/org/example/petshopdesktop/Validator.class
new file mode 100644
index 00000000..b5653a2d
Binary files /dev/null and b/src/main/java/org/example/petshopdesktop/Validator.class differ
diff --git a/src/main/java/org/example/petshopdesktop/Validator.java b/src/main/java/org/example/petshopdesktop/Validator.java
index 29182e20..9c6f78a6 100644
--- a/src/main/java/org/example/petshopdesktop/Validator.java
+++ b/src/main/java/org/example/petshopdesktop/Validator.java
@@ -3,14 +3,14 @@ package org.example.petshopdesktop;
public class Validator {
/**
- * Checks if string is not empty
+ * Checks if string is not blank
* @param value string to check
* @param name name of the input
- * @return error msg if string is not empty, otherwise empty
+ * @return error msg if string is blank, otherwise empty
*/
public static String isPresent(String value, String name){
- String msg =""; //OK so far
- if(value.isEmpty() || name.isBlank()){
+ String msg = "";
+ if (value == null || value.isBlank()){
msg += name + " is required. \n";
}
return msg;
@@ -23,7 +23,7 @@ public class Validator {
* @return error msg if input is not a number or negative, otherwise empty
*/
public static String isNonNegativeDouble(String value, String name){
- String msg =""; //OK so far
+ String msg ="";
double result;
try{
result = Double.parseDouble(value);
@@ -40,13 +40,13 @@ public class Validator {
/**
* Checks if the input is a double in 2 different range
* @param value input of string
- * @param name name of inpt
+ * @param name name of input
* @param minValue min value of range
* @param maxValue max value of range
* @return error msg if input is out of range, otherwise empty
*/
public static String isDoubleInRange(String value, String name, double minValue, double maxValue){
- String msg =""; //OK so far
+ String msg ="";
double result;
try{
result = Double.parseDouble(value);
@@ -67,7 +67,7 @@ public class Validator {
* @return error msg if input is not a number or negative, otherwise empty
*/
public static String isNonNegativeInteger(String value, String name){
- String msg =""; //OK so far
+ String msg ="";
int result;
try{
result = Integer.parseInt(value);
@@ -85,6 +85,7 @@ public class Validator {
* check if the string is a given amount of characters or fewer
* @param value input of string
* @param name name of input
+ * @param length max allowed length
* @return error msg if input is more than given characters length
*/
public static String isLessThanVarChars(String value, String name, int length){
@@ -102,8 +103,7 @@ public class Validator {
* @return error msg if input is not a valid email format, otherwise empty
*/
public static String isValidEmail(String value, String name){
- String msg = ""; //OK so far
- // Email regex
+ String msg = "";
String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
if (!value.matches(regex)){
@@ -119,8 +119,7 @@ public class Validator {
* @return error msg if input is not in valid phone format, otherwise empty
*/
public static String isValidPhoneNumber(String value, String name){
- String msg = ""; //OK so far
- // Phone regex
+ String msg = "";
String regex = "^\\d{3}-\\d{3}-\\d{4}$";
if (!value.matches(regex)){
diff --git a/src/main/java/org/example/petshopdesktop/auth/Role.class b/src/main/java/org/example/petshopdesktop/auth/Role.class
new file mode 100644
index 00000000..b3d3c6cb
Binary files /dev/null and b/src/main/java/org/example/petshopdesktop/auth/Role.class differ
diff --git a/src/main/java/org/example/petshopdesktop/auth/Role.java b/src/main/java/org/example/petshopdesktop/auth/Role.java
index e631ddf7..64f38459 100644
--- a/src/main/java/org/example/petshopdesktop/auth/Role.java
+++ b/src/main/java/org/example/petshopdesktop/auth/Role.java
@@ -1,13 +1,6 @@
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
index 2cc4538a..450fb3ff 100644
--- a/src/main/java/org/example/petshopdesktop/auth/UserSession.java
+++ b/src/main/java/org/example/petshopdesktop/auth/UserSession.java
@@ -1,24 +1,15 @@
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();
@@ -26,13 +17,11 @@ public class 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;
@@ -46,13 +35,10 @@ public class UserSession {
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/LoginController.java b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java
index 440c5686..43bb3334 100644
--- a/src/main/java/org/example/petshopdesktop/controllers/LoginController.java
+++ b/src/main/java/org/example/petshopdesktop/controllers/LoginController.java
@@ -14,10 +14,6 @@ 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
@@ -31,18 +27,15 @@ public class LoginController {
@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,7 +43,6 @@ public class LoginController {
return;
}
- // Session state is stored in memory for use by controllers and UI RBAC.
UserSession.getInstance().login(user.getUsername(), user.getRole());
openMainLayout();
@@ -61,7 +53,6 @@ public class LoginController {
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());
diff --git a/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java b/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java
index 8198d517..01cab249 100644
--- a/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java
+++ b/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java
@@ -11,12 +11,23 @@ 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 {
+ private static final String NAV_BASE_STYLE = "-fx-background-color: transparent; " +
+ "-fx-text-fill: #cbd5e1; " +
+ "-fx-background-radius: 10; " +
+ "-fx-cursor: hand; " +
+ "-fx-focus-color: transparent; " +
+ "-fx-faint-focus-color: transparent;";
+
+ private static final String NAV_ACTIVE_STYLE = "-fx-background-color: #FF6B6B; " +
+ "-fx-text-fill: white; " +
+ "-fx-background-radius: 10; " +
+ "-fx-cursor: hand; " +
+ "-fx-focus-color: transparent; " +
+ "-fx-faint-focus-color: transparent; " +
+ "-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.22), 10, 0.15, 0, 2);";
+
@FXML
private Button btnAdoptions;
@@ -124,7 +135,6 @@ public class MainLayoutController {
@FXML
void btnLogoutClicked(ActionEvent event) {
- // Logout clears session state before returning to the login view.
UserSession.getInstance().logout();
try {
FXMLLoader loader = new FXMLLoader(
@@ -141,22 +151,18 @@ public class MainLayoutController {
@FXML
public void initialize() {
- // RBAC state is applied once during initial layout load.
applyRBAC();
- // Default landing view after successful authentication.
loadView("pet-view.fxml");
+ updateButtons(btnPets);
}
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());
+ lblRole.setText("Leon's Petstore");
- // 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);
@@ -165,7 +171,6 @@ public class MainLayoutController {
btnProductSuppliers.setVisible(isAdmin);
btnProductSuppliers.setManaged(isAdmin);
- // Privileged operations should still be enforced within the relevant controllers and database methods.
}
/**
@@ -174,10 +179,8 @@ public class MainLayoutController {
*/
private void loadView(String fxmlFile) {
try {
- //Get the location of the fxml for view
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/modelviews/" + fxmlFile));
Parent view = loader.load();
- //Clear any content that is in the stack pane and add the new view to display
spContentArea.getChildren().clear();
spContentArea.getChildren().add(view);
} catch (Exception e) {
@@ -191,21 +194,13 @@ public class MainLayoutController {
* @param activeButton the button to be set active
*/
private void updateButtons(Button activeButton) {
- //reset all buttons
Button[] BUTTONS = {btnAdoptions, btnPets, btnAppointments, btnInventory,
- btnSalesHistory, btnServices, btnSuppliers, btnProductSuppliers, btnProducts};
+ btnSalesHistory, btnServices, btnSuppliers, btnProductSuppliers, btnProducts, btnPurchaseOrders};
for (Button button : BUTTONS) {
- //set all buttons to inactive
- button.setStyle("-fx-background-color: transparent; " +
- "-fx-text-fill: #CCCCCC; " +
- "-fx-cursor: hand");
+ button.setStyle(NAV_BASE_STYLE);
}
- //set active button
- activeButton.setStyle("-fx-background-color: #FF6B6B; " +
- "-fx-text-fill: white; " +
- "-fx-cursor: hand; " +
- "-fx-background-radius: 8");
+ activeButton.setStyle(NAV_ACTIVE_STYLE);
}
}
diff --git a/src/main/java/org/example/petshopdesktop/controllers/SaleController.java b/src/main/java/org/example/petshopdesktop/controllers/SaleController.java
index a54139a2..41e74906 100644
--- a/src/main/java/org/example/petshopdesktop/controllers/SaleController.java
+++ b/src/main/java/org/example/petshopdesktop/controllers/SaleController.java
@@ -1,11 +1,14 @@
package org.example.petshopdesktop.controllers;
+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.scene.control.*;
+import javafx.scene.control.cell.PropertyValueFactory;
+import org.example.petshopdesktop.DTOs.SaleDTO;
+import org.example.petshopdesktop.database.SaleDB;
+
+import java.sql.SQLException;
public class SaleController {
@@ -13,38 +16,137 @@ public class SaleController {
private Button btnRefresh;
@FXML
- private TableColumn, ?> colCustomerName;
+ private TableColumn colCustomerName;
@FXML
- private TableColumn, ?> colSaleDate;
+ private TableColumn colSaleDate;
@FXML
- private TableColumn, ?> colSaleId;
+ private TableColumn colSaleId;
@FXML
- private TableColumn, ?> colSalePaymentType;
+ private TableColumn colSalePaymentType;
@FXML
- private TableColumn, ?> colSaleQuantity;
+ private TableColumn colSaleQuantity;
@FXML
- private TableColumn, ?> colSaleTotal;
+ private TableColumn colSaleTotal;
@FXML
- private TableColumn, ?> colSaleUnitPrice;
+ private TableColumn colSaleUnitPrice;
@FXML
- private TableColumn, ?> colServiceProduct;
+ private TableColumn colServiceProduct;
@FXML
- private TableView> tvSales;
+ private TableView tvSales;
@FXML
private TextField txtSearch;
- @FXML
- void btnRefresh(ActionEvent event) {
+ private ObservableList salesData;
+ /**
+ * Initialize the controller - set up table columns and load data
+ */
+ @FXML
+ public void initialize() {
+ // Set up table columns
+ colSaleId.setCellValueFactory(new PropertyValueFactory<>("saleId"));
+ colSaleDate.setCellValueFactory(new PropertyValueFactory<>("saleDate"));
+ colCustomerName.setCellValueFactory(new PropertyValueFactory<>("employeeName"));
+ colServiceProduct.setCellValueFactory(new PropertyValueFactory<>("productName"));
+ colSaleQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
+ colSaleUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
+ colSaleTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
+ colSalePaymentType.setCellValueFactory(new PropertyValueFactory<>("paymentMethod"));
+
+ // Format currency columns
+ colSaleUnitPrice.setCellFactory(tc -> new TableCell() {
+ @Override
+ protected void updateItem(Double price, boolean empty) {
+ super.updateItem(price, empty);
+ if (empty || price == null) {
+ setText(null);
+ } else {
+ setText(String.format("$%.2f", price));
+ }
+ }
+ });
+
+ colSaleTotal.setCellFactory(tc -> new TableCell() {
+ @Override
+ protected void updateItem(Double total, boolean empty) {
+ super.updateItem(total, empty);
+ if (empty || total == null) {
+ setText(null);
+ } else {
+ setText(String.format("$%.2f", total));
+ }
+ }
+ });
+
+ // Load initial data
+ loadSales();
+
+ // Add search functionality
+ txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
+ if (newValue == null || newValue.trim().isEmpty()) {
+ loadSales();
+ } else {
+ searchSales(newValue.trim());
+ }
+ });
}
+ /**
+ * Load all sales from database
+ */
+ private void loadSales() {
+ try {
+ salesData = SaleDB.getSales();
+ tvSales.setItems(salesData);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ showError("Failed to load sales data", e.getMessage());
+ }
+ }
+
+ /**
+ * Search sales based on filter text
+ * @param filter search term
+ */
+ private void searchSales(String filter) {
+ try {
+ ObservableList filteredSales = SaleDB.getFilteredSales(filter);
+ tvSales.setItems(filteredSales);
+ } catch (SQLException e) {
+ e.printStackTrace();
+ showError("Failed to search sales", e.getMessage());
+ }
+ }
+
+ /**
+ * Refresh button handler - reload all sales data
+ * @param event button click event
+ */
+ @FXML
+ void btnRefresh(ActionEvent event) {
+ txtSearch.clear();
+ loadSales();
+ }
+
+ /**
+ * Show error alert
+ * @param title alert title
+ * @param message error message
+ */
+ private void showError(String title, String message) {
+ Alert alert = new Alert(Alert.AlertType.ERROR);
+ alert.setTitle(title);
+ alert.setHeaderText(null);
+ alert.setContentText(message);
+ alert.showAndWait();
+ }
}
diff --git a/src/main/java/org/example/petshopdesktop/database/AdoptionDB.java b/src/main/java/org/example/petshopdesktop/database/AdoptionDB.java
index 87c087ef..6c436479 100644
--- a/src/main/java/org/example/petshopdesktop/database/AdoptionDB.java
+++ b/src/main/java/org/example/petshopdesktop/database/AdoptionDB.java
@@ -115,10 +115,10 @@ public class AdoptionDB {
Connection conn = ConnectionDB.getConnection();
Statement stmt = conn.createStatement();
- ResultSet rs = stmt.executeQuery("SELECT customerId, firstName, lastName FROM customer");
+ ResultSet rs = stmt.executeQuery("SELECT customerId, firstName, lastName, email, phone FROM customer");
while (rs.next()) {
- customers.add(new Customer(rs.getInt(1), rs.getString(2), rs.getString(3)));
+ customers.add(new Customer(rs.getInt(1), rs.getString(2), rs.getString(3), rs.getString(4), rs.getString(5)));
}
conn.close();
diff --git a/src/main/java/org/example/petshopdesktop/database/ProductSupplierDB.java b/src/main/java/org/example/petshopdesktop/database/ProductSupplierDB.java
index cc651bd0..c3298699 100644
--- a/src/main/java/org/example/petshopdesktop/database/ProductSupplierDB.java
+++ b/src/main/java/org/example/petshopdesktop/database/ProductSupplierDB.java
@@ -23,7 +23,7 @@ public class ProductSupplierDB {
//Execute Query
Statement stmt = conn.createStatement();
String sql = "SELECT ps.supId, ps.prodId, s.supCompany, p.prodName, ps.cost " +
- "FROM productsupplier ps " +
+ "FROM productSupplier ps " +
"LEFT JOIN product p " +
"ON p.prodId = ps.prodId " +
"LEFT JOIN supplier s " +
@@ -62,7 +62,7 @@ public class ProductSupplierDB {
String sql =
"SELECT ps.supId, ps.prodId, s.supCompany, p.prodName, ps.cost " +
"FROM product p " +
- "LEFT JOIN productsupplier ps " +
+ "LEFT JOIN productSupplier ps " +
"ON p.prodId = ps.prodId " +
"LEFT JOIN supplier s " +
"ON s.supId = ps.supId " +
@@ -108,7 +108,7 @@ public class ProductSupplierDB {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
- String sql = "INSERT INTO productsupplier (prodId, supId, cost) " +
+ String sql = "INSERT INTO productSupplier (prodId, supId, cost) " +
"VALUES (?, ?, ?)";
//These are the values from productSupplier to put into query above
@@ -141,7 +141,7 @@ public class ProductSupplierDB {
try{
//Delete old data first
- String sql = "DELETE FROM productsupplier WHERE supId = ? AND prodId = ?";
+ String sql = "DELETE FROM productSupplier WHERE supId = ? AND prodId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, oldSupId);
stmt.setInt(2, oldProdId);
@@ -149,7 +149,7 @@ public class ProductSupplierDB {
//Then change the data by inserting a new relation with given keys (only if delete worked)
if(numRows > 0){
- sql = "INSERT INTO productsupplier (prodId, supId, cost) " +
+ sql = "INSERT INTO productSupplier (prodId, supId, cost) " +
"VALUES (?, ?, ?)";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, productSupplier.getProdId());
@@ -184,7 +184,7 @@ public class ProductSupplierDB {
int numRows = 0;
Connection conn = ConnectionDB.getConnection();
- String sql = "DELETE FROM productsupplier WHERE supId = ? AND prodId = ?";
+ String sql = "DELETE FROM productSupplier WHERE supId = ? AND prodId = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, supId);
stmt.setInt(2, prodId);
diff --git a/src/main/java/org/example/petshopdesktop/database/SaleDB.java b/src/main/java/org/example/petshopdesktop/database/SaleDB.java
new file mode 100644
index 00000000..43bc798d
--- /dev/null
+++ b/src/main/java/org/example/petshopdesktop/database/SaleDB.java
@@ -0,0 +1,113 @@
+package org.example.petshopdesktop.database;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import org.example.petshopdesktop.DTOs.SaleDTO;
+
+import java.sql.*;
+
+public class SaleDB {
+
+ /**
+ * Get all sale items with details
+ * @return ObservableList of SaleDTOs
+ * @throws SQLException if database operation fails
+ */
+ public static ObservableList getSales() throws SQLException {
+ ObservableList sales = FXCollections.observableArrayList();
+ Connection conn = ConnectionDB.getConnection();
+
+ String sql = """
+ SELECT
+ s.saleId,
+ DATE_FORMAT(s.saleDate, '%Y-%m-%d %H:%i') as saleDate,
+ CONCAT(e.firstName, ' ', e.lastName) as employeeName,
+ p.prodName,
+ si.quantity,
+ si.unitPrice,
+ (si.quantity * si.unitPrice) as lineTotal,
+ s.paymentMethod
+ FROM sale s
+ JOIN saleItem si ON s.saleId = si.saleId
+ JOIN product p ON si.prodId = p.prodId
+ JOIN employee e ON s.employeeId = e.employeeId
+ ORDER BY s.saleDate DESC, s.saleId, si.saleItemId
+ """;
+
+ Statement stmt = conn.createStatement();
+ ResultSet rs = stmt.executeQuery(sql);
+
+ while (rs.next()) {
+ sales.add(new SaleDTO(
+ rs.getInt("saleId"),
+ rs.getString("saleDate"),
+ rs.getString("employeeName"),
+ rs.getString("prodName"),
+ rs.getInt("quantity"),
+ rs.getDouble("unitPrice"),
+ rs.getDouble("lineTotal"),
+ rs.getString("paymentMethod")
+ ));
+ }
+
+ conn.close();
+ return sales;
+ }
+
+ /**
+ * Get filtered sale items
+ * @param filter search term
+ * @return ObservableList of SaleDTOs matching the filter
+ * @throws SQLException if database operation fails
+ */
+ public static ObservableList getFilteredSales(String filter) throws SQLException {
+ ObservableList sales = FXCollections.observableArrayList();
+ Connection conn = ConnectionDB.getConnection();
+
+ String sql = """
+ SELECT
+ s.saleId,
+ DATE_FORMAT(s.saleDate, '%Y-%m-%d %H:%i') as saleDate,
+ CONCAT(e.firstName, ' ', e.lastName) as employeeName,
+ p.prodName,
+ si.quantity,
+ si.unitPrice,
+ (si.quantity * si.unitPrice) as lineTotal,
+ s.paymentMethod
+ FROM sale s
+ JOIN saleItem si ON s.saleId = si.saleId
+ JOIN product p ON si.prodId = p.prodId
+ JOIN employee e ON s.employeeId = e.employeeId
+ WHERE s.saleId LIKE ?
+ OR p.prodName LIKE ?
+ OR CONCAT(e.firstName, ' ', e.lastName) LIKE ?
+ OR s.paymentMethod LIKE ?
+ ORDER BY s.saleDate DESC, s.saleId, si.saleItemId
+ """;
+
+ PreparedStatement pstmt = conn.prepareStatement(sql);
+ String searchPattern = "%" + filter + "%";
+ pstmt.setString(1, searchPattern);
+ pstmt.setString(2, searchPattern);
+ pstmt.setString(3, searchPattern);
+ pstmt.setString(4, searchPattern);
+
+ ResultSet rs = pstmt.executeQuery();
+
+ while (rs.next()) {
+ sales.add(new SaleDTO(
+ rs.getInt("saleId"),
+ rs.getString("saleDate"),
+ rs.getString("employeeName"),
+ rs.getString("prodName"),
+ rs.getInt("quantity"),
+ rs.getDouble("unitPrice"),
+ rs.getDouble("lineTotal"),
+ rs.getString("paymentMethod")
+ ));
+ }
+
+ conn.close();
+ return sales;
+ }
+}
diff --git a/src/main/java/org/example/petshopdesktop/database/UserDB.java b/src/main/java/org/example/petshopdesktop/database/UserDB.java
index 3b5f78b4..88efe556 100644
--- a/src/main/java/org/example/petshopdesktop/database/UserDB.java
+++ b/src/main/java/org/example/petshopdesktop/database/UserDB.java
@@ -5,10 +5,6 @@ import org.example.petshopdesktop.models.User;
import java.sql.*;
-/*
-Petshop Desktop
-Purpose: User authentication and role lookup against the users table.
-*/
public class UserDB {
/**
@@ -34,8 +30,6 @@ public class UserDB {
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);
@@ -59,7 +53,6 @@ public class UserDB {
)
""";
- // 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')
diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml
index ef02cdf0..c3a14b62 100644
--- a/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml
@@ -13,7 +13,7 @@
-
+
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
index b12bc777..99cf0833 100644
--- a/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml
@@ -17,7 +17,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml
index 0784811b..d4c97ddb 100644
--- a/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml
@@ -13,7 +13,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml
index 2972f5ed..2164ec66 100644
--- a/src/main/resources/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/dialogviews/product-dialog-view.fxml
@@ -13,7 +13,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml
index 0ef57a60..ec29e4b6 100644
--- a/src/main/resources/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/dialogviews/product-supplier-dialog-view.fxml
@@ -13,7 +13,7 @@
-
+
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
index 327ce2d0..6cf098fd 100644
--- a/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml
@@ -13,7 +13,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml
index e26557c8..968d1021 100644
--- a/src/main/resources/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/dialogviews/supplier-dialog-view.fxml
@@ -12,7 +12,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/login-view.fxml b/src/main/resources/org/example/petshopdesktop/login-view.fxml
index 65199c95..ad6e0bca 100644
--- a/src/main/resources/org/example/petshopdesktop/login-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/login-view.fxml
@@ -10,7 +10,7 @@
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 a1471b98..fdf5c267 100644
--- a/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml
@@ -5,131 +5,146 @@
+
-
+
-
+
-
+
-
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml
index c1ec6564..9443ef14 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml
@@ -11,7 +11,7 @@
-
+
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 4bfd3029..f698c5c1 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml
index dea27d1b..18e9ad5a 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/pet-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/pet-view.fxml
index a9ab6772..43c3c323 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/pet-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/pet-view.fxml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/product-supplier-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/product-supplier-view.fxml
index 6ef50d13..19817faa 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/product-supplier-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/product-supplier-view.fxml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/product-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/product-view.fxml
index 2473d057..d9b9a37c 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/product-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/product-view.fxml
@@ -11,7 +11,7 @@
-
+
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
index e4c3a488..74a00e6d 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/purchase-order-view.fxml
@@ -7,7 +7,7 @@
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/purchaseorder-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/purchaseorder-view.fxml
deleted file mode 100644
index fdac860e..00000000
--- a/src/main/resources/org/example/petshopdesktop/modelviews/purchaseorder-view.fxml
+++ /dev/null
@@ -1,82 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml
index 26b24418..8ed34120 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/sale-view.fxml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/service-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/service-view.fxml
index 2adac139..5353b0e6 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/service-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/service-view.fxml
@@ -11,7 +11,7 @@
-
+
diff --git a/src/main/resources/org/example/petshopdesktop/modelviews/supplier-view.fxml b/src/main/resources/org/example/petshopdesktop/modelviews/supplier-view.fxml
index 423ae2bf..9c6d75cc 100644
--- a/src/main/resources/org/example/petshopdesktop/modelviews/supplier-view.fxml
+++ b/src/main/resources/org/example/petshopdesktop/modelviews/supplier-view.fxml
@@ -11,7 +11,7 @@
-
+