seperated staff and customer on desktop
This commit is contained in:
@@ -0,0 +1,172 @@
|
|||||||
|
package org.example.petshopdesktop.controllers;
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.collections.transformation.FilteredList;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.fxml.FXMLLoader;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.control.*;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import org.example.petshopdesktop.api.dto.user.UserResponse;
|
||||||
|
import org.example.petshopdesktop.api.endpoints.CustomerApi;
|
||||||
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
import org.example.petshopdesktop.util.TableViewSupport;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class CustomerAccountsController {
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableView<UserResponse> tvCustomers;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerUsername;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerName;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerEmail;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerPhone;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, Object> colCustomerLoyaltyPoints;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, String> colCustomerStatus;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TableColumn<UserResponse, Object> colCustomerCreated;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private TextField txtSearchCustomer;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnEditCustomer;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnRefresh;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblError;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Label lblStatus;
|
||||||
|
|
||||||
|
private final ObservableList<UserResponse> customerAccounts = FXCollections.observableArrayList();
|
||||||
|
private FilteredList<UserResponse> filteredCustomers;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
public void initialize() {
|
||||||
|
colCustomerUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername()));
|
||||||
|
colCustomerName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName()));
|
||||||
|
colCustomerEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail()));
|
||||||
|
colCustomerPhone.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getPhone()));
|
||||||
|
colCustomerLoyaltyPoints.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getLoyaltyPoints()));
|
||||||
|
colCustomerStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getActive() != null && data.getValue().getActive() ? "Active" : "Inactive"));
|
||||||
|
colCustomerCreated.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getCreatedAt()));
|
||||||
|
|
||||||
|
filteredCustomers = new FilteredList<>(customerAccounts, a -> true);
|
||||||
|
TableViewSupport.bindSortedItems(tvCustomers, filteredCustomers);
|
||||||
|
TableViewSupport.installDoubleClickAction(tvCustomers, this::openEditDialog);
|
||||||
|
|
||||||
|
tvCustomers.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) ->
|
||||||
|
btnEditCustomer.setDisable(newVal == null));
|
||||||
|
btnEditCustomer.setDisable(true);
|
||||||
|
|
||||||
|
txtSearchCustomer.textProperty().addListener((obs, o, n) -> applyCustomerFilter(n));
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnRefreshClicked(ActionEvent event) {
|
||||||
|
txtSearchCustomer.clear();
|
||||||
|
TableViewSupport.clearSort(tvCustomers);
|
||||||
|
refresh();
|
||||||
|
TableViewSupport.flashStatus(lblStatus, "Refreshed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnEditCustomerClicked(ActionEvent event) {
|
||||||
|
lblError.setText("");
|
||||||
|
openEditDialog(tvCustomers.getSelectionModel().getSelectedItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openEditDialog(UserResponse selected) {
|
||||||
|
if (selected == null) {
|
||||||
|
lblError.setText("Select a customer to edit.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml"));
|
||||||
|
Stage dialog = new Stage();
|
||||||
|
dialog.initOwner(tvCustomers.getScene().getWindow());
|
||||||
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
dialog.setTitle("Edit Customer Account");
|
||||||
|
dialog.setScene(new Scene(loader.load()));
|
||||||
|
dialog.setResizable(false);
|
||||||
|
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
|
||||||
|
controller.setUser(selected);
|
||||||
|
dialog.showAndWait();
|
||||||
|
refresh();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ActivityLogger.getInstance().logException("CustomerAccountsController.openEditDialog", e, "Opening customer edit dialog");
|
||||||
|
lblError.setText("Could not open customer account editor.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refresh() {
|
||||||
|
lblError.setText("");
|
||||||
|
tvCustomers.setDisable(true);
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Comparator<UserResponse> byCreated = Comparator.comparing(
|
||||||
|
UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
|
||||||
|
|
||||||
|
List<UserResponse> customers = CustomerApi.getInstance().listCustomers(null).stream()
|
||||||
|
.sorted(byCreated)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
customerAccounts.setAll(customers);
|
||||||
|
tvCustomers.setDisable(false);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
ActivityLogger.getInstance().logException("CustomerAccountsController.refresh", e, "Loading customer accounts");
|
||||||
|
Platform.runLater(() -> {
|
||||||
|
lblError.setText("Could not load customer accounts.");
|
||||||
|
tvCustomers.setDisable(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String safe(String v) {
|
||||||
|
return v == null ? "" : v.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,6 +89,9 @@ public class MainLayoutController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Button btnStaffAccounts;
|
private Button btnStaffAccounts;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button btnCustomers;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button btnAnalytics;
|
private Button btnAnalytics;
|
||||||
|
|
||||||
@@ -179,6 +182,12 @@ public class MainLayoutController {
|
|||||||
updateButtons(btnStaffAccounts);
|
updateButtons(btnStaffAccounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
void btnCustomersClicked(ActionEvent event) {
|
||||||
|
loadView("customer-accounts-view.fxml");
|
||||||
|
updateButtons(btnCustomers);
|
||||||
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnAnalyticsClicked(ActionEvent event) {
|
void btnAnalyticsClicked(ActionEvent event) {
|
||||||
loadView("analytics-view.fxml");
|
loadView("analytics-view.fxml");
|
||||||
@@ -415,8 +424,13 @@ public class MainLayoutController {
|
|||||||
btnPurchaseOrders.setManaged(isAdmin);
|
btnPurchaseOrders.setManaged(isAdmin);
|
||||||
|
|
||||||
if (btnStaffAccounts != null) {
|
if (btnStaffAccounts != null) {
|
||||||
btnStaffAccounts.setVisible(true);
|
btnStaffAccounts.setVisible(isAdmin);
|
||||||
btnStaffAccounts.setManaged(true);
|
btnStaffAccounts.setManaged(isAdmin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnCustomers != null) {
|
||||||
|
btnCustomers.setVisible(true);
|
||||||
|
btnCustomers.setManaged(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lblAdminSection != null) {
|
if (lblAdminSection != null) {
|
||||||
@@ -493,6 +507,7 @@ public class MainLayoutController {
|
|||||||
btnProducts,
|
btnProducts,
|
||||||
btnPurchaseOrders,
|
btnPurchaseOrders,
|
||||||
btnStaffAccounts,
|
btnStaffAccounts,
|
||||||
|
btnCustomers,
|
||||||
btnAnalytics,
|
btnAnalytics,
|
||||||
btnActivityLogs,
|
btnActivityLogs,
|
||||||
btnCoupons,
|
btnCoupons,
|
||||||
|
|||||||
@@ -255,6 +255,8 @@ public class SaleController {
|
|||||||
boolean isAdmin = UserSession.getInstance().isAdmin();
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
vbCreateSale.setVisible(!isAdmin);
|
vbCreateSale.setVisible(!isAdmin);
|
||||||
vbCreateSale.setManaged(!isAdmin);
|
vbCreateSale.setManaged(!isAdmin);
|
||||||
|
btnRefund.setVisible(!isAdmin);
|
||||||
|
btnRefund.setManaged(!isAdmin);
|
||||||
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
|
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,16 +8,11 @@ import javafx.event.ActionEvent;
|
|||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.TableColumn;
|
|
||||||
import javafx.scene.control.TableView;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.example.petshopdesktop.api.dto.user.UserResponse;
|
import org.example.petshopdesktop.api.dto.user.UserResponse;
|
||||||
import org.example.petshopdesktop.api.endpoints.CustomerApi;
|
|
||||||
import org.example.petshopdesktop.api.endpoints.UserApi;
|
import org.example.petshopdesktop.api.endpoints.UserApi;
|
||||||
import org.example.petshopdesktop.auth.UserSession;
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
@@ -32,36 +27,6 @@ public class StaffAccountsController {
|
|||||||
@FXML
|
@FXML
|
||||||
private VBox staffSection;
|
private VBox staffSection;
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TableView<UserResponse> tvCustomers;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TableColumn<UserResponse, String> colCustomerUsername;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TableColumn<UserResponse, String> colCustomerName;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TableColumn<UserResponse, String> colCustomerEmail;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TableColumn<UserResponse, String> colCustomerPhone;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TableColumn<UserResponse, Object> colCustomerLoyaltyPoints;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TableColumn<UserResponse, String> colCustomerStatus;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TableColumn<UserResponse, Object> colCustomerCreated;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private TextField txtSearchCustomer;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Button btnEditCustomer;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private TableView<UserResponse> tvStaff;
|
private TableView<UserResponse> tvStaff;
|
||||||
|
|
||||||
@@ -104,32 +69,11 @@ public class StaffAccountsController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Label lblStatus;
|
private Label lblStatus;
|
||||||
|
|
||||||
private final ObservableList<UserResponse> customerAccounts = FXCollections.observableArrayList();
|
|
||||||
private FilteredList<UserResponse> filteredCustomers;
|
|
||||||
|
|
||||||
private final ObservableList<UserResponse> staffAccounts = FXCollections.observableArrayList();
|
private final ObservableList<UserResponse> staffAccounts = FXCollections.observableArrayList();
|
||||||
private FilteredList<UserResponse> filteredStaff;
|
private FilteredList<UserResponse> filteredStaff;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
colCustomerUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername()));
|
|
||||||
colCustomerName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName()));
|
|
||||||
colCustomerEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail()));
|
|
||||||
colCustomerPhone.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getPhone()));
|
|
||||||
colCustomerLoyaltyPoints.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getLoyaltyPoints()));
|
|
||||||
colCustomerStatus.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getActive() != null && data.getValue().getActive() ? "Active" : "Inactive"));
|
|
||||||
colCustomerCreated.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getCreatedAt()));
|
|
||||||
|
|
||||||
filteredCustomers = new FilteredList<>(customerAccounts, a -> true);
|
|
||||||
TableViewSupport.bindSortedItems(tvCustomers, filteredCustomers);
|
|
||||||
TableViewSupport.installDoubleClickAction(tvCustomers, this::openEditDialog);
|
|
||||||
|
|
||||||
tvCustomers.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) ->
|
|
||||||
btnEditCustomer.setDisable(newVal == null));
|
|
||||||
btnEditCustomer.setDisable(true);
|
|
||||||
|
|
||||||
txtSearchCustomer.textProperty().addListener((obs, o, n) -> applyCustomerFilter(n));
|
|
||||||
|
|
||||||
colUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername()));
|
colUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername()));
|
||||||
colName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName()));
|
colName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName()));
|
||||||
colEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail()));
|
colEmail.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getEmail()));
|
||||||
@@ -148,29 +92,17 @@ public class StaffAccountsController {
|
|||||||
|
|
||||||
txtSearch.textProperty().addListener((obs, o, n) -> applyStaffFilter(n));
|
txtSearch.textProperty().addListener((obs, o, n) -> applyStaffFilter(n));
|
||||||
|
|
||||||
boolean isAdmin = UserSession.getInstance().isAdmin();
|
|
||||||
staffSection.setVisible(isAdmin);
|
|
||||||
staffSection.setManaged(isAdmin);
|
|
||||||
|
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnRefreshClicked(ActionEvent event) {
|
void btnRefreshClicked(ActionEvent event) {
|
||||||
txtSearchCustomer.clear();
|
|
||||||
txtSearch.clear();
|
txtSearch.clear();
|
||||||
TableViewSupport.clearSort(tvCustomers);
|
|
||||||
TableViewSupport.clearSort(tvStaff);
|
TableViewSupport.clearSort(tvStaff);
|
||||||
refresh();
|
refresh();
|
||||||
TableViewSupport.flashStatus(lblStatus, "Refreshed");
|
TableViewSupport.flashStatus(lblStatus, "Refreshed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
|
||||||
void btnEditCustomerClicked(ActionEvent event) {
|
|
||||||
lblError.setText("");
|
|
||||||
openEditDialog(tvCustomers.getSelectionModel().getSelectedItem());
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void btnCreateAccountClicked(ActionEvent event) {
|
void btnCreateAccountClicked(ActionEvent event) {
|
||||||
lblError.setText("");
|
lblError.setText("");
|
||||||
@@ -214,10 +146,9 @@ public class StaffAccountsController {
|
|||||||
try {
|
try {
|
||||||
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml"));
|
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml"));
|
||||||
Stage dialog = new Stage();
|
Stage dialog = new Stage();
|
||||||
Stage owner = (tvStaff.getScene() != null) ? (Stage) tvStaff.getScene().getWindow() : (Stage) tvCustomers.getScene().getWindow();
|
dialog.initOwner(tvStaff.getScene().getWindow());
|
||||||
dialog.initOwner(owner);
|
|
||||||
dialog.initModality(Modality.APPLICATION_MODAL);
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
dialog.setTitle("Edit User Account");
|
dialog.setTitle("Edit Staff Account");
|
||||||
dialog.setScene(new Scene(loader.load()));
|
dialog.setScene(new Scene(loader.load()));
|
||||||
dialog.setResizable(false);
|
dialog.setResizable(false);
|
||||||
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
|
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
|
||||||
@@ -232,7 +163,6 @@ public class StaffAccountsController {
|
|||||||
|
|
||||||
private void refresh() {
|
private void refresh() {
|
||||||
lblError.setText("");
|
lblError.setText("");
|
||||||
tvCustomers.setDisable(true);
|
|
||||||
tvStaff.setDisable(true);
|
tvStaff.setDisable(true);
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
@@ -240,60 +170,25 @@ public class StaffAccountsController {
|
|||||||
Comparator<UserResponse> byCreated = Comparator.comparing(
|
Comparator<UserResponse> byCreated = Comparator.comparing(
|
||||||
UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
|
UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
|
||||||
|
|
||||||
final List<UserResponse> customers;
|
List<UserResponse> staff = UserApi.getInstance().listUsers(null).stream()
|
||||||
final List<UserResponse> staff;
|
.filter(u -> !"CUSTOMER".equalsIgnoreCase(u.getRole()))
|
||||||
|
.sorted(byCreated)
|
||||||
if (UserSession.getInstance().isAdmin()) {
|
.collect(Collectors.toList());
|
||||||
List<UserResponse> allUsers = UserApi.getInstance().listUsers(null);
|
|
||||||
customers = allUsers.stream()
|
|
||||||
.filter(u -> "CUSTOMER".equalsIgnoreCase(u.getRole()))
|
|
||||||
.sorted(byCreated)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
staff = allUsers.stream()
|
|
||||||
.filter(u -> !"CUSTOMER".equalsIgnoreCase(u.getRole()))
|
|
||||||
.sorted(byCreated)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
} else {
|
|
||||||
customers = CustomerApi.getInstance().listCustomers(null).stream()
|
|
||||||
.sorted(byCreated)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
staff = List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
customerAccounts.setAll(customers);
|
|
||||||
staffAccounts.setAll(staff);
|
staffAccounts.setAll(staff);
|
||||||
tvCustomers.setDisable(false);
|
|
||||||
tvStaff.setDisable(false);
|
tvStaff.setDisable(false);
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading user accounts");
|
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
String message = e.getMessage();
|
lblError.setText("Could not load staff accounts.");
|
||||||
lblError.setText(message == null || message.isBlank()
|
|
||||||
? "Could not load user accounts."
|
|
||||||
: "Could not load user accounts: " + message);
|
|
||||||
tvCustomers.setDisable(false);
|
|
||||||
tvStaff.setDisable(false);
|
tvStaff.setDisable(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyStaffFilter(String text) {
|
private void applyStaffFilter(String text) {
|
||||||
String q = text == null ? "" : text.trim().toLowerCase();
|
String q = text == null ? "" : text.trim().toLowerCase();
|
||||||
if (q.isEmpty()) {
|
if (q.isEmpty()) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import javafx.fxml.FXMLLoader;
|
|||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
import javafx.stage.Modality;
|
import javafx.stage.Modality;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
|
import org.example.petshopdesktop.auth.UserSession;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -46,6 +47,12 @@ public class SaleDetailDialogController {
|
|||||||
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
|
||||||
colUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
colUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
|
||||||
colLineTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
colLineTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
|
||||||
|
|
||||||
|
if (btnRefund != null) {
|
||||||
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
|
btnRefund.setVisible(!isAdmin);
|
||||||
|
btnRefund.setManaged(!isAdmin);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void displaySaleDetails(SaleDetail sale) {
|
public void displaySaleDetails(SaleDetail sale) {
|
||||||
@@ -57,7 +64,10 @@ public class SaleDetailDialogController {
|
|||||||
lblTotal.setText(currency.format(sale.getTotalAmount()));
|
lblTotal.setText(currency.format(sale.getTotalAmount()));
|
||||||
tvItems.setItems(sale.getItems());
|
tvItems.setItems(sale.getItems());
|
||||||
if (btnRefund != null) {
|
if (btnRefund != null) {
|
||||||
btnRefund.setDisable(sale.isRefund());
|
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||||
|
if (!isAdmin) {
|
||||||
|
btnRefund.setDisable(sale.isRefund());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,6 +176,14 @@
|
|||||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button fx:id="btnCustomers" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnCustomersClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Customers" textFill="#cbd5e1">
|
||||||
|
<font>
|
||||||
|
<Font name="System" size="12.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Separator fx:id="separatorAdmin" prefWidth="200.0" style="-fx-background-color: #444444; -fx-opacity: 0.35;" />
|
<Separator fx:id="separatorAdmin" prefWidth="200.0" style="-fx-background-color: #444444; -fx-opacity: 0.35;" />
|
||||||
|
|
||||||
@@ -220,7 +228,7 @@
|
|||||||
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</Button>
|
</Button>
|
||||||
<Button fx:id="btnStaffAccounts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnStaffAccountsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="User Accounts" textFill="#cbd5e1">
|
<Button fx:id="btnStaffAccounts" alignment="CENTER_LEFT" maxWidth="Infinity" mnemonicParsing="false" onAction="#btnStaffAccountsClicked" style="-fx-background-color: transparent; -fx-background-radius: 8; -fx-cursor: hand; -fx-focus-color: transparent; -fx-faint-focus-color: transparent;" text="Staff Accounts" textFill="#cbd5e1">
|
||||||
<font>
|
<font>
|
||||||
<Font name="System" size="12.0" />
|
<Font name="System" size="12.0" />
|
||||||
</font>
|
</font>
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.TableColumn?>
|
||||||
|
<?import javafx.scene.control.TableView?>
|
||||||
|
<?import javafx.scene.control.TextField?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
|
<VBox spacing="16.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.CustomerAccountsController">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</padding>
|
||||||
|
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||||
|
<children>
|
||||||
|
<Label text="Customers" textFill="#2c3e50">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="30.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="btnRefresh" mnemonicParsing="false" onAction="#btnRefreshClicked" prefHeight="44.0" prefWidth="118.0" style="-fx-background-color: #4ECDC4; -fx-cursor: hand; -fx-background-radius: 8;" text="Refresh" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<VBox spacing="10.0" VBox.vgrow="ALWAYS">
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="12.0">
|
||||||
|
<children>
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<Button fx:id="btnEditCustomer" mnemonicParsing="false" onAction="#btnEditCustomerClicked" prefHeight="40.0" style="-fx-background-color: #F4A261; -fx-cursor: hand; -fx-background-radius: 8;" text="Edit Customer" textFill="WHITE">
|
||||||
|
<font>
|
||||||
|
<Font name="System Bold" size="14.0" />
|
||||||
|
</font>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 1; -fx-border-radius: 14; -fx-border-color: #e6e6e6;">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
|
||||||
|
</padding>
|
||||||
|
<children>
|
||||||
|
<TextField fx:id="txtSearchCustomer" promptText="Search customers..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
||||||
|
<font>
|
||||||
|
<Font size="15.0" />
|
||||||
|
</font>
|
||||||
|
</TextField>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<TableView fx:id="tvCustomers" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||||
|
<columns>
|
||||||
|
<TableColumn fx:id="colCustomerUsername" prefWidth="130.0" text="Username" />
|
||||||
|
<TableColumn fx:id="colCustomerName" prefWidth="160.0" text="Name" />
|
||||||
|
<TableColumn fx:id="colCustomerEmail" prefWidth="200.0" text="Email" />
|
||||||
|
<TableColumn fx:id="colCustomerPhone" prefWidth="130.0" text="Phone" />
|
||||||
|
<TableColumn fx:id="colCustomerLoyaltyPoints" prefWidth="120.0" text="Loyalty Points" />
|
||||||
|
<TableColumn fx:id="colCustomerStatus" prefWidth="90.0" text="Status" />
|
||||||
|
<TableColumn fx:id="colCustomerCreated" prefWidth="150.0" text="Created" />
|
||||||
|
</columns>
|
||||||
|
</TableView>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
|
|
||||||
|
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true">
|
||||||
|
<font>
|
||||||
|
<Font size="13.0" />
|
||||||
|
</font>
|
||||||
|
</Label>
|
||||||
|
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true" />
|
||||||
|
</children>
|
||||||
|
</VBox>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
</HBox>
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
<FlowPane hgap="8.0" maxWidth="Infinity" prefWrapLength="260.0" vgap="8.0">
|
<HBox alignment="CENTER_RIGHT" spacing="8.0">
|
||||||
<children>
|
<children>
|
||||||
<Button fx:id="btnRefund" mnemonicParsing="false" onAction="#btnRefund" prefHeight="32.0" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
|
<Button fx:id="btnRefund" mnemonicParsing="false" onAction="#btnRefund" prefHeight="32.0" style="-fx-background-color: #FF6b6b; -fx-cursor: hand; -fx-background-radius: 8;" text="Process Refund" textFill="WHITE">
|
||||||
<font>
|
<font>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
</padding>
|
</padding>
|
||||||
</Button>
|
</Button>
|
||||||
</children>
|
</children>
|
||||||
</FlowPane>
|
</HBox>
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<children>
|
<children>
|
||||||
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
||||||
<children>
|
<children>
|
||||||
<Label text="User Accounts" textFill="#2c3e50">
|
<Label text="Staff Accounts" textFill="#2c3e50">
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="30.0" />
|
<Font name="System Bold" size="30.0" />
|
||||||
</font>
|
</font>
|
||||||
@@ -36,65 +36,10 @@
|
|||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<!-- Customers Section -->
|
<VBox fx:id="staffSection" spacing="10.0" VBox.vgrow="ALWAYS">
|
||||||
<VBox spacing="10.0" VBox.vgrow="ALWAYS">
|
|
||||||
<children>
|
<children>
|
||||||
<HBox alignment="CENTER_LEFT" spacing="12.0">
|
<HBox alignment="CENTER_LEFT" spacing="12.0">
|
||||||
<children>
|
<children>
|
||||||
<Label text="Customers" textFill="#2c3e50">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="20.0" />
|
|
||||||
</font>
|
|
||||||
</Label>
|
|
||||||
<Region HBox.hgrow="ALWAYS" />
|
|
||||||
<Button fx:id="btnEditCustomer" mnemonicParsing="false" onAction="#btnEditCustomerClicked" prefHeight="40.0" style="-fx-background-color: #F4A261; -fx-cursor: hand; -fx-background-radius: 8;" text="Edit Customer" textFill="WHITE">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="14.0" />
|
|
||||||
</font>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="10.0" left="20.0" right="20.0" top="10.0" />
|
|
||||||
</padding>
|
|
||||||
</Button>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
|
|
||||||
<HBox alignment="CENTER_LEFT" spacing="10.0" style="-fx-background-color: white; -fx-background-radius: 14; -fx-border-width: 1; -fx-border-radius: 14; -fx-border-color: #e6e6e6;">
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0" />
|
|
||||||
</padding>
|
|
||||||
<children>
|
|
||||||
<TextField fx:id="txtSearchCustomer" promptText="Search customers..." style="-fx-border-width: 0; -fx-background-color: transparent;" HBox.hgrow="ALWAYS">
|
|
||||||
<font>
|
|
||||||
<Font size="15.0" />
|
|
||||||
</font>
|
|
||||||
</TextField>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
|
|
||||||
<TableView fx:id="tvCustomers" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
|
||||||
<columns>
|
|
||||||
<TableColumn fx:id="colCustomerUsername" prefWidth="130.0" text="Username" />
|
|
||||||
<TableColumn fx:id="colCustomerName" prefWidth="160.0" text="Name" />
|
|
||||||
<TableColumn fx:id="colCustomerEmail" prefWidth="200.0" text="Email" />
|
|
||||||
<TableColumn fx:id="colCustomerPhone" prefWidth="130.0" text="Phone" />
|
|
||||||
<TableColumn fx:id="colCustomerLoyaltyPoints" prefWidth="120.0" text="Loyalty Points" />
|
|
||||||
<TableColumn fx:id="colCustomerStatus" prefWidth="90.0" text="Status" />
|
|
||||||
<TableColumn fx:id="colCustomerCreated" prefWidth="150.0" text="Created" />
|
|
||||||
</columns>
|
|
||||||
</TableView>
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
|
|
||||||
<!-- Staff Section (admin only) -->
|
|
||||||
<VBox fx:id="staffSection" spacing="10.0" managed="false" visible="false" VBox.vgrow="ALWAYS">
|
|
||||||
<children>
|
|
||||||
<HBox alignment="CENTER_LEFT" spacing="12.0">
|
|
||||||
<children>
|
|
||||||
<Label text="Staff" textFill="#2c3e50">
|
|
||||||
<font>
|
|
||||||
<Font name="System Bold" size="20.0" />
|
|
||||||
</font>
|
|
||||||
</Label>
|
|
||||||
<Region HBox.hgrow="ALWAYS" />
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
<Button fx:id="btnCreateAccount" mnemonicParsing="false" onAction="#btnCreateAccountClicked" prefHeight="40.0" style="-fx-background-color: #FF6B6B; -fx-cursor: hand; -fx-background-radius: 8;" text="Create Account" textFill="WHITE">
|
<Button fx:id="btnCreateAccount" mnemonicParsing="false" onAction="#btnCreateAccountClicked" prefHeight="40.0" style="-fx-background-color: #FF6B6B; -fx-cursor: hand; -fx-background-radius: 8;" text="Create Account" textFill="WHITE">
|
||||||
<font>
|
<font>
|
||||||
|
|||||||
Reference in New Issue
Block a user