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 24250c4c..d13a5ed4 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 @@ -44,6 +44,9 @@ public interface PetApi { @GET("api/v1/dropdowns/adoption-pets") Call> getAdoptionPets(); + @GET("api/v1/dropdowns/pets") + Call> getPetDropdowns(); + // Get pet by id @GET("api/v1/pets/{id}") Call getPetById(@Path("id") Long id); 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 0e936edf..3c63819d 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 @@ -54,7 +54,9 @@ public class AdoptionDetailFragment extends Fragment { setupSpinners(); setupDatePicker(); observeViewModel(); - viewModel.loadInitialFormData(); + Bundle args = getArguments(); + boolean isEditing = args != null && args.containsKey("adoptionId"); + viewModel.loadInitialFormData(isEditing); handleArguments(); binding.btnAdoptionBack.setOnClickListener(v -> navigateBack()); 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 bb2b9d58..adfea86c 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 @@ -25,6 +25,8 @@ import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.UIUtils; import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel; +import java.util.List; + import dagger.hilt.android.AndroidEntryPoint; /** @@ -35,12 +37,6 @@ public class AppointmentDetailFragment extends Fragment { private FragmentAppointmentDetailBinding binding; - private long preselectedPetId = -1; - private long preselectedServiceId = -1; - private long preselectedCustomerId = -1; - private long preselectedStoreId = -1; - private long preselectedStaffId = -1; - private final Integer[] HOURS = {9, 10, 11, 12, 13, 14, 15, 16, 17}; private final Integer[] MINUTES = {0, 15, 30, 45}; @@ -118,27 +114,42 @@ public class AppointmentDetailFragment extends Fragment { } private void setupDatePicker() { - binding.etAppointmentDate.setOnClickListener(v -> + binding.etAppointmentDate.setOnClickListener(v -> UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange)); } private void observeViewModel() { viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); - - viewModel.getCustomers().observe(getViewLifecycleOwner(), list -> - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, list, DropdownDTO::getLabel, "-- Select Customer --", preselectedCustomerId, DropdownDTO::getId)); - - viewModel.getStores().observe(getViewLifecycleOwner(), list -> - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId)); - - viewModel.getServices().observe(getViewLifecycleOwner(), list -> - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, list, ServiceDTO::getServiceName, "-- Select Service --", preselectedServiceId, ServiceDTO::getServiceId)); - viewModel.getCustomerPets().observe(getViewLifecycleOwner(), list -> - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, list, DropdownDTO::getLabel, "-- Select Pet --", preselectedPetId, DropdownDTO::getId)); + viewModel.getCustomers().observe(getViewLifecycleOwner(), list -> { + AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue(); + Long id = state != null ? state.selectedCustomerId : null; + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, list, DropdownDTO::getLabel, "-- Select Customer --", id, DropdownDTO::getId); + }); - viewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list -> - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, list, DropdownDTO::getLabel, "-- Select Staff --", preselectedStaffId, DropdownDTO::getId)); + viewModel.getStores().observe(getViewLifecycleOwner(), list -> { + AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue(); + Long id = state != null ? state.selectedStoreId : null; + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", id, DropdownDTO::getId); + }); + + viewModel.getServices().observe(getViewLifecycleOwner(), list -> { + AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue(); + Long id = state != null ? state.selectedServiceId : null; + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, list, ServiceDTO::getServiceName, "-- Select Service --", id, ServiceDTO::getServiceId); + }); + + viewModel.getCustomerPets().observe(getViewLifecycleOwner(), list -> { + AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue(); + Long id = state != null ? state.selectedPetId : null; + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, list, DropdownDTO::getLabel, "-- Select Pet --", id, DropdownDTO::getId); + }); + + viewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list -> { + AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue(); + Long id = state != null ? state.selectedStaffId : null; + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, list, DropdownDTO::getLabel, "-- Select Staff --", id, DropdownDTO::getId); + }); } private void setLoading(boolean loading) { @@ -155,7 +166,7 @@ public class AppointmentDetailFragment extends Fragment { binding.tvAppointmentId.setVisibility(state.isEditing ? View.VISIBLE : View.GONE); binding.btnDeleteAppointment.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE); binding.btnSaveAppointment.setVisibility(state.isSaveVisible ? View.VISIBLE : View.GONE); - + UIUtils.setFieldEnabled(state.isCustomerEnabled, binding.spinnerCustomer, binding.tvLabelCustomer); UIUtils.setFieldEnabled(state.isStoreEnabled, binding.spinnerStore, binding.tvLabelStore); UIUtils.setFieldEnabled(state.isPetEnabled, binding.spinnerPet, binding.tvLabelPet); @@ -171,6 +182,27 @@ public class AppointmentDetailFragment extends Fragment { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, state.availableStatuses); SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, current); + // Re-populate dropdown spinners with current selected IDs from ViewState + List customers = viewModel.getCustomers().getValue(); + if (customers != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, + customers, DropdownDTO::getLabel, "-- Select Customer --", state.selectedCustomerId, DropdownDTO::getId); + + List stores = viewModel.getStores().getValue(); + if (stores != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, + stores, DropdownDTO::getLabel, "-- Select Store --", state.selectedStoreId, DropdownDTO::getId); + + List services = viewModel.getServices().getValue(); + if (services != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, + services, ServiceDTO::getServiceName, "-- Select Service --", state.selectedServiceId, ServiceDTO::getServiceId); + + List pets = viewModel.getCustomerPets().getValue(); + if (pets != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, + pets, DropdownDTO::getLabel, "-- Select Pet --", state.selectedPetId, DropdownDTO::getId); + + List staff = viewModel.getStoreEmployees().getValue(); + if (staff != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, + staff, DropdownDTO::getLabel, "-- Select Staff --", state.selectedStaffId, DropdownDTO::getId); + isUpdatingUI = false; } @@ -200,20 +232,15 @@ public class AppointmentDetailFragment extends Fragment { setLoading(resource.status == Resource.Status.LOADING); if (resource.status == Resource.Status.SUCCESS && resource.data != null) { AppointmentDTO a = resource.data; - preselectedPetId = a.getPetId() != null ? a.getPetId() : -1; - 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()); parseAndSetTimeSpinners(a.getAppointmentTime() != null ? a.getAppointmentTime() : "09:00"); - String status = a.getAppointmentStatus(); if (status != null && !status.isEmpty()) { SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, DateTimeUtils.formatStatusFromBackend(status)); } notifyDateTimeStatusChange(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Failed to load appointment", Toast.LENGTH_SHORT).show(); } }); } 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 623a4daa..df7e4e5d 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 @@ -47,6 +47,13 @@ public class PetRepository extends BaseRepository { return executeCall(petApi.getAdoptionPets()); } + /** + * Retrieves all pets from the dropdowns API. + */ + public LiveData>> getPetDropdowns() { + return executeCall(petApi.getPetDropdowns()); + } + /** * Retrieves a specific pet by its ID from the API. */ diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java index 6a8ae492..a744f92c 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java @@ -69,12 +69,12 @@ public class AdoptionDetailViewModel extends ViewModel { state.isAdoptionIdVisible = isEditing; state.isDeleteVisible = isEditing; state.isPetEnabled = isEditing; - state.isEmployeeEnabled = isEditing; + state.isEmployeeEnabled = false; }); } - public void loadInitialFormData() { - petRepository.getAdoptionPets().observeForever(r -> { + public void loadInitialFormData(boolean isEditing) { + (isEditing ? petRepository.getPetDropdowns() : petRepository.getAdoptionPets()).observeForever(r -> { if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) { petList.setValue(r.data); } diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java index 685b645a..e05d261a 100644 --- a/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AppointmentDetailViewModel.java @@ -41,6 +41,7 @@ public class AppointmentDetailViewModel extends ViewModel { private final MutableLiveData viewState = new MutableLiveData<>(new ViewState()); private long appointmentId = -1; + private boolean isOriginallyCancel = false; private Long currentCustomerId; private Long currentStoreId; private Long currentPetId; @@ -229,6 +230,7 @@ public class AppointmentDetailViewModel extends ViewModel { repository.getAppointmentById(appointmentId).observeForever(resource -> { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { AppointmentDTO a = resource.data; + isOriginallyCancel = "CANCELLED".equalsIgnoreCase(a.getAppointmentStatus()); currentCustomerId = a.getCustomerId(); currentStoreId = a.getStoreId(); currentPetId = a.getPetId(); @@ -279,9 +281,8 @@ public class AppointmentDetailViewModel extends ViewModel { updateViewState(s -> { s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus); boolean isPast = DateTimeUtils.isDateTimeInPast(date, time); - boolean isCancelled = "Cancelled".equalsIgnoreCase(currentStatus); - if (isCancelled) { + if (isOriginallyCancel) { s.isPast = true; setAllFieldsEnabled(s, false); s.isStatusEnabled = false; @@ -311,7 +312,7 @@ public class AppointmentDetailViewModel extends ViewModel { private String[] calculateAvailableStatuses(boolean isEditing, String date, String currentTime, String currentStatus) { if (!isEditing) return new String[]{"Booked"}; if (date == null || date.isEmpty()) return new String[]{}; - if ("Cancelled".equalsIgnoreCase(currentStatus)) return new String[]{"Cancelled"}; + if (isOriginallyCancel) return new String[]{"Cancelled"}; if (DateTimeUtils.isDateTimeInPast(date, currentTime)) return new String[]{"Completed", "Missed"}; return new String[]{"Booked", "Cancelled"}; }