From 6f11f4ebbb3f667915f6495a7daf07311c63e753 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:53:42 -0600 Subject: [PATCH] making appointment userfrendly part1 andriod --- .../example/petstoremobile/api/PetApi.java | 1 + .../example/petstoremobile/api/StoreApi.java | 7 ++ .../petstoremobile/dtos/DropdownDTO.java | 29 +++++++ .../listfragments/AdoptionFragment.java | 18 ---- .../listfragments/AppointmentFragment.java | 20 ----- .../fragments/listfragments/PetFragment.java | 2 +- .../AdoptionDetailFragment.java | 2 +- .../AppointmentDetailFragment.java | 87 ++++++++++++++++--- .../repositories/PetRepository.java | 4 +- .../repositories/StoreRepository.java | 10 +++ .../example/petstoremobile/utils/UIUtils.java | 9 +- .../viewmodels/PetViewModel.java | 4 +- .../viewmodels/StoreViewModel.java | 10 +++ .../layout/fragment_appointment_detail.xml | 6 +- .../backend/controller/PetController.java | 3 +- .../backend/repository/PetRepository.java | 5 +- .../petshop/backend/service/PetService.java | 4 +- 17 files changed, 151 insertions(+), 70 deletions(-) create mode 100644 android/app/src/main/java/com/example/petstoremobile/dtos/DropdownDTO.java diff --git a/android/app/src/main/java/com/example/petstoremobile/api/PetApi.java b/android/app/src/main/java/com/example/petstoremobile/api/PetApi.java index acae1b51..f594b8f9 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/PetApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/PetApi.java @@ -31,6 +31,7 @@ public interface PetApi { @Query("status") String status, @Query("species") String species, @Query("storeId") Long storeId, + @Query("customerId") Long customerId, @Query("sort") String sort ); diff --git a/android/app/src/main/java/com/example/petstoremobile/api/StoreApi.java b/android/app/src/main/java/com/example/petstoremobile/api/StoreApi.java index 8fe7f9c1..9819df22 100644 --- a/android/app/src/main/java/com/example/petstoremobile/api/StoreApi.java +++ b/android/app/src/main/java/com/example/petstoremobile/api/StoreApi.java @@ -1,10 +1,14 @@ package com.example.petstoremobile.api; +import com.example.petstoremobile.dtos.DropdownDTO; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.StoreDTO; +import java.util.List; + import retrofit2.Call; import retrofit2.http.GET; +import retrofit2.http.Path; import retrofit2.http.Query; public interface StoreApi { @@ -13,4 +17,7 @@ public interface StoreApi { Call> getAllStores( @Query("page") int page, @Query("size") int size); + + @GET("api/v1/dropdowns/stores/{storeId}/employees") + Call> getStoreEmployees(@Path("storeId") Long storeId); } diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/DropdownDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/DropdownDTO.java new file mode 100644 index 00000000..3174dae5 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/DropdownDTO.java @@ -0,0 +1,29 @@ +package com.example.petstoremobile.dtos; + +public class DropdownDTO { + private Long id; + private String label; + + public DropdownDTO() {} + + public DropdownDTO(Long id, String label) { + this.id = id; + this.label = label; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java index 577d58b1..f2a01b5d 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AdoptionFragment.java @@ -133,24 +133,6 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop private void setupFilterToggle() { UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption, binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption); - - 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.spinnerStatusAdoption.setSelection(0); - binding.spinnerStoreAdoption.setSelection(0); - selectedCalendarDay = null; - binding.calendarViewAdoption.clearSelection(); - loadAdoptions(); - } - }); } /** diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java index 3d4d6cd6..b921d867 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/AppointmentFragment.java @@ -163,26 +163,6 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter. 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 -> { - boolean isVisible = binding.layoutFilter.getVisibility() == View.VISIBLE; - binding.layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE); - - binding.btnToggleFilter.setImageResource(isVisible ? - android.R.drawable.ic_menu_search : - android.R.drawable.ic_menu_close_clear_cancel); - - if (isVisible) { - binding.etSearchAppointment.setText(""); - binding.spinnerStatus.setSelection(0); - binding.spinnerStore.setSelection(0); - binding.btnMyAppointments.setChecked(false); - selectedCalendarDay = null; - binding.calendarView.clearSelection(); - loadAppointmentData(); - } - }); } /** diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java index e7e36eae..87a1a54c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/PetFragment.java @@ -215,7 +215,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen if (status.equals("All Statuses")) status = null; if (species.equals("All Species")) species = null; - viewModel.getAllPets(0, 100, query, status, species, storeId, "petName").observe(getViewLifecycleOwner(), resource -> { + viewModel.getAllPets(0, 100, query, status, species, storeId, null, "petName").observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; switch (resource.status) { diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java index 7098b795..687a6a7c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AdoptionDetailFragment.java @@ -126,7 +126,7 @@ public class AdoptionDetailFragment extends Fragment { * Loads the list of pets from the API. */ private void loadPets() { - petViewModel.getAllPets(0, 200, null, null, null, null, "petName").observe(getViewLifecycleOwner(), resource -> { + petViewModel.getAllPets(0, 200, null, null, null, null, null, "petName").observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { petList = resource.data.getContent(); refreshPetSpinner(); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java index 1081462a..285f0d60 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/AppointmentDetailFragment.java @@ -47,7 +47,7 @@ public class AppointmentDetailFragment extends Fragment { private List serviceList = new ArrayList<>(); private List customerList = new ArrayList<>(); private List storeList = new ArrayList<>(); - private List staffList = new ArrayList<>(); + private List staffList = new ArrayList<>(); private final Integer[] HOURS = {9,10,11,12,13,14,15,16,17}; private final Integer[] MINUTES = {0,15,30,45}; @@ -107,6 +107,40 @@ public class AppointmentDetailFragment extends Fragment { hours[i] = String.format("%02d:00", HOURS[i]); SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerHour, hours); SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00","15","30","45"}); + + // Listener to load pets based on selected customer + binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (position > 0 && position <= customerList.size()) { + CustomerDTO selectedCustomer = customerList.get(position - 1); + loadPets(selectedCustomer.getCustomerId()); + } else { + petList.clear(); + refreshPetSpinner(); + } + } + + @Override + public void onNothingSelected(AdapterView parent) {} + }); + + // Listener to load staff based on selected store + binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + if (position > 0 && position <= storeList.size()) { + StoreDTO selectedStore = storeList.get(position - 1); + loadStaff(selectedStore.getStoreId()); + } else { + staffList.clear(); + refreshStaffSpinner(); + } + } + + @Override + public void onNothingSelected(AdapterView parent) {} + }); } /** @@ -129,18 +163,16 @@ public class AppointmentDetailFragment extends Fragment { * Fetches all required data for spinners from the backend. */ private void loadSpinnersData() { - loadPets(); loadServices(); loadCustomers(); loadStores(); - loadStaff(); } /** - * Loads the list of pets from the ViewModel. + * Loads the list of pets from the ViewModel, filtered by customerId. */ - private void loadPets() { - petViewModel.getAllPets(0, 200, null, null, null, null, "petName").observe(getViewLifecycleOwner(), resource -> { + private void loadPets(Long customerId) { + petViewModel.getAllPets(0, 200, null, null, null, null, customerId, "petName").observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { petList = resource.data.getContent(); refreshPetSpinner(); @@ -222,12 +254,12 @@ public class AppointmentDetailFragment extends Fragment { } /** - * Loads the list of staff from the API. + * Loads the list of staff for a specific store. */ - private void loadStaff() { - userViewModel.getUsers("STAFF", 0, 100).observe(getViewLifecycleOwner(), resource -> { + private void loadStaff(Long storeId) { + storeViewModel.getStoreEmployees(storeId).observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - staffList = resource.data.getContent(); + staffList = resource.data; refreshStaffSpinner(); } }); @@ -238,8 +270,8 @@ public class AppointmentDetailFragment extends Fragment { */ private void refreshStaffSpinner() { SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, staffList, - UserDTO::getFullName, "-- Select Staff --", - preselectedStaffId, UserDTO::getId); + DropdownDTO::getLabel, "-- Select Staff --", + preselectedStaffId, DropdownDTO::getId); } /** @@ -254,11 +286,42 @@ public class AppointmentDetailFragment extends Fragment { binding.tvAppointmentId.setText("ID: " + appointmentId); binding.tvAppointmentId.setVisibility(View.VISIBLE); binding.btnDeleteAppointment.setVisibility(View.VISIBLE); + + // Disable and fade fields in edit mode + binding.spinnerCustomer.setEnabled(false); + binding.spinnerStore.setEnabled(false); + binding.spinnerPet.setEnabled(false); + binding.spinnerService.setEnabled(false); + binding.spinnerCustomer.setAlpha(0.5f); + binding.spinnerStore.setAlpha(0.5f); + binding.spinnerPet.setAlpha(0.5f); + binding.spinnerService.setAlpha(0.5f); + + binding.tvLabelCustomer.setAlpha(0.5f); + binding.tvLabelStore.setAlpha(0.5f); + binding.tvLabelPet.setAlpha(0.5f); + binding.tvLabelService.setAlpha(0.5f); + loadAppointmentData(); } else { binding.tvApptMode.setText("Add Appointment"); binding.btnDeleteAppointment.setVisibility(View.GONE); binding.tvAppointmentId.setVisibility(View.GONE); + + // enable fields in add mode + binding.spinnerCustomer.setEnabled(true); + binding.spinnerStore.setEnabled(true); + binding.spinnerPet.setEnabled(true); + binding.spinnerService.setEnabled(true); + binding.spinnerCustomer.setAlpha(1.0f); + binding.spinnerStore.setAlpha(1.0f); + binding.spinnerPet.setAlpha(1.0f); + binding.spinnerService.setAlpha(1.0f); + + binding.tvLabelCustomer.setAlpha(1.0f); + binding.tvLabelStore.setAlpha(1.0f); + binding.tvLabelPet.setAlpha(1.0f); + binding.tvLabelService.setAlpha(1.0f); } } diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java index 019b5884..d49ec00f 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/PetRepository.java @@ -26,8 +26,8 @@ public class PetRepository extends BaseRepository { /** * Retrieves a paginated list of pets from the API with optional filters. */ - public LiveData>> getAllPets(int page, int size, String query, String status, String species, Long storeId, String sort) { - return executeCall(petApi.getAllPets(page, size, query, status, species, storeId, sort)); + public LiveData>> getAllPets(int page, int size, String query, String status, String species, Long storeId, Long customerId, String sort) { + return executeCall(petApi.getAllPets(page, size, query, status, species, storeId, customerId, sort)); } /** diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/StoreRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/StoreRepository.java index 44781a32..9aa4aa1e 100644 --- a/android/app/src/main/java/com/example/petstoremobile/repositories/StoreRepository.java +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/StoreRepository.java @@ -3,10 +3,13 @@ package com.example.petstoremobile.repositories; import androidx.lifecycle.LiveData; import com.example.petstoremobile.api.StoreApi; +import com.example.petstoremobile.dtos.DropdownDTO; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.utils.Resource; +import java.util.List; + import javax.inject.Inject; import javax.inject.Singleton; @@ -26,4 +29,11 @@ public class StoreRepository extends BaseRepository { public LiveData>> getAllStores(int page, int size) { return executeCall(storeApi.getAllStores(page, size)); } + + /** + * Retrieves a list of employees for a specific store from the dropdowns API. + */ + public LiveData>> getStoreEmployees(Long storeId) { + return executeCall(storeApi.getStoreEmployees(storeId)); + } } diff --git a/android/app/src/main/java/com/example/petstoremobile/utils/UIUtils.java b/android/app/src/main/java/com/example/petstoremobile/utils/UIUtils.java index 399cabc4..05f6ac18 100644 --- a/android/app/src/main/java/com/example/petstoremobile/utils/UIUtils.java +++ b/android/app/src/main/java/com/example/petstoremobile/utils/UIUtils.java @@ -25,7 +25,7 @@ public class UIUtils { } /** - * Sets up a toggle for a filter layout, including icon changes and field resets. + * Sets up a toggle for a filter layout, including icon changes. */ public static void setupFilterToggle(ImageButton btnToggle, View layoutFilter, EditText etSearch, Spinner... spinners) { btnToggle.setOnClickListener(v -> { @@ -36,13 +36,6 @@ public class UIUtils { 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); - } - } }); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java index c75926a7..6b5f6025 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetViewModel.java @@ -28,8 +28,8 @@ public class PetViewModel extends ViewModel { /** * Fetches a paginated list of pets with filters. */ - public LiveData>> getAllPets(int page, int size, String query, String status, String species, Long storeId, String sort) { - return repository.getAllPets(page, size, query, status, species, storeId, sort); + public LiveData>> getAllPets(int page, int size, String query, String status, String species, Long storeId, Long customerId, String sort) { + return repository.getAllPets(page, size, query, status, species, storeId, customerId, sort); } /** diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StoreViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StoreViewModel.java index 83f4c3b3..5fac8de4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StoreViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StoreViewModel.java @@ -3,11 +3,14 @@ package com.example.petstoremobile.viewmodels; import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModel; +import com.example.petstoremobile.dtos.DropdownDTO; import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.repositories.StoreRepository; import com.example.petstoremobile.utils.Resource; +import java.util.List; + import javax.inject.Inject; import dagger.hilt.android.lifecycle.HiltViewModel; @@ -27,4 +30,11 @@ public class StoreViewModel extends ViewModel { public LiveData>> getAllStores(int page, int size) { return repository.getAllStores(page, size); } + + /** + * Fetches a list of employees for a specific store. + */ + public LiveData>> getStoreEmployees(Long storeId) { + return repository.getStoreEmployees(storeId); + } } diff --git a/android/app/src/main/res/layout/fragment_appointment_detail.xml b/android/app/src/main/res/layout/fragment_appointment_detail.xml index 1f31bc54..f8a87fa0 100644 --- a/android/app/src/main/res/layout/fragment_appointment_detail.xml +++ b/android/app/src/main/res/layout/fragment_appointment_detail.xml @@ -69,6 +69,7 @@ { "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + "(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " + "(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status)) AND " + - "(:storeId IS NULL OR p.store.storeId = :storeId)") - Page searchPets(@Param("q") String query, @Param("species") String species, @Param("status") String status, @Param("storeId") Long storeId, Pageable pageable); + "(:storeId IS NULL OR p.store.storeId = :storeId) AND " + + "(:customerId IS NULL OR p.owner.id = :customerId)") + Page searchPets(@Param("q") String query, @Param("species") String species, @Param("status") String status, @Param("storeId") Long storeId, @Param("customerId") Long customerId, Pageable pageable); @Query("SELECT p FROM Pet p WHERE LOWER(p.petStatus) = 'available' AND " + "(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.petBreed, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " + diff --git a/backend/src/main/java/com/petshop/backend/service/PetService.java b/backend/src/main/java/com/petshop/backend/service/PetService.java index e35deb7d..0051d893 100644 --- a/backend/src/main/java/com/petshop/backend/service/PetService.java +++ b/backend/src/main/java/com/petshop/backend/service/PetService.java @@ -48,7 +48,7 @@ public class PetService { } @Transactional(readOnly = true) - public Page getAllPets(String query, String species, String status, Long storeId, Pageable pageable) { + public Page getAllPets(String query, String species, String status, Long storeId, Long customerId, Pageable pageable) { String normalizedQuery = normalizeFilter(query); String normalizedSpecies = normalizeFilter(species); String normalizedStatus = normalizeFilter(status); @@ -61,7 +61,7 @@ public class PetService { } pets = petRepository.searchPublicPets(normalizedQuery, normalizedSpecies, storeId, pageable); } else if (viewer.role() == User.Role.STAFF || viewer.role() == User.Role.ADMIN) { - pets = petRepository.searchPets(normalizedQuery, normalizedSpecies, normalizedStatus, storeId, pageable); + pets = petRepository.searchPets(normalizedQuery, normalizedSpecies, normalizedStatus, storeId, customerId, pageable); } else if (viewer.role() == User.Role.CUSTOMER) { if (!isAllowedCustomerStatus(normalizedStatus)) { return new PageImpl<>(java.util.List.of(), pageable, 0);