Show staff analytics

This commit is contained in:
2026-03-29 23:34:52 -06:00
parent 061275ba30
commit a9fc3e3227
4 changed files with 104 additions and 45 deletions

View File

@@ -8,6 +8,8 @@ public class DashboardResponse {
private InventorySummary inventorySummary; private InventorySummary inventorySummary;
private List<TopProduct> topProducts; private List<TopProduct> topProducts;
private List<DailySales> dailySales; private List<DailySales> dailySales;
private List<PaymentMethodData> paymentMethods;
private List<EmployeePerformanceData> employeePerformance;
public DashboardResponse() { public DashboardResponse() {
} }
@@ -44,11 +46,28 @@ public class DashboardResponse {
this.dailySales = dailySales; this.dailySales = dailySales;
} }
public List<PaymentMethodData> getPaymentMethods() {
return paymentMethods;
}
public void setPaymentMethods(List<PaymentMethodData> paymentMethods) {
this.paymentMethods = paymentMethods;
}
public List<EmployeePerformanceData> getEmployeePerformance() {
return employeePerformance;
}
public void setEmployeePerformance(List<EmployeePerformanceData> employeePerformance) {
this.employeePerformance = employeePerformance;
}
public static class SalesSummary { public static class SalesSummary {
private BigDecimal totalRevenue; private BigDecimal totalRevenue;
private Long totalSales; private Long totalSales;
private BigDecimal totalRefunds; private BigDecimal totalRefunds;
private Long totalRefundCount; private Long totalRefundCount;
private Long totalItemsSold;
public SalesSummary() { public SalesSummary() {
} }
@@ -84,6 +103,14 @@ public class DashboardResponse {
public void setTotalRefundCount(Long totalRefundCount) { public void setTotalRefundCount(Long totalRefundCount) {
this.totalRefundCount = totalRefundCount; this.totalRefundCount = totalRefundCount;
} }
public Long getTotalItemsSold() {
return totalItemsSold;
}
public void setTotalItemsSold(Long totalItemsSold) {
this.totalItemsSold = totalItemsSold;
}
} }
public static class InventorySummary { public static class InventorySummary {
@@ -118,4 +145,46 @@ public class DashboardResponse {
this.outOfStockProducts = outOfStockProducts; this.outOfStockProducts = outOfStockProducts;
} }
} }
public static class PaymentMethodData {
private String paymentMethod;
private Long count;
public String getPaymentMethod() {
return paymentMethod;
}
public void setPaymentMethod(String paymentMethod) {
this.paymentMethod = paymentMethod;
}
public Long getCount() {
return count;
}
public void setCount(Long count) {
this.count = count;
}
}
public static class EmployeePerformanceData {
private String employeeName;
private BigDecimal revenue;
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public BigDecimal getRevenue() {
return revenue;
}
public void setRevenue(BigDecimal revenue) {
this.revenue = revenue;
}
}
} }

View File

@@ -92,4 +92,8 @@ public class UserSession {
public boolean isAdmin() { public boolean isAdmin() {
return Role.ADMIN.equals(role); return Role.ADMIN.equals(role);
} }
public boolean isStaff() {
return Role.STAFF.equals(role);
}
} }

View File

@@ -9,9 +9,8 @@ import javafx.scene.control.Label;
import org.example.petshopdesktop.api.dto.analytics.DailySales; import org.example.petshopdesktop.api.dto.analytics.DailySales;
import org.example.petshopdesktop.api.dto.analytics.DashboardResponse; import org.example.petshopdesktop.api.dto.analytics.DashboardResponse;
import org.example.petshopdesktop.api.dto.analytics.TopProduct; import org.example.petshopdesktop.api.dto.analytics.TopProduct;
import org.example.petshopdesktop.api.dto.sale.SaleResponse;
import org.example.petshopdesktop.api.endpoints.AnalyticsApi; 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; import org.example.petshopdesktop.util.ActivityLogger;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -127,16 +126,17 @@ public class AnalyticsController {
new Thread(() -> { new Thread(() -> {
try { try {
DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10); DashboardResponse dashboard = AnalyticsApi.getInstance().getDashboard(30, 10);
List<SaleResponse> sales = SaleApi.getInstance().listSales(0, Integer.MAX_VALUE, null);
Platform.runLater(() -> { Platform.runLater(() -> {
try { try {
boolean isAdmin = UserSession.getInstance().isAdmin();
loadSummaryData(dashboard); loadSummaryData(dashboard);
loadSalesOverTime(dashboard); loadSalesOverTime(dashboard);
loadTopProductsByRevenue(dashboard); loadTopProductsByRevenue(dashboard);
loadTopProductsByQuantity(dashboard); loadTopProductsByQuantity(dashboard);
loadPaymentMethodDistribution(sales); loadPaymentMethodDistribution(dashboard);
loadEmployeePerformance(sales); loadEmployeePerformance(dashboard, isAdmin);
applyRoleVisibility(isAdmin);
} catch (Exception e) { } catch (Exception e) {
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data"); ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
lblError.setText("Error loading analytics data. Please try again."); lblError.setText("Error loading analytics data. Please try again.");
@@ -157,15 +157,12 @@ public class AnalyticsController {
if (dashboard != null) { if (dashboard != null) {
BigDecimal totalRevenue = BigDecimal.ZERO; BigDecimal totalRevenue = BigDecimal.ZERO;
Long totalSales = 0L; Long totalSales = 0L;
Long totalProducts = 0L;
if (dashboard.getSalesSummary() != null) { if (dashboard.getSalesSummary() != null) {
totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO; totalRevenue = dashboard.getSalesSummary().getTotalRevenue() != null ? dashboard.getSalesSummary().getTotalRevenue() : BigDecimal.ZERO;
totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L; totalSales = dashboard.getSalesSummary().getTotalSales() != null ? dashboard.getSalesSummary().getTotalSales() : 0L;
} lblTotalItems.setText(wholeNumber.format(dashboard.getSalesSummary().getTotalItemsSold() != null ? dashboard.getSalesSummary().getTotalItemsSold() : 0L));
} else {
if (dashboard.getInventorySummary() != null) { lblTotalItems.setText(wholeNumber.format(0));
totalProducts = dashboard.getInventorySummary().getTotalProducts() != null ? dashboard.getInventorySummary().getTotalProducts() : 0L;
} }
lblTotalRevenue.setText(currency.format(totalRevenue)); lblTotalRevenue.setText(currency.format(totalRevenue));
@@ -176,7 +173,6 @@ public class AnalyticsController {
avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP); avgTransaction = totalRevenue.divide(BigDecimal.valueOf(totalSales), 2, RoundingMode.HALF_UP);
} }
lblAvgTransaction.setText(currency.format(avgTransaction)); lblAvgTransaction.setText(currency.format(avgTransaction));
lblTotalItems.setText(wholeNumber.format(totalProducts));
} }
} }
@@ -243,24 +239,14 @@ public class AnalyticsController {
applyBarChartColor(chartTopQuantity, QUANTITY_COLOR); applyBarChartColor(chartTopQuantity, QUANTITY_COLOR);
} }
private void loadPaymentMethodDistribution(List<SaleResponse> sales) throws Exception { private void loadPaymentMethodDistribution(DashboardResponse dashboard) throws Exception {
Map<String, Long> paymentMethodCount = sales.stream()
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
.collect(Collectors.groupingBy(
sale -> sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "Unknown",
Collectors.counting()
));
chartPaymentMethods.getData().clear(); chartPaymentMethods.getData().clear();
List<Map.Entry<String, Long>> paymentEntries = paymentMethodCount.entrySet().stream() List<DashboardResponse.PaymentMethodData> paymentEntries = dashboard.getPaymentMethods() != null ? dashboard.getPaymentMethods() : List.of();
.sorted(Map.Entry.comparingByKey()) for (DashboardResponse.PaymentMethodData entry : paymentEntries) {
.toList();
for (Map.Entry<String, Long> entry : paymentEntries) {
PieChart.Data slice = new PieChart.Data( PieChart.Data slice = new PieChart.Data(
entry.getKey() + " (" + entry.getValue() + ")", entry.getPaymentMethod() + " (" + entry.getCount() + ")",
entry.getValue() entry.getCount()
); );
chartPaymentMethods.getData().add(slice); chartPaymentMethods.getData().add(slice);
} }
@@ -269,24 +255,14 @@ public class AnalyticsController {
applyPieChartColors(); applyPieChartColors();
} }
private void loadEmployeePerformance(List<SaleResponse> sales) throws Exception { private void loadEmployeePerformance(DashboardResponse dashboard, boolean isAdmin) throws Exception {
Map<String, Double> employeeRevenue = sales.stream()
.filter(sale -> sale.getIsRefund() == null || !sale.getIsRefund())
.filter(sale -> sale.getEmployeeName() != null)
.collect(Collectors.groupingBy(
SaleResponse::getEmployeeName,
Collectors.summingDouble(sale -> sale.getTotalAmount() != null ? sale.getTotalAmount().doubleValue() : 0.0)
));
XYChart.Series<String, Number> series = new XYChart.Series<>(); XYChart.Series<String, Number> series = new XYChart.Series<>();
series.setName("Revenue"); series.setName(isAdmin ? "Revenue" : "My Revenue");
List<Map.Entry<String, Double>> employeeEntries = employeeRevenue.entrySet().stream() List<DashboardResponse.EmployeePerformanceData> employeeEntries = dashboard.getEmployeePerformance() != null ? dashboard.getEmployeePerformance() : List.of();
.sorted(Map.Entry.comparingByKey()) for (DashboardResponse.EmployeePerformanceData entry : employeeEntries) {
.toList(); BigDecimal revenue = entry.getRevenue() != null ? entry.getRevenue() : BigDecimal.ZERO;
series.getData().add(new XYChart.Data<>(entry.getEmployeeName(), revenue));
for (Map.Entry<String, Double> entry : employeeEntries) {
series.getData().add(new XYChart.Data<>(entry.getKey(), entry.getValue()));
} }
chartEmployeePerformance.getData().clear(); chartEmployeePerformance.getData().clear();
@@ -294,6 +270,15 @@ public class AnalyticsController {
applyBarChartColor(chartEmployeePerformance, EMPLOYEE_COLOR); applyBarChartColor(chartEmployeePerformance, EMPLOYEE_COLOR);
} }
private void applyRoleVisibility(boolean isAdmin) {
chartEmployeePerformance.setVisible(isAdmin);
chartEmployeePerformance.setManaged(isAdmin);
if (chartEmployeePerformance.getParent() != null) {
chartEmployeePerformance.getParent().setVisible(isAdmin);
chartEmployeePerformance.getParent().setManaged(isAdmin);
}
}
private void applyLineChartColor(LineChart<Number, Number> chart, String color) { private void applyLineChartColor(LineChart<Number, Number> chart, String color) {
Platform.runLater(() -> { Platform.runLater(() -> {
for (XYChart.Series<Number, Number> series : chart.getData()) { for (XYChart.Series<Number, Number> series : chart.getData()) {

View File

@@ -358,6 +358,7 @@ public class MainLayoutController {
lblRole.setText("Leon's Petstore"); lblRole.setText("Leon's Petstore");
boolean isAdmin = session.isAdmin(); boolean isAdmin = session.isAdmin();
boolean canViewAnalytics = isAdmin || session.isStaff();
btnInventory.setVisible(isAdmin); btnInventory.setVisible(isAdmin);
btnInventory.setManaged(isAdmin); btnInventory.setManaged(isAdmin);
btnSuppliers.setVisible(isAdmin); btnSuppliers.setVisible(isAdmin);
@@ -384,8 +385,8 @@ public class MainLayoutController {
} }
if (btnAnalytics != null) { if (btnAnalytics != null) {
btnAnalytics.setVisible(isAdmin); btnAnalytics.setVisible(canViewAnalytics);
btnAnalytics.setManaged(isAdmin); btnAnalytics.setManaged(canViewAnalytics);
} }
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales"); btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");