From b4c1175ee15c3be71a7e2aa6283efefecbdc8cf5 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 7 Apr 2026 07:27:37 -0600 Subject: [PATCH 1/2] fixed creating adoption for the backend and implemented adoption to andriod for changes --- .../adapters/AdoptionAdapter.java | 2 +- .../petstoremobile/dtos/AdoptionDTO.java | 21 +++++++++-- .../AdoptionDetailFragment.java | 36 ++++++++++++++++++- .../res/layout/fragment_adoption_detail.xml | 16 ++++++++- .../controller/AdoptionController.java | 8 +++-- .../repository/AdoptionRepository.java | 26 +++++++------- .../backend/service/AdoptionService.java | 32 +++++++++-------- 7 files changed, 105 insertions(+), 36 deletions(-) diff --git a/android/app/src/main/java/com/example/petstoremobile/adapters/AdoptionAdapter.java b/android/app/src/main/java/com/example/petstoremobile/adapters/AdoptionAdapter.java index c6bd678c..6dd8eeb4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/adapters/AdoptionAdapter.java +++ b/android/app/src/main/java/com/example/petstoremobile/adapters/AdoptionAdapter.java @@ -57,7 +57,7 @@ public class AdoptionAdapter extends RecyclerView.Adapter petList = new ArrayList<>(); private List customerList = new ArrayList<>(); + private List storeList = new ArrayList<>(); - private final String[] STATUSES = {"Pending", "Approved", "Rejected"}; + private final String[] STATUSES = {"Pending", "Completed", "Cancelled"}; private AdoptionViewModel adoptionViewModel; private PetViewModel petViewModel; private CustomerViewModel customerViewModel; + private StoreViewModel storeViewModel; @Override public void onCreate(Bundle savedInstanceState) { @@ -51,6 +55,7 @@ public class AdoptionDetailFragment extends Fragment { adoptionViewModel = new ViewModelProvider(this).get(AdoptionViewModel.class); petViewModel = new ViewModelProvider(this).get(PetViewModel.class); customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class); + storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class); } @Override @@ -107,6 +112,7 @@ public class AdoptionDetailFragment extends Fragment { private void loadSpinnersData() { loadPets(); loadCustomers(); + loadStores(); } /** @@ -152,6 +158,27 @@ public class AdoptionDetailFragment extends Fragment { preselectedCustomerId, CustomerDTO::getCustomerId); } + /** + * Loads the list of stores from the API. + */ + private void loadStores() { + storeViewModel.getAllStores(0, 200).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + storeList = resource.data.getContent(); + refreshStoreSpinner(); + } + }); + } + + /** + * Populates the store selection spinner with data. + */ + private void refreshStoreSpinner() { + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, storeList, + StoreDTO::getStoreName, "-- Select Store --", + preselectedStoreId, StoreDTO::getStoreId); + } + /** * Handles arguments to determine if the fragment is in edit or add mode. */ @@ -182,11 +209,13 @@ public class AdoptionDetailFragment extends Fragment { AdoptionDTO a = resource.data; preselectedPetId = a.getPetId() != null ? a.getPetId() : -1; preselectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1; + preselectedStoreId = a.getSourceStoreId() != null ? a.getSourceStoreId() : -1; binding.etAdoptionDate.setText(a.getAdoptionDate()); SpinnerUtils.setSelectionByValue(binding.spinnerAdoptionStatus, a.getAdoptionStatus()); refreshPetSpinner(); refreshCustomerSpinner(); + refreshStoreSpinner(); } else if (resource.status == Resource.Status.ERROR) { Toast.makeText(getContext(), "Failed to load adoption: " + resource.message, Toast.LENGTH_SHORT).show(); } @@ -203,6 +232,9 @@ public class AdoptionDetailFragment extends Fragment { if (binding.spinnerAdoptionPet.getSelectedItemPosition() == 0) { Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return; } + if (binding.spinnerAdoptionStore.getSelectedItemPosition() == 0) { + Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return; + } String date = binding.etAdoptionDate.getText().toString().trim(); if (date.isEmpty()) { Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return; @@ -210,11 +242,13 @@ public class AdoptionDetailFragment extends Fragment { CustomerDTO customer = customerList.get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1); PetDTO pet = petList.get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1); + StoreDTO store = storeList.get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1); String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()]; AdoptionDTO dto = new AdoptionDTO( pet.getPetId(), customer.getCustomerId(), + store.getStoreId(), date, status ); diff --git a/android/app/src/main/res/layout/fragment_adoption_detail.xml b/android/app/src/main/res/layout/fragment_adoption_detail.xml index 2f6b153f..0ad14fcf 100644 --- a/android/app/src/main/res/layout/fragment_adoption_detail.xml +++ b/android/app/src/main/res/layout/fragment_adoption_detail.xml @@ -95,6 +95,20 @@ android:layout_height="wrap_content" android:layout_marginBottom="16dp"/> + + + + - \ No newline at end of file + diff --git a/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java b/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java index bcb61db4..0e833dd5 100644 --- a/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java +++ b/backend/src/main/java/com/petshop/backend/controller/AdoptionController.java @@ -33,6 +33,8 @@ public class AdoptionController { @PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')") public ResponseEntity> getAllAdoptions( @RequestParam(required = false) String q, + @RequestParam(required = false) Long customerId, + @RequestParam(required = false) String status, Pageable pageable) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String role = authentication.getAuthorities().stream() @@ -40,13 +42,13 @@ public class AdoptionController { .map(authority -> authority.getAuthority().replace("ROLE_", "")) .orElse(null); - Long customerId = null; + Long effectiveCustomerId = customerId; if (role != null && role.equals("CUSTOMER")) { User user = AuthenticationHelper.getAuthenticatedUser(userRepository); - customerId = user.getId(); + effectiveCustomerId = user.getId(); } - return ResponseEntity.ok(adoptionService.getAllAdoptions(q, pageable, customerId)); + return ResponseEntity.ok(adoptionService.getAllAdoptions(q, effectiveCustomerId, status, pageable)); } @GetMapping("/{id}") diff --git a/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java b/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java index 7502ec33..e3c21776 100644 --- a/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java +++ b/backend/src/main/java/com/petshop/backend/repository/AdoptionRepository.java @@ -14,22 +14,24 @@ import java.util.Optional; public interface AdoptionRepository extends JpaRepository { @Query("SELECT a FROM Adoption a WHERE " + + "(:q IS NULL OR (" + "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%'))") - Page searchAdoptions(@Param("q") String query, Pageable pageable); - - Page findByCustomerId(Long customerId, Pageable pageable); - - @Query("SELECT a FROM Adoption a WHERE a.customer.id = :customerId AND (" + - "LOWER(a.customer.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(a.customer.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " + - "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%')))") - Page searchAdoptionsByCustomer(@Param("customerId") Long customerId, @Param("q") String query, Pageable pageable); + "LOWER(a.pet.petName) LIKE LOWER(CONCAT('%', :q, '%'))" + + ")) AND " + + "(:customerId IS NULL OR a.customer.id = :customerId) AND " + + "(:status IS NULL OR LOWER(a.adoptionStatus) = LOWER(:status))") + Page searchAdoptions( + @Param("q") String query, + @Param("customerId") Long customerId, + @Param("status") String status, + Pageable pageable); Optional findFirstByPet_IdAndAdoptionStatusOrderByAdoptionDateDesc(Long petId, String adoptionStatus); - boolean existsByPetPetIdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(Long petId, String adoptionStatus, Long adoptionId); + @Query("SELECT CASE WHEN COUNT(a) > 0 THEN true ELSE false END FROM Adoption a WHERE a.pet.id = :petId AND LOWER(a.adoptionStatus) = LOWER(:adoptionStatus) AND a.adoptionId <> :adoptionId") + boolean existsByPetPetIdAndAdoptionStatusIgnoreCaseAndAdoptionIdNot(@Param("petId") Long petId, @Param("adoptionStatus") String adoptionStatus, @Param("adoptionId") Long adoptionId); - boolean existsByPetPetIdAndAdoptionStatusIgnoreCase(Long petId, String adoptionStatus); + @Query("SELECT CASE WHEN COUNT(a) > 0 THEN true ELSE false END FROM Adoption a WHERE a.pet.id = :petId AND LOWER(a.adoptionStatus) = LOWER(:adoptionStatus)") + boolean existsByPetPetIdAndAdoptionStatusIgnoreCase(@Param("petId") Long petId, @Param("adoptionStatus") String adoptionStatus); } diff --git a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java index 10b3fd02..ec9ef393 100644 --- a/backend/src/main/java/com/petshop/backend/service/AdoptionService.java +++ b/backend/src/main/java/com/petshop/backend/service/AdoptionService.java @@ -38,22 +38,16 @@ public class AdoptionService { this.storeRepository = storeRepository; } - public Page getAllAdoptions(String query, Pageable pageable, Long customerId) { - Page adoptions; + public Page getAllAdoptions(String query, Long customerId, String status, Pageable pageable) { + String normalizedQuery = normalizeFilter(query); + String normalizedStatus = normalizeFilter(status); - if (customerId != null) { - if (query != null && !query.trim().isEmpty()) { - adoptions = adoptionRepository.searchAdoptionsByCustomer(customerId, query, pageable); - } else { - adoptions = adoptionRepository.findByCustomerId(customerId, pageable); - } - } else { - if (query != null && !query.trim().isEmpty()) { - adoptions = adoptionRepository.searchAdoptions(query, pageable); - } else { - adoptions = adoptionRepository.findAll(pageable); - } - } + Page adoptions = adoptionRepository.searchAdoptions( + normalizedQuery, + customerId, + normalizedStatus, + pageable + ); return adoptions.map(this::mapToResponse); } @@ -140,6 +134,14 @@ public class AdoptionService { adoptionRepository.deleteAllById(request.getIds()); } + private String normalizeFilter(String value) { + if (value == null) { + return null; + } + String trimmed = value.trim(); + return trimmed.isEmpty() ? null : trimmed; + } + private AdoptionResponse mapToResponse(Adoption adoption) { StoreLocation sourceStore = adoption.getSourceStore(); return new AdoptionResponse( From 8261cdfc2d847c06d9f2fd281954234514923699 Mon Sep 17 00:00:00 2001 From: Alex <78383757+Lextical@users.noreply.github.com> Date: Tue, 7 Apr 2026 07:46:22 -0600 Subject: [PATCH 2/2] added an api connection to Users in Andriod NOTE Will have to change backend so staffs can access other staffs --- .../example/petstoremobile/api/UserApi.java | 13 +++++++ .../petstoremobile/di/NetworkModule.java | 8 ++++- .../AppointmentDetailFragment.java | 35 +++++++++++++++++++ .../repositories/UserRepository.java | 26 ++++++++++++++ .../viewmodels/UserViewModel.java | 27 ++++++++++++++ .../layout/fragment_appointment_detail.xml | 18 ++++++++-- 6 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 android/app/src/main/java/com/example/petstoremobile/api/UserApi.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/repositories/UserRepository.java create mode 100644 android/app/src/main/java/com/example/petstoremobile/viewmodels/UserViewModel.java diff --git a/android/app/src/main/java/com/example/petstoremobile/api/UserApi.java b/android/app/src/main/java/com/example/petstoremobile/api/UserApi.java new file mode 100644 index 00000000..53611e66 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/api/UserApi.java @@ -0,0 +1,13 @@ +package com.example.petstoremobile.api; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.UserDTO; + +import retrofit2.Call; +import retrofit2.http.GET; +import retrofit2.http.Query; + +public interface UserApi { + @GET("api/v1/users") + Call> getUsers(@Query("role") String role, @Query("page") int page, @Query("size") int size); +} diff --git a/android/app/src/main/java/com/example/petstoremobile/di/NetworkModule.java b/android/app/src/main/java/com/example/petstoremobile/di/NetworkModule.java index 0311840d..b5f1ca64 100644 --- a/android/app/src/main/java/com/example/petstoremobile/di/NetworkModule.java +++ b/android/app/src/main/java/com/example/petstoremobile/di/NetworkModule.java @@ -173,4 +173,10 @@ public class NetworkModule { public static CategoryApi provideCategoryApi(Retrofit retrofit) { return retrofit.create(CategoryApi.class); } -} \ No newline at end of file + + @Provides + @Singleton + public static UserApi provideUserApi(Retrofit retrofit) { + return retrofit.create(UserApi.class); + } +} 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 cf9bb837..7757f156 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 @@ -21,6 +21,7 @@ import com.example.petstoremobile.viewmodels.CustomerViewModel; import com.example.petstoremobile.viewmodels.PetViewModel; import com.example.petstoremobile.viewmodels.ServiceViewModel; import com.example.petstoremobile.viewmodels.StoreViewModel; +import com.example.petstoremobile.viewmodels.UserViewModel; import java.util.*; @@ -40,11 +41,13 @@ public class AppointmentDetailFragment extends Fragment { private long preselectedServiceId = -1; private long preselectedCustomerId = -1; private long preselectedStoreId = -1; + private long preselectedStaffId = -1; private List petList = new ArrayList<>(); private List serviceList = new ArrayList<>(); private List customerList = new ArrayList<>(); private List storeList = 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}; @@ -54,6 +57,7 @@ public class AppointmentDetailFragment extends Fragment { private ServiceViewModel serviceViewModel; private StoreViewModel storeViewModel; private CustomerViewModel customerViewModel; + private UserViewModel userViewModel; @Override public void onCreate(Bundle savedInstanceState) { @@ -63,6 +67,7 @@ public class AppointmentDetailFragment extends Fragment { serviceViewModel = new ViewModelProvider(this).get(ServiceViewModel.class); storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class); customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class); + userViewModel = new ViewModelProvider(this).get(UserViewModel.class); } @Override @@ -128,6 +133,7 @@ public class AppointmentDetailFragment extends Fragment { loadServices(); loadCustomers(); loadStores(); + loadStaff(); } /** @@ -215,6 +221,27 @@ public class AppointmentDetailFragment extends Fragment { preselectedStoreId, StoreDTO::getStoreId); } + /** + * Loads the list of staff from the API. + */ + private void loadStaff() { + userViewModel.getUsers("STAFF", 0, 100).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + staffList = resource.data.getContent(); + refreshStaffSpinner(); + } + }); + } + + /** + * Populates the staff selection spinner. + */ + private void refreshStaffSpinner() { + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, staffList, + UserDTO::getFullName, "-- Select Staff --", + preselectedStaffId, UserDTO::getId); + } + /** * Handles arguments to determine if the fragment is in edit or add mode. */ @@ -247,6 +274,7 @@ public class AppointmentDetailFragment extends Fragment { preselectedServiceId = (a.getServiceId() != null) ? a.getServiceId() : -1; preselectedCustomerId = (a.getCustomerId() != null) ? a.getCustomerId() : -1; preselectedStoreId = (a.getStoreId() != null) ? a.getStoreId() : -1; + preselectedStaffId = (a.getEmployeeId() != null) ? a.getEmployeeId() : -1; binding.etAppointmentDate.setText(a.getAppointmentDate()); @@ -276,6 +304,7 @@ public class AppointmentDetailFragment extends Fragment { refreshServiceSpinner(); refreshCustomerSpinner(); refreshStoreSpinner(); + refreshStaffSpinner(); } else if (resource.status == Resource.Status.ERROR) { Toast.makeText(getContext(), "Failed to load appointment: " + resource.message, Toast.LENGTH_SHORT).show(); } @@ -307,6 +336,11 @@ public class AppointmentDetailFragment extends Fragment { StoreDTO store = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1); PetDTO pet = petList.get(binding.spinnerPet.getSelectedItemPosition() - 1); ServiceDTO service = serviceList.get(binding.spinnerService.getSelectedItemPosition() - 1); + + Long employeeId = null; + if (binding.spinnerStaff.getSelectedItemPosition() > 0) { + employeeId = staffList.get(binding.spinnerStaff.getSelectedItemPosition() - 1).getId(); + } String time = String.format("%02d:%02d", HOURS[binding.spinnerHour.getSelectedItemPosition()], @@ -346,6 +380,7 @@ public class AppointmentDetailFragment extends Fragment { customer.getCustomerId(), store.getStoreId(), service.getServiceId(), + employeeId, date, time, status, diff --git a/android/app/src/main/java/com/example/petstoremobile/repositories/UserRepository.java b/android/app/src/main/java/com/example/petstoremobile/repositories/UserRepository.java new file mode 100644 index 00000000..0ed9ced9 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/repositories/UserRepository.java @@ -0,0 +1,26 @@ +package com.example.petstoremobile.repositories; + +import androidx.lifecycle.LiveData; + +import com.example.petstoremobile.api.UserApi; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.UserDTO; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class UserRepository extends BaseRepository { + private final UserApi userApi; + + @Inject + public UserRepository(UserApi userApi) { + super("UserRepository"); + this.userApi = userApi; + } + + public LiveData>> getUsers(String role, int page, int size) { + return executeCall(userApi.getUsers(role, page, size)); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/UserViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/UserViewModel.java new file mode 100644 index 00000000..d839f6c4 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/UserViewModel.java @@ -0,0 +1,27 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.UserDTO; +import com.example.petstoremobile.repositories.UserRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class UserViewModel extends ViewModel { + private final UserRepository userRepository; + + @Inject + public UserViewModel(UserRepository userRepository) { + this.userRepository = userRepository; + } + + public LiveData>> getUsers(String role, int page, int size) { + return userRepository.getUsers(role, page, size); + } +} 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 ef1d8b33..1f31bc54 100644 --- a/android/app/src/main/res/layout/fragment_appointment_detail.xml +++ b/android/app/src/main/res/layout/fragment_appointment_detail.xml @@ -124,15 +124,27 @@ android:textSize="12sp" android:layout_marginBottom="4dp"/> - - - + + + + +