From 50c344091f3aef0215f62487c2e8fa162d31f679 Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sat, 11 Apr 2026 22:54:27 -0600 Subject: [PATCH] Add log viewer --- desktop/src/main/java/module-info.java | 1 + .../api/dto/activity/ActivityLogResponse.java | 126 ++++++++++++++ .../api/endpoints/ActivityLogApi.java | 29 ++++ .../controllers/ActivityLogController.java | 154 ++++++++++++++++++ .../controllers/MainLayoutController.java | 15 ++ .../petshopdesktop/main-layout-view.fxml | 26 ++- .../modelviews/activity-log-view.fxml | 52 ++++++ 7 files changed, 394 insertions(+), 9 deletions(-) create mode 100644 desktop/src/main/java/org/example/petshopdesktop/api/dto/activity/ActivityLogResponse.java create mode 100644 desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java create mode 100644 desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java create mode 100644 desktop/src/main/resources/org/example/petshopdesktop/modelviews/activity-log-view.fxml diff --git a/desktop/src/main/java/module-info.java b/desktop/src/main/java/module-info.java index d6cefd63..ecd94fd5 100644 --- a/desktop/src/main/java/module-info.java +++ b/desktop/src/main/java/module-info.java @@ -34,6 +34,7 @@ module org.example.petshopdesktop { opens org.example.petshopdesktop.api.dto.employee to com.fasterxml.jackson.databind; opens org.example.petshopdesktop.api.dto.analytics to com.fasterxml.jackson.databind; opens org.example.petshopdesktop.api.dto.purchaseorder to com.fasterxml.jackson.databind; + opens org.example.petshopdesktop.api.dto.activity to com.fasterxml.jackson.databind; exports org.example.petshopdesktop; exports org.example.petshopdesktop.controllers; diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/dto/activity/ActivityLogResponse.java b/desktop/src/main/java/org/example/petshopdesktop/api/dto/activity/ActivityLogResponse.java new file mode 100644 index 00000000..482476eb --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/api/dto/activity/ActivityLogResponse.java @@ -0,0 +1,126 @@ +package org.example.petshopdesktop.api.dto.activity; + +import java.time.LocalDateTime; + +public class ActivityLogResponse { + private Long logId; + private Long userId; + private String username; + private String fullName; + private String role; + private Long storeId; + private String storeName; + private String usernameSnapshot; + private String fullNameSnapshot; + private String roleSnapshot; + private String storeNameSnapshot; + private String activity; + private LocalDateTime logTimestamp; + + public ActivityLogResponse() { + } + + public Long getLogId() { + return logId; + } + + public void setLogId(Long logId) { + this.logId = logId; + } + + 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 getFullName() { + return fullName; + } + + public void setFullName(String fullName) { + this.fullName = fullName; + } + + public String getRole() { + return role; + } + + public void setRole(String role) { + this.role = role; + } + + public Long getStoreId() { + return storeId; + } + + public void setStoreId(Long storeId) { + this.storeId = storeId; + } + + public String getStoreName() { + return storeName; + } + + public void setStoreName(String storeName) { + this.storeName = storeName; + } + + public String getUsernameSnapshot() { + return usernameSnapshot; + } + + public void setUsernameSnapshot(String usernameSnapshot) { + this.usernameSnapshot = usernameSnapshot; + } + + public String getFullNameSnapshot() { + return fullNameSnapshot; + } + + public void setFullNameSnapshot(String fullNameSnapshot) { + this.fullNameSnapshot = fullNameSnapshot; + } + + public String getRoleSnapshot() { + return roleSnapshot; + } + + public void setRoleSnapshot(String roleSnapshot) { + this.roleSnapshot = roleSnapshot; + } + + public String getStoreNameSnapshot() { + return storeNameSnapshot; + } + + public void setStoreNameSnapshot(String storeNameSnapshot) { + this.storeNameSnapshot = storeNameSnapshot; + } + + public String getActivity() { + return activity; + } + + public void setActivity(String activity) { + this.activity = activity; + } + + public LocalDateTime getLogTimestamp() { + return logTimestamp; + } + + public void setLogTimestamp(LocalDateTime logTimestamp) { + this.logTimestamp = logTimestamp; + } +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java new file mode 100644 index 00000000..d4553cb8 --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java @@ -0,0 +1,29 @@ +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.activity.ActivityLogResponse; + +import java.util.List; + +public class ActivityLogApi { + private static final ActivityLogApi INSTANCE = new ActivityLogApi(); + private final ApiClient apiClient; + + private ActivityLogApi() { + this.apiClient = ApiClient.getInstance(); + } + + public static ActivityLogApi getInstance() { + return INSTANCE; + } + + public List getActivityLogs(int limit) throws Exception { + String path = "/api/v1/activity-logs?limit=" + limit; + String response = apiClient.getRawResponse(path); + return apiClient.getObjectMapper().readValue( + response, + new TypeReference>() {} + ); + } +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java new file mode 100644 index 00000000..d593289e --- /dev/null +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java @@ -0,0 +1,154 @@ +package org.example.petshopdesktop.controllers; + +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import org.example.petshopdesktop.api.dto.activity.ActivityLogResponse; +import org.example.petshopdesktop.api.endpoints.ActivityLogApi; +import org.example.petshopdesktop.util.ActivityLogger; +import org.example.petshopdesktop.util.TableViewSupport; + +import java.time.format.DateTimeFormatter; +import java.util.List; + +public class ActivityLogController { + + @FXML + private TableView tvActivityLogs; + + @FXML + private TableColumn colTimestamp; + + @FXML + private TableColumn colUser; + + @FXML + private TableColumn colRole; + + @FXML + private TableColumn colStore; + + @FXML + private TableColumn colActivity; + + @FXML + private Button btnRefresh; + + @FXML + private Label lblStatus; + + @FXML + private Label lblError; + + private final ObservableList activityLogs = FXCollections.observableArrayList(); + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final int DEFAULT_LIMIT = 2000; + + @FXML + public void initialize() { + colTimestamp.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getLogTimestamp())); + colTimestamp.setCellFactory(column -> new javafx.scene.control.TableCell<>() { + @Override + protected void updateItem(Object item, boolean empty) { + super.updateItem(item, empty); + if (empty || item == null) { + setText(null); + } else if (item instanceof java.time.LocalDateTime time) { + setText(time.format(formatter)); + } else { + setText(item.toString()); + } + } + }); + colUser.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(displayUser(data.getValue()))); + colRole.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(displayRole(data.getValue()))); + colStore.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(displayStore(data.getValue()))); + colActivity.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(nullToBlank(data.getValue().getActivity()))); + + TableViewSupport.bindSortedItems(tvActivityLogs, new javafx.collections.transformation.FilteredList<>(activityLogs, a -> true)); + loadLogs(); + } + + @FXML + void btnRefreshClicked(ActionEvent event) { + loadLogs(); + } + + private void loadLogs() { + lblError.setText(""); + tvActivityLogs.setDisable(true); + btnRefresh.setDisable(true); + + new Thread(() -> { + try { + List content = ActivityLogApi.getInstance().getActivityLogs(DEFAULT_LIMIT); + List safeContent = content != null ? content : List.of(); + + Platform.runLater(() -> { + activityLogs.setAll(safeContent); + tvActivityLogs.setDisable(false); + btnRefresh.setDisable(false); + TableViewSupport.flashStatus(lblStatus, "Refreshed"); + }); + } catch (Exception e) { + ActivityLogger.getInstance().logException("ActivityLogController.loadLogs", e, "Loading activity logs"); + Platform.runLater(() -> { + lblError.setText(e.getMessage() == null || e.getMessage().isBlank() + ? "Could not load activity logs." + : "Could not load activity logs: " + e.getMessage()); + tvActivityLogs.setDisable(false); + btnRefresh.setDisable(false); + }); + } + }).start(); + } + + private String displayUser(ActivityLogResponse log) { + if (log == null) { + return ""; + } + if (log.getFullNameSnapshot() != null && !log.getFullNameSnapshot().isBlank()) { + return log.getFullNameSnapshot(); + } + if (log.getFullName() != null && !log.getFullName().isBlank()) { + return log.getFullName(); + } + if (log.getUsernameSnapshot() != null && !log.getUsernameSnapshot().isBlank()) { + return log.getUsernameSnapshot(); + } + if (log.getUsername() != null && !log.getUsername().isBlank()) { + return log.getUsername(); + } + return log.getUserId() != null ? String.valueOf(log.getUserId()) : ""; + } + + private String displayRole(ActivityLogResponse log) { + if (log == null) { + return ""; + } + if (log.getRoleSnapshot() != null && !log.getRoleSnapshot().isBlank()) { + return log.getRoleSnapshot(); + } + return nullToBlank(log.getRole()); + } + + private String displayStore(ActivityLogResponse log) { + if (log == null) { + return ""; + } + if (log.getStoreNameSnapshot() != null && !log.getStoreNameSnapshot().isBlank()) { + return log.getStoreNameSnapshot(); + } + return nullToBlank(log.getStoreName()); + } + + private String nullToBlank(String value) { + return value == null ? "" : value; + } +} diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java index 69916845..c4af659f 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/MainLayoutController.java @@ -70,6 +70,9 @@ public class MainLayoutController { @FXML private Button btnProducts; + @FXML + private Button btnActivityLogs; + @FXML private Button btnSalesHistory; @@ -178,6 +181,12 @@ public class MainLayoutController { updateButtons(btnAnalytics); } + @FXML + void btnActivityLogsClicked(ActionEvent event) { + loadView("activity-log-view.fxml"); + updateButtons(btnActivityLogs); + } + @FXML void btnServicesClicked(ActionEvent event) { loadView("service-view.fxml"); @@ -401,6 +410,11 @@ public class MainLayoutController { btnAnalytics.setManaged(canViewAnalytics); } + if (btnActivityLogs != null) { + btnActivityLogs.setVisible(isAdmin); + btnActivityLogs.setManaged(isAdmin); + } + btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales"); // Initial chat state and subscription @@ -451,6 +465,7 @@ public class MainLayoutController { btnPurchaseOrders, btnStaffAccounts, btnAnalytics, + btnActivityLogs, btnChat }; diff --git a/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml index 984aad76..77800377 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/main-layout-view.fxml @@ -220,16 +220,24 @@ - + + - + + + +