added filter by customer for sales to backend and android

This commit is contained in:
Alex
2026-04-14 04:03:15 -06:00
parent 4594139f8e
commit f623c17071
12 changed files with 76 additions and 17 deletions

View File

@@ -48,6 +48,12 @@ public class SaleAdapter extends RecyclerView.Adapter<SaleAdapter.SaleViewHolder
binding.tvSaleId.setText("Sale #" + (s.getSaleId() != null ? s.getSaleId() : ""));
binding.tvSaleEmployee.setText("By: " + (s.getEmployeeName() != null ? s.getEmployeeName() : ""));
if (s.getCustomerName() != null && !s.getCustomerName().isEmpty()) {
binding.tvSaleCustomer.setText("Customer: " + s.getCustomerName());
binding.tvSaleCustomer.setVisibility(View.VISIBLE);
} else {
binding.tvSaleCustomer.setVisibility(View.GONE);
}
binding.tvSaleDate.setText(s.getSaleDate() != null ? s.getSaleDate().substring(0, Math.min(10, s.getSaleDate().length())) : "");
binding.tvSalePayment.setText(s.getPaymentMethod() != null ? s.getPaymentMethod() : "");
binding.tvSaleTotal.setText(s.getTotalAmount() != null ? "$" + s.getTotalAmount() : "");

View File

@@ -20,6 +20,7 @@ public interface SaleApi {
@Query("paymentMethod") String paymentMethod,
@Query("storeId") Long storeId,
@Query("isRefund") Boolean isRefund,
@Query("customerId") Long customerId,
@Query("sort") String sort);
@GET("api/v1/sales/{id}")

View File

@@ -17,6 +17,7 @@ import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.SaleAdapter;
import com.example.petstoremobile.api.auth.TokenManager;
import com.example.petstoremobile.databinding.FragmentSaleBinding;
import com.example.petstoremobile.dtos.CustomerDTO;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.utils.SpinnerUtils;
@@ -57,10 +58,11 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
setupStoreFilter();
setupPaymentMethodFilter();
setupRefundStatusFilter();
setupCustomerFilter();
setupSwipeRefresh();
setupFilterToggle();
observeViewModel();
loadSales(true);
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
@@ -89,6 +91,11 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
StoreDTO::getStoreName, "All Stores", null, StoreDTO::getStoreId);
});
viewModel.getCustomers().observe(getViewLifecycleOwner(), list -> {
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCustomer, list,
CustomerDTO::getFullName, "All Customers", null, CustomerDTO::getCustomerId);
});
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
binding.swipeRefreshSale.setRefreshing(loading);
});
@@ -98,16 +105,17 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
public void onResume() {
super.onResume();
if (!isStaff()) viewModel.loadStores();
viewModel.loadCustomers();
}
private void setupFilterToggle() {
if (isStaff()) {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
binding.spinnerPaymentMethod, binding.spinnerRefundStatus);
binding.spinnerPaymentMethod, binding.spinnerRefundStatus, binding.spinnerCustomer);
binding.spinnerStore.setVisibility(View.GONE);
} else {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
binding.spinnerPaymentMethod, binding.spinnerStore, binding.spinnerRefundStatus);
binding.spinnerPaymentMethod, binding.spinnerStore, binding.spinnerRefundStatus, binding.spinnerCustomer);
}
}
@@ -133,6 +141,10 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRefundStatus, refundStatuses, () -> loadSales(true));
}
private void setupCustomerFilter() {
SpinnerUtils.setupFilterSpinner(binding.spinnerCustomer, () -> loadSales(true));
}
private void setupRecyclerView() {
adapter = new SaleAdapter(saleList, this);
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
@@ -189,7 +201,13 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
isRefund = binding.spinnerRefundStatus.getSelectedItemPosition() == 2;
}
viewModel.loadSales(reset, query, paymentMethod, storeId, isRefund);
Long customerId = null;
List<CustomerDTO> customerList = viewModel.getCustomers().getValue();
if (binding.spinnerCustomer.getSelectedItemPosition() > 0 && customerList != null && !customerList.isEmpty()) {
customerId = customerList.get(binding.spinnerCustomer.getSelectedItemPosition() - 1).getCustomerId();
}
viewModel.loadSales(reset, query, paymentMethod, storeId, isRefund, customerId);
}
@Override

View File

@@ -20,8 +20,8 @@ public class SaleRepository extends BaseRepository {
this.saleApi = saleApi;
}
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, Boolean isRefund, String sortBy) {
return executeCall(saleApi.getAllSales(page, size, query, paymentMethod, storeId, isRefund, sortBy));
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, Boolean isRefund, Long customerId, String sortBy) {
return executeCall(saleApi.getAllSales(page, size, query, paymentMethod, storeId, isRefund, customerId, sortBy));
}
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {

View File

@@ -51,7 +51,7 @@ public class AnalyticsViewModel extends ViewModel {
public void loadAnalytics() {
isLoading.setValue(true);
errorMessage.setValue(null);
observeOnce(saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc"), resource -> {
observeOnce(saleRepository.getAllSales(0, 2000, null, null, null, null, null, "saleDate,desc"), resource -> {
if (resource != null) {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
cachedSales = resource.data.getContent();

View File

@@ -35,7 +35,7 @@ public class RefundViewModel extends ViewModel {
}
public LiveData<Resource<PageResponse<SaleDTO>>> loadAllSales() {
return saleRepository.getAllSales(0, 1000, null, null, null, null, "saleDate,desc");
return saleRepository.getAllSales(0, 1000, null, null, null, null, null, "saleDate,desc");
}
public void setAllSales(List<SaleDTO> sales) {

View File

@@ -4,9 +4,11 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.CustomerDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.repositories.CustomerRepository;
import com.example.petstoremobile.repositories.SaleRepository;
import com.example.petstoremobile.repositories.StoreRepository;
import com.example.petstoremobile.utils.Resource;
@@ -24,27 +26,31 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
public class SaleListViewModel extends ViewModel {
private final SaleRepository saleRepository;
private final StoreRepository storeRepository;
private final CustomerRepository customerRepository;
private final MutableLiveData<List<SaleDTO>> sales = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<CustomerDTO>> customers = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
private int currentPage = 0;
private boolean isLastPage = false;
private static final int PAGE_SIZE = 20;
@Inject
public SaleListViewModel(SaleRepository saleRepository, StoreRepository storeRepository) {
public SaleListViewModel(SaleRepository saleRepository, StoreRepository storeRepository, CustomerRepository customerRepository) {
this.saleRepository = saleRepository;
this.storeRepository = storeRepository;
this.customerRepository = customerRepository;
}
public LiveData<List<SaleDTO>> getSales() { return sales; }
public LiveData<List<StoreDTO>> getStores() { return stores; }
public LiveData<List<CustomerDTO>> getCustomers() { return customers; }
public LiveData<Boolean> getIsLoading() { return isLoading; }
public boolean isLastPage() { return isLastPage; }
public void loadSales(boolean reset, String query, String paymentMethod, Long storeId, Boolean isRefund) {
public void loadSales(boolean reset, String query, String paymentMethod, Long storeId, Boolean isRefund, Long customerId) {
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
if (reset) {
@@ -53,7 +59,7 @@ public class SaleListViewModel extends ViewModel {
}
isLoading.setValue(true);
observeOnce(saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, isRefund, "saleDate,desc"), resource -> {
observeOnce(saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, isRefund, customerId, "saleDate,desc"), resource -> {
if (resource != null) {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());
@@ -77,6 +83,14 @@ public class SaleListViewModel extends ViewModel {
});
}
public void loadCustomers() {
observeOnce(customerRepository.getAllCustomers(0, 500), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
customers.setValue(resource.data.getContent());
}
});
}
private <T> void observeOnce(LiveData<Resource<T>> liveData, Observer<Resource<T>> handler) {
liveData.observeForever(new Observer<Resource<T>>() {
@Override

View File

@@ -151,6 +151,15 @@
android:layout_marginStart="4dp"/>
</LinearLayout>
<Spinner
android:id="@+id/spinnerCustomer"
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

View File

@@ -74,6 +74,15 @@
android:textColor="#888888"
android:textSize="14sp" />
<TextView
android:id="@+id/tvSaleCustomer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="#888888"
android:textSize="14sp"
android:visibility="gone" />
<TextView
android:id="@+id/tvSaleDate"
android:layout_width="wrap_content"