diff --git a/backend/src/main/java/com/petshop/backend/controller/ActivityLogController.java b/backend/src/main/java/com/petshop/backend/controller/ActivityLogController.java index 49e4cc85..6d202ddb 100644 --- a/backend/src/main/java/com/petshop/backend/controller/ActivityLogController.java +++ b/backend/src/main/java/com/petshop/backend/controller/ActivityLogController.java @@ -2,7 +2,9 @@ package com.petshop.backend.controller; import com.petshop.backend.dto.activity.ActivityLogResponse; import com.petshop.backend.service.ActivityLogService; +import java.time.LocalDate; import java.util.List; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -26,8 +28,10 @@ public class ActivityLogController { @RequestParam(defaultValue = "2000") int limit, @RequestParam(required = false) Long storeId, @RequestParam(required = false) String role, - @RequestParam(required = false) String search) { + @RequestParam(required = false) String search, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate, + @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) { int safeLimit = Math.min(Math.max(1, limit), 10000); - return ResponseEntity.ok(activityLogService.getLogs(safeLimit, storeId, role, search)); + return ResponseEntity.ok(activityLogService.getLogs(safeLimit, storeId, role, search, startDate, endDate)); } } diff --git a/backend/src/main/java/com/petshop/backend/service/ActivityLogService.java b/backend/src/main/java/com/petshop/backend/service/ActivityLogService.java index 76acaa6c..a3880071 100644 --- a/backend/src/main/java/com/petshop/backend/service/ActivityLogService.java +++ b/backend/src/main/java/com/petshop/backend/service/ActivityLogService.java @@ -15,6 +15,7 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @@ -52,6 +53,11 @@ public class ActivityLogService { entry.setStoreNameSnapshot(store != null ? store.getStoreName() : null); entry.setActivity(activity.trim()); activityLogRepository.save(entry); + log.info("[ACTIVITY] {} | {} | {} | {}", + entry.getRoleSnapshot(), + entry.getUsernameSnapshot(), + entry.getStoreNameSnapshot() != null ? entry.getStoreNameSnapshot() : "no store", + entry.getActivity()); } catch (Exception ex) { log.warn("Failed to persist activity log", ex); } @@ -65,7 +71,7 @@ public class ActivityLogService { } @Transactional(readOnly = true) - public List getLogs(int limit, Long storeId, String role, String search) { + public List getLogs(int limit, Long storeId, String role, String search, LocalDate startDate, LocalDate endDate) { Specification spec = (root, query, cb) -> { List predicates = new ArrayList<>(); @@ -87,6 +93,14 @@ public class ActivityLogService { predicates.add(searchPredicate); } + if (startDate != null) { + predicates.add(cb.greaterThanOrEqualTo(root.get("logTimestamp"), startDate.atStartOfDay())); + } + + if (endDate != null) { + predicates.add(cb.lessThan(root.get("logTimestamp"), endDate.plusDays(1).atStartOfDay())); + } + return cb.and(predicates.toArray(new Predicate[0])); }; @@ -96,9 +110,14 @@ public class ActivityLogService { .toList(); } + @Transactional(readOnly = true) + public List getLogs(int limit, Long storeId, String role, String search) { + return getLogs(limit, storeId, role, search, null, null); + } + @Transactional(readOnly = true) public List getLogs(int limit) { - return getLogs(limit, null, null, null); + return getLogs(limit, null, null, null, null, null); } private ActivityLogResponse toResponse(ActivityLog entry) { 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 index d4553cb8..6c092b78 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/endpoints/ActivityLogApi.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import org.example.petshopdesktop.api.ApiClient; import org.example.petshopdesktop.api.dto.activity.ActivityLogResponse; +import java.time.LocalDate; import java.util.List; public class ActivityLogApi { @@ -18,12 +19,18 @@ public class ActivityLogApi { return INSTANCE; } - public List getActivityLogs(int limit) throws Exception { - String path = "/api/v1/activity-logs?limit=" + limit; - String response = apiClient.getRawResponse(path); + public List getActivityLogs(int limit, LocalDate startDate, LocalDate endDate) throws Exception { + StringBuilder path = new StringBuilder("/api/v1/activity-logs?limit=").append(limit); + if (startDate != null) path.append("&startDate=").append(startDate); + if (endDate != null) path.append("&endDate=").append(endDate); + String response = apiClient.getRawResponse(path.toString()); return apiClient.getObjectMapper().readValue( response, new TypeReference>() {} ); } + + public List getActivityLogs(int limit) throws Exception { + return getActivityLogs(limit, null, null); + } } diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java index d593289e..3d59934d 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ActivityLogController.java @@ -3,9 +3,12 @@ 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.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.DatePicker; import javafx.scene.control.Label; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; @@ -40,6 +43,15 @@ public class ActivityLogController { @FXML private Button btnRefresh; + @FXML + private DatePicker dpStart; + + @FXML + private DatePicker dpEnd; + + @FXML + private CheckBox chkHideViewOnly; + @FXML private Label lblStatus; @@ -47,6 +59,7 @@ public class ActivityLogController { private Label lblError; private final ObservableList activityLogs = FXCollections.observableArrayList(); + private FilteredList filteredLogs; private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private static final int DEFAULT_LIMIT = 2000; @@ -71,7 +84,8 @@ public class ActivityLogController { 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)); + filteredLogs = new FilteredList<>(activityLogs, a -> true); + TableViewSupport.bindSortedItems(tvActivityLogs, filteredLogs); loadLogs(); } @@ -80,18 +94,46 @@ public class ActivityLogController { loadLogs(); } + @FXML + void onDateChanged(ActionEvent event) { + loadLogs(); + } + + @FXML + void onHideViewOnlyChanged(ActionEvent event) { + applyViewOnlyFilter(); + } + + private void applyViewOnlyFilter() { + boolean hide = chkHideViewOnly != null && chkHideViewOnly.isSelected(); + if (filteredLogs != null) { + filteredLogs.setPredicate(hide + ? a -> { + String act = a.getActivity(); + if (act == null) return true; + String trimmed = act.stripLeading(); + return !trimmed.startsWith("View"); + } + : a -> true); + } + } + private void loadLogs() { lblError.setText(""); tvActivityLogs.setDisable(true); btnRefresh.setDisable(true); + java.time.LocalDate startDate = dpStart != null ? dpStart.getValue() : null; + java.time.LocalDate endDate = dpEnd != null ? dpEnd.getValue() : null; + new Thread(() -> { try { - List content = ActivityLogApi.getInstance().getActivityLogs(DEFAULT_LIMIT); + List content = ActivityLogApi.getInstance().getActivityLogs(DEFAULT_LIMIT, startDate, endDate); List safeContent = content != null ? content : List.of(); Platform.runLater(() -> { activityLogs.setAll(safeContent); + applyViewOnlyFilter(); tvActivityLogs.setDisable(false); btnRefresh.setDisable(false); TableViewSupport.flashStatus(lblStatus, "Refreshed"); diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java index 77818453..23a6a00a 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/dialogcontrollers/StaffEditDialogController.java @@ -32,7 +32,6 @@ public class StaffEditDialogController { @FXML private PasswordField txtPassword; @FXML private PasswordField txtPasswordConfirm; @FXML private ComboBox cbRole; - @FXML private ComboBox cbStaffRole; @FXML private ComboBox cbActive; @FXML private ComboBox cbStore; @FXML private Label lblError; @@ -46,8 +45,6 @@ public class StaffEditDialogController { TextFieldFormatSupport.applyPhoneNumberFormat(txtPhone); cbRole.setItems(FXCollections.observableArrayList("STAFF", "ADMIN")); - cbStaffRole.setItems(FXCollections.observableArrayList( - "STORE_MANAGER", "SALES_ASSOCIATE", "GROOMER", "VETERINARIAN")); cbActive.setItems(FXCollections.observableArrayList("Active", "Inactive")); cbStore.setCellFactory(param -> new ListCell<>() { @@ -109,7 +106,6 @@ public class StaffEditDialogController { txtUsername.setText(emp.getUsername()); if (emp.getRole() != null) cbRole.setValue(emp.getRole()); - if (emp.getStaffRole() != null) cbStaffRole.setValue(emp.getStaffRole()); cbActive.setValue(Boolean.TRUE.equals(emp.getActive()) ? "Active" : "Inactive"); pendingStoreId = emp.getPrimaryStoreId(); @@ -171,10 +167,6 @@ public class StaffEditDialogController { lblError.setText("Role is required."); return; } - if (cbStaffRole.getValue() == null) { - lblError.setText("Staff role is required."); - return; - } if (cbStore.getValue() == null) { lblError.setText("Primary store is required."); return; @@ -183,7 +175,6 @@ public class StaffEditDialogController { btnSave.setDisable(true); String role = cbRole.getValue(); - String staffRole = cbStaffRole.getValue(); boolean active = "Active".equals(cbActive.getValue()); Long storeId = cbStore.getValue().getId(); @@ -198,7 +189,6 @@ public class StaffEditDialogController { request.setEmail(email); request.setPhone(phone); request.setRole(role); - request.setStaffRole(staffRole); request.setActive(active); request.setPrimaryStoreId(storeId); diff --git a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml index 5b5e66fd..1bc43317 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/dialogviews/staff-edit-dialog-view.fxml @@ -87,22 +87,12 @@ - + - - - - - - - - + - + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/activity-log-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/activity-log-view.fxml index 7caa317a..3249cae3 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/activity-log-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/activity-log-view.fxml @@ -2,6 +2,8 @@ + + @@ -30,6 +32,11 @@ + diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/chat-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/chat-view.fxml index e3b1dbd4..2a1a0c07 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/chat-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/chat-view.fxml @@ -88,7 +88,7 @@ -