@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
13
src/main/java/org/example/petshopdesktop/auth/Role.java
Normal file
13
src/main/java/org/example/petshopdesktop/auth/Role.java
Normal 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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -36,6 +47,12 @@ public class MainLayoutController {
|
||||
@FXML
|
||||
private Button btnSuppliers;
|
||||
|
||||
@FXML
|
||||
private Label lblUsername;
|
||||
|
||||
@FXML
|
||||
private Label lblRole;
|
||||
|
||||
@FXML
|
||||
private StackPane spContentArea;
|
||||
|
||||
@@ -93,11 +110,52 @@ public class MainLayoutController {
|
||||
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
|
||||
@@ -111,7 +169,8 @@ public class MainLayoutController {
|
||||
spContentArea.getChildren().clear();
|
||||
spContentArea.getChildren().add(view);
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error loading view: " + fxmlFile);
|
||||
System.err.println("Error loading view: " + fxmlFile);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
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.controllers.dialogcontrollers.PetDialogController;
|
||||
import org.example.petshopdesktop.database.PetDB;
|
||||
import org.example.petshopdesktop.database.ProductDB;
|
||||
import org.example.petshopdesktop.models.Pet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.SQLIntegrityConstraintViolationException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PetController {
|
||||
|
||||
@@ -19,45 +32,191 @@ public class PetController {
|
||||
private Button btnEdit;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colPetAge;
|
||||
private TableColumn<Pet, Integer> colPetAge;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colPetBreed;
|
||||
private TableColumn<Pet, String> colPetBreed;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colPetId;
|
||||
private TableColumn<Pet, Integer> colPetId;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colPetName;
|
||||
private TableColumn<Pet, String> colPetName;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colPetPrice;
|
||||
private TableColumn<Pet, Double> colPetPrice;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colPetSpecies;
|
||||
private TableColumn<Pet, String> colPetSpecies;
|
||||
|
||||
@FXML
|
||||
private TableColumn<?, ?> colPetStatus;
|
||||
private TableColumn<Pet, String> colPetStatus;
|
||||
|
||||
@FXML
|
||||
private TableView<?> tvPets;
|
||||
private TableView<Pet> tvPets;
|
||||
|
||||
@FXML
|
||||
private TextField txtSearch;
|
||||
|
||||
@FXML
|
||||
void btnAddClicked(ActionEvent event) {
|
||||
|
||||
mode = "Add";
|
||||
openDialog(null,mode);
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnDeleteClicked(ActionEvent event) {
|
||||
int numRows = 0;
|
||||
Pet selectedPet = tvPets.getSelectionModel().getSelectedItem();
|
||||
|
||||
//ask user to confirm
|
||||
Alert question = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
question.setHeaderText("Please confirm delete");
|
||||
question.setContentText("Are you sure you want to delete this pet?");
|
||||
Optional<ButtonType> result = question.showAndWait(); //show alert and wait for response
|
||||
|
||||
//if confirmed,start deletion
|
||||
if (result.isPresent() && result.get() == ButtonType.OK) {
|
||||
int petId = selectedPet.getPetId();
|
||||
|
||||
//try deleting
|
||||
try{
|
||||
numRows = PetDB.deletePet(petId);
|
||||
}
|
||||
catch (SQLIntegrityConstraintViolationException e){
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Database Operation Error");
|
||||
alert.setContentText("Delete failed\n" +
|
||||
"the selected pet is being referred in another table");
|
||||
alert.showAndWait();
|
||||
return;
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
//prompt user of any errors
|
||||
if (numRows == 0){
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Database Operation Error");
|
||||
alert.setContentText("Delete failed");
|
||||
alert.showAndWait();
|
||||
}
|
||||
else{
|
||||
//prompt user of delete conformation
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText("Delete successful");
|
||||
alert.showAndWait();
|
||||
//refresh display and reset inputs
|
||||
displayPets();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnEditClicked(ActionEvent event) {
|
||||
Pet selectedPet = tvPets.getSelectionModel().getSelectedItem();
|
||||
|
||||
if(selectedPet != null){
|
||||
mode = "Edit";
|
||||
openDialog(selectedPet,mode);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private ObservableList<Pet> data = FXCollections.observableArrayList();
|
||||
String mode = null;
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
btnEdit.setDisable(true);
|
||||
btnDelete.setDisable(true);
|
||||
|
||||
colPetId.setCellValueFactory(new PropertyValueFactory<Pet,Integer>("petId"));
|
||||
colPetName.setCellValueFactory(new PropertyValueFactory<Pet,String>("petName"));
|
||||
colPetSpecies.setCellValueFactory(new PropertyValueFactory<Pet,String>("petSpecies"));
|
||||
colPetBreed.setCellValueFactory(new PropertyValueFactory<Pet,String>("petBreed"));
|
||||
colPetAge.setCellValueFactory(new PropertyValueFactory<Pet,Integer>("petAge"));
|
||||
colPetStatus.setCellValueFactory(new PropertyValueFactory<Pet,String>("petStatus"));
|
||||
colPetPrice.setCellValueFactory(new PropertyValueFactory<Pet,Double>("petPrice"));
|
||||
|
||||
displayPets();
|
||||
|
||||
tvPets.getSelectionModel().selectedItemProperty().addListener(
|
||||
(observable, oldValue, newValue) -> {
|
||||
btnEdit.setDisable(false);
|
||||
btnDelete.setDisable(false);
|
||||
});
|
||||
|
||||
txtSearch.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
displayFilteredPet(newValue);
|
||||
});
|
||||
}
|
||||
|
||||
private void displayFilteredPet(String filter) {
|
||||
data.clear();
|
||||
try{
|
||||
if (txtSearch.getText() == null || txtSearch.getText().isEmpty()){
|
||||
displayPets();
|
||||
}
|
||||
else {
|
||||
data = PetDB.getFilteredPets(filter);
|
||||
tvPets.setItems(data);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void displayPets() {
|
||||
data.clear();
|
||||
|
||||
try{
|
||||
data = PetDB.getPets();
|
||||
}
|
||||
catch(SQLException e){
|
||||
System.out.println("Error while fetching table data: " + e.getMessage());
|
||||
}
|
||||
|
||||
tvPets.setItems(data);
|
||||
}
|
||||
|
||||
private void openDialog(Pet pet, String mode){
|
||||
//Get new view
|
||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/pet-dialog-view.fxml"));
|
||||
Scene scene = null;
|
||||
try{
|
||||
scene = new Scene(fxmlLoader.load());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
PetDialogController dialogController = fxmlLoader.getController(); //controller associated with this view
|
||||
dialogController.setMode(mode);
|
||||
|
||||
//Open the dialog depending on the mode
|
||||
if(mode.equals("Edit")){
|
||||
//Make it display pet details in dialog
|
||||
dialogController.displayPetDetails(pet);
|
||||
}
|
||||
Stage dialogStage = new Stage();
|
||||
dialogStage.initModality(Modality.APPLICATION_MODAL); //make it modal
|
||||
if(mode.equals("Add")){
|
||||
dialogStage.setTitle("Add Pet");
|
||||
}
|
||||
else {
|
||||
dialogStage.setTitle("Edit Pet");
|
||||
}
|
||||
dialogStage.setScene(scene);
|
||||
dialogStage.showAndWait();
|
||||
|
||||
//When dialog closes update table view and disable edit and delete buttons, and reset search bar
|
||||
displayPets();
|
||||
btnDelete.setDisable(true);
|
||||
btnEdit.setDisable(true);
|
||||
txtSearch.setText("");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package org.example.petshopdesktop.controllers.dialogcontrollers;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.DTOs.ProductDTO;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.database.PetDB;
|
||||
import org.example.petshopdesktop.models.Category;
|
||||
import org.example.petshopdesktop.models.Pet;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class PetDialogController {
|
||||
|
||||
@FXML
|
||||
private Button btnCancel;
|
||||
|
||||
@FXML
|
||||
private Button btnSave;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbPetStatus;
|
||||
|
||||
@FXML
|
||||
private Label lblMode;
|
||||
|
||||
@FXML
|
||||
private Label lblPetId;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetAge;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetBreed;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetName;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetPrice;
|
||||
|
||||
@FXML
|
||||
private TextField txtPetSpecies;
|
||||
|
||||
private String mode = null;
|
||||
|
||||
private ObservableList<String> statusList = FXCollections.observableArrayList(
|
||||
"Available", "Adopted"
|
||||
);
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
|
||||
cbPetStatus.setItems(statusList); //set status combobox
|
||||
|
||||
//Set up mouse handlers for buttons
|
||||
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
buttonSaveClicked(mouseEvent);
|
||||
}
|
||||
});
|
||||
|
||||
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent mouseEvent) {
|
||||
closeStage(mouseEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void buttonSaveClicked(MouseEvent mouseEvent) {
|
||||
int numRow = 0;
|
||||
String errorMsg = "";
|
||||
|
||||
//Check validation (input required)
|
||||
errorMsg += Validator.isPresent(txtPetName.getText(), "Pet Name");
|
||||
errorMsg += Validator.isPresent(txtPetAge.getText(), "Age");
|
||||
errorMsg += Validator.isPresent(txtPetBreed.getText(), "Breed");
|
||||
errorMsg += Validator.isPresent(txtPetSpecies.getText(), "Species");
|
||||
errorMsg += Validator.isPresent(txtPetPrice.getText(), "Price");
|
||||
if (cbPetStatus.getSelectionModel().getSelectedItem() == null){
|
||||
errorMsg += "Status is required";
|
||||
}
|
||||
|
||||
//Check validation (length size)
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetName.getText(), "Pet Name", 50);
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetSpecies.getText(), "Species", 50);
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetBreed.getText(), "Breed", 50);
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetPrice.getText(), "Price", 12);
|
||||
errorMsg += Validator.isLessThanVarChars(txtPetAge.getText(), "Age", 11);
|
||||
|
||||
//Check validation (format)
|
||||
errorMsg += Validator.isNonNegativeDouble(txtPetPrice.getText(), "Price");
|
||||
errorMsg += Validator.isNonNegativeInteger(txtPetAge.getText(), "Age");
|
||||
|
||||
if(errorMsg.isEmpty()){
|
||||
Pet pet = collectPet();
|
||||
if(mode.equals("Add")) {
|
||||
try{
|
||||
numRow = PetDB.insertPet(pet);
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
else {
|
||||
try {
|
||||
numRow = PetDB.updatePet(pet.getPetId(), pet);
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
//if no rows were affected then there was an error (prompt user of error)
|
||||
if (numRow == 0){
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Database Operation Error");
|
||||
alert.setContentText(mode + " failed");
|
||||
alert.showAndWait();
|
||||
closeStage(mouseEvent);
|
||||
}
|
||||
else {
|
||||
//tell the user operation was successful
|
||||
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
|
||||
alert.setHeaderText("Database Operation Confirmed");
|
||||
alert.setContentText(mode + " succeeded");
|
||||
alert.showAndWait();
|
||||
closeStage(mouseEvent);
|
||||
}
|
||||
}
|
||||
else{
|
||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
||||
alert.setHeaderText("Input Error");
|
||||
alert.setContentText(errorMsg);
|
||||
alert.showAndWait();
|
||||
}
|
||||
}
|
||||
|
||||
private Pet collectPet() {
|
||||
int petId =0;
|
||||
Pet pet = null;
|
||||
|
||||
if(lblPetId.isVisible()){
|
||||
petId = Integer.parseInt(lblPetId.getText().split(": ")[1]);
|
||||
}
|
||||
pet = new Pet(
|
||||
petId,
|
||||
txtPetName.getText(),
|
||||
txtPetSpecies.getText(),
|
||||
txtPetBreed.getText(),
|
||||
Integer.parseInt(txtPetAge.getText()),
|
||||
cbPetStatus.getValue(),
|
||||
Double.parseDouble(txtPetPrice.getText())
|
||||
);
|
||||
return pet;
|
||||
}
|
||||
|
||||
private void closeStage(MouseEvent mouseEvent) {
|
||||
Node node = (Node) mouseEvent.getSource();
|
||||
Stage stage = (Stage) node.getScene().getWindow();
|
||||
stage.close();
|
||||
}
|
||||
|
||||
public void displayPetDetails(Pet pet){
|
||||
if (pet!=null){
|
||||
lblPetId.setText("ID: " + pet.getPetId());
|
||||
txtPetName.setText(pet.getPetName());
|
||||
txtPetSpecies.setText(pet.getPetSpecies());
|
||||
txtPetBreed.setText(pet.getPetBreed());
|
||||
txtPetAge.setText(pet.getPetAge() + "");
|
||||
txtPetPrice.setText(pet.getPetPrice() + "");
|
||||
|
||||
//get the right combobox selection
|
||||
for (String status : cbPetStatus.getItems()) {
|
||||
if(status.equals(pet.getPetStatus())){
|
||||
cbPetStatus.getSelectionModel().select(status);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void setMode(String mode) {
|
||||
this.mode = mode;
|
||||
lblMode.setText(mode + " Pet");
|
||||
if(mode.equals("Add")) {
|
||||
lblPetId.setVisible(false);
|
||||
}
|
||||
else if(mode.equals("Edit")) {
|
||||
lblPetId.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,9 @@ package org.example.petshopdesktop.database;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
@@ -9,8 +12,7 @@ import java.util.Properties;
|
||||
|
||||
public class ConnectionDB {
|
||||
/**
|
||||
* Method to try and connect to the database sing cnnection.properties located in the
|
||||
* root of C drive
|
||||
* Method to try and connect to the database using connectionpetstore.properties.
|
||||
* @return Connection to the database
|
||||
*/
|
||||
public static Connection getConnection(){
|
||||
@@ -18,10 +20,23 @@ public class ConnectionDB {
|
||||
String user = "";
|
||||
String password = "";
|
||||
|
||||
try{
|
||||
//Read connection.properties file
|
||||
FileInputStream fis = new FileInputStream("c:\\connectionpetstore.properties"); //location of connection can be changed here
|
||||
Properties prop = new Properties();
|
||||
Properties prop = new Properties();
|
||||
Path propsPath;
|
||||
|
||||
String explicitPath = System.getenv("PETSTORE_DB_PROPS");
|
||||
if (explicitPath != null && !explicitPath.isBlank()) {
|
||||
propsPath = Paths.get(explicitPath);
|
||||
} else {
|
||||
Path cwd = Paths.get(System.getProperty("user.dir"), "connectionpetstore.properties");
|
||||
Path xdg = Paths.get(System.getProperty("user.home"), ".config", "petstore", "connectionpetstore.properties");
|
||||
Path legacyWindows = Paths.get("./connectionpetstore.properties");
|
||||
|
||||
if (Files.exists(cwd)) propsPath = cwd;
|
||||
else if (Files.exists(xdg)) propsPath = xdg;
|
||||
else propsPath = legacyWindows;
|
||||
}
|
||||
|
||||
try (FileInputStream fis = new FileInputStream(propsPath.toString())) {
|
||||
prop.load(fis);
|
||||
url = prop.getProperty("url");
|
||||
user = prop.getProperty("user");
|
||||
@@ -31,12 +46,8 @@ public class ConnectionDB {
|
||||
throw new RuntimeException("Problem with reading connection info: "+e.getMessage());
|
||||
}
|
||||
|
||||
Connection conn = null;
|
||||
|
||||
try{
|
||||
//try to get connection with the data taken from connection.properties
|
||||
conn = DriverManager.getConnection(url,user,password);
|
||||
return conn;
|
||||
return DriverManager.getConnection(url,user,password);
|
||||
}
|
||||
catch (SQLException e) {
|
||||
throw new RuntimeException("Problem with database connection: "+e.getMessage());
|
||||
|
||||
145
src/main/java/org/example/petshopdesktop/database/PetDB.java
Normal file
145
src/main/java/org/example/petshopdesktop/database/PetDB.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package org.example.petshopdesktop.database;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.example.petshopdesktop.models.Pet;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class PetDB {
|
||||
public static ObservableList<Pet> getPets() throws SQLException {
|
||||
//Connect to the database
|
||||
ObservableList<Pet> pets = FXCollections.observableArrayList();
|
||||
Connection conn = ConnectionDB.getConnection();
|
||||
|
||||
//Execute Query
|
||||
Statement stmt = conn.createStatement();
|
||||
ResultSet rs = stmt.executeQuery("SELECT * FROM pet");
|
||||
|
||||
//While there is still data add pets to the list
|
||||
while(rs.next()){
|
||||
Pet pet = new Pet(
|
||||
rs.getInt(1),
|
||||
rs.getString(2),
|
||||
rs.getString(3),
|
||||
rs.getString(4),
|
||||
rs.getInt(5),
|
||||
rs.getString(6),
|
||||
rs.getDouble(7)
|
||||
);
|
||||
pets.add(pet);
|
||||
}
|
||||
|
||||
//close connection and return pets
|
||||
conn.close();
|
||||
return pets;
|
||||
}
|
||||
|
||||
public static ObservableList<Pet> getFilteredPets(String filter) throws SQLException {
|
||||
//Connect to the database
|
||||
ObservableList<Pet> pets = FXCollections.observableArrayList();
|
||||
Connection conn = ConnectionDB.getConnection();
|
||||
|
||||
//Get SQL query for filtered word
|
||||
String sql = "SELECT * FROM pet " +
|
||||
" WHERE " +
|
||||
"petName LIKE ? OR " +
|
||||
"petSpecies LIKE ? OR " +
|
||||
"petBreed LIKE ? OR " +
|
||||
"petAge LIKE ? OR " +
|
||||
"petStatus LIKE ? OR " +
|
||||
"petPrice LIKE ?";
|
||||
|
||||
String filteredString = "%" + filter + "%";
|
||||
|
||||
PreparedStatement stmt = conn.prepareStatement(sql);
|
||||
stmt.setString(1, filteredString);
|
||||
stmt.setString(2, filteredString);
|
||||
stmt.setString(3, filteredString);
|
||||
stmt.setString(4, filteredString);
|
||||
stmt.setString(5, filteredString);
|
||||
stmt.setString(6, filteredString);
|
||||
|
||||
ResultSet rs = stmt.executeQuery();
|
||||
|
||||
while(rs.next()){
|
||||
Pet pet = new Pet(
|
||||
rs.getInt(1),
|
||||
rs.getString(2),
|
||||
rs.getString(3),
|
||||
rs.getString(4),
|
||||
rs.getInt(5),
|
||||
rs.getString(6),
|
||||
rs.getDouble(7)
|
||||
);
|
||||
pets.add(pet);
|
||||
}
|
||||
|
||||
conn.close();
|
||||
return pets;
|
||||
}
|
||||
|
||||
public static int insertPet(Pet pet) throws SQLException {
|
||||
int numRows = 0;
|
||||
|
||||
Connection conn = ConnectionDB.getConnection();
|
||||
String sql = "INSERT INTO pet (petId, petName, petSpecies, petBreed, petAge, petStatus, petPrice)" +
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
PreparedStatement stmt = conn.prepareStatement(sql);
|
||||
stmt.setInt(1, pet.getPetId());
|
||||
stmt.setString(2, pet.getPetName());
|
||||
stmt.setString(3, pet.getPetSpecies());
|
||||
stmt.setString(4, pet.getPetBreed());
|
||||
stmt.setInt(5, pet.getPetAge());
|
||||
stmt.setString(6, pet.getPetStatus());
|
||||
stmt.setDouble(7, pet.getPetPrice());
|
||||
|
||||
numRows = stmt.executeUpdate();
|
||||
conn.close();
|
||||
|
||||
return numRows;
|
||||
}
|
||||
|
||||
public static int updatePet(int petId, Pet pet) throws SQLException {
|
||||
int numRows = 0;
|
||||
|
||||
Connection conn = ConnectionDB.getConnection();
|
||||
String sql = "UPDATE pet SET " +
|
||||
" petName = ?, " +
|
||||
" petSpecies = ?, " +
|
||||
" petBreed = ?, " +
|
||||
" petAge = ?, " +
|
||||
" petStatus = ?, " +
|
||||
" petPrice = ? " +
|
||||
" WHERE petId = ?";
|
||||
PreparedStatement stmt = conn.prepareStatement(sql);
|
||||
stmt.setString(1, pet.getPetName());
|
||||
stmt.setString(2, pet.getPetSpecies());
|
||||
stmt.setString(3, pet.getPetBreed());
|
||||
stmt.setInt(4, pet.getPetAge());
|
||||
stmt.setString(5, pet.getPetStatus());
|
||||
stmt.setDouble(6, pet.getPetPrice());
|
||||
stmt.setInt(7, petId);
|
||||
|
||||
numRows = stmt.executeUpdate();
|
||||
conn.close();
|
||||
|
||||
return numRows;
|
||||
}
|
||||
|
||||
public static int deletePet(int petId) throws SQLException {
|
||||
int numRows = 0;
|
||||
Connection conn = ConnectionDB.getConnection();
|
||||
|
||||
String sql = "DELETE FROM pet WHERE petId = ?";
|
||||
PreparedStatement stmt = conn.prepareStatement(sql);
|
||||
|
||||
stmt.setInt(1, petId);
|
||||
|
||||
numRows = stmt.executeUpdate();
|
||||
conn.close();
|
||||
|
||||
return numRows;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
109
src/main/java/org/example/petshopdesktop/models/Pet.java
Normal file
109
src/main/java/org/example/petshopdesktop/models/Pet.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package org.example.petshopdesktop.models;
|
||||
|
||||
import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
|
||||
public class Pet {
|
||||
private SimpleIntegerProperty petId;
|
||||
private SimpleStringProperty petName;
|
||||
private SimpleStringProperty petSpecies;
|
||||
private SimpleStringProperty petBreed;
|
||||
private SimpleIntegerProperty petAge;
|
||||
private SimpleStringProperty petStatus;
|
||||
private SimpleDoubleProperty petPrice;
|
||||
|
||||
public Pet(int petId, String petName, String petSpecies, String petBreed, int petAge, String petStatus, double petPrice) {
|
||||
this.petId = new SimpleIntegerProperty(petId);
|
||||
this.petName = new SimpleStringProperty(petName);
|
||||
this.petSpecies = new SimpleStringProperty(petSpecies);
|
||||
this.petBreed = new SimpleStringProperty(petBreed);
|
||||
this.petAge = new SimpleIntegerProperty(petAge);
|
||||
this.petStatus = new SimpleStringProperty(petStatus);
|
||||
this.petPrice = new SimpleDoubleProperty(petPrice);
|
||||
}
|
||||
|
||||
public int getPetId() {
|
||||
return petId.get();
|
||||
}
|
||||
|
||||
public void setPetId(int petId) {
|
||||
this.petId.set(petId);
|
||||
}
|
||||
|
||||
public SimpleIntegerProperty petIdProperty() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public String getPetName() {
|
||||
return petName.get();
|
||||
}
|
||||
|
||||
public void setPetName(String petName) {
|
||||
this.petName.set(petName);
|
||||
}
|
||||
|
||||
public SimpleStringProperty petNameProperty() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public String getPetSpecies() {
|
||||
return petSpecies.get();
|
||||
}
|
||||
|
||||
public void setPetSpecies(String petSpecies) {
|
||||
this.petSpecies.set(petSpecies);
|
||||
}
|
||||
|
||||
public SimpleStringProperty petSpeciesProperty() {
|
||||
return petSpecies;
|
||||
}
|
||||
|
||||
public String getPetBreed() {
|
||||
return petBreed.get();
|
||||
}
|
||||
|
||||
public void setPetBreed(String petBreed) {
|
||||
this.petBreed.set(petBreed);
|
||||
}
|
||||
|
||||
public SimpleStringProperty petBreedProperty() {
|
||||
return petBreed;
|
||||
}
|
||||
|
||||
public int getPetAge() {
|
||||
return petAge.get();
|
||||
}
|
||||
|
||||
public void setPetAge(int petAge) {
|
||||
this.petAge.set(petAge);
|
||||
}
|
||||
|
||||
public SimpleIntegerProperty petAgeProperty() {
|
||||
return petAge;
|
||||
}
|
||||
|
||||
public String getPetStatus() {
|
||||
return petStatus.get();
|
||||
}
|
||||
|
||||
public void setPetStatus(String petStatus) {
|
||||
this.petStatus.set(petStatus);
|
||||
}
|
||||
|
||||
public SimpleStringProperty petStatusProperty() {
|
||||
return petStatus;
|
||||
}
|
||||
|
||||
public double getPetPrice() {
|
||||
return petPrice.get();
|
||||
}
|
||||
|
||||
public void setPetPrice(double petPrice) {
|
||||
this.petPrice.set(petPrice);
|
||||
}
|
||||
|
||||
public SimpleDoubleProperty petPriceProperty() {
|
||||
return petPrice;
|
||||
}
|
||||
}
|
||||
27
src/main/java/org/example/petshopdesktop/models/User.java
Normal file
27
src/main/java/org/example/petshopdesktop/models/User.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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,17 @@
|
||||
<Insets bottom="12.0" left="12.0" right="45.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>
|
||||
|
||||
Reference in New Issue
Block a user