Add sales analytics screen
This commit is contained in:
@@ -0,0 +1,183 @@
|
|||||||
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.chart.*;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
|
import org.example.petshopdesktop.database.SaleDB;
|
||||||
|
import org.example.petshopdesktop.models.analytics.*;
|
||||||
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class AnalyticsController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnRefresh;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblError;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblTotalRevenue;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblTotalTransactions;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblAvgTransaction;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblTotalItems;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private LineChart<String, Number> chartSalesOverTime;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private BarChart<Number, String> chartTopRevenue;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private BarChart<Number, String> chartTopQuantity;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private PieChart chartPaymentMethods;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private BarChart<String, Number> chartEmployeePerformance;
|
||||||
|
|
||||||
|
private final NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.CANADA);
|
||||||
|
private final NumberFormat wholeNumber = NumberFormat.getIntegerInstance();
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
if (!UserSession.getInstance().isAdmin()) {
|
||||||
|
lblError.setText("Access restricted to administrators only.");
|
||||||
|
lblError.setVisible(true);
|
||||||
|
disableAllCharts();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
configureCharts();
|
||||||
|
loadAnalyticsData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableAllCharts() {
|
||||||
|
chartSalesOverTime.setVisible(false);
|
||||||
|
chartTopRevenue.setVisible(false);
|
||||||
|
chartTopQuantity.setVisible(false);
|
||||||
|
chartPaymentMethods.setVisible(false);
|
||||||
|
chartEmployeePerformance.setVisible(false);
|
||||||
|
btnRefresh.setDisable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureCharts() {
|
||||||
|
chartSalesOverTime.setAnimated(true);
|
||||||
|
chartTopRevenue.setAnimated(true);
|
||||||
|
chartTopQuantity.setAnimated(true);
|
||||||
|
chartPaymentMethods.setAnimated(true);
|
||||||
|
chartEmployeePerformance.setAnimated(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAnalyticsData() {
|
||||||
|
lblError.setVisible(false);
|
||||||
|
try {
|
||||||
|
loadSummaryData();
|
||||||
|
loadSalesOverTime();
|
||||||
|
loadTopProductsByRevenue();
|
||||||
|
loadTopProductsByQuantity();
|
||||||
|
loadPaymentMethodDistribution();
|
||||||
|
loadEmployeePerformance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ActivityLogger.getInstance().logException("AnalyticsController.loadAnalyticsData", e, "Loading analytics data");
|
||||||
|
lblError.setText("Error loading analytics data. Please try again.");
|
||||||
|
lblError.setVisible(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSummaryData() throws Exception {
|
||||||
|
SalesSummary summary = SaleDB.getSalesSummary();
|
||||||
|
if (summary != null) {
|
||||||
|
lblTotalRevenue.setText(currency.format(summary.getTotalRevenue()));
|
||||||
|
lblTotalTransactions.setText(wholeNumber.format(summary.getTotalTransactions()));
|
||||||
|
lblAvgTransaction.setText(currency.format(summary.getAvgTransactionValue()));
|
||||||
|
lblTotalItems.setText(wholeNumber.format(summary.getTotalItemsSold()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadSalesOverTime() throws Exception {
|
||||||
|
ObservableList<DailySalesData> data = SaleDB.getDailySalesRevenue();
|
||||||
|
XYChart.Series<String, Number> series = new XYChart.Series<>();
|
||||||
|
series.setName("Daily Revenue");
|
||||||
|
|
||||||
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd");
|
||||||
|
for (DailySalesData dailySale : data) {
|
||||||
|
String dateStr = dailySale.getDate().format(formatter);
|
||||||
|
series.getData().add(new XYChart.Data<>(dateStr, dailySale.getRevenue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
chartSalesOverTime.getData().clear();
|
||||||
|
chartSalesOverTime.getData().add(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadTopProductsByRevenue() throws Exception {
|
||||||
|
ObservableList<ProductSalesData> data = SaleDB.getTopProductsByRevenue(10);
|
||||||
|
XYChart.Series<Number, String> series = new XYChart.Series<>();
|
||||||
|
series.setName("Revenue");
|
||||||
|
|
||||||
|
for (ProductSalesData product : data) {
|
||||||
|
series.getData().add(new XYChart.Data<>(product.getTotalRevenue(), product.getProductName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
chartTopRevenue.getData().clear();
|
||||||
|
chartTopRevenue.getData().add(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadTopProductsByQuantity() throws Exception {
|
||||||
|
ObservableList<ProductSalesData> data = SaleDB.getTopProductsByQuantity(10);
|
||||||
|
XYChart.Series<Number, String> series = new XYChart.Series<>();
|
||||||
|
series.setName("Quantity");
|
||||||
|
|
||||||
|
for (ProductSalesData product : data) {
|
||||||
|
series.getData().add(new XYChart.Data<>(product.getTotalQuantity(), product.getProductName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
chartTopQuantity.getData().clear();
|
||||||
|
chartTopQuantity.getData().add(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadPaymentMethodDistribution() throws Exception {
|
||||||
|
ObservableList<PaymentMethodData> data = SaleDB.getPaymentMethodDistribution();
|
||||||
|
chartPaymentMethods.getData().clear();
|
||||||
|
|
||||||
|
for (PaymentMethodData payment : data) {
|
||||||
|
PieChart.Data slice = new PieChart.Data(
|
||||||
|
payment.getPaymentMethod() + " (" + payment.getTransactionCount() + ")",
|
||||||
|
payment.getTransactionCount()
|
||||||
|
);
|
||||||
|
chartPaymentMethods.getData().add(slice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadEmployeePerformance() throws Exception {
|
||||||
|
ObservableList<EmployeeSalesData> data = SaleDB.getEmployeeSalesPerformance();
|
||||||
|
XYChart.Series<String, Number> series = new XYChart.Series<>();
|
||||||
|
series.setName("Revenue");
|
||||||
|
|
||||||
|
for (EmployeeSalesData employee : data) {
|
||||||
|
series.getData().add(new XYChart.Data<>(employee.getEmployeeName(), employee.getTotalRevenue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
chartEmployeePerformance.getData().clear();
|
||||||
|
chartEmployeePerformance.getData().add(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void handleRefresh(ActionEvent event) {
|
||||||
|
loadAnalyticsData();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,6 +66,9 @@ public class MainLayoutController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Button btnStaffAccounts;
|
private Button btnStaffAccounts;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnAnalytics;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Label lblUsername;
|
private Label lblUsername;
|
||||||
|
|
||||||
@@ -129,6 +132,12 @@ public class MainLayoutController {
|
|||||||
updateButtons(btnStaffAccounts);
|
updateButtons(btnStaffAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnAnalyticsClicked(ActionEvent event) {
|
||||||
|
loadView("analytics-view.fxml");
|
||||||
|
updateButtons(btnAnalytics);
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnServicesClicked(ActionEvent event) {
|
void btnServicesClicked(ActionEvent event) {
|
||||||
loadView("service-view.fxml");
|
loadView("service-view.fxml");
|
||||||
@@ -195,6 +204,11 @@ public class MainLayoutController {
|
|||||||
btnStaffAccounts.setManaged(isAdmin);
|
btnStaffAccounts.setManaged(isAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (btnAnalytics != null) {
|
||||||
|
btnAnalytics.setVisible(isAdmin);
|
||||||
|
btnAnalytics.setManaged(isAdmin);
|
||||||
|
}
|
||||||
|
|
||||||
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");
|
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");
|
||||||
|
|
||||||
|
|
||||||
@@ -234,7 +248,8 @@ public class MainLayoutController {
|
|||||||
btnProductSuppliers,
|
btnProductSuppliers,
|
||||||
btnProducts,
|
btnProducts,
|
||||||
btnPurchaseOrders,
|
btnPurchaseOrders,
|
||||||
btnStaffAccounts
|
btnStaffAccounts,
|
||||||
|
btnAnalytics
|
||||||
};
|
};
|
||||||
|
|
||||||
for (Button button : buttons) {
|
for (Button button : buttons) {
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import javafx.collections.ObservableList;
|
|||||||
import org.example.petshopdesktop.DTOs.SaleDTO;
|
import org.example.petshopdesktop.DTOs.SaleDTO;
|
||||||
import org.example.petshopdesktop.models.SaleCartItem;
|
import org.example.petshopdesktop.models.SaleCartItem;
|
||||||
import org.example.petshopdesktop.models.SaleLineItem;
|
import org.example.petshopdesktop.models.SaleLineItem;
|
||||||
|
import org.example.petshopdesktop.models.analytics.*;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
|
||||||
import java.sql.*;
|
import java.sql.*;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
public class SaleDB {
|
public class SaleDB {
|
||||||
|
|
||||||
@@ -230,4 +232,169 @@ public class SaleDB {
|
|||||||
conn.close();
|
conn.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ObservableList<DailySalesData> getDailySalesRevenue() throws SQLException {
|
||||||
|
ObservableList<DailySalesData> dailySales = FXCollections.observableArrayList();
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
SELECT DATE(s.saleDate) as saleDate, SUM(s.totalAmount) as revenue
|
||||||
|
FROM sale s
|
||||||
|
GROUP BY DATE(s.saleDate)
|
||||||
|
ORDER BY saleDate ASC
|
||||||
|
""";
|
||||||
|
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(sql);
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
LocalDate date = rs.getDate("saleDate").toLocalDate();
|
||||||
|
double revenue = rs.getDouble("revenue");
|
||||||
|
dailySales.add(new DailySalesData(date, revenue));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return dailySales;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObservableList<ProductSalesData> getTopProductsByRevenue(int limit) throws SQLException {
|
||||||
|
ObservableList<ProductSalesData> products = FXCollections.observableArrayList();
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
SELECT p.prodName, SUM(si.quantity * si.unitPrice) as totalRevenue
|
||||||
|
FROM saleItem si
|
||||||
|
JOIN product p ON si.prodId = p.prodId
|
||||||
|
GROUP BY p.prodId, p.prodName
|
||||||
|
ORDER BY totalRevenue DESC
|
||||||
|
LIMIT ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||||||
|
pstmt.setInt(1, limit);
|
||||||
|
ResultSet rs = pstmt.executeQuery();
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
String productName = rs.getString("prodName");
|
||||||
|
double totalRevenue = rs.getDouble("totalRevenue");
|
||||||
|
products.add(new ProductSalesData(productName, 0, totalRevenue));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return products;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObservableList<ProductSalesData> getTopProductsByQuantity(int limit) throws SQLException {
|
||||||
|
ObservableList<ProductSalesData> products = FXCollections.observableArrayList();
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
SELECT p.prodName, SUM(si.quantity) as totalQuantity,
|
||||||
|
SUM(si.quantity * si.unitPrice) as totalRevenue
|
||||||
|
FROM saleItem si
|
||||||
|
JOIN product p ON si.prodId = p.prodId
|
||||||
|
GROUP BY p.prodId, p.prodName
|
||||||
|
ORDER BY totalQuantity DESC
|
||||||
|
LIMIT ?
|
||||||
|
""";
|
||||||
|
|
||||||
|
PreparedStatement pstmt = conn.prepareStatement(sql);
|
||||||
|
pstmt.setInt(1, limit);
|
||||||
|
ResultSet rs = pstmt.executeQuery();
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
String productName = rs.getString("prodName");
|
||||||
|
int totalQuantity = rs.getInt("totalQuantity");
|
||||||
|
double totalRevenue = rs.getDouble("totalRevenue");
|
||||||
|
products.add(new ProductSalesData(productName, totalQuantity, totalRevenue));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return products;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObservableList<PaymentMethodData> getPaymentMethodDistribution() throws SQLException {
|
||||||
|
ObservableList<PaymentMethodData> paymentMethods = FXCollections.observableArrayList();
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
SELECT s.paymentMethod, COUNT(*) as transactionCount,
|
||||||
|
SUM(s.totalAmount) as totalRevenue
|
||||||
|
FROM sale s
|
||||||
|
GROUP BY s.paymentMethod
|
||||||
|
ORDER BY totalRevenue DESC
|
||||||
|
""";
|
||||||
|
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(sql);
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
String paymentMethod = rs.getString("paymentMethod");
|
||||||
|
int transactionCount = rs.getInt("transactionCount");
|
||||||
|
double totalRevenue = rs.getDouble("totalRevenue");
|
||||||
|
paymentMethods.add(new PaymentMethodData(paymentMethod, transactionCount, totalRevenue));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return paymentMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ObservableList<EmployeeSalesData> getEmployeeSalesPerformance() throws SQLException {
|
||||||
|
ObservableList<EmployeeSalesData> employees = FXCollections.observableArrayList();
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
SELECT CONCAT(e.firstName, ' ', e.lastName) as employeeName,
|
||||||
|
COUNT(DISTINCT s.saleId) as transactionCount,
|
||||||
|
SUM(s.totalAmount) as totalRevenue,
|
||||||
|
COALESCE(SUM(si.quantity), 0) as totalItemsSold
|
||||||
|
FROM sale s
|
||||||
|
JOIN employee e ON s.employeeId = e.employeeId
|
||||||
|
LEFT JOIN saleItem si ON s.saleId = si.saleId
|
||||||
|
GROUP BY e.employeeId, e.firstName, e.lastName
|
||||||
|
ORDER BY totalRevenue DESC
|
||||||
|
""";
|
||||||
|
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(sql);
|
||||||
|
|
||||||
|
while (rs.next()) {
|
||||||
|
String employeeName = rs.getString("employeeName");
|
||||||
|
int transactionCount = rs.getInt("transactionCount");
|
||||||
|
double totalRevenue = rs.getDouble("totalRevenue");
|
||||||
|
int totalItemsSold = rs.getInt("totalItemsSold");
|
||||||
|
employees.add(new EmployeeSalesData(employeeName, transactionCount, totalRevenue, totalItemsSold));
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return employees;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SalesSummary getSalesSummary() throws SQLException {
|
||||||
|
Connection conn = ConnectionDB.getConnection();
|
||||||
|
|
||||||
|
String sql = """
|
||||||
|
SELECT COUNT(DISTINCT s.saleId) as totalTransactions,
|
||||||
|
COALESCE(SUM(s.totalAmount), 0) as totalRevenue,
|
||||||
|
COALESCE(AVG(s.totalAmount), 0) as avgTransactionValue,
|
||||||
|
COALESCE(SUM(si.quantity), 0) as totalItemsSold
|
||||||
|
FROM sale s
|
||||||
|
LEFT JOIN saleItem si ON s.saleId = si.saleId
|
||||||
|
""";
|
||||||
|
|
||||||
|
Statement stmt = conn.createStatement();
|
||||||
|
ResultSet rs = stmt.executeQuery(sql);
|
||||||
|
|
||||||
|
SalesSummary summary = null;
|
||||||
|
if (rs.next()) {
|
||||||
|
int totalTransactions = rs.getInt("totalTransactions");
|
||||||
|
double totalRevenue = rs.getDouble("totalRevenue");
|
||||||
|
double avgTransactionValue = rs.getDouble("avgTransactionValue");
|
||||||
|
int totalItemsSold = rs.getInt("totalItemsSold");
|
||||||
|
summary = new SalesSummary(totalTransactions, totalRevenue, avgTransactionValue, totalItemsSold);
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.close();
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.example.petshopdesktop.models.analytics;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
|
||||||
|
public class DailySalesData {
|
||||||
|
private final LocalDate date;
|
||||||
|
private final double revenue;
|
||||||
|
|
||||||
|
public DailySalesData(LocalDate date, double revenue) {
|
||||||
|
this.date = date;
|
||||||
|
this.revenue = revenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalDate getDate() {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getRevenue() {
|
||||||
|
return revenue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.example.petshopdesktop.models.analytics;
|
||||||
|
|
||||||
|
public class EmployeeSalesData {
|
||||||
|
private final String employeeName;
|
||||||
|
private final int transactionCount;
|
||||||
|
private final double totalRevenue;
|
||||||
|
private final int totalItemsSold;
|
||||||
|
|
||||||
|
public EmployeeSalesData(String employeeName, int transactionCount, double totalRevenue, int totalItemsSold) {
|
||||||
|
this.employeeName = employeeName;
|
||||||
|
this.transactionCount = transactionCount;
|
||||||
|
this.totalRevenue = totalRevenue;
|
||||||
|
this.totalItemsSold = totalItemsSold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmployeeName() {
|
||||||
|
return employeeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTransactionCount() {
|
||||||
|
return transactionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalRevenue() {
|
||||||
|
return totalRevenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalItemsSold() {
|
||||||
|
return totalItemsSold;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.example.petshopdesktop.models.analytics;
|
||||||
|
|
||||||
|
public class PaymentMethodData {
|
||||||
|
private final String paymentMethod;
|
||||||
|
private final int transactionCount;
|
||||||
|
private final double totalRevenue;
|
||||||
|
|
||||||
|
public PaymentMethodData(String paymentMethod, int transactionCount, double totalRevenue) {
|
||||||
|
this.paymentMethod = paymentMethod;
|
||||||
|
this.transactionCount = transactionCount;
|
||||||
|
this.totalRevenue = totalRevenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPaymentMethod() {
|
||||||
|
return paymentMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTransactionCount() {
|
||||||
|
return transactionCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalRevenue() {
|
||||||
|
return totalRevenue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package org.example.petshopdesktop.models.analytics;
|
||||||
|
|
||||||
|
public class ProductSalesData {
|
||||||
|
private final String productName;
|
||||||
|
private final int totalQuantity;
|
||||||
|
private final double totalRevenue;
|
||||||
|
|
||||||
|
public ProductSalesData(String productName, int totalQuantity, double totalRevenue) {
|
||||||
|
this.productName = productName;
|
||||||
|
this.totalQuantity = totalQuantity;
|
||||||
|
this.totalRevenue = totalRevenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalQuantity() {
|
||||||
|
return totalQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalRevenue() {
|
||||||
|
return totalRevenue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package org.example.petshopdesktop.models.analytics;
|
||||||
|
|
||||||
|
public class SalesSummary {
|
||||||
|
private final int totalTransactions;
|
||||||
|
private final double totalRevenue;
|
||||||
|
private final double avgTransactionValue;
|
||||||
|
private final int totalItemsSold;
|
||||||
|
|
||||||
|
public SalesSummary(int totalTransactions, double totalRevenue, double avgTransactionValue, int totalItemsSold) {
|
||||||
|
this.totalTransactions = totalTransactions;
|
||||||
|
this.totalRevenue = totalRevenue;
|
||||||
|
this.avgTransactionValue = avgTransactionValue;
|
||||||
|
this.totalItemsSold = totalItemsSold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalTransactions() {
|
||||||
|
return totalTransactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getTotalRevenue() {
|
||||||
|
return totalRevenue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getAvgTransactionValue() {
|
||||||
|
return avgTransactionValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalItemsSold() {
|
||||||
|
return totalItemsSold;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,6 +143,15 @@
|
|||||||
</padding>
|
</padding>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button fx:id="btnAnalytics" alignment="CENTER_LEFT" mnemonicParsing="false" onAction="#btnAnalyticsClicked" prefWidth="250.0" style="-fx-background-color: transparent; -fx-background-radius: 10; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Analytics" textFill="#cbd5e1">
|
||||||
|
<font>
|
||||||
|
<Font name="System" size="13.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="14.0" right="14.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Region VBox.vgrow="ALWAYS" />
|
<Region VBox.vgrow="ALWAYS" />
|
||||||
|
|
||||||
<Button fx:id="btnLogout" alignment="CENTER_LEFT" mnemonicParsing="false" onAction="#btnLogoutClicked" prefWidth="250.0" style="-fx-background-color: rgba(255,255,255,0.08); -fx-background-radius: 10; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Logout" textFill="#e2e8f0">
|
<Button fx:id="btnLogout" alignment="CENTER_LEFT" mnemonicParsing="false" onAction="#btnLogoutClicked" prefWidth="250.0" style="-fx-background-color: rgba(255,255,255,0.08); -fx-background-radius: 10; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Logout" textFill="#e2e8f0">
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.chart.BarChart?>
|
||||||
|
<?import javafx.scene.chart.CategoryAxis?>
|
||||||
|
<?import javafx.scene.chart.LineChart?>
|
||||||
|
<?import javafx.scene.chart.NumberAxis?>
|
||||||
|
<?import javafx.scene.chart.PieChart?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ScrollPane?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<ScrollPane fitToWidth="true" xmlns="http://javafx.com/javafx/21" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.AnalyticsController">
|
||||||
|
<VBox spacing="20.0" style="-fx-background-color: #f8fafc;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||||
|
<Label text="Sales Analytics" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="24.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Button fx:id="btnRefresh" onAction="#handleRefresh" style="-fx-background-color: #4ECDC4; -fx-text-fill: white; -fx-background-radius: 5; -fx-cursor: hand;" text="Refresh">
|
||||||
|
<font>
|
||||||
|
<Font size="13.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="8.0" left="16.0" right="16.0" top="8.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<Label fx:id="lblError" textFill="#FF6B6B" visible="false">
|
||||||
|
<font>
|
||||||
|
<Font size="13.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<HBox spacing="20.0">
|
||||||
|
<VBox alignment="CENTER" spacing="8.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Total Revenue" textFill="#64748b">
|
||||||
|
<font>
|
||||||
|
<Font size="12.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="lblTotalRevenue" text="$0.00" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="24.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<VBox alignment="CENTER" spacing="8.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Total Transactions" textFill="#64748b">
|
||||||
|
<font>
|
||||||
|
<Font size="12.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="lblTotalTransactions" text="0" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="24.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<VBox alignment="CENTER" spacing="8.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Average Transaction Value" textFill="#64748b">
|
||||||
|
<font>
|
||||||
|
<Font size="12.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="lblAvgTransaction" text="$0.00" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="24.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<VBox alignment="CENTER" spacing="8.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Total Items Sold" textFill="#64748b">
|
||||||
|
<font>
|
||||||
|
<Font size="12.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="lblTotalItems" text="0" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="24.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<VBox spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Sales Over Time" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<LineChart fx:id="chartSalesOverTime" animated="true" legendSide="BOTTOM" prefHeight="300.0">
|
||||||
|
<xAxis>
|
||||||
|
<CategoryAxis label="Date" side="BOTTOM" />
|
||||||
|
</xAxis>
|
||||||
|
<yAxis>
|
||||||
|
<NumberAxis label="Revenue ($)" side="LEFT" />
|
||||||
|
</yAxis>
|
||||||
|
</LineChart>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<HBox spacing="20.0">
|
||||||
|
<VBox spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Top Products by Revenue" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<BarChart fx:id="chartTopRevenue" animated="true" legendSide="BOTTOM" prefHeight="300.0">
|
||||||
|
<xAxis>
|
||||||
|
<NumberAxis label="Revenue ($)" side="BOTTOM" />
|
||||||
|
</xAxis>
|
||||||
|
<yAxis>
|
||||||
|
<CategoryAxis label="Product" side="LEFT" />
|
||||||
|
</yAxis>
|
||||||
|
</BarChart>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<VBox spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Top Products by Quantity" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<BarChart fx:id="chartTopQuantity" animated="true" legendSide="BOTTOM" prefHeight="300.0">
|
||||||
|
<xAxis>
|
||||||
|
<NumberAxis label="Quantity Sold" side="BOTTOM" />
|
||||||
|
</xAxis>
|
||||||
|
<yAxis>
|
||||||
|
<CategoryAxis label="Product" side="LEFT" />
|
||||||
|
</yAxis>
|
||||||
|
</BarChart>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<HBox spacing="20.0">
|
||||||
|
<VBox spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Payment Method Distribution" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<PieChart fx:id="chartPaymentMethods" animated="true" legendSide="BOTTOM" prefHeight="300.0" />
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<VBox spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 10; -fx-border-color: #e2e8f0; -fx-border-radius: 10; -fx-border-width: 1;" HBox.hgrow="ALWAYS">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
<Label text="Employee Performance" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="16.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<BarChart fx:id="chartEmployeePerformance" animated="true" legendSide="BOTTOM" prefHeight="300.0">
|
||||||
|
<xAxis>
|
||||||
|
<CategoryAxis label="Employee" side="BOTTOM" />
|
||||||
|
</xAxis>
|
||||||
|
<yAxis>
|
||||||
|
<NumberAxis label="Revenue ($)" side="LEFT" />
|
||||||
|
</yAxis>
|
||||||
|
</BarChart>
|
||||||
|
</VBox>
|
||||||
|
</HBox>
|
||||||
|
</VBox>
|
||||||
|
</ScrollPane>
|
||||||
Reference in New Issue
Block a user