From 0cf71443875d218ef164f07f510568003bdfd1aa Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Wed, 15 Apr 2026 01:52:35 -0600 Subject: [PATCH] turned logs to laymen terms and added to android --- .../adapters/ActivityLogAdapter.java | 83 ++++++++-- .../petstoremobile/api/ActivityLogApi.java | 4 +- .../listfragments/ActivityLogFragment.java | 33 ++++ .../repositories/ActivityLogRepository.java | 4 +- .../viewmodels/ActivityLogListViewModel.java | 11 +- .../main/res/layout/fragment_activity_log.xml | 52 ++++++ .../src/main/res/layout/item_activity_log.xml | 52 ++++-- .../backend/config/ActivityLoggingFilter.java | 151 +++++++++++++++--- .../backend/controller/AuthController.java | 2 +- 9 files changed, 343 insertions(+), 49 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/ActivityLogAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/ActivityLogAdapter.java index 2c61551b..eb571ca2 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/ActivityLogAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/ActivityLogAdapter.java @@ -10,11 +10,19 @@ import androidx.recyclerview.widget.RecyclerView; import com.example.petstoremobile.R; import com.example.petstoremobile.dtos.ActivityLogDTO; -import com.example.petstoremobile.utils.DateTimeUtils; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; +import java.util.Locale; public class ActivityLogAdapter extends RecyclerView.Adapter { + + private static final String SEPARATOR = " | "; + private static final SimpleDateFormat INPUT_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.getDefault()); + private static final SimpleDateFormat OUTPUT_FORMAT = new SimpleDateFormat("MMM d, HH:mm", Locale.getDefault()); + private final List items; public ActivityLogAdapter(List items) { @@ -32,26 +40,77 @@ public class ActivityLogAdapter extends RecyclerView.Adapter= 16) ? timestamp.substring(11, 16) : null; - holder.tvTimestamp.setText(date != null && time != null ? date + " " + time : date); + + String activity = log.getActivity() != null ? log.getActivity() : ""; + int separatorIndex = activity.indexOf(SEPARATOR); + if (separatorIndex >= 0) { + holder.tvActivity.setText(activity.substring(0, separatorIndex).trim()); + holder.tvTechnical.setText(activity.substring(separatorIndex + SEPARATOR.length()).trim()); + holder.tvTechnical.setVisibility(View.VISIBLE); + } else { + holder.tvActivity.setText(activity); + holder.tvTechnical.setVisibility(View.GONE); + } + + String fullName = firstNonBlank(log.getFullName(), log.getFullNameSnapshot(), "Unknown"); + String username = firstNonBlank(log.getUsername(), log.getUsernameSnapshot(), ""); + holder.tvUser.setText(username.isEmpty() ? fullName : fullName + " (" + username + ")"); + + String role = firstNonBlank(log.getRole(), log.getRoleSnapshot(), ""); + String store = firstNonBlank(log.getStoreName(), log.getStoreNameSnapshot(), ""); + if (!role.isEmpty() && !store.isEmpty()) { + holder.tvMeta.setText(store + " · " + formatRole(role)); + } else if (!role.isEmpty()) { + holder.tvMeta.setText(formatRole(role)); + } else if (!store.isEmpty()) { + holder.tvMeta.setText(store); + } else { + holder.tvMeta.setText(""); + } + + holder.tvTimestamp.setText(formatTimestamp(log.getLogTimestamp())); } @Override public int getItemCount() { return items.size(); } + private String formatTimestamp(String raw) { + if (raw == null) return ""; + try { + String normalized = raw.length() > 19 ? raw.substring(0, 19) : raw; + Date date = INPUT_FORMAT.parse(normalized); + return date != null ? OUTPUT_FORMAT.format(date) : raw.substring(0, Math.min(16, raw.length())).replace("T", " "); + } catch (ParseException e) { + return raw.length() >= 16 ? raw.substring(0, 16).replace("T", " ") : raw; + } + } + + private String formatRole(String role) { + if (role == null) return ""; + switch (role.toUpperCase(Locale.ROOT)) { + case "ADMIN": return "Admin"; + case "STAFF": return "Staff"; + case "CUSTOMER": return "Customer"; + default: return role; + } + } + + private String firstNonBlank(String... values) { + for (String v : values) { + if (v != null && !v.isBlank()) return v; + } + return ""; + } + public static class ViewHolder extends RecyclerView.ViewHolder { - TextView tvActivity, tvUser, tvMeta, tvTimestamp; + TextView tvActivity, tvTechnical, tvUser, tvMeta, tvTimestamp; public ViewHolder(@NonNull View itemView) { super(itemView); - tvActivity = itemView.findViewById(R.id.tvLogActivity); - tvUser = itemView.findViewById(R.id.tvLogUser); - tvMeta = itemView.findViewById(R.id.tvLogMeta); + tvActivity = itemView.findViewById(R.id.tvLogActivity); + tvTechnical = itemView.findViewById(R.id.tvLogTechnical); + tvUser = itemView.findViewById(R.id.tvLogUser); + tvMeta = itemView.findViewById(R.id.tvLogMeta); tvTimestamp = itemView.findViewById(R.id.tvLogTimestamp); } } diff --git a/android/app/src/main/java/com/example/petstoremobile/api/ActivityLogApi.java b/android/app/src/main/java/com/example/petstoremobile/api/ActivityLogApi.java index d6b7e2ae..12ceb5bd 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/ActivityLogApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/ActivityLogApi.java @@ -15,6 +15,8 @@ public interface ActivityLogApi { @Query("limit") int limit, @Query("storeId") Long storeId, @Query("role") String role, - @Query("search") String search + @Query("search") String search, + @Query("startDate") String startDate, + @Query("endDate") String endDate ); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ActivityLogFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ActivityLogFragment.java index 03a90fe3..f369d383 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ActivityLogFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/ActivityLogFragment.java @@ -1,5 +1,6 @@ package com.example.petstoremobile.fragments.listfragments; +import android.app.DatePickerDialog; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -28,6 +29,7 @@ import com.example.petstoremobile.viewmodels.ActivityLogListViewModel; import javax.inject.Inject; import java.util.ArrayList; +import java.util.Calendar; import java.util.List; import dagger.hilt.android.AndroidEntryPoint; @@ -39,6 +41,8 @@ public class ActivityLogFragment extends Fragment { private ActivityLogAdapter adapter; private final List logList = new ArrayList<>(); private List storeList = new ArrayList<>(); + private String selectedStartDate = null; + private String selectedEndDate = null; @Inject TokenManager tokenManager; @@ -106,6 +110,35 @@ public class ActivityLogFragment extends Fragment { ? binding.spinnerRoleFilter.getSelectedItem().toString() : "All Roles")); SpinnerUtils.setupFilterSpinner(binding.spinnerStoreFilter, this::onStoreSelected); + + binding.btnStartDate.setOnClickListener(v -> showDatePicker(true)); + binding.btnEndDate.setOnClickListener(v -> showDatePicker(false)); + binding.btnClearDates.setOnClickListener(v -> { + selectedStartDate = null; + selectedEndDate = null; + binding.btnStartDate.setText("Start Date"); + binding.btnEndDate.setText("End Date"); + binding.btnClearDates.setVisibility(View.GONE); + viewModel.setDateRange(null, null); + }); + } + + private void showDatePicker(boolean isStart) { + Calendar cal = Calendar.getInstance(); + new DatePickerDialog(requireContext(), (view, year, month, day) -> { + String date = String.format("%04d-%02d-%02d", year, month + 1, day); + String label = String.format("%02d/%02d/%04d", day, month + 1, year); + if (isStart) { + selectedStartDate = date; + binding.btnStartDate.setText(label); + } else { + selectedEndDate = date; + binding.btnEndDate.setText(label); + } + binding.btnClearDates.setVisibility( + selectedStartDate != null || selectedEndDate != null ? View.VISIBLE : View.GONE); + viewModel.setDateRange(selectedStartDate, selectedEndDate); + }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show(); } private void onStoreSelected() { diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/ActivityLogRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/ActivityLogRepository.java index 866ea2ad..6c8acddf 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/ActivityLogRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/ActivityLogRepository.java @@ -21,7 +21,7 @@ public class ActivityLogRepository extends BaseRepository { this.activityLogApi = activityLogApi; } - public LiveData>> getActivityLogs(int limit, Long storeId, String role, String search) { - return executeCall(activityLogApi.getActivityLogs(limit, storeId, role, search)); + public LiveData>> getActivityLogs(int limit, Long storeId, String role, String search, String startDate, String endDate) { + return executeCall(activityLogApi.getActivityLogs(limit, storeId, role, search, startDate, endDate)); } } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ActivityLogListViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ActivityLogListViewModel.java index ff7ff71c..d944de83 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ActivityLogListViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ActivityLogListViewModel.java @@ -32,6 +32,8 @@ public class ActivityLogListViewModel extends ViewModel { private Long currentStoreId = null; private String currentRole = null; private String currentSearch = null; + private String currentStartDate = null; + private String currentEndDate = null; private int currentLimit = PAGE_SIZE; private boolean isLastPage = false; @@ -71,7 +73,7 @@ public class ActivityLogListViewModel extends ViewModel { if (isLastPage) return; isLoading.setValue(true); - observeOnce(repository.getActivityLogs(currentLimit, currentStoreId, currentRole, currentSearch), resource -> { + observeOnce(repository.getActivityLogs(currentLimit, currentStoreId, currentRole, currentSearch, currentStartDate, currentEndDate), resource -> { if (resource != null) { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { List result = resource.data; @@ -101,6 +103,13 @@ public class ActivityLogListViewModel extends ViewModel { loadLogs(true); } + public void setDateRange(String startDate, String endDate) { + currentStartDate = startDate; + currentEndDate = endDate; + loadLogs(true); + } + + private void observeOnce(LiveData> liveData, Observer> handler) { liveData.observeForever(new Observer>() { @Override diff --git a/android/app/src/main/res/layout/fragment_activity_log.xml b/android/app/src/main/res/layout/fragment_activity_log.xml index b81dcea7..35923546 100644 --- a/android/app/src/main/res/layout/fragment_activity_log.xml +++ b/android/app/src/main/res/layout/fragment_activity_log.xml @@ -88,6 +88,57 @@ + + + +