From 1990022c1e3b721a8d5384cf69254afc6be6ec29 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 7 Apr 2026 05:24:25 -0600 Subject: [PATCH] Added filter by store for inventory in back end and added search to inventory --- .../petstoremobile/api/PurchaseOrderApi.java | 5 +- .../listfragments/PurchaseOrderFragment.java | 134 ++++++++++++------ .../repositories/PurchaseOrderRepository.java | 4 +- .../viewmodels/PurchaseOrderViewModel.java | 4 +- .../res/layout/fragment_purchase_order.xml | 76 ++++++++-- .../controller/PurchaseOrderController.java | 3 +- .../repository/PurchaseOrderRepository.java | 5 +- .../backend/service/PurchaseOrderService.java | 18 ++- 8 files changed, 181 insertions(+), 68 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java b/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java index 1e4f4ffe..e5a5a06d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/PurchaseOrderApi.java @@ -12,7 +12,10 @@ public interface PurchaseOrderApi { @GET("api/v1/purchase-orders") Call> getAllPurchaseOrders( @Query("page") int page, - @Query("size") int size); + @Query("size") int size, + @Query("query") String query, + @Query("storeId") Long storeId, + @Query("sort") String sort); @GET("api/v1/purchase-orders/{id}") Call getPurchaseOrderById(@Path("id") Long id); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java index 78fcd981..452f8d69 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PurchaseOrderFragment.java @@ -1,10 +1,15 @@ package com.example.petstoremobile.fragments.listfragments; import android.os.Bundle; -import android.text.*; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Log; -import android.view.*; -import android.widget.*; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; @@ -16,10 +21,15 @@ import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.PurchaseOrderAdapter; import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding; import com.example.petstoremobile.dtos.PurchaseOrderDTO; +import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.fragments.ListFragment; +import com.example.petstoremobile.utils.Resource; +import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel; +import com.example.petstoremobile.viewmodels.StoreViewModel; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import dagger.hilt.android.AndroidEntryPoint; @@ -29,21 +39,23 @@ public class PurchaseOrderFragment extends Fragment private FragmentPurchaseOrderBinding binding; private List poList = new ArrayList<>(); - private List filteredList = new ArrayList<>(); + private List storeList = new ArrayList<>(); private PurchaseOrderAdapter adapter; private PurchaseOrderViewModel viewModel; + private StoreViewModel storeViewModel; /** - * Initializes the fragment and its associated PurchaseOrderViewModel. + * Initializes the fragment and its associated ViewModels. */ @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class); + storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class); } /** - * Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh. + * Sets up the fragment's UI components, including RecyclerView, filters, and swipe-to-refresh. */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, @@ -52,8 +64,9 @@ public class PurchaseOrderFragment extends Fragment setupRecyclerView(); setupSearch(); + setupStoreFilter(); setupSwipeRefresh(); - loadData(); + setupFilterToggle(); binding.btnHamburgerPO.setOnClickListener(v -> { Fragment parent = getParentFragment(); @@ -75,12 +88,32 @@ public class PurchaseOrderFragment extends Fragment } /** - * Initializes the RecyclerView with a layout manager and adapter for purchase orders. + * Reloads data every time the fragment becomes visible. */ - private void setupRecyclerView() { - adapter = new PurchaseOrderAdapter(filteredList, this); - binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext())); - binding.recyclerViewPO.setAdapter(adapter); + @Override + public void onResume() { + super.onResume(); + loadData(); + loadStoreData(); + } + + /** + * Sets up the filter toggle button to show/hide the filter layout. + */ + private void setupFilterToggle() { + binding.btnToggleFilter.setOnClickListener(v -> { + if (binding.layoutFilter.getVisibility() == View.GONE) { + binding.layoutFilter.setVisibility(View.VISIBLE); + binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel); + } else { + binding.layoutFilter.setVisibility(View.GONE); + binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search); + + // Reset filters when closing + binding.etSearchPO.setText(""); + binding.spinnerStore.setSelection(0); + } + }); } /** @@ -88,18 +121,49 @@ public class PurchaseOrderFragment extends Fragment */ private void setupSearch() { binding.etSearchPO.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int a, int b, int c) { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + loadData(); } + @Override public void afterTextChanged(Editable s) {} + }); + } - public void afterTextChanged(Editable s) { + /** + * Configures the store filter spinner. + */ + private void setupStoreFilter() { + binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + loadData(); } + @Override public void onNothingSelected(AdapterView parent) {} + }); + } - public void onTextChanged(CharSequence s, int start, int before, int count) { - filter(s.toString()); + /** + * Fetches store data to populate the store filter. + */ + private void loadStoreData() { + storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + storeList = resource.data.getContent(); + SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList, + StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId); } }); } + /** + * Initializes the RecyclerView with a layout manager and adapter. + */ + private void setupRecyclerView() { + adapter = new PurchaseOrderAdapter(poList, this); + binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext())); + binding.recyclerViewPO.setAdapter(adapter); + } + /** * Sets up the SwipeRefreshLayout to allow manual reloading of purchase order data. */ @@ -108,30 +172,18 @@ public class PurchaseOrderFragment extends Fragment } /** - * Filters the purchase order list based on the search query. - */ - private void filter(String query) { - filteredList.clear(); - if (query.isEmpty()) { - filteredList.addAll(poList); - } else { - String lower = query.toLowerCase(); - for (PurchaseOrderDTO po : poList) { - if ((po.getSupplierName() != null && po.getSupplierName().toLowerCase().contains(lower)) - || (po.getStatus() != null && po.getStatus().toLowerCase().contains(lower))) { - filteredList.add(po); - } - } - } - adapter.notifyDataSetChanged(); - } - - /** - * Fetches all purchase order data from the server through the ViewModel and updates the UI. + * Fetches purchase order data from the server with active filters and updates the UI. */ private void loadData() { - //Load all purchase orders from the backend using viewModel - viewModel.getAllPurchaseOrders(0, 100).observe(getViewLifecycleOwner(), resource -> { + String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : ""; + if (query.isEmpty()) query = null; + + Long storeId = null; + if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) { + storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId(); + } + + viewModel.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc").observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; // Check the status to see if the resource is loaded and display the data @@ -146,7 +198,7 @@ public class PurchaseOrderFragment extends Fragment if (resource.data != null) { poList.clear(); poList.addAll(resource.data.getContent()); - filter(binding.etSearchPO != null ? binding.etSearchPO.getText().toString() : ""); + adapter.notifyDataSetChanged(); } break; case ERROR: @@ -164,7 +216,7 @@ public class PurchaseOrderFragment extends Fragment */ private void openDetail(int position) { Bundle args = new Bundle(); - PurchaseOrderDTO po = filteredList.get(position); + PurchaseOrderDTO po = poList.get(position); args.putLong("purchaseOrderId", po.getPurchaseOrderId()); NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args); } diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java index bd1b224e..b55f9a33 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/PurchaseOrderRepository.java @@ -23,8 +23,8 @@ public class PurchaseOrderRepository extends BaseRepository { /** * Retrieves a paginated list of all purchase orders from the API. */ - public LiveData>> getAllPurchaseOrders(int page, int size) { - return executeCall(api.getAllPurchaseOrders(page, size)); + public LiveData>> getAllPurchaseOrders(int page, int size, String query, Long storeId, String sort) { + return executeCall(api.getAllPurchaseOrders(page, size, query, storeId, sort)); } /** diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderViewModel.java index 0b5d7ea1..d9a24e5e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderViewModel.java @@ -24,8 +24,8 @@ public class PurchaseOrderViewModel extends ViewModel { /** * Fetches a paginated list of all purchase orders. */ - public LiveData>> getAllPurchaseOrders(int page, int size) { - return repository.getAllPurchaseOrders(page, size); + public LiveData>> getAllPurchaseOrders(int page, int size, String query, Long storeId, String sort) { + return repository.getAllPurchaseOrders(page, size, query, storeId, sort); } /** diff --git a/android/app/src/main/res/layout/fragment_purchase_order.xml b/android/app/src/main/res/layout/fragment_purchase_order.xml index ea11397d..3ed22d3d 100644 --- a/android/app/src/main/res/layout/fragment_purchase_order.xml +++ b/android/app/src/main/res/layout/fragment_purchase_order.xml @@ -1,6 +1,7 @@ @@ -27,27 +28,78 @@ android:contentDescription="Open menu"/> + android:textStyle="bold" + android:layout_marginStart="8dp"/> + + - + android:orientation="vertical" + android:paddingStart="12dp" + android:paddingEnd="12dp" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:visibility="gone" + android:background="@color/primary_dark" + android:elevation="4dp"> + + + + + + + + + + + + > getAllPurchaseOrders( @RequestParam(required = false) String q, + @RequestParam(required = false) Long storeId, Pageable pageable) { - return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(q, pageable)); + return ResponseEntity.ok(purchaseOrderService.getAllPurchaseOrders(q, storeId, pageable)); } @GetMapping("/{id}") diff --git a/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java b/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java index d3b445c4..5ebf9457 100644 --- a/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/PurchaseOrderRepository.java @@ -12,6 +12,7 @@ import org.springframework.stereotype.Repository; public interface PurchaseOrderRepository extends JpaRepository { @Query("SELECT po FROM PurchaseOrder po WHERE " + - "LOWER(po.supplier.supCompany) LIKE LOWER(CONCAT('%', :q, '%'))") - Page searchPurchaseOrders(@Param("q") String query, Pageable pageable); + "(:q IS NULL OR LOWER(po.supplier.supCompany) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + + "(:storeId IS NULL OR po.store.storeId = :storeId)") + Page searchPurchaseOrders(@Param("q") String query, @Param("storeId") Long storeId, Pageable pageable); } diff --git a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java index e9f7e4a5..7c42cee9 100644 --- a/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java +++ b/backend/src/main/java/com/petshop/backend/service/PurchaseOrderService.java @@ -18,13 +18,9 @@ public class PurchaseOrderService { this.purchaseOrderRepository = purchaseOrderRepository; } - public Page getAllPurchaseOrders(String query, Pageable pageable) { - Page purchaseOrders; - if (query != null && !query.trim().isEmpty()) { - purchaseOrders = purchaseOrderRepository.searchPurchaseOrders(query, pageable); - } else { - purchaseOrders = purchaseOrderRepository.findAll(pageable); - } + public Page getAllPurchaseOrders(String query, Long storeId, Pageable pageable) { + String normalizedQuery = normalizeFilter(query); + Page purchaseOrders = purchaseOrderRepository.searchPurchaseOrders(normalizedQuery, storeId, pageable); return purchaseOrders.map(this::mapToResponse); } @@ -34,6 +30,14 @@ public class PurchaseOrderService { return mapToResponse(purchaseOrder); } + private String normalizeFilter(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } + private PurchaseOrderResponse mapToResponse(PurchaseOrder purchaseOrder) { StoreLocation store = purchaseOrder.getStore(); return new PurchaseOrderResponse(