added filter to analytics desktop
This commit is contained in:
@@ -1,19 +1,22 @@
|
||||
package org.example.petshopdesktop.controllers;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.chart.*;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.DatePicker;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DailySales;
|
||||
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
|
||||
import org.example.petshopdesktop.api.dto.analytics.TopProduct;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleItemResponse;
|
||||
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.AnalyticsApi;
|
||||
import org.example.petshopdesktop.api.endpoints.SaleApi;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
@@ -74,6 +77,32 @@ public class AnalyticsController {
|
||||
@FXML
|
||||
private TabPane tabPane;
|
||||
|
||||
@FXML
|
||||
private Button btnToggleFilters;
|
||||
|
||||
@FXML
|
||||
private Label lblFilterSummary;
|
||||
|
||||
@FXML
|
||||
private VBox vbFilterContent;
|
||||
|
||||
@FXML
|
||||
private DatePicker dpStartDate;
|
||||
|
||||
@FXML
|
||||
private DatePicker dpEndDate;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbPaymentFilter;
|
||||
|
||||
@FXML
|
||||
private ComboBox<String> cbTopN;
|
||||
|
||||
private List<SaleResponse> cachedSales = new ArrayList<>();
|
||||
private FilterState currentFilter = new FilterState();
|
||||
|
||||
private static final int[] TOP_N_VALUES = {5, 10, 15, 20};
|
||||
|
||||
private static final String SALES_COLOR = "#ff6b35";
|
||||
private static final String REVENUE_COLOR = "#4ecdc4";
|
||||
private static final String QUANTITY_COLOR = "#ff9f1c";
|
||||
@@ -88,13 +117,15 @@ public class AnalyticsController {
|
||||
@FXML
|
||||
public void initialize() {
|
||||
configureCharts();
|
||||
if (tabPane != null) {
|
||||
tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab) -> {
|
||||
if (oldTab != newTab && newTab != null) {
|
||||
loadAnalyticsData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cbTopN.setItems(FXCollections.observableArrayList("Top 5", "Top 10", "Top 15", "Top 20"));
|
||||
cbTopN.getSelectionModel().selectFirst();
|
||||
|
||||
cbPaymentFilter.setItems(FXCollections.observableArrayList("All"));
|
||||
cbPaymentFilter.getSelectionModel().selectFirst();
|
||||
|
||||
lblFilterSummary.setText("All time");
|
||||
|
||||
loadAnalyticsData();
|
||||
}
|
||||
|
||||
@@ -142,15 +173,42 @@ public class AnalyticsController {
|
||||
|
||||
private void loadAnalyticsData() {
|
||||
lblError.setVisible(false);
|
||||
if (lblStatus != null) {
|
||||
lblStatus.setVisible(false);
|
||||
}
|
||||
btnRefresh.setDisable(true);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
|
||||
|
||||
Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId();
|
||||
List<SaleResponse> sales = SaleApi.getInstance().listAllSales(null, storeId);
|
||||
Platform.runLater(() -> {
|
||||
cachedSales = sales;
|
||||
derivePaymentMethods();
|
||||
applyCurrentFilter();
|
||||
btnRefresh.setDisable(false);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading sales data for analytics");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
lblError.setVisible(true);
|
||||
btnRefresh.setDisable(false);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void applyCurrentFilter() {
|
||||
try {
|
||||
List<SaleResponse> filtered = filterSales(cachedSales, currentFilter);
|
||||
String start = currentFilter.startDate.isEmpty() ? LocalDate.now().minusDays(6).toString() : currentFilter.startDate;
|
||||
String end = currentFilter.endDate.isEmpty() ? LocalDate.now().toString() : currentFilter.endDate;
|
||||
|
||||
DashboardResponse dashboard = new DashboardResponse();
|
||||
dashboard.setSalesSummary(buildSalesSummary(filtered));
|
||||
dashboard.setDailySales(buildDailySalesBetween(filtered, start, end));
|
||||
dashboard.setTopProducts(buildTopProducts(filtered, currentFilter.topN > 0 ? currentFilter.topN : 5));
|
||||
dashboard.setPaymentMethods(buildPaymentMethods(filtered));
|
||||
dashboard.setEmployeePerformance(buildEmployeePerformance(filtered));
|
||||
|
||||
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||
loadSummaryData(dashboard);
|
||||
loadSalesOverTime(dashboard);
|
||||
@@ -159,45 +217,61 @@ public class AnalyticsController {
|
||||
loadPaymentMethodDistribution(dashboard);
|
||||
loadEmployeePerformance(dashboard, isAdmin);
|
||||
applyRoleVisibility(isAdmin);
|
||||
updateFilterSummary();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.applyCurrentFilter", e, "Computing analytics from filtered sales");
|
||||
lblError.setText("Error computing analytics. Please try again.");
|
||||
lblError.setVisible(true);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
if (UserSession.getInstance().isStaff()) {
|
||||
try {
|
||||
DashboardResponse fallback = buildStaffFallbackDashboard();
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
loadSummaryData(fallback);
|
||||
loadSalesOverTime(fallback);
|
||||
loadTopProductsByRevenue(fallback);
|
||||
loadTopProductsByQuantity(fallback);
|
||||
loadPaymentMethodDistribution(fallback);
|
||||
loadEmployeePerformance(fallback, false);
|
||||
applyRoleVisibility(false);
|
||||
lblError.setVisible(false);
|
||||
} catch (Exception inner) {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", inner, "Rendering fallback analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
lblError.setVisible(true);
|
||||
}
|
||||
});
|
||||
return;
|
||||
} catch (Exception fallbackError) {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", fallbackError, "Loading fallback analytics data");
|
||||
}
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||
lblError.setText("Error loading analytics data. Please try again.");
|
||||
lblError.setVisible(true);
|
||||
});
|
||||
private List<SaleResponse> filterSales(List<SaleResponse> sales, FilterState filter) {
|
||||
return sales.stream().filter(sale -> {
|
||||
String date = sale.getSaleDate() != null ? sale.getSaleDate().toLocalDate().toString() : "";
|
||||
if (!filter.startDate.isEmpty() && !date.isEmpty() && date.compareTo(filter.startDate) < 0) return false;
|
||||
if (!filter.endDate.isEmpty() && !date.isEmpty() && date.compareTo(filter.endDate) > 0) return false;
|
||||
if (!filter.paymentMethod.equals("All") && !filter.paymentMethod.isEmpty()) {
|
||||
if (!filter.paymentMethod.equalsIgnoreCase(sale.getPaymentMethod())) return false;
|
||||
}
|
||||
}).start();
|
||||
return true;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void derivePaymentMethods() {
|
||||
Set<String> methods = new TreeSet<>();
|
||||
for (SaleResponse s : cachedSales) {
|
||||
if (s.getPaymentMethod() != null && !s.getPaymentMethod().isEmpty()) {
|
||||
methods.add(s.getPaymentMethod());
|
||||
}
|
||||
}
|
||||
List<String> items = new ArrayList<>();
|
||||
items.add("All");
|
||||
items.addAll(methods);
|
||||
String current = cbPaymentFilter.getValue();
|
||||
cbPaymentFilter.setItems(FXCollections.observableArrayList(items));
|
||||
if (current != null && items.contains(current)) {
|
||||
cbPaymentFilter.setValue(current);
|
||||
} else {
|
||||
cbPaymentFilter.getSelectionModel().selectFirst();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFilterSummary() {
|
||||
String start = currentFilter.startDate;
|
||||
String end = currentFilter.endDate;
|
||||
if (start.isEmpty() && end.isEmpty()) {
|
||||
lblFilterSummary.setText("All time");
|
||||
} else if (start.isEmpty()) {
|
||||
lblFilterSummary.setText("Up to " + shortDate(end));
|
||||
} else if (end.isEmpty()) {
|
||||
lblFilterSummary.setText("From " + shortDate(start));
|
||||
} else {
|
||||
lblFilterSummary.setText(shortDate(start) + " – " + shortDate(end));
|
||||
}
|
||||
}
|
||||
|
||||
private String shortDate(String date) {
|
||||
return (date != null && date.length() >= 10) ? date.substring(5) : date;
|
||||
}
|
||||
|
||||
private void loadSummaryData(DashboardResponse dashboard) throws Exception {
|
||||
@@ -326,26 +400,23 @@ public class AnalyticsController {
|
||||
}
|
||||
}
|
||||
|
||||
private DashboardResponse buildStaffFallbackDashboard() throws Exception {
|
||||
Long storeId = UserSession.getInstance().isAdmin() ? null : UserSession.getInstance().getStoreId();
|
||||
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null, storeId);
|
||||
String employeeName = UserSession.getInstance().getEmployeeName();
|
||||
if (employeeName == null || employeeName.isBlank()) {
|
||||
employeeName = UserSession.getInstance().getUsername();
|
||||
private List<DashboardResponse.EmployeePerformanceData> buildEmployeePerformance(List<SaleResponse> sales) {
|
||||
Map<String, BigDecimal> empRevenue = new LinkedHashMap<>();
|
||||
for (SaleResponse sale : sales) {
|
||||
if (Boolean.TRUE.equals(sale.getIsRefund())) continue;
|
||||
String emp = sale.getEmployeeName() != null ? sale.getEmployeeName() : "Unknown";
|
||||
BigDecimal amount = sale.getTotalAmount() != null ? sale.getTotalAmount() : BigDecimal.ZERO;
|
||||
empRevenue.merge(emp, amount, BigDecimal::add);
|
||||
}
|
||||
final String employeeNameFilter = employeeName;
|
||||
|
||||
List<SaleResponse> personalSales = sales.stream()
|
||||
.filter(sale -> sale.getEmployeeName() != null && sale.getEmployeeName().equalsIgnoreCase(employeeNameFilter))
|
||||
.toList();
|
||||
|
||||
DashboardResponse dashboard = new DashboardResponse();
|
||||
dashboard.setSalesSummary(buildSalesSummary(personalSales));
|
||||
dashboard.setDailySales(buildDailySales(personalSales, 30));
|
||||
dashboard.setTopProducts(buildTopProducts(personalSales, 10));
|
||||
dashboard.setPaymentMethods(buildPaymentMethods(personalSales));
|
||||
dashboard.setEmployeePerformance(List.of(new DashboardResponse.EmployeePerformanceData()));
|
||||
return dashboard;
|
||||
return empRevenue.entrySet().stream()
|
||||
.sorted((a, b) -> b.getValue().compareTo(a.getValue()))
|
||||
.map(e -> {
|
||||
DashboardResponse.EmployeePerformanceData d = new DashboardResponse.EmployeePerformanceData();
|
||||
d.setEmployeeName(e.getKey());
|
||||
d.setRevenue(e.getValue());
|
||||
return d;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private DashboardResponse.SalesSummary buildSalesSummary(List<SaleResponse> sales) {
|
||||
@@ -379,21 +450,26 @@ public class AnalyticsController {
|
||||
return summary;
|
||||
}
|
||||
|
||||
private List<DailySales> buildDailySales(List<SaleResponse> sales, int days) {
|
||||
private List<DailySales> buildDailySalesBetween(List<SaleResponse> sales, String startStr, String endStr) {
|
||||
Map<LocalDate, DailySales> daily = new LinkedHashMap<>();
|
||||
LocalDate start = LocalDate.now().minusDays(days - 1L);
|
||||
for (int i = 0; i < days; i++) {
|
||||
LocalDate date = start.plusDays(i);
|
||||
try {
|
||||
LocalDate start = LocalDate.parse(startStr);
|
||||
LocalDate end = LocalDate.parse(endStr);
|
||||
if (start.plusDays(60).isBefore(end)) {
|
||||
start = end.minusDays(59);
|
||||
}
|
||||
for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
|
||||
DailySales row = new DailySales();
|
||||
row.setDate(date.toString());
|
||||
row.setRevenue(BigDecimal.ZERO);
|
||||
row.setSalesCount(0L);
|
||||
daily.put(date, row);
|
||||
}
|
||||
for (SaleResponse sale : sales) {
|
||||
if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getSaleDate() == null) {
|
||||
continue;
|
||||
} catch (Exception e) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
for (SaleResponse sale : sales) {
|
||||
if (Boolean.TRUE.equals(sale.getIsRefund()) || sale.getSaleDate() == null) continue;
|
||||
LocalDate date = sale.getSaleDate().toLocalDate();
|
||||
DailySales row = daily.get(date);
|
||||
if (row != null) {
|
||||
@@ -494,4 +570,73 @@ public class AnalyticsController {
|
||||
loadAnalyticsData();
|
||||
TableViewSupport.flashStatus(lblStatus, "Refreshed");
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnToggleFilters(ActionEvent event) {
|
||||
boolean expanded = vbFilterContent.isVisible();
|
||||
vbFilterContent.setVisible(!expanded);
|
||||
vbFilterContent.setManaged(!expanded);
|
||||
btnToggleFilters.setText(expanded ? "▼ Filters" : "▲ Filters");
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnPresetToday(ActionEvent event) { applyPreset(0, 0); }
|
||||
|
||||
@FXML
|
||||
void btnPreset7D(ActionEvent event) { applyPreset(-6, 0); }
|
||||
|
||||
@FXML
|
||||
void btnPreset30D(ActionEvent event) { applyPreset(-29, 0); }
|
||||
|
||||
@FXML
|
||||
void btnPreset3M(ActionEvent event) { applyPreset(-89, 0); }
|
||||
|
||||
@FXML
|
||||
void btnPreset1Y(ActionEvent event) { applyPreset(-364, 0); }
|
||||
|
||||
@FXML
|
||||
void btnPresetAll(ActionEvent event) {
|
||||
dpStartDate.setValue(null);
|
||||
dpEndDate.setValue(null);
|
||||
applyFiltersFromUI();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnApplyFilter(ActionEvent event) {
|
||||
applyFiltersFromUI();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnResetFilter(ActionEvent event) {
|
||||
dpStartDate.setValue(null);
|
||||
dpEndDate.setValue(null);
|
||||
cbPaymentFilter.getSelectionModel().selectFirst();
|
||||
cbTopN.getSelectionModel().selectFirst();
|
||||
currentFilter = new FilterState();
|
||||
applyCurrentFilter();
|
||||
}
|
||||
|
||||
private void applyPreset(int startOffset, int endOffset) {
|
||||
dpStartDate.setValue(LocalDate.now().plusDays(startOffset));
|
||||
dpEndDate.setValue(LocalDate.now().plusDays(endOffset));
|
||||
applyFiltersFromUI();
|
||||
}
|
||||
|
||||
private void applyFiltersFromUI() {
|
||||
currentFilter = new FilterState();
|
||||
currentFilter.startDate = dpStartDate.getValue() != null ? dpStartDate.getValue().toString() : "";
|
||||
currentFilter.endDate = dpEndDate.getValue() != null ? dpEndDate.getValue().toString() : "";
|
||||
String pm = cbPaymentFilter.getValue();
|
||||
currentFilter.paymentMethod = pm != null ? pm : "All";
|
||||
int topNPos = cbTopN.getSelectionModel().getSelectedIndex();
|
||||
currentFilter.topN = (topNPos >= 0 && topNPos < TOP_N_VALUES.length) ? TOP_N_VALUES[topNPos] : 5;
|
||||
applyCurrentFilter();
|
||||
}
|
||||
|
||||
private static class FilterState {
|
||||
String startDate = "";
|
||||
String endDate = "";
|
||||
String paymentMethod = "All";
|
||||
int topN = 5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
<?import javafx.scene.chart.NumberAxis?>
|
||||
<?import javafx.scene.chart.PieChart?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.ComboBox?>
|
||||
<?import javafx.scene.control.DatePicker?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.Tab?>
|
||||
<?import javafx.scene.control.TabPane?>
|
||||
<?import javafx.scene.layout.FlowPane?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
@@ -37,6 +40,75 @@
|
||||
</Button>
|
||||
</HBox>
|
||||
|
||||
<VBox style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #e2e8f0; -fx-border-radius: 8; -fx-border-width: 1;">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="12.0" right="12.0" top="10.0" />
|
||||
</padding>
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||
<children>
|
||||
<Button fx:id="btnToggleFilters" mnemonicParsing="false" onAction="#btnToggleFilters" style="-fx-background-color: transparent; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="▼ Filters" textFill="#2c3e50">
|
||||
<font><Font name="System Bold" size="12.0" /></font>
|
||||
</Button>
|
||||
<Label fx:id="lblFilterSummary" text="All time" textFill="#64748b">
|
||||
<font><Font size="12.0" /></font>
|
||||
</Label>
|
||||
</children>
|
||||
</HBox>
|
||||
<VBox fx:id="vbFilterContent" spacing="8.0" visible="false" managed="false">
|
||||
<VBox.margin><Insets top="8.0" /></VBox.margin>
|
||||
<children>
|
||||
<FlowPane alignment="CENTER_LEFT" hgap="8.0" vgap="8.0">
|
||||
<children>
|
||||
<DatePicker fx:id="dpStartDate" prefWidth="145.0" promptText="Start date" />
|
||||
<Label text="to" textFill="#64748b" />
|
||||
<DatePicker fx:id="dpEndDate" prefWidth="145.0" promptText="End date" />
|
||||
<Button fx:id="btnPresetToday" mnemonicParsing="false" onAction="#btnPresetToday" style="-fx-background-color: #f1f5f9; -fx-background-radius: 6; -fx-border-color: #e2e8f0; -fx-border-radius: 6; -fx-border-width: 1; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Today" textFill="#475569">
|
||||
<font><Font size="11.0" /></font>
|
||||
<padding><Insets bottom="5.0" left="10.0" right="10.0" top="5.0" /></padding>
|
||||
</Button>
|
||||
<Button fx:id="btnPreset7D" mnemonicParsing="false" onAction="#btnPreset7D" style="-fx-background-color: #f1f5f9; -fx-background-radius: 6; -fx-border-color: #e2e8f0; -fx-border-radius: 6; -fx-border-width: 1; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="7D" textFill="#475569">
|
||||
<font><Font size="11.0" /></font>
|
||||
<padding><Insets bottom="5.0" left="10.0" right="10.0" top="5.0" /></padding>
|
||||
</Button>
|
||||
<Button fx:id="btnPreset30D" mnemonicParsing="false" onAction="#btnPreset30D" style="-fx-background-color: #f1f5f9; -fx-background-radius: 6; -fx-border-color: #e2e8f0; -fx-border-radius: 6; -fx-border-width: 1; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="30D" textFill="#475569">
|
||||
<font><Font size="11.0" /></font>
|
||||
<padding><Insets bottom="5.0" left="10.0" right="10.0" top="5.0" /></padding>
|
||||
</Button>
|
||||
<Button fx:id="btnPreset3M" mnemonicParsing="false" onAction="#btnPreset3M" style="-fx-background-color: #f1f5f9; -fx-background-radius: 6; -fx-border-color: #e2e8f0; -fx-border-radius: 6; -fx-border-width: 1; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="3M" textFill="#475569">
|
||||
<font><Font size="11.0" /></font>
|
||||
<padding><Insets bottom="5.0" left="10.0" right="10.0" top="5.0" /></padding>
|
||||
</Button>
|
||||
<Button fx:id="btnPreset1Y" mnemonicParsing="false" onAction="#btnPreset1Y" style="-fx-background-color: #f1f5f9; -fx-background-radius: 6; -fx-border-color: #e2e8f0; -fx-border-radius: 6; -fx-border-width: 1; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="1Y" textFill="#475569">
|
||||
<font><Font size="11.0" /></font>
|
||||
<padding><Insets bottom="5.0" left="10.0" right="10.0" top="5.0" /></padding>
|
||||
</Button>
|
||||
<Button fx:id="btnPresetAll" mnemonicParsing="false" onAction="#btnPresetAll" style="-fx-background-color: #f1f5f9; -fx-background-radius: 6; -fx-border-color: #e2e8f0; -fx-border-radius: 6; -fx-border-width: 1; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="All" textFill="#475569">
|
||||
<font><Font size="11.0" /></font>
|
||||
<padding><Insets bottom="5.0" left="10.0" right="10.0" top="5.0" /></padding>
|
||||
</Button>
|
||||
</children>
|
||||
</FlowPane>
|
||||
<HBox alignment="CENTER_LEFT" spacing="8.0">
|
||||
<children>
|
||||
<ComboBox fx:id="cbPaymentFilter" prefWidth="145.0" promptText="All Payments" />
|
||||
<ComboBox fx:id="cbTopN" prefWidth="110.0" />
|
||||
<Region HBox.hgrow="ALWAYS" />
|
||||
<Button fx:id="btnApplyFilter" mnemonicParsing="false" onAction="#btnApplyFilter" style="-fx-background-color: #4ECDC4; -fx-background-radius: 6; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Apply" textFill="WHITE">
|
||||
<font><Font name="System Bold" size="12.0" /></font>
|
||||
<padding><Insets bottom="6.0" left="18.0" right="18.0" top="6.0" /></padding>
|
||||
</Button>
|
||||
<Button fx:id="btnResetFilter" mnemonicParsing="false" onAction="#btnResetFilter" style="-fx-background-color: transparent; -fx-border-color: #cbd5e1; -fx-border-radius: 6; -fx-background-radius: 6; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Reset" textFill="#64748b">
|
||||
<font><Font size="12.0" /></font>
|
||||
<padding><Insets bottom="6.0" left="14.0" right="14.0" top="6.0" /></padding>
|
||||
</Button>
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
<Label fx:id="lblStatus" textFill="#64748b" visible="false" managed="true">
|
||||
<font>
|
||||
<Font size="13.0" />
|
||||
|
||||
Reference in New Issue
Block a user