<collection id="usersDc_entitybox"
class="com.company.tegastest.entity.User" >
<loader id="usersDl_entitybox"
readOnly="true">
<query>
<![CDATA[select e from User e]]>
</query>
</loader>
<fetchPlan extends="_base"/>
</collection>
<collection id="module1Dc" class="com.company.tegastest.entity.ActivityLog">
<loader id="module1Dl">
<query>
<![CDATA[select e from ActivityLog e]]>
</query>
</loader>
<fetchPlan extends="_base"/>
</collection>
<collection id="module2Dc" class="com.company.tegastest.entity.ActivityLog"/>
<collection id="module3Dc" class="com.company.tegastest.entity.ActivityLog"/>
<collection id="module4Dc" class="com.company.tegastest.entity.ActivityLog"/>
</data>
<facets>
<dataLoadCoordinator auto="true"/>
<urlQueryParameters>
<pagination component="pagination1"/>
<pagination component="pagination2"/>
<pagination component="pagination3"/>
<pagination component="pagination4"/>
</urlQueryParameters>
</facets>
<layout classNames="layout-dashboard" width="100%" padding="false">
<vbox classNames="vbox-dashboard">
<h3 id="dashboard_h3_main" text="Dashboard"/>
<div classNames="auditTrail-form-table">
<vbox>
<hbox classNames="hbox-table-dashboard" width="100%" justifyContent="BETWEEN" padding="true">
<h3 id="dashboard_h3_table" text="Audit Trail"/>
</hbox>
<tabSheet classNames="tabsheet-dashboard" width="100%" >
<tab id="tab1" label="Log in/Log out history" classNames="tab-dashboard">
<vbox>
<hbox width="100%">
<textField id="dashboard_searchFieldPlate1" placeholder="Action by..." label="Search" width="55%"
classNames="dashboard-searchFieldPlate"/>
<comboBox id="action_userComboBox_audit" label="Action" placeholder="ALL" width="15%"/>
<datePicker id="datepicker_audit" label="Date" width="15%"/>
<button id="data1_bttn" text="Download Report" action="dataGrid1.rf"/>
</hbox>
<dataGrid dataContainer="module1Dc" width="100%" height="100%" minHeight="15em" pageSize="5" id="dataGrid1">
<columns>
<column property="activity" header="Action"/>
<column property="actionBy" header="Action by"/>
<column property="user" header="User Role"/>
<column property="actionDate" header="Date/Time"/>
</columns>
<actions>
<action id="rf" type="report_runListEntityReport"/>
</actions>
</dataGrid>
<simplePagination id="pagination1" dataLoader="activityDl" alignSelf="CENTER" itemsPerPageDefaultValue="5"/>
</vbox>
</tab>
<tab id="tab2" label="Submission history">
<vbox>
<textField id="dashboard_searchFieldPlate2" placeholder="Enter Company Name..." label="Search" width="50%"
classNames="dashboard-searchFieldPlate"/>
<dataGrid dataContainer="module2Dc" width="100%" height="100%" minHeight="15em" pageSize="5" id="dataGrid2">
<columns>
<column property="actionBy" header="Action by"/>
<column property="user" header="User Role"/>
<column property="actionDate" header="Date/Time"/>
</columns>
</dataGrid>
<simplePagination id="pagination2" dataLoader="activityDl" alignSelf="CENTER" itemsPerPageDefaultValue="5"/>
</vbox>
</tab>
<tab id="tab3" label="Approval/Rejected/Blacklist">
<vbox>
<textField id="dashboard_searchFieldPlate3" placeholder="Enter Company Name..." label="Search" width="50%"
classNames="dashboard-searchFieldPlate"/>
<dataGrid dataContainer="module3Dc" width="100%" height="100%" minHeight="15em" pageSize="5" id="dataGrid3">
<columns>
<column property="actionBy" header="Action by"/>
<column property="user" header="User Role"/>
<column property="actionDate" header="Date/Time"/>
</columns>
</dataGrid>
<simplePagination id="pagination3" dataLoader="activityDl" alignSelf="CENTER" itemsPerPageDefaultValue="5"/>
</vbox>
</tab>
<tab id="tab4" label="Registration/Profile Edit">
<vbox>
<textField id="dashboard_searchFieldPlate4" placeholder="Enter Company Name..." label="Search" width="50%"
classNames="dashboard-searchFieldPlate"/>
<dataGrid dataContainer="module4Dc" width="100%" height="100%" minHeight="15em" pageSize="5" id="dataGrid4">
<columns>
<column property="actionBy" header="Action by"/>
<column property="user" header="User Role"/>
<column property="actionDate" header="Date/Time"/>
</columns>
</dataGrid>
<simplePagination id="pagination4" dataLoader="activityDl" alignSelf="CENTER" itemsPerPageDefaultValue="5"/>
</vbox>
</tab>
</tabSheet>
</vbox>
</div>
</vbox>
<footer classNames="footer-dashboard" width="100%">
<hbox width="100%" justifyContent="BETWEEN" padding="true">
<hbox>
<nativeLabel id="dashboard_textfield_footer" classNames="dashboard-textfield-footer"
text="Copyright © 2023 | Tabung Gagasan Ekonomi Anak Sarawak (TEGAS)"/>
</hbox>
<hbox>
<hbox>
<anchor href="https://www.facebook.com/tegastf" target="BLANK">
<image resource="icons/Icon facebook.svg"/>
</anchor>
<anchor href="https://www.instagram.com/tegas_sarawak" target="BLANK">
<image resource="icons/Icon Instagram.svg"/>
</anchor>
</hbox>
</hbox>
</hbox>
</footer>
</layout>
package com.company.tegastest.view.audittrail;
import com.company.tegastest.entity.ActivityLog;
import com.company.tegastest.view.main.MainView;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.router.Route;
import io.jmix.core.DataManager;
import io.jmix.flowui.component.datepicker.TypedDatePicker;
import io.jmix.flowui.component.grid.DataGrid;
import io.jmix.flowui.component.textfield.TypedTextField;
import io.jmix.flowui.model.CollectionContainer;
import io.jmix.flowui.view.*;
import org.springframework.beans.factory.annotation.Autowired;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
@Route(value = “audit-trail-view”, layout = MainView.class)
@ViewController(id = “AuditTrailView”)
@ViewDescriptor(path = “audit-trail-view.xml”)
public class AuditTrailView extends StandardView {
// --- Injected Components ---
@Autowired
private DataManager dataManager;
// Filter Components
@ViewComponent
private TypedTextField<String> dashboard_searchFieldPlate1;
@ViewComponent
private ComboBox<String> action_userComboBox_audit;
// Data Containers
@ViewComponent
private CollectionContainer<ActivityLog> module1Dc;
@ViewComponent
private CollectionContainer<ActivityLog> module2Dc;
@ViewComponent
private CollectionContainer<ActivityLog> module3Dc;
@ViewComponent
private CollectionContainer<ActivityLog> module4Dc;
// Data Grids
@ViewComponent
private DataGrid<ActivityLog> dataGrid1;
@ViewComponent
private DataGrid<ActivityLog> dataGrid2;
@ViewComponent
private DataGrid<ActivityLog> dataGrid3;
@ViewComponent
private DataGrid<ActivityLog> dataGrid4;
@ViewComponent
private TypedDatePicker<Comparable> datepicker_audit;
@Subscribe
public void onInit(InitEvent event) {
// Populate the action combo box with items
action_userComboBox_audit.setItems(
"Login", "Logout", "Submit", "Approve", "Reject",
"Blacklist", "Suspend", "Unsuspend", "Register", "Edit"
);
// Add a generated column to display the friendly action name in each grid
setupCustomGridColumns();
// Set up listeners for all filter components
setupFilterListeners();
// Perform the initial data load with no filters active
applyFilters();
}
/**
* Sets up listeners for all filter components. When any filter value changes,
* the central applyFilters() method is called.
*/
private void setupFilterListeners() {
dashboard_searchFieldPlate1.addTypedValueChangeListener(e -> applyFilters());
action_userComboBox_audit.addValueChangeListener(e -> applyFilters());
datepicker_audit.addValueChangeListener(e -> applyFilters());
}
/**
* Central method to apply all active filters. It retrieves the current values
* from the filter components and reloads the data for all modules.
*/
private void applyFilters() {
String searchText = dashboard_searchFieldPlate1.getValue();
String selectedAction = action_userComboBox_audit.getValue();
LocalDate selectedDate = datepicker_audit.getValue();
loadModuleLogs(1, module1Dc, searchText, selectedAction, selectedDate);
loadModuleLogs(2, module2Dc, searchText, selectedAction, selectedDate);
loadModuleLogs(3, module3Dc, searchText, selectedAction, selectedDate);
loadModuleLogs(4, module4Dc, searchText, selectedAction, selectedDate);
}
/**
* Loads logs for a specific module, dynamically building a query
* based on the active filters.
*/
private void loadModuleLogs(int moduleId, CollectionContainer<ActivityLog> container,
String searchText, String selectedAction, LocalDate selectedDate) {
StringBuilder query = new StringBuilder("select a from ActivityLog a where a.module = :module");
Map<String, Object> params = new HashMap<>();
params.put("module", moduleId);
boolean hasSearchText = searchText != null && !searchText.trim().isEmpty();
boolean hasSelectedAction = selectedAction != null && !selectedAction.isEmpty();
boolean hasSelectedDate = selectedDate != null;
// --- Apply Search Text Filter (Action By) ---
if (hasSearchText) {
query.append(" and lower(a.actionBy) like :searchText");
params.put("searchText", "%" + searchText.toLowerCase().trim() + "%");
}
// --- Apply Date Filter ---
if (hasSelectedDate) {
ZonedDateTime startOfDay = selectedDate.atStartOfDay(ZoneId.systemDefault());
ZonedDateTime endOfDay = selectedDate.atTime(LocalTime.MAX).atZone(ZoneId.systemDefault());
query.append(" and a.actionDate >= :startDate and a.actionDate <= :endDate");
params.put("startDate", Date.from(startOfDay.toInstant()));
params.put("endDate", Date.from(endOfDay.toInstant()));
}
query.append(" order by a.actionDate desc");
List<ActivityLog> logs = dataManager.load(ActivityLog.class)
.query(query.toString())
.parameters(params)
.list();
// --- Apply Action Filter (In-Memory) ---
// This is done in memory because the action name is resolved from a code
if (hasSelectedAction) {
logs = logs.stream()
.filter(log -> selectedAction.equals(resolveActivityName(log.getModule(), log.getActivity())))
.toList();
}
container.setItems(logs);
}
private void setupCustomGridColumns() {
DataGrid<ActivityLog>[] grids = new DataGrid[]{dataGrid1, dataGrid2, dataGrid3, dataGrid4};
for (DataGrid<ActivityLog> grid : grids) {
grid.addColumn(log -> resolveActivityName(log.getModule(), log.getActivity()))
.setHeader("Action")
.setSortable(true)
.setAutoWidth(true);
}
}
private String resolveActivityName(Integer module, Integer activityCode) {
if (module == null || activityCode == null) return "";
return switch (module) {
case 1 -> switch (activityCode) {
case 1 -> "Login";
case 2 -> "Logout";
default -> "Unknown";
};
case 2 -> switch (activityCode) {
case 3 -> "Submit";
default -> "Unknown";
};
case 3 -> switch (activityCode) {
case 4 -> "Approve";
case 5 -> "Reject";
case 6 -> "Blacklist";
case 9 -> "Suspend";
case 10 -> "Unsuspend";
default -> "Unknown";
};
case 4 -> switch (activityCode) {
case 7 -> "Register";
case 8 -> "Edit";
default -> "Unknown";
};
default -> "Unknown Module";
};
}
}