added filters to desktop

This commit is contained in:
Alex
2026-04-14 04:29:28 -06:00
parent f623c17071
commit eefb8de460
17 changed files with 445 additions and 65 deletions

View File

@@ -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();
}

View File

@@ -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<String> cbStatusFilter;
@FXML
private ComboBox<DropdownOption> cbStoreFilter;
private final java.util.ArrayList<DropdownOption> 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<AdoptionResponse> adoptions = AdoptionApi.getInstance().listAdoptions(filter, storeId);
List<Adoption> 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<DropdownOption> stores = DropdownApi.getInstance().getStores();
DropdownOption allStores = new DropdownOption();
allStores.setLabel("All Stores");
Platform.runLater(() -> {
storeOptions.clear();
storeOptions.addAll(stores);
java.util.List<DropdownOption> 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(),

View File

@@ -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<String> cbStatusFilter;
@FXML private ComboBox<DropdownOption> cbStoreFilter;
private final java.util.ArrayList<DropdownOption> 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<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(null, storeId, employeeId);
List<AppointmentDTO> 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<AppointmentResponse> responses = AppointmentApi.getInstance().listAppointments(query, storeId, employeeId);
List<AppointmentDTO> 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<DropdownOption> stores = DropdownApi.getInstance().getStores();
DropdownOption allStores = new DropdownOption();
allStores.setLabel("All Stores");
Platform.runLater(() -> {
storeOptions.clear();
storeOptions.addAll(stores);
java.util.List<DropdownOption> 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);

View File

@@ -49,6 +49,9 @@ public class CustomerAccountsController {
@FXML
private TextField txtSearchCustomer;
@FXML
private ComboBox<String> 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) {

View File

@@ -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<DropdownOption> cbStoreFilter;
private final java.util.ArrayList<DropdownOption> storeOptions = new java.util.ArrayList<>();
private ObservableList<Inventory> 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<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(filter, storeId);
List<Inventory> 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<InventoryResponse> inventories = InventoryApi.getInstance().listInventory(null, storeId);
List<Inventory> inventoryList = inventories.stream()
.map(this::mapToInventory)
@@ -253,6 +268,33 @@ public class InventoryController {
txtSearch.setText("");
}
private void loadStoreFilter() {
new Thread(() -> {
try {
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
DropdownOption allStores = new DropdownOption();
allStores.setLabel("All Stores");
Platform.runLater(() -> {
storeOptions.clear();
storeOptions.addAll(stores);
java.util.List<DropdownOption> 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(),

View File

@@ -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 {

View File

@@ -86,18 +86,20 @@ public class PetController {
@FXML
private ComboBox<String> cbStatusFilter;
@FXML
private ComboBox<DropdownOption> cbStoreFilter;
private final java.util.ArrayList<DropdownOption> 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<PetResponse> pets = PetApi.getInstance().listPets(filter, species, status, storeId);
List<Pet> 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<PetResponse> pets = PetApi.getInstance().listPets(null, selectedSpecies(), selectedStatus(), storeId);
List<Pet> petList = pets.stream()
.map(this::mapToPet)
@@ -281,6 +290,33 @@ public class PetController {
displayFilteredPet(txtSearch.getText());
}
private void loadStoreFilter() {
new Thread(() -> {
try {
List<DropdownOption> stores = DropdownApi.getInstance().getStores();
DropdownOption allStores = new DropdownOption();
allStores.setLabel("All Stores");
Platform.runLater(() -> {
storeOptions.clear();
storeOptions.addAll(stores);
java.util.List<DropdownOption> 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 {

View File

@@ -146,6 +146,20 @@ public class SaleController {
@FXML
private TextField txtSearch;
@FXML
private ComboBox<String> cbFilterPayment;
@FXML
private ComboBox<String> cbFilterRefundStatus;
@FXML
private ComboBox<DropdownOption> cbFilterCustomer;
@FXML
private ComboBox<DropdownOption> cbStoreFilter;
private final java.util.ArrayList<DropdownOption> storeOptions = new java.util.ArrayList<>();
@FXML
private ComboBox<DropdownOption> 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<List<SaleLineItem>> task = new Task<List<SaleLineItem>>() {
@Override
protected List<SaleLineItem> call() throws Exception {
Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId();
List<SaleResponse> sales = SaleApi.getInstance().listAllSales(null, storeId);
List<SaleResponse> sales = SaleApi.getInstance().listAllSales(null, selectedStoreId());
sales.sort(Comparator.comparing(SaleResponse::getSaleDate, Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(SaleResponse::getSaleId, Comparator.nullsLast(Comparator.reverseOrder())));
List<SaleLineItem> 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<DropdownOption> customers = DropdownApi.getInstance().getCustomers();
DropdownOption allCustomers = new DropdownOption();
allCustomers.setLabel("All Customers");
Platform.runLater(() -> {
java.util.List<DropdownOption> 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<DropdownOption> stores = DropdownApi.getInstance().getStores();
DropdownOption allStores = new DropdownOption();
allStores.setLabel("All Stores");
Platform.runLater(() -> {
storeOptions.clear();
storeOptions.addAll(stores);
java.util.List<DropdownOption> 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);

View File

@@ -34,6 +34,8 @@ public class StaffAccountsController {
@FXML private TableColumn<EmployeeResponse, String> colStatus;
@FXML private TableColumn<EmployeeResponse, Object> colCreated;
@FXML private TextField txtSearch;
@FXML private ComboBox<String> cbRoleFilter;
@FXML private ComboBox<String> 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) {

View File

@@ -59,14 +59,26 @@
<Insets bottom="6.0" left="10.0" right="10.0" top="6.0" />
</padding>
</Button>
<Button fx:id="btnRemoveAvatar" mnemonicParsing="false" onAction="#btnRemoveAvatarClicked" style="-fx-background-color: transparent; -fx-border-color: rgba(226,232,240,0.35); -fx-border-radius: 8; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Remove" textFill="#cbd5e1">
<font>
<Font name="System" size="11.0" />
</font>
<padding>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0" />
</padding>
</Button>
<HBox spacing="6.0">
<children>
<Button fx:id="btnRemoveAvatar" mnemonicParsing="false" onAction="#btnRemoveAvatarClicked" style="-fx-background-color: transparent; -fx-border-color: rgba(226,232,240,0.35); -fx-border-radius: 8; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Remove" textFill="#cbd5e1">
<font>
<Font name="System" size="11.0" />
</font>
<padding>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0" />
</padding>
</Button>
<Button fx:id="btnRefreshAvatar" mnemonicParsing="false" onAction="#btnRefreshAvatarClicked" style="-fx-background-color: transparent; -fx-border-color: rgba(226,232,240,0.35); -fx-border-radius: 8; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="↻" textFill="#cbd5e1">
<font>
<Font name="System" size="13.0" />
</font>
<padding>
<Insets bottom="5.0" left="8.0" right="8.0" top="5.0" />
</padding>
</Button>
</children>
</HBox>
</children>
</VBox>
</children>

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
@@ -72,6 +73,8 @@
<Font size="15.0" />
</font>
</TextField>
<ComboBox fx:id="cbStatusFilter" prefWidth="150.0" promptText="All Statuses" />
<ComboBox fx:id="cbStoreFilter" prefWidth="150.0" promptText="All Stores" visible="false" managed="false" />
</children>
</HBox>
<CalendarPane fx:id="calendarPane" maxWidth="Infinity" />

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ToggleButton?>
<?import javafx.scene.control.TableColumn?>
@@ -81,6 +82,8 @@
<Font size="15.0" />
</font>
</TextField>
<ComboBox fx:id="cbStatusFilter" prefWidth="150.0" promptText="All Statuses" />
<ComboBox fx:id="cbStoreFilter" prefWidth="150.0" promptText="All Stores" visible="false" managed="false" />
</children>
</HBox>
<CalendarPane fx:id="calendarPane" maxWidth="Infinity" />

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
@@ -62,6 +63,7 @@
<Font size="15.0" />
</font>
</TextField>
<ComboBox fx:id="cbStatusFilter" prefWidth="140.0" promptText="All Statuses" />
</children>
</HBox>

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
@@ -71,6 +72,7 @@
<Font size="15.0" />
</font>
</TextField>
<ComboBox fx:id="cbStoreFilter" prefWidth="150.0" promptText="All Stores" visible="false" managed="false" />
</children>
</HBox>
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true">

View File

@@ -75,6 +75,7 @@
</TextField>
<ComboBox fx:id="cbSpeciesFilter" prefWidth="150.0" promptText="Species" />
<ComboBox fx:id="cbStatusFilter" prefWidth="150.0" promptText="Status" />
<ComboBox fx:id="cbStoreFilter" prefWidth="150.0" promptText="All Stores" visible="false" managed="false" />
</children>
</HBox>
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true">

View File

@@ -210,6 +210,10 @@
<Font size="15.0" />
</font>
</TextField>
<ComboBox fx:id="cbFilterPayment" prefWidth="140.0" promptText="All Payments" />
<ComboBox fx:id="cbFilterRefundStatus" prefWidth="140.0" promptText="All Status" />
<ComboBox fx:id="cbFilterCustomer" prefWidth="160.0" promptText="All Customers" />
<ComboBox fx:id="cbStoreFilter" prefWidth="150.0" promptText="All Stores" visible="false" managed="false" />
</children>
</HBox>
<TableView fx:id="tvSales" prefHeight="270.0" style="-fx-background-color: white; -fx-background-radius: 12; -fx-padding: 2;" VBox.vgrow="ALWAYS">

View File

@@ -2,6 +2,7 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
@@ -70,6 +71,8 @@
<Font size="15.0" />
</font>
</TextField>
<ComboBox fx:id="cbRoleFilter" prefWidth="120.0" promptText="All Roles" />
<ComboBox fx:id="cbStatusFilter" prefWidth="130.0" promptText="All Statuses" />
</children>
</HBox>