diff --git a/src/main/java/org/example/petshopdesktop/api/dto/auth/UserInfoResponse.java b/src/main/java/org/example/petshopdesktop/api/dto/auth/UserInfoResponse.java index 4fc49442..fe83893c 100644 --- a/src/main/java/org/example/petshopdesktop/api/dto/auth/UserInfoResponse.java +++ b/src/main/java/org/example/petshopdesktop/api/dto/auth/UserInfoResponse.java @@ -5,6 +5,7 @@ public class UserInfoResponse { private String username; private String email; private String fullName; + private String phone; private String avatarUrl; private String role; private Long storeId; @@ -45,6 +46,14 @@ public class UserInfoResponse { this.fullName = fullName; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public String getAvatarUrl() { return avatarUrl; } diff --git a/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java b/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java new file mode 100644 index 00000000..f047f641 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeRequest.java @@ -0,0 +1,29 @@ +package org.example.petshopdesktop.api.dto.employee; + +public class EmployeeRequest { + private String username; + private String password; + private String firstName; + private String lastName; + private String email; + private String phone; + private String role; + private Boolean active; + + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getPassword() { return password; } + public void setPassword(String password) { this.password = password; } + public String getFirstName() { return firstName; } + public void setFirstName(String firstName) { this.firstName = firstName; } + public String getLastName() { return lastName; } + public void setLastName(String lastName) { this.lastName = lastName; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + public Boolean getActive() { return active; } + public void setActive(Boolean active) { this.active = active; } +} diff --git a/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java b/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java new file mode 100644 index 00000000..030488c1 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/api/dto/employee/EmployeeResponse.java @@ -0,0 +1,43 @@ +package org.example.petshopdesktop.api.dto.employee; + +import java.time.LocalDateTime; + +public class EmployeeResponse { + private Long employeeId; + private Long userId; + private String username; + private String firstName; + private String lastName; + private String fullName; + private String email; + private String phone; + private String role; + private Boolean active; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + public Long getEmployeeId() { return employeeId; } + public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; } + public Long getUserId() { return userId; } + public void setUserId(Long userId) { this.userId = userId; } + public String getUsername() { return username; } + public void setUsername(String username) { this.username = username; } + public String getFirstName() { return firstName; } + public void setFirstName(String firstName) { this.firstName = firstName; } + public String getLastName() { return lastName; } + public void setLastName(String lastName) { this.lastName = lastName; } + public String getFullName() { return fullName; } + public void setFullName(String fullName) { this.fullName = fullName; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getPhone() { return phone; } + public void setPhone(String phone) { this.phone = phone; } + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + public Boolean getActive() { return active; } + public void setActive(Boolean active) { this.active = active; } + public LocalDateTime getCreatedAt() { return createdAt; } + public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; } + public LocalDateTime getUpdatedAt() { return updatedAt; } + public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; } +} diff --git a/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java b/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java index 6a1884a8..a6c9f669 100644 --- a/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java +++ b/src/main/java/org/example/petshopdesktop/api/dto/user/UserRequest.java @@ -5,6 +5,7 @@ public class UserRequest { private String password; private String fullName; private String email; + private String phone; private String role; private Boolean active; @@ -43,6 +44,14 @@ public class UserRequest { this.email = email; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public String getRole() { return role; } diff --git a/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java b/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java index 32d997a2..3a42f128 100644 --- a/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java +++ b/src/main/java/org/example/petshopdesktop/api/dto/user/UserResponse.java @@ -7,6 +7,7 @@ public class UserResponse { private String username; private String fullName; private String email; + private String phone; private String role; private Boolean active; private LocalDateTime createdAt; @@ -47,6 +48,14 @@ public class UserResponse { this.email = email; } + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + public String getRole() { return role; } diff --git a/src/main/java/org/example/petshopdesktop/api/endpoints/EmployeeApi.java b/src/main/java/org/example/petshopdesktop/api/endpoints/EmployeeApi.java new file mode 100644 index 00000000..e3a8ad52 --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/api/endpoints/EmployeeApi.java @@ -0,0 +1,48 @@ +package org.example.petshopdesktop.api.endpoints; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.example.petshopdesktop.api.ApiClient; +import org.example.petshopdesktop.api.dto.common.PageResponse; +import org.example.petshopdesktop.api.dto.employee.EmployeeRequest; +import org.example.petshopdesktop.api.dto.employee.EmployeeResponse; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; + +public class EmployeeApi { + private static final EmployeeApi INSTANCE = new EmployeeApi(); + private final ApiClient apiClient; + + private EmployeeApi() { + this.apiClient = ApiClient.getInstance(); + } + + public static EmployeeApi getInstance() { + return INSTANCE; + } + + public List listEmployees(String query) throws Exception { + String path = "/api/v1/employees?page=0&size=1000"; + if (query != null && !query.isEmpty()) { + path += "&q=" + URLEncoder.encode(query, StandardCharsets.UTF_8); + } + String response = apiClient.getRawResponse(path); + PageResponse pageResponse = apiClient.getObjectMapper().readValue( + response, + new TypeReference>() {} + ); + if (pageResponse == null) { + throw new IllegalStateException("Null response from employees endpoint"); + } + return pageResponse.getContent(); + } + + public EmployeeResponse createEmployee(EmployeeRequest request) throws Exception { + 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); + } +} diff --git a/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java b/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java index 59cf5dfb..ffb67aa9 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/StaffAccountsController.java @@ -16,8 +16,8 @@ import javafx.scene.control.TextField; import javafx.scene.control.cell.PropertyValueFactory; import javafx.stage.Modality; import javafx.stage.Stage; -import org.example.petshopdesktop.api.dto.user.UserResponse; -import org.example.petshopdesktop.api.endpoints.UserApi; +import org.example.petshopdesktop.api.dto.employee.EmployeeResponse; +import org.example.petshopdesktop.api.endpoints.EmployeeApi; import org.example.petshopdesktop.auth.UserSession; import org.example.petshopdesktop.models.StaffAccount; import org.example.petshopdesktop.util.ActivityLogger; @@ -59,6 +59,9 @@ public class StaffAccountsController { @FXML private Button btnCreateAccount; + @FXML + private Button btnEditAccount; + private final ObservableList staffAccounts = FXCollections.observableArrayList(); private FilteredList filtered; @@ -76,13 +79,26 @@ public class StaffAccountsController { 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()) { lblError.setText("Access restricted."); tvStaff.setDisable(true); btnCreateAccount.setDisable(true); + if (btnEditAccount != null) { + btnEditAccount.setDisable(true); + } return; } + if (btnEditAccount != null) { + btnEditAccount.setDisable(true); + } + refresh(); } @@ -110,14 +126,40 @@ 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() { lblError.setText(""); tvStaff.setDisable(true); new Thread(() -> { try { - List users = UserApi.getInstance().listUsers(null); - List accounts = users.stream() + List employees = EmployeeApi.getInstance().listEmployees(null); + List accounts = employees.stream() .map(this::mapToStaffAccount) .collect(Collectors.toList()); @@ -135,21 +177,23 @@ public class StaffAccountsController { }).start(); } - private StaffAccount mapToStaffAccount(UserResponse user) { - long id = user.getId() != null ? user.getId() : 0L; - String username = user.getUsername(); - String fullName = user.getFullName() != null ? user.getFullName() : ""; + private StaffAccount mapToStaffAccount(EmployeeResponse employee) { + long userId = employee.getUserId() != null ? employee.getUserId() : 0L; + long employeeId = employee.getEmployeeId() != null ? employee.getEmployeeId() : 0L; + String username = employee.getUsername(); + String fullName = employee.getFullName() != null ? employee.getFullName() : ""; String[] names = splitFullName(fullName); String firstName = names[0]; String lastName = names[1]; - String email = user.getEmail() != null ? user.getEmail() : ""; - String phone = ""; - boolean active = user.getActive() != null ? user.getActive() : false; - Timestamp createdAt = user.getCreatedAt() != null - ? Timestamp.from(user.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant()) + String email = employee.getEmail() != null ? employee.getEmail() : ""; + String phone = employee.getPhone() != null ? employee.getPhone() : ""; + String role = employee.getRole() != null ? employee.getRole() : "STAFF"; + boolean active = employee.getActive() != null ? employee.getActive() : false; + Timestamp createdAt = employee.getCreatedAt() != null + ? Timestamp.from(employee.getCreatedAt().atZone(ZoneId.systemDefault()).toInstant()) : null; - return new StaffAccount(id, id, 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) { diff --git a/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java b/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java new file mode 100644 index 00000000..5ed6316e --- /dev/null +++ b/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java @@ -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(); + } +} diff --git a/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java b/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java index ae0d6600..8d121dde 100644 --- a/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java +++ b/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffRegisterDialogController.java @@ -9,8 +9,9 @@ import javafx.scene.control.Label; import javafx.scene.control.PasswordField; import javafx.scene.control.TextField; import javafx.stage.Stage; -import org.example.petshopdesktop.api.dto.user.UserRequest; -import org.example.petshopdesktop.api.endpoints.UserApi; +import org.example.petshopdesktop.api.dto.employee.EmployeeRequest; +import org.example.petshopdesktop.api.endpoints.EmployeeApi; +import org.example.petshopdesktop.Validator; import org.example.petshopdesktop.util.ActivityLogger; public class StaffRegisterDialogController { @@ -49,6 +50,7 @@ public class StaffRegisterDialogController { 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(); String confirm = txtPasswordConfirm.getText() == null ? "" : txtPasswordConfirm.getText(); @@ -61,6 +63,15 @@ public class StaffRegisterDialogController { 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; @@ -78,15 +89,17 @@ public class StaffRegisterDialogController { new Thread(() -> { try { - UserRequest request = new UserRequest(); + EmployeeRequest request = new EmployeeRequest(); request.setUsername(username); request.setPassword(password); - request.setFullName(firstName + " " + lastName); + request.setFirstName(firstName); + request.setLastName(lastName); request.setEmail(email); + request.setPhone(phone); request.setRole("STAFF"); request.setActive(true); - UserApi.getInstance().createUser(request); + EmployeeApi.getInstance().createEmployee(request); Platform.runLater(() -> { Alert alert = new Alert(Alert.AlertType.INFORMATION); diff --git a/src/main/java/org/example/petshopdesktop/models/StaffAccount.java b/src/main/java/org/example/petshopdesktop/models/StaffAccount.java index e1b0ab62..59923338 100644 --- a/src/main/java/org/example/petshopdesktop/models/StaffAccount.java +++ b/src/main/java/org/example/petshopdesktop/models/StaffAccount.java @@ -10,10 +10,11 @@ public class StaffAccount { private final String lastName; private final String email; private final String phone; + private final String role; private final boolean active; 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.employeeId = employeeId; this.username = username; @@ -21,6 +22,7 @@ public class StaffAccount { this.lastName = lastName; this.email = email; this.phone = phone; + this.role = role; this.active = active; this.createdAt = createdAt; } @@ -59,6 +61,10 @@ public class StaffAccount { return phone; } + public String getRole() { + return role; + } + public boolean isActive() { return active; } diff --git a/src/main/resources/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml b/src/main/resources/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml new file mode 100644 index 00000000..354ea20d --- /dev/null +++ b/src/main/resources/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + +