Added so adoption status can be missed and fixed adoption bugs for andriod
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import okhttp3.MultipartBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ProductApi {
|
||||
String PRODUCT_IMAGE_PATH = "api/v1/products/%d/image";
|
||||
|
||||
@@ -35,4 +38,10 @@ public interface ProductApi {
|
||||
|
||||
@DELETE("api/v1/products/{id}/image")
|
||||
Call<Void> deleteProductImage(@Path("id") Long id);
|
||||
|
||||
@GET("api/v1/dropdowns/products")
|
||||
Call<List<DropdownDTO>> getProductDropdowns();
|
||||
|
||||
@GET("api/v1/dropdowns/categories")
|
||||
Call<List<DropdownDTO>> getCategoryDropdowns();
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import android.view.ViewGroup;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.ProductAdapter;
|
||||
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
@@ -74,7 +74,7 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
||||
|
||||
viewModel.getCategories().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCategory, list,
|
||||
CategoryDTO::getCategoryName, "All Categories", -1L, CategoryDTO::getCategoryId);
|
||||
DropdownDTO::getLabel, "All Categories", -1L, DropdownDTO::getId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
@@ -111,9 +111,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long categoryId = null;
|
||||
List<CategoryDTO> categories = viewModel.getCategories().getValue();
|
||||
List<DropdownDTO> categories = viewModel.getCategories().getValue();
|
||||
if (binding.spinnerCategory.getSelectedItemPosition() > 0 && categories != null && !categories.isEmpty()) {
|
||||
categoryId = categories.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getCategoryId();
|
||||
categoryId = categories.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getId();
|
||||
}
|
||||
|
||||
viewModel.loadProducts(query, categoryId);
|
||||
|
||||
@@ -32,8 +32,7 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
|
||||
private FragmentAdoptionDetailBinding binding;
|
||||
private AdoptionDetailViewModel viewModel;
|
||||
|
||||
private final String[] STATUSES = {"Pending", "Completed", "Cancelled"};
|
||||
private boolean isUpdatingUI = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@@ -109,9 +108,7 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void setupSpinners() {
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, STATUSES);
|
||||
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, new String[]{});
|
||||
|
||||
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
@@ -130,10 +127,22 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAdoptionPet, p -> viewModel.onPetSelected(p));
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAdoptionStatus, p -> notifyDateStatusChange());
|
||||
}
|
||||
|
||||
private void setupDatePicker() {
|
||||
binding.etAdoptionDate.setOnClickListener(v -> UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, null));
|
||||
binding.etAdoptionDate.setOnClickListener(v ->
|
||||
UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, this::notifyDateStatusChange));
|
||||
}
|
||||
|
||||
private void notifyDateStatusChange() {
|
||||
if (isUpdatingUI) return;
|
||||
String date = binding.etAdoptionDate.getText().toString();
|
||||
Object selected = binding.spinnerAdoptionStatus.getSelectedItem();
|
||||
String status = selected != null ? selected.toString() : "";
|
||||
viewModel.onDateChanged(date, status);
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
@@ -157,14 +166,25 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void applyViewState(AdoptionDetailViewModel.ViewState state) {
|
||||
isUpdatingUI = true;
|
||||
|
||||
binding.tvAdoptionMode.setText(state.modeTitle);
|
||||
binding.tvAdoptionId.setText(DateTimeUtils.formatId(viewModel.getAdoptionId()));
|
||||
binding.tvAdoptionId.setVisibility(state.isAdoptionIdVisible ? View.VISIBLE : View.GONE);
|
||||
binding.btnDeleteAdoption.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||
binding.btnSaveAdoption.setText(state.saveButtonText);
|
||||
binding.btnSaveAdoption.setVisibility(state.isSaveVisible ? View.VISIBLE : View.GONE);
|
||||
|
||||
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerAdoptionCustomer);
|
||||
UIUtils.setViewsEnabled(state.isPetEnabled, binding.spinnerAdoptionPet);
|
||||
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerAdoptionStore);
|
||||
UIUtils.setViewsEnabled(state.isEmployeeEnabled, binding.spinnerAdoptionEmployee);
|
||||
UIUtils.setViewsEnabled(state.isDateEnabled, binding.etAdoptionDate);
|
||||
UIUtils.setViewsEnabled(state.isFeeEnabled, binding.etAdoptionFee);
|
||||
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAdoptionStatus);
|
||||
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, state.availableStatuses);
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerAdoptionStatus, state.selectedStatus);
|
||||
|
||||
if (!state.adoptionDate.isEmpty()) {
|
||||
binding.etAdoptionDate.setText(state.adoptionDate);
|
||||
@@ -172,9 +192,6 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
if (!state.adoptionFee.isEmpty()) {
|
||||
binding.etAdoptionFee.setText(state.adoptionFee);
|
||||
}
|
||||
if (!state.adoptionStatus.isEmpty()) {
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerAdoptionStatus, state.adoptionStatus);
|
||||
}
|
||||
|
||||
// Re-populate spinners with updated preselected IDs
|
||||
List<DropdownDTO> pets = viewModel.getPetList().getValue();
|
||||
@@ -192,6 +209,8 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
List<DropdownDTO> employees = viewModel.getEmployeeList().getValue();
|
||||
if (employees != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee,
|
||||
employees, DropdownDTO::getLabel, "-- Select Staff --", state.selectedEmployeeId, DropdownDTO::getId);
|
||||
|
||||
isUpdatingUI = false;
|
||||
}
|
||||
|
||||
private void saveAdoption() {
|
||||
@@ -203,8 +222,7 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
BigDecimal fee = BigDecimal.ZERO;
|
||||
String feeStr = binding.etAdoptionFee.getText().toString().trim();
|
||||
if (!feeStr.isEmpty()) {
|
||||
if (!InputValidator.isPositiveDecimal(binding.etAdoptionFee, "Adoption Fee")) return;
|
||||
fee = new BigDecimal(feeStr);
|
||||
try { fee = new BigDecimal(feeStr); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
DropdownDTO customer = viewModel.getCustomerList().getValue().get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
||||
@@ -217,7 +235,8 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
}
|
||||
|
||||
String adoptionDate = binding.etAdoptionDate.getText().toString().trim();
|
||||
String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()];
|
||||
Object selectedStatus = binding.spinnerAdoptionStatus.getSelectedItem();
|
||||
String status = selectedStatus != null ? selectedStatus.toString().toUpperCase() : "";
|
||||
|
||||
AdoptionDTO dto = new AdoptionDTO(
|
||||
pet.getId(), customer.getId(), employeeId, store.getId(), adoptionDate, status, fee);
|
||||
|
||||
@@ -177,10 +177,8 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerMinute);
|
||||
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAppointmentStatus);
|
||||
|
||||
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
|
||||
String current = selected != null ? selected.toString() : "";
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, state.availableStatuses);
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, current);
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, state.selectedStatus);
|
||||
|
||||
// Re-populate dropdown spinners with current selected IDs from ViewState
|
||||
List<DropdownDTO> customers = viewModel.getCustomers().getValue();
|
||||
|
||||
@@ -92,7 +92,7 @@ public class InventoryDetailFragment extends Fragment {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setProductList(resource.data.getContent());
|
||||
viewModel.setProductList(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -105,8 +105,8 @@ public class InventoryDetailFragment extends Fragment {
|
||||
|
||||
private void refreshProductSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
|
||||
ProductDTO::getProdName, "-- Select Product --",
|
||||
preselectedProductId, ProductDTO::getProdId);
|
||||
DropdownDTO::getLabel, "-- Select Product --",
|
||||
preselectedProductId, DropdownDTO::getId);
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
@@ -156,9 +156,9 @@ public class InventoryDetailFragment extends Fragment {
|
||||
|
||||
int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim());
|
||||
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
|
||||
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
|
||||
DropdownDTO product = viewModel.getProductList().getValue().get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
|
||||
|
||||
InventoryDTO request = new InventoryDTO(product.getProdId(), store.getId(), quantity);
|
||||
InventoryDTO request = new InventoryDTO(product.getId(), store.getId(), quantity);
|
||||
setButtonsEnabled(false);
|
||||
|
||||
viewModel.saveInventory(request).observe(getViewLifecycleOwner(), resource -> {
|
||||
|
||||
@@ -115,7 +115,7 @@ public class ProductDetailFragment extends Fragment {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setCategoryList(resource.data.getContent());
|
||||
viewModel.setCategoryList(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -128,8 +128,8 @@ public class ProductDetailFragment extends Fragment {
|
||||
|
||||
private void updateCategorySpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
|
||||
CategoryDTO::getCategoryName, "-- Select Category --",
|
||||
preselectedCategoryId, CategoryDTO::getCategoryId);
|
||||
DropdownDTO::getLabel, "-- Select Category --",
|
||||
preselectedCategoryId, DropdownDTO::getId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -248,8 +248,8 @@ public class ProductDetailFragment extends Fragment {
|
||||
String desc = binding.etProductDesc.getText().toString().trim();
|
||||
BigDecimal price = new BigDecimal(binding.etProductPrice.getText().toString().trim());
|
||||
|
||||
CategoryDTO category = viewModel.getCategoryList().getValue().get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
|
||||
ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price);
|
||||
DropdownDTO category = viewModel.getCategoryList().getValue().get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
|
||||
ProductDTO dto = new ProductDTO(name, category.getId(), desc, price);
|
||||
|
||||
viewModel.saveProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
@@ -54,6 +54,13 @@ public class PetRepository extends BaseRepository {
|
||||
return executeCall(petApi.getPetDropdowns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves available pets for a specific store.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<PetDTO>>> getAvailablePetsByStore(Long storeId) {
|
||||
return executeCall(petApi.getAllPets(0, 200, null, "available", null, storeId, null, "petName"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific pet by its ID from the API.
|
||||
*/
|
||||
|
||||
@@ -3,10 +3,13 @@ package com.example.petstoremobile.repositories;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.example.petstoremobile.api.ProductApi;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -70,4 +73,18 @@ public class ProductRepository extends BaseRepository {
|
||||
public LiveData<Resource<Void>> deleteProductImage(Long id) {
|
||||
return executeCall(productApi.deleteProductImage(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of product dropdowns from the API.
|
||||
*/
|
||||
public LiveData<Resource<List<DropdownDTO>>> getProductDropdowns() {
|
||||
return executeCall(productApi.getProductDropdowns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of category dropdowns from the API.
|
||||
*/
|
||||
public LiveData<Resource<List<DropdownDTO>>> getCategoryDropdowns() {
|
||||
return executeCall(productApi.getCategoryDropdowns());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,31 @@ public class DateTimeUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given date is strictly before today (today and future return false).
|
||||
* format: date = "YYYY-MM-DD"
|
||||
*/
|
||||
public static boolean isDateBeforeToday(String date) {
|
||||
if (date == null || date.isEmpty()) return false;
|
||||
try {
|
||||
String[] parts = date.split("-");
|
||||
Calendar today = Calendar.getInstance();
|
||||
today.set(Calendar.HOUR_OF_DAY, 0);
|
||||
today.set(Calendar.MINUTE, 0);
|
||||
today.set(Calendar.SECOND, 0);
|
||||
today.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
Calendar selected = Calendar.getInstance();
|
||||
selected.set(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]) - 1,
|
||||
Integer.parseInt(parts[2]), 0, 0, 0);
|
||||
selected.set(Calendar.MILLISECOND, 0);
|
||||
return selected.before(today);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error parsing date: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given date and time are in the past.
|
||||
* format: date = "YYYY-MM-DD", time = "HH:MM"
|
||||
|
||||
@@ -10,10 +10,13 @@ import com.example.petstoremobile.repositories.AdoptionRepository;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -68,17 +71,30 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
state.saveButtonText = isEditing ? "Save" : "Add";
|
||||
state.isAdoptionIdVisible = isEditing;
|
||||
state.isDeleteVisible = isEditing;
|
||||
state.isPetEnabled = isEditing;
|
||||
state.isEmployeeEnabled = false;
|
||||
state.isFeeEnabled = false; // fee is always read-only
|
||||
if (!isEditing) {
|
||||
state.isCustomerEnabled = true;
|
||||
state.isStoreEnabled = true;
|
||||
state.isPetEnabled = false; // until customer selected
|
||||
state.isEmployeeEnabled = false; // until store selected
|
||||
state.isDateEnabled = true;
|
||||
state.isStatusEnabled = true;
|
||||
state.availableStatuses = new String[]{"Pending"};
|
||||
state.selectedStatus = "Pending";
|
||||
} else {
|
||||
// edit: date-based logic applied after load
|
||||
state.isCustomerEnabled = false;
|
||||
state.isStoreEnabled = false;
|
||||
state.isPetEnabled = false;
|
||||
state.isEmployeeEnabled = false;
|
||||
state.isDateEnabled = false;
|
||||
state.isStatusEnabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
// Pets are loaded dynamically based on store selection; no pre-load needed.
|
||||
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
customerList.setValue(r.data);
|
||||
@@ -95,13 +111,7 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
List<DropdownDTO> list = customerList.getValue();
|
||||
Long customerId = (position > 0 && list != null && position <= list.size())
|
||||
? list.get(position - 1).getId() : null;
|
||||
updateViewState(state -> {
|
||||
state.selectedCustomerId = customerId;
|
||||
state.isPetEnabled = customerId != null;
|
||||
if (customerId == null && !state.isEditing) {
|
||||
state.selectedPetId = null;
|
||||
}
|
||||
});
|
||||
updateViewState(state -> state.selectedCustomerId = customerId);
|
||||
}
|
||||
|
||||
public void onStoreSelected(int position) {
|
||||
@@ -110,19 +120,70 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
Long storeId = list.get(position - 1).getId();
|
||||
updateViewState(state -> {
|
||||
state.selectedStoreId = storeId;
|
||||
state.isEmployeeEnabled = true;
|
||||
if (!state.isCancelled && !state.isEditing) {
|
||||
state.isEmployeeEnabled = true;
|
||||
state.isPetEnabled = true;
|
||||
}
|
||||
});
|
||||
loadEmployeesForStore(storeId);
|
||||
if (!isEditing()) loadAvailablePetsByStore(storeId);
|
||||
} else {
|
||||
employeeList.setValue(new ArrayList<>());
|
||||
petList.setValue(new ArrayList<>());
|
||||
updateViewState(state -> {
|
||||
state.selectedStoreId = null;
|
||||
state.selectedEmployeeId = null;
|
||||
state.selectedPetId = null;
|
||||
state.isEmployeeEnabled = false;
|
||||
state.isPetEnabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void onPetSelected(int position) {
|
||||
List<DropdownDTO> list = petList.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
Long petId = list.get(position - 1).getId();
|
||||
updateViewState(s -> s.selectedPetId = petId);
|
||||
loadPetPrice(petId);
|
||||
} else {
|
||||
updateViewState(s -> {
|
||||
s.selectedPetId = null;
|
||||
s.adoptionFee = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAvailablePetsByStore(Long storeId) {
|
||||
petRepository.getAvailablePetsByStore(storeId).observeForever(r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
List<DropdownDTO> dropdowns = new ArrayList<>();
|
||||
for (com.example.petstoremobile.dtos.PetDTO pet : r.data.getContent()) {
|
||||
dropdowns.add(new DropdownDTO(pet.getPetId(), pet.getPetName()));
|
||||
}
|
||||
petList.setValue(dropdowns);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadPetPrice(Long petId) {
|
||||
petRepository.getPetById(petId).observeForever(r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
com.example.petstoremobile.dtos.PetDTO pet = r.data;
|
||||
// In edit mode, add the pet to the list so the spinner can display its name
|
||||
if (isEditing()) {
|
||||
List<DropdownDTO> single = new ArrayList<>();
|
||||
single.add(new DropdownDTO(pet.getPetId(), pet.getPetName()));
|
||||
petList.setValue(single);
|
||||
}
|
||||
if (pet.getPetPrice() != null) {
|
||||
String price = String.format(Locale.getDefault(), "%.2f", pet.getPetPrice());
|
||||
updateViewState(s -> s.adoptionFee = price);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadEmployeesForStore(Long storeId) {
|
||||
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
||||
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||
@@ -131,26 +192,99 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the date or status changes in the UI. Applies date-based field enabling.
|
||||
*/
|
||||
public void onDateChanged(String date, String currentStatus) {
|
||||
updateViewState(s -> {
|
||||
if (s.isCancelled) return;
|
||||
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date);
|
||||
List<String> available = Arrays.asList(s.availableStatuses);
|
||||
if (!currentStatus.isEmpty() && available.contains(currentStatus)) {
|
||||
s.selectedStatus = currentStatus;
|
||||
} else if (!available.contains(s.selectedStatus) && s.availableStatuses.length > 0) {
|
||||
s.selectedStatus = s.availableStatuses[0];
|
||||
}
|
||||
|
||||
if (!s.isEditing) return; // add mode: field enabling handled separately
|
||||
|
||||
boolean isPast = DateTimeUtils.isDateBeforeToday(date);
|
||||
if (isPast) {
|
||||
setAllEditableFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = true;
|
||||
} else if (!date.isEmpty()) {
|
||||
setAllEditableFieldsEnabled(s, false);
|
||||
s.isEmployeeEnabled = true;
|
||||
s.isDateEnabled = true;
|
||||
s.isStatusEnabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String[] calculateAvailableStatuses(boolean isEditing, String date) {
|
||||
if (!isEditing) return new String[]{"Pending"};
|
||||
if (date == null || date.isEmpty()) return new String[]{};
|
||||
if (DateTimeUtils.isDateBeforeToday(date)) return new String[]{"Completed", "Missed"};
|
||||
return new String[]{"Pending", "Cancelled"};
|
||||
}
|
||||
|
||||
/** Disables all editable fields (fee is always disabled separately). */
|
||||
private void setAllEditableFieldsEnabled(ViewState s, boolean enabled) {
|
||||
s.isCustomerEnabled = enabled;
|
||||
s.isStoreEnabled = enabled;
|
||||
s.isPetEnabled = enabled;
|
||||
s.isEmployeeEnabled = enabled;
|
||||
s.isDateEnabled = enabled;
|
||||
// fee never editable
|
||||
}
|
||||
|
||||
public LiveData<Resource<AdoptionDTO>> loadAdoption() {
|
||||
MutableLiveData<Resource<AdoptionDTO>> result = new MutableLiveData<>();
|
||||
adoptionRepository.getAdoptionById(adoptionId).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
AdoptionDTO a = resource.data;
|
||||
String formattedStatus = DateTimeUtils.formatStatusFromBackend(
|
||||
a.getAdoptionStatus() != null ? a.getAdoptionStatus() : "");
|
||||
String adoptionDate = a.getAdoptionDate() != null ? a.getAdoptionDate() : "";
|
||||
|
||||
updateViewState(state -> {
|
||||
state.selectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
||||
state.selectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
|
||||
state.selectedStoreId = a.getSourceStoreId() != null ? a.getSourceStoreId() : -1;
|
||||
state.selectedEmployeeId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
|
||||
state.adoptionDate = a.getAdoptionDate() != null ? a.getAdoptionDate() : "";
|
||||
state.adoptionFee = a.getAdoptionFee() != null ? a.getAdoptionFee().toString() : "";
|
||||
state.adoptionStatus = a.getAdoptionStatus() != null ? a.getAdoptionStatus() : "";
|
||||
state.isPetEnabled = state.selectedCustomerId != null && state.selectedCustomerId != -1;
|
||||
state.isEmployeeEnabled = state.selectedStoreId != null && state.selectedStoreId != -1;
|
||||
state.adoptionDate = adoptionDate;
|
||||
state.adoptionFee = a.getAdoptionFee() != null ? a.getAdoptionFee().toPlainString() : "";
|
||||
state.selectedStatus = formattedStatus;
|
||||
state.adoptionStatus = formattedStatus;
|
||||
|
||||
if ("Cancelled".equalsIgnoreCase(formattedStatus)) {
|
||||
state.isCancelled = true;
|
||||
state.isCustomerEnabled = false;
|
||||
state.isStoreEnabled = false;
|
||||
state.isPetEnabled = false;
|
||||
state.isEmployeeEnabled = false;
|
||||
state.isStatusEnabled = false;
|
||||
state.isDateEnabled = false;
|
||||
state.isFeeEnabled = false;
|
||||
state.isSaveVisible = false;
|
||||
state.availableStatuses = new String[]{"Cancelled"};
|
||||
} else {
|
||||
state.availableStatuses = calculateAvailableStatuses(true, adoptionDate);
|
||||
boolean isPast = DateTimeUtils.isDateBeforeToday(adoptionDate);
|
||||
if (isPast) {
|
||||
setAllEditableFieldsEnabled(state, false);
|
||||
state.isStatusEnabled = true;
|
||||
} else if (!adoptionDate.isEmpty()) {
|
||||
setAllEditableFieldsEnabled(state, false);
|
||||
state.isEmployeeEnabled = true;
|
||||
state.isDateEnabled = true;
|
||||
state.isStatusEnabled = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (a.getSourceStoreId() != null) {
|
||||
loadEmployeesForStore(a.getSourceStoreId());
|
||||
}
|
||||
if (a.getSourceStoreId() != null) loadEmployeesForStore(a.getSourceStoreId());
|
||||
if (a.getPetId() != null) loadPetPrice(a.getPetId());
|
||||
}
|
||||
result.setValue(resource);
|
||||
});
|
||||
@@ -173,7 +307,6 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||
public LiveData<List<DropdownDTO>> getEmployeeList() { return employeeList; }
|
||||
|
||||
// Kept for backward-compatibility with any remaining direct calls
|
||||
public void setEmployeeList(List<DropdownDTO> list) { employeeList.setValue(list); }
|
||||
|
||||
private void updateViewState(Action<ViewState> action) {
|
||||
@@ -192,8 +325,17 @@ public class AdoptionDetailViewModel extends ViewModel {
|
||||
public boolean isEditing = false;
|
||||
public boolean isAdoptionIdVisible = false;
|
||||
public boolean isDeleteVisible = false;
|
||||
public boolean isCancelled = false;
|
||||
public boolean isPetEnabled = false;
|
||||
public boolean isEmployeeEnabled = false;
|
||||
public boolean isCustomerEnabled = true;
|
||||
public boolean isStoreEnabled = true;
|
||||
public boolean isStatusEnabled = true;
|
||||
public boolean isDateEnabled = true;
|
||||
public boolean isFeeEnabled = false; // always read-only
|
||||
public boolean isSaveVisible = true;
|
||||
public String[] availableStatuses = new String[]{};
|
||||
public String selectedStatus = "";
|
||||
public String modeTitle = "Add Adoption";
|
||||
public String saveButtonText = "Add";
|
||||
public Long selectedPetId = null;
|
||||
|
||||
@@ -237,12 +237,14 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
currentServiceId = a.getServiceId();
|
||||
currentStaffId = a.getEmployeeId();
|
||||
|
||||
String formattedStatus = DateTimeUtils.formatStatusFromBackend(a.getAppointmentStatus());
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = currentCustomerId;
|
||||
s.selectedStoreId = currentStoreId;
|
||||
s.selectedPetId = currentPetId;
|
||||
s.selectedServiceId = currentServiceId;
|
||||
s.selectedStaffId = currentStaffId;
|
||||
s.selectedStatus = formattedStatus;
|
||||
});
|
||||
|
||||
if (currentCustomerId != null) loadPetsForCustomer(currentCustomerId);
|
||||
@@ -280,6 +282,13 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
||||
updateViewState(s -> {
|
||||
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
||||
// Keep selectedStatus if still valid; prefer explicit currentStatus from UI if valid
|
||||
java.util.List<String> available = java.util.Arrays.asList(s.availableStatuses);
|
||||
if (!currentStatus.isEmpty() && available.contains(currentStatus)) {
|
||||
s.selectedStatus = currentStatus;
|
||||
} else if (!available.contains(s.selectedStatus) && s.availableStatuses.length > 0) {
|
||||
s.selectedStatus = s.availableStatuses[0];
|
||||
}
|
||||
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
||||
|
||||
if (isOriginallyCancel) {
|
||||
@@ -349,6 +358,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
s.isPetEnabled = false; // until customer selected
|
||||
s.isStaffEnabled = false; // until store selected
|
||||
s.availableStatuses = new String[]{"Booked"};
|
||||
s.selectedStatus = "Booked";
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -392,6 +402,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
||||
public boolean isTimeEnabled = true;
|
||||
public boolean isStatusEnabled = true;
|
||||
public String[] availableStatuses = new String[]{};
|
||||
public String selectedStatus = "";
|
||||
|
||||
// Selected IDs
|
||||
public Long selectedCustomerId = null;
|
||||
|
||||
@@ -30,7 +30,7 @@ public class InventoryDetailViewModel extends ViewModel {
|
||||
private boolean isEditing = false;
|
||||
|
||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<DropdownDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
@Inject
|
||||
public InventoryDetailViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository, ProductRepository productRepository) {
|
||||
@@ -55,8 +55,8 @@ public class InventoryDetailViewModel extends ViewModel {
|
||||
return storeRepository.getStoreDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<ProductDTO>>> loadProducts() {
|
||||
return productRepository.getAllProducts(null, null, 0, 500, "prodName");
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadProducts() {
|
||||
return productRepository.getProductDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<InventoryDTO>> saveInventory(InventoryDTO dto) {
|
||||
@@ -74,6 +74,6 @@ public class InventoryDetailViewModel extends ViewModel {
|
||||
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||
|
||||
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
|
||||
public LiveData<List<ProductDTO>> getProductList() { return productList; }
|
||||
public void setProductList(List<DropdownDTO> list) { productList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getProductList() { return productList; }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||
@@ -24,7 +25,7 @@ public class ProductDetailViewModel extends ViewModel {
|
||||
private final ProductRepository productRepository;
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
private final MutableLiveData<List<CategoryDTO>> categoryList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<DropdownDTO>> categoryList = new MutableLiveData<>(new ArrayList<>());
|
||||
private long prodId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
@@ -47,8 +48,8 @@ public class ProductDetailViewModel extends ViewModel {
|
||||
return isEditing;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<CategoryDTO>>> loadCategories() {
|
||||
return categoryRepository.getAllCategories(0, 100);
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadCategories() {
|
||||
return productRepository.getCategoryDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<ProductDTO>> loadProduct() {
|
||||
@@ -75,11 +76,11 @@ public class ProductDetailViewModel extends ViewModel {
|
||||
return productRepository.deleteProductImage(prodId);
|
||||
}
|
||||
|
||||
public void setCategoryList(List<CategoryDTO> list) {
|
||||
public void setCategoryList(List<DropdownDTO> list) {
|
||||
categoryList.setValue(list);
|
||||
}
|
||||
|
||||
public LiveData<List<CategoryDTO>> getCategoryList() {
|
||||
public LiveData<List<DropdownDTO>> getCategoryList() {
|
||||
return categoryList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
@@ -23,7 +23,7 @@ public class ProductListViewModel extends ViewModel {
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
private final MutableLiveData<List<ProductDTO>> products = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<CategoryDTO>> categories = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<DropdownDTO>> categories = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
@@ -33,7 +33,7 @@ public class ProductListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public LiveData<List<ProductDTO>> getProducts() { return products; }
|
||||
public LiveData<List<CategoryDTO>> getCategories() { return categories; }
|
||||
public LiveData<List<DropdownDTO>> getCategories() { return categories; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadProducts(String query, Long categoryId) {
|
||||
@@ -51,9 +51,9 @@ public class ProductListViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void loadCategories() {
|
||||
categoryRepository.getAllCategories(0, 100).observeForever(resource -> {
|
||||
productRepository.getCategoryDropdowns().observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
categories.setValue(resource.data.getContent());
|
||||
categories.setValue(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -84,21 +84,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Pet -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionPet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Store -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
@@ -129,6 +114,21 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Pet -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionPet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Adoption Date -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
||||
@@ -29,6 +29,7 @@ public class AdoptionService {
|
||||
private static final String ADOPTION_STATUS_PENDING = "Pending";
|
||||
private static final String ADOPTION_STATUS_COMPLETED = "Completed";
|
||||
private static final String ADOPTION_STATUS_CANCELLED = "Cancelled";
|
||||
private static final String ADOPTION_STATUS_MISSED = "Missed";
|
||||
private static final String PET_STATUS_AVAILABLE = "Available";
|
||||
private static final String PET_STATUS_ADOPTED = "Adopted";
|
||||
|
||||
@@ -218,7 +219,10 @@ public class AdoptionService {
|
||||
if (ADOPTION_STATUS_CANCELLED.equalsIgnoreCase(trimmedStatus)) {
|
||||
return ADOPTION_STATUS_CANCELLED;
|
||||
}
|
||||
throw new IllegalArgumentException("Adoption status must be Pending, Completed, or Cancelled");
|
||||
if (ADOPTION_STATUS_MISSED.equalsIgnoreCase(trimmedStatus)) {
|
||||
return ADOPTION_STATUS_MISSED;
|
||||
}
|
||||
throw new IllegalArgumentException("Adoption status must be Pending, Completed, Cancelled, or Missed");
|
||||
}
|
||||
|
||||
private void validatePetAvailability(Pet pet, Long adoptionId, Long currentPetId) {
|
||||
|
||||
Reference in New Issue
Block a user