turned logs to laymen terms and added to android

This commit is contained in:
Alex
2026-04-15 01:52:35 -06:00
parent 7b176fe938
commit 0cf7144387
9 changed files with 343 additions and 49 deletions

View File

@@ -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<ActivityLogAdapter.ViewHolder> {
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<ActivityLogDTO> items;
public ActivityLogAdapter(List<ActivityLogDTO> items) {
@@ -32,26 +40,77 @@ public class ActivityLogAdapter extends RecyclerView.Adapter<ActivityLogAdapter.
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
ActivityLogDTO log = items.get(position);
holder.tvActivity.setText(log.getActivity());
holder.tvUser.setText(log.getFullName() + " (" + log.getUsername() + ")");
holder.tvMeta.setText(log.getStoreName() + " · " + log.getRole());
String timestamp = log.getLogTimestamp();
String date = DateTimeUtils.extractDate(timestamp);
String time = (timestamp != null && timestamp.length() >= 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);
}
}

View File

@@ -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
);
}

View File

@@ -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<ActivityLogDTO> logList = new ArrayList<>();
private List<DropdownDTO> 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() {

View File

@@ -21,7 +21,7 @@ public class ActivityLogRepository extends BaseRepository {
this.activityLogApi = activityLogApi;
}
public LiveData<Resource<List<ActivityLogDTO>>> getActivityLogs(int limit, Long storeId, String role, String search) {
return executeCall(activityLogApi.getActivityLogs(limit, storeId, role, search));
public LiveData<Resource<List<ActivityLogDTO>>> getActivityLogs(int limit, Long storeId, String role, String search, String startDate, String endDate) {
return executeCall(activityLogApi.getActivityLogs(limit, storeId, role, search, startDate, endDate));
}
}

View File

@@ -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<ActivityLogDTO> 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 <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
liveData.observeForever(new Observer<Resource<T>>() {
@Override

View File

@@ -88,6 +88,57 @@
</LinearLayout>
<!-- Date range pickers -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<Button
android:id="@+id/btnStartDate"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:text="Start Date"
android:textSize="12sp"
android:backgroundTint="@color/white"
android:textColor="@color/text_dark"
android:ellipsize="end"
android:maxLines="1"/>
<View
android:layout_width="8dp"
android:layout_height="0dp"/>
<Button
android:id="@+id/btnEndDate"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:text="End Date"
android:textSize="12sp"
android:backgroundTint="@color/white"
android:textColor="@color/text_dark"
android:ellipsize="end"
android:maxLines="1"/>
<View
android:layout_width="8dp"
android:layout_height="0dp"/>
<Button
android:id="@+id/btnClearDates"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:text="Clear"
android:textSize="12sp"
android:backgroundTint="#e2e8f0"
android:textColor="@color/text_dark"
android:visibility="gone"/>
</LinearLayout>
<!-- Role and Store spinners -->
<LinearLayout
android:layout_width="match_parent"
@@ -119,6 +170,7 @@
</LinearLayout>
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout

View File

@@ -13,17 +13,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="4dp">
android:gravity="center_vertical"
android:layout_marginBottom="2dp">
<TextView
android:id="@+id/tvLogActivity"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="13sp"
android:textSize="14sp"
android:textColor="@color/text_dark"
android:textStyle="bold"
android:fontFamily="monospace"/>
android:textStyle="bold"/>
<TextView
android:id="@+id/tvLogTimestamp"
@@ -36,18 +36,42 @@
</LinearLayout>
<TextView
android:id="@+id/tvLogUser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@color/text_dark"
android:layout_marginBottom="2dp"/>
<TextView
android:id="@+id/tvLogMeta"
android:id="@+id/tvLogTechnical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="11sp"
android:textColor="@color/text_light"/>
android:textColor="@color/text_light"
android:fontFamily="monospace"
android:layout_marginBottom="6dp"
android:visibility="gone"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#F0F0F0"
android:layout_marginBottom="6dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvLogUser"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="12sp"
android:textColor="@color/text_dark"/>
<TextView
android:id="@+id/tvLogMeta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="11sp"
android:textColor="@color/text_light"/>
</LinearLayout>
</LinearLayout>