user avatar in edit dialogs (#312)
This commit was merged in pull request #312.
This commit is contained in:
@@ -16,6 +16,7 @@ public class EmployeeResponse {
|
||||
private String staffRole;
|
||||
private Long primaryStoreId;
|
||||
private Boolean active;
|
||||
private String avatarUrl;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@@ -45,6 +46,8 @@ public class EmployeeResponse {
|
||||
public void setPrimaryStoreId(Long primaryStoreId) { this.primaryStoreId = primaryStoreId; }
|
||||
public Boolean getActive() { return active; }
|
||||
public void setActive(Boolean active) { this.active = active; }
|
||||
public String getAvatarUrl() { return avatarUrl; }
|
||||
public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; }
|
||||
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||
|
||||
@@ -11,6 +11,7 @@ public class UserResponse {
|
||||
private String role;
|
||||
private Boolean active;
|
||||
private Integer loyaltyPoints;
|
||||
private String avatarUrl;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@@ -81,6 +82,14 @@ public class UserResponse {
|
||||
this.loyaltyPoints = loyaltyPoints;
|
||||
}
|
||||
|
||||
public String getAvatarUrl() {
|
||||
return avatarUrl;
|
||||
}
|
||||
|
||||
public void setAvatarUrl(String avatarUrl) {
|
||||
this.avatarUrl = avatarUrl;
|
||||
}
|
||||
|
||||
public LocalDateTime getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.example.petshopdesktop.api.dto.user.UserResponse;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public class UserApi {
|
||||
@@ -45,4 +46,12 @@ public class UserApi {
|
||||
public UserResponse updateUser(Long id, UserRequest request) throws Exception {
|
||||
return apiClient.put("/api/v1/users/" + id, request, UserResponse.class);
|
||||
}
|
||||
|
||||
public void uploadUserAvatar(Long userId, Path filePath) throws Exception {
|
||||
apiClient.postMultipart("/api/v1/users/" + userId + "/avatar", "avatar", filePath, Object.class);
|
||||
}
|
||||
|
||||
public void deleteUserAvatar(Long userId) throws Exception {
|
||||
apiClient.delete("/api/v1/users/" + userId + "/avatar");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,21 @@ import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.user.UserRequest;
|
||||
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;
|
||||
import org.example.petshopdesktop.util.DesktopImageSupport;
|
||||
import org.example.petshopdesktop.util.FilePickerSupport;
|
||||
import org.example.petshopdesktop.util.TextFieldFormatSupport;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class CustomerEditDialogController {
|
||||
|
||||
@FXML private TextField txtFirstName;
|
||||
@@ -32,7 +38,15 @@ public class CustomerEditDialogController {
|
||||
@FXML private Label lblError;
|
||||
@FXML private Button btnSave;
|
||||
|
||||
@FXML private ImageView imgAvatarPreview;
|
||||
@FXML private Label lblAvatarStatus;
|
||||
@FXML private Button btnChangeAvatar;
|
||||
@FXML private Button btnRemoveAvatar;
|
||||
|
||||
private UserResponse customer;
|
||||
private File selectedAvatarFile;
|
||||
private String currentAvatarUrl;
|
||||
private boolean removeAvatarRequested;
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
@@ -41,6 +55,9 @@ public class CustomerEditDialogController {
|
||||
|
||||
boolean isAdmin = UserSession.getInstance().isAdmin();
|
||||
txtLoyaltyPoints.setDisable(!isAdmin);
|
||||
|
||||
btnChangeAvatar.setOnMouseClicked(e -> handleChangeAvatar());
|
||||
btnRemoveAvatar.setOnMouseClicked(e -> handleRemoveAvatar());
|
||||
}
|
||||
|
||||
public void setCustomer(UserResponse user) {
|
||||
@@ -55,6 +72,65 @@ public class CustomerEditDialogController {
|
||||
cbActive.setValue(Boolean.TRUE.equals(user.getActive()) ? "Active" : "Inactive");
|
||||
int pts = user.getLoyaltyPoints() != null ? user.getLoyaltyPoints() : 0;
|
||||
txtLoyaltyPoints.setText(String.valueOf(pts));
|
||||
|
||||
currentAvatarUrl = user.getAvatarUrl();
|
||||
refreshAvatarPreview();
|
||||
}
|
||||
|
||||
private void handleChangeAvatar() {
|
||||
File file = FilePickerSupport.pickImageFile(btnSave.getScene().getWindow());
|
||||
if (file == null) return;
|
||||
selectedAvatarFile = file;
|
||||
removeAvatarRequested = false;
|
||||
lblAvatarStatus.setText("Selected: " + file.getName());
|
||||
DesktopImageSupport.loadImageInto(imgAvatarPreview, file.toURI().toString(), 90, 90);
|
||||
btnRemoveAvatar.setDisable(false);
|
||||
}
|
||||
|
||||
private void handleRemoveAvatar() {
|
||||
selectedAvatarFile = null;
|
||||
removeAvatarRequested = true;
|
||||
currentAvatarUrl = null;
|
||||
refreshAvatarPreview();
|
||||
}
|
||||
|
||||
private void applyAvatarChanges(Long userId) throws Exception {
|
||||
String previousAvatarUrl = currentAvatarUrl;
|
||||
if (removeAvatarRequested) {
|
||||
try {
|
||||
UserApi.getInstance().deleteUserAvatar(userId);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (selectedAvatarFile != null) {
|
||||
UserApi.getInstance().uploadUserAvatar(userId, selectedAvatarFile.toPath());
|
||||
currentAvatarUrl = "/api/v1/users/" + userId + "/avatar/file";
|
||||
} else if (removeAvatarRequested) {
|
||||
currentAvatarUrl = null;
|
||||
}
|
||||
DesktopImageSupport.evict(previousAvatarUrl);
|
||||
DesktopImageSupport.evict(currentAvatarUrl);
|
||||
selectedAvatarFile = null;
|
||||
removeAvatarRequested = false;
|
||||
}
|
||||
|
||||
private void refreshAvatarPreview() {
|
||||
if (imgAvatarPreview == null || lblAvatarStatus == null || btnRemoveAvatar == null) return;
|
||||
imgAvatarPreview.setImage(null);
|
||||
if (selectedAvatarFile != null) {
|
||||
lblAvatarStatus.setText("Selected: " + selectedAvatarFile.getName());
|
||||
DesktopImageSupport.loadImageInto(imgAvatarPreview, selectedAvatarFile.toURI().toString(), 90, 90);
|
||||
btnRemoveAvatar.setDisable(false);
|
||||
return;
|
||||
}
|
||||
if (currentAvatarUrl != null && !currentAvatarUrl.isBlank()) {
|
||||
lblAvatarStatus.setText("Current avatar loaded");
|
||||
DesktopImageSupport.loadImageInto(imgAvatarPreview, currentAvatarUrl, 90, 90);
|
||||
btnRemoveAvatar.setDisable(false);
|
||||
return;
|
||||
}
|
||||
lblAvatarStatus.setText("No avatar");
|
||||
btnRemoveAvatar.setDisable(true);
|
||||
}
|
||||
|
||||
private String[] splitFullName(String fullName) {
|
||||
@@ -148,6 +224,7 @@ public class CustomerEditDialogController {
|
||||
if (finalLoyaltyPoints != null) request.setLoyaltyPoints(finalLoyaltyPoints);
|
||||
|
||||
CustomerApi.getInstance().updateCustomer(customer.getId(), request);
|
||||
applyAvatarChanges(customer.getId());
|
||||
|
||||
Platform.runLater(this::close);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ListCell;
|
||||
import javafx.scene.control.PasswordField;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.Validator;
|
||||
import org.example.petshopdesktop.api.dto.common.DropdownOption;
|
||||
@@ -17,9 +18,13 @@ import org.example.petshopdesktop.api.dto.employee.EmployeeRequest;
|
||||
import org.example.petshopdesktop.api.dto.employee.EmployeeResponse;
|
||||
import org.example.petshopdesktop.api.endpoints.DropdownApi;
|
||||
import org.example.petshopdesktop.api.endpoints.EmployeeApi;
|
||||
import org.example.petshopdesktop.api.endpoints.UserApi;
|
||||
import org.example.petshopdesktop.util.ActivityLogger;
|
||||
import org.example.petshopdesktop.util.DesktopImageSupport;
|
||||
import org.example.petshopdesktop.util.FilePickerSupport;
|
||||
import org.example.petshopdesktop.util.TextFieldFormatSupport;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public class StaffEditDialogController {
|
||||
@@ -37,8 +42,16 @@ public class StaffEditDialogController {
|
||||
@FXML private Label lblError;
|
||||
@FXML private Button btnSave;
|
||||
|
||||
@FXML private ImageView imgAvatarPreview;
|
||||
@FXML private Label lblAvatarStatus;
|
||||
@FXML private Button btnChangeAvatar;
|
||||
@FXML private Button btnRemoveAvatar;
|
||||
|
||||
private EmployeeResponse employee;
|
||||
private Long pendingStoreId = null;
|
||||
private File selectedAvatarFile;
|
||||
private String currentAvatarUrl;
|
||||
private boolean removeAvatarRequested;
|
||||
|
||||
@FXML
|
||||
void initialize() {
|
||||
@@ -60,6 +73,9 @@ public class StaffEditDialogController {
|
||||
}
|
||||
});
|
||||
|
||||
btnChangeAvatar.setOnMouseClicked(e -> handleChangeAvatar());
|
||||
btnRemoveAvatar.setOnMouseClicked(e -> handleRemoveAvatar());
|
||||
|
||||
loadStores();
|
||||
}
|
||||
|
||||
@@ -110,6 +126,65 @@ public class StaffEditDialogController {
|
||||
|
||||
pendingStoreId = emp.getPrimaryStoreId();
|
||||
applyPendingStore();
|
||||
|
||||
currentAvatarUrl = emp.getAvatarUrl();
|
||||
refreshAvatarPreview();
|
||||
}
|
||||
|
||||
private void handleChangeAvatar() {
|
||||
File file = FilePickerSupport.pickImageFile(btnSave.getScene().getWindow());
|
||||
if (file == null) return;
|
||||
selectedAvatarFile = file;
|
||||
removeAvatarRequested = false;
|
||||
lblAvatarStatus.setText("Selected: " + file.getName());
|
||||
DesktopImageSupport.loadImageInto(imgAvatarPreview, file.toURI().toString(), 90, 90);
|
||||
btnRemoveAvatar.setDisable(false);
|
||||
}
|
||||
|
||||
private void handleRemoveAvatar() {
|
||||
selectedAvatarFile = null;
|
||||
removeAvatarRequested = true;
|
||||
currentAvatarUrl = null;
|
||||
refreshAvatarPreview();
|
||||
}
|
||||
|
||||
private void applyAvatarChanges(Long userId) throws Exception {
|
||||
String previousAvatarUrl = currentAvatarUrl;
|
||||
if (removeAvatarRequested) {
|
||||
try {
|
||||
UserApi.getInstance().deleteUserAvatar(userId);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
if (selectedAvatarFile != null) {
|
||||
UserApi.getInstance().uploadUserAvatar(userId, selectedAvatarFile.toPath());
|
||||
currentAvatarUrl = "/api/v1/users/" + userId + "/avatar/file";
|
||||
} else if (removeAvatarRequested) {
|
||||
currentAvatarUrl = null;
|
||||
}
|
||||
DesktopImageSupport.evict(previousAvatarUrl);
|
||||
DesktopImageSupport.evict(currentAvatarUrl);
|
||||
selectedAvatarFile = null;
|
||||
removeAvatarRequested = false;
|
||||
}
|
||||
|
||||
private void refreshAvatarPreview() {
|
||||
if (imgAvatarPreview == null || lblAvatarStatus == null || btnRemoveAvatar == null) return;
|
||||
imgAvatarPreview.setImage(null);
|
||||
if (selectedAvatarFile != null) {
|
||||
lblAvatarStatus.setText("Selected: " + selectedAvatarFile.getName());
|
||||
DesktopImageSupport.loadImageInto(imgAvatarPreview, selectedAvatarFile.toURI().toString(), 90, 90);
|
||||
btnRemoveAvatar.setDisable(false);
|
||||
return;
|
||||
}
|
||||
if (currentAvatarUrl != null && !currentAvatarUrl.isBlank()) {
|
||||
lblAvatarStatus.setText("Current avatar loaded");
|
||||
DesktopImageSupport.loadImageInto(imgAvatarPreview, currentAvatarUrl, 90, 90);
|
||||
btnRemoveAvatar.setDisable(false);
|
||||
return;
|
||||
}
|
||||
lblAvatarStatus.setText("No avatar");
|
||||
btnRemoveAvatar.setDisable(true);
|
||||
}
|
||||
|
||||
private String[] splitFullName(String fullName) {
|
||||
@@ -193,6 +268,7 @@ public class StaffEditDialogController {
|
||||
request.setPrimaryStoreId(storeId);
|
||||
|
||||
EmployeeApi.getInstance().updateEmployee(employee.getId(), request);
|
||||
applyAvatarChanges(employee.getUserId());
|
||||
|
||||
Platform.runLater(this::close);
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.PasswordField?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
@@ -24,6 +25,23 @@
|
||||
|
||||
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true" />
|
||||
|
||||
<HBox alignment="CENTER_LEFT" spacing="15.0">
|
||||
<children>
|
||||
<ImageView fx:id="imgAvatarPreview" fitHeight="90.0" fitWidth="90.0" pickOnBounds="true" preserveRatio="true" />
|
||||
<VBox spacing="8.0">
|
||||
<children>
|
||||
<Label fx:id="lblAvatarStatus" text="No avatar" textFill="#2c3e50" />
|
||||
<HBox spacing="8.0">
|
||||
<children>
|
||||
<Button fx:id="btnChangeAvatar" mnemonicParsing="false" text="Change Avatar" />
|
||||
<Button fx:id="btnRemoveAvatar" mnemonicParsing="false" text="Remove Avatar" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
<HBox spacing="10.0">
|
||||
<children>
|
||||
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.PasswordField?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
@@ -24,6 +25,23 @@
|
||||
|
||||
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true" />
|
||||
|
||||
<HBox alignment="CENTER_LEFT" spacing="15.0">
|
||||
<children>
|
||||
<ImageView fx:id="imgAvatarPreview" fitHeight="90.0" fitWidth="90.0" pickOnBounds="true" preserveRatio="true" />
|
||||
<VBox spacing="8.0">
|
||||
<children>
|
||||
<Label fx:id="lblAvatarStatus" text="No avatar" textFill="#2c3e50" />
|
||||
<HBox spacing="8.0">
|
||||
<children>
|
||||
<Button fx:id="btnChangeAvatar" mnemonicParsing="false" text="Change Avatar" />
|
||||
<Button fx:id="btnRemoveAvatar" mnemonicParsing="false" text="Remove Avatar" />
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
|
||||
<HBox spacing="10.0">
|
||||
<children>
|
||||
<VBox spacing="6.0" HBox.hgrow="ALWAYS">
|
||||
|
||||
Reference in New Issue
Block a user