From 98bd619ba6bab1198a504c452f3effc4d8504d88 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 14 Apr 2026 00:55:51 -0600 Subject: [PATCH 01/69] fixes to desktop part 1 --- .../listfragments/AdoptionFragment.java | 2 +- .../petshopdesktop/DTOs/AppointmentDTO.java | 7 +- .../api/dto/adoption/AdoptionRequest.java | 11 +- .../api/dto/adoption/AdoptionResponse.java | 9 + .../api/dto/user/UserRequest.java | 9 + .../api/endpoints/AppointmentApi.java | 5 +- .../petshopdesktop/api/endpoints/PetApi.java | 4 + .../controllers/AdoptionController.java | 3 +- .../controllers/AppointmentController.java | 20 +- .../CustomerAccountsController.java | 6 +- .../controllers/StaffAccountsController.java | 94 ++-- .../AdoptionDialogController.java | 480 ++++++++++-------- .../AppointmentDialogController.java | 142 +++++- .../CustomerEditDialogController.java | 175 +++++++ .../InventoryDialogController.java | 136 +++-- .../StaffEditDialogController.java | 178 ++++--- .../StaffRegisterDialogController.java | 131 +++-- .../petshopdesktop/models/Adoption.java | 8 +- .../dialogviews/adoption-dialog-view.fxml | 29 ++ .../dialogviews/appointment-dialog-view.fxml | 14 + .../customer-edit-dialog-view.fxml | 114 +++++ .../dialogviews/inventory-dialog-view.fxml | 14 + .../dialogviews/staff-edit-dialog-view.fxml | 37 +- .../staff-register-dialog-view.fxml | 37 +- .../modelviews/appointment-view.fxml | 9 + 25 files changed, 1216 insertions(+), 458 deletions(-) create mode 100644 desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java create mode 100644 desktop/src/main/resources/org/example/petshopdesktop/dialogviews/customer-edit-dialog-view.fxml diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java index cfc05233..125d92d8 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java @@ -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); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java b/desktop/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java index 7f1c0a5a..82b242b9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java +++ b/desktop/src/main/java/org/example/petshopdesktop/DTOs/AppointmentDTO.java @@ -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; } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java index 75ece4d9..b1d56eab 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionRequest.java @@ -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; } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionResponse.java index 715a1a06..56134bff 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionResponse.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/adoption/AdoptionResponse.java @@ -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; } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java index a6c9f669..28eab2cf 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java @@ -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; + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AppointmentApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AppointmentApi.java index 5462fff4..0fe56661 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AppointmentApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/AppointmentApi.java @@ -23,7 +23,7 @@ public class AppointmentApi { return INSTANCE; } - public List listAppointments(String query, Long storeId) throws Exception { + public List 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 pageResponse = apiClient.getObjectMapper().readValue( response, diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java index 5f69e025..865bb1ea 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/PetApi.java @@ -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); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java index 2c9565b9..10452d27 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java @@ -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() ); } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java index 9583b11c..7fdd5396 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java @@ -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 responses = AppointmentApi.getInstance().listAppointments(null, storeId); + Long employeeId = btnMyAppointments.isSelected() ? UserSession.getInstance().getEmployeeId() : null; + List responses = AppointmentApi.getInstance().listAppointments(null, storeId, employeeId); List 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 responses = AppointmentApi.getInstance().listAppointments(query, storeId); + Long employeeId = btnMyAppointments.isSelected() ? UserSession.getInstance().getEmployeeId() : null; + List responses = AppointmentApi.getInstance().listAppointments(query, storeId, employeeId); List 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() ); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java index 4de53100..42866e75 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java @@ -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) { diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java index 76771784..565b339e 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java @@ -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 tvStaff; + @FXML private TableColumn colUsername; + @FXML private TableColumn colName; + @FXML private TableColumn colEmail; + @FXML private TableColumn colPhone; + @FXML private TableColumn colRole; + @FXML private TableColumn colStatus; + @FXML private TableColumn 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 tvStaff; - - @FXML - private TableColumn colUsername; - - @FXML - private TableColumn colName; - - @FXML - private TableColumn colEmail; - - @FXML - private TableColumn colPhone; - - @FXML - private TableColumn colRole; - - @FXML - private TableColumn colStatus; - - @FXML - private TableColumn 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 staffAccounts = FXCollections.observableArrayList(); - private FilteredList filteredStaff; + private final ObservableList staffAccounts = FXCollections.observableArrayList(); + private FilteredList 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 byCreated = Comparator.comparing( - UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder())); + Comparator byCreated = Comparator.comparing( + EmployeeResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder())); - List staff = UserApi.getInstance().listUsers(null).stream() - .filter(u -> !"CUSTOMER".equalsIgnoreCase(u.getRole())) + List 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) ); } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java index 399f1ce6..f9041fb9 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AdoptionDialogController.java @@ -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 cbAdoptionStatus; - - @FXML - private ComboBox cbCustomer; - - @FXML - private ComboBox cbEmployee; - - @FXML - private ComboBox cbPet; - - @FXML - private DatePicker dpAdoptionDate; - - @FXML - private Label lblAdoptionId; - - @FXML - private Label lblMode; + @FXML private Button btnCancel; + @FXML private Button btnSave; + @FXML private ComboBox cbAdoptionStatus; + @FXML private ComboBox cbCustomer; + @FXML private ComboBox cbEmployee; + @FXML private ComboBox cbPet; + @FXML private ComboBox 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 statusList = FXCollections.observableArrayList( + private final ObservableList 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 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 pets = DropdownApi.getInstance().getAdoptionPets(); - Platform.runLater(() -> { - if (pets != null) { - ObservableList 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 employees = DropdownApi.getInstance().getEmployees(); - Platform.runLater(() -> { - ObservableList 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 customers = DropdownApi.getInstance().getCustomers(); - Platform.runLater(() -> { - if (customers != null) { - ObservableList 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() { @Override @@ -182,32 +110,146 @@ public class AdoptionDialogController { }); } + private void setupDropdownCellFactory(ComboBox 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 pets = DropdownApi.getInstance().getAdoptionPets(); + Platform.runLater(() -> { + if (pets != null) { + ObservableList 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 employees = DropdownApi.getInstance().getEmployees(); + Platform.runLater(() -> { + ObservableList 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 customers = DropdownApi.getInstance().getCustomers(); + Platform.runLater(() -> { + if (customers != null) { + ObservableList 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 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 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 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 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(); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java index c3980df5..2c9c66bc 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/AppointmentDialogController.java @@ -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 cbCustomer; @FXML private ComboBox cbPet; @FXML private ComboBox cbEmployee; + @FXML private ComboBox cbStore; + @FXML private VBox vbStore; @FXML private ComboBox cbHour; @FXML private ComboBox 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 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 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() { diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java new file mode 100644 index 00000000..c997e51c --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/CustomerEditDialogController.java @@ -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 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(); + } +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java index 54ff00f5..2aa797e4 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/InventoryDialogController.java @@ -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 cbProduct; + @FXML private ComboBox cbStore; + @FXML private VBox vbStoreField; + @FXML private Label lblInventoryId; + @FXML private Label lblMode; + @FXML private TextField txtQuantity; - @FXML - private Button btnSave; - - @FXML - private ComboBox 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() { - - //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 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() { @Override public void handle(MouseEvent mouseEvent) { @@ -100,7 +106,6 @@ public class InventoryDialogController { } }); - //Cancel button handler btnCancel.setOnMouseClicked(new EventHandler() { @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 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"); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java index 45dd6dae..77818453 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java @@ -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 cbRole; + @FXML private ComboBox cbStaffRole; + @FXML private ComboBox cbActive; + @FXML private ComboBox 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 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); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java index 91208a70..b6f9e54c 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java @@ -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 cbRole; + @FXML private ComboBox cbStaffRole; + @FXML private ComboBox cbActive; + @FXML private ComboBox 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 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")) { diff --git a/desktop/src/main/java/org/example/petshopdesktop/models/Adoption.java b/desktop/src/main/java/org/example/petshopdesktop/models/Adoption.java index 4ab50dea..73f23afc 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/models/Adoption.java +++ b/desktop/src/main/java/org/example/petshopdesktop/models/Adoption.java @@ -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; } } diff --git a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml index b1140dad..7b0f3610 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/adoption-dialog-view.fxml @@ -5,6 +5,7 @@ + @@ -146,6 +147,34 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml index df0ccb5a..2e70d6cd 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/appointment-dialog-view.fxml @@ -208,6 +208,20 @@ + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml index be6feef8..e55b0e0c 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/adoption-view.fxml @@ -2,6 +2,7 @@ + @@ -72,6 +73,8 @@ + + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml index 1cdedf21..4538a831 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/appointment-view.fxml @@ -2,6 +2,7 @@ + @@ -81,6 +82,8 @@ + + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/customer-accounts-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/customer-accounts-view.fxml index 323c3577..d7156dab 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/customer-accounts-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/customer-accounts-view.fxml @@ -2,6 +2,7 @@ + @@ -62,6 +63,7 @@ + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml index a723127d..feff63b0 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/inventory-view.fxml @@ -2,6 +2,7 @@ + @@ -71,6 +72,7 @@ +