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 ece652fd..eae36642 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 @@ -16,11 +16,7 @@ import com.example.petstoremobile.utils.DialogUtils; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.UIUtils; -import com.example.petstoremobile.viewmodels.AdoptionViewModel; -import com.example.petstoremobile.viewmodels.CustomerViewModel; -import com.example.petstoremobile.viewmodels.PetViewModel; -import com.example.petstoremobile.viewmodels.StoreViewModel; -import com.example.petstoremobile.viewmodels.UserViewModel; +import com.example.petstoremobile.viewmodels.AdoptionDetailViewModel; import java.math.BigDecimal; import java.util.*; @@ -34,35 +30,19 @@ import dagger.hilt.android.AndroidEntryPoint; public class AdoptionDetailFragment extends Fragment { private FragmentAdoptionDetailBinding binding; + private AdoptionDetailViewModel viewModel; - private long adoptionId = -1; - private boolean isEditing = false; private long preselectedPetId = -1; private long preselectedCustomerId = -1; private long preselectedStoreId = -1; private long preselectedEmployeeId = -1; - private List petList = new ArrayList<>(); - private List customerList = new ArrayList<>(); - private List storeList = new ArrayList<>(); - private List employeeList = new ArrayList<>(); - private final String[] STATUSES = {"Pending", "Completed", "Cancelled"}; - private AdoptionViewModel adoptionViewModel; - private PetViewModel petViewModel; - private CustomerViewModel customerViewModel; - private StoreViewModel storeViewModel; - private UserViewModel userViewModel; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - 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); - userViewModel = new ViewModelProvider(this).get(UserViewModel.class); + viewModel = new ViewModelProvider(this).get(AdoptionDetailViewModel.class); } @Override @@ -77,6 +57,7 @@ public class AdoptionDetailFragment extends Fragment { super.onViewCreated(view, savedInstanceState); setupSpinners(); setupDatePicker(); + observeViewModel(); loadSpinnersData(); handleArguments(); @@ -85,29 +66,31 @@ public class AdoptionDetailFragment extends Fragment { binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete()); } + private void observeViewModel() { + viewModel.getPetList().observe(getViewLifecycleOwner(), list -> refreshPetSpinner()); + viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> refreshCustomerSpinner()); + viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner()); + viewModel.getEmployeeList().observe(getViewLifecycleOwner(), list -> refreshEmployeeSpinner()); + } + @Override public void onDestroyView() { super.onDestroyView(); binding = null; } - /** - * Configures the spinner for adoption status. - */ private void setupSpinners() { SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, STATUSES); - // Pet spinner disabled by default until customer is selected UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet); - // Listener to enable pet spinner based on customer selection binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { if (position > 0) { UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet); } else { - if (!isEditing) { + if (!viewModel.isEditing()) { binding.spinnerAdoptionPet.setSelection(0); UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet); } @@ -117,27 +100,21 @@ public class AdoptionDetailFragment extends Fragment { public void onNothingSelected(AdapterView parent) {} }); - // Listener to load employees based on selected store binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - if (position > 0 && position <= storeList.size()) { - DropdownDTO selectedStore = storeList.get(position - 1); + if (position > 0 && viewModel.getStoreList().getValue() != null && position <= viewModel.getStoreList().getValue().size()) { + DropdownDTO selectedStore = viewModel.getStoreList().getValue().get(position - 1); loadEmployees(selectedStore.getId()); } else { - employeeList.clear(); - refreshEmployeeSpinner(); + viewModel.setEmployeeList(new ArrayList<>()); } } - @Override public void onNothingSelected(AdapterView parent) {} }); } - /** - * Configures the date picker dialog for the adoption date field. - */ private void setupDatePicker() { binding.etAdoptionDate.setOnClickListener(v -> { Calendar c = Calendar.getInstance(); @@ -150,129 +127,77 @@ public class AdoptionDetailFragment extends Fragment { }); } - /** - * Fetches required data for spinners from the backend. - */ private void loadSpinnersData() { - loadPets(); - loadCustomers(); - loadStores(); - } - - /** - * Loads the list of pets from the API. - */ - private void loadPets() { - petViewModel.getAdoptionPets().observe(getViewLifecycleOwner(), resource -> { + viewModel.loadPets().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - petList = resource.data; - refreshPetSpinner(); + viewModel.setPetList(resource.data); + } + }); + viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + viewModel.setCustomerList(resource.data); + } + }); + viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + viewModel.setStoreList(resource.data); } }); } - /** - * Populates the pet selection spinner with data. - */ private void refreshPetSpinner() { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, petList, + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, viewModel.getPetList().getValue(), DropdownDTO::getLabel, "-- Select Pet --", preselectedPetId, DropdownDTO::getId); } - /** - * Loads the list of customers from the API. - */ - private void loadCustomers() { - customerViewModel.getCustomerDropdowns().observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - customerList = resource.data; - refreshCustomerSpinner(); - } - }); - } - - /** - * Populates the customer selection spinner with data. - */ private void refreshCustomerSpinner() { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, customerList, - DropdownDTO::getLabel, - "-- Select Customer --", + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, viewModel.getCustomerList().getValue(), + DropdownDTO::getLabel, "-- Select Customer --", preselectedCustomerId, DropdownDTO::getId); } - /** - * Loads the list of stores from the API. - */ - private void loadStores() { - storeViewModel.getStoreDropdowns().observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - storeList = resource.data; - refreshStoreSpinner(); - } - }); - } - - /** - * Populates the store selection spinner with data. - */ private void refreshStoreSpinner() { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, storeList, + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, viewModel.getStoreList().getValue(), DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId); } - /** - * Loads the list of employees for a specific store. - */ private void loadEmployees(Long storeId) { - storeViewModel.getStoreEmployees(storeId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadEmployees(storeId).observe(getViewLifecycleOwner(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { - employeeList = resource.data; - refreshEmployeeSpinner(); + viewModel.setEmployeeList(resource.data); } }); } - /** - * Populates the employee selection spinner with data. - */ private void refreshEmployeeSpinner() { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, employeeList, + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, viewModel.getEmployeeList().getValue(), DropdownDTO::getLabel, "-- Select Staff --", preselectedEmployeeId, DropdownDTO::getId); } - /** - * Handles arguments to determine if the fragment is in edit or add mode. - */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("adoptionId")) { - isEditing = true; - adoptionId = a.getLong("adoptionId"); + long adoptionId = a.getLong("adoptionId"); + viewModel.setAdoptionId(adoptionId); binding.tvAdoptionMode.setText("Edit Adoption"); binding.tvAdoptionId.setText("ID: " + adoptionId); binding.tvAdoptionId.setVisibility(View.VISIBLE); binding.btnDeleteAdoption.setVisibility(View.VISIBLE); loadAdoptionData(); } else { - isEditing = false; + viewModel.setAdoptionId(-1); binding.tvAdoptionMode.setText("Add Adoption"); binding.btnDeleteAdoption.setVisibility(View.GONE); binding.tvAdoptionId.setVisibility(View.GONE); - - // Explicitly disable in add mode UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet); } } - /** - * Fetches specific adoption details from the backend using the ID. - */ private void loadAdoptionData() { - adoptionViewModel.getAdoptionById(adoptionId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; if (resource.status == Resource.Status.SUCCESS && resource.data != null) { AdoptionDTO a = resource.data; @@ -289,7 +214,6 @@ public class AdoptionDetailFragment extends Fragment { refreshCustomerSpinner(); refreshStoreSpinner(); - // In edit mode, if a customer is already set, ensure pet spinner is enabled if (preselectedCustomerId != -1) { UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet); } @@ -299,9 +223,6 @@ public class AdoptionDetailFragment extends Fragment { }); } - /** - * Validates input and saves the adoption request to the backend. - */ private void saveAdoption() { if (binding.spinnerAdoptionCustomer.getSelectedItemPosition() == 0) { Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return; @@ -328,13 +249,13 @@ public class AdoptionDetailFragment extends Fragment { } } - DropdownDTO customer = customerList.get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1); - DropdownDTO pet = petList.get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1); - DropdownDTO store = storeList.get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1); + DropdownDTO customer = viewModel.getCustomerList().getValue().get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1); + DropdownDTO pet = viewModel.getPetList().getValue().get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1); + DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1); Long employeeId = null; if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0) { - employeeId = employeeList.get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId(); + employeeId = viewModel.getEmployeeList().getValue().get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId(); } String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()]; @@ -349,33 +270,19 @@ public class AdoptionDetailFragment extends Fragment { fee ); - if (isEditing) { - adoptionViewModel.updateAdoption(adoptionId, dto).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { - Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } else { - adoptionViewModel.createAdoption(dto).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { - Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } + viewModel.saveAdoption(dto).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS) { + Toast.makeText(getContext(), viewModel.isEditing() ? "Updated" : "Saved", Toast.LENGTH_SHORT).show(); + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); + } + }); } - /** - * Shows a confirmation dialog before deleting an adoption request. - */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption", () -> - adoptionViewModel.deleteAdoption(adoptionId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteAdoption().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS) { Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show(); navigateBack(); @@ -385,9 +292,6 @@ public class AdoptionDetailFragment extends Fragment { })); } - /** - * Navigates back to the previous fragment. - */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } 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 1d280270..bf63fa41 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 @@ -43,37 +43,28 @@ public class AppointmentDetailFragment extends Fragment { private final Integer[] HOURS = {9, 10, 11, 12, 13, 14, 15, 16, 17}; private final Integer[] MINUTES = {0, 15, 30, 45}; - private AppointmentDetailViewModel appointmentViewModel; + private AppointmentDetailViewModel viewModel; private boolean isUpdatingUI = false; - /** - * Called when the fragment is first created. - */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - appointmentViewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class); + viewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class); } - /** - * Creates and returns the view hierarchy with the fragment. - */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentAppointmentDetailBinding.inflate(inflater, container, false); return binding.getRoot(); } - /** - * Called immediately after onCreateView has returned. - */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); setupSpinners(); setupDatePicker(); observeViewModel(); - appointmentViewModel.loadInitialFormData(); + viewModel.loadInitialFormData(); handleArguments(); binding.btnApptBack.setOnClickListener(v -> navigateBack()); @@ -81,110 +72,83 @@ public class AppointmentDetailFragment extends Fragment { binding.btnDeleteAppointment.setOnClickListener(v -> confirmDelete()); } - /** - * Called when the view previously created by onCreateView has been detached. - */ @Override public void onDestroyView() { super.onDestroyView(); binding = null; } - /** - * Configures the adapters and listeners for all spinners. - */ private void setupSpinners() { - //Status Spinner is empty by default the date determines whats in here SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, new String[]{}); - // Set up hour and minute spinners String[] hours = new String[HOURS.length]; for (int i = 0; i < HOURS.length; i++) hours[i] = DateTimeUtils.formatTime(HOURS[i], 0); SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerHour, hours); SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00", "15", "30", "45"}); - // Pet and Staff spinners disabled by until parent selection UIUtils.setViewsEnabled(false, binding.spinnerPet, binding.spinnerStaff); - // Listener to notify ViewModel of customer selection binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - appointmentViewModel.onCustomerSelected(position); + viewModel.onCustomerSelected(position); } @Override public void onNothingSelected(AdapterView parent) {} }); - // Listener to notify ViewModel of store selection binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { - appointmentViewModel.onStoreSelected(position); + viewModel.onStoreSelected(position); } @Override public void onNothingSelected(AdapterView parent) {} }); - // Listeners for other selections - SpinnerUtils.setOnIndexSelectedListener(binding.spinnerService, p -> appointmentViewModel.onServiceSelected(p)); - SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPet, p -> appointmentViewModel.onPetSelected(p)); - SpinnerUtils.setOnIndexSelectedListener(binding.spinnerStaff, p -> appointmentViewModel.onStaffSelected(p)); + SpinnerUtils.setOnIndexSelectedListener(binding.spinnerService, p -> viewModel.onServiceSelected(p)); + SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPet, p -> viewModel.onPetSelected(p)); + SpinnerUtils.setOnIndexSelectedListener(binding.spinnerStaff, p -> viewModel.onStaffSelected(p)); - // Listeners for time changes SpinnerUtils.setOnIndexSelectedListener(binding.spinnerHour, p -> notifyDateTimeStatusChange()); SpinnerUtils.setOnIndexSelectedListener(binding.spinnerMinute, p -> notifyDateTimeStatusChange()); - - // Listener to notify ViewModel of status selection SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange()); } - /** - * Configures the date picker dialog for the appointment date field. - */ private void setupDatePicker() { binding.etAppointmentDate.setOnClickListener(v -> UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange)); } - /** - * Observes the ViewModel for UI state and list updates. - */ private void observeViewModel() { - appointmentViewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); + viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState); - // Populate spinners when data arrives - appointmentViewModel.getCustomers().observe(getViewLifecycleOwner(), list -> + viewModel.getCustomers().observe(getViewLifecycleOwner(), list -> SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, list, DropdownDTO::getLabel, "-- Select Customer --", preselectedCustomerId, DropdownDTO::getId)); - appointmentViewModel.getStores().observe(getViewLifecycleOwner(), list -> + viewModel.getStores().observe(getViewLifecycleOwner(), list -> SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId)); - appointmentViewModel.getServices().observe(getViewLifecycleOwner(), list -> + viewModel.getServices().observe(getViewLifecycleOwner(), list -> SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, list, ServiceDTO::getServiceName, "-- Select Service --", preselectedServiceId, ServiceDTO::getServiceId)); - appointmentViewModel.getCustomerPets().observe(getViewLifecycleOwner(), list -> + viewModel.getCustomerPets().observe(getViewLifecycleOwner(), list -> SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, list, DropdownDTO::getLabel, "-- Select Pet --", preselectedPetId, DropdownDTO::getId)); - appointmentViewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list -> + viewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list -> SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, list, DropdownDTO::getLabel, "-- Select Staff --", preselectedStaffId, DropdownDTO::getId)); } - /** - * Applies the ViewState provided by the ViewModel to the UI components. - */ private void applyViewState(AppointmentDetailViewModel.ViewState state) { isUpdatingUI = true; - // Mode specific UI binding.tvApptMode.setText(state.isEditing ? "Edit Appointment" : "Add Appointment"); - binding.tvAppointmentId.setText(DateTimeUtils.formatId(appointmentViewModel.getAppointmentId())); + binding.tvAppointmentId.setText(DateTimeUtils.formatId(viewModel.getAppointmentId())); 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); - // Enabling/Disabling Views and Labels UIUtils.setFieldEnabled(state.isCustomerEnabled, binding.spinnerCustomer, binding.tvLabelCustomer); UIUtils.setFieldEnabled(state.isStoreEnabled, binding.spinnerStore, binding.tvLabelStore); UIUtils.setFieldEnabled(state.isPetEnabled, binding.spinnerPet, binding.tvLabelPet); @@ -195,7 +159,6 @@ public class AppointmentDetailFragment extends Fragment { UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerMinute); UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAppointmentStatus); - // Update status options Object selected = binding.spinnerAppointmentStatus.getSelectedItem(); String current = selected != null ? selected.toString() : ""; SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, state.availableStatuses); @@ -204,9 +167,6 @@ public class AppointmentDetailFragment extends Fragment { isUpdatingUI = false; } - /** - * Notifies the ViewModel that the date, time, or status has changed. - */ private void notifyDateTimeStatusChange() { if (isUpdatingUI) return; @@ -214,27 +174,21 @@ public class AppointmentDetailFragment extends Fragment { String time = buildTimeString(); Object selected = binding.spinnerAppointmentStatus.getSelectedItem(); String status = selected != null ? selected.toString() : ""; - appointmentViewModel.onDateOrTimeChanged(date, time, status); + viewModel.onDateOrTimeChanged(date, time, status); } - /** - * Handles arguments to determine if the fragment is in edit or add mode. - */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("appointmentId")) { - appointmentViewModel.setAppointmentId(a.getLong("appointmentId")); + viewModel.setAppointmentId(a.getLong("appointmentId")); loadAppointmentData(); } else { - appointmentViewModel.setAppointmentId(-1); + viewModel.setAppointmentId(-1); } } - /** - * Fetches specific appointment details from the backend using the ID. - */ private void loadAppointmentData() { - appointmentViewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> { + viewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> { if (resource == null || resource.status != Resource.Status.SUCCESS || resource.data == null) return; AppointmentDTO a = resource.data; preselectedPetId = a.getPetId() != null ? a.getPetId() : -1; @@ -254,9 +208,6 @@ public class AppointmentDetailFragment extends Fragment { }); } - /** - * Validates input and saves the appointment to the backend. - */ private void saveAppointment() { if (!validateRequiredFields()) return; @@ -264,14 +215,14 @@ public class AppointmentDetailFragment extends Fragment { String time = buildTimeString(); String status = binding.spinnerAppointmentStatus.getSelectedItem().toString().toUpperCase(); - if (!appointmentViewModel.isValidFutureBooking(status, date, time)) { + if (!viewModel.isValidFutureBooking(status, date, time)) { DialogUtils.showInfoDialog(requireContext(), "Invalid Time", "Booked appointments must be in the future."); return; } - appointmentViewModel.saveAppointment(date, time, status).observe(getViewLifecycleOwner(), resource -> { + viewModel.saveAppointment(date, time, status).observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS) { - AppointmentDetailViewModel.ViewState state = appointmentViewModel.getViewState().getValue(); + AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue(); String message = (state != null && state.isEditing) ? "Updated" : "Saved"; Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show(); navigateBack(); @@ -281,9 +232,6 @@ public class AppointmentDetailFragment extends Fragment { }); } - /** - * Validates that all required fields are selected. - */ private boolean validateRequiredFields() { if (binding.spinnerCustomer.getSelectedItemPosition() == 0) return UIUtils.showToast(getContext(), "Select a customer"); if (binding.spinnerStore.getSelectedItemPosition() == 0) return UIUtils.showToast(getContext(), "Select a store"); @@ -293,24 +241,15 @@ public class AppointmentDetailFragment extends Fragment { return true; } - /** - * Builds a time string from the hour and minute spinners. - */ private String buildTimeString() { return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]); } - /** - * Handles errors that occur during the saving process. - */ private void handleSaveError(String errorMessage) { if (errorMessage != null && errorMessage.toLowerCase().contains("not available")) showNoAvailabilityDialog(); else Toast.makeText(getContext(), errorMessage != null ? errorMessage : "Error saving", Toast.LENGTH_SHORT).show(); } - /** - * Shows a specialized dialog when a time slot is not available. - */ private void showNoAvailabilityDialog() { new androidx.appcompat.app.AlertDialog.Builder(requireContext()) .setTitle("No Availability") @@ -319,26 +258,17 @@ public class AppointmentDetailFragment extends Fragment { .setNegativeButton("Cancel Booking", (d, w) -> navigateBack()).show(); } - /** - * Shows a confirmation dialog and handles the deletion of an appointment. - */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () -> - appointmentViewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS) navigateBack(); })); } - /** - * Navigates back to the previous screen. - */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } - /** - * Parses a time string and sets the hour and minute spinners. - */ private void parseAndSetTimeSpinners(String time) { int[] parsedTime = DateTimeUtils.parseTimeString(time); if (parsedTime == null) return; diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/InventoryDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/InventoryDetailFragment.java index 640b99b9..a6d1c613 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/InventoryDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/InventoryDetailFragment.java @@ -17,16 +17,10 @@ import com.example.petstoremobile.databinding.FragmentInventoryDetailBinding; import com.example.petstoremobile.dtos.DropdownDTO; import com.example.petstoremobile.dtos.InventoryDTO; import com.example.petstoremobile.dtos.ProductDTO; -import com.example.petstoremobile.dtos.StoreDTO; import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.SpinnerUtils; -import com.example.petstoremobile.viewmodels.InventoryViewModel; -import com.example.petstoremobile.viewmodels.ProductViewModel; -import com.example.petstoremobile.viewmodels.StoreViewModel; - -import java.util.ArrayList; -import java.util.List; +import com.example.petstoremobile.viewmodels.InventoryDetailViewModel; import dagger.hilt.android.AndroidEntryPoint; @@ -37,33 +31,17 @@ import dagger.hilt.android.AndroidEntryPoint; public class InventoryDetailFragment extends Fragment { private FragmentInventoryDetailBinding binding; + private InventoryDetailViewModel viewModel; - private InventoryViewModel inventoryViewModel; - private ProductViewModel productViewModel; - private StoreViewModel storeViewModel; - - private boolean isEditing = false; - private long inventoryId = -1; private long preselectedStoreId = -1; private long preselectedProductId = -1; - private List storeList = new ArrayList<>(); - private List productList = new ArrayList<>(); - - /** - * Initializes the view models. - */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - inventoryViewModel = new ViewModelProvider(this).get(InventoryViewModel.class); - productViewModel = new ViewModelProvider(this).get(ProductViewModel.class); - storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class); + viewModel = new ViewModelProvider(this).get(InventoryDetailViewModel.class); } - /** - * Inflates the layout. - */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -71,13 +49,11 @@ public class InventoryDetailFragment extends Fragment { return binding.getRoot(); } - /** - * Sets up UI components after the view is created. - */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + observeViewModel(); loadSpinnersData(); handleArguments(); @@ -86,64 +62,47 @@ public class InventoryDetailFragment extends Fragment { binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete()); } + private void observeViewModel() { + viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner()); + viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner()); + } + @Override public void onDestroyView() { super.onDestroyView(); binding = null; } - /** - * Fetches required data for spinners from the backend. - */ private void loadSpinnersData() { - loadStores(); - loadProducts(); - } - - /** - * Loads the list of stores for the spinner. - */ - private void loadStores() { - storeViewModel.getStoreDropdowns().observe(getViewLifecycleOwner(), resource -> { + viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - storeList = resource.data; - refreshStoreSpinner(); + viewModel.setStoreList(resource.data); + } + }); + viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + viewModel.setProductList(resource.data.getContent()); } }); } private void refreshStoreSpinner() { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, storeList, + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, viewModel.getStoreList().getValue(), DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId); } - /** - * Loads the list of products for the spinner. - */ - private void loadProducts() { - productViewModel.getAllProducts(null, null, 0, 500, "prodName").observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - productList = resource.data.getContent(); - refreshProductSpinner(); - } - }); - } - private void refreshProductSpinner() { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, productList, + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(), ProductDTO::getProdName, "-- Select Product --", preselectedProductId, ProductDTO::getProdId); } - /** - * Handles fragment arguments to determine if we are in edit or add mode. - */ private void handleArguments() { Bundle args = getArguments(); if (args != null && args.containsKey("inventoryId")) { - isEditing = true; - inventoryId = args.getLong("inventoryId"); + long inventoryId = args.getLong("inventoryId"); + viewModel.setInventoryId(inventoryId); binding.tvInventoryMode.setText("Edit Inventory"); binding.tvInventoryId.setText("Inventory ID: " + inventoryId); @@ -153,7 +112,7 @@ public class InventoryDetailFragment extends Fragment { loadInventoryData(); } else { - isEditing = false; + viewModel.setInventoryId(-1); binding.tvInventoryMode.setText("Add Inventory"); binding.tvInventoryId.setVisibility(View.GONE); binding.btnDeleteInventory.setVisibility(View.GONE); @@ -161,11 +120,8 @@ public class InventoryDetailFragment extends Fragment { } } - /** - * Loads existing inventory data from the backend. - */ private void loadInventoryData() { - inventoryViewModel.getInventoryById(inventoryId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadInventory().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; if (resource.status == Resource.Status.SUCCESS && resource.data != null) { InventoryDTO inv = resource.data; @@ -181,9 +137,6 @@ public class InventoryDetailFragment extends Fragment { }); } - /** - * Validates input and saves the current inventory item details to the backend. - */ private void saveInventory() { if (binding.spinnerInventoryStore.getSelectedItemPosition() == 0) { Toast.makeText(getContext(), "Please select a store", Toast.LENGTH_SHORT).show(); @@ -200,38 +153,23 @@ public class InventoryDetailFragment extends Fragment { } int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim()); - DropdownDTO store = storeList.get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1); - ProductDTO product = productList.get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1); + DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1); + ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1); InventoryDTO request = new InventoryDTO(product.getProdId(), store.getId(), quantity); setButtonsEnabled(false); - if (isEditing) { - inventoryViewModel.updateInventory(inventoryId, request).observe(getViewLifecycleOwner(), resource -> { - setButtonsEnabled(true); - if (resource.status == Resource.Status.SUCCESS) { - Toast.makeText(getContext(), "Inventory updated", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } else { - inventoryViewModel.createInventory(request).observe(getViewLifecycleOwner(), resource -> { - setButtonsEnabled(true); - if (resource.status == Resource.Status.SUCCESS) { - Toast.makeText(getContext(), "Inventory created", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } + viewModel.saveInventory(request).observe(getViewLifecycleOwner(), resource -> { + setButtonsEnabled(true); + if (resource.status == Resource.Status.SUCCESS) { + Toast.makeText(getContext(), viewModel.isEditing() ? "Inventory updated" : "Inventory created", Toast.LENGTH_SHORT).show(); + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); + } + }); } - /** - * Shows a confirmation dialog before deleting an inventory item. - */ private void confirmDelete() { new AlertDialog.Builder(requireContext()) .setTitle("Delete inventory item?") @@ -241,12 +179,9 @@ public class InventoryDetailFragment extends Fragment { .show(); } - /** - * Sends a request to the API to delete the inventory item. - */ private void deleteInventory() { setButtonsEnabled(false); - inventoryViewModel.deleteInventory(inventoryId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteInventory().observe(getViewLifecycleOwner(), resource -> { setButtonsEnabled(true); if (resource.status == Resource.Status.SUCCESS) { Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show(); @@ -257,16 +192,10 @@ public class InventoryDetailFragment extends Fragment { }); } - /** - * Navigates back to the previous fragment. - */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } - /** - * Enables or disables action buttons. - */ private void setButtonsEnabled(boolean enabled) { binding.btnSaveInventory.setEnabled(enabled); binding.btnDeleteInventory.setEnabled(enabled); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java index 6c44ca59..d260d723 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PetDetailFragment.java @@ -26,12 +26,8 @@ import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.UIUtils; -import com.example.petstoremobile.viewmodels.CustomerViewModel; -import com.example.petstoremobile.viewmodels.PetViewModel; -import com.example.petstoremobile.viewmodels.StoreViewModel; +import com.example.petstoremobile.viewmodels.PetDetailViewModel; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import dagger.hilt.android.AndroidEntryPoint; @@ -43,23 +39,15 @@ import dagger.hilt.android.AndroidEntryPoint; public class PetDetailFragment extends Fragment { private FragmentPetDetailBinding binding; - private long petId; - private boolean isEditing = false; - - private PetViewModel viewModel; - private CustomerViewModel customerViewModel; - private StoreViewModel storeViewModel; - private List customerList = new ArrayList<>(); - private List storeList = new ArrayList<>(); + private PetDetailViewModel viewModel; + private Long selectedCustomerId = null; private Long selectedStoreId = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - viewModel = new ViewModelProvider(this).get(PetViewModel.class); - customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class); - storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class); + viewModel = new ViewModelProvider(this).get(PetDetailViewModel.class); } @Override @@ -74,8 +62,7 @@ public class PetDetailFragment extends Fragment { super.onViewCreated(view, savedInstanceState); setupSpinner(); - loadCustomers(); - loadStores(); + observeViewModel(); handleArguments(); //set button click listeners @@ -84,6 +71,23 @@ public class PetDetailFragment extends Fragment { binding.btnDeletePet.setOnClickListener(v -> deletePet()); } + private void observeViewModel() { + viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> updateCustomerSpinnerSelection()); + viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> updateStoreSpinnerSelection()); + + viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + viewModel.setCustomerList(resource.data); + } + }); + + viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + viewModel.setStoreList(resource.data); + } + }); + } + @Override public void onDestroyView() { super.onDestroyView(); @@ -113,14 +117,14 @@ public class PetDetailFragment extends Fragment { Long customerId = null; int customerPos = binding.spinnerCustomer.getSelectedItemPosition(); if (customerPos > 0) { // 0 means no customer for pet - customerId = customerList.get(customerPos - 1).getId(); + customerId = viewModel.getCustomerList().getValue().get(customerPos - 1).getId(); } // Get selected store Long storeId = null; int storePos = binding.spinnerStore.getSelectedItemPosition(); if (storePos > 0) { - storeId = storeList.get(storePos - 1).getId(); + storeId = viewModel.getStoreList().getValue().get(storePos - 1).getId(); } // Validation: If status is Available, a store must be selected @@ -150,31 +154,20 @@ public class PetDetailFragment extends Fragment { petDTO.setCustomerId(customerId); petDTO.setStoreId(storeId); - //check if the pet is being edited or added - if (isEditing) { - // Update existing pet - petDTO.setPetId(petId); - viewModel.updatePet(petId, petDTO).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { - ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) petId); + viewModel.savePet(petDTO).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS) { + if (viewModel.isEditing()) { + ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) viewModel.getPetId()); Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show(); - navigateToPetList(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } else { - // Add new pet - viewModel.createPet(petDTO).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { + } else { ActivityLogger.log(requireContext(), "Added new Pet: " + name); Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show(); - navigateToPetList(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); } - }); - } + navigateToPetList(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); + } + }); } /** @@ -182,9 +175,9 @@ public class PetDetailFragment extends Fragment { */ private void deletePet() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Pet", () -> - viewModel.deletePet(petId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deletePet().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS) { - ActivityLogger.logChange(requireContext(), "Pet", "DELETED", (int) petId); + ActivityLogger.logChange(requireContext(), "Pet", "DELETED", (int) viewModel.getPetId()); Toast.makeText(getContext(), "Pet deleted successfully!", Toast.LENGTH_SHORT).show(); navigateToPetList(); } else if (resource.status == Resource.Status.ERROR) { @@ -211,30 +204,23 @@ public class PetDetailFragment extends Fragment { * Handles arguments passed to the fragment to determine if it's in edit or add mode. */ private void handleArguments() { - // Pet is being edited if the bundle contains a petId if (getArguments() != null && getArguments().containsKey("petId")) { - // Get pet data from arguments and populate fields - isEditing = true; - petId = getArguments().getLong("petId"); + long petId = getArguments().getLong("petId"); + viewModel.setPetId(petId); binding.tvMode.setText("Edit Pet"); binding.tvPetId.setText("ID: " + petId); binding.tvPetId.setVisibility(View.VISIBLE); binding.btnDeletePet.setVisibility(View.VISIBLE); - // Disable species and breed fields in edit mode UIUtils.setViewsEnabled(false, binding.etPetSpecies, binding.etPetBreed); - loadPetData(); } else { - // Pet is being added - // Set default values for add a new pet - isEditing = false; + viewModel.setPetId(-1); binding.tvMode.setText("Add Pet"); binding.tvPetId.setVisibility(View.GONE); binding.btnDeletePet.setVisibility(View.GONE); binding.btnSavePet.setText("Add"); - // Enable species and breed fields in edit mode UIUtils.setViewsEnabled(true, binding.etPetSpecies, binding.etPetBreed); } } @@ -243,7 +229,7 @@ public class PetDetailFragment extends Fragment { * Fetches specific pet details from the backend using the ID. */ private void loadPetData() { - viewModel.getPetById(petId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadPet().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; if (resource.status == Resource.Status.SUCCESS && resource.data != null) { PetDTO p = resource.data; @@ -267,30 +253,6 @@ public class PetDetailFragment extends Fragment { }); } - /** - * Fetches the list of customers and populates the spinner. - */ - private void loadCustomers() { - customerViewModel.getCustomerDropdowns().observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { - customerList = resource.data; - updateCustomerSpinnerSelection(); - } - }); - } - - /** - * Fetches the list of stores and populates the spinner. - */ - private void loadStores() { - storeViewModel.getStoreDropdowns().observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { - storeList = resource.data; - updateStoreSpinnerSelection(); - } - }); - } - /** * Updates the customer spinner with the current list and sets the selection if needed. */ @@ -298,7 +260,7 @@ public class PetDetailFragment extends Fragment { SpinnerUtils.populateSpinner( requireContext(), binding.spinnerCustomer, - customerList, + viewModel.getCustomerList().getValue(), DropdownDTO::getLabel, "No Owner", selectedCustomerId, @@ -313,7 +275,7 @@ public class PetDetailFragment extends Fragment { SpinnerUtils.populateSpinner( requireContext(), binding.spinnerStore, - storeList, + viewModel.getStoreList().getValue(), DropdownDTO::getLabel, "None", selectedStoreId, @@ -333,11 +295,9 @@ public class PetDetailFragment extends Fragment { public void onItemSelected(AdapterView parent, View view, int position, long id) { String status = parent.getItemAtPosition(position).toString(); - // Clear any existing error icons when status changes clearSpinnerError(binding.spinnerCustomer); clearSpinnerError(binding.spinnerStore); - //Disable the customer spinner if the status is "Available" if ("Available".equalsIgnoreCase(status)) { binding.spinnerCustomer.setSelection(0); UIUtils.setViewsEnabled(false, binding.spinnerCustomer); @@ -345,7 +305,6 @@ public class PetDetailFragment extends Fragment { UIUtils.setViewsEnabled(true, binding.spinnerCustomer); } - //Disable the store spinner if the status is "Owned" if ("Owned".equalsIgnoreCase(status)) { binding.spinnerStore.setSelection(0); UIUtils.setViewsEnabled(false, binding.spinnerStore); @@ -360,9 +319,6 @@ public class PetDetailFragment extends Fragment { }); } - /** - * Clears error messages from a Spinner's selected view. - */ private void clearSpinnerError(Spinner spinner) { View selectedView = spinner.getSelectedView(); if (selectedView instanceof TextView) { diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java index d2527d71..2d07c280 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductDetailFragment.java @@ -17,7 +17,7 @@ import com.example.petstoremobile.api.*; import com.example.petstoremobile.api.auth.TokenManager; import com.example.petstoremobile.databinding.FragmentProductDetailBinding; import com.example.petstoremobile.dtos.*; -import com.example.petstoremobile.viewmodels.ProductViewModel; +import com.example.petstoremobile.viewmodels.ProductDetailViewModel; import com.example.petstoremobile.utils.DialogUtils; import com.example.petstoremobile.utils.FileUtils; import com.example.petstoremobile.utils.GlideUtils; @@ -31,7 +31,6 @@ import java.math.BigDecimal; import java.util.*; import javax.inject.Inject; - import javax.inject.Named; import dagger.hilt.android.AndroidEntryPoint; @@ -46,29 +45,22 @@ import okhttp3.RequestBody; public class ProductDetailFragment extends Fragment { private FragmentProductDetailBinding binding; + private ProductDetailViewModel viewModel; + private ImagePickerHelper imagePickerHelper; - private long prodId = -1; - private boolean isEditing = false; private long preselectedCategoryId = -1; private boolean hasImage = false; private boolean isImageChanged = false; private boolean isImageRemoved = false; - - private List categoryList = new ArrayList<>(); private Uri photoUri; - private ProductViewModel viewModel; - private ImagePickerHelper imagePickerHelper; @Inject @Named("baseUrl") String baseUrl; @Inject TokenManager tokenManager; - /** - * Initializes activity launchers and the ImagePickerHelper. - */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - viewModel = new ViewModelProvider(this).get(ProductViewModel.class); + viewModel = new ViewModelProvider(this).get(ProductDetailViewModel.class); imagePickerHelper = new ImagePickerHelper(this, "product_photo.jpg", new ImagePickerHelper.ImagePickerListener() { @Override @@ -95,9 +87,6 @@ public class ProductDetailFragment extends Fragment { }); } - /** - * Inflates the layout. - */ @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -105,14 +94,11 @@ public class ProductDetailFragment extends Fragment { return binding.getRoot(); } - /** - * Sets up UI components and listeners after the view is created. - */ @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - loadCategories(); + observeViewModel(); handleArguments(); binding.btnProductBack.setOnClickListener(v -> navigateBack()); @@ -121,34 +107,33 @@ public class ProductDetailFragment extends Fragment { binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage)); } + private void observeViewModel() { + viewModel.getCategoryList().observe(getViewLifecycleOwner(), list -> updateCategorySpinner()); + + viewModel.loadCategories().observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + viewModel.setCategoryList(resource.data.getContent()); + } + }); + } + + private void updateCategorySpinner() { + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(), + CategoryDTO::getCategoryName, "-- Select Category --", + preselectedCategoryId, CategoryDTO::getCategoryId); + } + @Override public void onDestroyView() { super.onDestroyView(); binding = null; } - /** - * Fetches all product categories for the selection spinner. - */ - private void loadCategories() { - viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { - categoryList = resource.data.getContent(); - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, categoryList, - CategoryDTO::getCategoryName, "-- Select Category --", - preselectedCategoryId, CategoryDTO::getCategoryId); - } - }); - } - - /** - * Checks if the fragment was opened with existing product data for editing. - */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("prodId")) { - isEditing = true; - prodId = a.getLong("prodId"); + long prodId = a.getLong("prodId"); + viewModel.setProdId(prodId); binding.tvProductMode.setText("Edit Product"); binding.tvProductId.setText("ID: " + prodId); binding.tvProductId.setVisibility(View.VISIBLE); @@ -156,6 +141,7 @@ public class ProductDetailFragment extends Fragment { loadProductData(); loadProductImage(); } else { + viewModel.setProdId(-1); binding.tvProductMode.setText("Add Product"); binding.btnDeleteProduct.setVisibility(View.GONE); binding.tvProductId.setVisibility(View.GONE); @@ -163,11 +149,8 @@ public class ProductDetailFragment extends Fragment { } } - /** - * Loads the product data from the backend. - */ private void loadProductData() { - viewModel.getProductById(prodId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadProduct().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; if (resource.status == Resource.Status.SUCCESS && resource.data != null) { ProductDTO p = resource.data; @@ -175,24 +158,15 @@ public class ProductDetailFragment extends Fragment { binding.etProductDesc.setText(p.getProdDesc()); binding.etProductPrice.setText(p.getProdPrice() != null ? p.getProdPrice().toString() : ""); preselectedCategoryId = p.getCategoryId() != null ? p.getCategoryId() : -1; - - // Refresh spinner selection once data is loaded - if (!categoryList.isEmpty()) { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, categoryList, - CategoryDTO::getCategoryName, "-- Select Category --", - preselectedCategoryId, CategoryDTO::getCategoryId); - } + updateCategorySpinner(); } else if (resource.status == Resource.Status.ERROR) { Toast.makeText(getContext(), "Failed to load product: " + resource.message, Toast.LENGTH_SHORT).show(); } }); } - /** - * Loads the product image from the backend. - */ private void loadProductImage() { - String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, prodId); + String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, viewModel.getProdId()); String token = tokenManager.getToken(); GlideUtils.loadImageWithToken(requireContext(), binding.ivProductImage, imageUrl, token, R.drawable.placeholder2, new GlideUtils.ImageLoadListener() { @@ -208,12 +182,9 @@ public class ProductDetailFragment extends Fragment { }); } - /** - * Performs image related actions (upload/delete) after product details are saved. - */ private void performPendingImageActions(String successMsg) { if (isImageRemoved) { - viewModel.deleteProductImage(prodId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteProductImage().observe(getViewLifecycleOwner(), resource -> { if (resource != null && resource.status != Resource.Status.LOADING) { if (resource.status == Resource.Status.SUCCESS) { Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); @@ -231,9 +202,6 @@ public class ProductDetailFragment extends Fragment { } } - /** - * Uploads the selected image file to the server. - */ private void uploadProductImageAndNavigate(Uri uri, String successMsg) { File file = FileUtils.getFileFromUri(requireContext(), uri); if (file == null) { @@ -245,7 +213,7 @@ public class ProductDetailFragment extends Fragment { RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri))); MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile); - viewModel.uploadProductImage(prodId, body).observe(getViewLifecycleOwner(), resource -> { + viewModel.uploadProductImage(body).observe(getViewLifecycleOwner(), resource -> { if (resource != null && resource.status != Resource.Status.LOADING) { if (resource.status == Resource.Status.SUCCESS) { Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show(); @@ -257,9 +225,6 @@ public class ProductDetailFragment extends Fragment { }); } - /** - * Validates input fields and saves product information to the backend. - */ private void saveProduct() { if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) return; @@ -276,39 +241,26 @@ public class ProductDetailFragment extends Fragment { String desc = binding.etProductDesc.getText().toString().trim(); BigDecimal price = new BigDecimal(binding.etProductPrice.getText().toString().trim()); - CategoryDTO category = categoryList.get(binding.spinnerProductCategory.getSelectedItemPosition() - 1); + CategoryDTO category = viewModel.getCategoryList().getValue().get(binding.spinnerProductCategory.getSelectedItemPosition() - 1); ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price); - if (isEditing) { - viewModel.updateProduct(prodId, dto).observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status != Resource.Status.LOADING) { - if (resource.status == Resource.Status.SUCCESS) { - performPendingImageActions("Updated"); - } else { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); + viewModel.saveProduct(dto).observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status != Resource.Status.LOADING) { + if (resource.status == Resource.Status.SUCCESS) { + if (resource.data != null) { + viewModel.setProdId(resource.data.getProdId()); } + performPendingImageActions(viewModel.isEditing() ? "Updated" : "Saved"); + } else { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); } - }); - } else { - viewModel.createProduct(dto).observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status != Resource.Status.LOADING) { - if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - prodId = resource.data.getProdId(); - performPendingImageActions("Saved"); - } else { - Toast.makeText(getContext(), "Error saving: " + resource.message, Toast.LENGTH_SHORT).show(); - } - } - }); - } + } + }); } - /** - * Displays a confirmation dialog before deleting the product. - */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () -> - viewModel.deleteProduct(prodId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteProduct().observe(getViewLifecycleOwner(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS) { navigateBack(); } else if (resource != null && resource.status == Resource.Status.ERROR) { @@ -317,9 +269,6 @@ public class ProductDetailFragment extends Fragment { })); } - /** - * Navigates back to the previous fragment. - */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductSupplierDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductSupplierDetailFragment.java index aa570d13..a0e7d1a6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductSupplierDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ProductSupplierDetailFragment.java @@ -15,9 +15,7 @@ import com.example.petstoremobile.utils.DialogUtils; import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.SpinnerUtils; -import com.example.petstoremobile.viewmodels.ProductSupplierViewModel; -import com.example.petstoremobile.viewmodels.ProductViewModel; -import com.example.petstoremobile.viewmodels.SupplierViewModel; +import com.example.petstoremobile.viewmodels.ProductSupplierDetailViewModel; import java.math.BigDecimal; import java.util.*; @@ -31,26 +29,15 @@ import dagger.hilt.android.AndroidEntryPoint; public class ProductSupplierDetailFragment extends Fragment { private FragmentProductSupplierDetailBinding binding; + private ProductSupplierDetailViewModel viewModel; - private boolean isEditing = false; - private long editProductId = -1; - private long editSupplierId = -1; private long preselectedProductId = -1; private long preselectedSupplierId = -1; - private List productList = new ArrayList<>(); - private List supplierList = new ArrayList<>(); - - private ProductSupplierViewModel psViewModel; - private ProductViewModel productViewModel; - private SupplierViewModel supplierViewModel; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - psViewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class); - productViewModel = new ViewModelProvider(this).get(ProductViewModel.class); - supplierViewModel = new ViewModelProvider(this).get(SupplierViewModel.class); + viewModel = new ViewModelProvider(this).get(ProductSupplierDetailViewModel.class); } @Override @@ -63,6 +50,7 @@ public class ProductSupplierDetailFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + observeViewModel(); loadSpinnersData(); handleArguments(); @@ -71,81 +59,59 @@ public class ProductSupplierDetailFragment extends Fragment { binding.btnDeletePS.setOnClickListener(v -> confirmDelete()); } + private void observeViewModel() { + viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner()); + viewModel.getSupplierList().observe(getViewLifecycleOwner(), list -> refreshSupplierSpinner()); + } + @Override public void onDestroyView() { super.onDestroyView(); binding = null; } - /** - * Fetches products and suppliers to populate the spinners. - */ private void loadSpinnersData() { - loadProducts(); - loadSuppliers(); - } - - /** - * Loads the list of products from the API. - */ - private void loadProducts() { - productViewModel.getAllProducts(null, null, 0, 200, "prodName").observe(getViewLifecycleOwner(), resource -> { + viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - productList = resource.data.getContent(); - refreshProductSpinner(); + viewModel.setProductList(resource.data.getContent()); + } + }); + viewModel.loadSuppliers().observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) { + viewModel.setSupplierList(resource.data.getContent()); } }); } private void refreshProductSpinner() { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, productList, + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, viewModel.getProductList().getValue(), ProductDTO::getProdName, "-- Select Product --", preselectedProductId, ProductDTO::getProdId); } - /** - * Loads the list of suppliers from the API. - */ - private void loadSuppliers() { - supplierViewModel.getAllSuppliers(0, 200, null, "supCompany").observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS && resource.data != null) { - supplierList = resource.data.getContent(); - refreshSupplierSpinner(); - } - }); - } - private void refreshSupplierSpinner() { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, supplierList, + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, viewModel.getSupplierList().getValue(), SupplierDTO::getSupCompany, "-- Select Supplier --", preselectedSupplierId, SupplierDTO::getSupId); } - /** - * Handles arguments to determine if the fragment is in edit or add mode. - */ private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("productId") && a.containsKey("supplierId")) { - isEditing = true; - editProductId = a.getLong("productId"); - editSupplierId = a.getLong("supplierId"); - preselectedProductId = editProductId; - preselectedSupplierId = editSupplierId; + long productId = a.getLong("productId"); + long supplierId = a.getLong("supplierId"); + viewModel.setEditMode(productId, supplierId); + preselectedProductId = productId; + preselectedSupplierId = supplierId; binding.tvPSMode.setText("Edit Product Supplier"); binding.btnDeletePS.setVisibility(View.VISIBLE); - } else { binding.tvPSMode.setText("Add Product Supplier"); binding.btnDeletePS.setVisibility(View.GONE); } } - - /** - * Validates input and saves the product-supplier to the backend. - */ private void save() { if (binding.spinnerPSProduct.getSelectedItemPosition() == 0) { Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show(); return; @@ -159,40 +125,25 @@ public class ProductSupplierDetailFragment extends Fragment { return; } - ProductDTO product = productList.get(binding.spinnerPSProduct.getSelectedItemPosition() - 1); - SupplierDTO supplier = supplierList.get(binding.spinnerPSSupplier.getSelectedItemPosition() - 1); + ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerPSProduct.getSelectedItemPosition() - 1); + SupplierDTO supplier = viewModel.getSupplierList().getValue().get(binding.spinnerPSSupplier.getSelectedItemPosition() - 1); BigDecimal cost = new BigDecimal(binding.etPSCost.getText().toString().trim()); - ProductSupplierDTO dto = new ProductSupplierDTO( - product.getProdId(), supplier.getSupId(), cost); + ProductSupplierDTO dto = new ProductSupplierDTO(product.getProdId(), supplier.getSupId(), cost); - if (isEditing) { - psViewModel.updateProductSupplier(editProductId, editSupplierId, dto).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { - Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } else { - psViewModel.createProductSupplier(dto).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { - Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } + viewModel.saveProductSupplier(dto).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS) { + Toast.makeText(getContext(), viewModel.isEditing() ? "Updated" : "Saved", Toast.LENGTH_SHORT).show(); + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); + } + }); } - /** - * Shows a confirmation dialog before deleting a product-supplier relationship. - */ private void confirmDelete() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Product Supplier", () -> - psViewModel.deleteProductSupplier(editProductId, editSupplierId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteProductSupplier().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS) { Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show(); navigateBack(); @@ -202,9 +153,6 @@ public class ProductSupplierDetailFragment extends Fragment { })); } - /** - * Navigates back to the previous screen. - */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PurchaseOrderDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PurchaseOrderDetailFragment.java index eb69bd16..90ebf645 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PurchaseOrderDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/PurchaseOrderDetailFragment.java @@ -14,7 +14,7 @@ import androidx.navigation.fragment.NavHostFragment; import com.example.petstoremobile.databinding.FragmentPurchaseOrderDetailBinding; import com.example.petstoremobile.dtos.PurchaseOrderDTO; import com.example.petstoremobile.utils.Resource; -import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel; +import com.example.petstoremobile.viewmodels.PurchaseOrderDetailViewModel; import dagger.hilt.android.AndroidEntryPoint; @@ -25,13 +25,13 @@ import dagger.hilt.android.AndroidEntryPoint; public class PurchaseOrderDetailFragment extends Fragment { private FragmentPurchaseOrderDetailBinding binding; - private PurchaseOrderViewModel viewModel; + private PurchaseOrderDetailViewModel viewModel; private long purchaseOrderId; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class); + viewModel = new ViewModelProvider(this).get(PurchaseOrderDetailViewModel.class); } /** @@ -67,7 +67,7 @@ public class PurchaseOrderDetailFragment extends Fragment { } private void loadPurchaseOrderData() { - viewModel.getPurchaseOrderById(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadPurchaseOrder(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; if (resource.status == Resource.Status.SUCCESS && resource.data != null) { PurchaseOrderDTO po = resource.data; diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/RefundFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/RefundFragment.java index 8d05252c..d94b0041 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/RefundFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/RefundFragment.java @@ -12,7 +12,8 @@ import androidx.navigation.fragment.NavHostFragment; import com.example.petstoremobile.R; import com.example.petstoremobile.databinding.FragmentRefundBinding; import com.example.petstoremobile.dtos.SaleDTO; -import com.example.petstoremobile.viewmodels.SaleViewModel; +import com.example.petstoremobile.viewmodels.RefundViewModel; +import com.example.petstoremobile.utils.Resource; import dagger.hilt.android.AndroidEntryPoint; import java.math.BigDecimal; import java.math.RoundingMode; @@ -22,53 +23,23 @@ import java.util.*; public class RefundFragment extends Fragment { private FragmentRefundBinding binding; - private SaleViewModel saleViewModel; - private SaleDTO currentSale; - private List allSales = new ArrayList<>(); - - // Items available to refund (after accounting for previous refunds) - private List availableItems = new ArrayList<>(); - // Items user has added to refund cart - private List refundCart = new ArrayList<>(); + private RefundViewModel viewModel; private final String[] PAYMENT_METHODS = {"Cash", "Card"}; - // Inner class to track refund items - static class RefundItem { - long prodId; - String productName; - int quantity; - BigDecimal unitPrice; - - RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) { - this.prodId = prodId; - this.productName = productName; - this.quantity = quantity; - this.unitPrice = unitPrice; - } - - BigDecimal getTotal() { - return unitPrice != null - ? unitPrice.multiply(BigDecimal.valueOf(quantity)) - : BigDecimal.ZERO; - } - } - @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentRefundBinding.inflate(inflater, container, false); - saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class); + viewModel = new ViewModelProvider(this).get(RefundViewModel.class); setupSpinner(); + observeViewModel(); loadAllSales(); - // Pre-fill sale ID if passed from SaleFragment Bundle args = getArguments(); if (args != null && args.containsKey("saleId")) { - long saleId = args.getLong("saleId"); - binding.etRefundSaleId.setText(String.valueOf(saleId)); - // Auto-load after sales are fetched + binding.etRefundSaleId.setText(String.valueOf(args.getLong("saleId"))); } binding.btnLoadSale.setOnClickListener(v -> loadSale()); @@ -83,27 +54,25 @@ public class RefundFragment extends Fragment { android.R.layout.simple_spinner_item, PAYMENT_METHODS)); } + private void observeViewModel() { + viewModel.getAvailableItems().observe(getViewLifecycleOwner(), items -> renderOriginalItems()); + viewModel.getRefundCart().observe(getViewLifecycleOwner(), cart -> { + renderRefundCart(); + updateRefundTotal(); + renderOriginalItems(); // Re-render to reflect quantities in cart + }); + } + private void loadAllSales() { - saleViewModel.getAllSales(0, 1000, null, null, null, "saleDate,desc") - .observe(getViewLifecycleOwner(), resource -> { - if (resource != null) { - switch (resource.status) { - case SUCCESS: - if (resource.data != null) { - allSales = resource.data.getContent(); - // Auto-load if saleId was pre-filled - Bundle args = getArguments(); - if (args != null && args.containsKey("saleId")) { - loadSale(); - } - } - break; - case ERROR: - Log.e("Refund", "Failed to load sales: " + resource.message); - break; - } - } - }); + viewModel.loadAllSales().observe(getViewLifecycleOwner(), resource -> { + if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { + viewModel.setAllSales(resource.data.getContent()); + Bundle args = getArguments(); + if (args != null && args.containsKey("saleId")) { + loadSale(); + } + } + }); } private void loadSale() { @@ -120,11 +89,12 @@ public class RefundFragment extends Fragment { return; } - // Find sale in loaded list SaleDTO found = null; - for (SaleDTO s : allSales) { - if (s.getSaleId() != null && s.getSaleId() == saleId) { - found = s; break; + if (viewModel.getAllSalesList() != null) { + for (SaleDTO s : viewModel.getAllSalesList()) { + if (s.getSaleId() != null && s.getSaleId() == saleId) { + found = s; break; + } } } @@ -139,9 +109,9 @@ public class RefundFragment extends Fragment { return; } - currentSale = found; + viewModel.setCurrentSale(found); + SaleDTO currentSale = viewModel.getCurrentSale(); - // Show sale info binding.tvSaleInfo.setVisibility(View.VISIBLE); binding.tvSaleInfo.setText("Sale #" + currentSale.getSaleId() + " | " + (currentSale.getSaleDate() != null @@ -151,7 +121,6 @@ public class RefundFragment extends Fragment { + " | Total: $" + currentSale.getTotalAmount() + " | Payment: " + currentSale.getPaymentMethod()); - // Pre-select payment method if (currentSale.getPaymentMethod() != null) { for (int i = 0; i < PAYMENT_METHODS.length; i++) { if (PAYMENT_METHODS[i].equalsIgnoreCase(currentSale.getPaymentMethod())) { @@ -160,85 +129,40 @@ public class RefundFragment extends Fragment { } } - // Build refundable items accounting for previous refunds - buildRefundableItems(); - - if (availableItems.isEmpty()) { - Toast.makeText(getContext(), - "This sale has no remaining refundable items", Toast.LENGTH_LONG).show(); + if (viewModel.getAvailableItems().getValue() == null || viewModel.getAvailableItems().getValue().isEmpty()) { + Toast.makeText(getContext(), "This sale has no remaining refundable items", Toast.LENGTH_LONG).show(); return; } - // Reset refund cart - refundCart.clear(); - - // Show cards binding.cardOriginalItems.setVisibility(View.VISIBLE); binding.cardRefundItems.setVisibility(View.VISIBLE); binding.cardPayment.setVisibility(View.VISIBLE); binding.btnProcessRefund.setVisibility(View.VISIBLE); - - renderOriginalItems(); - renderRefundCart(); - updateRefundTotal(); - } - - private void buildRefundableItems() { - availableItems.clear(); - if (currentSale.getItems() == null) return; - - // Find all previous refunds for this sale - Map alreadyRefunded = new HashMap<>(); - for (SaleDTO s : allSales) { - if (Boolean.TRUE.equals(s.getIsRefund()) - && currentSale.getSaleId().equals(s.getOriginalSaleId()) - && s.getItems() != null) { - for (SaleDTO.SaleItemDTO item : s.getItems()) { - if (item.getProdId() != null && item.getQuantity() != null) { - alreadyRefunded.merge(item.getProdId(), - Math.abs(item.getQuantity()), Integer::sum); - } - } - } - } - - // Build available items - for (SaleDTO.SaleItemDTO item : currentSale.getItems()) { - if (item.getProdId() == null || item.getQuantity() == null) continue; - int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0); - int remaining = item.getQuantity() - refunded; - if (remaining > 0) { - availableItems.add(new RefundItem( - item.getProdId(), - item.getProductName() != null ? item.getProductName() : "Unknown", - remaining, - item.getUnitPrice() - )); - } - } } private void renderOriginalItems() { binding.llOriginalItems.removeAllViews(); + List available = viewModel.getAvailableItems().getValue(); + if (available == null) return; - // Header addTableHeader(binding.llOriginalItems); - for (RefundItem item : availableItems) { - // Calculate pending in cart - int pendingQty = 0; - for (RefundItem r : refundCart) { - if (r.prodId == item.prodId) { pendingQty = r.quantity; break; } + for (RefundViewModel.RefundItem item : available) { + int inCart = 0; + if (viewModel.getRefundCart().getValue() != null) { + for (RefundViewModel.RefundItem r : viewModel.getRefundCart().getValue()) { + if (r.prodId == item.prodId) { inCart = r.quantity; break; } + } } - int displayQty = item.quantity - pendingQty; + int displayQty = item.quantity - inCart; if (displayQty <= 0) continue; LinearLayout row = buildItemRow( item.productName, displayQty, item.unitPrice, - true, // show add button - () -> showQuantityDialog(item) + true, + () -> showQuantityDialog(item, displayQty) ); binding.llOriginalItems.addView(row); } @@ -246,8 +170,9 @@ public class RefundFragment extends Fragment { private void renderRefundCart() { binding.llRefundItems.removeAllViews(); + List cart = viewModel.getRefundCart().getValue(); - if (refundCart.isEmpty()) { + if (cart == null || cart.isEmpty()) { TextView empty = new TextView(getContext()); empty.setText("No items added to refund yet"); empty.setTextColor(0xFF888780); @@ -258,18 +183,13 @@ public class RefundFragment extends Fragment { addTableHeader(binding.llRefundItems); - for (RefundItem item : refundCart) { + for (RefundViewModel.RefundItem item : cart) { LinearLayout row = buildItemRow( item.productName, item.quantity, item.unitPrice, - false, // show remove button - () -> { - refundCart.remove(item); - renderOriginalItems(); - renderRefundCart(); - updateRefundTotal(); - } + false, + () -> viewModel.removeFromCart(item) ); binding.llRefundItems.addView(row); } @@ -342,24 +262,10 @@ public class RefundFragment extends Fragment { return row; } - private void showQuantityDialog(RefundItem item) { - // Calculate how many are already in cart - int inCart = 0; - for (RefundItem r : refundCart) { - if (r.prodId == item.prodId) { inCart = r.quantity; break; } - } - int available = item.quantity - inCart; - if (available <= 0) { - Toast.makeText(getContext(), "All units already added to refund", - Toast.LENGTH_SHORT).show(); - return; - } - - // Build dialog + private void showQuantityDialog(RefundViewModel.RefundItem item, int available) { AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); builder.setTitle("Refund Quantity"); - builder.setMessage("Product: " + item.productName - + "\nAvailable: " + available); + builder.setMessage("Product: " + item.productName + "\nAvailable: " + available); EditText input = new EditText(getContext()); input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER); @@ -377,36 +283,15 @@ public class RefundFragment extends Fragment { return; } if (qty <= 0) { - Toast.makeText(getContext(), "Quantity must be at least 1", - Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), "Quantity must be at least 1", Toast.LENGTH_SHORT).show(); return; } if (qty > available) { - Toast.makeText(getContext(), "Cannot exceed " + available, - Toast.LENGTH_SHORT).show(); + Toast.makeText(getContext(), "Cannot exceed " + available, Toast.LENGTH_SHORT).show(); return; } - // Add or merge into cart - boolean merged = false; - for (int i = 0; i < refundCart.size(); i++) { - if (refundCart.get(i).prodId == item.prodId) { - RefundItem existing = refundCart.get(i); - refundCart.set(i, new RefundItem(existing.prodId, - existing.productName, - existing.quantity + qty, - existing.unitPrice)); - merged = true; break; - } - } - if (!merged) { - refundCart.add(new RefundItem(item.prodId, item.productName, - qty, item.unitPrice)); - } - - renderOriginalItems(); - renderRefundCart(); - updateRefundTotal(); + viewModel.addToCart(item, qty); }); builder.setNegativeButton("Cancel", null); @@ -415,31 +300,31 @@ public class RefundFragment extends Fragment { private void updateRefundTotal() { BigDecimal total = BigDecimal.ZERO; - for (RefundItem item : refundCart) total = total.add(item.getTotal()); + List cart = viewModel.getRefundCart().getValue(); + if (cart != null) { + for (RefundViewModel.RefundItem item : cart) total = total.add(item.getTotal()); + } binding.tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP)); } private void processRefund() { - if (currentSale == null) { + if (viewModel.getCurrentSale() == null) { Toast.makeText(getContext(), "Load a sale first", Toast.LENGTH_SHORT).show(); return; } - if (refundCart.isEmpty()) { - Toast.makeText(getContext(), "Add at least one item to refund", - Toast.LENGTH_SHORT).show(); + if (viewModel.getRefundCart().getValue() == null || viewModel.getRefundCart().getValue().isEmpty()) { + Toast.makeText(getContext(), "Add at least one item to refund", Toast.LENGTH_SHORT).show(); return; } String payment = PAYMENT_METHODS[binding.spinnerRefundPayment.getSelectedItemPosition()]; - - // Confirm dialog BigDecimal total = BigDecimal.ZERO; - for (RefundItem item : refundCart) total = total.add(item.getTotal()); + for (RefundViewModel.RefundItem item : viewModel.getRefundCart().getValue()) total = total.add(item.getTotal()); final BigDecimal finalTotal = total; new AlertDialog.Builder(requireContext()) .setTitle("Confirm Refund") - .setMessage("Process refund for Sale #" + currentSale.getSaleId() + .setMessage("Process refund for Sale #" + viewModel.getCurrentSale().getSaleId() + "?\nRefund amount: $" + finalTotal.setScale(2, RoundingMode.HALF_UP)) .setPositiveButton("Yes", (d, w) -> submitRefund(payment)) .setNegativeButton("No", null) @@ -447,41 +332,13 @@ public class RefundFragment extends Fragment { } private void submitRefund(String payment) { - // Build sale items list - List items = new ArrayList<>(); - for (RefundItem item : refundCart) { - // Backend expects negative quantity for refunds - items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity)); - } - - SaleDTO dto = new SaleDTO( - currentSale.getStoreId(), - payment, - items, - true, // isRefund = true - currentSale.getSaleId(), // originalSaleId - null // no customer needed - ); - - Log.d("REFUND", "Submitting refund for saleId=" + currentSale.getSaleId() - + " items=" + items.size()); - - saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> { + viewModel.submitRefund(payment).observe(getViewLifecycleOwner(), resource -> { if (resource != null) { - switch (resource.status) { - case SUCCESS: - if (resource.data != null) { - Toast.makeText(getContext(), - "Refund #" + resource.data.getSaleId() + " processed successfully!", - Toast.LENGTH_LONG).show(); - navigateBack(); - } - break; - case ERROR: - Log.e("REFUND", "Error: " + resource.message); - Toast.makeText(getContext(), "Error: " + resource.message, - Toast.LENGTH_LONG).show(); - break; + if (resource.status == Resource.Status.SUCCESS) { + Toast.makeText(getContext(), "Refund processed successfully!", Toast.LENGTH_LONG).show(); + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show(); } } }); diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java index 690a2fa1..7ae18823 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SaleDetailFragment.java @@ -11,7 +11,7 @@ import androidx.navigation.fragment.NavHostFragment; import com.example.petstoremobile.R; import com.example.petstoremobile.databinding.FragmentSaleDetailBinding; import com.example.petstoremobile.dtos.*; -import com.example.petstoremobile.viewmodels.*; +import com.example.petstoremobile.viewmodels.SaleDetailViewModel; import com.example.petstoremobile.utils.SpinnerUtils; import com.example.petstoremobile.utils.DialogUtils; import com.example.petstoremobile.utils.Resource; @@ -24,18 +24,7 @@ import java.util.*; public class SaleDetailFragment extends Fragment { private FragmentSaleDetailBinding binding; - private SaleViewModel saleViewModel; - private StoreViewModel storeViewModel; - private CustomerViewModel customerViewModel; - private ProductViewModel productViewModel; - - private boolean viewOnly = false; - private long saleId = -1; - - private List storeList = new ArrayList<>(); - private List customerList = new ArrayList<>(); - private List productList = new ArrayList<>(); - private List cartItems = new ArrayList<>(); + private SaleDetailViewModel viewModel; private final String[] PAYMENT_METHODS = { "Cash", "Card"}; @@ -43,17 +32,14 @@ public class SaleDetailFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentSaleDetailBinding.inflate(inflater, container, false); - - saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class); - storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class); - customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class); - productViewModel = new ViewModelProvider(this).get(ProductViewModel.class); + viewModel = new ViewModelProvider(this).get(SaleDetailViewModel.class); SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPaymentMethod, PAYMENT_METHODS); + observeViewModel(); handleArguments(); - if (!viewOnly) { + if (!viewModel.isViewOnly()) { loadData(); setupAddItem(); } @@ -65,20 +51,42 @@ public class SaleDetailFragment extends Fragment { return binding.getRoot(); } + private void observeViewModel() { + viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> { + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, list, + DropdownDTO::getLabel, "-- Select Store --", -1L, DropdownDTO::getId); + }); + + viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> { + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, list, + DropdownDTO::getLabel, "-- No Customer --", -1L, DropdownDTO::getId); + }); + + viewModel.getProductList().observe(getViewLifecycleOwner(), list -> { + SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, list, + ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId); + }); + + viewModel.getCartItems().observe(getViewLifecycleOwner(), items -> { + renderCartItems(); + updateTotal(); + }); + } + private void handleArguments() { Bundle a = getArguments(); if (a != null && a.containsKey("saleId")) { - saleId = a.getLong("saleId"); - viewOnly = a.getBoolean("viewOnly", false); + long saleId = a.getLong("saleId"); + boolean viewOnly = a.getBoolean("viewOnly", false); + viewModel.setSaleId(saleId, viewOnly); + binding.tvSaleMode.setText("Sale #" + saleId); binding.tvSaleDetailId.setText("ID: " + saleId); - // Show refund button for existing non-refund sales if (!a.getBoolean("isRefund", false)) { binding.btnRefundSale.setVisibility(View.VISIBLE); } - // Hide save and input controls for view only if (viewOnly) { binding.btnSaveSale.setVisibility(View.GONE); UIUtils.setViewsEnabled(false, @@ -89,9 +97,9 @@ public class SaleDetailFragment extends Fragment { binding.llExtraInfo.setVisibility(View.VISIBLE); } - // Load sale details loadSaleDetails(); } else { + viewModel.setSaleId(-1, false); binding.tvSaleMode.setText("New Sale"); binding.tvSaleDetailId.setVisibility(View.GONE); binding.btnRefundSale.setVisibility(View.GONE); @@ -100,82 +108,47 @@ public class SaleDetailFragment extends Fragment { } private void loadData() { - loadStores(); - loadCustomers(); - loadProducts(); - } - - private void loadStores() { - storeViewModel.getStoreDropdowns().observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { - storeList = resource.data; - if (binding != null) { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, storeList, - DropdownDTO::getLabel, "-- Select Store --", -1L, DropdownDTO::getId); - } - } + viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setStoreList(resource.data); }); - } - - private void loadCustomers() { - customerViewModel.getCustomerDropdowns().observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { - customerList = resource.data; - if (binding != null) { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, customerList, - DropdownDTO::getLabel, "-- No Customer --", -1L, DropdownDTO::getId); - } - } + viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setCustomerList(resource.data); }); - } - - private void loadProducts() { - productViewModel.getAllProducts(null, null, 0, 200, null).observe(getViewLifecycleOwner(), resource -> { - if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { - productList = resource.data.getContent(); - if (binding != null) { - SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, productList, - ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId); - } - } + viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setProductList(resource.data.getContent()); }); } private void loadSaleDetails() { - saleViewModel.getSaleById(saleId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadSaleDetails().observe(getViewLifecycleOwner(), resource -> { if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) { SaleDTO sale = resource.data; - if (binding != null) { - binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount()); - binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount())); - - if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) { - binding.llCouponDiscount.setVisibility(View.VISIBLE); - binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount()); - } else { - binding.llCouponDiscount.setVisibility(View.GONE); - } + binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount()); + binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount())); + + if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) { + binding.llCouponDiscount.setVisibility(View.VISIBLE); + binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount()); + } else { + binding.llCouponDiscount.setVisibility(View.GONE); + } - if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) { - binding.llEmployeeDiscount.setVisibility(View.VISIBLE); - binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount()); - } else { - binding.llEmployeeDiscount.setVisibility(View.GONE); - } + if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) { + binding.llEmployeeDiscount.setVisibility(View.VISIBLE); + binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount()); + } else { + binding.llEmployeeDiscount.setVisibility(View.GONE); + } - binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—"); - binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0)); + binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—"); + binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0)); - SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod()); + SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod()); - // Display items - if (sale.getItems() != null) { - binding.llSaleItems.removeAllViews(); - for (SaleDTO.SaleItemDTO item : sale.getItems()) { - addItemRow(item.getProductName(), - Math.abs(item.getQuantity()), - item.getUnitPrice()); - } + if (sale.getItems() != null) { + binding.llSaleItems.removeAllViews(); + for (SaleDTO.SaleItemDTO item : sale.getItems()) { + addItemRow(item.getProductName(), Math.abs(item.getQuantity()), item.getUnitPrice()); } } } @@ -194,31 +167,46 @@ public class SaleDetailFragment extends Fragment { return; } int qty; - try { - qty = Integer.parseInt(qtyStr); - } catch (Exception e) { + try { qty = Integer.parseInt(qtyStr); } + catch (Exception e) { binding.etSaleQuantity.setError("Invalid quantity"); return; } - ProductDTO product = productList.get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1); + ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1); - // Check if product already in cart - for (SaleDTO.SaleItemDTO existing : cartItems) { + for (SaleDTO.SaleItemDTO existing : viewModel.getCartItems().getValue()) { if (existing.getProdId().equals(product.getProdId())) { Toast.makeText(getContext(), "Product already added", Toast.LENGTH_SHORT).show(); return; } } - SaleDTO.SaleItemDTO item = new SaleDTO.SaleItemDTO(product.getProdId(), qty); - cartItems.add(item); - addItemRow(product.getProdName(), qty, product.getProdPrice()); - updateTotal(); + viewModel.addToCart(new SaleDTO.SaleItemDTO(product.getProdId(), qty)); binding.etSaleQuantity.setText(""); }); } + private void renderCartItems() { + binding.llSaleItems.removeAllViews(); + List items = viewModel.getCartItems().getValue(); + List products = viewModel.getProductList().getValue(); + if (items == null || products == null) return; + + for (SaleDTO.SaleItemDTO item : items) { + String name = "Unknown"; + BigDecimal price = BigDecimal.ZERO; + for (ProductDTO p : products) { + if (p.getProdId().equals(item.getProdId())) { + name = p.getProdName(); + price = p.getProdPrice(); + break; + } + } + addItemRow(name, item.getQuantity(), price); + } + } + private void addItemRow(String name, int qty, BigDecimal price) { if (getContext() == null) return; LinearLayout row = new LinearLayout(getContext()); @@ -226,18 +214,15 @@ public class SaleDetailFragment extends Fragment { row.setPadding(0, 8, 0, 8); TextView tvName = new TextView(getContext()); - tvName.setLayoutParams(new LinearLayout.LayoutParams( - 0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f)); + tvName.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f)); tvName.setText(name); TextView tvQty = new TextView(getContext()); - tvQty.setLayoutParams(new LinearLayout.LayoutParams( - 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)); + tvQty.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)); tvQty.setText("x" + qty); TextView tvPrice = new TextView(getContext()); - tvPrice.setLayoutParams(new LinearLayout.LayoutParams( - 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)); + tvPrice.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)); tvPrice.setText(price != null ? "$" + price : ""); row.addView(tvName); @@ -247,16 +232,7 @@ public class SaleDetailFragment extends Fragment { } private void updateTotal() { - BigDecimal total = BigDecimal.ZERO; - for (SaleDTO.SaleItemDTO item : cartItems) { - for (ProductDTO p : productList) { - if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) { - total = total.add(p.getProdPrice() - .multiply(BigDecimal.valueOf(item.getQuantity()))); - break; - } - } - } + BigDecimal total = viewModel.calculateSubtotal(); binding.tvSaleSubtotal.setText("$" + total); binding.tvSaleDetailTotal.setText("Total: $" + total); } @@ -266,40 +242,28 @@ public class SaleDetailFragment extends Fragment { Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return; } - if (cartItems.isEmpty()) { + if (viewModel.getCartItems().getValue() == null || viewModel.getCartItems().getValue().isEmpty()) { Toast.makeText(getContext(), "Add at least one item", Toast.LENGTH_SHORT).show(); return; } - DropdownDTO store = storeList.get(binding.spinnerSaleStore.getSelectedItemPosition() - 1); + DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerSaleStore.getSelectedItemPosition() - 1); String payment = PAYMENT_METHODS[binding.spinnerPaymentMethod.getSelectedItemPosition()]; - // Optional customer Long customerId = null; if (binding.spinnerSaleCustomer.getSelectedItemPosition() > 0) { - customerId = customerList.get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1) - .getId(); + customerId = viewModel.getCustomerList().getValue().get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1).getId(); } - SaleDTO dto = new SaleDTO( - store.getId(), - payment, - cartItems, - false, - null, - customerId); + SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId); - saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> { + viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> { if (resource != null) { - switch (resource.status) { - case SUCCESS: - Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show(); - navigateBack(); - break; - case ERROR: - Log.e("SALE_SAVE", "Error: " + resource.message); - DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message); - break; + if (resource.status == Resource.Status.SUCCESS) { + Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show(); + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message); } } }); @@ -309,7 +273,7 @@ public class SaleDetailFragment extends Fragment { DialogUtils.showConfirmDialog(requireContext(), "Process Refund", "Are you sure you want to process a refund for this sale?", () -> { Bundle args = new Bundle(); - args.putLong("saleId", saleId); + args.putLong("saleId", viewModel.getSaleId()); NavHostFragment.findNavController(this).navigate(R.id.nav_refund, args); }); } diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java index 49c51141..7fdaae9b 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/ServiceDetailFragment.java @@ -20,7 +20,7 @@ import com.example.petstoremobile.utils.ActivityLogger; import com.example.petstoremobile.utils.DialogUtils; import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.Resource; -import com.example.petstoremobile.viewmodels.ServiceViewModel; +import com.example.petstoremobile.viewmodels.ServiceDetailViewModel; import dagger.hilt.android.AndroidEntryPoint; @@ -31,15 +31,12 @@ import dagger.hilt.android.AndroidEntryPoint; public class ServiceDetailFragment extends Fragment { private FragmentServiceDetailBinding binding; - private long serviceId; - private boolean isEditing = false; - - private ServiceViewModel viewModel; + private ServiceDetailViewModel viewModel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - viewModel = new ViewModelProvider(this).get(ServiceViewModel.class); + viewModel = new ViewModelProvider(this).get(ServiceDetailViewModel.class); } @Override @@ -53,10 +50,8 @@ public class ServiceDetailFragment extends Fragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - //get controls from layout and display the view depending on the mode handleArguments(); - //set button click listeners binding.btnBack.setOnClickListener(v -> navigateBack()); binding.btnSaveService.setOnClickListener(v -> saveService()); binding.btnDeleteService.setOnClickListener(v -> deleteService()); @@ -68,63 +63,44 @@ public class ServiceDetailFragment extends Fragment { binding = null; } - /** - * Handles the saving of service data (adding or updating). - */ private void saveService() { - // Validates all fields using InputValidator if (!InputValidator.isNotEmpty(binding.etServiceName, "Service Name")) return; if (!InputValidator.isNotEmpty(binding.etServiceDesc, "Description")) return; if (!InputValidator.isPositiveInteger(binding.etServiceDuration, "Duration")) return; if (!InputValidator.isPositiveDecimal(binding.etServicePrice, "Price")) return; - //get all the values from the fields String name = binding.etServiceName.getText().toString().trim(); String desc = binding.etServiceDesc.getText().toString().trim(); int duration = Integer.parseInt(binding.etServiceDuration.getText().toString().trim()); double price = Double.parseDouble(binding.etServicePrice.getText().toString().trim()); - //create a service object to send to the API ServiceDTO serviceDTO = new ServiceDTO(); serviceDTO.setServiceName(name); serviceDTO.setServiceDesc(desc); serviceDTO.setServiceDuration(duration); serviceDTO.setServicePrice(price); - //check if the service is being edited or added - if (isEditing) { - // Update existing service - serviceDTO.setServiceId(serviceId); - viewModel.updateService(serviceId, serviceDTO).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { - ActivityLogger.logChange(requireContext(), "Service", "UPDATED", (int) serviceId); + viewModel.saveService(serviceDTO).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS) { + if (viewModel.isEditing()) { + ActivityLogger.logChange(requireContext(), "Service", "UPDATED", (int) viewModel.getServiceId()); Toast.makeText(getContext(), "Service updated successfully!", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } else { - viewModel.createService(serviceDTO).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { + } else { ActivityLogger.log(requireContext(), "Added new Service: " + name); Toast.makeText(getContext(), "Service added successfully!", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); } - }); - } + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); + } + }); } - /** - * Displays a confirmation dialog and handles the deletion of a service. - */ private void deleteService() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Service", () -> - viewModel.deleteService(serviceId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteService().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS) { - ActivityLogger.logChange(requireContext(), "Service", "DELETED", (int) serviceId); + ActivityLogger.logChange(requireContext(), "Service", "DELETED", (int) viewModel.getServiceId()); Toast.makeText(getContext(), "Service deleted successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else if (resource.status == Resource.Status.ERROR) { @@ -133,30 +109,20 @@ public class ServiceDetailFragment extends Fragment { })); } - /** - * Navigates back to the previous screen. - */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } - /** - * Handles arguments passed to the fragment to determine if it's in edit or add mode. - */ private void handleArguments() { - // Service is being edited if the bundle contains a serviceId if (getArguments() != null && getArguments().containsKey("serviceId")) { - // Get service data from arguments and populate fields - isEditing = true; - serviceId = getArguments().getLong("serviceId"); + long serviceId = getArguments().getLong("serviceId"); + viewModel.setServiceId(serviceId); binding.tvMode.setText("Edit Service"); binding.tvServiceId.setText("ID: " + serviceId); binding.btnDeleteService.setVisibility(View.VISIBLE); loadServiceData(); } else { - // Service is being added - // Set default values for add a new service - isEditing = false; + viewModel.setServiceId(-1); binding.tvMode.setText("Add Service"); binding.tvServiceId.setVisibility(View.GONE); binding.btnDeleteService.setVisibility(View.GONE); @@ -164,11 +130,8 @@ public class ServiceDetailFragment extends Fragment { } } - /** - * Fetches specific service details from the backend using the ID. - */ private void loadServiceData() { - viewModel.getServiceById(serviceId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadService().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; if (resource.status == Resource.Status.SUCCESS && resource.data != null) { ServiceDTO s = resource.data; diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/StaffDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/StaffDetailFragment.java index 508282bc..0013a277 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/StaffDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/StaffDetailFragment.java @@ -1,7 +1,6 @@ package com.example.petstoremobile.fragments.listfragments.detailfragments; import android.os.Bundle; -import android.util.Log; import android.view.*; import android.widget.*; import androidx.annotation.NonNull; @@ -12,16 +11,15 @@ import androidx.navigation.fragment.NavHostFragment; import com.example.petstoremobile.R; import com.example.petstoremobile.databinding.FragmentStaffDetailBinding; import com.example.petstoremobile.dtos.EmployeeDTO; -import com.example.petstoremobile.viewmodels.EmployeeViewModel; +import com.example.petstoremobile.viewmodels.StaffDetailViewModel; +import com.example.petstoremobile.utils.Resource; import dagger.hilt.android.AndroidEntryPoint; @AndroidEntryPoint public class StaffDetailFragment extends Fragment { private FragmentStaffDetailBinding binding; - private EmployeeViewModel employeeViewModel; - private long employeeId = -1; - private boolean isEditing = false; + private StaffDetailViewModel viewModel; private final String[] ROLES = {"STAFF", "ADMIN"}; private final String[] STATUSES = {"Active", "Inactive"}; @@ -30,7 +28,7 @@ public class StaffDetailFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentStaffDetailBinding.inflate(inflater, container, false); - employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class); + viewModel = new ViewModelProvider(this).get(StaffDetailViewModel.class); setupSpinners(); handleArguments(); @@ -51,8 +49,8 @@ public class StaffDetailFragment extends Fragment { private void handleArguments() { Bundle a = getArguments(); if (a != null && a.getBoolean("isEditing", false)) { - isEditing = true; - employeeId = a.getLong("employeeId", -1); + long employeeId = a.getLong("employeeId", -1); + viewModel.setEmployeeId(employeeId, true); binding.tvStaffMode.setText("Edit Staff Account"); binding.tvStaffId.setText("ID: " + employeeId); @@ -64,7 +62,6 @@ public class StaffDetailFragment extends Fragment { binding.etStaffPhone.setText(a.getString("phone", "")); binding.btnDeleteStaff.setVisibility(View.VISIBLE); - // Pre-fill role String role = a.getString("role", "STAFF"); for (int i = 0; i < ROLES.length; i++) { if (ROLES[i].equals(role)) { @@ -73,13 +70,11 @@ public class StaffDetailFragment extends Fragment { } } - // Pre-fill status boolean active = a.getBoolean("active", true); binding.spinnerStaffStatus.setSelection(active ? 0 : 1); } else { - isEditing = false; - employeeId = -1; + viewModel.setEmployeeId(-1, false); binding.tvStaffMode.setText("Add Staff Account"); binding.btnDeleteStaff.setVisibility(View.GONE); binding.tvStaffId.setVisibility(View.GONE); @@ -97,12 +92,11 @@ public class StaffDetailFragment extends Fragment { String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()]; boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0; - // Validation if (username.isEmpty()) { binding.etStaffUsername.setError("Required"); return; } - if (!isEditing && password.isEmpty()) { + if (!viewModel.isEditing() && password.isEmpty()) { binding.etStaffPassword.setError("Required for new account"); return; } - if (!isEditing && password.length() < 6) { + if (!viewModel.isEditing() && password.length() < 6) { binding.etStaffPassword.setError("At least 6 characters"); return; } if (firstName.isEmpty()) { binding.etStaffFirstName.setError("Required"); return; } @@ -121,35 +115,16 @@ public class StaffDetailFragment extends Fragment { active ); - if (isEditing && employeeId > 0) { - employeeViewModel.updateEmployee(employeeId, dto).observe(getViewLifecycleOwner(), resource -> { - if (resource != null) { - switch (resource.status) { - case SUCCESS: - Toast.makeText(getContext(), "Updated successfully", Toast.LENGTH_SHORT).show(); - navigateBack(); - break; - case ERROR: - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show(); - break; - } + viewModel.saveEmployee(dto).observe(getViewLifecycleOwner(), resource -> { + if (resource != null) { + if (resource.status == Resource.Status.SUCCESS) { + Toast.makeText(getContext(), viewModel.isEditing() ? "Updated successfully" : "Staff account created", Toast.LENGTH_SHORT).show(); + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show(); } - }); - } else { - employeeViewModel.createEmployee(dto).observe(getViewLifecycleOwner(), resource -> { - if (resource != null) { - switch (resource.status) { - case SUCCESS: - Toast.makeText(getContext(), "Staff account created", Toast.LENGTH_SHORT).show(); - navigateBack(); - break; - case ERROR: - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show(); - break; - } - } - }); - } + } + }); } private void confirmDelete() { @@ -157,16 +132,13 @@ public class StaffDetailFragment extends Fragment { .setTitle("Delete Staff Account?") .setMessage("This will permanently delete this staff account.") .setPositiveButton("Yes", (d, w) -> - employeeViewModel.deleteEmployee(employeeId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteEmployee().observe(getViewLifecycleOwner(), resource -> { if (resource != null) { - switch (resource.status) { - case SUCCESS: - navigateBack(); - break; - case ERROR: - Toast.makeText(getContext(), "Delete failed: " + resource.message, - Toast.LENGTH_SHORT).show(); - break; + if (resource.status == Resource.Status.SUCCESS) { + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Delete failed: " + resource.message, + Toast.LENGTH_SHORT).show(); } } })) diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java index 4935cb8b..62c7c381 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/listfragments/detailfragments/SupplierDetailFragment.java @@ -20,7 +20,7 @@ import com.example.petstoremobile.utils.DialogUtils; import com.example.petstoremobile.utils.InputValidator; import com.example.petstoremobile.utils.Resource; import com.example.petstoremobile.utils.UIUtils; -import com.example.petstoremobile.viewmodels.SupplierViewModel; +import com.example.petstoremobile.viewmodels.SupplierDetailViewModel; import dagger.hilt.android.AndroidEntryPoint; @@ -31,15 +31,12 @@ import dagger.hilt.android.AndroidEntryPoint; public class SupplierDetailFragment extends Fragment { private FragmentSupplierDetailBinding binding; - private long supId; - private boolean isEditing = false; - - private SupplierViewModel viewModel; + private SupplierDetailViewModel viewModel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - viewModel = new ViewModelProvider(this).get(SupplierViewModel.class); + viewModel = new ViewModelProvider(this).get(SupplierDetailViewModel.class); } @Override @@ -53,12 +50,9 @@ public class SupplierDetailFragment extends Fragment { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - // Add phone number formatting (CA) and limit length to 14 characters UIUtils.formatPhoneInput(binding.etSupPhone); - handleArguments(); - //set button click listeners binding.btnBack.setOnClickListener(v -> navigateBack()); binding.btnSaveSupplier.setOnClickListener(v -> saveSupplier()); binding.btnDeleteSupplier.setOnClickListener(v -> deleteSupplier()); @@ -70,25 +64,19 @@ public class SupplierDetailFragment extends Fragment { binding = null; } - /** - * Handles the saving of supplier data (adding or updating). - */ private void saveSupplier() { - // Validates all fields using InputValidator if (!InputValidator.isNotEmpty(binding.etSupCompany, "Company Name")) return; if (!InputValidator.isNotEmpty(binding.etSupContactFirstName, "First Name")) return; if (!InputValidator.isNotEmpty(binding.etSupContactLastName, "Last Name")) return; if (!InputValidator.isValidEmail(binding.etSupEmail)) return; if (!InputValidator.isValidPhone(binding.etSupPhone)) return; - //get all the values from the fields String company = binding.etSupCompany.getText().toString().trim(); String firstName = binding.etSupContactFirstName.getText().toString().trim(); String lastName = binding.etSupContactLastName.getText().toString().trim(); String email = binding.etSupEmail.getText().toString().trim(); String phone = binding.etSupPhone.getText().toString().trim(); - //create a supplier object to send to the API SupplierDTO supplierDTO = new SupplierDTO(); supplierDTO.setSupCompany(company); supplierDTO.setSupContactFirstName(firstName); @@ -96,41 +84,27 @@ public class SupplierDetailFragment extends Fragment { supplierDTO.setSupEmail(email); supplierDTO.setSupPhone(phone); - //check if the supplier is being edited or added - if (isEditing) { - // Update existing supplier - supplierDTO.setSupId(supId); - viewModel.updateSupplier(supId, supplierDTO).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { - ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", (int) supId); + viewModel.saveSupplier(supplierDTO).observe(getViewLifecycleOwner(), resource -> { + if (resource.status == Resource.Status.SUCCESS) { + if (viewModel.isEditing()) { + ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", (int) viewModel.getSupId()); Toast.makeText(getContext(), "Supplier updated successfully!", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); - } - }); - } else { - // Add new supplier - viewModel.createSupplier(supplierDTO).observe(getViewLifecycleOwner(), resource -> { - if (resource.status == Resource.Status.SUCCESS) { + } else { ActivityLogger.log(requireContext(), "Added new Supplier: " + company); Toast.makeText(getContext(), "Supplier added successfully!", Toast.LENGTH_SHORT).show(); - navigateBack(); - } else if (resource.status == Resource.Status.ERROR) { - Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); } - }); - } + navigateBack(); + } else if (resource.status == Resource.Status.ERROR) { + Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show(); + } + }); } - /** - * Displays a confirmation dialog and handles the deletion of a supplier. - */ private void deleteSupplier() { DialogUtils.showDeleteConfirmDialog(requireContext(), "Supplier", () -> - viewModel.deleteSupplier(supId).observe(getViewLifecycleOwner(), resource -> { + viewModel.deleteSupplier().observe(getViewLifecycleOwner(), resource -> { if (resource.status == Resource.Status.SUCCESS) { - ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", (int) supId); + ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", (int) viewModel.getSupId()); Toast.makeText(getContext(), "Supplier deleted successfully!", Toast.LENGTH_SHORT).show(); navigateBack(); } else if (resource.status == Resource.Status.ERROR) { @@ -139,31 +113,21 @@ public class SupplierDetailFragment extends Fragment { })); } - /** - * Navigates back to the previous screen. - */ private void navigateBack() { NavHostFragment.findNavController(this).popBackStack(); } - /** - * Handles arguments passed to the fragment to determine if it's in edit or add mode. - */ private void handleArguments() { - // Supplier is being edited if the bundle contains a supId if (getArguments() != null && getArguments().containsKey("supId")) { - // Get supplier data from arguments and populate fields - isEditing = true; - supId = getArguments().getLong("supId"); + long supId = getArguments().getLong("supId"); + viewModel.setSupId(supId); binding.tvMode.setText("Edit Supplier"); binding.tvSupId.setText("ID: " + supId); binding.tvSupId.setVisibility(View.VISIBLE); binding.btnDeleteSupplier.setVisibility(View.VISIBLE); loadSupplierData(); } else { - // Supplier is being added - // Set default values for add a new supplier - isEditing = false; + viewModel.setSupId(-1); binding.tvMode.setText("Add Supplier"); binding.tvSupId.setVisibility(View.GONE); binding.btnDeleteSupplier.setVisibility(View.GONE); @@ -171,11 +135,8 @@ public class SupplierDetailFragment extends Fragment { } } - /** - * Fetches specific supplier details from the backend using the ID. - */ private void loadSupplierData() { - viewModel.getSupplierById(supId).observe(getViewLifecycleOwner(), resource -> { + viewModel.loadSupplier().observe(getViewLifecycleOwner(), resource -> { if (resource == null) return; if (resource.status == Resource.Status.SUCCESS && resource.data != null) { SupplierDTO s = resource.data; 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 new file mode 100644 index 00000000..f6e24cd6 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/AdoptionDetailViewModel.java @@ -0,0 +1,102 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.AdoptionDTO; +import com.example.petstoremobile.dtos.DropdownDTO; +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.Resource; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class AdoptionDetailViewModel extends ViewModel { + private final AdoptionRepository adoptionRepository; + private final PetRepository petRepository; + private final CustomerRepository customerRepository; + private final StoreRepository storeRepository; + + private long adoptionId = -1; + private boolean isEditing = false; + + private final MutableLiveData> petList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> customerList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> storeList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> employeeList = new MutableLiveData<>(new ArrayList<>()); + + @Inject + public AdoptionDetailViewModel(AdoptionRepository adoptionRepository, PetRepository petRepository, + CustomerRepository customerRepository, StoreRepository storeRepository) { + this.adoptionRepository = adoptionRepository; + this.petRepository = petRepository; + this.customerRepository = customerRepository; + this.storeRepository = storeRepository; + } + + public void setAdoptionId(long id) { + this.adoptionId = id; + this.isEditing = id != -1; + } + + public long getAdoptionId() { + return adoptionId; + } + + public boolean isEditing() { + return isEditing; + } + + public LiveData> loadAdoption() { + return adoptionRepository.getAdoptionById(adoptionId); + } + + public LiveData>> loadPets() { + return petRepository.getAdoptionPets(); + } + + public LiveData>> loadCustomers() { + return customerRepository.getCustomerDropdowns(); + } + + public LiveData>> loadStores() { + return storeRepository.getStoreDropdowns(); + } + + public LiveData>> loadEmployees(Long storeId) { + return storeRepository.getStoreEmployees(storeId); + } + + public LiveData> saveAdoption(AdoptionDTO dto) { + if (isEditing) { + return adoptionRepository.updateAdoption(adoptionId, dto); + } else { + return adoptionRepository.createAdoption(dto); + } + } + + public LiveData> deleteAdoption() { + return adoptionRepository.deleteAdoption(adoptionId); + } + + public void setPetList(List list) { petList.setValue(list); } + public LiveData> getPetList() { return petList; } + + public void setCustomerList(List list) { customerList.setValue(list); } + public LiveData> getCustomerList() { return customerList; } + + public void setStoreList(List list) { storeList.setValue(list); } + public LiveData> getStoreList() { return storeList; } + + public void setEmployeeList(List list) { employeeList.setValue(list); } + public LiveData> getEmployeeList() { return employeeList; } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryDetailViewModel.java new file mode 100644 index 00000000..a76785af --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/InventoryDetailViewModel.java @@ -0,0 +1,79 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.DropdownDTO; +import com.example.petstoremobile.dtos.InventoryDTO; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductDTO; +import com.example.petstoremobile.repositories.InventoryRepository; +import com.example.petstoremobile.repositories.ProductRepository; +import com.example.petstoremobile.repositories.StoreRepository; +import com.example.petstoremobile.utils.Resource; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class InventoryDetailViewModel extends ViewModel { + private final InventoryRepository inventoryRepository; + private final StoreRepository storeRepository; + private final ProductRepository productRepository; + + private long inventoryId = -1; + private boolean isEditing = false; + + private final MutableLiveData> storeList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> productList = new MutableLiveData<>(new ArrayList<>()); + + @Inject + public InventoryDetailViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository, ProductRepository productRepository) { + this.inventoryRepository = inventoryRepository; + this.storeRepository = storeRepository; + this.productRepository = productRepository; + } + + public void setInventoryId(long id) { + this.inventoryId = id; + this.isEditing = id != -1; + } + + public long getInventoryId() { return inventoryId; } + public boolean isEditing() { return isEditing; } + + public LiveData> loadInventory() { + return inventoryRepository.getInventoryById(inventoryId); + } + + public LiveData>> loadStores() { + return storeRepository.getStoreDropdowns(); + } + + public LiveData>> loadProducts() { + return productRepository.getAllProducts(null, null, 0, 500, "prodName"); + } + + public LiveData> saveInventory(InventoryDTO dto) { + if (isEditing) { + return inventoryRepository.updateInventory(inventoryId, dto); + } else { + return inventoryRepository.createInventory(dto); + } + } + + public LiveData> deleteInventory() { + return inventoryRepository.deleteInventory(inventoryId); + } + + public void setStoreList(List list) { storeList.setValue(list); } + public LiveData> getStoreList() { return storeList; } + + public void setProductList(List list) { productList.setValue(list); } + public LiveData> getProductList() { return productList; } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java new file mode 100644 index 00000000..00c074e9 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PetDetailViewModel.java @@ -0,0 +1,94 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.DropdownDTO; +import com.example.petstoremobile.dtos.PetDTO; +import com.example.petstoremobile.repositories.CustomerRepository; +import com.example.petstoremobile.repositories.PetRepository; +import com.example.petstoremobile.repositories.StoreRepository; +import com.example.petstoremobile.utils.Resource; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class PetDetailViewModel extends ViewModel { + private final PetRepository petRepository; + private final CustomerRepository customerRepository; + private final StoreRepository storeRepository; + + private final MutableLiveData petState = new MutableLiveData<>(); + private final MutableLiveData> customerList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> storeList = new MutableLiveData<>(new ArrayList<>()); + + private long petId = -1; + private boolean isEditing = false; + + @Inject + public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) { + this.petRepository = petRepository; + this.customerRepository = customerRepository; + this.storeRepository = storeRepository; + } + + public void setPetId(long id) { + this.petId = id; + this.isEditing = id != -1; + } + + public long getPetId() { + return petId; + } + + public boolean isEditing() { + return isEditing; + } + + public LiveData> loadPet() { + return petRepository.getPetById(petId); + } + + public LiveData>> loadCustomers() { + return customerRepository.getCustomerDropdowns(); + } + + public LiveData>> loadStores() { + return storeRepository.getStoreDropdowns(); + } + + public LiveData> savePet(PetDTO petDTO) { + if (isEditing) { + petDTO.setPetId(petId); + return petRepository.updatePet(petId, petDTO); + } else { + return petRepository.createPet(petDTO); + } + } + + public LiveData> deletePet() { + return petRepository.deletePet(petId); + } + + public void setCustomerList(List list) { + customerList.setValue(list); + } + + public LiveData> getCustomerList() { + return customerList; + } + + public void setStoreList(List list) { + storeList.setValue(list); + } + + public LiveData> getStoreList() { + return storeList; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductDetailViewModel.java new file mode 100644 index 00000000..9ec0628a --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductDetailViewModel.java @@ -0,0 +1,85 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.CategoryDTO; +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductDTO; +import com.example.petstoremobile.repositories.CategoryRepository; +import com.example.petstoremobile.repositories.ProductRepository; +import com.example.petstoremobile.utils.Resource; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; +import okhttp3.MultipartBody; + +@HiltViewModel +public class ProductDetailViewModel extends ViewModel { + private final ProductRepository productRepository; + private final CategoryRepository categoryRepository; + + private final MutableLiveData> categoryList = new MutableLiveData<>(new ArrayList<>()); + private long prodId = -1; + private boolean isEditing = false; + + @Inject + public ProductDetailViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) { + this.productRepository = productRepository; + this.categoryRepository = categoryRepository; + } + + public void setProdId(long id) { + this.prodId = id; + this.isEditing = id != -1; + } + + public long getProdId() { + return prodId; + } + + public boolean isEditing() { + return isEditing; + } + + public LiveData>> loadCategories() { + return categoryRepository.getAllCategories(0, 100); + } + + public LiveData> loadProduct() { + return productRepository.getProductById(prodId); + } + + public LiveData> saveProduct(ProductDTO dto) { + if (isEditing) { + return productRepository.updateProduct(prodId, dto); + } else { + return productRepository.createProduct(dto); + } + } + + public LiveData> deleteProduct() { + return productRepository.deleteProduct(prodId); + } + + public LiveData> uploadProductImage(MultipartBody.Part image) { + return productRepository.uploadProductImage(prodId, image); + } + + public LiveData> deleteProductImage() { + return productRepository.deleteProductImage(prodId); + } + + public void setCategoryList(List list) { + categoryList.setValue(list); + } + + public LiveData> getCategoryList() { + return categoryList; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierDetailViewModel.java new file mode 100644 index 00000000..552a99fc --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ProductSupplierDetailViewModel.java @@ -0,0 +1,78 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.ProductDTO; +import com.example.petstoremobile.dtos.ProductSupplierDTO; +import com.example.petstoremobile.dtos.SupplierDTO; +import com.example.petstoremobile.repositories.ProductRepository; +import com.example.petstoremobile.repositories.ProductSupplierRepository; +import com.example.petstoremobile.repositories.SupplierRepository; +import com.example.petstoremobile.utils.Resource; + +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class ProductSupplierDetailViewModel extends ViewModel { + private final ProductSupplierRepository psRepository; + private final ProductRepository productRepository; + private final SupplierRepository supplierRepository; + + private boolean isEditing = false; + private long editProductId = -1; + private long editSupplierId = -1; + + private final MutableLiveData> productList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> supplierList = new MutableLiveData<>(new ArrayList<>()); + + @Inject + public ProductSupplierDetailViewModel(ProductSupplierRepository psRepository, ProductRepository productRepository, SupplierRepository supplierRepository) { + this.psRepository = psRepository; + this.productRepository = productRepository; + this.supplierRepository = supplierRepository; + } + + public void setEditMode(long productId, long supplierId) { + this.isEditing = true; + this.editProductId = productId; + this.editSupplierId = supplierId; + } + + public boolean isEditing() { return isEditing; } + public long getEditProductId() { return editProductId; } + public long getEditSupplierId() { return editSupplierId; } + + public LiveData>> loadProducts() { + return productRepository.getAllProducts(null, null, 0, 200, "prodName"); + } + + public LiveData>> loadSuppliers() { + return supplierRepository.getAllSuppliers(0, 200, null, "supCompany"); + } + + public LiveData> saveProductSupplier(ProductSupplierDTO dto) { + if (isEditing) { + return psRepository.updateProductSupplier(editProductId, editSupplierId, dto); + } else { + return psRepository.createProductSupplier(dto); + } + } + + public LiveData> deleteProductSupplier() { + return psRepository.deleteProductSupplier(editProductId, editSupplierId); + } + + public void setProductList(List list) { productList.setValue(list); } + public LiveData> getProductList() { return productList; } + + public void setSupplierList(List list) { supplierList.setValue(list); } + public LiveData> getSupplierList() { return supplierList; } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderDetailViewModel.java new file mode 100644 index 00000000..436cfa4c --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/PurchaseOrderDetailViewModel.java @@ -0,0 +1,26 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PurchaseOrderDTO; +import com.example.petstoremobile.repositories.PurchaseOrderRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class PurchaseOrderDetailViewModel extends ViewModel { + private final PurchaseOrderRepository repository; + + @Inject + public PurchaseOrderDetailViewModel(PurchaseOrderRepository repository) { + this.repository = repository; + } + + public LiveData> loadPurchaseOrder(long id) { + return repository.getPurchaseOrderById(id); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/RefundViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/RefundViewModel.java new file mode 100644 index 00000000..d2b13732 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/RefundViewModel.java @@ -0,0 +1,167 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.PageResponse; +import com.example.petstoremobile.dtos.SaleDTO; +import com.example.petstoremobile.repositories.SaleRepository; +import com.example.petstoremobile.utils.Resource; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class RefundViewModel extends ViewModel { + private final SaleRepository saleRepository; + + private final MutableLiveData> allSales = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData currentSale = new MutableLiveData<>(); + private final MutableLiveData> availableItems = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> refundCart = new MutableLiveData<>(new ArrayList<>()); + + @Inject + public RefundViewModel(SaleRepository saleRepository) { + this.saleRepository = saleRepository; + } + + public LiveData>> loadAllSales() { + return saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc"); + } + + public void setAllSales(List sales) { + allSales.setValue(sales); + } + + public List getAllSalesList() { + return allSales.getValue(); + } + + public void setCurrentSale(SaleDTO sale) { + currentSale.setValue(sale); + buildRefundableItems(); + } + + public SaleDTO getCurrentSale() { + return currentSale.getValue(); + } + + public LiveData> getAvailableItems() { + return availableItems; + } + + public LiveData> getRefundCart() { + return refundCart; + } + + private void buildRefundableItems() { + SaleDTO sale = currentSale.getValue(); + List sales = allSales.getValue(); + if (sale == null || sales == null || sale.getItems() == null) { + availableItems.setValue(new ArrayList<>()); + return; + } + + Map alreadyRefunded = new HashMap<>(); + for (SaleDTO s : sales) { + if (Boolean.TRUE.equals(s.getIsRefund()) + && sale.getSaleId().equals(s.getOriginalSaleId()) + && s.getItems() != null) { + for (SaleDTO.SaleItemDTO item : s.getItems()) { + if (item.getProdId() != null && item.getQuantity() != null) { + alreadyRefunded.merge(item.getProdId(), + Math.abs(item.getQuantity()), Integer::sum); + } + } + } + } + + List items = new ArrayList<>(); + for (SaleDTO.SaleItemDTO item : sale.getItems()) { + if (item.getProdId() == null || item.getQuantity() == null) continue; + int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0); + int remaining = item.getQuantity() - refunded; + if (remaining > 0) { + items.add(new RefundItem( + item.getProdId(), + item.getProductName() != null ? item.getProductName() : "Unknown", + remaining, + item.getUnitPrice() + )); + } + } + availableItems.setValue(items); + refundCart.setValue(new ArrayList<>()); + } + + public void addToCart(RefundItem item, int qty) { + List cart = new ArrayList<>(refundCart.getValue()); + boolean merged = false; + for (int i = 0; i < cart.size(); i++) { + if (cart.get(i).prodId == item.prodId) { + RefundItem existing = cart.get(i); + cart.set(i, new RefundItem(existing.prodId, existing.productName, existing.quantity + qty, existing.unitPrice)); + merged = true; + break; + } + } + if (!merged) { + cart.add(new RefundItem(item.prodId, item.productName, qty, item.unitPrice)); + } + refundCart.setValue(cart); + } + + public void removeFromCart(RefundItem item) { + List cart = new ArrayList<>(refundCart.getValue()); + cart.remove(item); + refundCart.setValue(cart); + } + + public LiveData> submitRefund(String paymentMethod) { + SaleDTO sale = currentSale.getValue(); + List cart = refundCart.getValue(); + if (sale == null || cart == null || cart.isEmpty()) return null; + + List items = new ArrayList<>(); + for (RefundItem item : cart) { + items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity)); + } + + SaleDTO dto = new SaleDTO( + sale.getStoreId(), + paymentMethod, + items, + true, + sale.getSaleId(), + null + ); + + return saleRepository.createSale(dto); + } + + public static class RefundItem { + public long prodId; + public String productName; + public int quantity; + public BigDecimal unitPrice; + + public RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) { + this.prodId = prodId; + this.productName = productName; + this.quantity = quantity; + this.unitPrice = unitPrice; + } + + public BigDecimal getTotal() { + return unitPrice != null ? unitPrice.multiply(BigDecimal.valueOf(quantity)) : BigDecimal.ZERO; + } + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleDetailViewModel.java new file mode 100644 index 00000000..bf84ab32 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SaleDetailViewModel.java @@ -0,0 +1,109 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.DropdownDTO; +import com.example.petstoremobile.dtos.ProductDTO; +import com.example.petstoremobile.dtos.SaleDTO; +import com.example.petstoremobile.repositories.CustomerRepository; +import com.example.petstoremobile.repositories.ProductRepository; +import com.example.petstoremobile.repositories.SaleRepository; +import com.example.petstoremobile.repositories.StoreRepository; +import com.example.petstoremobile.utils.Resource; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class SaleDetailViewModel extends ViewModel { + private final SaleRepository saleRepository; + private final StoreRepository storeRepository; + private final CustomerRepository customerRepository; + private final ProductRepository productRepository; + + private long saleId = -1; + private boolean viewOnly = false; + + private final MutableLiveData> storeList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> customerList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> productList = new MutableLiveData<>(new ArrayList<>()); + private final MutableLiveData> cartItems = new MutableLiveData<>(new ArrayList<>()); + + @Inject + public SaleDetailViewModel(SaleRepository saleRepository, StoreRepository storeRepository, + CustomerRepository customerRepository, ProductRepository productRepository) { + this.saleRepository = saleRepository; + this.storeRepository = storeRepository; + this.customerRepository = customerRepository; + this.productRepository = productRepository; + } + + public void setSaleId(long id, boolean viewOnly) { + this.saleId = id; + this.viewOnly = viewOnly; + } + + public long getSaleId() { return saleId; } + public boolean isViewOnly() { return viewOnly; } + + public LiveData> loadSaleDetails() { + return saleRepository.getSaleById(saleId); + } + + public LiveData>> loadStores() { + return storeRepository.getStoreDropdowns(); + } + + public LiveData>> loadCustomers() { + return customerRepository.getCustomerDropdowns(); + } + + public LiveData>> loadProducts() { + return productRepository.getAllProducts(null, null, 0, 200, null); + } + + public LiveData> createSale(SaleDTO sale) { + return saleRepository.createSale(sale); + } + + public void setStoreList(List list) { storeList.setValue(list); } + public LiveData> getStoreList() { return storeList; } + + public void setCustomerList(List list) { customerList.setValue(list); } + public LiveData> getCustomerList() { return customerList; } + + public void setProductList(List list) { productList.setValue(list); } + public LiveData> getProductList() { return productList; } + + public void addToCart(SaleDTO.SaleItemDTO item) { + List currentCart = new ArrayList<>(cartItems.getValue()); + currentCart.add(item); + cartItems.setValue(currentCart); + } + + public LiveData> getCartItems() { return cartItems; } + + public BigDecimal calculateSubtotal() { + BigDecimal total = BigDecimal.ZERO; + List items = cartItems.getValue(); + List products = productList.getValue(); + if (items != null && products != null) { + for (SaleDTO.SaleItemDTO item : items) { + for (ProductDTO p : products) { + if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) { + total = total.add(p.getProdPrice().multiply(BigDecimal.valueOf(item.getQuantity()))); + break; + } + } + } + } + return total; + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java new file mode 100644 index 00000000..fca74229 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/ServiceDetailViewModel.java @@ -0,0 +1,54 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.ServiceDTO; +import com.example.petstoremobile.repositories.ServiceRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class ServiceDetailViewModel extends ViewModel { + private final ServiceRepository repository; + private long serviceId = -1; + private boolean isEditing = false; + + @Inject + public ServiceDetailViewModel(ServiceRepository repository) { + this.repository = repository; + } + + public void setServiceId(long id) { + this.serviceId = id; + this.isEditing = id != -1; + } + + public long getServiceId() { + return serviceId; + } + + public boolean isEditing() { + return isEditing; + } + + public LiveData> loadService() { + return repository.getServiceById(serviceId); + } + + public LiveData> saveService(ServiceDTO dto) { + if (isEditing) { + dto.setServiceId(serviceId); + return repository.updateService(serviceId, dto); + } else { + return repository.createService(dto); + } + } + + public LiveData> deleteService() { + return repository.deleteService(serviceId); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffDetailViewModel.java new file mode 100644 index 00000000..91162405 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/StaffDetailViewModel.java @@ -0,0 +1,49 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.EmployeeDTO; +import com.example.petstoremobile.repositories.EmployeeRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class StaffDetailViewModel extends ViewModel { + private final EmployeeRepository repository; + private long employeeId = -1; + private boolean isEditing = false; + + @Inject + public StaffDetailViewModel(EmployeeRepository repository) { + this.repository = repository; + } + + public void setEmployeeId(long id, boolean isEditing) { + this.employeeId = id; + this.isEditing = isEditing; + } + + public long getEmployeeId() { + return employeeId; + } + + public boolean isEditing() { + return isEditing; + } + + public LiveData> saveEmployee(EmployeeDTO dto) { + if (isEditing && employeeId > 0) { + return repository.updateEmployee(employeeId, dto); + } else { + return repository.createEmployee(dto); + } + } + + public LiveData> deleteEmployee() { + return repository.deleteEmployee(employeeId); + } +} diff --git a/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java new file mode 100644 index 00000000..591beb52 --- /dev/null +++ b/android/app/src/main/java/com/example/petstoremobile/viewmodels/SupplierDetailViewModel.java @@ -0,0 +1,54 @@ +package com.example.petstoremobile.viewmodels; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.ViewModel; + +import com.example.petstoremobile.dtos.SupplierDTO; +import com.example.petstoremobile.repositories.SupplierRepository; +import com.example.petstoremobile.utils.Resource; + +import javax.inject.Inject; + +import dagger.hilt.android.lifecycle.HiltViewModel; + +@HiltViewModel +public class SupplierDetailViewModel extends ViewModel { + private final SupplierRepository repository; + private long supId = -1; + private boolean isEditing = false; + + @Inject + public SupplierDetailViewModel(SupplierRepository repository) { + this.repository = repository; + } + + public void setSupId(long id) { + this.supId = id; + this.isEditing = id != -1; + } + + public long getSupId() { + return supId; + } + + public boolean isEditing() { + return isEditing; + } + + public LiveData> loadSupplier() { + return repository.getSupplierById(supId); + } + + public LiveData> saveSupplier(SupplierDTO dto) { + if (isEditing) { + dto.setSupId(supId); + return repository.updateSupplier(supId, dto); + } else { + return repository.createSupplier(dto); + } + } + + public LiveData> deleteSupplier() { + return repository.deleteSupplier(supId); + } +}