added filtering to activity logs for desktop
This commit is contained in:
@@ -124,11 +124,15 @@ public class ActivityLoggingFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "chat" -> {
|
case "chat" -> {
|
||||||
if ("conversations".equals(seg3) && isPost) return "Started a new chat conversation";
|
if ("conversations".equals(seg3) && isPost && seg4 == null) return "Started a new chat conversation";
|
||||||
if (seg3IsId && "messages".equals(sub) && isPost) return "Sent a chat message";
|
if ("conversations".equals(seg3) && seg4IsId) {
|
||||||
if (seg3IsId && "attachments".equals(sub) && isPost) return "Sent a file in chat";
|
String convId = seg4;
|
||||||
if (seg3IsId && "request-human".equals(sub) && isPost) return "Requested human support in chat";
|
String chatSub = parts.length > 5 ? parts[5] : null;
|
||||||
if (seg3IsId && isPut) return "Updated chat conversation #" + id;
|
if ("messages".equals(seg5) && isPost) return "Sent a chat message";
|
||||||
|
if ("attachments".equals(seg5) && isPost) return "Sent a file in chat";
|
||||||
|
if ("request-human".equals(seg5) && isPost) return "Requested human support in chat";
|
||||||
|
if (chatSub == null && isPut) return "Updated chat conversation #" + convId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "ai-chat" -> {
|
case "ai-chat" -> {
|
||||||
if ("message".equals(seg3) && isPost) return "Sent a message to the AI assistant";
|
if ("message".equals(seg3) && isPost) return "Sent a message to the AI assistant";
|
||||||
|
|||||||
@@ -145,10 +145,10 @@ public class EmailService {
|
|||||||
.html(html)
|
.html(html)
|
||||||
.build();
|
.build();
|
||||||
resend.emails().send(options);
|
resend.emails().send(options);
|
||||||
activityLogService.record(recipientUserId, "Email sent: " + subject + " → " + to);
|
activityLogService.record(recipientUserId, "Sent an email | Email sent: " + subject + " → " + to);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("Failed to send email '{}' to {}: {}", subject, to, ex.getMessage());
|
log.error("Failed to send email '{}' to {}: {}", subject, to, ex.getMessage());
|
||||||
activityLogService.record(recipientUserId, "Email failed: " + subject + " → " + to);
|
activityLogService.record(recipientUserId, "Failed to send email | Email failed: " + subject + " → " + to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,14 @@ import javafx.event.ActionEvent;
|
|||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.DatePicker;
|
import javafx.scene.control.DatePicker;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TableView;
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
import org.example.petshopdesktop.api.dto.activity.ActivityLogResponse;
|
import org.example.petshopdesktop.api.dto.activity.ActivityLogResponse;
|
||||||
import org.example.petshopdesktop.api.endpoints.ActivityLogApi;
|
import org.example.petshopdesktop.api.endpoints.ActivityLogApi;
|
||||||
import org.example.petshopdesktop.util.ActivityLogger;
|
import org.example.petshopdesktop.util.ActivityLogger;
|
||||||
@@ -20,73 +24,105 @@ import org.example.petshopdesktop.util.TableViewSupport;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
public class ActivityLogController {
|
public class ActivityLogController {
|
||||||
|
|
||||||
@FXML
|
private static final String SEPARATOR_NEW = " | ";
|
||||||
private TableView<ActivityLogResponse> tvActivityLogs;
|
private static final String SEPARATOR_OLD = " \u00b7 ";
|
||||||
|
private static final DateTimeFormatter DISPLAY_FORMATTER = DateTimeFormatter.ofPattern("MMM d, HH:mm");
|
||||||
|
private static final int DEFAULT_LIMIT = 2000;
|
||||||
|
|
||||||
@FXML
|
@FXML private TableView<ActivityLogResponse> tvActivityLogs;
|
||||||
private TableColumn<ActivityLogResponse, Object> colTimestamp;
|
@FXML private TableColumn<ActivityLogResponse, Object> colTimestamp;
|
||||||
|
@FXML private TableColumn<ActivityLogResponse, String> colUser;
|
||||||
@FXML
|
@FXML private TableColumn<ActivityLogResponse, String> colRole;
|
||||||
private TableColumn<ActivityLogResponse, String> colUser;
|
@FXML private TableColumn<ActivityLogResponse, String> colStore;
|
||||||
|
@FXML private TableColumn<ActivityLogResponse, String> colActivity;
|
||||||
@FXML
|
@FXML private Button btnRefresh;
|
||||||
private TableColumn<ActivityLogResponse, String> colRole;
|
@FXML private DatePicker dpStart;
|
||||||
|
@FXML private DatePicker dpEnd;
|
||||||
@FXML
|
@FXML private CheckBox chkHideViewOnly;
|
||||||
private TableColumn<ActivityLogResponse, String> colStore;
|
@FXML private TextField tfSearch;
|
||||||
|
@FXML private ComboBox<String> cbRoleFilter;
|
||||||
@FXML
|
@FXML private ComboBox<String> cbStoreFilter;
|
||||||
private TableColumn<ActivityLogResponse, String> colActivity;
|
@FXML private Label lblStatus;
|
||||||
|
@FXML private Label lblError;
|
||||||
@FXML
|
|
||||||
private Button btnRefresh;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private DatePicker dpStart;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private DatePicker dpEnd;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private CheckBox chkHideViewOnly;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label lblStatus;
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
private Label lblError;
|
|
||||||
|
|
||||||
private final ObservableList<ActivityLogResponse> activityLogs = FXCollections.observableArrayList();
|
private final ObservableList<ActivityLogResponse> activityLogs = FXCollections.observableArrayList();
|
||||||
private FilteredList<ActivityLogResponse> filteredLogs;
|
private FilteredList<ActivityLogResponse> filteredLogs;
|
||||||
private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
|
||||||
private static final int DEFAULT_LIMIT = 2000;
|
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
colTimestamp.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getLogTimestamp()));
|
colTimestamp.setCellValueFactory(data -> new javafx.beans.property.SimpleObjectProperty<>(data.getValue().getLogTimestamp()));
|
||||||
colTimestamp.setCellFactory(column -> new javafx.scene.control.TableCell<>() {
|
colTimestamp.setCellFactory(col -> new TableCell<>() {
|
||||||
@Override
|
@Override
|
||||||
protected void updateItem(Object item, boolean empty) {
|
protected void updateItem(Object item, boolean empty) {
|
||||||
super.updateItem(item, empty);
|
super.updateItem(item, empty);
|
||||||
if (empty || item == null) {
|
if (empty || item == null) {
|
||||||
setText(null);
|
setText(null);
|
||||||
} else if (item instanceof java.time.LocalDateTime time) {
|
} else if (item instanceof java.time.LocalDateTime time) {
|
||||||
setText(time.format(formatter));
|
setText(time.format(DISPLAY_FORMATTER));
|
||||||
} else {
|
} else {
|
||||||
setText(item.toString());
|
setText(item.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
colUser.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(displayUser(data.getValue())));
|
colUser.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(displayUser(data.getValue())));
|
||||||
colRole.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(displayRole(data.getValue())));
|
colRole.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(formatRole(displayRole(data.getValue()))));
|
||||||
colStore.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(displayStore(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())));
|
colActivity.setCellValueFactory(data -> new javafx.beans.property.SimpleStringProperty(nullToBlank(data.getValue().getActivity())));
|
||||||
|
colActivity.setCellFactory(col -> new TableCell<>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(String item, boolean empty) {
|
||||||
|
super.updateItem(item, empty);
|
||||||
|
if (empty || item == null || item.isBlank()) {
|
||||||
|
setGraphic(null);
|
||||||
|
setText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int sep = item.indexOf(SEPARATOR_NEW);
|
||||||
|
int sepLen = SEPARATOR_NEW.length();
|
||||||
|
if (sep < 0) {
|
||||||
|
sep = item.indexOf(SEPARATOR_OLD);
|
||||||
|
sepLen = SEPARATOR_OLD.length();
|
||||||
|
}
|
||||||
|
if (sep >= 0) {
|
||||||
|
String description = item.substring(0, sep).trim();
|
||||||
|
String technical = item.substring(sep + sepLen).trim();
|
||||||
|
Label lblDesc = new Label(description);
|
||||||
|
lblDesc.setStyle("-fx-font-weight: bold; -fx-text-fill: #1f2937;");
|
||||||
|
lblDesc.setWrapText(true);
|
||||||
|
Label lblTech = new Label(technical);
|
||||||
|
lblTech.setStyle("-fx-font-size: 11px; -fx-text-fill: #94a3b8; -fx-font-family: monospace;");
|
||||||
|
lblTech.setWrapText(true);
|
||||||
|
VBox box = new VBox(2, lblDesc, lblTech);
|
||||||
|
setGraphic(box);
|
||||||
|
setText(null);
|
||||||
|
} else {
|
||||||
|
setGraphic(null);
|
||||||
|
setText(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tvActivityLogs.setFixedCellSize(52);
|
||||||
|
|
||||||
|
cbRoleFilter.setItems(FXCollections.observableArrayList("All Roles", "ADMIN", "STAFF", "CUSTOMER"));
|
||||||
|
cbRoleFilter.getSelectionModel().selectFirst();
|
||||||
|
cbRoleFilter.setOnAction(e -> applyFilters());
|
||||||
|
|
||||||
|
cbStoreFilter.setItems(FXCollections.observableArrayList("All Stores"));
|
||||||
|
cbStoreFilter.getSelectionModel().selectFirst();
|
||||||
|
cbStoreFilter.setOnAction(e -> applyFilters());
|
||||||
|
|
||||||
|
tfSearch.textProperty().addListener((obs, oldVal, newVal) -> applyFilters());
|
||||||
|
|
||||||
filteredLogs = new FilteredList<>(activityLogs, a -> true);
|
filteredLogs = new FilteredList<>(activityLogs, a -> true);
|
||||||
TableViewSupport.bindSortedItems(tvActivityLogs, filteredLogs);
|
TableViewSupport.bindSortedItems(tvActivityLogs, filteredLogs);
|
||||||
|
|
||||||
dpStart.setValue(LocalDate.now().minusDays(30));
|
dpStart.setValue(LocalDate.now().minusDays(30));
|
||||||
dpEnd.setValue(LocalDate.now());
|
dpEnd.setValue(LocalDate.now());
|
||||||
loadLogs();
|
loadLogs();
|
||||||
@@ -103,22 +139,59 @@ public class ActivityLogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
void onHideViewOnlyChanged(ActionEvent event) {
|
void onFilterChanged(ActionEvent event) {
|
||||||
applyViewOnlyFilter();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyViewOnlyFilter() {
|
private void applyFilters() {
|
||||||
boolean hide = chkHideViewOnly != null && chkHideViewOnly.isSelected();
|
String search = tfSearch != null && tfSearch.getText() != null ? tfSearch.getText().trim().toLowerCase() : "";
|
||||||
if (filteredLogs != null) {
|
String role = cbRoleFilter != null ? cbRoleFilter.getValue() : null;
|
||||||
filteredLogs.setPredicate(hide
|
String store = cbStoreFilter != null ? cbStoreFilter.getValue() : null;
|
||||||
? a -> {
|
boolean hideViewOnly = chkHideViewOnly != null && chkHideViewOnly.isSelected();
|
||||||
String act = a.getActivity();
|
|
||||||
if (act == null) return true;
|
if (filteredLogs == null) return;
|
||||||
String trimmed = act.stripLeading();
|
|
||||||
return !trimmed.startsWith("View");
|
filteredLogs.setPredicate(log -> {
|
||||||
|
if (hideViewOnly) {
|
||||||
|
String act = log.getActivity();
|
||||||
|
if (act != null && act.stripLeading().startsWith("View")) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role != null && !role.equals("All Roles")) {
|
||||||
|
String logRole = displayRole(log);
|
||||||
|
if (!role.equalsIgnoreCase(logRole)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store != null && !store.equals("All Stores")) {
|
||||||
|
if (!store.equals(displayStore(log))) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!search.isEmpty()) {
|
||||||
|
String activity = nullToBlank(log.getActivity()).toLowerCase();
|
||||||
|
String user = displayUser(log).toLowerCase();
|
||||||
|
String username = nullToBlank(log.getUsername()).toLowerCase();
|
||||||
|
String usernameSnap = nullToBlank(log.getUsernameSnapshot()).toLowerCase();
|
||||||
|
if (!activity.contains(search) && !user.contains(search)
|
||||||
|
&& !username.contains(search) && !usernameSnap.contains(search)) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
: a -> true);
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateStoreFilter(List<ActivityLogResponse> logs) {
|
||||||
|
TreeSet<String> stores = new TreeSet<>();
|
||||||
|
for (ActivityLogResponse log : logs) {
|
||||||
|
String s = displayStore(log);
|
||||||
|
if (!s.isBlank()) stores.add(s);
|
||||||
}
|
}
|
||||||
|
String current = cbStoreFilter.getValue();
|
||||||
|
ObservableList<String> options = FXCollections.observableArrayList("All Stores");
|
||||||
|
options.addAll(stores);
|
||||||
|
cbStoreFilter.setItems(options);
|
||||||
|
cbStoreFilter.setValue(options.contains(current) ? current : "All Stores");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadLogs() {
|
private void loadLogs() {
|
||||||
@@ -126,8 +199,8 @@ public class ActivityLogController {
|
|||||||
tvActivityLogs.setDisable(true);
|
tvActivityLogs.setDisable(true);
|
||||||
btnRefresh.setDisable(true);
|
btnRefresh.setDisable(true);
|
||||||
|
|
||||||
java.time.LocalDate startDate = dpStart != null ? dpStart.getValue() : null;
|
LocalDate startDate = dpStart != null ? dpStart.getValue() : null;
|
||||||
java.time.LocalDate endDate = dpEnd != null ? dpEnd.getValue() : null;
|
LocalDate endDate = dpEnd != null ? dpEnd.getValue() : null;
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -136,7 +209,8 @@ public class ActivityLogController {
|
|||||||
|
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
activityLogs.setAll(safeContent);
|
activityLogs.setAll(safeContent);
|
||||||
applyViewOnlyFilter();
|
populateStoreFilter(safeContent);
|
||||||
|
applyFilters();
|
||||||
tvActivityLogs.setDisable(false);
|
tvActivityLogs.setDisable(false);
|
||||||
btnRefresh.setDisable(false);
|
btnRefresh.setDisable(false);
|
||||||
TableViewSupport.flashStatus(lblStatus, "Refreshed");
|
TableViewSupport.flashStatus(lblStatus, "Refreshed");
|
||||||
@@ -145,8 +219,8 @@ public class ActivityLogController {
|
|||||||
ActivityLogger.getInstance().logException("ActivityLogController.loadLogs", e, "Loading activity logs");
|
ActivityLogger.getInstance().logException("ActivityLogController.loadLogs", e, "Loading activity logs");
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
lblError.setText(e.getMessage() == null || e.getMessage().isBlank()
|
lblError.setText(e.getMessage() == null || e.getMessage().isBlank()
|
||||||
? "Could not load activity logs."
|
? "Could not load activity logs."
|
||||||
: "Could not load activity logs: " + e.getMessage());
|
: "Could not load activity logs: " + e.getMessage());
|
||||||
tvActivityLogs.setDisable(false);
|
tvActivityLogs.setDisable(false);
|
||||||
btnRefresh.setDisable(false);
|
btnRefresh.setDisable(false);
|
||||||
});
|
});
|
||||||
@@ -155,41 +229,33 @@ public class ActivityLogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String displayUser(ActivityLogResponse log) {
|
private String displayUser(ActivityLogResponse log) {
|
||||||
if (log == null) {
|
if (log == null) return "";
|
||||||
return "";
|
if (log.getFullNameSnapshot() != null && !log.getFullNameSnapshot().isBlank()) return log.getFullNameSnapshot();
|
||||||
}
|
if (log.getFullName() != null && !log.getFullName().isBlank()) return log.getFullName();
|
||||||
if (log.getFullNameSnapshot() != null && !log.getFullNameSnapshot().isBlank()) {
|
if (log.getUsernameSnapshot() != null && !log.getUsernameSnapshot().isBlank()) return log.getUsernameSnapshot();
|
||||||
return log.getFullNameSnapshot();
|
if (log.getUsername() != null && !log.getUsername().isBlank()) return log.getUsername();
|
||||||
}
|
|
||||||
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()) : "";
|
return log.getUserId() != null ? String.valueOf(log.getUserId()) : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private String displayRole(ActivityLogResponse log) {
|
private String displayRole(ActivityLogResponse log) {
|
||||||
if (log == null) {
|
if (log == null) return "";
|
||||||
return "";
|
if (log.getRoleSnapshot() != null && !log.getRoleSnapshot().isBlank()) return log.getRoleSnapshot();
|
||||||
}
|
|
||||||
if (log.getRoleSnapshot() != null && !log.getRoleSnapshot().isBlank()) {
|
|
||||||
return log.getRoleSnapshot();
|
|
||||||
}
|
|
||||||
return nullToBlank(log.getRole());
|
return nullToBlank(log.getRole());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String formatRole(String role) {
|
||||||
|
if (role == null) return "";
|
||||||
|
return switch (role.toUpperCase()) {
|
||||||
|
case "ADMIN" -> "Admin";
|
||||||
|
case "STAFF" -> "Staff";
|
||||||
|
case "CUSTOMER" -> "Customer";
|
||||||
|
default -> role;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private String displayStore(ActivityLogResponse log) {
|
private String displayStore(ActivityLogResponse log) {
|
||||||
if (log == null) {
|
if (log == null) return "";
|
||||||
return "";
|
if (log.getStoreNameSnapshot() != null && !log.getStoreNameSnapshot().isBlank()) return log.getStoreNameSnapshot();
|
||||||
}
|
|
||||||
if (log.getStoreNameSnapshot() != null && !log.getStoreNameSnapshot().isBlank()) {
|
|
||||||
return log.getStoreNameSnapshot();
|
|
||||||
}
|
|
||||||
return nullToBlank(log.getStoreName());
|
return nullToBlank(log.getStoreName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,57 +3,79 @@
|
|||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.CheckBox?>
|
<?import javafx.scene.control.CheckBox?>
|
||||||
|
<?import javafx.scene.control.ComboBox?>
|
||||||
<?import javafx.scene.control.DatePicker?>
|
<?import javafx.scene.control.DatePicker?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.TableColumn?>
|
<?import javafx.scene.control.TableColumn?>
|
||||||
<?import javafx.scene.control.TableView?>
|
<?import javafx.scene.control.TableView?>
|
||||||
|
<?import javafx.scene.control.TextField?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.Priority?>
|
||||||
|
<?import javafx.scene.layout.Region?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
<?import javafx.scene.text.Font?>
|
<?import javafx.scene.text.Font?>
|
||||||
|
|
||||||
<VBox spacing="18.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.ActivityLogController">
|
<VBox spacing="12.0" style="-fx-font-size: 14px;" xmlns="http://javafx.com/javafx/25" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.example.petshopdesktop.controllers.ActivityLogController">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
</padding>
|
</padding>
|
||||||
|
|
||||||
<children>
|
<children>
|
||||||
<HBox alignment="CENTER_LEFT" spacing="20.0">
|
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="12.0">
|
||||||
<children>
|
<children>
|
||||||
<Label text="Activity Logs" textFill="#2c3e50">
|
<Label text="Activity Logs" textFill="#2c3e50">
|
||||||
<font>
|
<font>
|
||||||
<Font name="System Bold" size="30.0" />
|
<Font name="System Bold" size="30.0" />
|
||||||
</font>
|
</font>
|
||||||
</Label>
|
</Label>
|
||||||
<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">
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
<font>
|
<Button fx:id="btnRefresh" mnemonicParsing="false" onAction="#btnRefreshClicked"
|
||||||
<Font name="System Bold" size="14.0" />
|
prefHeight="36.0" prefWidth="100.0"
|
||||||
</font>
|
style="-fx-background-color: #4ECDC4; -fx-cursor: hand; -fx-background-radius: 8;"
|
||||||
<padding>
|
text="Refresh" textFill="WHITE">
|
||||||
<Insets bottom="12.0" left="24.0" right="24.0" top="12.0" />
|
<font><Font name="System Bold" size="13.0" /></font>
|
||||||
</padding>
|
|
||||||
</Button>
|
</Button>
|
||||||
<Label text="From" textFill="#64748b" />
|
|
||||||
<DatePicker fx:id="dpStart" onAction="#onDateChanged" promptText="Start date" prefWidth="140.0" />
|
|
||||||
<Label text="To" textFill="#64748b" />
|
|
||||||
<DatePicker fx:id="dpEnd" onAction="#onDateChanged" promptText="End date" prefWidth="140.0" />
|
|
||||||
<CheckBox fx:id="chkHideViewOnly" onAction="#onHideViewOnlyChanged" text="Hide view-only" />
|
|
||||||
</children>
|
</children>
|
||||||
</HBox>
|
</HBox>
|
||||||
|
|
||||||
<Label text="Showing activity for the selected date range" textFill="#64748b" />
|
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<TextField fx:id="tfSearch" promptText="Search activity or user…"
|
||||||
|
prefHeight="36.0" HBox.hgrow="ALWAYS"
|
||||||
|
style="-fx-background-radius: 8; -fx-border-radius: 8; -fx-border-color: #e2e8f0;" />
|
||||||
|
|
||||||
|
<ComboBox fx:id="cbRoleFilter" prefHeight="36.0" prefWidth="130.0"
|
||||||
|
style="-fx-background-radius: 8;" />
|
||||||
|
|
||||||
|
<ComboBox fx:id="cbStoreFilter" prefHeight="36.0" prefWidth="160.0"
|
||||||
|
style="-fx-background-radius: 8;" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
|
<HBox alignment="CENTER_LEFT" spacing="10.0">
|
||||||
|
<children>
|
||||||
|
<Label text="From" textFill="#64748b" />
|
||||||
|
<DatePicker fx:id="dpStart" onAction="#onDateChanged" promptText="Start date" prefWidth="140.0" prefHeight="36.0" />
|
||||||
|
<Label text="To" textFill="#64748b" />
|
||||||
|
<DatePicker fx:id="dpEnd" onAction="#onDateChanged" promptText="End date" prefWidth="140.0" prefHeight="36.0" />
|
||||||
|
<Region HBox.hgrow="ALWAYS" />
|
||||||
|
<CheckBox fx:id="chkHideViewOnly" onAction="#onFilterChanged" text="Hide view-only" />
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
|
||||||
<TableView fx:id="tvActivityLogs" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
<TableView fx:id="tvActivityLogs" style="-fx-background-color: white; -fx-background-radius: 12;" VBox.vgrow="ALWAYS">
|
||||||
<columns>
|
<columns>
|
||||||
<TableColumn fx:id="colTimestamp" prefWidth="170.0" text="Timestamp" />
|
<TableColumn fx:id="colTimestamp" prefWidth="150.0" text="Timestamp" />
|
||||||
<TableColumn fx:id="colUser" prefWidth="170.0" text="User" />
|
<TableColumn fx:id="colUser" prefWidth="160.0" text="User" />
|
||||||
<TableColumn fx:id="colRole" prefWidth="90.0" text="Role" />
|
<TableColumn fx:id="colRole" prefWidth="90.0" text="Role" />
|
||||||
<TableColumn fx:id="colStore" prefWidth="160.0" text="Store" />
|
<TableColumn fx:id="colStore" prefWidth="140.0" text="Store" />
|
||||||
<TableColumn fx:id="colActivity" prefWidth="520.0" text="Activity" />
|
<TableColumn fx:id="colActivity" prefWidth="560.0" text="Activity" />
|
||||||
</columns>
|
</columns>
|
||||||
</TableView>
|
</TableView>
|
||||||
|
|
||||||
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true" />
|
<Label fx:id="lblStatus" text="" textFill="#64748b" visible="false" managed="true" />
|
||||||
|
|
||||||
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true" />
|
<Label fx:id="lblError" text="" textFill="#FF6B6B" wrapText="true" />
|
||||||
|
|
||||||
</children>
|
</children>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|||||||
Reference in New Issue
Block a user