added filtering for Sales and added helper method for setting up filtertoggle andriod

This commit is contained in:
Alex
2026-04-08 01:32:34 -06:00
parent 526650bd98
commit 57ac8ad2ce
21 changed files with 151 additions and 155 deletions

View File

@@ -16,13 +16,14 @@ public interface SaleApi {
Call<PageResponse<SaleDTO>> getAllSales( Call<PageResponse<SaleDTO>> getAllSales(
@Query("page") int page, @Query("page") int page,
@Query("size") int size, @Query("size") int size,
@Query("query") String query, @Query("q") String query,
@Query("paymentMethod") String paymentMethod, @Query("paymentMethod") String paymentMethod,
@Query("sortBy") String sortBy); @Query("storeId") Long storeId,
@Query("sort") String sort);
@GET("api/v1/sales/{id}") @GET("api/v1/sales/{id}")
Call<SaleDTO> getSaleById(@Path("id") Long id); Call<SaleDTO> getSaleById(@Path("id") Long id);
@POST("api/v1/sales") @POST("api/v1/sales")
Call<SaleDTO> createSale(@Body SaleDTO sale); Call<SaleDTO> createSale(@Body SaleDTO sale);
} }

View File

@@ -26,6 +26,7 @@ import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.AdoptionViewModel; import com.example.petstoremobile.viewmodels.AdoptionViewModel;
import com.example.petstoremobile.utils.EventDecorator; import com.example.petstoremobile.utils.EventDecorator;
import com.example.petstoremobile.viewmodels.StoreViewModel; import com.example.petstoremobile.viewmodels.StoreViewModel;
@@ -141,20 +142,24 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilterAdoption.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
if (binding.layoutFilterAdoption.getVisibility() == View.GONE) { binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption);
binding.layoutFilterAdoption.setVisibility(View.VISIBLE);
binding.btnToggleFilterAdoption.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
} else {
binding.layoutFilterAdoption.setVisibility(View.GONE);
binding.btnToggleFilterAdoption.setImageResource(android.R.drawable.ic_menu_search);
// Reset filters when closing binding.btnToggleFilterAdoption.setOnClickListener(v -> {
boolean isVisible = binding.layoutFilterAdoption.getVisibility() == View.VISIBLE;
binding.layoutFilterAdoption.setVisibility(isVisible ? View.GONE : View.VISIBLE);
binding.btnToggleFilterAdoption.setImageResource(isVisible ?
android.R.drawable.ic_menu_search :
android.R.drawable.ic_menu_close_clear_cancel);
if (isVisible) {
binding.etSearchAdoption.setText(""); binding.etSearchAdoption.setText("");
binding.spinnerStatusAdoption.setSelection(0); binding.spinnerStatusAdoption.setSelection(0);
binding.spinnerStoreAdoption.setSelection(0); binding.spinnerStoreAdoption.setSelection(0);
selectedCalendarDay = null; selectedCalendarDay = null;
binding.calendarViewAdoption.clearSelection(); binding.calendarViewAdoption.clearSelection();
loadAdoptions();
} }
}); });
} }

View File

@@ -75,7 +75,7 @@ public class AnalyticsFragment extends Fragment {
tvAvgTransaction.setText("..."); tvAvgTransaction.setText("...");
tvTotalItems.setText("..."); tvTotalItems.setText("...");
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000, null, null, null) RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000, null, null, null, "saleDate,desc")
.enqueue(new Callback<PageResponse<SaleDTO>>() { .enqueue(new Callback<PageResponse<SaleDTO>>() {
public void onResponse(Call<PageResponse<SaleDTO>> c, public void onResponse(Call<PageResponse<SaleDTO>> c,
Response<PageResponse<SaleDTO>> r) { Response<PageResponse<SaleDTO>> r) {

View File

@@ -27,6 +27,7 @@ import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.AppointmentViewModel; import com.example.petstoremobile.viewmodels.AppointmentViewModel;
import com.example.petstoremobile.utils.EventDecorator; import com.example.petstoremobile.utils.EventDecorator;
import com.example.petstoremobile.viewmodels.AuthViewModel; import com.example.petstoremobile.viewmodels.AuthViewModel;
@@ -171,21 +172,26 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment,
binding.spinnerStatus, binding.spinnerStore);
// Add additional reset logic for elements specific to this fragment
binding.btnToggleFilter.setOnClickListener(v -> { binding.btnToggleFilter.setOnClickListener(v -> {
if (binding.layoutFilter.getVisibility() == View.GONE) { boolean isVisible = binding.layoutFilter.getVisibility() == View.VISIBLE;
binding.layoutFilter.setVisibility(View.VISIBLE); binding.layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
} else { binding.btnToggleFilter.setImageResource(isVisible ?
binding.layoutFilter.setVisibility(View.GONE); android.R.drawable.ic_menu_search :
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search); android.R.drawable.ic_menu_close_clear_cancel);
// Reset filters when closing if (isVisible) {
binding.etSearchAppointment.setText(""); binding.etSearchAppointment.setText("");
binding.spinnerStatus.setSelection(0); binding.spinnerStatus.setSelection(0);
binding.spinnerStore.setSelection(0); binding.spinnerStore.setSelection(0);
binding.btnMyAppointments.setChecked(false); binding.btnMyAppointments.setChecked(false);
selectedCalendarDay = null; selectedCalendarDay = null;
binding.calendarView.clearSelection(); binding.calendarView.clearSelection();
loadAppointmentData();
} }
}); });
} }
@@ -214,10 +220,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
*/ */
private void updateCalendarDecorators() { private void updateCalendarDecorators() {
HashSet<CalendarDay> datesWithAppointments = new HashSet<>(); HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
SimpleDateFormat displayFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
for (AppointmentDTO appointment : appointmentList) { for (AppointmentDTO appointment : appointmentList) {
try { try {
//Get the appointment date //Get the appointment date
Date date = dateFormat.parse(appointment.getAppointmentDate()); Date date = displayFormat.parse(appointment.getAppointmentDate());
//if the date is not null, add it to the hashset //if the date is not null, add it to the hashset
if (date != null) { if (date != null) {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();

View File

@@ -24,6 +24,7 @@ import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.UIUtils;
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 com.example.petstoremobile.utils.SpinnerUtils;
@@ -115,19 +116,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory, binding.spinnerStore);
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.etSearchInventory.setText("");
binding.spinnerStore.setSelection(0);
}
});
} }
/** /**

View File

@@ -27,6 +27,7 @@ import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.PetViewModel; import com.example.petstoremobile.viewmodels.PetViewModel;
import com.example.petstoremobile.viewmodels.StoreViewModel; import com.example.petstoremobile.viewmodels.StoreViewModel;
@@ -126,21 +127,8 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
if (binding.layoutFilter.getVisibility() == View.GONE) { binding.spinnerStatus, binding.spinnerSpecies, binding.spinnerStore);
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.etSearchPet.setText("");
binding.spinnerStatus.setSelection(0);
binding.spinnerSpecies.setSelection(0);
binding.spinnerStore.setSelection(0);
}
});
} }
/** /**

View File

@@ -25,6 +25,7 @@ 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.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.ProductViewModel; import com.example.petstoremobile.viewmodels.ProductViewModel;
import java.util.ArrayList; import java.util.ArrayList;
@@ -104,19 +105,8 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
if (binding.layoutFilter.getVisibility() == View.GONE) { binding.etSearchProduct, binding.spinnerCategory);
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.etSearchProduct.setText("");
binding.spinnerCategory.setSelection(0);
}
});
} }
/** /**

View File

@@ -26,6 +26,7 @@ import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel; import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
import com.example.petstoremobile.viewmodels.ProductViewModel; import com.example.petstoremobile.viewmodels.ProductViewModel;
import com.example.petstoremobile.viewmodels.SupplierViewModel; import com.example.petstoremobile.viewmodels.SupplierViewModel;
@@ -125,20 +126,8 @@ public class ProductSupplierFragment extends Fragment
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS,
if (binding.layoutFilter.getVisibility() == View.GONE) { binding.spinnerProduct, binding.spinnerSupplier);
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.etSearchPS.setText("");
binding.spinnerProduct.setSelection(0);
binding.spinnerSupplier.setSelection(0);
}
});
} }
/** /**

View File

@@ -24,6 +24,7 @@ import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel; import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
import com.example.petstoremobile.viewmodels.StoreViewModel; import com.example.petstoremobile.viewmodels.StoreViewModel;
@@ -100,19 +101,7 @@ public class PurchaseOrderFragment extends Fragment
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO, binding.spinnerStore);
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);
}
});
} }
/** /**

View File

@@ -20,13 +20,15 @@ import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.SaleAdapter; import com.example.petstoremobile.adapters.SaleAdapter;
import com.example.petstoremobile.databinding.FragmentSaleBinding; import com.example.petstoremobile.databinding.FragmentSaleBinding;
import com.example.petstoremobile.dtos.SaleDTO; import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.SaleViewModel; import com.example.petstoremobile.viewmodels.SaleViewModel;
import com.example.petstoremobile.viewmodels.StoreViewModel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import dagger.hilt.android.AndroidEntryPoint; import dagger.hilt.android.AndroidEntryPoint;
@@ -35,12 +37,14 @@ import dagger.hilt.android.AndroidEntryPoint;
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener { public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
private static final String TAG = "SaleFragment"; private static final String TAG = "SaleFragment";
private static final int PAGE_SIZE = 20; private static final int PAGE_SIZE = 200;
private FragmentSaleBinding binding; private FragmentSaleBinding binding;
private final List<SaleDTO> saleList = new ArrayList<>(); private final List<SaleDTO> saleList = new ArrayList<>();
private final List<StoreDTO> storeList = new ArrayList<>();
private SaleAdapter adapter; private SaleAdapter adapter;
private SaleViewModel saleViewModel; private SaleViewModel saleViewModel;
private StoreViewModel storeViewModel;
// Pagination // Pagination
private int currentPage = 0; private int currentPage = 0;
@@ -58,9 +62,11 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class); saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
setupRecyclerView(); setupRecyclerView();
setupSearch(); setupSearch();
setupStoreFilter();
setupPaymentMethodFilter(); setupPaymentMethodFilter();
setupSwipeRefresh(); setupSwipeRefresh();
setupFilterToggle(); setupFilterToggle();
@@ -83,26 +89,35 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
NavHostFragment.findNavController(this).navigate(R.id.nav_refund)); NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
} }
@Override
public void onResume() {
super.onResume();
loadStoreData();
}
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
if (binding.layoutFilter.getVisibility() == View.GONE) { binding.spinnerPaymentMethod, binding.spinnerStore);
binding.layoutFilter.setVisibility(View.VISIBLE); }
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
} else { private void setupStoreFilter() {
binding.layoutFilter.setVisibility(View.GONE); SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search); }
binding.etSearchSale.setText("");
binding.spinnerPaymentMethod.setSelection(0); private void loadStoreData() {
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
storeList.clear();
storeList.addAll(resource.data.getContent());
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
StoreDTO::getStoreName, "Stores", null, StoreDTO::getStoreId);
} }
}); });
} }
private void setupPaymentMethodFilter() { private void setupPaymentMethodFilter() {
List<String> paymentMethods = Arrays.asList("Cash", "Card"); String[] paymentMethods = {"Payments", "Cash", "Card"};
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
s -> s, "All Payments", null, s -> (long) s.hashCode());
SpinnerUtils.setupFilterSpinner(binding.spinnerPaymentMethod, () -> loadSales(true));
} }
@Override @Override
@@ -121,12 +136,13 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (dy <= 0) return; if (dy <= 0) return;
LinearLayoutManager lm = (LinearLayoutManager) binding.recyclerViewSales.getLayoutManager(); LinearLayoutManager lm = (LinearLayoutManager) binding.recyclerViewSales.getLayoutManager();
if (lm == null) return; if (lm != null) {
int visible = lm.getChildCount(); int visible = lm.getChildCount();
int total = lm.getItemCount(); int total = lm.getItemCount();
int firstVis = lm.findFirstVisibleItemPosition(); int firstVis = lm.findFirstVisibleItemPosition();
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) { if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
loadSales(false); loadSales(false);
}
} }
} }
}); });
@@ -163,7 +179,12 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
paymentMethod = (String) binding.spinnerPaymentMethod.getSelectedItem(); paymentMethod = (String) binding.spinnerPaymentMethod.getSelectedItem();
} }
saleViewModel.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, "saleDate,desc").observe(getViewLifecycleOwner(), resource -> { Long storeId = null;
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
}
saleViewModel.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, "saleDate,desc").observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return; if (resource == null) return;
switch (resource.status) { switch (resource.status) {
@@ -186,7 +207,9 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
isLoading = false; isLoading = false;
binding.swipeRefreshSale.setRefreshing(false); binding.swipeRefreshSale.setRefreshing(false);
Log.e(TAG, "Error loading sales: " + resource.message); Log.e(TAG, "Error loading sales: " + resource.message);
Toast.makeText(getContext(), "Failed to load sales: " + resource.message, Toast.LENGTH_SHORT).show(); if (getContext() != null) {
Toast.makeText(getContext(), "Failed to load sales: " + resource.message, Toast.LENGTH_SHORT).show();
}
break; break;
} }
}); });

View File

@@ -24,6 +24,7 @@ import com.example.petstoremobile.dtos.ServiceDTO;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.ServiceViewModel; import com.example.petstoremobile.viewmodels.ServiceViewModel;
import java.util.ArrayList; import java.util.ArrayList;
@@ -113,18 +114,7 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService);
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.etSearchService.setText("");
}
});
} }
/** /**

View File

@@ -18,6 +18,7 @@ import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.dtos.EmployeeDTO; import com.example.petstoremobile.dtos.EmployeeDTO;
import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.UIUtils;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.*; import java.util.*;
import retrofit2.*; import retrofit2.*;
@@ -50,13 +51,7 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
hamburger.setOnClickListener(v -> openDrawer()); hamburger.setOnClickListener(v -> openDrawer());
ImageButton btnToggleFilter = view.findViewById(R.id.btnToggleFilterStaff); ImageButton btnToggleFilter = view.findViewById(R.id.btnToggleFilterStaff);
btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(btnToggleFilter, layoutFilter, etSearch);
if (layoutFilter.getVisibility() == View.VISIBLE) {
layoutFilter.setVisibility(View.GONE);
} else {
layoutFilter.setVisibility(View.VISIBLE);
}
});
return view; return view;
} }

View File

@@ -24,6 +24,7 @@ import com.example.petstoremobile.dtos.SupplierDTO;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler; import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.SupplierViewModel; import com.example.petstoremobile.viewmodels.SupplierViewModel;
import java.util.ArrayList; import java.util.ArrayList;
@@ -104,18 +105,7 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
* Sets up the filter toggle button to show/hide the filter layout. * Sets up the filter toggle button to show/hide the filter layout.
*/ */
private void setupFilterToggle() { private void setupFilterToggle() {
binding.btnToggleFilter.setOnClickListener(v -> { UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier);
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 search when closing
binding.etSearchSupplier.setText("");
}
});
} }
/** /**

View File

@@ -101,7 +101,7 @@ public class RefundFragment extends Fragment {
} }
private void loadAllSales() { private void loadAllSales() {
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000, null, null, null) RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000, null, null, null, "saleDate,desc")
.enqueue(new Callback<PageResponse<SaleDTO>>() { .enqueue(new Callback<PageResponse<SaleDTO>>() {
public void onResponse(Call<PageResponse<SaleDTO>> c, public void onResponse(Call<PageResponse<SaleDTO>> c,
Response<PageResponse<SaleDTO>> r) { Response<PageResponse<SaleDTO>> r) {

View File

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

View File

@@ -2,7 +2,12 @@ package com.example.petstoremobile.utils;
import android.telephony.PhoneNumberFormattingTextWatcher; import android.telephony.PhoneNumberFormattingTextWatcher;
import android.text.InputFilter; import android.text.InputFilter;
import android.view.View;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Spinner;
import com.example.petstoremobile.R;
/** /**
* Utility class for shared UI component logic and formatting. * Utility class for shared UI component logic and formatting.
@@ -15,4 +20,26 @@ public class UIUtils {
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher("CA")); editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher("CA"));
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(14)}); editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(14)});
} }
/**
* Sets up a toggle for a filter layout, including icon changes and field resets.
*/
public static void setupFilterToggle(ImageButton btnToggle, View layoutFilter, EditText etSearch, Spinner... spinners) {
btnToggle.setOnClickListener(v -> {
boolean isVisible = layoutFilter.getVisibility() == View.VISIBLE;
layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
// Use Android default icons or app-specific ones if available
btnToggle.setImageResource(isVisible ?
android.R.drawable.ic_menu_search :
android.R.drawable.ic_menu_close_clear_cancel);
if (isVisible) {
if (etSearch != null) etSearch.setText("");
for (Spinner spinner : spinners) {
if (spinner != null) spinner.setSelection(0);
}
}
});
}
} }

View File

@@ -21,8 +21,8 @@ public class SaleViewModel extends ViewModel {
this.saleRepository = saleRepository; this.saleRepository = saleRepository;
} }
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, String sortBy) { public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, String sortBy) {
return saleRepository.getAllSales(page, size, query, paymentMethod, sortBy); return saleRepository.getAllSales(page, size, query, paymentMethod, storeId, sortBy);
} }
public LiveData<Resource<SaleDTO>> getSaleById(Long id) { public LiveData<Resource<SaleDTO>> getSaleById(Long id) {

View File

@@ -81,7 +81,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1"
android:hint="Search by employee or date..." android:hint="Search by employee or store..."
android:inputType="text" android:inputType="text"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:textColor="@color/text_dark" android:textColor="@color/text_dark"
@@ -100,7 +100,7 @@
android:gravity="center_vertical"> android:gravity="center_vertical">
<Spinner <Spinner
android:id="@+id/spinnerPaymentMethod" android:id="@+id/spinnerStore"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="44dp" android:layout_height="44dp"
android:layout_weight="1" android:layout_weight="1"
@@ -109,6 +109,17 @@
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:layout_marginEnd="4dp"/> android:layout_marginEnd="4dp"/>
<Spinner
android:id="@+id/spinnerPaymentMethod"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:background="@drawable/bg_spinner"
android:paddingStart="12dp"
android:paddingEnd="8dp"
android:layout_marginEnd="4dp"
android:layout_marginStart="4dp"/>
<Button <Button
android:id="@+id/btnOpenRefund" android:id="@+id/btnOpenRefund"
android:layout_width="0dp" android:layout_width="0dp"

View File

@@ -26,8 +26,9 @@ public class SaleController {
public ResponseEntity<Page<SaleResponse>> getAllSales( public ResponseEntity<Page<SaleResponse>> getAllSales(
@RequestParam(required = false) String q, @RequestParam(required = false) String q,
@RequestParam(required = false) String paymentMethod, @RequestParam(required = false) String paymentMethod,
@RequestParam(required = false) Long storeId,
Pageable pageable) { Pageable pageable) {
return ResponseEntity.ok(saleService.getAllSales(q, paymentMethod, pageable)); return ResponseEntity.ok(saleService.getAllSales(q, paymentMethod, storeId, pageable));
} }
@GetMapping("/{id}") @GetMapping("/{id}")

View File

@@ -19,8 +19,9 @@ public interface SaleRepository extends JpaRepository<Sale, Long> {
"LOWER(s.employee.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(s.employee.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
"LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))" + "LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))" +
")) AND " + ")) AND " +
"(:paymentMethod IS NULL OR LOWER(s.paymentMethod) = LOWER(:paymentMethod))") "(:paymentMethod IS NULL OR LOWER(s.paymentMethod) = LOWER(:paymentMethod)) AND " +
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, Pageable pageable); "(:storeId IS NULL OR s.store.storeId = :storeId)")
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, Pageable pageable);
List<Sale> findByOriginalSaleSaleId(Long originalSaleId); List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
} }

View File

@@ -39,8 +39,8 @@ public class SaleService {
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
public Page<SaleResponse> getAllSales(String query, String paymentMethod, Pageable pageable) { public Page<SaleResponse> getAllSales(String query, String paymentMethod, Long storeId, Pageable pageable) {
Page<Sale> sales = saleRepository.searchSales(normalizeFilter(query), normalizeFilter(paymentMethod), pageable); Page<Sale> sales = saleRepository.searchSales(normalizeFilter(query), normalizeFilter(paymentMethod), storeId, pageable);
return sales.map(this::mapToResponse); return sales.map(this::mapToResponse);
} }