added Analytics filter
This commit is contained in:
@@ -2,8 +2,7 @@ package com.example.petstoremobile.dtos;
|
||||
|
||||
public class EmployeeDTO {
|
||||
|
||||
private long EmployeeId;
|
||||
private Long userId;
|
||||
private Long id;
|
||||
private String username;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
@@ -11,16 +10,18 @@ public class EmployeeDTO {
|
||||
private String email;
|
||||
private String phone;
|
||||
private String role;
|
||||
private String staffRole;
|
||||
private Boolean active;
|
||||
private String createAt;
|
||||
private Integer loyaltyPoints;
|
||||
private Long primaryStoreId;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
private String password;
|
||||
|
||||
|
||||
// Constructor for create and update the employee
|
||||
|
||||
public EmployeeDTO() {}
|
||||
|
||||
public EmployeeDTO(String username, String password, String firstName, String lastName,
|
||||
String email, String phone, String role, boolean active) {
|
||||
String email, String phone, String role, String staffRole, boolean active, Long primaryStoreId) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.firstName = firstName;
|
||||
@@ -28,75 +29,128 @@ public class EmployeeDTO {
|
||||
this.email = email;
|
||||
this.phone = phone;
|
||||
this.role = role;
|
||||
this.staffRole = staffRole;
|
||||
this.active = active;
|
||||
}
|
||||
// password field for request only
|
||||
private String password;
|
||||
|
||||
|
||||
public long getEmployeeId() {
|
||||
|
||||
return EmployeeId;
|
||||
this.primaryStoreId = primaryStoreId;
|
||||
}
|
||||
|
||||
public Long getUserId() {
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
return userId;
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
public String getPhone() {
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public Boolean getActive() {
|
||||
public void setRole(String role) {
|
||||
this.role = role;
|
||||
}
|
||||
|
||||
public String getStaffRole() {
|
||||
return staffRole;
|
||||
}
|
||||
|
||||
public void setStaffRole(String staffRole) {
|
||||
this.staffRole = staffRole;
|
||||
}
|
||||
|
||||
public Boolean getActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public String getCreateAt() {
|
||||
public void setActive(Boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
return createAt;
|
||||
public Integer getLoyaltyPoints() {
|
||||
return loyaltyPoints;
|
||||
}
|
||||
|
||||
public void setLoyaltyPoints(Integer loyaltyPoints) {
|
||||
this.loyaltyPoints = loyaltyPoints;
|
||||
}
|
||||
|
||||
public Long getPrimaryStoreId() {
|
||||
return primaryStoreId;
|
||||
}
|
||||
|
||||
public void setPrimaryStoreId(Long primaryStoreId) {
|
||||
this.primaryStoreId = primaryStoreId;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(String createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import com.example.petstoremobile.databinding.FragmentAnalyticsBinding;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.AnalyticsViewModel;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
@@ -20,6 +21,10 @@ public class AnalyticsFragment extends Fragment {
|
||||
|
||||
private FragmentAnalyticsBinding binding;
|
||||
private AnalyticsViewModel viewModel;
|
||||
private boolean filtersExpanded = false;
|
||||
|
||||
private static final String[] TOP_N_OPTIONS = {"5", "10", "15", "20"};
|
||||
private static final int[] TOP_N_VALUES = { 5, 10, 15, 20 };
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
@@ -27,19 +32,114 @@ public class AnalyticsFragment extends Fragment {
|
||||
binding = FragmentAnalyticsBinding.inflate(inflater, container, false);
|
||||
viewModel = new ViewModelProvider(this).get(AnalyticsViewModel.class);
|
||||
|
||||
setupFilterPanel();
|
||||
observeViewModel();
|
||||
viewModel.loadAnalytics();
|
||||
|
||||
binding.btnRefreshAnalytics.setOnClickListener(v -> viewModel.loadAnalytics());
|
||||
|
||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerAnalytics, this);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
// Filter Panel
|
||||
|
||||
private void setupFilterPanel() {
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerTopN, TOP_N_OPTIONS);
|
||||
|
||||
// Toggle expand/collapse
|
||||
binding.rowFilterHeader.setOnClickListener(v -> toggleFilters());
|
||||
|
||||
// Date pickers
|
||||
binding.etFilterStartDate.setOnClickListener(v ->
|
||||
UIUtils.showDatePicker(requireContext(), binding.etFilterStartDate, this::updateFilterSummary));
|
||||
binding.etFilterEndDate.setOnClickListener(v ->
|
||||
UIUtils.showDatePicker(requireContext(), binding.etFilterEndDate, this::updateFilterSummary));
|
||||
|
||||
// Quick presets
|
||||
binding.btnPresetToday.setOnClickListener(v -> applyPreset(0, 0));
|
||||
binding.btnPreset7D.setOnClickListener(v -> applyPreset(-6, 0));
|
||||
binding.btnPreset30D.setOnClickListener(v -> applyPreset(-29, 0));
|
||||
binding.btnPreset3M.setOnClickListener(v -> applyPreset(-89, 0));
|
||||
binding.btnPreset1Y.setOnClickListener(v -> applyPreset(-364, 0));
|
||||
binding.btnPresetAll.setOnClickListener(v -> {
|
||||
binding.etFilterStartDate.setText("");
|
||||
binding.etFilterEndDate.setText("");
|
||||
updateFilterSummary();
|
||||
});
|
||||
|
||||
binding.btnFilterApply.setOnClickListener(v -> applyFiltersFromUI());
|
||||
binding.btnFilterReset.setOnClickListener(v -> resetFilters());
|
||||
}
|
||||
|
||||
private void toggleFilters() {
|
||||
filtersExpanded = !filtersExpanded;
|
||||
binding.llFilterContent.setVisibility(filtersExpanded ? View.VISIBLE : View.GONE);
|
||||
binding.tvFilterToggleIcon.setText(filtersExpanded ? "▲" : "▼");
|
||||
}
|
||||
|
||||
private void applyPreset(int startOffset, int endOffset) {
|
||||
binding.etFilterStartDate.setText(getDateString(startOffset));
|
||||
binding.etFilterEndDate.setText(getDateString(endOffset));
|
||||
updateFilterSummary();
|
||||
applyFiltersFromUI();
|
||||
}
|
||||
|
||||
private void applyFiltersFromUI() {
|
||||
AnalyticsViewModel.FilterState filter = new AnalyticsViewModel.FilterState();
|
||||
filter.startDate = binding.etFilterStartDate.getText().toString().trim();
|
||||
filter.endDate = binding.etFilterEndDate.getText().toString().trim();
|
||||
|
||||
Object pm = binding.spinnerFilterPayment.getSelectedItem();
|
||||
filter.paymentMethod = pm != null ? pm.toString() : "All";
|
||||
|
||||
int topNPos = binding.spinnerTopN.getSelectedItemPosition();
|
||||
filter.topN = (topNPos >= 0 && topNPos < TOP_N_VALUES.length) ? TOP_N_VALUES[topNPos] : 5;
|
||||
|
||||
updateFilterSummary();
|
||||
viewModel.applyFilter(filter);
|
||||
}
|
||||
|
||||
private void resetFilters() {
|
||||
binding.etFilterStartDate.setText("");
|
||||
binding.etFilterEndDate.setText("");
|
||||
binding.spinnerTopN.setSelection(0);
|
||||
// Reset payment method to "All"
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerFilterPayment, "All");
|
||||
updateFilterSummary();
|
||||
viewModel.resetFilter();
|
||||
}
|
||||
|
||||
private void updateFilterSummary() {
|
||||
String start = binding.etFilterStartDate.getText().toString().trim();
|
||||
String end = binding.etFilterEndDate.getText().toString().trim();
|
||||
if (start.isEmpty() && end.isEmpty()) {
|
||||
binding.tvFilterSummary.setText("All time");
|
||||
} else if (start.isEmpty()) {
|
||||
binding.tvFilterSummary.setText("Up to " + shortDate(end));
|
||||
} else if (end.isEmpty()) {
|
||||
binding.tvFilterSummary.setText("From " + shortDate(start));
|
||||
} else {
|
||||
binding.tvFilterSummary.setText(shortDate(start) + " – " + shortDate(end));
|
||||
}
|
||||
}
|
||||
|
||||
private String shortDate(String date) {
|
||||
return (date != null && date.length() >= 10) ? date.substring(5) : date;
|
||||
}
|
||||
|
||||
private String getDateString(int offsetDays) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.add(Calendar.DAY_OF_YEAR, offsetDays);
|
||||
return String.format(Locale.US, "%04d-%02d-%02d",
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
|
||||
// ViewModel Observation
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay);
|
||||
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
if (loading) {
|
||||
@@ -53,6 +153,15 @@ public class AnalyticsFragment extends Fragment {
|
||||
viewModel.getErrorMessage().observe(getViewLifecycleOwner(), error -> {
|
||||
if (error != null) showError(error);
|
||||
});
|
||||
|
||||
viewModel.getAvailablePaymentMethods().observe(getViewLifecycleOwner(), methods -> {
|
||||
if (methods == null || methods.isEmpty()) return;
|
||||
String currentSelection = binding.spinnerFilterPayment.getSelectedItem() != null
|
||||
? binding.spinnerFilterPayment.getSelectedItem().toString() : "All";
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerFilterPayment,
|
||||
methods.toArray(new String[0]));
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerFilterPayment, currentSelection);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,10 +170,12 @@ public class AnalyticsFragment extends Fragment {
|
||||
binding = null;
|
||||
}
|
||||
|
||||
// Display
|
||||
|
||||
private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) {
|
||||
if (data == null) return;
|
||||
|
||||
// Summary
|
||||
// Summary cards
|
||||
binding.tvTotalRevenue.setText("$" + data.totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
||||
binding.tvTotalTransactions.setText(String.valueOf(data.totalTransactions));
|
||||
binding.tvAvgTransaction.setText("$" + data.avgTransaction);
|
||||
@@ -73,11 +184,12 @@ public class AnalyticsFragment extends Fragment {
|
||||
// Top Revenue Products
|
||||
binding.llTopRevenue.removeAllViews();
|
||||
if (data.topRevenueProducts != null && !data.topRevenueProducts.isEmpty()) {
|
||||
BigDecimal maxRevenue = data.topRevenueProducts.get(0).getValue();
|
||||
if (maxRevenue.compareTo(BigDecimal.ZERO) == 0) maxRevenue = BigDecimal.ONE;
|
||||
BigDecimal maxRev = data.topRevenueProducts.get(0).getValue();
|
||||
if (maxRev.compareTo(BigDecimal.ZERO) == 0) maxRev = BigDecimal.ONE;
|
||||
for (Map.Entry<String, BigDecimal> e : data.topRevenueProducts) {
|
||||
addBarRow(binding.llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
||||
addBarRow(binding.llTopRevenue, e.getKey(),
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxRev.floatValue(), "#ff6b35");
|
||||
}
|
||||
} else {
|
||||
addEmptyRow(binding.llTopRevenue, "No data");
|
||||
@@ -99,15 +211,13 @@ public class AnalyticsFragment extends Fragment {
|
||||
// Payment Methods
|
||||
binding.llPaymentMethods.removeAllViews();
|
||||
if (data.paymentMethodStats != null && !data.paymentMethodStats.isEmpty()) {
|
||||
int maxPayment = data.paymentMethodStats.stream().mapToInt(Map.Entry::getValue).max().orElse(1);
|
||||
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
||||
int maxPay = data.paymentMethodStats.stream().mapToInt(Map.Entry::getValue).max().orElse(1);
|
||||
String[] payColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
||||
int ci = 0;
|
||||
for (Map.Entry<String, Integer> e : data.paymentMethodStats) {
|
||||
addBarRow(binding.llPaymentMethods, e.getKey(),
|
||||
e.getValue() + " transactions",
|
||||
(float) e.getValue() / maxPayment,
|
||||
paymentColors[ci % paymentColors.length]);
|
||||
ci++;
|
||||
(float) e.getValue() / maxPay, payColors[ci++ % payColors.length]);
|
||||
}
|
||||
} else {
|
||||
addEmptyRow(binding.llPaymentMethods, "No data");
|
||||
@@ -116,36 +226,37 @@ public class AnalyticsFragment extends Fragment {
|
||||
// Employee Performance
|
||||
binding.llEmployeePerformance.removeAllViews();
|
||||
if (data.employeePerformance != null && !data.employeePerformance.isEmpty()) {
|
||||
BigDecimal maxEmp = data.employeePerformance.get(data.employeePerformance.size() - 1).getValue();
|
||||
BigDecimal maxEmp = data.employeePerformance.get(0).getValue();
|
||||
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
||||
maxEmp = data.employeePerformance.get(0).getValue();
|
||||
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
||||
|
||||
for (Map.Entry<String, BigDecimal> e : data.employeePerformance) {
|
||||
addBarRow(binding.llEmployeePerformance, e.getKey(),
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxEmp.floatValue(),
|
||||
"#1a759f");
|
||||
e.getValue().floatValue() / maxEmp.floatValue(), "#1a759f");
|
||||
}
|
||||
} else {
|
||||
addEmptyRow(binding.llEmployeePerformance, "No data");
|
||||
}
|
||||
|
||||
// Daily Revenue
|
||||
binding.tvDailyRevenueTitle.setText(data.dailyRevenueTitle);
|
||||
binding.llDailyRevenue.removeAllViews();
|
||||
if (data.dailyRevenue != null && !data.dailyRevenue.isEmpty()) {
|
||||
BigDecimal maxDaily = data.dailyRevenue.stream().map(Map.Entry::getValue).max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
||||
BigDecimal maxDaily = data.dailyRevenue.stream()
|
||||
.map(Map.Entry::getValue).max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
||||
if (maxDaily.compareTo(BigDecimal.ZERO) == 0) maxDaily = BigDecimal.ONE;
|
||||
for (Map.Entry<String, BigDecimal> e : data.dailyRevenue) {
|
||||
String label = e.getKey().length() >= 10 ? e.getKey().substring(5) : e.getKey();
|
||||
addBarRow(binding.llDailyRevenue, label,
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxDaily.floatValue(),
|
||||
"#ff6b35");
|
||||
e.getValue().floatValue() / maxDaily.floatValue(), "#ff6b35");
|
||||
}
|
||||
} else {
|
||||
addEmptyRow(binding.llDailyRevenue, "No data");
|
||||
}
|
||||
}
|
||||
|
||||
// Chart Helpers
|
||||
|
||||
private void addBarRow(LinearLayout parent, String label, String value, float ratio, String color) {
|
||||
if (getContext() == null) return;
|
||||
LinearLayout row = new LinearLayout(getContext());
|
||||
@@ -156,8 +267,7 @@ public class AnalyticsFragment extends Fragment {
|
||||
labelRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
TextView tvLabel = new TextView(getContext());
|
||||
tvLabel.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvLabel.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvLabel.setText(label);
|
||||
tvLabel.setTextColor(Color.parseColor("#444441"));
|
||||
tvLabel.setTextSize(13f);
|
||||
@@ -172,22 +282,19 @@ public class AnalyticsFragment extends Fragment {
|
||||
labelRow.addView(tvValue);
|
||||
|
||||
LinearLayout barBg = new LinearLayout(getContext());
|
||||
LinearLayout.LayoutParams bgParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, 12);
|
||||
LinearLayout.LayoutParams bgParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 12);
|
||||
bgParams.setMargins(0, 4, 0, 0);
|
||||
barBg.setLayoutParams(bgParams);
|
||||
barBg.setBackgroundColor(Color.parseColor("#EEEEEE"));
|
||||
|
||||
float safeRatio = Math.max(0f, Math.min(1f, ratio));
|
||||
View barFill = new View(getContext());
|
||||
LinearLayout.LayoutParams fillParams = new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.MATCH_PARENT, ratio);
|
||||
barFill.setLayoutParams(fillParams);
|
||||
barFill.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, safeRatio));
|
||||
barFill.setBackgroundColor(Color.parseColor(color));
|
||||
barBg.addView(barFill);
|
||||
|
||||
View spacer = new View(getContext());
|
||||
spacer.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.MATCH_PARENT, 1f - ratio));
|
||||
spacer.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f - safeRatio));
|
||||
barBg.addView(spacer);
|
||||
|
||||
row.addView(labelRow);
|
||||
@@ -205,8 +312,7 @@ public class AnalyticsFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void showError(String msg) {
|
||||
if (getContext() == null || binding == null)
|
||||
return;
|
||||
if (getContext() == null || binding == null) return;
|
||||
binding.tvTotalRevenue.setText("Error");
|
||||
binding.tvTotalTransactions.setText("—");
|
||||
binding.tvAvgTransaction.setText("—");
|
||||
|
||||
@@ -76,7 +76,7 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
||||
Bundle args = new Bundle();
|
||||
if (position != -1) {
|
||||
EmployeeDTO e = staffList.get(position);
|
||||
args.putLong("employeeId", e.getEmployeeId());
|
||||
args.putLong("employeeId", e.getId());
|
||||
args.putString("username", e.getUsername() != null ? e.getUsername() : "");
|
||||
args.putString("firstName", e.getFirstName() != null ? e.getFirstName() : "");
|
||||
args.putString("lastName", e.getLastName() != null ? e.getLastName() : "");
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.databinding.FragmentStaffDetailBinding;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
@@ -16,6 +17,9 @@ import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.StaffDetailViewModel;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
@@ -25,8 +29,11 @@ public class StaffDetailFragment extends Fragment {
|
||||
private StaffDetailViewModel viewModel;
|
||||
|
||||
private final String[] ROLES = {"STAFF", "ADMIN"};
|
||||
private final String[] STAFF_ROLES = {"STORE_MANAGER", "SALES_ASSOCIATE", "GROOMER", "VETERINARIAN"};
|
||||
private final String[] STATUSES = {"Active", "Inactive"};
|
||||
|
||||
private long preselectedStoreId = -1;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -34,6 +41,8 @@ public class StaffDetailFragment extends Fragment {
|
||||
viewModel = new ViewModelProvider(this).get(StaffDetailViewModel.class);
|
||||
|
||||
setupSpinners();
|
||||
observeViewModel();
|
||||
loadStores();
|
||||
handleArguments();
|
||||
|
||||
binding.btnStaffBack.setOnClickListener(v -> navigateBack());
|
||||
@@ -45,11 +54,30 @@ public class StaffDetailFragment extends Fragment {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
||||
}
|
||||
|
||||
private void setupSpinners() {
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffRole, ROLES);
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffType, STAFF_ROLES);
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffStatus, STATUSES);
|
||||
}
|
||||
|
||||
private void loadStores() {
|
||||
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setStoreList(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshStoreSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaffStore, viewModel.getStoreList().getValue(),
|
||||
DropdownDTO::getLabel, "-- Select Store --",
|
||||
preselectedStoreId, DropdownDTO::getId);
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.getBoolean("isEditing", false)) {
|
||||
@@ -59,16 +87,9 @@ public class StaffDetailFragment extends Fragment {
|
||||
binding.tvStaffMode.setText("Edit Staff Account");
|
||||
binding.tvStaffId.setText("ID: " + employeeId);
|
||||
binding.tvStaffId.setVisibility(View.VISIBLE);
|
||||
binding.etStaffUsername.setText(a.getString("username", ""));
|
||||
binding.etStaffFirstName.setText(a.getString("firstName", ""));
|
||||
binding.etStaffLastName.setText(a.getString("lastName", ""));
|
||||
binding.etStaffEmail.setText(a.getString("email", ""));
|
||||
binding.etStaffPhone.setText(a.getString("phone", ""));
|
||||
binding.btnDeleteStaff.setVisibility(View.VISIBLE);
|
||||
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerStaffRole, a.getString("role", "STAFF"));
|
||||
binding.spinnerStaffStatus.setSelection(a.getBoolean("active", true) ? 0 : 1);
|
||||
|
||||
loadEmployeeData(employeeId);
|
||||
} else {
|
||||
viewModel.setEmployeeId(-1, false);
|
||||
binding.tvStaffMode.setText("Add Staff Account");
|
||||
@@ -77,6 +98,29 @@ public class StaffDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadEmployeeData(long id) {
|
||||
viewModel.loadEmployee(id).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
EmployeeDTO e = resource.data;
|
||||
binding.etStaffUsername.setText(e.getUsername());
|
||||
binding.etStaffFirstName.setText(e.getFirstName());
|
||||
binding.etStaffLastName.setText(e.getLastName());
|
||||
binding.etStaffEmail.setText(e.getEmail());
|
||||
binding.etStaffPhone.setText(e.getPhone());
|
||||
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerStaffRole, e.getRole());
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerStaffType, e.getStaffRole());
|
||||
binding.spinnerStaffStatus.setSelection(Boolean.TRUE.equals(e.getActive()) ? 0 : 1);
|
||||
|
||||
preselectedStoreId = e.getPrimaryStoreId() != null ? e.getPrimaryStoreId() : -1;
|
||||
refreshStoreSpinner();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
@@ -100,6 +144,7 @@ public class StaffDetailFragment extends Fragment {
|
||||
if (!InputValidator.isNotEmpty(binding.etStaffLastName, "Last Name")) return;
|
||||
if (!InputValidator.isValidEmail(binding.etStaffEmail)) return;
|
||||
if (!InputValidator.isValidPhone(binding.etStaffPhone)) return;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerStaffStore, "Primary Store")) return;
|
||||
|
||||
String username = binding.etStaffUsername.getText().toString().trim();
|
||||
String password = binding.etStaffPassword.getText().toString().trim();
|
||||
@@ -108,7 +153,11 @@ public class StaffDetailFragment extends Fragment {
|
||||
String email = binding.etStaffEmail.getText().toString().trim();
|
||||
String phone = binding.etStaffPhone.getText().toString().trim();
|
||||
String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()];
|
||||
String staffRole = STAFF_ROLES[binding.spinnerStaffType.getSelectedItemPosition()];
|
||||
boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0;
|
||||
|
||||
List<DropdownDTO> stores = viewModel.getStoreList().getValue();
|
||||
Long storeId = stores.get(binding.spinnerStaffStore.getSelectedItemPosition() - 1).getId();
|
||||
|
||||
EmployeeDTO dto = new EmployeeDTO(
|
||||
username,
|
||||
@@ -118,7 +167,9 @@ public class StaffDetailFragment extends Fragment {
|
||||
email,
|
||||
phone,
|
||||
role,
|
||||
active
|
||||
staffRole,
|
||||
active,
|
||||
storeId
|
||||
);
|
||||
|
||||
viewModel.saveEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
|
||||
@@ -16,6 +16,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
@@ -30,6 +31,10 @@ public class AnalyticsViewModel extends ViewModel {
|
||||
private final MutableLiveData<AnalyticsData> analyticsData = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<String>> availablePaymentMethods = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
private List<SaleDTO> cachedSales = new ArrayList<>();
|
||||
private FilterState currentFilter = new FilterState();
|
||||
|
||||
@Inject
|
||||
public AnalyticsViewModel(SaleRepository saleRepository) {
|
||||
@@ -39,14 +44,17 @@ public class AnalyticsViewModel extends ViewModel {
|
||||
public LiveData<AnalyticsData> getAnalyticsData() { return analyticsData; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
public LiveData<String> getErrorMessage() { return errorMessage; }
|
||||
public LiveData<List<String>> getAvailablePaymentMethods() { return availablePaymentMethods; }
|
||||
|
||||
public void loadAnalytics() {
|
||||
isLoading.setValue(true);
|
||||
errorMessage.setValue(null);
|
||||
saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc").observeForever(resource -> {
|
||||
saleRepository.getAllSales(0, 2000, null, null, null, "saleDate,desc").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
computeAnalytics(resource.data.getContent());
|
||||
cachedSales = resource.data.getContent();
|
||||
derivePaymentMethods();
|
||||
applyCurrentFilter();
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
errorMessage.setValue(resource.message);
|
||||
@@ -56,11 +64,53 @@ public class AnalyticsViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
private void computeAnalytics(List<SaleDTO> sales) {
|
||||
public void applyFilter(FilterState filter) {
|
||||
currentFilter = filter;
|
||||
applyCurrentFilter();
|
||||
}
|
||||
|
||||
public void resetFilter() {
|
||||
currentFilter = new FilterState();
|
||||
applyCurrentFilter();
|
||||
}
|
||||
|
||||
private void applyCurrentFilter() {
|
||||
List<SaleDTO> filtered = filterSales(cachedSales, currentFilter);
|
||||
computeAnalytics(filtered, currentFilter);
|
||||
}
|
||||
|
||||
private void derivePaymentMethods() {
|
||||
java.util.Set<String> methods = new java.util.TreeSet<>();
|
||||
for (SaleDTO s : cachedSales) {
|
||||
if (s.getPaymentMethod() != null && !s.getPaymentMethod().isEmpty()) {
|
||||
methods.add(s.getPaymentMethod());
|
||||
}
|
||||
}
|
||||
List<String> result = new ArrayList<>();
|
||||
result.add("All");
|
||||
result.addAll(methods);
|
||||
availablePaymentMethods.setValue(result);
|
||||
}
|
||||
|
||||
private List<SaleDTO> filterSales(List<SaleDTO> sales, FilterState filter) {
|
||||
List<SaleDTO> result = new ArrayList<>();
|
||||
for (SaleDTO s : sales) {
|
||||
String date = s.getSaleDate() != null && s.getSaleDate().length() >= 10
|
||||
? s.getSaleDate().substring(0, 10) : "";
|
||||
if (!filter.startDate.isEmpty() && !date.isEmpty() && date.compareTo(filter.startDate) < 0) continue;
|
||||
if (!filter.endDate.isEmpty() && !date.isEmpty() && date.compareTo(filter.endDate) > 0) continue;
|
||||
if (!filter.paymentMethod.equals("All") && !filter.paymentMethod.isEmpty()) {
|
||||
if (!filter.paymentMethod.equalsIgnoreCase(s.getPaymentMethod())) continue;
|
||||
}
|
||||
result.add(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void computeAnalytics(List<SaleDTO> sales, FilterState filter) {
|
||||
List<SaleDTO> regularSales = new ArrayList<>();
|
||||
for (SaleDTO s : sales) {
|
||||
if (!Boolean.TRUE.equals(s.getIsRefund()))
|
||||
regularSales.add(s);
|
||||
if (!Boolean.TRUE.equals(s.getIsRefund())) regularSales.add(s);
|
||||
}
|
||||
|
||||
AnalyticsData data = new AnalyticsData();
|
||||
@@ -83,72 +133,127 @@ public class AnalyticsViewModel extends ViewModel {
|
||||
: BigDecimal.ZERO;
|
||||
data.totalItems = totalItems;
|
||||
|
||||
// Product Maps
|
||||
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
||||
Map<String, Integer> quantityByProduct = new LinkedHashMap<>();
|
||||
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
||||
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
||||
|
||||
for (SaleDTO s : regularSales) {
|
||||
// Payments
|
||||
String method = s.getPaymentMethod() != null ? s.getPaymentMethod() : "Unknown";
|
||||
paymentCount.merge(method, 1, Integer::sum);
|
||||
|
||||
// Employee
|
||||
String emp = s.getEmployeeName() != null ? s.getEmployeeName() : "Unknown";
|
||||
if (s.getTotalAmount() != null) employeeRevenue.merge(emp, s.getTotalAmount(), BigDecimal::add);
|
||||
|
||||
// Items
|
||||
if (s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
String name = item.getProductName() != null ? item.getProductName() : "Unknown";
|
||||
int qty = item.getQuantity() != null ? Math.abs(item.getQuantity()) : 0;
|
||||
BigDecimal lineTotal = item.getUnitPrice() != null
|
||||
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty))
|
||||
: BigDecimal.ZERO;
|
||||
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty)) : BigDecimal.ZERO;
|
||||
revenueByProduct.merge(name, lineTotal, BigDecimal::add);
|
||||
quantityByProduct.merge(name, qty, Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort Top Revenue
|
||||
int topN = filter.topN > 0 ? filter.topN : 5;
|
||||
|
||||
data.topRevenueProducts = new ArrayList<>(revenueByProduct.entrySet());
|
||||
data.topRevenueProducts.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||
if (data.topRevenueProducts.size() > 5) data.topRevenueProducts = data.topRevenueProducts.subList(0, 5);
|
||||
if (data.topRevenueProducts.size() > topN) data.topRevenueProducts = data.topRevenueProducts.subList(0, topN);
|
||||
|
||||
// Sort Top Quantity
|
||||
data.topQuantityProducts = new ArrayList<>(quantityByProduct.entrySet());
|
||||
data.topQuantityProducts.sort((a, b) -> b.getValue() - a.getValue());
|
||||
if (data.topQuantityProducts.size() > 5) data.topQuantityProducts = data.topQuantityProducts.subList(0, 5);
|
||||
if (data.topQuantityProducts.size() > topN) data.topQuantityProducts = data.topQuantityProducts.subList(0, topN);
|
||||
|
||||
// Payment Stats
|
||||
data.paymentMethodStats = new ArrayList<>(paymentCount.entrySet());
|
||||
data.paymentMethodStats.sort((a, b) -> b.getValue() - a.getValue());
|
||||
|
||||
// Employee Performance
|
||||
data.employeePerformance = new ArrayList<>(employeeRevenue.entrySet());
|
||||
data.employeePerformance.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||
|
||||
// Daily Revenue (last 7 days)
|
||||
Map<String, BigDecimal> dailyMap = new TreeMap<>();
|
||||
for (int i = 6; i >= 0; i--) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.add(Calendar.DAY_OF_YEAR, -i);
|
||||
String key = String.format("%04d-%02d-%02d",
|
||||
day.get(Calendar.YEAR), day.get(Calendar.MONTH) + 1, day.get(Calendar.DAY_OF_MONTH));
|
||||
dailyMap.put(key, BigDecimal.ZERO);
|
||||
// Daily revenue display to filter date range, max 60 days
|
||||
String rangeStart = filter.startDate;
|
||||
String rangeEnd = filter.endDate;
|
||||
if (rangeStart.isEmpty() && rangeEnd.isEmpty()) {
|
||||
rangeEnd = todayString(0);
|
||||
rangeStart = todayString(-6);
|
||||
} else if (rangeStart.isEmpty()) {
|
||||
rangeStart = shiftDate(rangeEnd, -6);
|
||||
} else if (rangeEnd.isEmpty()) {
|
||||
rangeEnd = todayString(0);
|
||||
}
|
||||
|
||||
List<String> dateRange = buildDateRange(rangeStart, rangeEnd, 60);
|
||||
Map<String, BigDecimal> dailyMap = new TreeMap<>();
|
||||
for (String d : dateRange) dailyMap.put(d, BigDecimal.ZERO);
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getSaleDate() != null && s.getTotalAmount() != null) {
|
||||
String date = s.getSaleDate().length() >= 10 ? s.getSaleDate().substring(0, 10) : s.getSaleDate();
|
||||
if (dailyMap.containsKey(date)) dailyMap.merge(date, s.getTotalAmount(), BigDecimal::add);
|
||||
String d = s.getSaleDate().length() >= 10 ? s.getSaleDate().substring(0, 10) : s.getSaleDate();
|
||||
if (dailyMap.containsKey(d)) dailyMap.merge(d, s.getTotalAmount(), BigDecimal::add);
|
||||
}
|
||||
}
|
||||
data.dailyRevenue = new ArrayList<>(dailyMap.entrySet());
|
||||
data.dailyRevenueTitle = buildDailyTitle(filter, rangeStart, rangeEnd);
|
||||
|
||||
analyticsData.setValue(data);
|
||||
}
|
||||
|
||||
private String todayString(int offsetDays) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.add(Calendar.DAY_OF_YEAR, offsetDays);
|
||||
return String.format(Locale.US, "%04d-%02d-%02d",
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
|
||||
}
|
||||
|
||||
private String shiftDate(String date, int offsetDays) {
|
||||
try {
|
||||
String[] p = date.split("-");
|
||||
Calendar c = Calendar.getInstance();
|
||||
c.set(Integer.parseInt(p[0]), Integer.parseInt(p[1]) - 1, Integer.parseInt(p[2]), 0, 0, 0);
|
||||
c.add(Calendar.DAY_OF_YEAR, offsetDays);
|
||||
return String.format(Locale.US, "%04d-%02d-%02d",
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
|
||||
} catch (Exception e) {
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> buildDateRange(String start, String end, int maxDays) {
|
||||
List<String> dates = new ArrayList<>();
|
||||
try {
|
||||
String[] sp = start.split("-");
|
||||
String[] ep = end.split("-");
|
||||
Calendar cur = Calendar.getInstance();
|
||||
cur.set(Integer.parseInt(sp[0]), Integer.parseInt(sp[1]) - 1, Integer.parseInt(sp[2]), 0, 0, 0);
|
||||
Calendar endCal = Calendar.getInstance();
|
||||
endCal.set(Integer.parseInt(ep[0]), Integer.parseInt(ep[1]) - 1, Integer.parseInt(ep[2]), 0, 0, 0);
|
||||
int count = 0;
|
||||
while (!cur.after(endCal) && count < maxDays) {
|
||||
dates.add(String.format(Locale.US, "%04d-%02d-%02d",
|
||||
cur.get(Calendar.YEAR), cur.get(Calendar.MONTH) + 1, cur.get(Calendar.DAY_OF_MONTH)));
|
||||
cur.add(Calendar.DAY_OF_YEAR, 1);
|
||||
count++;
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
return dates;
|
||||
}
|
||||
|
||||
private String buildDailyTitle(FilterState filter, String rangeStart, String rangeEnd) {
|
||||
if (filter.startDate.isEmpty() && filter.endDate.isEmpty()) return "Daily Revenue (Last 7 Days)";
|
||||
String s = rangeStart.length() >= 10 ? rangeStart.substring(5) : rangeStart;
|
||||
String e = rangeEnd.length() >= 10 ? rangeEnd.substring(5) : rangeEnd;
|
||||
return "Daily Revenue (" + s + " – " + e + ")";
|
||||
}
|
||||
|
||||
public static class FilterState {
|
||||
public String startDate = "";
|
||||
public String endDate = "";
|
||||
public String paymentMethod = "All";
|
||||
public int topN = 5;
|
||||
}
|
||||
|
||||
public static class AnalyticsData {
|
||||
public BigDecimal totalRevenue;
|
||||
public int totalTransactions;
|
||||
@@ -159,5 +264,6 @@ public class AnalyticsViewModel extends ViewModel {
|
||||
public List<Map.Entry<String, Integer>> paymentMethodStats;
|
||||
public List<Map.Entry<String, BigDecimal>> employeePerformance;
|
||||
public List<Map.Entry<String, BigDecimal>> dailyRevenue;
|
||||
public String dailyRevenueTitle = "Daily Revenue";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||
import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
@@ -14,12 +19,31 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
@HiltViewModel
|
||||
public class StaffDetailViewModel extends ViewModel {
|
||||
private final EmployeeRepository repository;
|
||||
private final StoreRepository storeRepository;
|
||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>();
|
||||
private long employeeId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
@Inject
|
||||
public StaffDetailViewModel(EmployeeRepository repository) {
|
||||
public StaffDetailViewModel(EmployeeRepository repository, StoreRepository storeRepository) {
|
||||
this.repository = repository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||
return storeRepository.getStoreDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<List<DropdownDTO>> getStoreList() {
|
||||
return storeList;
|
||||
}
|
||||
|
||||
public void setStoreList(List<DropdownDTO> list) {
|
||||
storeList.setValue(list);
|
||||
}
|
||||
|
||||
public LiveData<Resource<EmployeeDTO>> loadEmployee(long id) {
|
||||
return repository.getEmployeeById(id);
|
||||
}
|
||||
|
||||
public void setEmployeeId(long id, boolean isEditing) {
|
||||
|
||||
@@ -45,9 +45,267 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/rowFilterHeader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Filters"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFilterSummary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="All time"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvFilterToggleIcon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="▼"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llFilterContent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="12dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Quick Range"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:layout_marginBottom="6dp"/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollbars="none"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPresetToday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:text="Today"
|
||||
android:textSize="11sp"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPreset7D"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:text="7D"
|
||||
android:textSize="11sp"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPreset30D"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:text="30D"
|
||||
android:textSize="11sp"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPreset3M"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:text="3M"
|
||||
android:textSize="11sp"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPreset1Y"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:text="1Y"
|
||||
android:textSize="11sp"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPresetAll"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:text="All"
|
||||
android:textSize="11sp"
|
||||
android:backgroundTint="@color/text_light"
|
||||
android:textColor="@color/white"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Date Range"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:layout_marginBottom="6dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etFilterStartDate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Start date"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||
android:textSize="13sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=" – "
|
||||
android:textColor="@color/text_light"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etFilterEndDate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="End date"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||
android:textSize="13sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Payment Method"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerFilterPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Top N Products"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerTopN"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnFilterApply"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:text="Apply"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnFilterReset"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Reset"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -299,6 +557,7 @@
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDailyRevenueTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Daily Revenue (Last 7 Days)"
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Role"
|
||||
android:text="User Role"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
@@ -184,7 +184,34 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Staff Role"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStaffType"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Primary Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStaffStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
Reference in New Issue
Block a user