seperated staff and customer on desktop

This commit is contained in:
Alex
2026-04-13 22:15:41 -06:00
parent c38bb24e94
commit d898732a17
9 changed files with 313 additions and 177 deletions

View File

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

View File

@@ -89,6 +89,9 @@ public class MainLayoutController {
@FXML
private Button btnStaffAccounts;
@FXML
private Button btnCustomers;
@FXML
private Button btnAnalytics;
@@ -179,6 +182,12 @@ public class MainLayoutController {
updateButtons(btnStaffAccounts);
}
@FXML
void btnCustomersClicked(ActionEvent event) {
loadView("customer-accounts-view.fxml");
updateButtons(btnCustomers);
}
@FXML
void btnAnalyticsClicked(ActionEvent event) {
loadView("analytics-view.fxml");
@@ -415,8 +424,13 @@ public class MainLayoutController {
btnPurchaseOrders.setManaged(isAdmin);
if (btnStaffAccounts != null) {
btnStaffAccounts.setVisible(true);
btnStaffAccounts.setManaged(true);
btnStaffAccounts.setVisible(isAdmin);
btnStaffAccounts.setManaged(isAdmin);
}
if (btnCustomers != null) {
btnCustomers.setVisible(true);
btnCustomers.setManaged(true);
}
if (lblAdminSection != null) {
@@ -493,6 +507,7 @@ public class MainLayoutController {
btnProducts,
btnPurchaseOrders,
btnStaffAccounts,
btnCustomers,
btnAnalytics,
btnActivityLogs,
btnCoupons,

View File

@@ -255,6 +255,8 @@ public class SaleController {
boolean isAdmin = UserSession.getInstance().isAdmin();
vbCreateSale.setVisible(!isAdmin);
vbCreateSale.setManaged(!isAdmin);
btnRefund.setVisible(!isAdmin);
btnRefund.setManaged(!isAdmin);
lblModeNote.setText(isAdmin ? "(View only)" : "(Staff can create sales)");
}

View File

@@ -8,16 +8,11 @@ import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
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.control.*;
import javafx.scene.layout.VBox;
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.api.endpoints.UserApi;
import org.example.petshopdesktop.auth.UserSession;
import org.example.petshopdesktop.util.ActivityLogger;
@@ -32,36 +27,6 @@ public class StaffAccountsController {
@FXML
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
private TableView<UserResponse> tvStaff;
@@ -104,32 +69,11 @@ public class StaffAccountsController {
@FXML
private Label lblStatus;
private final ObservableList<UserResponse> customerAccounts = FXCollections.observableArrayList();
private FilteredList<UserResponse> filteredCustomers;
private final ObservableList<UserResponse> staffAccounts = FXCollections.observableArrayList();
private FilteredList<UserResponse> filteredStaff;
@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));
colUsername.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getUsername()));
colName.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(data.getValue().getFullName()));
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));
boolean isAdmin = UserSession.getInstance().isAdmin();
staffSection.setVisible(isAdmin);
staffSection.setManaged(isAdmin);
refresh();
}
@FXML
void btnRefreshClicked(ActionEvent event) {
txtSearchCustomer.clear();
txtSearch.clear();
TableViewSupport.clearSort(tvCustomers);
TableViewSupport.clearSort(tvStaff);
refresh();
TableViewSupport.flashStatus(lblStatus, "Refreshed");
}
@FXML
void btnEditCustomerClicked(ActionEvent event) {
lblError.setText("");
openEditDialog(tvCustomers.getSelectionModel().getSelectedItem());
}
@FXML
void btnCreateAccountClicked(ActionEvent event) {
lblError.setText("");
@@ -214,10 +146,9 @@ public class StaffAccountsController {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml"));
Stage dialog = new Stage();
Stage owner = (tvStaff.getScene() != null) ? (Stage) tvStaff.getScene().getWindow() : (Stage) tvCustomers.getScene().getWindow();
dialog.initOwner(owner);
dialog.initOwner(tvStaff.getScene().getWindow());
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle("Edit User Account");
dialog.setTitle("Edit Staff Account");
dialog.setScene(new Scene(loader.load()));
dialog.setResizable(false);
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
@@ -232,7 +163,6 @@ public class StaffAccountsController {
private void refresh() {
lblError.setText("");
tvCustomers.setDisable(true);
tvStaff.setDisable(true);
new Thread(() -> {
@@ -240,60 +170,25 @@ public class StaffAccountsController {
Comparator<UserResponse> byCreated = Comparator.comparing(
UserResponse::getCreatedAt, Comparator.nullsLast(Comparator.reverseOrder()));
final List<UserResponse> customers;
final List<UserResponse> staff;
if (UserSession.getInstance().isAdmin()) {
List<UserResponse> allUsers = UserApi.getInstance().listUsers(null);
customers = allUsers.stream()
.filter(u -> "CUSTOMER".equalsIgnoreCase(u.getRole()))
.sorted(byCreated)
.collect(Collectors.toList());
staff = allUsers.stream()
List<UserResponse> staff = UserApi.getInstance().listUsers(null).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(() -> {
customerAccounts.setAll(customers);
staffAccounts.setAll(staff);
tvCustomers.setDisable(false);
tvStaff.setDisable(false);
});
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading user accounts");
ActivityLogger.getInstance().logException("StaffAccountsController.refresh", e, "Loading staff accounts");
Platform.runLater(() -> {
String message = e.getMessage();
lblError.setText(message == null || message.isBlank()
? "Could not load user accounts."
: "Could not load user accounts: " + message);
tvCustomers.setDisable(false);
lblError.setText("Could not load staff accounts.");
tvStaff.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 void applyStaffFilter(String text) {
String q = text == null ? "" : text.trim().toLowerCase();
if (q.isEmpty()) {

View File

@@ -13,6 +13,7 @@ import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Modality;
import org.example.petshopdesktop.util.ActivityLogger;
import org.example.petshopdesktop.auth.UserSession;
import java.util.function.Consumer;
@@ -46,6 +47,12 @@ public class SaleDetailDialogController {
colQuantity.setCellValueFactory(new PropertyValueFactory<>("quantity"));
colUnitPrice.setCellValueFactory(new PropertyValueFactory<>("unitPrice"));
colLineTotal.setCellValueFactory(new PropertyValueFactory<>("total"));
if (btnRefund != null) {
boolean isAdmin = UserSession.getInstance().isAdmin();
btnRefund.setVisible(!isAdmin);
btnRefund.setManaged(!isAdmin);
}
}
public void displaySaleDetails(SaleDetail sale) {
@@ -57,9 +64,12 @@ public class SaleDetailDialogController {
lblTotal.setText(currency.format(sale.getTotalAmount()));
tvItems.setItems(sale.getItems());
if (btnRefund != null) {
boolean isAdmin = UserSession.getInstance().isAdmin();
if (!isAdmin) {
btnRefund.setDisable(sale.isRefund());
}
}
}
public void setSaleId(Long saleId) {
this.saleId = saleId;

View File

@@ -176,6 +176,14 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</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;" />
@@ -220,7 +228,7 @@
<Insets bottom="8.0" left="10.0" right="10.0" top="8.0" />
</padding>
</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 name="System" size="12.0" />
</font>

View File

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

View File

@@ -48,7 +48,7 @@
</HBox>
</children>
</VBox>
<FlowPane hgap="8.0" maxWidth="Infinity" prefWrapLength="260.0" vgap="8.0">
<HBox alignment="CENTER_RIGHT" spacing="8.0">
<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">
<font>
@@ -67,7 +67,7 @@
</padding>
</Button>
</children>
</FlowPane>
</HBox>
</children>
</HBox>

View File

@@ -19,7 +19,7 @@
<children>
<HBox alignment="CENTER_LEFT" spacing="20.0">
<children>
<Label text="User Accounts" textFill="#2c3e50">
<Label text="Staff Accounts" textFill="#2c3e50">
<font>
<Font name="System Bold" size="30.0" />
</font>
@@ -36,65 +36,10 @@
</children>
</HBox>
<!-- Customers Section -->
<VBox spacing="10.0" VBox.vgrow="ALWAYS">
<VBox fx:id="staffSection" spacing="10.0" VBox.vgrow="ALWAYS">
<children>
<HBox alignment="CENTER_LEFT" spacing="12.0">
<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" />
<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>