making appointment userfrendly part1 andriod

This commit is contained in:
Alex
2026-04-08 16:53:42 -06:00
parent fb7c4c66ef
commit 6f11f4ebbb
17 changed files with 151 additions and 70 deletions

View File

@@ -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
);

View File

@@ -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<PageResponse<StoreDTO>> getAllStores(
@Query("page") int page,
@Query("size") int size);
@GET("api/v1/dropdowns/stores/{storeId}/employees")
Call<List<DropdownDTO>> getStoreEmployees(@Path("storeId") Long storeId);
}

View File

@@ -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;
}
}

View File

@@ -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();
}
});
}
/**

View File

@@ -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();
}
});
}
/**

View File

@@ -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) {

View File

@@ -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();

View File

@@ -47,7 +47,7 @@ public class AppointmentDetailFragment extends Fragment {
private List<ServiceDTO> serviceList = new ArrayList<>();
private List<CustomerDTO> customerList = new ArrayList<>();
private List<StoreDTO> storeList = new ArrayList<>();
private List<UserDTO> staffList = new ArrayList<>();
private List<DropdownDTO> 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);
}
}

View File

@@ -26,8 +26,8 @@ public class PetRepository extends BaseRepository {
/**
* Retrieves a paginated list of pets from the API with optional filters.
*/
public LiveData<Resource<PageResponse<PetDTO>>> 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<Resource<PageResponse<PetDTO>>> 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));
}
/**

View File

@@ -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<Resource<PageResponse<StoreDTO>>> 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<Resource<List<DropdownDTO>>> getStoreEmployees(Long storeId) {
return executeCall(storeApi.getStoreEmployees(storeId));
}
}

View File

@@ -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);
}
}
});
}

View File

@@ -28,8 +28,8 @@ public class PetViewModel extends ViewModel {
/**
* Fetches a paginated list of pets with filters.
*/
public LiveData<Resource<PageResponse<PetDTO>>> 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<Resource<PageResponse<PetDTO>>> 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);
}
/**

View File

@@ -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<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
return repository.getAllStores(page, size);
}
/**
* Fetches a list of employees for a specific store.
*/
public LiveData<Resource<List<DropdownDTO>>> getStoreEmployees(Long storeId) {
return repository.getStoreEmployees(storeId);
}
}

View File

@@ -69,6 +69,7 @@
<!-- Customer -->
<TextView
android:id="@+id/tvLabelCustomer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Customer"
@@ -84,6 +85,7 @@
<!-- Store -->
<TextView
android:id="@+id/tvLabelStore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Store"
@@ -100,7 +102,8 @@
<!-- Pet -->
<TextView
android:layout_width="wrap_content"
android:id="@+id/tvLabelPet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Pet"
android:textColor="@color/text_dark"
@@ -117,6 +120,7 @@
<!-- Service -->
<TextView
android:id="@+id/tvLabelService"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Service"