diff --git a/desktop/src/main/java/org/example/petshopdesktop/PetShopApplication.java b/desktop/src/main/java/org/example/petshopdesktop/PetShopApplication.java index 0127e42c..21d7d9ef 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/PetShopApplication.java +++ b/desktop/src/main/java/org/example/petshopdesktop/PetShopApplication.java @@ -3,16 +3,27 @@ package org.example.petshopdesktop; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; +import javafx.scene.image.Image; import javafx.stage.Stage; import java.io.IOException; +import java.util.Objects; public class PetShopApplication extends Application { @Override public void start(Stage stage) throws IOException { FXMLLoader fxmlLoader = new FXMLLoader(PetShopApplication.class.getResource("login-view.fxml")); Scene scene = new Scene(fxmlLoader.load()); - stage.setTitle("Pet Shop Manager - Login"); + stage.setTitle("Leon's Pet Store - Login"); + + try { + stage.getIcons().add(new Image(Objects.requireNonNull( + getClass().getResourceAsStream("/org/example/petshopdesktop/images/leons-pet-store-badge.png") + ))); + } catch (Exception e) { + System.err.println("Could not load application icon: " + e.getMessage()); + } + stage.setScene(scene); stage.show(); } 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 e135328d..3ab3c712 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AdoptionController.java @@ -14,7 +14,9 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.stage.Modality; import javafx.stage.Stage; import org.example.petshopdesktop.api.dto.adoption.AdoptionResponse; +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.controllers.dialogcontrollers.AdoptionDialogController; import org.example.petshopdesktop.models.Adoption; import org.example.petshopdesktop.ui.CalendarPane; @@ -76,6 +78,14 @@ public class AdoptionController { @FXML private TextField txtSearch; + @FXML + private ComboBox cbStatusFilter; + + @FXML + private ComboBox cbStoreFilter; + + private final java.util.ArrayList storeOptions = new java.util.ArrayList<>(); + @FXML private CalendarPane calendarPane; private LocalDate selectedCalendarDate = null; @@ -104,6 +114,17 @@ public class AdoptionController { filteredAdoptions = new FilteredList<>(data, a -> true); TableViewSupport.bindSortedItems(tvAdoptions, filteredAdoptions); + cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Pending", "Completed", "Cancelled")); + cbStatusFilter.getSelectionModel().selectFirst(); + cbStatusFilter.valueProperty().addListener((obs, o, n) -> applyFilterPredicate()); + + if (UserSession.getInstance().isAdmin()) { + cbStoreFilter.setVisible(true); + cbStoreFilter.setManaged(true); + loadStoreFilter(); + } + cbStoreFilter.valueProperty().addListener((obs, o, n) -> displayAdoptions()); + displayAdoptions(); TableViewSupport.installDoubleClickAction(tvAdoptions, selected -> openDialog(selected, "Edit")); @@ -120,7 +141,7 @@ public class AdoptionController { calendarPane.setOnDateSelected(date -> { selectedCalendarDate = date; - filteredAdoptions.setPredicate(a -> date == null || a.getAdoptionDate().equals(date.toString())); + applyFilterPredicate(); }); tvAdoptions.setOnKeyPressed(event -> { @@ -135,6 +156,8 @@ public class AdoptionController { @FXML void btnRefresh(ActionEvent event) { txtSearch.clear(); + cbStatusFilter.getSelectionModel().selectFirst(); + cbStoreFilter.getSelectionModel().selectFirst(); selectedCalendarDate = null; if (filteredAdoptions != null) filteredAdoptions.setPredicate(a -> true); tvAdoptions.getSortOrder().clear(); @@ -208,7 +231,7 @@ public class AdoptionController { } else { new Thread(() -> { try { - Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId(); + Long storeId = selectedStoreId(); List adoptions = AdoptionApi.getInstance().listAdoptions(filter, storeId); List adoptionList = adoptions.stream() .map(this::mapToAdoption) @@ -223,9 +246,7 @@ public class AdoptionController { .filter(d -> d != null) .collect(java.util.stream.Collectors.toSet()); calendarPane.setEventDates(dates); - if (selectedCalendarDate != null) { - filteredAdoptions.setPredicate(a -> a.getAdoptionDate().equals(selectedCalendarDate.toString())); - } + applyFilterPredicate(); }); } catch (Exception e) { Platform.runLater(() -> { @@ -307,6 +328,44 @@ public class AdoptionController { txtSearch.setText(""); } + private void loadStoreFilter() { + new Thread(() -> { + try { + List stores = DropdownApi.getInstance().getStores(); + DropdownOption allStores = new DropdownOption(); + allStores.setLabel("All Stores"); + Platform.runLater(() -> { + storeOptions.clear(); + storeOptions.addAll(stores); + java.util.List items = new java.util.ArrayList<>(); + items.add(allStores); + items.addAll(stores); + cbStoreFilter.setItems(FXCollections.observableArrayList(items)); + cbStoreFilter.getSelectionModel().selectFirst(); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException("AdoptionController.loadStoreFilter", e, "Loading store filter"); + } + }).start(); + } + + private Long selectedStoreId() { + if (!UserSession.getInstance().isAdmin()) return UserSession.getInstance().getStoreId(); + DropdownOption selected = cbStoreFilter.getValue(); + return (selected != null && selected.getId() != null) ? selected.getId() : null; + } + + private void applyFilterPredicate() { + String selectedStatus = cbStatusFilter.getValue(); + filteredAdoptions.setPredicate(a -> { + boolean dateMatch = selectedCalendarDate == null + || a.getAdoptionDate().equals(selectedCalendarDate.toString()); + boolean statusMatch = selectedStatus == null || selectedStatus.equals("All Statuses") + || a.getAdoptionStatus().equalsIgnoreCase(selectedStatus); + return dateMatch && statusMatch; + }); + } + private Adoption mapToAdoption(AdoptionResponse response) { return new Adoption( response.getAdoptionId().intValue(), 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 96461286..a5a40c39 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/AppointmentController.java @@ -16,7 +16,9 @@ import javafx.stage.Stage; import org.example.petshopdesktop.DTOs.AppointmentDTO; import org.example.petshopdesktop.api.dto.appointment.AppointmentResponse; +import org.example.petshopdesktop.api.dto.common.DropdownOption; import org.example.petshopdesktop.api.endpoints.AppointmentApi; +import org.example.petshopdesktop.api.endpoints.DropdownApi; import org.example.petshopdesktop.controllers.dialogcontrollers.AppointmentDialogController; import org.example.petshopdesktop.ui.CalendarPane; import org.example.petshopdesktop.util.ActivityLogger; @@ -52,6 +54,11 @@ public class AppointmentController { @FXML private TextField txtSearch; + @FXML private ComboBox cbStatusFilter; + @FXML private ComboBox cbStoreFilter; + + private final java.util.ArrayList storeOptions = new java.util.ArrayList<>(); + @FXML private CalendarPane calendarPane; private LocalDate selectedCalendarDate = null; @@ -83,6 +90,17 @@ public class AppointmentController { btnMyAppointments.setManaged(true); } + cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Booked", "Completed", "Missed", "Cancelled")); + cbStatusFilter.getSelectionModel().selectFirst(); + cbStatusFilter.valueProperty().addListener((obs, o, n) -> applyFilterPredicate()); + + if (UserSession.getInstance().isAdmin()) { + cbStoreFilter.setVisible(true); + cbStoreFilter.setManaged(true); + loadStoreFilter(); + } + cbStoreFilter.valueProperty().addListener((obs, o, n) -> loadAppointments()); + if (txtSearch != null) { txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n)); } @@ -105,7 +123,7 @@ public class AppointmentController { calendarPane.setOnDateSelected(date -> { selectedCalendarDate = date; - filtered.setPredicate(apt -> date == null || apt.getAppointmentDate().equals(date.toString())); + applyFilterPredicate(); }); } @@ -117,7 +135,7 @@ public class AppointmentController { private void loadAppointments(){ new Thread(() -> { try{ - Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId(); + Long storeId = selectedStoreId(); Long employeeId = btnMyAppointments.isSelected() ? UserSession.getInstance().getEmployeeId() : null; List responses = AppointmentApi.getInstance().listAppointments(null, storeId, employeeId); List appointmentDTOs = responses.stream() @@ -133,9 +151,7 @@ public class AppointmentController { .filter(d -> d != null) .collect(java.util.stream.Collectors.toSet()); calendarPane.setEventDates(dates); - if (selectedCalendarDate != null) { - filtered.setPredicate(apt -> apt.getAppointmentDate().equals(selectedCalendarDate.toString())); - } + applyFilterPredicate(); }); }catch(Exception e){ Platform.runLater(() -> { @@ -153,7 +169,7 @@ public class AppointmentController { String query = text == null || text.trim().isEmpty() ? null : text.trim(); new Thread(() -> { try { - Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId(); + Long storeId = selectedStoreId(); Long employeeId = btnMyAppointments.isSelected() ? UserSession.getInstance().getEmployeeId() : null; List responses = AppointmentApi.getInstance().listAppointments(query, storeId, employeeId); List appointmentDTOs = responses.stream() @@ -169,9 +185,7 @@ public class AppointmentController { .filter(d -> d != null) .collect(java.util.stream.Collectors.toSet()); calendarPane.setEventDates(dates); - if (selectedCalendarDate != null) { - filtered.setPredicate(apt -> apt.getAppointmentDate().equals(selectedCalendarDate.toString())); - } + applyFilterPredicate(); }); } catch (Exception e) { Platform.runLater(() -> { @@ -188,6 +202,8 @@ public class AppointmentController { @FXML void btnRefresh(ActionEvent event) { txtSearch.clear(); + cbStatusFilter.getSelectionModel().selectFirst(); + cbStoreFilter.getSelectionModel().selectFirst(); selectedCalendarDate = null; filtered.setPredicate(a -> true); tvAppointments.getSortOrder().clear(); @@ -291,6 +307,44 @@ public class AppointmentController { } } + private void loadStoreFilter() { + new Thread(() -> { + try { + List stores = DropdownApi.getInstance().getStores(); + DropdownOption allStores = new DropdownOption(); + allStores.setLabel("All Stores"); + Platform.runLater(() -> { + storeOptions.clear(); + storeOptions.addAll(stores); + java.util.List items = new java.util.ArrayList<>(); + items.add(allStores); + items.addAll(stores); + cbStoreFilter.setItems(FXCollections.observableArrayList(items)); + cbStoreFilter.getSelectionModel().selectFirst(); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException("AppointmentController.loadStoreFilter", e, "Loading store filter"); + } + }).start(); + } + + private Long selectedStoreId() { + if (!UserSession.getInstance().isAdmin()) return UserSession.getInstance().getStoreId(); + DropdownOption selected = cbStoreFilter.getValue(); + return (selected != null && selected.getId() != null) ? selected.getId() : null; + } + + private void applyFilterPredicate() { + String selectedStatus = cbStatusFilter.getValue(); + filtered.setPredicate(apt -> { + boolean dateMatch = selectedCalendarDate == null + || apt.getAppointmentDate().equals(selectedCalendarDate.toString()); + boolean statusMatch = selectedStatus == null || selectedStatus.equals("All Statuses") + || apt.getAppointmentStatus().equalsIgnoreCase(selectedStatus); + return dateMatch && statusMatch; + }); + } + private void showAlert(String title, String msg){ Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setTitle(title); 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 42866e75..a4e073f3 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/CustomerAccountsController.java @@ -49,6 +49,9 @@ public class CustomerAccountsController { @FXML private TextField txtSearchCustomer; + @FXML + private ComboBox cbStatusFilter; + @FXML private Button btnEditCustomer; @@ -82,6 +85,10 @@ public class CustomerAccountsController { btnEditCustomer.setDisable(newVal == null)); btnEditCustomer.setDisable(true); + cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Active", "Inactive")); + cbStatusFilter.getSelectionModel().selectFirst(); + cbStatusFilter.valueProperty().addListener((obs, o, n) -> applyCustomerFilter(txtSearchCustomer.getText())); + txtSearchCustomer.textProperty().addListener((obs, o, n) -> applyCustomerFilter(n)); refresh(); @@ -90,6 +97,7 @@ public class CustomerAccountsController { @FXML void btnRefreshClicked(ActionEvent event) { txtSearchCustomer.clear(); + cbStatusFilter.getSelectionModel().selectFirst(); TableViewSupport.clearSort(tvCustomers); refresh(); TableViewSupport.flashStatus(lblStatus, "Refreshed"); @@ -154,16 +162,19 @@ public class CustomerAccountsController { private void applyCustomerFilter(String text) { String q = text == null ? "" : text.trim().toLowerCase(); - if (q.isEmpty()) { - filteredCustomers.setPredicate(a -> true); - return; - } - filteredCustomers.setPredicate(a -> - safe(a.getUsername()).contains(q) - || safe(a.getFullName()).contains(q) - || safe(a.getEmail()).contains(q) - || safe(a.getPhone()).contains(q) - ); + String selectedStatus = cbStatusFilter.getValue(); + filteredCustomers.setPredicate(a -> { + boolean textMatch = q.isEmpty() + || safe(a.getUsername()).contains(q) + || safe(a.getFullName()).contains(q) + || safe(a.getEmail()).contains(q) + || safe(a.getPhone()).contains(q); + boolean active = Boolean.TRUE.equals(a.getActive()); + boolean statusMatch = selectedStatus == null || selectedStatus.equals("All Statuses") + || (selectedStatus.equals("Active") && active) + || (selectedStatus.equals("Inactive") && !active); + return textMatch && statusMatch; + }); } private static String safe(String v) { diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java index 1ab06a41..a724a367 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/InventoryController.java @@ -12,7 +12,9 @@ import javafx.scene.control.cell.PropertyValueFactory; import javafx.stage.Modality; import org.example.petshopdesktop.auth.UserSession; import javafx.stage.Stage; +import org.example.petshopdesktop.api.dto.common.DropdownOption; import org.example.petshopdesktop.api.dto.inventory.InventoryResponse; +import org.example.petshopdesktop.api.endpoints.DropdownApi; import org.example.petshopdesktop.api.endpoints.InventoryApi; import org.example.petshopdesktop.controllers.dialogcontrollers.InventoryDialogController; import org.example.petshopdesktop.models.Inventory; @@ -63,6 +65,11 @@ public class InventoryController { @FXML private TextField txtSearch; + @FXML + private ComboBox cbStoreFilter; + + private final java.util.ArrayList storeOptions = new java.util.ArrayList<>(); + private ObservableList data = FXCollections.observableArrayList(); //Determines if in add/edit mode @@ -81,6 +88,13 @@ public class InventoryController { colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity")); colStoreName.setCellValueFactory(new PropertyValueFactory<>("storeName")); + if (UserSession.getInstance().isAdmin()) { + cbStoreFilter.setVisible(true); + cbStoreFilter.setManaged(true); + loadStoreFilter(); + } + cbStoreFilter.valueProperty().addListener((obs, o, n) -> displayInventory()); + displayInventory(); TableViewSupport.installDoubleClickAction(tvInventory, selected -> openDialog(selected, "Edit")); @@ -108,6 +122,7 @@ public class InventoryController { @FXML void btnRefresh(ActionEvent event) { txtSearch.clear(); + cbStoreFilter.getSelectionModel().selectFirst(); tvInventory.getSortOrder().clear(); displayInventory(); TableViewSupport.flashStatus(lblStatus, "Refreshed"); @@ -172,7 +187,7 @@ public class InventoryController { } else { new Thread(() -> { try { - Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId(); + Long storeId = selectedStoreId(); List inventories = InventoryApi.getInstance().listInventory(filter, storeId); List inventoryList = inventories.stream() .map(this::mapToInventory) @@ -198,7 +213,7 @@ public class InventoryController { private void displayInventory() { new Thread(() -> { try { - Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId(); + Long storeId = selectedStoreId(); List inventories = InventoryApi.getInstance().listInventory(null, storeId); List inventoryList = inventories.stream() .map(this::mapToInventory) @@ -253,6 +268,33 @@ public class InventoryController { txtSearch.setText(""); } + private void loadStoreFilter() { + new Thread(() -> { + try { + List stores = DropdownApi.getInstance().getStores(); + DropdownOption allStores = new DropdownOption(); + allStores.setLabel("All Stores"); + Platform.runLater(() -> { + storeOptions.clear(); + storeOptions.addAll(stores); + java.util.List items = new java.util.ArrayList<>(); + items.add(allStores); + items.addAll(stores); + cbStoreFilter.setItems(FXCollections.observableArrayList(items)); + cbStoreFilter.getSelectionModel().selectFirst(); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException("InventoryController.loadStoreFilter", e, "Loading store filter"); + } + }).start(); + } + + private Long selectedStoreId() { + if (!UserSession.getInstance().isAdmin()) return UserSession.getInstance().getStoreId(); + DropdownOption selected = cbStoreFilter.getValue(); + return (selected != null && selected.getId() != null) ? selected.getId() : null; + } + private Inventory mapToInventory(InventoryResponse response) { return new Inventory( response.getInventoryId().intValue(), diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java index 997899d3..11d8cceb 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java @@ -113,6 +113,9 @@ public class MainLayoutController { @FXML private Button btnRemoveAvatar; + @FXML + private Button btnRefreshAvatar; + @FXML private Label lblUsername; @@ -253,6 +256,29 @@ public class MainLayoutController { } } + @FXML + void btnRefreshAvatarClicked(ActionEvent event) { + btnRefreshAvatar.setDisable(true); + new Thread(() -> { + try { + UserInfoResponse userInfo = AuthApi.getInstance().getCurrentUser(); + String displayName = userInfo.getFullName() == null || userInfo.getFullName().isBlank() + ? UserSession.getInstance().getUsername() + : userInfo.getFullName(); + Image avatarImage = loadAvatarImage(userInfo.getAvatarUrl()); + Platform.runLater(() -> { + UserSession.getInstance().setAvatarUrl(userInfo.getAvatarUrl()); + renderAvatar(displayName, avatarImage); + btnRemoveAvatar.setDisable(userInfo.getAvatarUrl() == null || userInfo.getAvatarUrl().isBlank()); + btnRefreshAvatar.setDisable(false); + }); + } catch (Exception e) { + Platform.runLater(() -> btnRefreshAvatar.setDisable(false)); + ActivityLogger.getInstance().logException("MainLayoutController.btnRefreshAvatarClicked", e, "Refreshing avatar"); + } + }).start(); + } + @FXML void btnRemoveAvatarClicked(ActionEvent event) { try { diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java index f92799cc..35201c45 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/PetController.java @@ -86,18 +86,20 @@ public class PetController { @FXML private ComboBox cbStatusFilter; + @FXML + private ComboBox cbStoreFilter; + + private final java.util.ArrayList storeOptions = new java.util.ArrayList<>(); + @FXML private TextField txtSearch; @FXML void btnRefresh(ActionEvent event) { txtSearch.clear(); - if (cbSpeciesFilter != null) { - cbSpeciesFilter.getSelectionModel().selectFirst(); - } - if (cbStatusFilter != null) { - cbStatusFilter.getSelectionModel().selectFirst(); - } + if (cbSpeciesFilter != null) cbSpeciesFilter.getSelectionModel().selectFirst(); + if (cbStatusFilter != null) cbStatusFilter.getSelectionModel().selectFirst(); + cbStoreFilter.getSelectionModel().selectFirst(); tvPets.getSortOrder().clear(); displayPets(); TableViewSupport.flashStatus(lblStatus, "Refreshed"); @@ -194,6 +196,13 @@ public class PetController { cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Available", "Adopted", "Owned", "Pending")); cbStatusFilter.getSelectionModel().selectFirst(); + if (UserSession.getInstance().isAdmin()) { + cbStoreFilter.setVisible(true); + cbStoreFilter.setManaged(true); + loadStoreFilter(); + } + cbStoreFilter.valueProperty().addListener((obs, o, n) -> applyFilters()); + displayPets(); TableViewSupport.installDoubleClickAction(tvPets, selected -> openDialog(selected, "Edit")); @@ -229,7 +238,7 @@ public class PetController { } else { new Thread(() -> { try { - Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId(); + Long storeId = selectedStoreId(); List pets = PetApi.getInstance().listPets(filter, species, status, storeId); List petList = pets.stream() .map(this::mapToPet) @@ -255,7 +264,7 @@ public class PetController { private void displayPets() { new Thread(() -> { try { - Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId(); + Long storeId = selectedStoreId(); List pets = PetApi.getInstance().listPets(null, selectedSpecies(), selectedStatus(), storeId); List petList = pets.stream() .map(this::mapToPet) @@ -281,6 +290,33 @@ public class PetController { displayFilteredPet(txtSearch.getText()); } + private void loadStoreFilter() { + new Thread(() -> { + try { + List stores = DropdownApi.getInstance().getStores(); + DropdownOption allStores = new DropdownOption(); + allStores.setLabel("All Stores"); + Platform.runLater(() -> { + storeOptions.clear(); + storeOptions.addAll(stores); + java.util.List items = new java.util.ArrayList<>(); + items.add(allStores); + items.addAll(stores); + cbStoreFilter.setItems(FXCollections.observableArrayList(items)); + cbStoreFilter.getSelectionModel().selectFirst(); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException("PetController.loadStoreFilter", e, "Loading store filter"); + } + }).start(); + } + + private Long selectedStoreId() { + if (!UserSession.getInstance().isAdmin()) return UserSession.getInstance().getStoreId(); + DropdownOption selected = cbStoreFilter.getValue(); + return (selected != null && selected.getId() != null) ? selected.getId() : null; + } + private void loadSpeciesFilter() { new Thread(() -> { try { diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java index cdf014cf..39ed6b51 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/SaleController.java @@ -146,6 +146,20 @@ public class SaleController { @FXML private TextField txtSearch; + @FXML + private ComboBox cbFilterPayment; + + @FXML + private ComboBox cbFilterRefundStatus; + + @FXML + private ComboBox cbFilterCustomer; + + @FXML + private ComboBox cbStoreFilter; + + private final java.util.ArrayList storeOptions = new java.util.ArrayList<>(); + @FXML private ComboBox cbCustomer; @@ -249,6 +263,24 @@ public class SaleController { TableViewSupport.bindSortedItems(tvSales, filteredSales); TableViewSupport.installDoubleClickAction(tvSales, selected -> openSaleDetailDialog(selected.getSaleId())); + if (UserSession.getInstance().isAdmin()) { + cbStoreFilter.setVisible(true); + cbStoreFilter.setManaged(true); + loadStoreFilter(); + } + cbStoreFilter.valueProperty().addListener((obs, o, n) -> refreshSales(false)); + + cbFilterPayment.setItems(FXCollections.observableArrayList("All Payments", "Cash", "Card")); + cbFilterPayment.getSelectionModel().selectFirst(); + cbFilterPayment.valueProperty().addListener((obs, o, n) -> applySalesFilter(txtSearch.getText())); + + cbFilterRefundStatus.setItems(FXCollections.observableArrayList("All Status", "Sales Only", "Refunds Only")); + cbFilterRefundStatus.getSelectionModel().selectFirst(); + cbFilterRefundStatus.valueProperty().addListener((obs, o, n) -> applySalesFilter(txtSearch.getText())); + + loadCustomerFilter(); + cbFilterCustomer.valueProperty().addListener((obs, o, n) -> applySalesFilter(txtSearch.getText())); + txtSearch.textProperty().addListener((obs, oldVal, newVal) -> applySalesFilter(newVal)); tvSales.widthProperty().addListener((obs, oldWidth, newWidth) -> updateSalesColumnWidths(newWidth.doubleValue())); tvCart.widthProperty().addListener((obs, oldWidth, newWidth) -> updateCartColumnWidths(newWidth.doubleValue())); @@ -404,8 +436,7 @@ public class SaleController { Task> task = new Task>() { @Override protected List call() throws Exception { - Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId(); - List sales = SaleApi.getInstance().listAllSales(null, storeId); + List sales = SaleApi.getInstance().listAllSales(null, selectedStoreId()); sales.sort(Comparator.comparing(SaleResponse::getSaleDate, Comparator.nullsLast(Comparator.reverseOrder())) .thenComparing(SaleResponse::getSaleId, Comparator.nullsLast(Comparator.reverseOrder()))); List lineItems = new ArrayList<>(); @@ -465,6 +496,10 @@ public class SaleController { @FXML void btnRefresh(ActionEvent event) { txtSearch.clear(); + cbFilterPayment.getSelectionModel().selectFirst(); + cbFilterRefundStatus.getSelectionModel().selectFirst(); + cbFilterCustomer.getSelectionModel().selectFirst(); + cbStoreFilter.getSelectionModel().selectFirst(); TableViewSupport.clearSort(tvSales); refreshSales(false); TableViewSupport.flashStatus(lblStatus, "Refreshed"); @@ -942,25 +977,83 @@ public class SaleController { private void applySalesFilter(String filter) { String f = filter == null ? "" : filter.trim().toLowerCase(); - if (f.isEmpty()) { - filteredSales.setPredicate(s -> true); - return; - } + String selectedPayment = cbFilterPayment.getValue(); + String selectedStatus = cbFilterRefundStatus.getValue(); + DropdownOption selectedCustomer = cbFilterCustomer.getValue(); - filteredSales.setPredicate(s -> - String.valueOf(s.getSaleId()).contains(f) + filteredSales.setPredicate(s -> { + boolean textMatch = f.isEmpty() + || String.valueOf(s.getSaleId()).contains(f) || safe(s.getSaleDate()).contains(f) || safe(s.getEmployeeName()).contains(f) || safe(s.getCustomerName()).contains(f) || safe(s.getItemName()).contains(f) - || safe(s.getPaymentMethod()).contains(f) - ); + || safe(s.getPaymentMethod()).contains(f); + + boolean paymentMatch = selectedPayment == null || selectedPayment.equals("All Payments") + || safe(s.getPaymentMethod()).equals(selectedPayment.toLowerCase()); + + boolean refundMatch = selectedStatus == null || selectedStatus.equals("All Status") + || (selectedStatus.equals("Sales Only") && !s.isRefund()) + || (selectedStatus.equals("Refunds Only") && s.isRefund()); + + boolean customerMatch = selectedCustomer == null || selectedCustomer.getId() == null + || safe(s.getCustomerName()).equals(safe(selectedCustomer.getLabel())); + + return textMatch && paymentMatch && refundMatch && customerMatch; + }); } private static String safe(String v) { return v == null ? "" : v.toLowerCase(); } + private void loadCustomerFilter() { + new Thread(() -> { + try { + List customers = DropdownApi.getInstance().getCustomers(); + DropdownOption allCustomers = new DropdownOption(); + allCustomers.setLabel("All Customers"); + Platform.runLater(() -> { + java.util.List items = new java.util.ArrayList<>(); + items.add(allCustomers); + items.addAll(customers); + cbFilterCustomer.setItems(FXCollections.observableArrayList(items)); + cbFilterCustomer.getSelectionModel().selectFirst(); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException("SaleController.loadCustomerFilter", e, "Loading customer filter"); + } + }).start(); + } + + private void loadStoreFilter() { + new Thread(() -> { + try { + List stores = DropdownApi.getInstance().getStores(); + DropdownOption allStores = new DropdownOption(); + allStores.setLabel("All Stores"); + Platform.runLater(() -> { + storeOptions.clear(); + storeOptions.addAll(stores); + java.util.List items = new java.util.ArrayList<>(); + items.add(allStores); + items.addAll(stores); + cbStoreFilter.setItems(FXCollections.observableArrayList(items)); + cbStoreFilter.getSelectionModel().selectFirst(); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException("SaleController.loadStoreFilter", e, "Loading store filter"); + } + }).start(); + } + + private Long selectedStoreId() { + if (!UserSession.getInstance().isAdmin()) return UserSession.getInstance().getStoreId(); + DropdownOption selected = cbStoreFilter.getValue(); + return (selected != null && selected.getId() != null) ? selected.getId() : null; + } + private void showError(String title, String message) { Alert alert = new Alert(Alert.AlertType.ERROR); alert.setTitle(title); 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 565b339e..a91d6080 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java @@ -34,6 +34,8 @@ public class StaffAccountsController { @FXML private TableColumn colStatus; @FXML private TableColumn colCreated; @FXML private TextField txtSearch; + @FXML private ComboBox cbRoleFilter; + @FXML private ComboBox cbStatusFilter; @FXML private Button btnRefresh; @FXML private Button btnCreateAccount; @FXML private Button btnEditAccount; @@ -66,6 +68,14 @@ public class StaffAccountsController { btnEditAccount.setDisable(newVal == null)); btnEditAccount.setDisable(true); + cbRoleFilter.setItems(FXCollections.observableArrayList("All Roles", "Admin", "Staff")); + cbRoleFilter.getSelectionModel().selectFirst(); + cbRoleFilter.valueProperty().addListener((obs, o, n) -> applyStaffFilter(txtSearch.getText())); + + cbStatusFilter.setItems(FXCollections.observableArrayList("All Statuses", "Active", "Inactive")); + cbStatusFilter.getSelectionModel().selectFirst(); + cbStatusFilter.valueProperty().addListener((obs, o, n) -> applyStaffFilter(txtSearch.getText())); + txtSearch.textProperty().addListener((obs, o, n) -> applyStaffFilter(n)); refresh(); @@ -74,6 +84,8 @@ public class StaffAccountsController { @FXML void btnRefreshClicked(ActionEvent event) { txtSearch.clear(); + cbRoleFilter.getSelectionModel().selectFirst(); + cbStatusFilter.getSelectionModel().selectFirst(); TableViewSupport.clearSort(tvStaff); refresh(); TableViewSupport.flashStatus(lblStatus, "Refreshed"); @@ -166,18 +178,24 @@ public class StaffAccountsController { private void applyStaffFilter(String text) { String q = text == null ? "" : text.trim().toLowerCase(); - if (q.isEmpty()) { - filteredStaff.setPredicate(a -> true); - return; - } - filteredStaff.setPredicate(a -> - safe(a.getUsername()).contains(q) - || safe(a.getFullName()).contains(q) - || safe(a.getEmail()).contains(q) - || safe(a.getPhone()).contains(q) - || safe(a.getRole()).contains(q) - || safe(a.getStaffRole()).contains(q) - ); + String selectedRole = cbRoleFilter.getValue(); + String selectedStatus = cbStatusFilter.getValue(); + filteredStaff.setPredicate(a -> { + boolean textMatch = q.isEmpty() + || safe(a.getUsername()).contains(q) + || safe(a.getFullName()).contains(q) + || safe(a.getEmail()).contains(q) + || safe(a.getPhone()).contains(q) + || safe(a.getRole()).contains(q) + || safe(a.getStaffRole()).contains(q); + boolean active = Boolean.TRUE.equals(a.getActive()); + boolean statusMatch = selectedStatus == null || selectedStatus.equals("All Statuses") + || (selectedStatus.equals("Active") && active) + || (selectedStatus.equals("Inactive") && !active); + boolean roleMatch = selectedRole == null || selectedRole.equals("All Roles") + || safe(a.getRole()).equals(selectedRole.toLowerCase()); + return textMatch && statusMatch && roleMatch; + }); } private static String safe(String v) { diff --git a/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml index 7192ea69..674020aa 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml @@ -59,14 +59,26 @@ - + + + + + + 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 @@ +