updated inventory backend to have filter by store and added more search features to andriod
This commit is contained in:
@@ -16,12 +16,14 @@ import retrofit2.http.Query;
|
|||||||
|
|
||||||
public interface InventoryApi {
|
public interface InventoryApi {
|
||||||
|
|
||||||
// GET /api/v1/inventory?q=...&page=...&size=...
|
// GET /api/v1/inventory?q=...&page=...&size=...&category=...&storeId=...&sort=...
|
||||||
@GET("api/v1/inventory")
|
@GET("api/v1/inventory")
|
||||||
Call<PageResponse<InventoryDTO>> getAllInventory(
|
Call<PageResponse<InventoryDTO>> getAllInventory(
|
||||||
@Query("q") String query,
|
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@Query("size") int size,
|
@Query("size") int size,
|
||||||
|
@Query("q") String query,
|
||||||
|
@Query("category") String category,
|
||||||
|
@Query("storeId") Long storeId,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
// GET /api/v1/inventory/{id}
|
// GET /api/v1/inventory/{id}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ public interface ProductApi {
|
|||||||
@GET("api/v1/products")
|
@GET("api/v1/products")
|
||||||
Call<PageResponse<ProductDTO>> getAllProducts(
|
Call<PageResponse<ProductDTO>> getAllProducts(
|
||||||
@Query("q") String query,
|
@Query("q") String query,
|
||||||
|
@Query("categoryId") Long categoryId,
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@Query("size") int size);
|
@Query("size") int size,
|
||||||
|
@Query("sort") String sort);
|
||||||
|
|
||||||
@GET("api/v1/products/{id}")
|
@GET("api/v1/products/{id}")
|
||||||
Call<ProductDTO> getProductById(@Path("id") Long id);
|
Call<ProductDTO> getProductById(@Path("id") Long id);
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -21,14 +19,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
|
||||||
import com.example.petstoremobile.adapters.InventoryAdapter;
|
import com.example.petstoremobile.adapters.InventoryAdapter;
|
||||||
import com.example.petstoremobile.databinding.FragmentInventoryBinding;
|
import com.example.petstoremobile.databinding.FragmentInventoryBinding;
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
|
||||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.fragments.ListFragment;
|
||||||
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -43,26 +41,15 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
|
|
||||||
private FragmentInventoryBinding binding;
|
private FragmentInventoryBinding binding;
|
||||||
private final List<InventoryDTO> inventoryList = new ArrayList<>();
|
private final List<InventoryDTO> inventoryList = new ArrayList<>();
|
||||||
private final List<CategoryDTO> categoryList = new ArrayList<>();
|
private List<StoreDTO> storeList = new ArrayList<>();
|
||||||
private InventoryAdapter adapter;
|
private InventoryAdapter adapter;
|
||||||
private InventoryViewModel viewModel;
|
private InventoryViewModel viewModel;
|
||||||
|
|
||||||
// Debounce search
|
|
||||||
private final Handler searchHandler = new Handler(Looper.getMainLooper());
|
|
||||||
private Runnable searchRunnable;
|
|
||||||
private String currentQuery = "";
|
|
||||||
|
|
||||||
// Selected category filter — null means "All"
|
|
||||||
private String selectedCategory = null;
|
|
||||||
|
|
||||||
// Pagination
|
// Pagination
|
||||||
private int currentPage = 0;
|
private int currentPage = 0;
|
||||||
private boolean isLastPage = false;
|
private boolean isLastPage = false;
|
||||||
private boolean isLoading = false;
|
private boolean isLoading = false;
|
||||||
|
|
||||||
// Prevent spinner from firing on initial load
|
|
||||||
private boolean spinnerReady = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the fragment and its ViewModel.
|
* Initializes the fragment and its ViewModel.
|
||||||
*/
|
*/
|
||||||
@@ -73,7 +60,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the fragment's UI components, including the inventory list, search, and category filter.
|
* Sets up the fragment's UI components, including the inventory list and search.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -82,9 +69,11 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
|
|
||||||
setupRecyclerView();
|
setupRecyclerView();
|
||||||
setupSearch();
|
setupSearch();
|
||||||
|
setupStoreFilter();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
loadCategories(); // loads categories then triggers loadInventory
|
setupFilterToggle();
|
||||||
loadInventory(true);
|
loadInventory(true);
|
||||||
|
loadStoreData();
|
||||||
|
|
||||||
binding.fabAddInventory.setOnClickListener(v -> openDetail(null));
|
binding.fabAddInventory.setOnClickListener(v -> openDetail(null));
|
||||||
|
|
||||||
@@ -110,83 +99,59 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all product categories to populate the filter spinner.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void loadCategories() {
|
private void setupFilterToggle() {
|
||||||
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
binding.btnToggleFilter.setOnClickListener(v -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
||||||
categoryList.clear();
|
binding.layoutFilter.setVisibility(View.VISIBLE);
|
||||||
categoryList.addAll(resource.data.getContent());
|
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
||||||
setupCategorySpinner();
|
} else {
|
||||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
binding.layoutFilter.setVisibility(View.GONE);
|
||||||
Log.e(TAG, "Failed to load categories: " + resource.message);
|
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
||||||
setupCategorySpinner();
|
|
||||||
|
// Reset filters when closing
|
||||||
|
binding.etSearchInventory.setText("");
|
||||||
|
binding.spinnerStore.setSelection(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup the category filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupCategorySpinner() {
|
|
||||||
// First item is always "All Categories"
|
|
||||||
List<String> categoryNames = new ArrayList<>();
|
|
||||||
categoryNames.add("All Categories");
|
|
||||||
for (CategoryDTO c : categoryList) {
|
|
||||||
categoryNames.add(c.getCategoryName());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getContext() != null) {
|
|
||||||
BlackTextArrayAdapter<String> spinnerAdapter = new BlackTextArrayAdapter<>(
|
|
||||||
requireContext(),
|
|
||||||
android.R.layout.simple_spinner_item,
|
|
||||||
categoryNames);
|
|
||||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
binding.spinnerCategory.setAdapter(spinnerAdapter);
|
|
||||||
|
|
||||||
binding.spinnerCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
if (!spinnerReady) {
|
|
||||||
// Skip the first automatic trigger on setup
|
|
||||||
spinnerReady = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (position == 0) {
|
|
||||||
selectedCategory = null; // "All Categories"
|
|
||||||
} else {
|
|
||||||
selectedCategory = categoryList.get(position - 1).getCategoryName();
|
|
||||||
}
|
|
||||||
loadInventory(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNothingSelected(AdapterView<?> parent) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the search bar for filtering.
|
* Sets up the search bar for filtering.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchInventory.addTextChangedListener(new TextWatcher() {
|
binding.etSearchInventory.addTextChangedListener(new TextWatcher() {
|
||||||
@Override public void beforeTextChanged(CharSequence s, int i, int i1, int i2) {
|
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
}
|
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
loadInventory(true);
|
||||||
@Override public void afterTextChanged(Editable s) {
|
|
||||||
}
|
}
|
||||||
|
@Override public void afterTextChanged(Editable s) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the store filter spinner.
|
||||||
|
*/
|
||||||
|
private void setupStoreFilter() {
|
||||||
|
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
if (searchRunnable != null)
|
loadInventory(true);
|
||||||
searchHandler.removeCallbacks(searchRunnable);
|
}
|
||||||
searchRunnable = () -> {
|
@Override public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
currentQuery = s.toString().trim();
|
});
|
||||||
loadInventory(true);
|
}
|
||||||
};
|
|
||||||
searchHandler.postDelayed(searchRunnable, 400);
|
/**
|
||||||
|
* Fetches store data to populate the store filter.
|
||||||
|
*/
|
||||||
|
private void loadStoreData() {
|
||||||
|
viewModel.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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -228,19 +193,24 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
* Fetches a page of inventory items from the API.
|
* Fetches a page of inventory items from the API.
|
||||||
*/
|
*/
|
||||||
private void loadInventory(boolean reset) {
|
private void loadInventory(boolean reset) {
|
||||||
if (isLoading)
|
if (isLoading) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (reset) {
|
if (reset) {
|
||||||
currentPage = 0;
|
currentPage = 0;
|
||||||
isLastPage = false;
|
isLastPage = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build query: combine search text + selected category
|
// Search text from input
|
||||||
String q = buildQuery();
|
String query = binding.etSearchInventory != null ? binding.etSearchInventory.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();
|
||||||
|
}
|
||||||
|
|
||||||
//Load all inventory items from the backend using viewModel
|
//Load all inventory items from the backend using viewModel
|
||||||
viewModel.getAllInventory(q, currentPage, PAGE_SIZE, "inventoryId,asc").observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllInventory(query, null, storeId, currentPage, PAGE_SIZE, "product.prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
|
||||||
// Check the status to see if the resource is loaded and display the data
|
// Check the status to see if the resource is loaded and display the data
|
||||||
@@ -273,22 +243,6 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a query string based on the current search text and selected category.
|
|
||||||
*/
|
|
||||||
private String buildQuery() {
|
|
||||||
String q = null;
|
|
||||||
if (!currentQuery.isEmpty() && selectedCategory != null) {
|
|
||||||
// Both active — prioritize search text, category acts as context
|
|
||||||
q = currentQuery;
|
|
||||||
} else if (!currentQuery.isEmpty()) {
|
|
||||||
q = currentQuery;
|
|
||||||
} else if (selectedCategory != null) {
|
|
||||||
q = selectedCategory;
|
|
||||||
}
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays a confirmation dialog before performing a bulk deletion of selected items.
|
* Displays a confirmation dialog before performing a bulk deletion of selected items.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.*;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.*;
|
|
||||||
import android.widget.*;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
@@ -12,15 +9,27 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.ProductAdapter;
|
import com.example.petstoremobile.adapters.ProductAdapter;
|
||||||
import com.example.petstoremobile.api.auth.TokenManager;
|
|
||||||
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
||||||
|
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.fragments.ListFragment;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
@@ -32,12 +41,11 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
|
|
||||||
private FragmentProductBinding binding;
|
private FragmentProductBinding binding;
|
||||||
private List<ProductDTO> productList = new ArrayList<>();
|
private List<ProductDTO> productList = new ArrayList<>();
|
||||||
private List<ProductDTO> filteredList = new ArrayList<>();
|
private List<CategoryDTO> categoryList = new ArrayList<>();
|
||||||
private ProductAdapter adapter;
|
private ProductAdapter adapter;
|
||||||
private ProductViewModel viewModel;
|
private ProductViewModel viewModel;
|
||||||
|
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the fragment and its associated ProductViewModel.
|
* Initializes the fragment and its associated ProductViewModel.
|
||||||
@@ -49,7 +57,7 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the fragment's UI components, including the product list, search, and swipe-to-refresh.
|
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -58,11 +66,11 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
|
|
||||||
setupRecyclerView();
|
setupRecyclerView();
|
||||||
setupSearch();
|
setupSearch();
|
||||||
|
setupCategoryFilter();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
|
setupFilterToggle();
|
||||||
|
|
||||||
loadProducts();
|
binding.fabAddProduct.setOnClickListener(v -> openProductDetails(-1));
|
||||||
|
|
||||||
binding.fabAddProduct.setOnClickListener(v -> openDetail(-1));
|
|
||||||
|
|
||||||
binding.btnHamburgerProduct.setOnClickListener(v -> {
|
binding.btnHamburgerProduct.setOnClickListener(v -> {
|
||||||
Fragment parent = getParentFragment();
|
Fragment parent = getParentFragment();
|
||||||
@@ -84,61 +92,110 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the RecyclerView with a layout manager and adapter for displaying products.
|
* Reloads data every time the fragment becomes visible.
|
||||||
*/
|
*/
|
||||||
private void setupRecyclerView() {
|
@Override
|
||||||
adapter = new ProductAdapter(filteredList, this);
|
public void onResume() {
|
||||||
adapter.setBaseUrl(baseUrl);
|
super.onResume();
|
||||||
adapter.setToken(tokenManager.getToken());
|
loadProductData();
|
||||||
binding.recyclerViewProducts.setLayoutManager(new LinearLayoutManager(getContext()));
|
loadCategoryData();
|
||||||
binding.recyclerViewProducts.setAdapter(adapter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the search bar for filtering.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupFilterToggle() {
|
||||||
binding.etSearchProduct.addTextChangedListener(new TextWatcher() {
|
binding.btnToggleFilter.setOnClickListener(v -> {
|
||||||
public void beforeTextChanged(CharSequence s, int a, int b, int c) {}
|
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
||||||
public void afterTextChanged(Editable s) {}
|
binding.layoutFilter.setVisibility(View.VISIBLE);
|
||||||
public void onTextChanged(CharSequence s, int a, int b, int c) {
|
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
||||||
filter();
|
} else {
|
||||||
|
binding.layoutFilter.setVisibility(View.GONE);
|
||||||
|
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
||||||
|
|
||||||
|
// Reset filters when closing
|
||||||
|
binding.etSearchProduct.setText("");
|
||||||
|
binding.spinnerCategory.setSelection(0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the SwipeRefreshLayout to allow manual re-fetching of product data.
|
* Configures the search bar for triggering data load from backend.
|
||||||
|
*/
|
||||||
|
private void setupSearch() {
|
||||||
|
binding.etSearchProduct.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
|
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
loadProductData();
|
||||||
|
}
|
||||||
|
@Override public void afterTextChanged(Editable s) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the category filter spinner.
|
||||||
|
*/
|
||||||
|
private void setupCategoryFilter() {
|
||||||
|
binding.spinnerCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
loadProductData();
|
||||||
|
}
|
||||||
|
@Override public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches category data to populate the category filter.
|
||||||
|
*/
|
||||||
|
private void loadCategoryData() {
|
||||||
|
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
categoryList = resource.data.getContent();
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCategory, categoryList,
|
||||||
|
CategoryDTO::getCategoryName, "All Categories", -1L, CategoryDTO::getCategoryId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout.
|
||||||
*/
|
*/
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshProduct.setOnRefreshListener(this::loadProducts);
|
binding.swipeRefreshProduct.setOnRefreshListener(this::loadProductData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the product list based on the search query across name, category, and description.
|
* Navigates to the product detail screen.
|
||||||
*/
|
*/
|
||||||
private void filter() {
|
private void openProductDetails(int position) {
|
||||||
String query = binding.etSearchProduct.getText().toString().toLowerCase();
|
Bundle args = new Bundle();
|
||||||
|
if (position != -1) {
|
||||||
filteredList.clear();
|
ProductDTO product = productList.get(position);
|
||||||
for (ProductDTO p : productList) {
|
args.putLong("productId", product.getProdId());
|
||||||
boolean matchesSearch = query.isEmpty() ||
|
|
||||||
(p.getProdName() != null && p.getProdName().toLowerCase().contains(query)) ||
|
|
||||||
(p.getCategoryName() != null && p.getCategoryName().toLowerCase().contains(query)) ||
|
|
||||||
(p.getProdDesc() != null && p.getProdDesc().toLowerCase().contains(query));
|
|
||||||
|
|
||||||
if (matchesSearch) {
|
|
||||||
filteredList.add(p);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
adapter.notifyDataSetChanged();
|
NavHostFragment.findNavController(this).navigate(R.id.nav_product_detail, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProductClick(int position) {
|
||||||
|
openProductDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches all product data from the server through the ViewModel and updates the UI.
|
* Fetches product data from the server with search query, category, and sorting.
|
||||||
*/
|
*/
|
||||||
private void loadProducts() {
|
private void loadProductData() {
|
||||||
viewModel.getAllProducts(null, 0, 100).observe(getViewLifecycleOwner(), resource -> {
|
String query = binding.etSearchProduct.getText().toString().trim();
|
||||||
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
|
Long categoryId = null;
|
||||||
|
if (binding.spinnerCategory.getSelectedItemPosition() > 0 && !categoryList.isEmpty()) {
|
||||||
|
categoryId = categoryList.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getCategoryId();
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.getAllProducts(query, categoryId, 0, 100, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
|
||||||
switch (resource.status) {
|
switch (resource.status) {
|
||||||
@@ -150,12 +207,14 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
if (resource.data != null) {
|
if (resource.data != null) {
|
||||||
productList.clear();
|
productList.clear();
|
||||||
productList.addAll(resource.data.getContent());
|
productList.addAll(resource.data.getContent());
|
||||||
filter();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ERROR:
|
case ERROR:
|
||||||
binding.swipeRefreshProduct.setRefreshing(false);
|
binding.swipeRefreshProduct.setRefreshing(false);
|
||||||
Toast.makeText(getContext(), "Failed to load products: " + resource.message, Toast.LENGTH_SHORT).show();
|
if (getContext() != null) {
|
||||||
|
Toast.makeText(getContext(), "Failed to load products: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
Log.e("ProductFragment", "Error loading products: " + resource.message);
|
Log.e("ProductFragment", "Error loading products: " + resource.message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -163,20 +222,12 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigates to the product detail screen for a specific product or to add a new one.
|
* Initializes the RecyclerView.
|
||||||
*/
|
*/
|
||||||
private void openDetail(int position) {
|
private void setupRecyclerView() {
|
||||||
Bundle args = new Bundle();
|
adapter = new ProductAdapter(productList, this);
|
||||||
if (position != -1) {
|
adapter.setBaseUrl(baseUrl);
|
||||||
ProductDTO p = filteredList.get(position);
|
binding.recyclerViewProducts.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
args.putLong("prodId", p.getProdId());
|
binding.recyclerViewProducts.setAdapter(adapter);
|
||||||
}
|
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_product_detail, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles item click in the product list.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onProductClick(int position) { openDetail(position); }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
*/
|
*/
|
||||||
private void searchProducts(String query) {
|
private void searchProducts(String query) {
|
||||||
if (getView() == null) return;
|
if (getView() == null) return;
|
||||||
productViewModel.getAllProducts(query, 0, 20).observe(getViewLifecycleOwner(), resource -> {
|
productViewModel.getAllProducts(query, null, 0, 20, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
productSuggestions.clear();
|
productSuggestions.clear();
|
||||||
productSuggestions.addAll(resource.data.getContent());
|
productSuggestions.addAll(resource.data.getContent());
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
* Loads the list of products from the API.
|
* Loads the list of products from the API.
|
||||||
*/
|
*/
|
||||||
private void loadProducts() {
|
private void loadProducts() {
|
||||||
productViewModel.getAllProducts(null, 0, 200).observe(getViewLifecycleOwner(), resource -> {
|
productViewModel.getAllProducts(null, null, 0, 200, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
productList = resource.data.getContent();
|
productList = resource.data.getContent();
|
||||||
refreshProductSpinner();
|
refreshProductSpinner();
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ public class InventoryRepository extends BaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a paginated list of inventory items from the API with optional search and sort.
|
* Retrieves a paginated list of inventory items from the API with optional search, category, storeId and sort.
|
||||||
*/
|
*/
|
||||||
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, int page, int size, String sort) {
|
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, String category, Long storeId, int page, int size, String sort) {
|
||||||
return executeCall(inventoryApi.getAllInventory(query, page, size, sort));
|
return executeCall(inventoryApi.getAllInventory(page, size, query, category, storeId, sort));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ public class ProductRepository extends BaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a paginated list of products from the API, filtered by an optional query.
|
* Retrieves a paginated list of products from the API, filtered by an optional query, category and sorted.
|
||||||
*/
|
*/
|
||||||
public LiveData<Resource<PageResponse<ProductDTO>>> getAllProducts(String query, int page, int size) {
|
public LiveData<Resource<PageResponse<ProductDTO>>> getAllProducts(String query, Long categoryId, int page, int size, String sort) {
|
||||||
return executeCall(productApi.getAllProducts(query, page, size));
|
return executeCall(productApi.getAllProducts(query, categoryId, page, size, sort));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ import com.example.petstoremobile.dtos.CategoryDTO;
|
|||||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
import com.example.petstoremobile.dtos.InventoryRequest;
|
import com.example.petstoremobile.dtos.InventoryRequest;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||||
import com.example.petstoremobile.repositories.InventoryRepository;
|
import com.example.petstoremobile.repositories.InventoryRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -22,18 +24,20 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
|||||||
public class InventoryViewModel extends ViewModel {
|
public class InventoryViewModel extends ViewModel {
|
||||||
private final InventoryRepository inventoryRepository;
|
private final InventoryRepository inventoryRepository;
|
||||||
private final CategoryRepository categoryRepository;
|
private final CategoryRepository categoryRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public InventoryViewModel(InventoryRepository inventoryRepository, CategoryRepository categoryRepository) {
|
public InventoryViewModel(InventoryRepository inventoryRepository, CategoryRepository categoryRepository, StoreRepository storeRepository) {
|
||||||
this.inventoryRepository = inventoryRepository;
|
this.inventoryRepository = inventoryRepository;
|
||||||
this.categoryRepository = categoryRepository;
|
this.categoryRepository = categoryRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a paginated list of inventory items, with optional filtering and sorting.
|
* Retrieves a paginated list of inventory items, with optional filtering and sorting.
|
||||||
*/
|
*/
|
||||||
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, int page, int size, String sort) {
|
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, String category, Long storeId, int page, int size, String sort) {
|
||||||
return inventoryRepository.getAllInventory(query, page, size, sort);
|
return inventoryRepository.getAllInventory(query, category, storeId, page, size, sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,4 +81,11 @@ public class InventoryViewModel extends ViewModel {
|
|||||||
public LiveData<Resource<PageResponse<CategoryDTO>>> getAllCategories(int page, int size) {
|
public LiveData<Resource<PageResponse<CategoryDTO>>> getAllCategories(int page, int size) {
|
||||||
return categoryRepository.getAllCategories(page, size);
|
return categoryRepository.getAllCategories(page, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a paginated list of stores.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
|
||||||
|
return storeRepository.getAllStores(page, size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ public class ProductViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a paginated list of products, optionally filtered by a query string.
|
* Retrieves a paginated list of products, optionally filtered by a query string, category and sorted.
|
||||||
*/
|
*/
|
||||||
public LiveData<Resource<PageResponse<ProductDTO>>> getAllProducts(String query, int page, int size) {
|
public LiveData<Resource<PageResponse<ProductDTO>>> getAllProducts(String query, Long categoryId, int page, int size, String sort) {
|
||||||
return productRepository.getAllProducts(query, page, size);
|
return productRepository.getAllProducts(query, categoryId, page, size, sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,42 +30,78 @@
|
|||||||
android:contentDescription="Open menu"/>
|
android:contentDescription="Open menu"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:text="Inventory"
|
android:text="Inventory"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnToggleFilter"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:contentDescription="Toggle filter"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutFilter"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:padding="8dp"
|
android:paddingStart="12dp"
|
||||||
android:gravity="center_vertical">
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:background="@color/primary_dark"
|
||||||
|
android:elevation="4dp">
|
||||||
|
|
||||||
<EditText
|
<LinearLayout
|
||||||
android:id="@+id/etSearchInventory"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
android:layout_height="44dp"
|
||||||
android:layout_height="48dp"
|
android:background="@drawable/bg_search_bar"
|
||||||
android:layout_weight="1"
|
android:gravity="center_vertical"
|
||||||
android:hint="Search by product or category…"
|
android:paddingStart="12dp"
|
||||||
android:inputType="text"
|
android:paddingEnd="12dp">
|
||||||
android:drawableStart="@android:drawable/ic_menu_search"
|
|
||||||
android:drawablePadding="8dp"
|
<ImageView
|
||||||
android:background="@android:color/white"
|
android:layout_width="18dp"
|
||||||
android:padding="12dp"
|
android:layout_height="18dp"
|
||||||
android:textColor="@color/text_dark"/>
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:alpha="0.6"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etSearchInventory"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="Search by product…"
|
||||||
|
android:inputType="text"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textColorHint="#99000000"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerCategory"
|
android:id="@+id/spinnerStore"
|
||||||
android:layout_width="140dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="48dp"
|
android:layout_height="44dp"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:background="@android:color/white"
|
android:background="@drawable/bg_spinner"
|
||||||
android:padding="10dp"/>
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Bulk-delete action bar (hidden until long-press) -->
|
<!-- Bulk-delete action bar (hidden until long-press) -->
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="56dp"
|
||||||
android:background="@color/primary_dark"
|
android:background="@color/primary_dark"
|
||||||
@@ -28,27 +29,78 @@
|
|||||||
android:contentDescription="Open menu"/>
|
android:contentDescription="Open menu"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:text="Products"
|
android:text="Products"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnToggleFilter"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:contentDescription="Toggle filter"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<EditText
|
<LinearLayout
|
||||||
android:id="@+id/etSearchProduct"
|
android:id="@+id/layoutFilter"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:orientation="vertical"
|
||||||
android:hint="Search by name or category..."
|
android:paddingStart="12dp"
|
||||||
android:inputType="text"
|
android:paddingEnd="12dp"
|
||||||
android:drawableStart="@android:drawable/ic_menu_search"
|
android:paddingTop="10dp"
|
||||||
android:drawablePadding="8dp"
|
android:paddingBottom="10dp"
|
||||||
android:background="@android:color/white"
|
android:visibility="gone"
|
||||||
android:padding="12dp"
|
android:background="@color/primary_dark"
|
||||||
android:textColor="@color/text_dark"/>
|
android:elevation="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:background="@drawable/bg_search_bar"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:alpha="0.6"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etSearchProduct"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:hint="Search products..."
|
||||||
|
android:inputType="text"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textColorHint="#99000000"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerCategory"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:background="@drawable/bg_spinner"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swipeRefreshProduct"
|
android:id="@+id/swipeRefreshProduct"
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ public class InventoryController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<InventoryResponse>> getAllInventory(
|
public ResponseEntity<Page<InventoryResponse>> getAllInventory(
|
||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
return ResponseEntity.ok(inventoryService.getAllInventory(q, pageable));
|
return ResponseEntity.ok(inventoryService.getAllInventory(q, storeId, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ public interface InventoryRepository extends JpaRepository<Inventory, Long> {
|
|||||||
Optional<Inventory> findByProductIdAndStoreId(@Param("productId") Long productId, @Param("storeId") Long storeId);
|
Optional<Inventory> findByProductIdAndStoreId(@Param("productId") Long productId, @Param("storeId") Long storeId);
|
||||||
|
|
||||||
@Query("SELECT i FROM Inventory i LEFT JOIN i.store s WHERE " +
|
@Query("SELECT i FROM Inventory i LEFT JOIN i.store s WHERE " +
|
||||||
|
"(:q IS NULL OR (" +
|
||||||
"LOWER(i.product.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(i.product.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(i.product.category.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(i.product.category.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(s.storeName) LIKE LOWER(CONCAT('%', :q, '%'))")
|
"LOWER(s.storeName) LIKE LOWER(CONCAT('%', :q, '%')))) AND " +
|
||||||
Page<Inventory> searchInventory(@Param("q") String query, Pageable pageable);
|
"(:storeId IS NULL OR i.store.storeId = :storeId)")
|
||||||
|
Page<Inventory> searchInventory(@Param("q") String query, @Param("storeId") Long storeId, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,13 +28,9 @@ public class InventoryService {
|
|||||||
this.storeRepository = storeRepository;
|
this.storeRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Page<InventoryResponse> getAllInventory(String query, Pageable pageable) {
|
public Page<InventoryResponse> getAllInventory(String query, Long storeId, Pageable pageable) {
|
||||||
Page<Inventory> inventory;
|
String normalizedQuery = normalizeFilter(query);
|
||||||
if (query != null && !query.trim().isEmpty()) {
|
Page<Inventory> inventory = inventoryRepository.searchInventory(normalizedQuery, storeId, pageable);
|
||||||
inventory = inventoryRepository.searchInventory(query, pageable);
|
|
||||||
} else {
|
|
||||||
inventory = inventoryRepository.findAll(pageable);
|
|
||||||
}
|
|
||||||
return inventory.map(this::mapToResponse);
|
return inventory.map(this::mapToResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +93,14 @@ public class InventoryService {
|
|||||||
inventoryRepository.deleteAllById(request.getIds());
|
inventoryRepository.deleteAllById(request.getIds());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeFilter(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
private InventoryResponse mapToResponse(Inventory inventory) {
|
private InventoryResponse mapToResponse(Inventory inventory) {
|
||||||
StoreLocation store = inventory.getStore();
|
StoreLocation store = inventory.getStore();
|
||||||
return new InventoryResponse(
|
return new InventoryResponse(
|
||||||
|
|||||||
Reference in New Issue
Block a user