edit staff accounts

This commit is contained in:
2026-03-14 21:35:38 -06:00
parent 4b024984cd
commit 730d39c63e
6 changed files with 303 additions and 2 deletions

View File

@@ -41,4 +41,8 @@ public class EmployeeApi {
public EmployeeResponse createEmployee(EmployeeRequest request) throws Exception { public EmployeeResponse createEmployee(EmployeeRequest request) throws Exception {
return apiClient.post("/api/v1/employees", request, EmployeeResponse.class); return apiClient.post("/api/v1/employees", request, EmployeeResponse.class);
} }
public EmployeeResponse updateEmployee(Long id, EmployeeRequest request) throws Exception {
return apiClient.put("/api/v1/employees/" + id, request, EmployeeResponse.class);
}
} }

View File

@@ -59,6 +59,9 @@ public class StaffAccountsController {
@FXML @FXML
private Button btnCreateAccount; private Button btnCreateAccount;
@FXML
private Button btnEditAccount;
private final ObservableList<StaffAccount> staffAccounts = FXCollections.observableArrayList(); private final ObservableList<StaffAccount> staffAccounts = FXCollections.observableArrayList();
private FilteredList<StaffAccount> filtered; private FilteredList<StaffAccount> filtered;
@@ -76,13 +79,26 @@ public class StaffAccountsController {
txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n)); txtSearch.textProperty().addListener((obs, o, n) -> applyFilter(n));
tvStaff.getSelectionModel().selectedItemProperty().addListener((obs, oldValue, newValue) -> {
if (btnEditAccount != null) {
btnEditAccount.setDisable(newValue == null);
}
});
if (!UserSession.getInstance().isAdmin()) { if (!UserSession.getInstance().isAdmin()) {
lblError.setText("Access restricted."); lblError.setText("Access restricted.");
tvStaff.setDisable(true); tvStaff.setDisable(true);
btnCreateAccount.setDisable(true); btnCreateAccount.setDisable(true);
if (btnEditAccount != null) {
btnEditAccount.setDisable(true);
}
return; return;
} }
if (btnEditAccount != null) {
btnEditAccount.setDisable(true);
}
refresh(); refresh();
} }
@@ -110,6 +126,32 @@ public class StaffAccountsController {
} }
} }
@FXML
void btnEditAccountClicked(ActionEvent event) {
lblError.setText("");
StaffAccount selected = tvStaff.getSelectionModel().getSelectedItem();
if (selected == null) {
lblError.setText("Select a staff account 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(tvStaff.getScene().getWindow());
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle("Edit Staff Account");
dialog.setScene(new Scene(loader.load()));
dialog.setResizable(false);
var controller = (org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController) loader.getController();
controller.setStaffAccount(selected);
dialog.showAndWait();
refresh();
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffAccountsController.btnEditAccountClicked", e, "Opening staff edit dialog");
lblError.setText("Could not open staff account editor.");
}
}
private void refresh() { private void refresh() {
lblError.setText(""); lblError.setText("");
tvStaff.setDisable(true); tvStaff.setDisable(true);
@@ -145,12 +187,13 @@ public class StaffAccountsController {
String lastName = names[1]; String lastName = names[1];
String email = employee.getEmail() != null ? employee.getEmail() : ""; String email = employee.getEmail() != null ? employee.getEmail() : "";
String phone = employee.getPhone() != null ? employee.getPhone() : ""; String phone = employee.getPhone() != null ? employee.getPhone() : "";
String role = employee.getRole() != null ? employee.getRole() : "STAFF";
boolean active = employee.getActive() != null ? employee.getActive() : false; boolean active = employee.getActive() != null ? employee.getActive() : false;
Timestamp createdAt = employee.getCreatedAt() != null Timestamp createdAt = employee.getCreatedAt() != null
? Timestamp.from(employee.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant()) ? Timestamp.from(employee.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant())
: null; : null;
return new StaffAccount(userId, employeeId, username, firstName, lastName, email, phone, active, createdAt); return new StaffAccount(userId, employeeId, username, firstName, lastName, email, phone, role, active, createdAt);
} }
private String[] splitFullName(String fullName) { private String[] splitFullName(String fullName) {

View File

@@ -0,0 +1,144 @@
package org.example.petshopdesktop.controllers.dialogcontrollers;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.stage.Stage;
import org.example.petshopdesktop.Validator;
import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
import org.example.petshopdesktop.models.StaffAccount;
import org.example.petshopdesktop.util.ActivityLogger;
public class StaffEditDialogController {
@FXML
private TextField txtFirstName;
@FXML
private TextField txtLastName;
@FXML
private TextField txtEmail;
@FXML
private TextField txtPhone;
@FXML
private TextField txtUsername;
@FXML
private PasswordField txtPassword;
@FXML
private PasswordField txtPasswordConfirm;
@FXML
private Label lblError;
@FXML
private Button btnSave;
private StaffAccount staffAccount;
public void setStaffAccount(StaffAccount staffAccount) {
this.staffAccount = staffAccount;
txtFirstName.setText(staffAccount.getFirstName());
txtLastName.setText(staffAccount.getLastName());
txtEmail.setText(staffAccount.getEmail());
txtPhone.setText(staffAccount.getPhone());
txtUsername.setText(staffAccount.getUsername());
}
@FXML
void btnSaveClicked(ActionEvent event) {
lblError.setText("");
if (staffAccount == null) {
lblError.setText("No staff account selected.");
return;
}
String firstName = value(txtFirstName);
String lastName = value(txtLastName);
String email = value(txtEmail);
String phone = value(txtPhone);
String username = value(txtUsername);
String password = txtPassword.getText() == null ? "" : txtPassword.getText().trim();
String confirm = txtPasswordConfirm.getText() == null ? "" : txtPasswordConfirm.getText().trim();
if (firstName.isBlank() || lastName.isBlank()) {
lblError.setText("First name and last name are required.");
return;
}
if (email.isBlank()) {
lblError.setText("Email is required.");
return;
}
if (phone.isBlank()) {
lblError.setText("Phone is required.");
return;
}
String phoneError = Validator.isValidPhoneNumber(phone, "Phone");
if (!phoneError.isEmpty()) {
lblError.setText(phoneError.trim());
return;
}
if (username.isBlank()) {
lblError.setText("Username is required.");
return;
}
if (!password.isEmpty() && password.length() < 6) {
lblError.setText("Password must be at least 6 characters.");
return;
}
if (!password.equals(confirm)) {
lblError.setText("Passwords do not match.");
return;
}
btnSave.setDisable(true);
new Thread(() -> {
try {
EmployeeRequest request = new EmployeeRequest();
request.setUsername(username);
request.setPassword(password.isEmpty() ? null : password);
request.setFirstName(firstName);
request.setLastName(lastName);
request.setEmail(email);
request.setPhone(phone);
request.setRole(staffAccount.getRole());
request.setActive(staffAccount.isActive());
EmployeeApi.getInstance().updateEmployee(staffAccount.getEmployeeId(), request);
Platform.runLater(this::close);
} catch (Exception e) {
ActivityLogger.getInstance().logException("StaffEditDialogController.btnSaveClicked", e, "Updating staff account");
String msg = e.getMessage() == null ? "Could not update staff account." : e.getMessage();
Platform.runLater(() -> {
lblError.setText(msg);
btnSave.setDisable(false);
});
}
}).start();
}
@FXML
void btnCancelClicked(ActionEvent event) {
close();
}
private void close() {
Stage stage = (Stage) btnSave.getScene().getWindow();
stage.close();
}
private static String value(TextField tf) {
return tf.getText() == null ? "" : tf.getText().trim();
}
}

View File

@@ -10,10 +10,11 @@ public class StaffAccount {
private final String lastName; private final String lastName;
private final String email; private final String email;
private final String phone; private final String phone;
private final String role;
private final boolean active; private final boolean active;
private final Timestamp createdAt; private final Timestamp createdAt;
public StaffAccount(long userId, long employeeId, String username, String firstName, String lastName, String email, String phone, boolean active, Timestamp createdAt) { public StaffAccount(long userId, long employeeId, String username, String firstName, String lastName, String email, String phone, String role, boolean active, Timestamp createdAt) {
this.userId = userId; this.userId = userId;
this.employeeId = employeeId; this.employeeId = employeeId;
this.username = username; this.username = username;
@@ -21,6 +22,7 @@ public class StaffAccount {
this.lastName = lastName; this.lastName = lastName;
this.email = email; this.email = email;
this.phone = phone; this.phone = phone;
this.role = role;
this.active = active; this.active = active;
this.createdAt = createdAt; this.createdAt = createdAt;
} }
@@ -59,6 +61,10 @@ public class StaffAccount {
return phone; return phone;
} }
public String getRole() {
return role;
}
public boolean isActive() { public boolean isActive() {
return active; return active;
} }

View File

@@ -0,0 +1,96 @@
<?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.PasswordField?>
<?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="14.0" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.dialogcontrollers.StaffEditDialogController">
<padding>
<Insets bottom="18.0" left="18.0" right="18.0" top="18.0" />
</padding>
<children>
<Label text="Edit Staff Account" textFill="#2C3E50">
<font>
<Font name="System Bold" size="18.0" />
</font>
</Label>
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true" />
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="First Name" />
<TextField fx:id="txtFirstName" promptText="First name" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Last Name" />
<TextField fx:id="txtLastName" promptText="Last name" />
</children>
</VBox>
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Email" />
<TextField fx:id="txtEmail" promptText="name@petshop.com" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Phone" />
<TextField fx:id="txtPhone" promptText="123-456-7890" />
</children>
</VBox>
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Username" />
<TextField fx:id="txtUsername" promptText="username" />
</children>
</VBox>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="New Password" />
<PasswordField fx:id="txtPassword" promptText="leave blank to keep current" />
</children>
</VBox>
</children>
</HBox>
<HBox spacing="10.0">
<children>
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
<children>
<Label text="Confirm Password" />
<PasswordField fx:id="txtPasswordConfirm" promptText="confirm new password" />
</children>
</VBox>
<Region HBox.hgrow="ALWAYS" />
</children>
</HBox>
<HBox alignment="CENTER_RIGHT" spacing="10.0">
<children>
<Button mnemonicParsing="false" onAction="#btnCancelClicked" text="Cancel" />
<Button fx:id="btnSave" mnemonicParsing="false" onAction="#btnSaveClicked" style="-fx-background-color: #4ECDC4; -fx-text-fill: white; -fx-cursor: hand;" text="Save" />
</children>
</HBox>
</children>
</VBox>

View File

@@ -33,6 +33,14 @@
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" /> <Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
</padding> </padding>
</Button> </Button>
<Button fx:id="btnEditAccount" mnemonicParsing="false" onAction="#btnEditAccountClicked" prefHeight="44.0" style="-fx-background-color: #F4A261; -fx-cursor: hand; -fx-background-radius: 8;" text="Edit Account" 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>
<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"> <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>
<Font name="System Bold" size="14.0" /> <Font name="System Bold" size="14.0" />