fixes to desktop part 1

This commit is contained in:
Alex
2026-04-14 00:55:51 -06:00
parent d898732a17
commit d4958ec914
25 changed files with 1216 additions and 458 deletions

View File

@@ -194,7 +194,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
}
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Completed", "Pending", "Cancelled"};
String[] statuses = {"All Statuses", "Completed", "Pending", "Missed", "Cancelled"};
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, this::loadAdoptions);
}

View File

@@ -22,6 +22,7 @@ public class AppointmentDTO {
private SimpleStringProperty appointmentTime;
private SimpleStringProperty appointmentStatus;
private SimpleStringProperty storeName;
private Long storeId;
public AppointmentDTO(int appointmentId,
int customerId, String customerName,
@@ -32,7 +33,8 @@ public class AppointmentDTO {
String appointmentDate,
String appointmentTime,
String appointmentStatus,
String storeName) {
String storeName,
Long storeId) {
this.appointmentId = new SimpleIntegerProperty(appointmentId);
this.customerId = new SimpleIntegerProperty(customerId);
@@ -47,6 +49,7 @@ public class AppointmentDTO {
this.appointmentTime = new SimpleStringProperty(appointmentTime);
this.appointmentStatus = new SimpleStringProperty(appointmentStatus);
this.storeName = new SimpleStringProperty(storeName != null ? storeName : "");
this.storeId = storeId;
}
public int getAppointmentId() { return appointmentId.get(); }
@@ -66,4 +69,6 @@ public class AppointmentDTO {
public String getAppointmentTime() { return appointmentTime.get(); }
public String getAppointmentStatus() { return appointmentStatus.get(); }
public String getStoreName() { return storeName.get(); }
public Long getStoreId() { return storeId; }
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.api.dto.adoption;
import java.math.BigDecimal;
import java.time.LocalDate;
public class AdoptionRequest {
@@ -9,7 +10,7 @@ public class AdoptionRequest {
private Long sourceStoreId;
private LocalDate adoptionDate;
private String adoptionStatus;
private String paymentMethod;
private BigDecimal adoptionFee;
public AdoptionRequest() {
}
@@ -62,11 +63,11 @@ public class AdoptionRequest {
this.adoptionStatus = adoptionStatus;
}
public String getPaymentMethod() {
return paymentMethod;
public BigDecimal getAdoptionFee() {
return adoptionFee;
}
public void setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
public void setAdoptionFee(BigDecimal adoptionFee) {
this.adoptionFee = adoptionFee;
}
}

View File

@@ -13,6 +13,7 @@ public class AdoptionResponse {
private LocalDate adoptionDate;
private java.math.BigDecimal adoptionFee;
private String adoptionStatus;
private Long sourceStoreId;
private String sourceStoreName;
public AdoptionResponse() {
@@ -98,6 +99,14 @@ public class AdoptionResponse {
this.adoptionStatus = adoptionStatus;
}
public Long getSourceStoreId() {
return sourceStoreId;
}
public void setSourceStoreId(Long sourceStoreId) {
this.sourceStoreId = sourceStoreId;
}
public String getSourceStoreName() {
return sourceStoreName;
}

View File

@@ -8,6 +8,7 @@ public class UserRequest {
private String phone;
private String role;
private Boolean active;
private Integer loyaltyPoints;
public UserRequest() {
}
@@ -67,4 +68,12 @@ public class UserRequest {
public void setActive(Boolean active) {
this.active = active;
}
public Integer getLoyaltyPoints() {
return loyaltyPoints;
}
public void setLoyaltyPoints(Integer loyaltyPoints) {
this.loyaltyPoints = loyaltyPoints;
}
}

View File

@@ -23,7 +23,7 @@ public class AppointmentApi {
return INSTANCE;
}
public List<AppointmentResponse> listAppointments(String query, Long storeId) throws Exception {
public List<AppointmentResponse> listAppointments(String query, Long storeId, Long employeeId) throws Exception {
String path = "/api/v1/appointments?page=0&size=1000";
if (query != null && !query.isEmpty()) {
path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8);
@@ -31,6 +31,9 @@ public class AppointmentApi {
if (storeId != null) {
path += "&storeId=" + storeId;
}
if (employeeId != null) {
path += "&employeeId=" + employeeId;
}
String response = apiClient.getRawResponse(path);
PageResponse<AppointmentResponse> pageResponse = apiClient.getObjectMapper().readValue(
response,

View File

@@ -57,6 +57,10 @@ public class PetApi {
return listPets(query, null, null, null);
}
public PetResponse getPetById(Long id) throws Exception {
return apiClient.get("/api/v1/pets/" + id, PetResponse.class);
}
public PetResponse createPet(PetRequest request) throws Exception {
return apiClient.post("/api/v1/pets", request, PetResponse.class);
}

View File

@@ -284,7 +284,8 @@ public class AdoptionController {
response.getAdoptionDate() != null ? response.getAdoptionDate().toString() : "",
response.getAdoptionFee() != null ? response.getAdoptionFee().doubleValue() : 0.0,
response.getAdoptionStatus(),
response.getSourceStoreName()
response.getSourceStoreName(),
response.getSourceStoreId()
);
}
}

View File

@@ -43,6 +43,7 @@ public class AppointmentController {
@FXML private Button btnEdit;
@FXML private Button btnDelete;
@FXML private Button btnRefresh;
@FXML private javafx.scene.control.ToggleButton btnMyAppointments;
@FXML private Label lblStatus;
@@ -71,6 +72,11 @@ public class AppointmentController {
TableViewSupport.bindSortedItems(tvAppointments, filtered);
TableViewSupport.installDoubleClickAction(tvAppointments, selected -> openDialog(selected, "Edit"));
if (UserSession.getInstance().isStaff()) {
btnMyAppointments.setVisible(true);
btnMyAppointments.setManaged(true);
}
if (txtSearch != null) {
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
}
@@ -92,11 +98,17 @@ public class AppointmentController {
loadAppointments();
}
@FXML
void btnMyAppointmentsToggled(javafx.event.ActionEvent event) {
loadAppointments();
}
private void loadAppointments(){
new Thread(() -> {
try{
Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId();
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null, storeId);
Long employeeId = btnMyAppointments.isSelected() ? UserSession.getInstance().getEmployeeId() : null;
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null, storeId, employeeId);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
@@ -122,7 +134,8 @@ public class AppointmentController {
new Thread(() -> {
try {
Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId();
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query, storeId);
Long employeeId = btnMyAppointments.isSelected() ? UserSession.getInstance().getEmployeeId() : null;
List<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query, storeId, employeeId);
List<AppointmentDTO> appointmentDTOs = responses.stream()
.map(this::mapToAppointmentDTO)
.sorted(Comparator.comparing((AppointmentDTO a) -> a.getAppointmentDate() + "T" + a.getAppointmentTime()).reversed())
@@ -269,7 +282,8 @@ public class AppointmentController {
response.getAppointmentDate() != null ? response.getAppointmentDate().toString() : "",
response.getAppointmentTime() != null ? response.getAppointmentTime().toString() : "",
normalizeAppointmentStatus(response.getAppointmentStatus()),
response.getStoreName()
response.getStoreName(),
response.getStoreId()
);
}

View File

@@ -108,15 +108,15 @@ public class CustomerAccountsController {
}
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml"));
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/customer-edit-dialog-view.fxml"));
Stage dialog = new Stage();
dialog.initOwner(tvCustomers.getScene().getWindow());
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle("Edit Customer Account");
dialog.setScene(new Scene(loader.load()));
dialog.setResizable(false);
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
controller.setUser(selected);
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.CustomerEditDialogController) loader.getController();
controller.setCustomer(selected);
dialog.showAndWait();
refresh();
} catch (Exception e) {

View File

@@ -12,8 +12,8 @@ import javafx.scene.control.*;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.user.UserResponse;
import org.example.petshopdesktop.api.endpoints.UserApi;
import org.example.petshopdesktop.api.dto.employee.EmployeeResponse;
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TableViewSupport;
@@ -24,53 +24,24 @@ import java.util.stream.Collectors;
public class StaffAccountsController {
@FXML
private VBox staffSection;
@FXML private VBox staffSection;
@FXML private TableView<EmployeeResponse> tvStaff;
@FXML private TableColumn<EmployeeResponse, String> colUsername;
@FXML private TableColumn<EmployeeResponse, String> colName;
@FXML private TableColumn<EmployeeResponse, String> colEmail;
@FXML private TableColumn<EmployeeResponse, String> colPhone;
@FXML private TableColumn<EmployeeResponse, String> colRole;
@FXML private TableColumn<EmployeeResponse, String> colStatus;
@FXML private TableColumn<EmployeeResponse, Object> colCreated;
@FXML private TextField txtSearch;
@FXML private Button btnRefresh;
@FXML private Button btnCreateAccount;
@FXML private Button btnEditAccount;
@FXML private Label lblError;
@FXML private Label lblStatus;
@FXML
private TableView<UserResponse> tvStaff;
@FXML
private TableColumn<UserResponse, String> colUsername;
@FXML
private TableColumn<UserResponse, String> colName;
@FXML
private TableColumn<UserResponse, String> colEmail;
@FXML
private TableColumn<UserResponse, String> colPhone;
@FXML
private TableColumn<UserResponse, String> colRole;
@FXML
private TableColumn<UserResponse, String> colStatus;
@FXML
private TableColumn<UserResponse, Object> colCreated;
@FXML
private TextField txtSearch;
@FXML
private Button btnRefresh;
@FXML
private Button btnCreateAccount;
@FXML
private Button btnEditAccount;
@FXML
private Label lblError;
@FXML
private Label lblStatus;
private final ObservableList<UserResponse> staffAccounts = FXCollections.observableArrayList();
private FilteredList<UserResponse> filteredStaff;
private final ObservableList<EmployeeResponse> staffAccounts = FXCollections.observableArrayList();
private FilteredList<EmployeeResponse> filteredStaff;
@FXML
public void initialize() {
@@ -78,8 +49,13 @@ public class StaffAccountsController {
colName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName()));
colEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail()));
colPhone.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getPhone()));
colRole.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getRole()));
colStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getActive() != null && data.getValue().getActive() ? "Active" : "Inactive"));
colRole.setCellValueFactory(data -> {
String role = data.getValue().getRole() != null ? data.getValue().getRole() : "";
String staffRole = data.getValue().getStaffRole() != null ? " (" + data.getValue().getStaffRole() + ")" : "";
return new javafx.beans.property.SimpleStringProperty(role + staffRole);
});
colStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(
Boolean.TRUE.equals(data.getValue().getActive()) ? "Active" : "Inactive"));
colCreated.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getCreatedAt()));
filteredStaff = new FilteredList<>(staffAccounts, a -> true);
@@ -128,7 +104,7 @@ public class StaffAccountsController {
openEditDialog(tvStaff.getSelectionModel().getSelectedItem());
}
private void openEditDialog(UserResponse selected) {
private void openEditDialog(EmployeeResponse selected) {
if (selected == null) {
lblError.setText("Select a user account to edit.");
return;
@@ -152,12 +128,12 @@ public class StaffAccountsController {
dialog.setScene(new Scene(loader.load()));
dialog.setResizable(false);
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
controller.setUser(selected);
controller.setEmployee(selected);
dialog.showAndWait();
refresh();
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffAccountsController.openEditDialog", e, "Opening user edit dialog");
lblError.setText("Could not open user account editor.");
ActivityLogger.getInstance().logException("StaffAccountsController.openEditDialog", e, "Opening employee edit dialog");
lblError.setText("Could not open employee account editor.");
}
}
@@ -167,11 +143,10 @@ public class StaffAccountsController {
new Thread(() -> {
try {
Comparator<UserResponse> byCreated = Comparator.comparing(
UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
Comparator<EmployeeResponse> byCreated = Comparator.comparing(
EmployeeResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
List<UserResponse> staff = UserApi.getInstance().listUsers(null).stream()
.filter(u -> !"CUSTOMER".equalsIgnoreCase(u.getRole()))
List<EmployeeResponse> staff = EmployeeApi.getInstance().listEmployees(null).stream()
.sorted(byCreated)
.collect(Collectors.toList());
@@ -180,7 +155,7 @@ public class StaffAccountsController {
tvStaff.setDisable(false);
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading employee accounts");
Platform.runLater(() -> {
lblError.setText("Could not load staff accounts.");
tvStaff.setDisable(false);
@@ -201,6 +176,7 @@ public class StaffAccountsController {
|| safe(a.getEmail()).contains(q)
|| safe(a.getPhone()).contains(q)
|| safe(a.getRole()).contains(q)
|| safe(a.getStaffRole()).contains(q)
);
}

View File

@@ -8,164 +8,92 @@ import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ChoiceDialog;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.adoption.AdoptionRequest;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.endpoints.AdoptionApi;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.PetApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.models.Adoption;
import org.example.petshopdesktop.util.ActivityLogger;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import java.util.Objects;
public class AdoptionDialogController {
@FXML
private Button btnCancel;
@FXML
private Button btnSave;
@FXML
private ComboBox<String> cbAdoptionStatus;
@FXML
private ComboBox<DropdownOption> cbCustomer;
@FXML
private ComboBox<DropdownOption> cbEmployee;
@FXML
private ComboBox<DropdownOption> cbPet;
@FXML
private DatePicker dpAdoptionDate;
@FXML
private Label lblAdoptionId;
@FXML
private Label lblMode;
@FXML private Button btnCancel;
@FXML private Button btnSave;
@FXML private ComboBox<String> cbAdoptionStatus;
@FXML private ComboBox<DropdownOption> cbCustomer;
@FXML private ComboBox<DropdownOption> cbEmployee;
@FXML private ComboBox<DropdownOption> cbPet;
@FXML private ComboBox<DropdownOption> cbStore;
@FXML private VBox vbStore;
@FXML private DatePicker dpAdoptionDate;
@FXML private TextField txtAdoptionFee;
@FXML private Label lblAdoptionId;
@FXML private Label lblMode;
private String mode = null;
private Adoption selectedAdoption = null;
private String selectedPaymentMethod = null;
private boolean suppressPaymentDialog = false;
private boolean suppressStatusListener = false;
private Long pendingStoreId = null;
private ObservableList<String> statusList = FXCollections.observableArrayList(
private final ObservableList<String> statusList = FXCollections.observableArrayList(
"Pending", "Completed", "Missed", "Cancelled"
);
@FXML
void initialize() {
cbAdoptionStatus.setItems(statusList);
cbAdoptionStatus.valueProperty().addListener((obs, oldVal, newVal) -> {
if ("Completed".equals(newVal) && !"Completed".equals(oldVal) && !suppressPaymentDialog) {
ChoiceDialog<String> dialog = new ChoiceDialog<>("Cash", "Cash", "Credit Card", "Debit Card", "E-Transfer");
dialog.setTitle("Payment Method");
dialog.setHeaderText("Confirm payment received");
dialog.setContentText("Select payment method:");
dialog.showAndWait().ifPresentOrElse(
method -> selectedPaymentMethod = method,
() -> {
selectedPaymentMethod = null;
cbAdoptionStatus.setValue(oldVal);
}
);
} else if (!"Completed".equals(newVal)) {
selectedPaymentMethod = null;
if (!suppressStatusListener && newVal != null) {
applyStatusFieldRules(newVal);
}
});
cbEmployee.setPromptText("Select an employee");
txtAdoptionFee.setDisable(true);
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getAdoptionPets();
Platform.runLater(() -> {
if (pets != null) {
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
ensureSelectedPetOption(petsObs);
cbPet.setItems(petsObs);
applySelectedPet();
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading pets for combo box");
System.out.println("Error loading pets: " + e.getMessage());
});
}
}).start();
new Thread(() -> {
try {
List<DropdownOption> employees = DropdownApi.getInstance().getEmployees();
Platform.runLater(() -> {
ObservableList<DropdownOption> employeesObs = FXCollections.observableArrayList(employees);
ensureSelectedEmployeeOption(employeesObs);
cbEmployee.setItems(employeesObs);
applySelectedEmployee();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading employees for combo box");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
cbEmployee.setCellFactory(param -> new ListCell<>() {
LocalDate today = LocalDate.now();
dpAdoptionDate.setDayCellFactory(picker -> new javafx.scene.control.DateCell() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbEmployee.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
setDisable(empty || item.isBefore(today));
}
});
new Thread(() -> {
try {
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
Platform.runLater(() -> {
if (customers != null) {
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
cbCustomer.setItems(customersObs);
applySelectedCustomer();
}
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.initialize",
e,
"Loading customers for combo box");
System.out.println("Error loading customers: " + e.getMessage());
});
setupDropdownCellFactory(cbEmployee);
setupDropdownCellFactory(cbPet);
setupDropdownCellFactory(cbCustomer);
setupDropdownCellFactory(cbStore);
cbPet.valueProperty().addListener((obs, oldVal, newVal) -> {
if (newVal != null) {
loadPetPrice(newVal.getId());
} else {
txtAdoptionFee.setText("0");
}
}).start();
});
if (UserSession.getInstance().isAdmin()) {
vbStore.setVisible(true);
vbStore.setManaged(true);
}
loadDropdownsAsync();
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
@@ -182,32 +110,146 @@ public class AdoptionDialogController {
});
}
private void setupDropdownCellFactory(ComboBox<DropdownOption> cb) {
cb.setCellFactory(param -> new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
cb.setButtonCell(new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
}
private void loadDropdownsAsync() {
new Thread(() -> {
try {
List<DropdownOption> pets = DropdownApi.getInstance().getAdoptionPets();
Platform.runLater(() -> {
if (pets != null) {
ObservableList<DropdownOption> petsObs = FXCollections.observableArrayList(pets);
ensureSelectedPetOption(petsObs);
cbPet.setItems(petsObs);
applySelectedPet();
}
});
} catch (Exception e) {
Platform.runLater(() -> ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadDropdownsAsync", e, "Loading pets"));
}
}).start();
new Thread(() -> {
try {
List<DropdownOption> employees = DropdownApi.getInstance().getEmployees();
Platform.runLater(() -> {
ObservableList<DropdownOption> employeesObs = FXCollections.observableArrayList(employees);
ensureSelectedEmployeeOption(employeesObs);
cbEmployee.setItems(employeesObs);
applySelectedEmployee();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadDropdownsAsync", e, "Loading employees");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
new Thread(() -> {
try {
List<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
Platform.runLater(() -> {
if (customers != null) {
ObservableList<DropdownOption> customersObs = FXCollections.observableArrayList(customers);
cbCustomer.setItems(customersObs);
applySelectedCustomer();
}
});
} catch (Exception e) {
Platform.runLater(() -> ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadDropdownsAsync", e, "Loading customers"));
}
}).start();
if (UserSession.getInstance().isAdmin()) {
new Thread(() -> {
try {
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
Platform.runLater(() -> {
if (stores != null) {
cbStore.setItems(FXCollections.observableArrayList(stores));
if (pendingStoreId != null) {
for (DropdownOption store : cbStore.getItems()) {
if (pendingStoreId.equals(store.getId())) {
cbStore.setValue(store);
break;
}
}
pendingStoreId = null;
}
}
});
} catch (Exception e) {
Platform.runLater(() -> ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadDropdownsAsync", e, "Loading stores"));
}
}).start();
}
}
private void applyStatusFieldRules(String status) {
if ("Cancelled".equalsIgnoreCase(status) || "Completed".equalsIgnoreCase(status) || "Missed".equalsIgnoreCase(status)) {
cbEmployee.setDisable(true);
dpAdoptionDate.setDisable(true);
cbStore.setDisable(true);
} else {
cbEmployee.setDisable(false);
dpAdoptionDate.setDisable(false);
if (UserSession.getInstance().isAdmin()) cbStore.setDisable(false);
}
}
private void buttonSaveClicked(MouseEvent mouseEvent) {
String errorMsg = "";
if (cbPet.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Pet is required.\n";
}
if (cbPet.getSelectionModel().getSelectedItem() == null) errorMsg += "Pet is required.\n";
if (cbCustomer.getSelectionModel().getSelectedItem() == null) errorMsg += "Customer is required.\n";
if (cbEmployee.getSelectionModel().getSelectedItem() == null) errorMsg += "Employee is required.\n";
if (dpAdoptionDate.getValue() == null) errorMsg += "Adoption Date is required.\n";
if (cbAdoptionStatus.getSelectionModel().getSelectedItem() == null) errorMsg += "Status is required.\n";
if (cbCustomer.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Customer is required.\n";
}
if (cbEmployee.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Employee is required.\n";
}
if (dpAdoptionDate.getValue() == null) {
errorMsg += "Adoption Date is required.\n";
}
if (cbAdoptionStatus.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Status is required.\n";
BigDecimal adoptionFee = BigDecimal.ZERO;
String feeText = txtAdoptionFee.getText() == null ? "" : txtAdoptionFee.getText().trim();
if (!feeText.isEmpty()) {
try {
adoptionFee = new BigDecimal(feeText);
} catch (NumberFormatException e) {
adoptionFee = BigDecimal.ZERO;
}
}
if (errorMsg.isEmpty()) {
try {
Long storeId = UserSession.getInstance().getStoreId();
Long storeId;
if (UserSession.getInstance().isAdmin()) {
if (cbStore.getSelectionModel().getSelectedItem() == null) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error");
alert.setContentText("Store is required.");
alert.showAndWait();
return;
}
storeId = cbStore.getSelectionModel().getSelectedItem().getId();
} else {
storeId = UserSession.getInstance().getStoreId();
}
if (storeId == null || storeId <= 0) {
throw new IllegalStateException("Store is not set for this account");
}
@@ -219,15 +261,13 @@ public class AdoptionDialogController {
request.setSourceStoreId(storeId);
request.setAdoptionDate(dpAdoptionDate.getValue());
request.setAdoptionStatus(cbAdoptionStatus.getValue());
request.setPaymentMethod(selectedPaymentMethod);
request.setAdoptionFee(adoptionFee);
if (mode.equals("Add")) {
AdoptionApi.getInstance().createAdoption(request);
} else {
String[] parts = lblAdoptionId.getText().split(": ");
if (parts.length < 2) {
throw new IllegalStateException("Invalid adoption ID format");
}
if (parts.length < 2) throw new IllegalStateException("Invalid adoption ID format");
Long adoptionId = Long.parseLong(parts[1]);
AdoptionApi.getInstance().updateAdoption(adoptionId, request);
}
@@ -239,9 +279,7 @@ public class AdoptionDialogController {
closeStage(mouseEvent);
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.buttonSaveClicked",
e,
mode + " adoption");
"AdoptionDialogController.buttonSaveClicked", e, mode + " adoption");
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Database Operation Error");
alert.setContentText(e.getMessage());
@@ -262,36 +300,37 @@ public class AdoptionDialogController {
}
public void displayAdoptionDetails(Adoption adoption) {
if (adoption != null) {
selectedAdoption = adoption;
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
ensureSelectedEmployeeOption(cbEmployee.getItems());
applySelectedPet();
applySelectedCustomer();
applySelectedEmployee();
if (adoption == null) return;
selectedAdoption = adoption;
lblAdoptionId.setText("ID: " + adoption.getAdoptionId());
pendingStoreId = adoption.getStoreId();
ensureSelectedEmployeeOption(cbEmployee.getItems());
applySelectedPet();
applySelectedCustomer();
applySelectedEmployee();
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
try {
dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate()));
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.displayAdoptionDetails",
e,
"Parsing adoption date");
}
if (adoption.getAdoptionDate() != null && !adoption.getAdoptionDate().isEmpty()) {
try {
dpAdoptionDate.setValue(LocalDate.parse(adoption.getAdoptionDate()));
} catch (Exception e) {
ActivityLogger.getInstance().logException(
"AdoptionDialogController.displayAdoptionDetails", e, "Parsing adoption date");
}
suppressPaymentDialog = true;
cbAdoptionStatus.setItems(statusList);
for (String status : cbAdoptionStatus.getItems()) {
if (status.equals(adoption.getAdoptionStatus())) {
cbAdoptionStatus.getSelectionModel().select(status);
break;
}
}
suppressPaymentDialog = false;
applyEditModeLock();
}
txtAdoptionFee.setText(adoption.getAdoptionFee() > 0
? String.format("%.2f", adoption.getAdoptionFee()) : "0.00");
suppressStatusListener = true;
cbAdoptionStatus.setItems(statusList);
for (String status : cbAdoptionStatus.getItems()) {
if (status.equals(adoption.getAdoptionStatus())) {
cbAdoptionStatus.getSelectionModel().select(status);
break;
}
}
suppressStatusListener = false;
applyEditModeLock();
}
private void applyEditModeLock() {
@@ -304,6 +343,7 @@ public class AdoptionDialogController {
dpAdoptionDate.setDisable(true);
cbAdoptionStatus.setDisable(true);
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Cancelled"));
cbStore.setDisable(true);
btnSave.setDisable(true);
return;
}
@@ -311,23 +351,33 @@ public class AdoptionDialogController {
LocalDate adoptionDate = dpAdoptionDate.getValue();
boolean isPast = adoptionDate != null && adoptionDate.isBefore(LocalDate.now());
cbPet.setDisable(true);
cbCustomer.setDisable(true);
dpAdoptionDate.setDisable(false);
cbEmployee.setDisable(false);
cbAdoptionStatus.setDisable(false);
suppressPaymentDialog = true;
if (isPast) {
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Completed", "Missed"));
cbPet.setDisable(true);
cbCustomer.setDisable(true);
cbEmployee.setDisable(true);
dpAdoptionDate.setDisable(true);
cbStore.setDisable(true);
cbAdoptionStatus.setDisable(false);
suppressStatusListener = true;
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Completed", "Missed"));
if (!cbAdoptionStatus.getItems().contains(cbAdoptionStatus.getValue())) {
cbAdoptionStatus.getSelectionModel().selectFirst();
}
suppressStatusListener = false;
} else {
cbPet.setDisable(true);
cbCustomer.setDisable(true);
dpAdoptionDate.setDisable(false);
cbEmployee.setDisable(false);
cbAdoptionStatus.setDisable(false);
if (UserSession.getInstance().isAdmin()) cbStore.setDisable(false);
suppressStatusListener = true;
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending", "Cancelled"));
if (!cbAdoptionStatus.getItems().contains(cbAdoptionStatus.getValue())) {
cbAdoptionStatus.getSelectionModel().selectFirst();
}
suppressStatusListener = false;
}
if (!cbAdoptionStatus.getItems().contains(cbAdoptionStatus.getValue())) {
cbAdoptionStatus.getSelectionModel().selectFirst();
}
suppressPaymentDialog = false;
}
public void setMode(String mode) {
@@ -335,60 +385,64 @@ public class AdoptionDialogController {
lblMode.setText(mode + " Adoption");
lblAdoptionId.setVisible(mode.equals("Edit"));
if (mode.equals("Add")) {
suppressPaymentDialog = true;
suppressStatusListener = true;
cbAdoptionStatus.setItems(FXCollections.observableArrayList("Pending"));
cbAdoptionStatus.setValue("Pending");
cbAdoptionStatus.setDisable(true);
suppressPaymentDialog = false;
suppressStatusListener = false;
txtAdoptionFee.setText("");
}
}
private void applySelectedPet() {
if (selectedAdoption == null || selectedAdoption.getPetId() <= 0) {
return;
}
if (selectedAdoption == null || selectedAdoption.getPetId() <= 0) return;
DropdownOption selected = findOptionById(cbPet.getItems(), (long) selectedAdoption.getPetId());
if (selected != null && !Objects.equals(cbPet.getValue(), selected)) {
cbPet.setValue(selected);
}
if (selected != null && !Objects.equals(cbPet.getValue(), selected)) cbPet.setValue(selected);
}
private void applySelectedCustomer() {
if (selectedAdoption == null || selectedAdoption.getCustomerId() <= 0) {
return;
}
if (selectedAdoption == null || selectedAdoption.getCustomerId() <= 0) return;
DropdownOption selected = findOptionById(cbCustomer.getItems(), (long) selectedAdoption.getCustomerId());
if (selected != null && !Objects.equals(cbCustomer.getValue(), selected)) {
cbCustomer.setValue(selected);
}
if (selected != null && !Objects.equals(cbCustomer.getValue(), selected)) cbCustomer.setValue(selected);
}
private void applySelectedEmployee() {
if (selectedAdoption == null || selectedAdoption.getEmployeeId() <= 0) {
return;
}
if (selectedAdoption == null || selectedAdoption.getEmployeeId() <= 0) return;
DropdownOption selected = findOptionById(cbEmployee.getItems(), (long) selectedAdoption.getEmployeeId());
if (selected != null && !Objects.equals(cbEmployee.getValue(), selected)) {
cbEmployee.setValue(selected);
}
if (selected != null && !Objects.equals(cbEmployee.getValue(), selected)) cbEmployee.setValue(selected);
}
private DropdownOption findOptionById(List<DropdownOption> options, Long id) {
if (id == null || options == null) {
return null;
}
if (id == null || options == null) return null;
for (DropdownOption option : options) {
if (option.getId() != null && option.getId().equals(id)) {
return option;
}
if (option.getId() != null && option.getId().equals(id)) return option;
}
return null;
}
private void loadPetPrice(Long petId) {
new Thread(() -> {
try {
var pet = PetApi.getInstance().getPetById(petId);
Platform.runLater(() -> {
if (pet != null && pet.getPetPrice() != null) {
txtAdoptionFee.setText(String.format("%.2f", pet.getPetPrice()));
} else {
txtAdoptionFee.setText("0.00");
}
});
} catch (Exception e) {
Platform.runLater(() -> {
txtAdoptionFee.setText("0.00");
ActivityLogger.getInstance().logException(
"AdoptionDialogController.loadPetPrice", e, "Loading pet price");
});
}
}).start();
}
private void ensureSelectedEmployeeOption(ObservableList<DropdownOption> options) {
if (selectedAdoption == null || selectedAdoption.getEmployeeId() <= 0 || options == null) {
return;
}
if (selectedAdoption == null || selectedAdoption.getEmployeeId() <= 0 || options == null) return;
DropdownOption existing = findOptionById(options, (long) selectedAdoption.getEmployeeId());
if (existing == null) {
DropdownOption option = new DropdownOption();
@@ -399,9 +453,7 @@ public class AdoptionDialogController {
}
private void ensureSelectedPetOption(ObservableList<DropdownOption> options) {
if (selectedAdoption == null || selectedAdoption.getPetId() <= 0 || options == null) {
return;
}
if (selectedAdoption == null || selectedAdoption.getPetId() <= 0 || options == null) return;
DropdownOption existing = findOptionById(options, (long) selectedAdoption.getPetId());
if (existing == null) {
DropdownOption option = new DropdownOption();

View File

@@ -7,6 +7,7 @@ import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.scene.control.ListCell;
@@ -18,7 +19,6 @@ import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;
import java.time.DayOfWeek;
import java.time.LocalTime;
import java.time.LocalDate;
import java.util.List;
@@ -33,6 +33,8 @@ public class AppointmentDialogController {
@FXML private ComboBox<DropdownOption> cbCustomer;
@FXML private ComboBox<DropdownOption> cbPet;
@FXML private ComboBox<DropdownOption> cbEmployee;
@FXML private ComboBox<DropdownOption> cbStore;
@FXML private VBox vbStore;
@FXML private ComboBox<Integer> cbHour;
@FXML private ComboBox<Integer> cbMinute;
@@ -46,6 +48,7 @@ public class AppointmentDialogController {
private String mode = null;
private AppointmentDTO selectedAppointment = null;
private Long pendingPetSelectionId = null;
private Long pendingStoreId = null;
private boolean isOriginallyCancel = false;
private boolean isOriginallyCompletedOrMissed = false;
@@ -63,18 +66,39 @@ public class AppointmentDialogController {
@FXML
public void initialize() {
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Booked", "Completed", "Missed", "Cancelled"));
cbAppointmentStatus.valueProperty().addListener((obs, oldVal, newVal) -> {
if (newVal == null) return;
boolean lockAll = "Cancelled".equalsIgnoreCase(newVal)
|| "Completed".equalsIgnoreCase(newVal)
|| "Missed".equalsIgnoreCase(newVal);
if (lockAll) {
cbEmployee.setDisable(true);
cbHour.setDisable(true);
cbMinute.setDisable(true);
dpAppointmentDate.setDisable(true);
cbStore.setDisable(true);
} else {
if (!isOriginallyCancel && !isOriginallyCompletedOrMissed) {
cbEmployee.setDisable(false);
cbHour.setDisable(false);
cbMinute.setDisable(false);
dpAppointmentDate.setDisable(false);
if (UserSession.getInstance().isAdmin()) cbStore.setDisable(false);
}
}
});
cbPet.setDisable(true);
cbEmployee.setPromptText("Select an employee");
cbPet.setPromptText("Select a customer first");
cbCustomer.setPromptText("Select a customer");
cbService.setPromptText("Select a service");
LocalDate minDate = minAppointmentDate();
dpAppointmentDate.setValue(minDate);
LocalDate today = LocalDate.now();
dpAppointmentDate.setValue(today);
dpAppointmentDate.setDayCellFactory(picker -> new javafx.scene.control.DateCell() {
@Override
public void updateItem(LocalDate item, boolean empty) {
super.updateItem(item, empty);
setDisable(empty || item.isBefore(minDate));
setDisable(empty || item.isBefore(today));
}
});
cbAppointmentStatus.setValue("Booked");
@@ -179,7 +203,7 @@ public class AppointmentDialogController {
Long customerId = newValue != null ? newValue.getId() : null;
cbPet.setValue(null);
cbPet.setItems(FXCollections.observableArrayList());
cbPet.setDisable(customerId == null);
cbPet.setDisable(customerId == null || isOriginallyCancel || isOriginallyCompletedOrMissed);
if (customerId != null) {
cbPet.setPromptText("Loading customer pets...");
loadCustomerPets(customerId);
@@ -189,12 +213,36 @@ public class AppointmentDialogController {
}
});
cbStore.setCellFactory(param -> new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
cbStore.setButtonCell(new ListCell<>() {
@Override
protected void updateItem(DropdownOption option, boolean empty) {
super.updateItem(option, empty);
setText(empty || option == null ? null : option.getLabel());
}
});
if (UserSession.getInstance().isAdmin()) {
vbStore.setVisible(true);
vbStore.setManaged(true);
}
btnSave.setOnMouseClicked(this::buttonSaveClicked);
btnCancel.setOnMouseClicked(this::closeStage);
loadServices();
loadAppointmentCustomers();
loadEmployees();
if (UserSession.getInstance().isAdmin()) {
loadStores();
} else {
loadEmployees();
}
}
public void displayAppointmentDetails(AppointmentDTO appt) {
@@ -202,6 +250,7 @@ public class AppointmentDialogController {
selectedAppointment = appt;
lblAppointmentId.setText("ID: " + appt.getAppointmentId());
pendingPetSelectionId = appt.getPetId() > 0 ? (long) appt.getPetId() : null;
pendingStoreId = appt.getStoreId();
try {
dpAppointmentDate.setValue(
@@ -248,6 +297,7 @@ public class AppointmentDialogController {
dpAppointmentDate.setDisable(true);
cbAppointmentStatus.setDisable(true);
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Cancelled"));
cbStore.setDisable(true);
btnSave.setDisable(true);
} else if (isOriginallyCompletedOrMissed) {
cbService.setDisable(true);
@@ -257,6 +307,7 @@ public class AppointmentDialogController {
cbHour.setDisable(true);
cbMinute.setDisable(true);
dpAppointmentDate.setDisable(true);
cbStore.setDisable(true);
cbAppointmentStatus.setDisable(false);
cbAppointmentStatus.setItems(FXCollections.observableArrayList("Completed", "Missed"));
} else {
@@ -288,7 +339,16 @@ public class AppointmentDialogController {
}
LocalTime appointmentTime = LocalTime.of(cbHour.getValue(), cbMinute.getValue());
Long storeId = UserSession.getInstance().getStoreId();
Long storeId;
if (UserSession.getInstance().isAdmin()) {
if (cbStore.getSelectionModel().getSelectedItem() == null) {
showError("Store is required.");
return;
}
storeId = cbStore.getSelectionModel().getSelectedItem().getId();
} else {
storeId = UserSession.getInstance().getStoreId();
}
if (storeId == null || storeId <= 0) {
showError("Store is not set for this account");
return;
@@ -408,7 +468,7 @@ public class AppointmentDialogController {
}
}
cbPet.setItems(petOptions);
cbPet.setDisable(petOptions.isEmpty());
cbPet.setDisable(petOptions.isEmpty() || isOriginallyCancel || isOriginallyCompletedOrMissed);
cbPet.setPromptText(petOptions.isEmpty() ? "No pets for selected customer" : "Select a pet");
if (pendingPetSelectionId != null) {
for (DropdownOption pet : cbPet.getItems()) {
@@ -462,7 +522,7 @@ public class AppointmentDialogController {
Platform.runLater(() -> {
cbCustomer.setItems(FXCollections.observableArrayList(customers));
boolean hasCustomers = customers != null && !customers.isEmpty();
cbCustomer.setDisable(!hasCustomers);
cbCustomer.setDisable(!hasCustomers || isOriginallyCancel || isOriginallyCompletedOrMissed);
cbPet.setDisable(true);
cbPet.setItems(FXCollections.observableArrayList());
cbCustomer.setPromptText(hasCustomers ? "Select a customer" : "No customers with pets yet");
@@ -484,17 +544,61 @@ public class AppointmentDialogController {
}).start();
}
private LocalDate minAppointmentDate() {
LocalDate date = LocalDate.now();
int businessDaysAdded = 0;
while (businessDaysAdded < 2) {
date = date.plusDays(1);
DayOfWeek dow = date.getDayOfWeek();
if (dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY) {
businessDaysAdded++;
private void loadStores() {
new Thread(() -> {
try {
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
Platform.runLater(() -> {
if (stores != null) {
cbStore.setItems(FXCollections.observableArrayList(stores));
cbStore.valueProperty().addListener((obs, oldVal, newVal) -> {
Long sid = newVal != null ? newVal.getId() : null;
cbEmployee.setValue(null);
cbEmployee.setItems(FXCollections.observableArrayList());
if (sid != null) {
loadEmployeesForStore(sid);
}
});
if (pendingStoreId != null) {
for (DropdownOption store : cbStore.getItems()) {
if (pendingStoreId.equals(store.getId())) {
cbStore.setValue(store);
break;
}
}
pendingStoreId = null;
}
}
});
} catch (Exception e) {
Platform.runLater(() -> ActivityLogger.getInstance().logException(
"AppointmentDialogController.loadStores",
e,
"Loading stores for appointment dialog"));
}
}
return date;
}).start();
}
private void loadEmployeesForStore(Long storeId) {
new Thread(() -> {
try {
List<DropdownOption> employees = DropdownApi.getInstance().getStoreEmployees(storeId);
Platform.runLater(() -> {
cbEmployee.setItems(FXCollections.observableArrayList(employees));
applySelectedEmployee();
});
} catch (Exception e) {
Platform.runLater(() -> {
ActivityLogger.getInstance().logException(
"AppointmentDialogController.loadEmployeesForStore",
e,
"Loading employees for store");
cbEmployee.setDisable(true);
cbEmployee.setPromptText("Unable to load employees");
});
}
}).start();
}
private void loadEmployees() {

View File

@@ -0,0 +1,175 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.api.dto.user.UserRequest;
import org.example.petshopdesktop.api.dto.user.UserResponse;
import org.example.petshopdesktop.api.endpoints.CustomerApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TextFieldFormatSupport;
public class CustomerEditDialogController {
@FXML private TextField txtFirstName;
@FXML private TextField txtLastName;
@FXML private TextField txtEmail;
@FXML private TextField txtPhone;
@FXML private TextField txtUsername;
@FXML private PasswordField txtPassword;
@FXML private PasswordField txtPasswordConfirm;
@FXML private ComboBox<String> cbActive;
@FXML private TextField txtLoyaltyPoints;
@FXML private Label lblError;
@FXML private Button btnSave;
private UserResponse customer;
@FXML
void initialize() {
TextFieldFormatSupport.applyPhoneNumberFormat(txtPhone);
cbActive.setItems(FXCollections.observableArrayList("Active", "Inactive"));
boolean isAdmin = UserSession.getInstance().isAdmin();
txtLoyaltyPoints.setDisable(!isAdmin);
}
public void setCustomer(UserResponse user) {
this.customer = user;
String fullName = user.getFullName() == null ? "" : user.getFullName();
String[] names = splitFullName(fullName);
txtFirstName.setText(names[0]);
txtLastName.setText(names[1]);
txtEmail.setText(user.getEmail());
txtPhone.setText(user.getPhone());
txtUsername.setText(user.getUsername());
cbActive.setValue(Boolean.TRUE.equals(user.getActive()) ? "Active" : "Inactive");
int pts = user.getLoyaltyPoints() != null ? user.getLoyaltyPoints() : 0;
txtLoyaltyPoints.setText(String.valueOf(pts));
}
private String[] splitFullName(String fullName) {
if (fullName == null || fullName.trim().isEmpty()) return new String[]{"", ""};
String[] parts = fullName.trim().split("\\s+", 2);
return new String[]{parts.length > 0 ? parts[0] : "", parts.length > 1 ? parts[1] : ""};
}
@FXML
void btnSaveClicked(ActionEvent event) {
lblError.setText("");
if (customer == null) {
lblError.setText("No customer selected.");
return;
}
String firstName = value(txtFirstName);
String lastName = value(txtLastName);
String email = value(txtEmail);
String phone = value(txtPhone);
String username = value(txtUsername);
String password = txtPassword.getText() == null ? "" : txtPassword.getText().trim();
String confirm = txtPasswordConfirm.getText() == null ? "" : txtPasswordConfirm.getText().trim();
String loyaltyPointsText = value(txtLoyaltyPoints);
if (firstName.isBlank() || lastName.isBlank()) {
lblError.setText("First name and last name are required.");
return;
}
if (email.isBlank()) {
lblError.setText("Email is required.");
return;
}
if (phone.isBlank()) {
lblError.setText("Phone is required.");
return;
}
String phoneError = Validator.isValidPhoneNumber(phone, "Phone");
if (!phoneError.isEmpty()) {
lblError.setText(phoneError.trim());
return;
}
if (username.isBlank()) {
lblError.setText("Username is required.");
return;
}
if (!password.isEmpty() && password.length() < 6) {
lblError.setText("Password must be at least 6 characters.");
return;
}
if (!password.equals(confirm)) {
lblError.setText("Passwords do not match.");
return;
}
Integer loyaltyPoints = null;
if (UserSession.getInstance().isAdmin()) {
if (loyaltyPointsText.isBlank()) {
lblError.setText("Loyalty points is required.");
return;
}
try {
loyaltyPoints = Integer.parseInt(loyaltyPointsText);
if (loyaltyPoints < 0) {
lblError.setText("Loyalty points cannot be negative.");
return;
}
} catch (NumberFormatException e) {
lblError.setText("Loyalty points must be a valid integer.");
return;
}
}
btnSave.setDisable(true);
boolean active = "Active".equals(cbActive.getValue());
Integer finalLoyaltyPoints = loyaltyPoints;
new Thread(() -> {
try {
UserRequest request = new UserRequest();
request.setUsername(username);
request.setPassword(password.isEmpty() ? null : password);
request.setFullName(firstName + " " + lastName);
request.setEmail(email);
request.setPhone(phone);
request.setRole(customer.getRole());
request.setActive(active);
if (finalLoyaltyPoints != null) request.setLoyaltyPoints(finalLoyaltyPoints);
CustomerApi.getInstance().updateCustomer(customer.getId(), request);
Platform.runLater(this::close);
} catch (Exception e) {
ActivityLogger.getInstance().logException("CustomerEditDialogController.btnSaveClicked", e, "Updating customer");
String msg = e.getMessage() == null ? "Could not update customer." : e.getMessage();
Platform.runLater(() -> {
lblError.setText(msg);
btnSave.setDisable(false);
});
}
}).start();
}
@FXML
void btnCancelClicked(ActionEvent event) {
close();
}
private void close() {
Stage stage = (Stage) btnSave.getScene().getWindow();
stage.close();
}
private static String value(TextField tf) {
return tf.getText() == null ? "" : tf.getText().trim();
}
}

View File

@@ -1,5 +1,6 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
@@ -9,66 +10,65 @@ import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import org.example.petshopdesktop.models.Inventory;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.inventory.InventoryRequest;
import org.example.petshopdesktop.api.dto.inventory.InventoryResponse;
import org.example.petshopdesktop.api.dto.product.ProductResponse;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.InventoryApi;
import org.example.petshopdesktop.api.endpoints.ProductApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.models.Product;
import org.example.petshopdesktop.util.ActivityLogger;
import java.math.BigDecimal;
import java.util.List;
import java.util.stream.Collectors;
public class InventoryDialogController {
//FXML elements
@FXML
private Button btnCancel;
@FXML private Button btnCancel;
@FXML private Button btnSave;
@FXML private ComboBox<Product> cbProduct;
@FXML private ComboBox<DropdownOption> cbStore;
@FXML private VBox vbStoreField;
@FXML private Label lblInventoryId;
@FXML private Label lblMode;
@FXML private TextField txtQuantity;
@FXML
private Button btnSave;
@FXML
private ComboBox<Product> cbProduct;
@FXML
private Label lblInventoryId;
@FXML
private Label lblMode;
@FXML
private TextField txtQuantity;
//Determines if the mode is add or edit
private String mode = null;
private Long pendingStoreId = null;
//Loads upon .FXML boot
@FXML
void initialize() {
cbProduct.setConverter(new StringConverter<Product>() {
//Displays product in combobox (prodID + name)
@Override
public String toString(Product product) {
return product == null ? "" : product.getProdId() + ": " + product.getProdName();
}
//Not needed
@Override
public Product fromString(String string) { return null; }
});
//Load product list from API into combobox
cbStore.setCellFactory(param -> new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
cbStore.setButtonCell(new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
try {
List<ProductResponse> productResponses = ProductApi.getInstance().listProducts(null);
if (productResponses != null) {
@@ -89,10 +89,16 @@ public class InventoryDialogController {
"InventoryDialogController.initialize",
e,
"Loading products for combo box");
System.out.println("Error loading products: " + e.getMessage());
}
//Save button handler
boolean isAdmin = UserSession.getInstance().isAdmin();
if (isAdmin) {
loadStores();
} else {
vbStoreField.setManaged(false);
vbStoreField.setVisible(false);
}
btnSave.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
@@ -100,7 +106,6 @@ public class InventoryDialogController {
}
});
//Cancel button handler
btnCancel.setOnMouseClicked(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent mouseEvent) {
@@ -109,29 +114,66 @@ public class InventoryDialogController {
});
}
//Handles save button click event
private void loadStores() {
new Thread(() -> {
try {
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
Platform.runLater(() -> {
cbStore.setItems(FXCollections.observableArrayList(stores));
applyPendingStore();
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("InventoryDialogController.loadStores", e, "Loading stores");
Platform.runLater(() -> {
cbStore.setDisable(true);
cbStore.setPromptText("Unable to load stores");
});
}
}).start();
}
private void applyPendingStore() {
if (pendingStoreId == null) return;
for (DropdownOption opt : cbStore.getItems()) {
if (opt.getId() != null && opt.getId().equals(pendingStoreId)) {
cbStore.setValue(opt);
pendingStoreId = null;
return;
}
}
}
private void buttonSaveClicked(MouseEvent mouseEvent) {
int numRow = 0;
String errorMsg = "";
if (cbProduct.getSelectionModel().getSelectedItem() == null) {
errorMsg += "Product is required.\n";
}
//Validate inputs
errorMsg += Validator.isPresent(txtQuantity.getText(), "Quantity");
errorMsg += Validator.isLessThanVarChars(txtQuantity.getText(), "Quantity", 11);
errorMsg += Validator.isPositiveInteger(txtQuantity.getText(), "Quantity");
//Operation only occurs if there are no errors
boolean isAdmin = UserSession.getInstance().isAdmin();
Long storeId;
if (isAdmin) {
if (cbStore.getValue() == null) {
errorMsg += "Store is required.\n";
storeId = null;
} else {
storeId = cbStore.getValue().getId();
}
} else {
storeId = UserSession.getInstance().getStoreId();
if (storeId == null || storeId <= 0) {
errorMsg += "Store is not set for this account.\n";
}
}
if (errorMsg.isEmpty()) {
try {
InventoryRequest request = new InventoryRequest();
Product selectedProduct = cbProduct.getSelectionModel().getSelectedItem();
Long storeId = UserSession.getInstance().getStoreId();
if (storeId == null || storeId <= 0) {
throw new IllegalStateException("Store is not set for this account");
}
request.setProdId((long) selectedProduct.getProdId());
int quantity;
try {
@@ -168,10 +210,7 @@ public class InventoryDialogController {
alert.setContentText(e.getMessage());
alert.showAndWait();
}
}
//Displays validation errors
else {
} else {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setHeaderText("Input Error");
alert.setContentText(errorMsg);
@@ -179,22 +218,16 @@ public class InventoryDialogController {
}
}
//Close dialog view
private void closeStage(MouseEvent mouseEvent) {
Node node = (Node) mouseEvent.getSource();
Stage stage = (Stage) node.getScene().getWindow();
stage.close();
}
//Editing
//Displays fields with existing inventory data
public void displayInventoryDetails(Inventory inventory) {
if (inventory != null) {
//Displays inventory ID
lblInventoryId.setText("ID: " + inventory.getInventoryId());
//Selecting matching product in combobox
for (Product product : cbProduct.getItems()) {
if (product.getProdId() == inventory.getProdId()) {
cbProduct.getSelectionModel().select(product);
@@ -203,10 +236,15 @@ public class InventoryDialogController {
}
txtQuantity.setText(String.valueOf(inventory.getQuantity()));
boolean isAdmin = UserSession.getInstance().isAdmin();
if (isAdmin && inventory.getStoreId() > 0) {
pendingStoreId = (long) inventory.getStoreId();
applyPendingStore();
}
}
}
//Sets dialog view to add/edit. Updates UI labels
public void setMode(String mode) {
this.mode = mode;
lblMode.setText(mode + " Inventory");

View File

@@ -1,84 +1,132 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.api.dto.user.UserRequest;
import org.example.petshopdesktop.api.dto.user.UserResponse;
import org.example.petshopdesktop.api.endpoints.UserApi;
import org.example.petshopdesktop.api.endpoints.CustomerApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
import org.example.petshopdesktop.api.dto.employee.EmployeeResponse;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TextFieldFormatSupport;
import java.util.List;
public class StaffEditDialogController {
@FXML
private TextField txtFirstName;
@FXML private TextField txtFirstName;
@FXML private TextField txtLastName;
@FXML private TextField txtEmail;
@FXML private TextField txtPhone;
@FXML private TextField txtUsername;
@FXML private PasswordField txtPassword;
@FXML private PasswordField txtPasswordConfirm;
@FXML private ComboBox<String> cbRole;
@FXML private ComboBox<String> cbStaffRole;
@FXML private ComboBox<String> cbActive;
@FXML private ComboBox<DropdownOption> cbStore;
@FXML private Label lblError;
@FXML private Button btnSave;
@FXML
private TextField txtLastName;
@FXML
private TextField txtEmail;
@FXML
private TextField txtPhone;
@FXML
private TextField txtUsername;
@FXML
private PasswordField txtPassword;
@FXML
private PasswordField txtPasswordConfirm;
@FXML
private Label lblError;
@FXML
private Button btnSave;
private UserResponse user;
private EmployeeResponse employee;
private Long pendingStoreId = null;
@FXML
void initialize() {
TextFieldFormatSupport.applyPhoneNumberFormat(txtPhone);
cbRole.setItems(FXCollections.observableArrayList("STAFF", "ADMIN"));
cbStaffRole.setItems(FXCollections.observableArrayList(
"STORE_MANAGER", "SALES_ASSOCIATE", "GROOMER", "VETERINARIAN"));
cbActive.setItems(FXCollections.observableArrayList("Active", "Inactive"));
cbStore.setCellFactory(param -> new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
cbStore.setButtonCell(new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
loadStores();
}
public void setUser(UserResponse user) {
this.user = user;
String fullName = user.getFullName() == null ? "" : user.getFullName();
private void loadStores() {
new Thread(() -> {
try {
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
Platform.runLater(() -> {
cbStore.setItems(FXCollections.observableArrayList(stores));
applyPendingStore();
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffEditDialogController.loadStores", e, "Loading stores");
Platform.runLater(() -> {
cbStore.setDisable(true);
cbStore.setPromptText("Unable to load stores");
});
}
}).start();
}
private void applyPendingStore() {
if (pendingStoreId == null) return;
for (DropdownOption opt : cbStore.getItems()) {
if (opt.getId() != null && opt.getId().equals(pendingStoreId)) {
cbStore.setValue(opt);
pendingStoreId = null;
return;
}
}
}
public void setEmployee(EmployeeResponse emp) {
this.employee = emp;
String fullName = emp.getFullName() != null ? emp.getFullName() : "";
if (fullName.isEmpty() && emp.getFirstName() != null) {
fullName = emp.getFirstName() + (emp.getLastName() != null ? " " + emp.getLastName() : "");
}
String[] names = splitFullName(fullName);
txtFirstName.setText(names[0]);
txtLastName.setText(names[1]);
txtEmail.setText(user.getEmail());
txtPhone.setText(user.getPhone());
txtUsername.setText(user.getUsername());
txtEmail.setText(emp.getEmail());
txtPhone.setText(emp.getPhone());
txtUsername.setText(emp.getUsername());
if (emp.getRole() != null) cbRole.setValue(emp.getRole());
if (emp.getStaffRole() != null) cbStaffRole.setValue(emp.getStaffRole());
cbActive.setValue(Boolean.TRUE.equals(emp.getActive()) ? "Active" : "Inactive");
pendingStoreId = emp.getPrimaryStoreId();
applyPendingStore();
}
private String[] splitFullName(String fullName) {
if (fullName == null || fullName.trim().isEmpty()) {
return new String[]{"", ""};
}
if (fullName == null || fullName.trim().isEmpty()) return new String[]{"", ""};
String[] parts = fullName.trim().split("\\s+", 2);
String firstName = parts.length > 0 ? parts[0] : "";
String lastName = parts.length > 1 ? parts[1] : "";
return new String[]{firstName, lastName};
return new String[]{parts.length > 0 ? parts[0] : "", parts.length > 1 ? parts[1] : ""};
}
@FXML
void btnSaveClicked(ActionEvent event) {
lblError.setText("");
if (user == null) {
lblError.setText("No user selected.");
if (employee == null) {
lblError.setText("No employee selected.");
return;
}
@@ -119,31 +167,47 @@ public class StaffEditDialogController {
lblError.setText("Passwords do not match.");
return;
}
if (cbRole.getValue() == null) {
lblError.setText("Role is required.");
return;
}
if (cbStaffRole.getValue() == null) {
lblError.setText("Staff role is required.");
return;
}
if (cbStore.getValue() == null) {
lblError.setText("Primary store is required.");
return;
}
btnSave.setDisable(true);
String role = cbRole.getValue();
String staffRole = cbStaffRole.getValue();
boolean active = "Active".equals(cbActive.getValue());
Long storeId = cbStore.getValue().getId();
new Thread(() -> {
try {
UserRequest request = new UserRequest();
EmployeeRequest request = new EmployeeRequest();
request.setUsername(username);
request.setPassword(password.isEmpty() ? null : password);
request.setFirstName(firstName);
request.setLastName(lastName);
request.setFullName(firstName + " " + lastName);
request.setEmail(email);
request.setPhone(phone);
request.setRole(user.getRole());
request.setActive(user.getActive());
request.setRole(role);
request.setStaffRole(staffRole);
request.setActive(active);
request.setPrimaryStoreId(storeId);
UserSession session = UserSession.getInstance();
if (session.isAdmin()) {
UserApi.getInstance().updateUser(user.getId(), request);
} else {
CustomerApi.getInstance().updateCustomer(user.getId(), request);
}
EmployeeApi.getInstance().updateEmployee(employee.getId(), request);
Platform.runLater(this::close);
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffEditDialogController.btnSaveClicked", e, "Updating user");
String msg = e.getMessage() == null ? "Could not update user." : e.getMessage();
ActivityLogger.getInstance().logException("StaffEditDialogController.btnSaveClicked", e, "Updating employee");
String msg = e.getMessage() == null ? "Could not update employee." : e.getMessage();
Platform.runLater(() -> {
lblError.setText(msg);
btnSave.setDisable(false);

View File

@@ -1,54 +1,86 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.example.petshopdesktop.api.dto.user.UserRequest;
import org.example.petshopdesktop.api.endpoints.UserApi;
import org.example.petshopdesktop.api.endpoints.CustomerApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.api.dto.common.DropdownOption;
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
import org.example.petshopdesktop.api.endpoints.DropdownApi;
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.util.TextFieldFormatSupport;
import java.util.List;
public class StaffRegisterDialogController {
@FXML
private TextField txtFirstName;
@FXML
private TextField txtLastName;
@FXML
private TextField txtEmail;
@FXML
private TextField txtPhone;
@FXML
private TextField txtUsername;
@FXML
private PasswordField txtPassword;
@FXML
private PasswordField txtPasswordConfirm;
@FXML
private Label lblError;
@FXML
private Button btnCreate;
@FXML private TextField txtFirstName;
@FXML private TextField txtLastName;
@FXML private TextField txtEmail;
@FXML private TextField txtPhone;
@FXML private TextField txtUsername;
@FXML private PasswordField txtPassword;
@FXML private PasswordField txtPasswordConfirm;
@FXML private ComboBox<String> cbRole;
@FXML private ComboBox<String> cbStaffRole;
@FXML private ComboBox<String> cbActive;
@FXML private ComboBox<DropdownOption> cbStore;
@FXML private Label lblError;
@FXML private Button btnCreate;
@FXML
void initialize() {
TextFieldFormatSupport.applyPhoneNumberFormat(txtPhone);
cbRole.setItems(FXCollections.observableArrayList("STAFF", "ADMIN"));
cbRole.getSelectionModel().select("STAFF");
cbStaffRole.setItems(FXCollections.observableArrayList(
"STORE_MANAGER", "SALES_ASSOCIATE", "GROOMER", "VETERINARIAN"));
cbStaffRole.getSelectionModel().selectFirst();
cbActive.setItems(FXCollections.observableArrayList("Active", "Inactive"));
cbActive.getSelectionModel().select("Active");
cbStore.setCellFactory(param -> new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
cbStore.setButtonCell(new ListCell<>() {
@Override protected void updateItem(DropdownOption o, boolean empty) {
super.updateItem(o, empty);
setText(empty || o == null ? null : o.getLabel());
}
});
loadStores();
}
private void loadStores() {
new Thread(() -> {
try {
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
Platform.runLater(() -> cbStore.setItems(FXCollections.observableArrayList(stores)));
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffRegisterDialogController.loadStores", e, "Loading stores");
Platform.runLater(() -> {
cbStore.setDisable(true);
cbStore.setPromptText("Unable to load stores");
});
}
}).start();
}
@FXML
@@ -96,38 +128,53 @@ public class StaffRegisterDialogController {
lblError.setText("Passwords do not match.");
return;
}
if (cbRole.getValue() == null) {
lblError.setText("Role is required.");
return;
}
if (cbStaffRole.getValue() == null) {
lblError.setText("Staff role is required.");
return;
}
if (cbStore.getValue() == null) {
lblError.setText("Primary store is required.");
return;
}
btnCreate.setDisable(true);
String role = cbRole.getValue();
String staffRole = cbStaffRole.getValue();
boolean active = "Active".equals(cbActive.getValue());
Long storeId = cbStore.getValue().getId();
new Thread(() -> {
try {
UserSession session = UserSession.getInstance();
UserRequest request = new UserRequest();
EmployeeRequest request = new EmployeeRequest();
request.setUsername(username);
request.setPassword(password);
request.setFirstName(firstName);
request.setLastName(lastName);
request.setFullName(firstName + " " + lastName);
request.setEmail(email);
request.setPhone(phone);
request.setActive(true);
request.setRole(role);
request.setStaffRole(staffRole);
request.setActive(active);
request.setPrimaryStoreId(storeId);
if (session.isAdmin()) {
request.setRole("STAFF");
UserApi.getInstance().createUser(request);
} else {
request.setRole("CUSTOMER");
CustomerApi.getInstance().createCustomer(request);
}
EmployeeApi.getInstance().createEmployee(request);
Platform.runLater(() -> {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Account Created");
alert.setHeaderText(null);
alert.setContentText("Account created successfully.");
alert.setContentText("Staff account created successfully.");
alert.showAndWait();
close();
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffRegisterDialogController.btnCreateClicked", e, "Creating account");
ActivityLogger.getInstance().logException("StaffRegisterDialogController.btnCreateClicked", e, "Creating staff account");
String msg = e.getMessage() == null ? "Could not create account." : e.getMessage();
Platform.runLater(() -> {
if (msg.toLowerCase().contains("duplicate") || msg.toLowerCase().contains("unique")) {

View File

@@ -16,8 +16,9 @@ public class Adoption {
private SimpleDoubleProperty adoptionFee;
private SimpleStringProperty adoptionStatus;
private SimpleStringProperty storeName;
private Long storeId;
public Adoption(int adoptionId, int petId, int customerId, int employeeId, String petName, String customerName, String employeeName, String adoptionDate, double adoptionFee, String adoptionStatus, String storeName) {
public Adoption(int adoptionId, int petId, int customerId, int employeeId, String petName, String customerName, String employeeName, String adoptionDate, double adoptionFee, String adoptionStatus, String storeName, Long storeId) {
this.adoptionId = new SimpleIntegerProperty(adoptionId);
this.petId = new SimpleIntegerProperty(petId);
this.customerId = new SimpleIntegerProperty(customerId);
@@ -29,6 +30,7 @@ public class Adoption {
this.adoptionFee = new SimpleDoubleProperty(adoptionFee);
this.adoptionStatus = new SimpleStringProperty(adoptionStatus);
this.storeName = new SimpleStringProperty(storeName != null ? storeName : "");
this.storeId = storeId;
}
public int getAdoptionId() { return adoptionId.get(); }
@@ -96,4 +98,8 @@ public class Adoption {
public void setStoreName(String storeName) { this.storeName.set(storeName); }
public SimpleStringProperty storeNameProperty() { return storeName; }
public Long getStoreId() { return storeId; }
public void setStoreId(Long storeId) { this.storeId = storeId; }
}

View File

@@ -5,6 +5,7 @@
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.DatePicker?>
<?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?>
@@ -146,6 +147,34 @@
</ComboBox>
</children>
</VBox>
<VBox fx:id="vbStore" prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="3" visible="false" managed="false">
<children>
<Label text="Store:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbStore" prefHeight="29.0" prefWidth="336.0" promptText="Select Store" 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="2">
<children>
<Label text="Adoption Fee:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<TextField fx:id="txtAdoptionFee" promptText="0.00" style="-fx-border-color: #E8EBED; -fx-border-width: 2; -fx-border-radius: 10; -fx-background-radius: 10; -fx-background-color: white;">
<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" />

View File

@@ -208,6 +208,20 @@
</ComboBox>
</children>
</VBox>
<VBox fx:id="vbStore" prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="3" visible="false" managed="false">
<children>
<Label text="Store:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbStore" prefHeight="29.0" prefWidth="336.0" promptText="Select Store" 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="3">
<children>
<Label text="Employee:" textFill="#2c3e50">

View File

@@ -0,0 +1,114 @@
<?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.Label?>
<?import javafx.scene.control.PasswordField?>
<?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 prefWidth="480.0" spacing="14.0" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.CustomerEditDialogController">
<padding>
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
</padding>
<children>
<Label text="Edit Customer" textFill="#2C3E50">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true" />
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="First Name" />
<TextField fx:id="txtFirstName" promptText="First name" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Last Name" />
<TextField fx:id="txtLastName" promptText="Last name" />
</children>
</VBox>
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Email" />
<TextField fx:id="txtEmail" promptText="name@petshop.com" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Phone" />
<TextField fx:id="txtPhone" promptText="123-456-7890" />
</children>
</VBox>
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Username" />
<TextField fx:id="txtUsername" promptText="username" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="New Password" />
<PasswordField fx:id="txtPassword" promptText="leave blank to keep current" />
</children>
</VBox>
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Confirm Password" />
<PasswordField fx:id="txtPasswordConfirm" promptText="confirm new password" />
</children>
</VBox>
<Region HBox.hgrow="ALWAYS" />
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Status" />
<ComboBox fx:id="cbActive" maxWidth="1.7976931348623157E308" promptText="Select Status" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Loyalty Points" />
<TextField fx:id="txtLoyaltyPoints" promptText="0" />
</children>
</VBox>
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" spacing="10.0">
<children>
<Button mnemonicParsing="false" onAction="#btnCancelClicked" text="Cancel" />
<Button fx:id="btnSave" mnemonicParsing="false" onAction="#btnSaveClicked" style="-fx-background-color: #4ECDC4; -fx-text-fill: white; -fx-cursor: hand;" text="Save" />
</children>
</HBox>
</children>
</VBox>

View File

@@ -103,6 +103,20 @@
</TextField>
</children>
</VBox>
<VBox fx:id="vbStoreField" prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="1">
<children>
<Label text="Store:" textFill="#2c3e50">
<font>
<Font name="System Bold" size="16.0" />
</font>
</Label>
<ComboBox fx:id="cbStore" prefHeight="29.0" prefWidth="336.0" promptText="Select Store" 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" />
<VBox prefHeight="200.0" prefWidth="100.0" spacing="8.0" GridPane.rowIndex="2" />
</children>

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
@@ -10,7 +11,7 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox spacing="14.0" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController">
<VBox prefWidth="480.0" spacing="14.0" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController">
<padding>
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
</padding>
@@ -86,6 +87,40 @@
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Role" />
<ComboBox fx:id="cbRole" maxWidth="1.7976931348623157E308" promptText="Select Role" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Staff Role" />
<ComboBox fx:id="cbStaffRole" maxWidth="1.7976931348623157E308" promptText="Select Staff Role" />
</children>
</VBox>
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Status" />
<ComboBox fx:id="cbActive" maxWidth="1.7976931348623157E308" promptText="Select Status" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Primary Store" />
<ComboBox fx:id="cbStore" maxWidth="1.7976931348623157E308" promptText="Select Store" />
</children>
</VBox>
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" spacing="10.0">
<children>
<Button mnemonicParsing="false" onAction="#btnCancelClicked" text="Cancel" />

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
@@ -10,7 +11,7 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox spacing="14.0" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.StaffRegisterDialogController">
<VBox prefWidth="480.0" spacing="14.0" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.StaffRegisterDialogController">
<padding>
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
</padding>
@@ -86,6 +87,40 @@
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Role" />
<ComboBox fx:id="cbRole" maxWidth="1.7976931348623157E308" promptText="Select Role" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Staff Role" />
<ComboBox fx:id="cbStaffRole" maxWidth="1.7976931348623157E308" promptText="Select Staff Role" />
</children>
</VBox>
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Status" />
<ComboBox fx:id="cbActive" maxWidth="1.7976931348623157E308" promptText="Select Status" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Primary Store" />
<ComboBox fx:id="cbStore" maxWidth="1.7976931348623157E308" promptText="Select Store" />
</children>
</VBox>
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" spacing="10.0">
<children>
<Button mnemonicParsing="false" onAction="#btnCancelClicked" text="Cancel" />

View File

@@ -3,6 +3,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
@@ -59,6 +60,14 @@
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
</padding>
</Button>
<ToggleButton fx:id="btnMyAppointments" mnemonicParsing="false" onAction="#btnMyAppointmentsToggled" style="-fx-background-color: #8e44ad; -fx-cursor: hand; -fx-background-radius: 8;" text="My Appointments" textFill="WHITE" visible="false" managed="false">
<font>
<Font name="System Bold" size="14.0" />
</font>
<padding>
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
</padding>
</ToggleButton>
</children>
</HBox>
<HBox alignment="CENTER_LEFT" prefHeight="37.0" prefWidth="727.0" spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 2; -fx-border-radius: 14;">