Merge branch 'main' into AdoptionInventory

This commit is contained in:
2026-02-24 16:46:46 -07:00
committed by GitHub
32 changed files with 2453 additions and 103 deletions

View File

@@ -8,7 +8,9 @@ module org.example.petshopdesktop {
opens org.example.petshopdesktop to javafx.fxml;
opens org.example.petshopdesktop.controllers.dialogcontrollers to javafx.fxml;
opens org.example.petshopdesktop.controllers to javafx.fxml;
opens org.example.petshopdesktop.auth to javafx.fxml;
exports org.example.petshopdesktop;
exports org.example.petshopdesktop.controllers;
exports org.example.petshopdesktop.auth;
}

View File

@@ -0,0 +1,59 @@
package org.example.petshopdesktop.DTOs;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
public class AppointmentDTO {
private SimpleIntegerProperty appointmentId;
private SimpleIntegerProperty customerId;
private SimpleStringProperty customerName;
private SimpleIntegerProperty petId;
private SimpleStringProperty petName;
private SimpleIntegerProperty serviceId;
private SimpleStringProperty serviceName;
private SimpleStringProperty appointmentDate;
private SimpleStringProperty appointmentTime;
private SimpleStringProperty appointmentStatus;
// Constructor
public AppointmentDTO(int appointmentId,
int customerId, String customerName,
int petId, String petName,
int serviceId, String serviceName,
String appointmentDate,
String appointmentTime,
String appointmentStatus) {
this.appointmentId = new SimpleIntegerProperty(appointmentId);
this.customerId = new SimpleIntegerProperty(customerId);
this.customerName = new SimpleStringProperty(customerName);
this.petId = new SimpleIntegerProperty(petId);
this.petName = new SimpleStringProperty(petName);
this.serviceId = new SimpleIntegerProperty(serviceId);
this.serviceName = new SimpleStringProperty(serviceName);
this.appointmentDate = new SimpleStringProperty(appointmentDate);
this.appointmentTime = new SimpleStringProperty(appointmentTime);
this.appointmentStatus = new SimpleStringProperty(appointmentStatus);
}
// Getters
public int getAppointmentId() { return appointmentId.get(); }
public int getCustomerId() { return customerId.get(); }
public String getCustomerName() { return customerName.get(); }
public int getPetId() { return petId.get(); }
public String getPetName() { return petName.get(); }
public int getServiceId() { return serviceId.get(); }
public String getServiceName() { return serviceName.get(); }
public String getAppointmentDate() { return appointmentDate.get(); }
public String getAppointmentTime() { return appointmentTime.get(); }
public String getAppointmentStatus() { return appointmentStatus.get(); }
}

View File

@@ -0,0 +1,25 @@
package org.example.petshopdesktop.DTOs;
import javafx.beans.property.*;
public class PurchaseOrderDTO {
private IntegerProperty purchaseOrderId;
private StringProperty supplierName;
private StringProperty orderDate;
private StringProperty status;
public PurchaseOrderDTO(int id, String supplierName,
String orderDate, String status) {
this.purchaseOrderId = new SimpleIntegerProperty(id);
this.supplierName = new SimpleStringProperty(supplierName);
this.orderDate = new SimpleStringProperty(orderDate);
this.status = new SimpleStringProperty(status);
}
public int getPurchaseOrderId() { return purchaseOrderId.get(); }
public String getSupplierName() { return supplierName.get(); }
public String getOrderDate() { return orderDate.get(); }
public String getStatus() { return status.get(); }
}

View File

@@ -0,0 +1,110 @@
package org.example.petshopdesktop.DTOs;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import org.example.petshopdesktop.models.Service;
/**
* The class for ServiceDTO, all service data stored here
*/
public class ServiceDTO {
private SimpleIntegerProperty serviceId;
private SimpleStringProperty serviceName;
private SimpleStringProperty serviceDesc;
private SimpleIntegerProperty serviceDuration;
private SimpleDoubleProperty servicePrice;
// constructor
public ServiceDTO(int serviceId,
String serviceName,
String serviceDesc,
int serviceDuration,
double servicePrice) {
this.serviceId = new SimpleIntegerProperty(serviceId);
this.serviceName = new SimpleStringProperty(serviceName);
this.serviceDesc = new SimpleStringProperty(serviceDesc);
this.serviceDuration = new SimpleIntegerProperty(serviceDuration);
this.servicePrice = new SimpleDoubleProperty(servicePrice);
}
// getters & setters
public int getServiceId() {
return serviceId.get();
}
public SimpleIntegerProperty serviceIdProperty() {
return serviceId;
}
public void setServiceId(int serviceId) {
this.serviceId.set(serviceId);
}
public String getServiceName() {
return serviceName.get();
}
public SimpleStringProperty serviceNameProperty() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName.set(serviceName);
}
public String getServiceDesc() {
return serviceDesc.get();
}
public SimpleStringProperty serviceDescProperty() {
return serviceDesc;
}
public void setServiceDesc(String serviceDesc) {
this.serviceDesc.set(serviceDesc);
}
public int getServiceDuration() {
return serviceDuration.get();
}
public SimpleIntegerProperty serviceDurationProperty() {
return serviceDuration;
}
public void setServiceDuration(int serviceDuration) {
this.serviceDuration.set(serviceDuration);
}
public double getServicePrice() {
return servicePrice.get();
}
public SimpleDoubleProperty servicePriceProperty() {
return servicePrice;
}
public void setServicePrice(double servicePrice) {
this.servicePrice.set(servicePrice);
}
/**
* Converts DTO into Service model (for edit/delete)
*/
public Service toService() {
Service service = new Service(
getServiceId(),
getServiceName(),
getServiceDesc(),
getServiceDuration(),
getServicePrice()
);
return service;
}
}

View File

@@ -4,15 +4,21 @@ import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.UserDB;
import java.io.IOException;
public class PetShopApplication extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("main-layout-view.fxml"));
try {
UserDB.initializeTable();
} catch (Exception e) {
System.err.println("Warning: could not initialize users table: " + e.getMessage());
}
FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("login-view.fxml"));
Scene scene = new Scene(fxmlLoader.load());
stage.setTitle("Pet Shop Manager");
stage.setTitle("Pet Shop Manager - Login");
stage.setScene(scene);
stage.show();
}

View File

@@ -0,0 +1,13 @@
package org.example.petshopdesktop.auth;
/*
Petshop Desktop
Purpose: Application role definitions used by session state and role based access control.
*/
public enum Role {
// Administrative access, includes system management screens.
ADMIN,
// Staff access, limited to day to day operational screens.
STAFF
}

View File

@@ -0,0 +1,59 @@
package org.example.petshopdesktop.auth;
/*
Petshop Desktop
Purpose: In memory session state for the authenticated user.
Notes: Session is process local and cleared on logout or application restart.
*/
public class UserSession {
// Singleton instance used to share session state across controllers.
private static UserSession instance;
// Current authenticated username, null when logged out.
private String username;
// Current authenticated role, null when logged out.
private Role role;
private UserSession() {}
// Lazily initialised singleton accessor.
public static UserSession getInstance() {
if (instance == null) {
instance = new UserSession();
}
return instance;
}
// Stores identity and role for the active session.
public void login(String username, Role role) {
this.username = username;
this.role = role;
}
// Clears session state and returns the application to an unauthenticated state.
public void logout() {
this.username = null;
this.role = null;
}
public String getUsername() {
return username;
}
public Role getRole() {
return role;
}
// Convenience check for administrative privileges.
// Role.ADMIN.equals(role) remains safe when role is null.
public boolean isAdmin() {
return Role.ADMIN.equals(role);
}
// Session is considered active only when both username and role are set.
public boolean isLoggedIn() {
return username != null && role != null;
}
}

View File

@@ -1,63 +1,135 @@
package org.example.petshopdesktop.controllers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController;
import org.example.petshopdesktop.database.AppointmentDB;
public class AppointmentController {
@FXML
private Button btnAdd;
@FXML private TableView<AppointmentDTO> tvAppointments;
@FXML private TableColumn<AppointmentDTO,Integer> colAppointmentId;
@FXML private TableColumn<AppointmentDTO,String> colPetName;
@FXML private TableColumn<AppointmentDTO,String> colServiceName;
@FXML private TableColumn<AppointmentDTO,String> colAppointmentDate;
@FXML private TableColumn<AppointmentDTO,String> colAppointmentTime;
@FXML private TableColumn<AppointmentDTO,String> colCustomerName;
@FXML private TableColumn<AppointmentDTO,String> colAppointmentStatus;
@FXML private Button btnAdd;
@FXML private Button btnEdit;
@FXML private Button btnDelete;
@FXML private TextField txtSearch;
private ObservableList<AppointmentDTO> data = FXCollections.observableArrayList();
@FXML
private Button btnDelete;
public void initialize(){
@FXML
private Button btnEdit;
colAppointmentId.setCellValueFactory(new PropertyValueFactory<>("appointmentId"));
colPetName.setCellValueFactory(new PropertyValueFactory<>("petName"));
colServiceName.setCellValueFactory(new PropertyValueFactory<>("serviceName"));
colAppointmentDate.setCellValueFactory(new PropertyValueFactory<>("appointmentDate"));
colAppointmentTime.setCellValueFactory(new PropertyValueFactory<>("appointmentTime"));
colCustomerName.setCellValueFactory(new PropertyValueFactory<>("customerName"));
colAppointmentStatus.setCellValueFactory(new PropertyValueFactory<>("appointmentStatus"));
@FXML
private TableColumn<?, ?> colAppointmentDate;
@FXML
private TableColumn<?, ?> colAppointmentId;
@FXML
private TableColumn<?, ?> colAppointmentStatus;
@FXML
private TableColumn<?, ?> colAppointmentTime;
@FXML
private TableColumn<?, ?> colCustomerName;
@FXML
private TableColumn<?, ?> colPetName;
@FXML
private TableColumn<?, ?> colServiceName;
@FXML
private TableView<?> tvAppointments;
@FXML
private TextField txtSearch;
@FXML
void btnAddClicked(ActionEvent event) {
loadAppointments();
}
private void loadAppointments(){
try{
data = AppointmentDB.getAppointmentDTOs();
tvAppointments.setItems(data);
}catch(Exception e){
e.printStackTrace();
}
}
@FXML
void btnDeleteClicked(ActionEvent event) {
void btnAddClicked(ActionEvent event){
openDialog(null, "Add");
}
@FXML
void btnEditClicked(ActionEvent event) {
void btnEditClicked(ActionEvent event){
AppointmentDTO selected =
tvAppointments.getSelectionModel().getSelectedItem();
if(selected == null){
showAlert("Select Appointment", "Please select appointment to edit.");
return;
}
openDialog(selected, "Edit");
}
@FXML
void btnDeleteClicked(ActionEvent event){
AppointmentDTO selected =
tvAppointments.getSelectionModel().getSelectedItem();
if(selected == null) return;
try{
AppointmentDB.deleteAppointment(selected.getAppointmentId());
loadAppointments();
}catch(Exception e){
e.printStackTrace();
}
}
private void openDialog(AppointmentDTO appt, String mode){
try{
FXMLLoader loader = new FXMLLoader(
getClass().getResource(
"/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml"
)
);
Scene scene = new Scene(loader.load());
AppointmentDialogController controller =
loader.getController();
controller.setMode(mode);
if(mode.equals("Edit")){
controller.displayAppointmentDetails(appt);
}
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.setScene(scene);
stage.showAndWait();
loadAppointments();
}catch(Exception e){
e.printStackTrace();
}
}
private void showAlert(String title, String msg){
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(msg);
alert.showAndWait();
}
}

View File

@@ -0,0 +1,76 @@
package org.example.petshopdesktop.controllers;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.database.UserDB;
import org.example.petshopdesktop.models.User;
import java.sql.SQLException;
/*
Petshop Desktop
Purpose: Authentication controller responsible for validating credentials and initialising the user session.
*/
public class LoginController {
@FXML
private TextField txtUsername;
@FXML
private PasswordField txtPassword;
@FXML
private Label lblError;
@FXML
void btnLoginClicked(ActionEvent event) {
// Input normalisation keeps authentication behaviour consistent.
String username = txtUsername.getText().trim();
String password = txtPassword.getText();
// Basic validation to avoid unnecessary database calls.
if (username.isEmpty() || password.isEmpty()) {
lblError.setText("Please enter username and password.");
return;
}
try {
// Credential verification returns a fully populated User on success.
User user = UserDB.authenticate(username, password);
if (user == null) {
lblError.setText("Invalid username or password.");
txtPassword.clear();
return;
}
// Session state is stored in memory for use by controllers and UI RBAC.
UserSession.getInstance().login(user.getUsername(), user.getRole());
openMainLayout();
} catch (SQLException e) {
lblError.setText("Database error: " + e.getMessage());
}
}
private void openMainLayout() {
try {
// View transition into the post login application shell.
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/org/example/petshopdesktop/main-layout-view.fxml"));
Scene scene = new Scene(loader.load());
Stage stage = (Stage) txtUsername.getScene().getWindow();
stage.setScene(scene);
stage.setTitle("Pet Shop Manager");
} catch (Exception e) {
lblError.setText("Error loading application: " + e.getMessage());
e.printStackTrace();
}
}
}

View File

@@ -4,9 +4,17 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.example.petshopdesktop.auth.UserSession;
/*
Petshop Desktop
Purpose: Main application shell controller, includes navigation and UI level role based access control.
*/
public class MainLayoutController {
@FXML
@@ -18,6 +26,9 @@ public class MainLayoutController {
@FXML
private Button btnInventory;
@FXML
private Button btnLogout;
@FXML
private Button btnPets;
@@ -30,12 +41,21 @@ public class MainLayoutController {
@FXML
private Button btnSalesHistory;
@FXML
private Button btnPurchaseOrders;
@FXML
private Button btnServices;
@FXML
private Button btnSuppliers;
@FXML
private Label lblUsername;
@FXML
private Label lblRole;
@FXML
private StackPane spContentArea;
@@ -81,6 +101,12 @@ public class MainLayoutController {
updateButtons(btnSalesHistory);
}
@FXML
void btnPurchaseOrdersClicked() {
loadView("purchase-order-view.fxml");
updateButtons(btnPurchaseOrders);
}
@FXML
void btnServicesClicked(ActionEvent event) {
loadView("service-view.fxml");
@@ -91,13 +117,57 @@ public class MainLayoutController {
void btnSuppliersClicked(ActionEvent event) {
loadView("supplier-view.fxml");
updateButtons(btnSuppliers);
}
@FXML
void btnLogoutClicked(ActionEvent event) {
// Logout clears session state before returning to the login view.
UserSession.getInstance().logout();
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/org/example/petshopdesktop/login-view.fxml"));
Scene scene = new Scene(loader.load());
Stage stage = (Stage) btnLogout.getScene().getWindow();
stage.setScene(scene);
stage.setTitle("Pet Shop Manager - Login");
} catch (Exception e) {
System.err.println("Error loading login view: " + e.getMessage());
e.printStackTrace();
}
}
@FXML
public void initialize() {
// RBAC state is applied once during initial layout load.
applyRBAC();
// Default landing view after successful authentication.
loadView("pet-view.fxml");
}
private void applyRBAC() {
UserSession session = UserSession.getInstance();
// Session identity is displayed in the header for clarity and auditing.
lblUsername.setText(session.getUsername());
lblRole.setText(session.getRole().toString());
// UI level RBAC hides admin only navigation entries for non admin users.
// setManaged(false) removes the node from layout calculations to avoid empty spacing.
boolean isAdmin = session.isAdmin();
btnInventory.setVisible(isAdmin);
btnInventory.setManaged(isAdmin);
btnSuppliers.setVisible(isAdmin);
btnSuppliers.setManaged(isAdmin);
btnProductSuppliers.setVisible(isAdmin);
btnProductSuppliers.setManaged(isAdmin);
// Privileged operations should still be enforced within the relevant controllers and database methods.
}
/**
* Load a view when a button is clicked on the navigation
* @param fxmlFile the fxmlFile name to be loaded

View File

@@ -0,0 +1,54 @@
package org.example.petshopdesktop.controllers;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import org.example.petshopdesktop.DTOs.PurchaseOrderDTO;
import org.example.petshopdesktop.database.PurchaseOrderDB;
public class PurchaseOrderController {
@FXML private Button btnRefresh;
@FXML private TableView<PurchaseOrderDTO> tvPurchaseOrders;
@FXML private TableColumn<PurchaseOrderDTO,Integer> colOrderId;
@FXML private TableColumn<PurchaseOrderDTO,String> colSupplier;
@FXML private TableColumn<PurchaseOrderDTO,String> colOrderDate;
@FXML private TableColumn<PurchaseOrderDTO,String> colStatus;
@FXML
public void initialize() {
colOrderId.setCellValueFactory(
new PropertyValueFactory<>("purchaseOrderId"));
colSupplier.setCellValueFactory(
new PropertyValueFactory<>("supplierName"));
colOrderDate.setCellValueFactory(
new PropertyValueFactory<>("orderDate"));
colStatus.setCellValueFactory(
new PropertyValueFactory<>("status"));
loadPurchaseOrders();
}
private void loadPurchaseOrders() {
try {
tvPurchaseOrders.setItems(
PurchaseOrderDB.getPurchaseOrders()
);
} catch (Exception e) {
e.printStackTrace();
new Alert(Alert.AlertType.ERROR,
"Unable to load purchase orders").showAndWait();
}
}
@FXML
void btnRefresh() {
loadPurchaseOrders();
}
}

View File

@@ -2,56 +2,123 @@ package org.example.petshopdesktop.controllers;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB;
import org.example.petshopdesktop.models.Service;
import org.example.petshopdesktop.controllers.dialogcontrollers.ServiceDialogController;
import javafx.stage.Modality;
public class ServiceController {
@FXML
private Button btnAdd;
@FXML private Button btnAdd;
@FXML private Button btnDelete;
@FXML private Button btnEdit;
@FXML private TableColumn<Service, Integer> colServiceId;
@FXML private TableColumn<Service, String> colServiceName;
@FXML private TableColumn<Service, String> colServiceDesc;
@FXML private TableColumn<Service, Integer> colServiceDuration;
@FXML private TableColumn<Service, Double> colServicePrice;
@FXML private TableView<Service> tvServices;
@FXML private TextField txtSearch;
@FXML
private Button btnDelete;
public void initialize() {
@FXML
private Button btnEdit;
colServiceId.setCellValueFactory(new PropertyValueFactory<>("serviceId"));
colServiceName.setCellValueFactory(new PropertyValueFactory<>("serviceName"));
colServiceDesc.setCellValueFactory(new PropertyValueFactory<>("serviceDesc"));
colServiceDuration.setCellValueFactory(new PropertyValueFactory<>("serviceDuration"));
colServicePrice.setCellValueFactory(new PropertyValueFactory<>("servicePrice"));
@FXML
private TableColumn<?, ?> colServiceDesc;
loadServices();
}
@FXML
private TableColumn<?, ?> colServiceDuration;
private void loadServices() {
try {
tvServices.setItems(ServiceDB.getServices());
} catch (Exception e) {
showAlert("Database Error", "Unable to load services.");
e.printStackTrace();
}
}
@FXML
private TableColumn<?, ?> colServiceId;
@FXML
private TableColumn<?, ?> colServiceName;
@FXML
private TableColumn<?, ?> colServicePrice;
@FXML
private TableView<?> tvServices;
@FXML
private TextField txtSearch;
@FXML
void btnAddClicked(ActionEvent event) {
}
@FXML
void btnDeleteClicked(ActionEvent event) {
openDialog(null, "Add");
loadServices();
}
@FXML
void btnEditClicked(ActionEvent event) {
Service selected = tvServices.getSelectionModel().getSelectedItem();
if (selected == null) {
showAlert("Select Service", "Please select a service to edit.");
return;
}
openDialog(selected, "Edit");
loadServices();
}
}
@FXML
void btnDeleteClicked(ActionEvent e) {
Service service = tvServices.getSelectionModel().getSelectedItem();
if (service == null) return;
try {
ServiceDB.deleteService(service.getServiceId());
loadServices();
} catch (Exception ex) {
ex.printStackTrace();
}
}
private void openDialog(Service service, String mode) {
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/org/example/petshopdesktop/dialogviews/service-dialog-view.fxml")
);
Stage stage = new Stage();
stage.setScene(new Scene(loader.load()));
ServiceDialogController controller = loader.getController();
controller.setMode(mode);
if (mode.equals("Edit")) {
controller.setService(service);
}
stage.initModality(Modality.APPLICATION_MODAL);
stage.showAndWait();
loadServices();
} catch (Exception e) {
e.printStackTrace();
}
}
private void showAlert(String title, String msg) {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle(title);
alert.setHeaderText(null);
alert.setContentText(msg);
alert.showAndWait();
}
}

View File

@@ -0,0 +1,209 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import javafx.scene.control.ListCell;
import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.database.*;
import org.example.petshopdesktop.models.*;
import java.sql.Time;
public class AppointmentDialogController {
// ============================
// FXML
// ============================
@FXML private Button btnCancel;
@FXML private Button btnSave;
@FXML private ComboBox<Service> cbService;
@FXML private ComboBox<Customer> cbCustomer;
@FXML private ComboBox<Pet> cbPet;
@FXML private ComboBox<Integer> cbHour;
@FXML private ComboBox<Integer> cbMinute;
@FXML private ComboBox<String> cbAppointmentStatus;
@FXML private DatePicker dpAppointmentDate;
@FXML private Label lblAppointmentId;
@FXML private Label lblMode;
// ============================
// DATA
// ============================
private String mode = null; // Add | Edit
private AppointmentDTO selectedAppointment = null;
private ObservableList<String> statusList =
FXCollections.observableArrayList(
"Booked", "Completed", "Cancelled"
);
//
// MODE
//
public void setMode(String mode) {
this.mode = mode;
lblMode.setText(mode + " Appointment");
lblAppointmentId.setVisible(!mode.equals("Add"));
}
//
// INITIALIZE
//
@FXML
public void initialize() {
try {
cbService.setItems(ServiceDB.getServices());
cbCustomer.setItems(CustomerDB.getCustomers());
cbPet.setItems(PetDB.getPets());
} catch (Exception e) {
e.printStackTrace();
}
cbAppointmentStatus.setItems(statusList);
// Hours 9 AM - 5 PM
for (int i = 9; i <= 17; i++) {
cbHour.getItems().add(i);
}
cbMinute.getItems().addAll(0, 15, 30, 45);
// Show pet name
cbPet.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(Pet pet, boolean empty) {
super.updateItem(pet, empty);
setText(empty || pet == null ? null : pet.getPetName());
}
});
cbPet.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(Pet pet, boolean empty) {
super.updateItem(pet, empty);
setText(empty || pet == null ? null : pet.getPetName());
}
});
btnSave.setOnMouseClicked(this::buttonSaveClicked);
btnCancel.setOnMouseClicked(this::closeStage);
}
//
// DISPLAY FOR EDIT
//
public void displayAppointmentDetails(AppointmentDTO appt) {
selectedAppointment = appt;
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
dpAppointmentDate.setValue(
java.time.LocalDate.parse(appt.getAppointmentDate())
);
cbAppointmentStatus.setValue(appt.getAppointmentStatus());
Time time = Time.valueOf(appt.getAppointmentTime());
cbHour.setValue(time.toLocalTime().getHour());
cbMinute.setValue(time.toLocalTime().getMinute());
cbService.getItems().forEach(s -> {
if (s.getServiceId() == appt.getServiceId()) cbService.setValue(s);
});
cbCustomer.getItems().forEach(c -> {
if (c.getCustomerId() == appt.getCustomerId()) cbCustomer.setValue(c);
});
cbPet.getItems().forEach(p -> {
if (p.getPetId() == appt.getPetId()) cbPet.setValue(p);
});
}
//
// SAVE
//
private void buttonSaveClicked(MouseEvent e) {
if (cbService.getValue() == null ||
cbCustomer.getValue() == null ||
cbPet.getValue() == null ||
dpAppointmentDate.getValue() == null ||
cbHour.getValue() == null ||
cbMinute.getValue() == null ||
cbAppointmentStatus.getValue() == null) {
showError("All fields are required");
return;
}
Time appointmentTime =
Time.valueOf(String.format(
"%02d:%02d:00",
cbHour.getValue(),
cbMinute.getValue()
));
Appointment appt = new Appointment(
selectedAppointment == null ? 0 : selectedAppointment.getAppointmentId(),
cbService.getValue().getServiceId(),
cbCustomer.getValue().getCustomerId(),
dpAppointmentDate.getValue().toString(),
appointmentTime.toString(),
cbAppointmentStatus.getValue()
);
try {
if (mode.equals("Add")) {
int newId = AppointmentDB.insertAppointment(appt);
AppointmentDB.insertAppointmentPet(newId, cbPet.getValue().getPetId());
} else {
AppointmentDB.updateAppointment(
selectedAppointment.getAppointmentId(),
appt,
cbPet.getValue().getPetId()
);
}
closeStage(e);
} catch (Exception ex) {
ex.printStackTrace();
showError("Error saving appointment");
}
}
//
// UTIL
//
private void closeStage(MouseEvent e) {
Stage stage = (Stage) ((Node) e.getSource()).getScene().getWindow();
stage.close();
}
private void showError(String msg) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error");
alert.setContentText(msg);
alert.showAndWait();
}
}

View File

@@ -0,0 +1,153 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.stage.Stage;
import org.example.petshopdesktop.database.ServiceDB;
import org.example.petshopdesktop.models.Service;
import javafx.scene.control.Alert;
import javafx.scene.control.ComboBox;
public class ServiceDialogController {
@FXML
private Button btnCancel;
@FXML
private Button btnSave;
@FXML
private Label lblMode;
@FXML
private Label lblServiceId;
@FXML
private TextField txtServiceDesc;
@FXML
private TextField txtServiceName;
@FXML
private TextField txtServicePrice;
@FXML
private ComboBox<Integer> cbHours;
@FXML
private ComboBox<Integer> cbMinutes;
private String mode;
private Service selectedService;
@FXML
public void initialize() {
cbHours.getItems().addAll(0, 1, 2, 3, 4);
cbMinutes.getItems().addAll(0, 15, 30, 45);
btnSave.setOnAction(e -> saveService());
btnCancel.setOnAction(e -> close());
}
public void setMode(String mode) {
this.mode = mode;
lblMode.setText(mode + " Service");
if (mode.equals("Add")) {
lblServiceId.setVisible(false);
} else {
lblServiceId.setVisible(true);
}
}
public void setService(Service service) {
this.selectedService = service;
lblServiceId.setText("ID: " + service.getServiceId());
txtServiceName.setText(service.getServiceName());
txtServiceDesc.setText(service.getServiceDesc());
int totalMinutes = service.getServiceDuration();
cbHours.setValue(totalMinutes / 60);
cbMinutes.setValue(totalMinutes % 60);
txtServicePrice.setText(String.valueOf(service.getServicePrice()));
}
private void saveService() {
String name = txtServiceName.getText();
String desc = txtServiceDesc.getText();
String priceText = txtServicePrice.getText();
Integer hours = cbHours.getValue();
Integer minutes = cbMinutes.getValue();
// -------- VALIDATION --------
if (name == null || name.isBlank()) {
showError("Service name is required.");
return;
}
if (priceText == null || priceText.isBlank()) {
showError("Price is required.");
return;
}
if (hours == null || minutes == null) {
showError("Please select duration.");
return;
}
double price;
try {
price = Double.parseDouble(priceText);
} catch (NumberFormatException e) {
showError("Price must be numeric.");
return;
}
int duration = (hours * 60) + minutes;
Service service = new Service(
selectedService == null ? 0 : selectedService.getServiceId(),
name,
desc,
duration,
price
);
try {
if (mode.equals("Add")) {
ServiceDB.insertService(service);
} else {
ServiceDB.updateService(selectedService.getServiceId(), service);
}
close();
} catch (Exception e) {
e.printStackTrace();
showError("Database error while saving service.");
}
}
private void showError(String msg) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Invalid Input");
alert.setContentText(msg);
alert.showAndWait();
}
private void close() {
Stage stage = (Stage) btnSave.getScene().getWindow();
stage.close();
}
}

View File

@@ -0,0 +1,193 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.DTOs.AppointmentDTO;
import org.example.petshopdesktop.models.Appointment;
import java.sql.*;
public class AppointmentDB {
// ============================
// GET ALL APPOINTMENTS
// ============================
public static ObservableList<AppointmentDTO> getAppointmentDTOs()
throws SQLException {
ObservableList<AppointmentDTO> list =
FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT a.appointmentId,
c.customerId,
CONCAT(c.firstName,' ',c.lastName) AS customerName,
p.petId,
p.petName,
s.serviceId,
s.serviceName,
a.appointmentDate,
a.appointmentTime,
a.appointmentStatus
FROM appointment a
JOIN customer c ON a.customerId = c.customerId
JOIN appointmentPet ap ON a.appointmentId = ap.appointmentId
JOIN pet p ON ap.petId = p.petId
JOIN service s ON a.serviceId = s.serviceId
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
AppointmentDTO dto = new AppointmentDTO(
rs.getInt("appointmentId"),
rs.getInt("customerId"),
rs.getString("customerName"),
rs.getInt("petId"),
rs.getString("petName"),
rs.getInt("serviceId"),
rs.getString("serviceName"),
rs.getString("appointmentDate"),
rs.getString("appointmentTime"),
rs.getString("appointmentStatus")
);
list.add(dto);
}
conn.close();
return list;
}
// ============================
// INSERT APPOINTMENT
// ============================
public static int insertAppointment(Appointment appt)
throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = """
INSERT INTO appointment
(serviceId, customerId, appointmentDate,
appointmentTime, appointmentStatus)
VALUES (?,?,?,?,?)
""";
PreparedStatement ps =
conn.prepareStatement(sql,
Statement.RETURN_GENERATED_KEYS);
ps.setInt(1, appt.getServiceId());
ps.setInt(2, appt.getCustomerId());
ps.setString(3, appt.getAppointmentDate());
ps.setString(4, appt.getAppointmentTime());
ps.setString(5, appt.getAppointmentStatus());
ps.executeUpdate();
ResultSet keys = ps.getGeneratedKeys();
int newId = 0;
if (keys.next()) {
newId = keys.getInt(1);
}
conn.close();
return newId;
}
//
// LINK PET TO APPOINTMENT
//
public static void insertAppointmentPet(int appointmentId,
int petId)
throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql =
"INSERT INTO appointmentPet (appointmentId, petId) VALUES (?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, appointmentId);
ps.setInt(2, petId);
ps.executeUpdate();
conn.close();
}
//
// UPDATE APPOINTMENT
//
public static int updateAppointment(int id,
Appointment appt,
int petId)
throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql =
"UPDATE appointment SET serviceId=?, customerId=?, " +
"appointmentDate=?, appointmentTime=?, appointmentStatus=? " +
"WHERE appointmentId=?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, appt.getServiceId());
ps.setInt(2, appt.getCustomerId());
ps.setString(3, appt.getAppointmentDate());
ps.setString(4, appt.getAppointmentTime());
ps.setString(5, appt.getAppointmentStatus());
ps.setInt(6, id);
ps.executeUpdate();
String sql2 =
"UPDATE appointmentPet SET petId=? WHERE appointmentId=?";
PreparedStatement ps2 = conn.prepareStatement(sql2);
ps2.setInt(1, petId);
ps2.setInt(2, id);
ps2.executeUpdate();
conn.close();
return 1;
}
//
// DELETE APPOINTMENT
//
public static int deleteAppointment(int id)
throws SQLException {
Connection conn = ConnectionDB.getConnection();
PreparedStatement ps1 =
conn.prepareStatement(
"DELETE FROM appointmentPet WHERE appointmentId=?"
);
ps1.setInt(1, id);
ps1.executeUpdate();
PreparedStatement ps2 =
conn.prepareStatement(
"DELETE FROM appointment WHERE appointmentId=?"
);
ps2.setInt(1, id);
int rows = ps2.executeUpdate();
conn.close();
return rows;
}
}

View File

@@ -0,0 +1,43 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Customer;
import java.sql.*;
public class CustomerDB {
//
// GET ALL CUSTOMERS
//
public static ObservableList<Customer> getCustomers()
throws SQLException {
ObservableList<Customer> list =
FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = "SELECT * FROM customer";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()) {
Customer c = new Customer(
rs.getInt("customerId"),
rs.getString("firstName"),
rs.getString("lastName"),
rs.getString("email"),
rs.getString("phone")
);
list.add(c);
}
conn.close();
return list;
}
}

View File

@@ -0,0 +1,45 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.DTOs.PurchaseOrderDTO;
import java.sql.*;
public class PurchaseOrderDB {
public static ObservableList<PurchaseOrderDTO> getPurchaseOrders()
throws SQLException {
ObservableList<PurchaseOrderDTO> list =
FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = """
SELECT po.purchaseOrderId,
s.supCompany,
po.orderDate,
po.status
FROM purchaseOrder po
JOIN supplier s ON po.supId = s.supId
ORDER BY po.purchaseOrderId
""";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
list.add(new PurchaseOrderDTO(
rs.getInt("purchaseOrderId"),
rs.getString("supCompany"),
rs.getString("orderDate"),
rs.getString("status")
));
}
conn.close();
return list;
}
}

View File

@@ -0,0 +1,107 @@
package org.example.petshopdesktop.database;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import org.example.petshopdesktop.models.Service;
import java.sql.*;
public class ServiceDB {
//
// GET ALL SERVICES
//
public static ObservableList<Service> getServices() throws SQLException {
ObservableList<Service> list = FXCollections.observableArrayList();
Connection conn = ConnectionDB.getConnection();
String sql = "SELECT * FROM service";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next()) {
Service service = new Service(
rs.getInt("serviceId"),
rs.getString("serviceName"),
rs.getString("serviceDesc"),
rs.getInt("serviceDuration"),
rs.getDouble("servicePrice")
);
list.add(service);
}
conn.close();
return list;
}
//
// INSERT SERVICE
//
public static int insertService(Service service) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql =
"INSERT INTO service (serviceName, serviceDesc, serviceDuration, servicePrice) " +
"VALUES (?, ?, ?, ?)";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, service.getServiceName());
stmt.setString(2, service.getServiceDesc());
stmt.setInt(3, service.getServiceDuration());
stmt.setDouble(4, service.getServicePrice());
int rows = stmt.executeUpdate();
conn.close();
return rows;
}
//
// UPDATE SERVICE
//
public static int updateService(int id, Service service) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql =
"UPDATE service SET " +
"serviceName=?, serviceDesc=?, serviceDuration=?, servicePrice=? " +
"WHERE serviceId=?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, service.getServiceName());
stmt.setString(2, service.getServiceDesc());
stmt.setInt(3, service.getServiceDuration());
stmt.setDouble(4, service.getServicePrice());
stmt.setInt(5, id);
int rows = stmt.executeUpdate();
conn.close();
return rows;
}
//
// DELETE SERVICE
//
public static int deleteService(int id) throws SQLException {
Connection conn = ConnectionDB.getConnection();
String sql = "DELETE FROM service WHERE serviceId=?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, id);
int rows = stmt.executeUpdate();
conn.close();
return rows;
}
}

View File

@@ -0,0 +1,80 @@
package org.example.petshopdesktop.database;
import org.example.petshopdesktop.auth.Role;
import org.example.petshopdesktop.models.User;
import java.sql.*;
/*
Petshop Desktop
Purpose: User authentication and role lookup against the users table.
*/
public class UserDB {
/**
* Authenticate a user by username and password.
* Passwords are stored as SHA-256 hex digests in the database.
*
* @param username the username to authenticate
* @param password the plaintext password
* @return the User if credentials are valid, or null if authentication fails
*/
public static User authenticate(String username, String password) throws SQLException {
String sql = "SELECT user_id, username, role FROM users " +
"WHERE username = ? AND password_hash = SHA2(?, 256)";
try (Connection conn = ConnectionDB.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, username);
ps.setString(2, password);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
int userId = rs.getInt("user_id");
String uname = rs.getString("username");
// Role values are stored in the database as strings and normalised to match the enum.
// Table constraints limit role values, Role.valueOf is expected to be safe under normal operation.
Role role = Role.valueOf(rs.getString("role").toUpperCase());
return new User(userId, uname, role);
}
}
}
return null;
}
/**
* Create the users table and seed default admin/staff accounts if they do not exist.
* Passwords are stored as SHA2-256 hashes.
*/
public static void initializeTable() throws SQLException {
String createTable = """
CREATE TABLE IF NOT EXISTS users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL UNIQUE,
password_hash CHAR(64) NOT NULL,
role ENUM('ADMIN','STAFF') NOT NULL
)
""";
// Default accounts support initial development and testing, credentials should be rotated or removed for deployment.
String seedAdmin = """
INSERT IGNORE INTO users (username, password_hash, role)
VALUES ('admin', SHA2('admin123', 256), 'ADMIN')
""";
String seedStaff = """
INSERT IGNORE INTO users (username, password_hash, role)
VALUES ('staff', SHA2('staff123', 256), 'STAFF')
""";
try (Connection conn = ConnectionDB.getConnection();
Statement st = conn.createStatement()) {
st.executeUpdate(createTable);
st.executeUpdate(seedAdmin);
st.executeUpdate(seedStaff);
}
}
}

View File

@@ -0,0 +1,46 @@
package org.example.petshopdesktop.models;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
public class Appointment {
private SimpleIntegerProperty appointmentId;
private SimpleIntegerProperty serviceId;
private SimpleIntegerProperty customerId;
private SimpleStringProperty appointmentDate;
private SimpleStringProperty appointmentTime;
private SimpleStringProperty appointmentStatus;
// Constructor
public Appointment(int appointmentId,
int serviceId,
int customerId,
String appointmentDate,
String appointmentTime,
String appointmentStatus) {
this.appointmentId = new SimpleIntegerProperty(appointmentId);
this.serviceId = new SimpleIntegerProperty(serviceId);
this.customerId = new SimpleIntegerProperty(customerId);
this.appointmentDate = new SimpleStringProperty(appointmentDate);
this.appointmentTime = new SimpleStringProperty(appointmentTime);
this.appointmentStatus = new SimpleStringProperty(appointmentStatus);
}
// Getters
public int getAppointmentId() { return appointmentId.get(); }
public int getServiceId() { return serviceId.get(); }
public int getCustomerId() { return customerId.get(); }
public String getAppointmentDate() { return appointmentDate.get(); }
public String getAppointmentTime() { return appointmentTime.get(); }
public String getAppointmentStatus() { return appointmentStatus.get(); }
// Properties
public SimpleIntegerProperty appointmentIdProperty() { return appointmentId; }
public SimpleIntegerProperty serviceIdProperty() { return serviceId; }
public SimpleIntegerProperty customerIdProperty() { return customerId; }
public SimpleStringProperty appointmentDateProperty() { return appointmentDate; }
public SimpleStringProperty appointmentTimeProperty() { return appointmentTime; }
public SimpleStringProperty appointmentStatusProperty() { return appointmentStatus; }
}

View File

@@ -1,35 +1,75 @@
package org.example.petshopdesktop.models;
public class Customer {
private int customerId;
private String firstName;
private String lastName;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
//Constructor
public Customer(int customerId, String firstName, String lastName) {
this.customerId = customerId;
this.firstName = firstName;
this.lastName = lastName;
public class Customer {
private SimpleIntegerProperty customerId;
private SimpleStringProperty firstName;
private SimpleStringProperty lastName;
private SimpleStringProperty email;
private SimpleStringProperty phone;
// Constructor
public Customer(int customerId,
String firstName,
String lastName,
String email,
String phone) {
this.customerId = new SimpleIntegerProperty(customerId);
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
this.email = new SimpleStringProperty(email);
this.phone = new SimpleStringProperty(phone);
}
//Returns customer ID
// Getters
public int getCustomerId() {
return customerId.get();
}
public String getFirstName() {
return firstName.get();
}
public String getLastName() {
return lastName.get();
}
public String getEmail() {
return email.get();
}
public String getPhone() {
return phone.get();
}
// Properties (optional but useful later)
public SimpleIntegerProperty customerIdProperty() {
return customerId;
}
//Returns first name
public String getFirstName() {
public SimpleStringProperty firstNameProperty() {
return firstName;
}
//Returns last name
public String getLastName() {
public SimpleStringProperty lastNameProperty() {
return lastName;
}
//Returns first + last name
public SimpleStringProperty emailProperty() {
return email;
}
public SimpleStringProperty phoneProperty() {
return phone;
}
// This controls how customer appears in ComboBox
@Override
public String toString() {
return firstName + " " + lastName;
return getFirstName() + " " + getLastName();
}
}

View File

@@ -0,0 +1,35 @@
package org.example.petshopdesktop.models;
public class PurchaseOrder {
private int purchaseOrderId;
private int supId;
private String orderDate;
private String status;
public PurchaseOrder(int purchaseOrderId,
int supId,
String orderDate,
String status) {
this.purchaseOrderId = purchaseOrderId;
this.supId = supId;
this.orderDate = orderDate;
this.status = status;
}
public int getPurchaseOrderId() {
return purchaseOrderId;
}
public int getSupId() {
return supId;
}
public String getOrderDate() {
return orderDate;
}
public String getStatus() {
return status;
}
}

View File

@@ -0,0 +1,98 @@
package org.example.petshopdesktop.models;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
/**
* The class for the entity of services (contains all data relating to services)
*/
public class Service {
private SimpleIntegerProperty serviceId;
private SimpleStringProperty serviceName;
private SimpleStringProperty serviceDesc;
private SimpleIntegerProperty serviceDuration;
private SimpleDoubleProperty servicePrice;
// constructor
public Service(int serviceId,
String serviceName,
String serviceDesc,
int serviceDuration,
double servicePrice) {
this.serviceId = new SimpleIntegerProperty(serviceId);
this.serviceName = new SimpleStringProperty(serviceName);
this.serviceDesc = new SimpleStringProperty(serviceDesc);
this.serviceDuration = new SimpleIntegerProperty(serviceDuration);
this.servicePrice = new SimpleDoubleProperty(servicePrice);
}
// getters & setters
public int getServiceId() {
return serviceId.get();
}
public SimpleIntegerProperty serviceIdProperty() {
return serviceId;
}
public void setServiceId(int serviceId) {
this.serviceId.set(serviceId);
}
public String getServiceName() {
return serviceName.get();
}
public SimpleStringProperty serviceNameProperty() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName.set(serviceName);
}
public String getServiceDesc() {
return serviceDesc.get();
}
public SimpleStringProperty serviceDescProperty() {
return serviceDesc;
}
public void setServiceDesc(String serviceDesc) {
this.serviceDesc.set(serviceDesc);
}
public int getServiceDuration() {
return serviceDuration.get();
}
public SimpleIntegerProperty serviceDurationProperty() {
return serviceDuration;
}
public void setServiceDuration(int serviceDuration) {
this.serviceDuration.set(serviceDuration);
}
public double getServicePrice() {
return servicePrice.get();
}
public SimpleDoubleProperty servicePriceProperty() {
return servicePrice;
}
public void setServicePrice(double servicePrice) {
this.servicePrice.set(servicePrice);
}
@Override
public String toString() {
return getServiceName();
}
}

View File

@@ -0,0 +1,27 @@
package org.example.petshopdesktop.models;
import org.example.petshopdesktop.auth.Role;
public class User {
private int userId;
private String username;
private Role role;
public User(int userId, String username, Role role) {
this.userId = userId;
this.username = username;
this.role = role;
}
public int getUserId() {
return userId;
}
public String getUsername() {
return username;
}
public Role getRole() {
return role;
}
}

View File

@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.DatePicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.layout.HBox?>
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="523.0" prefWidth="790.0"
spacing="20.0" style="-fx-font-size: 14px;"
xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController">
<children>
<HBox alignment="CENTER_LEFT" prefHeight="79.0" prefWidth="727.0" spacing="20.0"
style="-fx-background-color: #2C3E50; -fx-background-radius: 14;">
<children>
<VBox alignment="CENTER_LEFT" prefHeight="93.0" prefWidth="299.0">
<children>
<Label fx:id="lblMode" prefHeight="42.0" prefWidth="275.0" text="Mode Appointment"
textFill="WHITE">
<font>
<Font name="Comic Sans MS Bold" size="30.0" />
</font>
</Label>
<Label fx:id="lblAppointmentId" text="ID: 1" textFill="#ffe66d">
<font>
<Font size="14.0" />
</font>
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Label>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
<HBox.margin>
<Insets />
</HBox.margin>
</VBox>
<Region prefHeight="93.0" prefWidth="151.0" HBox.hgrow="ALWAYS" />
<Button fx:id="btnCancel" layoutX="391.0" layoutY="38.0"
mnemonicParsing="false" style="-fx-background-color: #E74c3c; -fx-cursor: hand; -fx-background-radius: 8;" text="Cancel" textFill="WHITE">
<font>
<Font name="System Bold" size="14.0" />
</font>
<padding>
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
</padding>
</Button>
<Button fx:id="btnSave" layoutX="520.0" layoutY="38.0" mnemonicParsing="false"
style="-fx-background-color: #3fe06a; -fx-cursor: hand; -fx-background-radius: 8;" text="Save" textFill="WHITE">
<font>
<Font name="System Bold" size="14.0" />
</font>
<padding>
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
</padding>
</Button>
</children>
<padding>
<Insets left="15.0" right="15.0" />
</padding>
</HBox>
<VBox prefHeight="370.0" prefWidth="750.0"
style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #5580b5; -fx-border-radius: 14;">
<children>
<GridPane hgap="25.0" VBox.vgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0">
<children>
<Label text="Service:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbService" prefHeight="29.0" prefWidth="336.0" promptText="Select Service" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1">
<children>
<Label text="Customer:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbCustomer" prefHeight="29.0" prefWidth="336.0" promptText="Select Customer" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="1">
<children>
<Label text="Appointment Date:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<DatePicker fx:id="dpAppointmentDate" prefHeight="29.0" prefWidth="336.0" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</DatePicker>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="2">
<children>
<Label text="Status:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbAppointmentStatus" prefHeight="29.0" prefWidth="336.0" promptText="Select Status" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
<VBox prefHeight="200.0"
prefWidth="100.0"
spacing="8.0"
GridPane.columnIndex="1"
GridPane.rowIndex="1">
<children>
<Label text="Appointment Time:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<HBox spacing="10">
<!-- Hour -->
<ComboBox fx:id="cbHour"
promptText="Hour"
prefWidth="160"
style="-fx-border-color: #E8EBED;
-fx-border-width: 2;
-fx-border-radius: 10;
-fx-background-radius: 10;
-fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
<!-- Minute -->
<ComboBox fx:id="cbMinute"
promptText="Minute"
prefWidth="160"
style="-fx-border-color: #E8EBED;
-fx-border-width: 2;
-fx-border-radius: 10;
-fx-background-radius: 10;
-fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</HBox>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2">
<children>
<Label text="Pet:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbPet" prefHeight="29.0" prefWidth="336.0" promptText="Select Pet" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</children>
</VBox>
</children>
<VBox.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</VBox.margin>
</GridPane>
</children>
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding>
</VBox>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>

View File

@@ -0,0 +1,181 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.control.ComboBox?>
<VBox minHeight="-Infinity" minWidth="-Infinity" prefHeight="523.0" prefWidth="790.0" spacing="20.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.ServiceDialogController">
<children>
<HBox alignment="CENTER_LEFT" prefHeight="79.0" prefWidth="727.0" spacing="20.0" style="-fx-background-color: #2C3E50; -fx-background-radius: 14;">
<children>
<VBox alignment="CENTER_LEFT" prefHeight="79.0" prefWidth="246.0">
<children>
<Label fx:id="lblMode" prefHeight="54.0" prefWidth="246.0" text="Mode Service" textFill="WHITE">
<font>
<Font name="Comic Sans MS Bold" size="30.0" />
</font>
</Label>
<Label fx:id="lblServiceId" text="ID: 1" textFill="#ffe66d">
<font>
<Font size="14.0" />
</font>
<VBox.margin>
<Insets top="10.0" />
</VBox.margin>
</Label>
</children>
<padding>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</padding>
<HBox.margin>
<Insets />
</HBox.margin>
</VBox>
<Region prefHeight="79.0" prefWidth="243.0" HBox.hgrow="ALWAYS" />
<Button fx:id="btnCancel" layoutX="391.0" layoutY="38.0" mnemonicParsing="false" style="-fx-background-color: #E74c3c; -fx-cursor: hand; -fx-background-radius: 8;" text="Cancel" textFill="WHITE">
<font>
<Font name="System Bold" size="14.0" />
</font>
<padding>
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
</padding>
</Button>
<Button fx:id="btnSave" layoutX="520.0" layoutY="38.0" mnemonicParsing="false" style="-fx-background-color: #3fe06a; -fx-cursor: hand; -fx-background-radius: 8;" text="Save" textFill="WHITE">
<font>
<Font name="System Bold" size="14.0" />
</font>
<padding>
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
</padding>
</Button>
</children>
<padding>
<Insets left="15.0" right="15.0" />
</padding>
</HBox>
<VBox prefHeight="370.0" prefWidth="750.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-color: #5580b5; -fx-border-radius: 14;">
<children>
<GridPane hgap="25.0" VBox.vgrow="ALWAYS">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0">
<children>
<Label text="Service Name:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<TextField fx:id="txtServiceName" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10;">
<padding>
<Insets bottom="7.0" left="10.0" right="10.0" top="7.0" />
</padding>
</TextField>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1">
<children>
<Label text="Description:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<TextField fx:id="txtServiceDesc" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10;">
<padding>
<Insets bottom="7.0" left="10.0" right="10.0" top="7.0" />
</padding>
</TextField>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0"
spacing="8.0"
GridPane.rowIndex="1">
<children>
<Label text="Duration:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<HBox spacing="10">
<!-- Hours Combo -->
<ComboBox fx:id="cbHours"
promptText="Hours"
prefWidth="120"
style="-fx-border-color: #E8EBED;
-fx-border-width: 2;
-fx-border-radius: 10;
-fx-background-radius: 10;
-fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
<!-- Minutes Combo -->
<ComboBox fx:id="cbMinutes"
promptText="Minutes"
prefWidth="120"
style="-fx-border-color: #E8EBED;
-fx-border-width: 2;
-fx-border-radius: 10;
-fx-background-radius: 10;
-fx-background-color: white;">
<padding>
<Insets bottom="3.0" left="10.0" right="10.0" top="3.0" />
</padding>
</ComboBox>
</HBox>
</children>
</VBox>
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.columnIndex="1" GridPane.rowIndex="1">
<children>
<Label text="Price:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<TextField fx:id="txtServicePrice" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10;">
<padding>
<Insets bottom="7.0" left="10.0" right="10.0" top="7.0" />
</padding>
</TextField>
</children>
</VBox>
</children>
<VBox.margin>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</VBox.margin>
</GridPane>
</children>
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding>
</VBox>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
</VBox>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox alignment="CENTER" prefHeight="400.0" prefWidth="380.0" spacing="16.0"
style="-fx-background-color: #2C3E50;"
xmlns="http://javafx.com/javafx/25"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.petshopdesktop.controllers.LoginController">
<padding>
<Insets bottom="40.0" left="50.0" right="50.0" top="40.0" />
</padding>
<children>
<Label text="🐾 Pet Shop Manager" textFill="WHITE">
<font>
<Font name="Comic Sans MS Bold" size="22.0" />
</font>
<VBox.margin>
<Insets bottom="10.0" />
</VBox.margin>
</Label>
<Label text="Username" textFill="#cccccc">
<font>
<Font name="System Bold" size="13.0" />
</font>
</Label>
<TextField fx:id="txtUsername" promptText="Enter username"
style="-fx-background-color: #3d5166; -fx-text-fill: white; -fx-prompt-text-fill: #888; -fx-background-radius: 8; -fx-border-width: 0;">
<padding>
<Insets bottom="10.0" left="12.0" right="12.0" top="10.0" />
</padding>
<font>
<Font size="14.0" />
</font>
</TextField>
<Label text="Password" textFill="#cccccc">
<font>
<Font name="System Bold" size="13.0" />
</font>
</Label>
<PasswordField fx:id="txtPassword" promptText="Enter password"
style="-fx-background-color: #3d5166; -fx-text-fill: white; -fx-prompt-text-fill: #888; -fx-background-radius: 8; -fx-border-width: 0;">
<padding>
<Insets bottom="10.0" left="12.0" right="12.0" top="10.0" />
</padding>
<font>
<Font size="14.0" />
</font>
</PasswordField>
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true">
<font>
<Font size="13.0" />
</font>
</Label>
<Button fx:id="btnLogin" mnemonicParsing="false" onAction="#btnLoginClicked"
prefWidth="280.0"
style="-fx-background-color: #FF6B6B; -fx-background-radius: 8; -fx-cursor: hand;"
text="Login" textFill="WHITE">
<font>
<Font name="System Bold" size="15.0" />
</font>
<padding>
<Insets bottom="12.0" left="12.0" right="12.0" top="12.0" />
</padding>
<VBox.margin>
<Insets top="8.0" />
</VBox.margin>
</Button>
</children>
</VBox>

View File

@@ -16,12 +16,12 @@
<Insets bottom="30.0" left="30.0" right="30.0" top="30.0" />
</padding>
<children>
<Label text="Name" textFill="WHITE">
<Label fx:id="lblUsername" text="Name" textFill="WHITE">
<font>
<Font name="Comic Sans MS Bold" size="30.0" />
</font>
</Label>
<Label text="Pet Store Manager" textFill="#ffe66d">
<Label fx:id="lblRole" text="Pet Store Manager" textFill="#ffe66d">
<font>
<Font name="Comic Sans MS Bold" size="16.0" />
</font>
@@ -106,6 +106,32 @@
<Insets bottom="12.0" left="12.0" right="45.0" top="12.0" />
</padding>
</Button>
<Button fx:id="btnPurchaseOrders"
mnemonicParsing="false"
onAction="#btnPurchaseOrdersClicked"
prefWidth="250.0"
style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand;"
text="🧾 Purchase Orders"
textFill="#cccccc">
<font>
<Font name="Comic Sans MS Bold" size="14.0" />
</font>
<padding>
<Insets bottom="12.0" left="12.0" right="40.0" top="12.0" />
</padding>
</Button>
<Button fx:id="btnLogout" mnemonicParsing="false" onAction="#btnLogoutClicked" prefWidth="250.0" style="-fx-background-color: #34495E; -fx-background-radius: 8; -fx-cursor: hand;" text="🚪 Logout" textFill="#cccccc">
<font>
<Font name="Comic Sans MS Bold" size="14.0" />
</font>
<padding>
<Insets bottom="12.0" left="12.0" right="94.0" top="12.0" />
</padding>
<VBox.margin>
<Insets top="20.0" />
</VBox.margin>
</Button>
</children>
</VBox>
</left>

View File

@@ -66,15 +66,15 @@
</children>
</HBox>
<TableView fx:id="tvAppointments" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colAppointmentId" prefWidth="53.14288330078125" text="ID" />
<TableColumn fx:id="colPetName" prefWidth="108.00003051757812" text="Pet Name" />
<columns>
<TableColumn fx:id="colAppointmentId" prefWidth="53.14288330078125" text="ID" />
<TableColumn fx:id="colPetName" prefWidth="108.00003051757812" text="Pet Name" />
<TableColumn fx:id="colServiceName" prefWidth="132.0" text="Service" />
<TableColumn fx:id="colAppointmentDate" prefWidth="101.14288330078125" text="Date" />
<TableColumn fx:id="colAppointmentTime" prefWidth="89.7142333984375" text="Time" />
<TableColumn fx:id="colCustomerName" prefWidth="168.57147216796875" text="Customer" />
<TableColumn fx:id="colAppointmentStatus" prefWidth="98.28570556640625" text="Status" />
</columns>
</columns>
</TableView>
</children>
</VBox>

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<VBox spacing="20.0"
style="-fx-font-size: 14px;"
xmlns="http://javafx.com/javafx/25"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.petshopdesktop.controllers.PurchaseOrderController">
<padding>
<Insets bottom="20" left="20" right="20" top="20"/>
</padding>
<!-- HEADER -->
<HBox spacing="20" alignment="CENTER_LEFT">
<Label text="Purchase Orders" textFill="#2c3e50">
<font>
<Font name="System Bold" size="30"/>
</font>
</Label>
<Label text="(View Only)" textFill="#7f8c8d">
<font>
<Font name="System Bold" size="16"/>
</font>
<padding>
<Insets top="10"/>
</padding>
</Label>
<Region HBox.hgrow="ALWAYS"/>
<Button fx:id="btnRefresh"
text="Refresh"
onAction="#btnRefresh"
style="-fx-background-color:#4ECDC4; -fx-background-radius:8;"
textFill="WHITE"/>
</HBox>
<!-- SEARCH -->
<HBox spacing="10"
style="-fx-background-color:white; -fx-background-radius:14; -fx-border-width:2; -fx-border-radius:14;">
<padding>
<Insets bottom="10" left="15" right="15" top="10"/>
</padding>
<TextField fx:id="txtSearch"
promptText="Search Purchase Orders..."
style="-fx-border-width:0; -fx-background-color:transparent;"
HBox.hgrow="ALWAYS"/>
</HBox>
<!-- TABLE -->
<TableView fx:id="tvPurchaseOrders"
style="-fx-background-color:white; -fx-background-radius:12;"
VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colOrderId" text="Order ID" prefWidth="80"/>
<TableColumn fx:id="colSupplier" text="Supplier" prefWidth="200"/>
<TableColumn fx:id="colOrderDate" text="Order Date" prefWidth="150"/>
<TableColumn fx:id="colStatus" text="Status" prefWidth="120"/>
</columns>
</TableView>
</VBox>

View File

@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox minHeight="-Infinity"
minWidth="-Infinity"
spacing="20"
style="-fx-font-size:14px;"
xmlns="http://javafx.com/javafx/25"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="org.example.petshopdesktop.controllers.PurchaseOrderdialogController">
<padding>
<Insets top="20" right="20" bottom="20" left="20"/>
</padding>
<!-- HEADER -->
<HBox alignment="CENTER_LEFT" spacing="20">
<Label text="Purchase Orders" textFill="#2c3e50">
<font>
<Font name="System Bold" size="30"/>
</font>
</Label>
<Region HBox.hgrow="ALWAYS"/>
</HBox>
<!-- SEARCH BAR -->
<HBox alignment="CENTER_LEFT"
spacing="10"
style="-fx-background-color:white; -fx-background-radius:14;">
<padding>
<Insets top="10" bottom="10" left="15" right="15"/>
</padding>
<TextField fx:id="txtSearch"
promptText="Search Purchase Orders..."
style="-fx-border-width:0; -fx-background-color:transparent;"
HBox.hgrow="ALWAYS"/>
</HBox>
<!-- TABLE -->
<TableView fx:id="tvPurchaseOrders"
prefHeight="362"
style="-fx-background-color:white; -fx-background-radius:12;"
VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colOrderId"
text="Order ID"
prefWidth="120"/>
<TableColumn fx:id="colSupplier"
text="Supplier"
prefWidth="250"/>
<TableColumn fx:id="colOrderDate"
text="Order Date"
prefWidth="200"/>
<TableColumn fx:id="colStatus"
text="Status"
prefWidth="180"/>
</columns>
</TableView>
</VBox>

View File

@@ -66,13 +66,13 @@
</children>
</HBox>
<TableView fx:id="tvServices" prefHeight="362.0" prefWidth="752.0" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
<columns>
<TableColumn fx:id="colServiceId" prefWidth="60.0" text="ID" />
<TableColumn fx:id="colServiceName" prefWidth="169.71432495117188" text="Name" />
<columns>
<TableColumn fx:id="colServiceId" prefWidth="60.0" text="ID" />
<TableColumn fx:id="colServiceName" prefWidth="169.71432495117188" text="Name" />
<TableColumn fx:id="colServiceDesc" prefWidth="206.85711669921875" text="Description" />
<TableColumn fx:id="colServiceDuration" prefWidth="185.71429443359375" text="Duration (min)" />
<TableColumn fx:id="colServicePrice" prefWidth="129.14288330078125" text="Price" />
</columns>
</columns>
</TableView>
</children>
</VBox>