converted new fragments to use hilt, MVVM and jetpack nav

This commit is contained in:
Alex
2026-04-08 02:22:34 -06:00
parent a35b432b54
commit 811edf842b
5 changed files with 232 additions and 231 deletions

View File

@@ -7,30 +7,33 @@ import android.view.*;
import android.widget.*; import android.widget.*;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.example.petstoremobile.R; import androidx.lifecycle.ViewModelProvider;
import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.databinding.FragmentAnalyticsBinding; import com.example.petstoremobile.databinding.FragmentAnalyticsBinding;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SaleDTO; import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.fragments.ListFragment; import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.SaleViewModel;
import dagger.hilt.android.AndroidEntryPoint;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.*; import java.util.*;
import retrofit2.*;
@AndroidEntryPoint
public class AnalyticsFragment extends Fragment { public class AnalyticsFragment extends Fragment {
private FragmentAnalyticsBinding binding; private FragmentAnalyticsBinding binding;
private SaleViewModel saleViewModel;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
binding = FragmentAnalyticsBinding.inflate(inflater, container, false); binding = FragmentAnalyticsBinding.inflate(inflater, container, false);
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
loadAnalytics(); loadAnalytics();
binding.btnRefreshAnalytics.setOnClickListener(v -> loadAnalytics()); binding.btnRefreshAnalytics.setOnClickListener(v -> loadAnalytics());
binding.btnHamburgerAnalytics.setOnClickListener(v -> openDrawer());
UIUtils.setupHamburgerMenu(binding.btnHamburgerAnalytics, this);
return binding.getRoot(); return binding.getRoot();
} }
@@ -41,16 +44,6 @@ public class AnalyticsFragment extends Fragment {
binding = null; binding = null;
} }
private void openDrawer() {
Fragment parent = getParentFragment();
if (parent != null) {
Fragment grandParent = parent.getParentFragment();
if (grandParent instanceof ListFragment) {
((ListFragment) grandParent).openDrawer();
}
}
}
private void loadAnalytics() { private void loadAnalytics() {
// Clear all sections // Clear all sections
binding.llTopRevenue.removeAllViews(); binding.llTopRevenue.removeAllViews();
@@ -65,21 +58,24 @@ public class AnalyticsFragment extends Fragment {
binding.tvAvgTransaction.setText("..."); binding.tvAvgTransaction.setText("...");
binding.tvTotalItems.setText("..."); binding.tvTotalItems.setText("...");
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000, null, null, null, "saleDate,desc") saleViewModel.getAllSales(0, 1000, null, null, null, "saleDate,desc")
.enqueue(new Callback<PageResponse<SaleDTO>>() { .observe(getViewLifecycleOwner(), resource -> {
public void onResponse(Call<PageResponse<SaleDTO>> c, if (resource != null) {
Response<PageResponse<SaleDTO>> r) { switch (resource.status) {
if (r.isSuccessful() && r.body() != null) { case SUCCESS:
computeAndDisplay(r.body().getContent()); if (resource.data != null) {
} else { computeAndDisplay(resource.data.getContent());
showError("Failed to load sales data"); }
break;
case ERROR:
Log.e("Analytics", resource.message != null ? resource.message : "Error loading sales");
showError("Failed to load sales data");
break;
case LOADING:
// Already showing loading state in UI
break;
} }
} }
public void onFailure(Call<PageResponse<SaleDTO>> c, Throwable t) {
Log.e("Analytics", t.getMessage());
showError("Network error");
}
}); });
} }
@@ -250,13 +246,12 @@ public class AnalyticsFragment extends Fragment {
} }
} }
// Adds a horizontal bar row with label, value and a proportional bar
private void addBarRow(LinearLayout parent, String label, String value, float ratio, String color) { private void addBarRow(LinearLayout parent, String label, String value, float ratio, String color) {
if (getContext() == null) return;
LinearLayout row = new LinearLayout(getContext()); LinearLayout row = new LinearLayout(getContext());
row.setOrientation(LinearLayout.VERTICAL); row.setOrientation(LinearLayout.VERTICAL);
row.setPadding(0, 6, 0, 6); row.setPadding(0, 6, 0, 6);
// Label + value row
LinearLayout labelRow = new LinearLayout(getContext()); LinearLayout labelRow = new LinearLayout(getContext());
labelRow.setOrientation(LinearLayout.HORIZONTAL); labelRow.setOrientation(LinearLayout.HORIZONTAL);
@@ -276,7 +271,6 @@ public class AnalyticsFragment extends Fragment {
labelRow.addView(tvLabel); labelRow.addView(tvLabel);
labelRow.addView(tvValue); labelRow.addView(tvValue);
// Bar background
LinearLayout barBg = new LinearLayout(getContext()); LinearLayout barBg = new LinearLayout(getContext());
LinearLayout.LayoutParams bgParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams bgParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 12); LinearLayout.LayoutParams.MATCH_PARENT, 12);
@@ -284,16 +278,13 @@ public class AnalyticsFragment extends Fragment {
barBg.setLayoutParams(bgParams); barBg.setLayoutParams(bgParams);
barBg.setBackgroundColor(Color.parseColor("#EEEEEE")); barBg.setBackgroundColor(Color.parseColor("#EEEEEE"));
// Bar fill
View barFill = new View(getContext()); View barFill = new View(getContext());
int fillWidth = (int) (ratio * 100);
LinearLayout.LayoutParams fillParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams fillParams = new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.MATCH_PARENT, ratio); 0, LinearLayout.LayoutParams.MATCH_PARENT, ratio);
barFill.setLayoutParams(fillParams); barFill.setLayoutParams(fillParams);
barFill.setBackgroundColor(Color.parseColor(color)); barFill.setBackgroundColor(Color.parseColor(color));
barBg.addView(barFill); barBg.addView(barFill);
// Empty space
View spacer = new View(getContext()); View spacer = new View(getContext());
spacer.setLayoutParams(new LinearLayout.LayoutParams( spacer.setLayoutParams(new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.MATCH_PARENT, 1f - ratio)); 0, LinearLayout.LayoutParams.MATCH_PARENT, 1f - ratio));
@@ -305,6 +296,7 @@ public class AnalyticsFragment extends Fragment {
} }
private void addEmptyRow(LinearLayout parent, String message) { private void addEmptyRow(LinearLayout parent, String message) {
if (getContext() == null) return;
TextView tv = new TextView(getContext()); TextView tv = new TextView(getContext());
tv.setText(message); tv.setText(message);
tv.setTextColor(Color.parseColor("#888780")); tv.setTextColor(Color.parseColor("#888780"));
@@ -313,7 +305,7 @@ public class AnalyticsFragment extends Fragment {
} }
private void showError(String msg) { private void showError(String msg) {
if (getContext() == null) if (getContext() == null || binding == null)
return; return;
binding.tvTotalRevenue.setText("Error"); binding.tvTotalRevenue.setText("Error");
binding.tvTotalTransactions.setText(""); binding.tvTotalTransactions.setText("");

View File

@@ -5,23 +5,24 @@ import android.util.Log;
import android.view.*; import android.view.*;
import android.widget.*; import android.widget.*;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import com.example.petstoremobile.R; import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.EmployeeAdapter; import com.example.petstoremobile.adapters.EmployeeAdapter;
import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.databinding.FragmentStaffBinding; import com.example.petstoremobile.databinding.FragmentStaffBinding;
import com.example.petstoremobile.dtos.EmployeeDTO; import com.example.petstoremobile.dtos.EmployeeDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.utils.UIUtils; import com.example.petstoremobile.utils.UIUtils;
import com.example.petstoremobile.viewmodels.EmployeeViewModel;
import dagger.hilt.android.AndroidEntryPoint;
import java.util.*; import java.util.*;
import retrofit2.*;
@AndroidEntryPoint
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener { public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
private FragmentStaffBinding binding; private FragmentStaffBinding binding;
private EmployeeViewModel employeeViewModel;
private List<EmployeeDTO> employeeList = new ArrayList<>(); private List<EmployeeDTO> employeeList = new ArrayList<>();
private List<EmployeeDTO> filteredList = new ArrayList<>(); private List<EmployeeDTO> filteredList = new ArrayList<>();
private EmployeeAdapter adapter; private EmployeeAdapter adapter;
@@ -30,6 +31,7 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
binding = FragmentStaffBinding.inflate(inflater, container, false); binding = FragmentStaffBinding.inflate(inflater, container, false);
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
setupRecyclerView(); setupRecyclerView();
setupSearch(); setupSearch();
@@ -39,7 +41,6 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
binding.fabAddStaff.setOnClickListener(v -> openDetail(-1)); binding.fabAddStaff.setOnClickListener(v -> openDetail(-1));
UIUtils.setupHamburgerMenu(binding.btnHamburgerStaff, this); UIUtils.setupHamburgerMenu(binding.btnHamburgerStaff, this);
UIUtils.setupFilterToggle(binding.btnToggleFilterStaff, binding.layoutFilterStaff, binding.etSearchStaff); UIUtils.setupFilterToggle(binding.btnToggleFilterStaff, binding.layoutFilterStaff, binding.etSearchStaff);
return binding.getRoot(); return binding.getRoot();
@@ -79,27 +80,30 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
private void loadStaff() { private void loadStaff() {
binding.swipeRefreshStaff.setRefreshing(true); binding.swipeRefreshStaff.setRefreshing(true);
RetrofitClient.getEmployeeApi(requireContext()).getAllEmployees(0, 100) employeeViewModel.getAllEmployees(0, 100).observe(getViewLifecycleOwner(), resource -> {
.enqueue(new Callback<PageResponse<EmployeeDTO>>() { if (resource != null) {
public void onResponse(Call<PageResponse<EmployeeDTO>> c, switch (resource.status) {
Response<PageResponse<EmployeeDTO>> r) { case SUCCESS:
if (binding != null) binding.swipeRefreshStaff.setRefreshing(false); binding.swipeRefreshStaff.setRefreshing(false);
if (r.isSuccessful() && r.body() != null) { if (resource.data != null) {
employeeList.clear(); employeeList.clear();
employeeList.addAll(r.body().getContent()); employeeList.addAll(resource.data.getContent());
filter(binding != null ? binding.etSearchStaff.getText().toString() : ""); filter(binding != null ? binding.etSearchStaff.getText().toString() : "");
} else {
if (getContext() != null) {
Toast.makeText(getContext(), "Failed to load staff",
Toast.LENGTH_SHORT).show();
}
} }
} break;
public void onFailure(Call<PageResponse<EmployeeDTO>> c, Throwable t) { case ERROR:
if (binding != null) binding.swipeRefreshStaff.setRefreshing(false); binding.swipeRefreshStaff.setRefreshing(false);
Log.e("StaffFragment", t.getMessage()); if (getContext() != null) {
} Toast.makeText(getContext(), resource.message != null ? resource.message : "Failed to load staff",
}); Toast.LENGTH_SHORT).show();
}
break;
case LOADING:
binding.swipeRefreshStaff.setRefreshing(true);
break;
}
}
});
} }
private void openDetail(int position) { private void openDetail(int position) {

View File

@@ -7,20 +7,22 @@ import android.view.*;
import android.widget.*; import android.widget.*;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R; import com.example.petstoremobile.R;
import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.databinding.FragmentRefundBinding; import com.example.petstoremobile.databinding.FragmentRefundBinding;
import com.example.petstoremobile.dtos.SaleDTO; import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.dtos.PageResponse; import com.example.petstoremobile.viewmodels.SaleViewModel;
import dagger.hilt.android.AndroidEntryPoint;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.*; import java.util.*;
import retrofit2.*;
@AndroidEntryPoint
public class RefundFragment extends Fragment { public class RefundFragment extends Fragment {
private FragmentRefundBinding binding; private FragmentRefundBinding binding;
private SaleViewModel saleViewModel;
private SaleDTO currentSale; private SaleDTO currentSale;
private List<SaleDTO> allSales = new ArrayList<>(); private List<SaleDTO> allSales = new ArrayList<>();
@@ -56,6 +58,7 @@ public class RefundFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
binding = FragmentRefundBinding.inflate(inflater, container, false); binding = FragmentRefundBinding.inflate(inflater, container, false);
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
setupSpinner(); setupSpinner();
loadAllSales(); loadAllSales();
@@ -81,22 +84,25 @@ public class RefundFragment extends Fragment {
} }
private void loadAllSales() { private void loadAllSales() {
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000, null, null, null, "saleDate,desc") saleViewModel.getAllSales(0, 1000, null, null, null, "saleDate,desc")
.enqueue(new Callback<PageResponse<SaleDTO>>() { .observe(getViewLifecycleOwner(), resource -> {
public void onResponse(Call<PageResponse<SaleDTO>> c, if (resource != null) {
Response<PageResponse<SaleDTO>> r) { switch (resource.status) {
if (r.isSuccessful() && r.body() != null) { case SUCCESS:
allSales = r.body().getContent(); if (resource.data != null) {
// Auto-load if saleId was pre-filled allSales = resource.data.getContent();
Bundle args = getArguments(); // Auto-load if saleId was pre-filled
if (args != null && args.containsKey("saleId")) { Bundle args = getArguments();
loadSale(); if (args != null && args.containsKey("saleId")) {
} loadSale();
}
}
break;
case ERROR:
Log.e("Refund", "Failed to load sales: " + resource.message);
break;
} }
} }
public void onFailure(Call<PageResponse<SaleDTO>> c, Throwable t) {
Log.e("Refund", "Failed to load sales: " + t.getMessage());
}
}); });
} }
@@ -270,6 +276,7 @@ public class RefundFragment extends Fragment {
} }
private void addTableHeader(LinearLayout parent) { private void addTableHeader(LinearLayout parent) {
if (getContext() == null) return;
LinearLayout header = new LinearLayout(getContext()); LinearLayout header = new LinearLayout(getContext());
header.setOrientation(LinearLayout.HORIZONTAL); header.setOrientation(LinearLayout.HORIZONTAL);
header.setPadding(0, 0, 0, 8); header.setPadding(0, 0, 0, 8);
@@ -290,6 +297,7 @@ public class RefundFragment extends Fragment {
private LinearLayout buildItemRow(String name, int qty, BigDecimal unitPrice, private LinearLayout buildItemRow(String name, int qty, BigDecimal unitPrice,
boolean isAdd, Runnable action) { boolean isAdd, Runnable action) {
if (getContext() == null) return new LinearLayout(getContext());
LinearLayout row = new LinearLayout(getContext()); LinearLayout row = new LinearLayout(getContext());
row.setOrientation(LinearLayout.HORIZONTAL); row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(android.view.Gravity.CENTER_VERTICAL); row.setGravity(android.view.Gravity.CENTER_VERTICAL);
@@ -458,30 +466,25 @@ public class RefundFragment extends Fragment {
Log.d("REFUND", "Submitting refund for saleId=" + currentSale.getSaleId() Log.d("REFUND", "Submitting refund for saleId=" + currentSale.getSaleId()
+ " items=" + items.size()); + " items=" + items.size());
RetrofitClient.getSaleApi(requireContext()).createSale(dto) saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
.enqueue(new Callback<SaleDTO>() { if (resource != null) {
public void onResponse(Call<SaleDTO> c, Response<SaleDTO> r) { switch (resource.status) {
if (r.isSuccessful() && r.body() != null) { case SUCCESS:
if (resource.data != null) {
Toast.makeText(getContext(), Toast.makeText(getContext(),
"Refund #" + r.body().getSaleId() + " processed successfully!", "Refund #" + resource.data.getSaleId() + " processed successfully!",
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
navigateBack(); navigateBack();
} else {
try {
String err = r.errorBody().string();
Log.e("REFUND", "Error: " + err);
Toast.makeText(getContext(), "Error: " + err,
Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e("REFUND", "Failed to read error");
}
} }
} break;
public void onFailure(Call<SaleDTO> c, Throwable t) { case ERROR:
Log.e("REFUND", "Failure: " + t.getMessage()); Log.e("REFUND", "Error: " + resource.message);
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Error: " + resource.message,
} Toast.LENGTH_LONG).show();
}); break;
}
}
});
} }
private void navigateBack() { private void navigateBack() {

View File

@@ -6,18 +6,24 @@ import android.view.*;
import android.widget.*; import android.widget.*;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R; import com.example.petstoremobile.R;
import com.example.petstoremobile.api.*;
import com.example.petstoremobile.databinding.FragmentSaleDetailBinding; import com.example.petstoremobile.databinding.FragmentSaleDetailBinding;
import com.example.petstoremobile.dtos.*; import com.example.petstoremobile.dtos.*;
import com.example.petstoremobile.viewmodels.*;
import dagger.hilt.android.AndroidEntryPoint;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.*; import java.util.*;
import retrofit2.*;
@AndroidEntryPoint
public class SaleDetailFragment extends Fragment { public class SaleDetailFragment extends Fragment {
private FragmentSaleDetailBinding binding; private FragmentSaleDetailBinding binding;
private SaleViewModel saleViewModel;
private StoreViewModel storeViewModel;
private CustomerViewModel customerViewModel;
private ProductViewModel productViewModel;
private boolean viewOnly = false; private boolean viewOnly = false;
private long saleId = -1; private long saleId = -1;
@@ -34,6 +40,11 @@ public class SaleDetailFragment extends Fragment {
Bundle savedInstanceState) { Bundle savedInstanceState) {
binding = FragmentSaleDetailBinding.inflate(inflater, container, false); 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);
binding.spinnerPaymentMethod.setAdapter(new ArrayAdapter<>(requireContext(), binding.spinnerPaymentMethod.setAdapter(new ArrayAdapter<>(requireContext(),
android.R.layout.simple_spinner_item, PAYMENT_METHODS)); android.R.layout.simple_spinner_item, PAYMENT_METHODS));
@@ -91,89 +102,82 @@ public class SaleDetailFragment extends Fragment {
} }
private void loadStores() { private void loadStores() {
// Hardcoded since store endpoint is admin only
storeList = new ArrayList<>(); storeViewModel.getAllStores(0, 50).observe(getViewLifecycleOwner(), resource -> {
storeList.add(new StoreDTO(1L, "Downtown Branch")); if (resource != null && resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS && resource.data != null) {
List<String> names = new ArrayList<>(); storeList = resource.data.getContent();
names.add("-- Select Store --"); List<String> names = new ArrayList<>();
names.add("Downtown Branch"); names.add("-- Select Store --");
binding.spinnerSaleStore.setAdapter(new ArrayAdapter<>(requireContext(), for (StoreDTO s : storeList) names.add(s.getStoreName());
android.R.layout.simple_spinner_item, names)); if (binding != null) {
binding.spinnerSaleStore.setAdapter(new ArrayAdapter<>(requireContext(),
android.R.layout.simple_spinner_item, names));
}
} else if (storeList.isEmpty()) {
// Fallback if failed or empty
storeList = new ArrayList<>();
storeList.add(new StoreDTO(1L, "Downtown Branch"));
List<String> names = new ArrayList<>();
names.add("-- Select Store --");
names.add("Downtown Branch");
if (binding != null) {
binding.spinnerSaleStore.setAdapter(new ArrayAdapter<>(requireContext(),
android.R.layout.simple_spinner_item, names));
}
}
});
} }
private void loadCustomers() { private void loadCustomers() {
RetrofitClient.getCustomerApi(requireContext()).getAllCustomers(0, 200) customerViewModel.getAllCustomers(0, 200).observe(getViewLifecycleOwner(), resource -> {
.enqueue(new Callback<PageResponse<CustomerDTO>>() { if (resource != null && resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS && resource.data != null) {
public void onResponse(Call<PageResponse<CustomerDTO>> c, customerList = resource.data.getContent();
Response<PageResponse<CustomerDTO>> r) { List<String> names = new ArrayList<>();
if (r.isSuccessful() && r.body() != null) { names.add("-- No Customer --");
customerList = r.body().getContent(); for (CustomerDTO cu : customerList)
List<String> names = new ArrayList<>(); names.add(cu.getFirstName() + " " + cu.getLastName());
names.add("-- No Customer --"); if (binding != null) {
for (CustomerDTO cu : customerList) binding.spinnerSaleCustomer.setAdapter(new ArrayAdapter<>(requireContext(),
names.add(cu.getFirstName() + " " + cu.getLastName()); android.R.layout.simple_spinner_item, names));
if (binding != null) { }
binding.spinnerSaleCustomer.setAdapter(new ArrayAdapter<>(requireContext(), }
android.R.layout.simple_spinner_item, names)); });
}
}
}
public void onFailure(Call<PageResponse<CustomerDTO>> c, Throwable t) {
Log.e("SaleDetail", "Customer load failed: " + t.getMessage());
}
});
} }
private void loadProducts() { private void loadProducts() {
RetrofitClient.getProductApi(requireContext()).getAllProducts(null, null, 0, 200, null) productViewModel.getAllProducts(null, null, 0, 200, null).observe(getViewLifecycleOwner(), resource -> {
.enqueue(new Callback<PageResponse<ProductDTO>>() { if (resource != null && resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS && resource.data != null) {
public void onResponse(Call<PageResponse<ProductDTO>> c, productList = resource.data.getContent();
Response<PageResponse<ProductDTO>> r) { List<String> names = new ArrayList<>();
if (r.isSuccessful() && r.body() != null) { names.add("-- Select Product --");
productList = r.body().getContent(); for (ProductDTO p : productList)
List<String> names = new ArrayList<>(); names.add(p.getProdName());
names.add("-- Select Product --"); if (binding != null) {
for (ProductDTO p : productList) binding.spinnerSaleProduct.setAdapter(new ArrayAdapter<>(requireContext(),
names.add(p.getProdName()); android.R.layout.simple_spinner_item, names));
if (binding != null) { }
binding.spinnerSaleProduct.setAdapter(new ArrayAdapter<>(requireContext(), }
android.R.layout.simple_spinner_item, names)); });
}
}
}
public void onFailure(Call<PageResponse<ProductDTO>> c, Throwable t) {
Log.e("SaleDetail", "Product load failed: " + t.getMessage());
}
});
} }
private void loadSaleDetails() { private void loadSaleDetails() {
RetrofitClient.getSaleApi(requireContext()).getSaleById(saleId) saleViewModel.getSaleById(saleId).observe(getViewLifecycleOwner(), resource -> {
.enqueue(new Callback<SaleDTO>() { if (resource != null && resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS && resource.data != null) {
public void onResponse(Call<SaleDTO> c, Response<SaleDTO> r) { SaleDTO sale = resource.data;
if (r.isSuccessful() && r.body() != null) { if (binding != null) {
SaleDTO sale = r.body(); binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
if (binding != null) { // Display items
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount()); if (sale.getItems() != null) {
// Display items binding.llSaleItems.removeAllViews();
if (sale.getItems() != null) { for (SaleDTO.SaleItemDTO item : sale.getItems()) {
binding.llSaleItems.removeAllViews(); addItemRow(item.getProductName(),
for (SaleDTO.SaleItemDTO item : sale.getItems()) { Math.abs(item.getQuantity()),
addItemRow(item.getProductName(), item.getUnitPrice());
Math.abs(item.getQuantity()),
item.getUnitPrice());
}
}
}
} }
} }
}
public void onFailure(Call<SaleDTO> c, Throwable t) { }
Log.e("SaleDetail", "Load failed: " + t.getMessage()); });
}
});
} }
private void setupAddItem() { private void setupAddItem() {
@@ -214,6 +218,7 @@ public class SaleDetailFragment extends Fragment {
} }
private void addItemRow(String name, int qty, BigDecimal price) { private void addItemRow(String name, int qty, BigDecimal price) {
if (getContext() == null) return;
LinearLayout row = new LinearLayout(getContext()); LinearLayout row = new LinearLayout(getContext());
row.setOrientation(LinearLayout.HORIZONTAL); row.setOrientation(LinearLayout.HORIZONTAL);
row.setPadding(0, 8, 0, 8); row.setPadding(0, 8, 0, 8);
@@ -263,7 +268,7 @@ public class SaleDetailFragment extends Fragment {
return; return;
} }
StoreDTO store = storeList.get(0); // only one store StoreDTO store = storeList.get(binding.spinnerSaleStore.getSelectedItemPosition() - 1);
String payment = PAYMENT_METHODS[binding.spinnerPaymentMethod.getSelectedItemPosition()]; String payment = PAYMENT_METHODS[binding.spinnerPaymentMethod.getSelectedItemPosition()];
// Optional customer // Optional customer
@@ -281,29 +286,20 @@ public class SaleDetailFragment extends Fragment {
null, null,
customerId); customerId);
RetrofitClient.getSaleApi(requireContext()).createSale(dto) saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
.enqueue(new Callback<SaleDTO>() { if (resource != null) {
public void onResponse(Call<SaleDTO> c, Response<SaleDTO> r) { switch (resource.status) {
if (r.isSuccessful()) { case SUCCESS:
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show(); Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
navigateBack(); navigateBack();
} else { break;
try { case ERROR:
String err = r.errorBody().string(); Log.e("SALE_SAVE", "Error: " + resource.message);
Log.e("SALE_SAVE", "Error: " + err); Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
Toast.makeText(getContext(), "Error " + r.code() + ": " + err, break;
Toast.LENGTH_LONG).show(); }
} catch (Exception e) { }
Log.e("SALE_SAVE", "Failed to read error"); });
}
}
}
public void onFailure(Call<SaleDTO> c, Throwable t) {
Log.e("SALE_SAVE", "Failure: " + t.getMessage());
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
}
});
} }
private void showRefundDialog() { private void showRefundDialog() {

View File

@@ -7,17 +7,19 @@ import android.widget.*;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R; import com.example.petstoremobile.R;
import com.example.petstoremobile.api.EmployeeApi;
import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.databinding.FragmentStaffDetailBinding; import com.example.petstoremobile.databinding.FragmentStaffDetailBinding;
import com.example.petstoremobile.dtos.EmployeeDTO; import com.example.petstoremobile.dtos.EmployeeDTO;
import retrofit2.*; import com.example.petstoremobile.viewmodels.EmployeeViewModel;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class StaffDetailFragment extends Fragment { public class StaffDetailFragment extends Fragment {
private FragmentStaffDetailBinding binding; private FragmentStaffDetailBinding binding;
private EmployeeViewModel employeeViewModel;
private long employeeId = -1; private long employeeId = -1;
private boolean isEditing = false; private boolean isEditing = false;
@@ -28,6 +30,7 @@ public class StaffDetailFragment extends Fragment {
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
binding = FragmentStaffDetailBinding.inflate(inflater, container, false); binding = FragmentStaffDetailBinding.inflate(inflater, container, false);
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
setupSpinners(); setupSpinners();
handleArguments(); handleArguments();
@@ -118,34 +121,35 @@ public class StaffDetailFragment extends Fragment {
active active
); );
EmployeeApi api = RetrofitClient.getEmployeeApi(requireContext());
if (isEditing && employeeId > 0) { if (isEditing && employeeId > 0) {
api.updateEmployee(employeeId, dto).enqueue(simpleCallback("Updated successfully")); employeeViewModel.updateEmployee(employeeId, dto).observe(getViewLifecycleOwner(), resource -> {
} else { if (resource != null) {
api.createEmployee(dto).enqueue(simpleCallback("Staff account created")); switch (resource.status) {
} case SUCCESS:
} Toast.makeText(getContext(), "Updated successfully", Toast.LENGTH_SHORT).show();
navigateBack();
private Callback<EmployeeDTO> simpleCallback(String msg) { break;
return new Callback<>() { case ERROR:
public void onResponse(Call<EmployeeDTO> c, Response<EmployeeDTO> r) { Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
if (r.isSuccessful()) { break;
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
navigateBack();
} else {
try {
String err = r.errorBody().string();
Toast.makeText(getContext(), "Error " + r.code() + ": " + err,
Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e("STAFF_SAVE", "Failed to read error");
} }
} }
} });
public void onFailure(Call<EmployeeDTO> c, Throwable t) { } else {
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show(); 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() { private void confirmDelete() {
@@ -153,17 +157,19 @@ public class StaffDetailFragment extends Fragment {
.setTitle("Delete Staff Account?") .setTitle("Delete Staff Account?")
.setMessage("This will permanently delete this staff account.") .setMessage("This will permanently delete this staff account.")
.setPositiveButton("Yes", (d, w) -> .setPositiveButton("Yes", (d, w) ->
RetrofitClient.getEmployeeApi(requireContext()) employeeViewModel.deleteEmployee(employeeId).observe(getViewLifecycleOwner(), resource -> {
.deleteEmployee(employeeId) if (resource != null) {
.enqueue(new Callback<Void>() { switch (resource.status) {
public void onResponse(Call<Void> c, Response<Void> r) { case SUCCESS:
navigateBack(); navigateBack();
} break;
public void onFailure(Call<Void> c, Throwable t) { case ERROR:
Toast.makeText(getContext(), "Delete failed", Toast.makeText(getContext(), "Delete failed: " + resource.message,
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} break;
})) }
}
}))
.setNegativeButton("No", null).show(); .setNegativeButton("No", null).show();
} }