From 42a4bcd104d7e30524d5c59281af43b90b822cb8 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 14 Apr 2026 23:17:12 -0600 Subject: [PATCH] made admin analyics able to select store --- .../listfragments/AnalyticsFragment.java | 24 +++++++++- .../viewmodels/AnalyticsViewModel.java | 33 ++++++++++++++ .../main/res/layout/fragment_analytics.xml | 17 +++++++ .../controllers/AnalyticsController.java | 44 +++++++++++++++++++ .../modelviews/analytics-view.fxml | 1 + 5 files changed, 118 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java index 1b45569f..89aeb098 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AnalyticsFragment.java @@ -57,11 +57,13 @@ public class AnalyticsFragment extends Fragment { binding.btnMyAnalytics.setOnClickListener(v -> { viewModel.setViewMode("mine"); updateViewModeButtonStyles("mine"); + updateStoreFilterVisibility("mine"); }); binding.btnStoreAnalytics.setOnClickListener(v -> { viewModel.setViewMode("store"); updateViewModeButtonStyles("store"); + updateStoreFilterVisibility("store"); }); } @@ -72,6 +74,13 @@ public class AnalyticsFragment extends Fragment { android.content.res.ColorStateList.valueOf(mode.equals("store") ? COLOR_SELECTED : COLOR_UNSELECTED)); } + private void updateStoreFilterVisibility(String mode) { + boolean isAdmin = "ADMIN".equalsIgnoreCase(tokenManager.getRole()); + int vis = (isAdmin && mode.equals("store")) ? View.VISIBLE : View.GONE; + binding.tvStoreFilterLabel.setVisibility(vis); + binding.spinnerFilterStore.setVisibility(vis); + } + // Filter Panel private void setupFilterPanel() { @@ -126,6 +135,9 @@ public class AnalyticsFragment extends Fragment { int topNPos = binding.spinnerTopN.getSelectedItemPosition(); filter.topN = (topNPos >= 0 && topNPos < TOP_N_VALUES.length) ? TOP_N_VALUES[topNPos] : 5; + Object store = binding.spinnerFilterStore.getSelectedItem(); + viewModel.setStoreFilter(store != null ? store.toString() : "All Stores"); + updateFilterSummary(); viewModel.applyFilter(filter); } @@ -134,8 +146,8 @@ public class AnalyticsFragment extends Fragment { binding.etFilterStartDate.setText(""); binding.etFilterEndDate.setText(""); binding.spinnerTopN.setSelection(0); - // Reset payment method to "All" SpinnerUtils.setSelectionByValue(binding.spinnerFilterPayment, "All"); + SpinnerUtils.setSelectionByValue(binding.spinnerFilterStore, "All Stores"); updateFilterSummary(); viewModel.resetFilter(); } @@ -192,6 +204,16 @@ public class AnalyticsFragment extends Fragment { methods.toArray(new String[0])); SpinnerUtils.setSelectionByValue(binding.spinnerFilterPayment, currentSelection); }); + + viewModel.getAvailableStores().observe(getViewLifecycleOwner(), stores -> { + if (stores == null || stores.isEmpty()) return; + String currentSelection = binding.spinnerFilterStore.getSelectedItem() != null + ? binding.spinnerFilterStore.getSelectedItem().toString() : "All Stores"; + SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerFilterStore, + stores.toArray(new String[0])); + SpinnerUtils.setSelectionByValue(binding.spinnerFilterStore, currentSelection); + updateStoreFilterVisibility(viewModel.getViewMode()); + }); } @Override diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java index 7b69bc47..4a9b94d9 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AnalyticsViewModel.java @@ -37,10 +37,12 @@ public class AnalyticsViewModel extends ViewModel { private final MutableLiveData isLoading = new MutableLiveData<>(false); private final MutableLiveData errorMessage = new MutableLiveData<>(); private final MutableLiveData> availablePaymentMethods = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> availableStores = new MutableLiveData<>(new ArrayList<>()); private List cachedSales = new ArrayList<>(); private FilterState currentFilter = new FilterState(); private String viewMode = "store"; + private String storeFilter = "All Stores"; @Inject public AnalyticsViewModel(SaleRepository saleRepository, TokenManager tokenManager) { @@ -52,6 +54,7 @@ public class AnalyticsViewModel extends ViewModel { public LiveData getIsLoading() { return isLoading; } public LiveData getErrorMessage() { return errorMessage; } public LiveData> getAvailablePaymentMethods() { return availablePaymentMethods; } + public LiveData> getAvailableStores() { return availableStores; } public void loadAnalytics() { isLoading.setValue(true); @@ -61,6 +64,7 @@ public class AnalyticsViewModel extends ViewModel { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { cachedSales = resource.data.getContent(); derivePaymentMethods(); + deriveStores(); applyCurrentFilter(); isLoading.setValue(false); } else if (resource.status == Resource.Status.ERROR) { @@ -78,6 +82,7 @@ public class AnalyticsViewModel extends ViewModel { public void resetFilter() { currentFilter = new FilterState(); + storeFilter = "All Stores"; applyCurrentFilter(); } @@ -90,6 +95,15 @@ public class AnalyticsViewModel extends ViewModel { return viewMode; } + public void setStoreFilter(String store) { + storeFilter = (store != null && !store.isEmpty()) ? store : "All Stores"; + applyCurrentFilter(); + } + + public String getStoreFilter() { + return storeFilter; + } + private void applyCurrentFilter() { List salesForMode; if (viewMode.equals("mine")) { @@ -100,10 +114,29 @@ public class AnalyticsViewModel extends ViewModel { } else { salesForMode = cachedSales; } + if (!storeFilter.equals("All Stores") && !storeFilter.isEmpty()) { + final String sf = storeFilter; + salesForMode = salesForMode.stream() + .filter(s -> sf.equalsIgnoreCase(s.getStoreName() != null ? s.getStoreName() : "")) + .collect(Collectors.toList()); + } List filtered = filterSales(salesForMode, currentFilter); computeAnalytics(filtered, currentFilter); } + private void deriveStores() { + java.util.Set stores = new java.util.TreeSet<>(); + for (SaleDTO s : cachedSales) { + if (s.getStoreName() != null && !s.getStoreName().isEmpty()) { + stores.add(s.getStoreName()); + } + } + List result = new ArrayList<>(); + result.add("All Stores"); + result.addAll(stores); + availableStores.setValue(result); + } + private void derivePaymentMethods() { java.util.Set methods = new java.util.TreeSet<>(); for (SaleDTO s : cachedSales) { diff --git a/android/app/src/main/res/layout/fragment_analytics.xml b/android/app/src/main/res/layout/fragment_analytics.xml index 2e375f58..9e59b7d7 100644 --- a/android/app/src/main/res/layout/fragment_analytics.xml +++ b/android/app/src/main/res/layout/fragment_analytics.xml @@ -136,6 +136,23 @@ android:layout_marginTop="12dp" android:visibility="gone"> + + + + cbTopN; + @FXML + private ComboBox cbStoreFilter; + @FXML private HBox hbViewToggle; @@ -138,6 +141,9 @@ public class AnalyticsController { cbPaymentFilter.setItems(FXCollections.observableArrayList("All")); cbPaymentFilter.getSelectionModel().selectFirst(); + cbStoreFilter.setItems(FXCollections.observableArrayList("All Stores")); + cbStoreFilter.getSelectionModel().selectFirst(); + lblFilterSummary.setText("All time"); ToggleGroup tgViewMode = new ToggleGroup(); @@ -151,6 +157,7 @@ public class AnalyticsController { } viewMode = (newVal == tbnMyAnalytics) ? "mine" : "store"; updateViewModeStyles(); + updateStoreFilterVisibility(); applyCurrentFilter(); }); @@ -213,6 +220,8 @@ public class AnalyticsController { Platform.runLater(() -> { cachedSales = sales; derivePaymentMethods(); + deriveStores(); + updateStoreFilterVisibility(); applyCurrentFilter(); btnRefresh.setDisable(false); }); @@ -250,6 +259,12 @@ public class AnalyticsController { } else { salesForMode = cachedSales; } + String storeFilter = currentFilter.storeFilter; + if (!storeFilter.equals("All Stores") && !storeFilter.isBlank()) { + salesForMode = salesForMode.stream() + .filter(s -> storeFilter.equalsIgnoreCase(s.getStoreName() != null ? s.getStoreName() : "")) + .collect(Collectors.toList()); + } List filtered = filterSales(salesForMode, currentFilter); String start = currentFilter.startDate.isEmpty() ? LocalDate.now().minusDays(6).toString() : currentFilter.startDate; String end = currentFilter.endDate.isEmpty() ? LocalDate.now().toString() : currentFilter.endDate; @@ -308,6 +323,31 @@ public class AnalyticsController { } } + private void deriveStores() { + Set stores = new TreeSet<>(); + for (SaleResponse s : cachedSales) { + if (s.getStoreName() != null && !s.getStoreName().isBlank()) { + stores.add(s.getStoreName()); + } + } + List items = new ArrayList<>(); + items.add("All Stores"); + items.addAll(stores); + String current = cbStoreFilter.getValue(); + cbStoreFilter.setItems(FXCollections.observableArrayList(items)); + if (current != null && items.contains(current)) { + cbStoreFilter.setValue(current); + } else { + cbStoreFilter.getSelectionModel().selectFirst(); + } + } + + private void updateStoreFilterVisibility() { + boolean show = UserSession.getInstance().isAdmin() && viewMode.equals("store"); + cbStoreFilter.setVisible(show); + cbStoreFilter.setManaged(show); + } + private void updateFilterSummary() { String start = currentFilter.startDate; String end = currentFilter.endDate; @@ -665,6 +705,7 @@ public class AnalyticsController { dpEndDate.setValue(null); cbPaymentFilter.getSelectionModel().selectFirst(); cbTopN.getSelectionModel().selectFirst(); + cbStoreFilter.getSelectionModel().selectFirst(); currentFilter = new FilterState(); applyCurrentFilter(); } @@ -683,6 +724,8 @@ public class AnalyticsController { currentFilter.paymentMethod = pm != null ? pm : "All"; int topNPos = cbTopN.getSelectionModel().getSelectedIndex(); currentFilter.topN = (topNPos >= 0 && topNPos < TOP_N_VALUES.length) ? TOP_N_VALUES[topNPos] : 5; + String sf = cbStoreFilter.getValue(); + currentFilter.storeFilter = (sf != null && !sf.isBlank()) ? sf : "All Stores"; applyCurrentFilter(); } @@ -691,5 +734,6 @@ public class AnalyticsController { String endDate = ""; String paymentMethod = "All"; int topN = 5; + String storeFilter = "All Stores"; } } diff --git a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/analytics-view.fxml b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/analytics-view.fxml index 6fe2e3bf..4f86758a 100644 --- a/desktop/src/main/resources/org/example/petshopdesktop/modelviews/analytics-view.fxml +++ b/desktop/src/main/resources/org/example/petshopdesktop/modelviews/analytics-view.fxml @@ -102,6 +102,7 @@ +