Update early fixes
This commit is contained in:
@@ -2,10 +2,12 @@ package com.example.petstoremobile.adapters;
|
|||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.databinding.ItemEmployeeBinding;
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -24,47 +26,48 @@ public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.Employ
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class EmployeeViewHolder extends RecyclerView.ViewHolder {
|
public static class EmployeeViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView tvFullName, tvUsername, tvEmail, tvPhone, tvRole, tvStatus;
|
private final ItemEmployeeBinding binding;
|
||||||
|
|
||||||
public EmployeeViewHolder(@NonNull View v) {
|
public EmployeeViewHolder(@NonNull ItemEmployeeBinding binding) {
|
||||||
super(v);
|
super(binding.getRoot());
|
||||||
tvFullName = v.findViewById(R.id.tvEmployeeFullName);
|
this.binding = binding;
|
||||||
tvUsername = v.findViewById(R.id.tvEmployeeUsername);
|
|
||||||
tvEmail = v.findViewById(R.id.tvEmployeeEmail);
|
|
||||||
tvPhone = v.findViewById(R.id.tvEmployeePhone);
|
|
||||||
tvRole = v.findViewById(R.id.tvEmployeeRole);
|
|
||||||
tvStatus = v.findViewById(R.id.tvEmployeeStatus);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public EmployeeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public EmployeeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
View v = LayoutInflater.from(parent.getContext())
|
ItemEmployeeBinding binding = ItemEmployeeBinding.inflate(
|
||||||
.inflate(R.layout.item_employee, parent, false);
|
LayoutInflater.from(parent.getContext()), parent, false);
|
||||||
return new EmployeeViewHolder(v);
|
return new EmployeeViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull EmployeeViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull EmployeeViewHolder holder, int position) {
|
||||||
EmployeeDTO e = list.get(position);
|
EmployeeDTO e = list.get(position);
|
||||||
|
ItemEmployeeBinding binding = holder.binding;
|
||||||
|
|
||||||
holder.tvFullName.setText(e.getFullName() != null ? e.getFullName() : "");
|
binding.tvEmployeeFullName.setText(e.getFullName() != null ? e.getFullName() : "");
|
||||||
holder.tvUsername.setText("@" + (e.getUsername() != null ? e.getUsername() : ""));
|
binding.tvEmployeeUsername.setText("@" + (e.getUsername() != null ? e.getUsername() : ""));
|
||||||
holder.tvEmail.setText(e.getEmail() != null ? e.getEmail() : "");
|
binding.tvEmployeeEmail.setText(e.getEmail() != null ? e.getEmail() : "");
|
||||||
holder.tvPhone.setText(e.getPhone() != null ? e.getPhone() : "");
|
|
||||||
|
|
||||||
// Role badge
|
// Role badge
|
||||||
String role = e.getRole() != null ? e.getRole() : "";
|
String role = e.getRole() != null ? e.getRole() : "STAFF";
|
||||||
holder.tvRole.setText(role);
|
binding.tvEmployeeRole.setText(role);
|
||||||
holder.tvRole.setBackgroundColor(
|
|
||||||
"ADMIN".equals(role) ? Color.parseColor("#1a759f") : Color.parseColor("#577590"));
|
|
||||||
|
|
||||||
// Status badge
|
if ("ADMIN".equalsIgnoreCase(role)) {
|
||||||
|
binding.tvEmployeeRole.setBackgroundColor(Color.parseColor("#1a759f"));
|
||||||
|
} else {
|
||||||
|
binding.tvEmployeeRole.setBackgroundColor(Color.parseColor("#577590"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status text and color
|
||||||
boolean active = Boolean.TRUE.equals(e.getActive());
|
boolean active = Boolean.TRUE.equals(e.getActive());
|
||||||
holder.tvStatus.setText(active ? "Active" : "Inactive");
|
binding.tvEmployeeStatus.setText(active ? "Active" : "Inactive");
|
||||||
holder.tvStatus.setBackgroundColor(
|
binding.tvEmployeeStatus.setTextColor(active ? Color.parseColor("#4CAF50") : Color.parseColor("#F44336"));
|
||||||
active ? Color.parseColor("#4CAF50") : Color.parseColor("#F44336"));
|
|
||||||
|
// Placeholder for profile image - matching Pet style
|
||||||
|
binding.ivEmployeeProfile.setImageResource(R.drawable.placeholder);
|
||||||
|
|
||||||
holder.itemView.setOnClickListener(v -> listener.onEmployeeClick(position));
|
holder.itemView.setOnClickListener(v -> listener.onEmployeeClick(position));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.databinding.ItemSaleBinding;
|
import com.example.petstoremobile.databinding.ItemSaleBinding;
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -53,11 +54,10 @@ public class SaleAdapter extends RecyclerView.Adapter<SaleAdapter.SaleViewHolder
|
|||||||
|
|
||||||
if (Boolean.TRUE.equals(s.getIsRefund())) {
|
if (Boolean.TRUE.equals(s.getIsRefund())) {
|
||||||
binding.tvSaleRefundBadge.setVisibility(View.VISIBLE);
|
binding.tvSaleRefundBadge.setVisibility(View.VISIBLE);
|
||||||
binding.tvSaleRefundBadge.setBackgroundColor(Color.parseColor("#F44336"));
|
binding.tvSaleTotal.setTextColor(ContextCompat.getColor(holder.itemView.getContext(), R.color.status_adopted));
|
||||||
binding.tvSaleTotal.setTextColor(Color.parseColor("#F44336"));
|
|
||||||
} else {
|
} else {
|
||||||
binding.tvSaleRefundBadge.setVisibility(View.GONE);
|
binding.tvSaleRefundBadge.setVisibility(View.GONE);
|
||||||
binding.tvSaleTotal.setTextColor(Color.parseColor("#4CAF50"));
|
binding.tvSaleTotal.setTextColor(ContextCompat.getColor(holder.itemView.getContext(), R.color.status_available));
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.itemView.setOnClickListener(v -> listener.onSaleClick(position));
|
holder.itemView.setOnClickListener(v -> listener.onSaleClick(position));
|
||||||
|
|||||||
@@ -15,7 +15,11 @@ public interface SaleApi {
|
|||||||
@GET("api/v1/sales")
|
@GET("api/v1/sales")
|
||||||
Call<PageResponse<SaleDTO>> getAllSales(
|
Call<PageResponse<SaleDTO>> getAllSales(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@Query("size") int size);
|
@Query("size") int size,
|
||||||
|
@Query("q") String query,
|
||||||
|
@Query("paymentMethod") String paymentMethod,
|
||||||
|
@Query("storeId") Long storeId,
|
||||||
|
@Query("sort") String sort);
|
||||||
|
|
||||||
@GET("api/v1/sales/{id}")
|
@GET("api/v1/sales/{id}")
|
||||||
Call<SaleDTO> getSaleById(@Path("id") Long id);
|
Call<SaleDTO> getSaleById(@Path("id") Long id);
|
||||||
|
|||||||
@@ -12,9 +12,16 @@ public class SaleDTO {
|
|||||||
private Long storeId;
|
private Long storeId;
|
||||||
private String storeName;
|
private String storeName;
|
||||||
private BigDecimal totalAmount;
|
private BigDecimal totalAmount;
|
||||||
|
private BigDecimal subtotalAmount;
|
||||||
|
private BigDecimal couponDiscountAmount;
|
||||||
|
private BigDecimal employeeDiscountAmount;
|
||||||
private String paymentMethod;
|
private String paymentMethod;
|
||||||
|
private String channel;
|
||||||
private Boolean isRefund;
|
private Boolean isRefund;
|
||||||
private Long originalSaleId;
|
private Long originalSaleId;
|
||||||
|
private Long cartId;
|
||||||
|
private Long couponId;
|
||||||
|
private Integer pointsEarned;
|
||||||
private List<SaleItemDTO> items;
|
private List<SaleItemDTO> items;
|
||||||
private String createdAt;
|
private String createdAt;
|
||||||
|
|
||||||
@@ -60,10 +67,26 @@ public class SaleDTO {
|
|||||||
return totalAmount;
|
return totalAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BigDecimal getSubtotalAmount() {
|
||||||
|
return subtotalAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getCouponDiscountAmount() {
|
||||||
|
return couponDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getEmployeeDiscountAmount() {
|
||||||
|
return employeeDiscountAmount;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPaymentMethod() {
|
public String getPaymentMethod() {
|
||||||
return paymentMethod;
|
return paymentMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
public Boolean getIsRefund() {
|
public Boolean getIsRefund() {
|
||||||
return isRefund;
|
return isRefund;
|
||||||
}
|
}
|
||||||
@@ -72,6 +95,18 @@ public class SaleDTO {
|
|||||||
return originalSaleId;
|
return originalSaleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getCartId() {
|
||||||
|
return cartId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCouponId() {
|
||||||
|
return couponId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPointsEarned() {
|
||||||
|
return pointsEarned;
|
||||||
|
}
|
||||||
|
|
||||||
public List<SaleItemDTO> getItems() {
|
public List<SaleItemDTO> getItems() {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ package com.example.petstoremobile.fragments.listfragments;
|
|||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -22,10 +20,10 @@ import com.example.petstoremobile.adapters.AdoptionAdapter;
|
|||||||
import com.example.petstoremobile.databinding.FragmentAdoptionBinding;
|
import com.example.petstoremobile.databinding.FragmentAdoptionBinding;
|
||||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.AdoptionViewModel;
|
import com.example.petstoremobile.viewmodels.AdoptionViewModel;
|
||||||
import com.example.petstoremobile.utils.EventDecorator;
|
import com.example.petstoremobile.utils.EventDecorator;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||||
@@ -86,15 +84,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
|
|
||||||
binding.fabAddAdoption.setOnClickListener(v -> openDetail(-1));
|
binding.fabAddAdoption.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
binding.btnHamburgerAdoption.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerAdoption, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
binding.btnToggleCalendarModeAdoption.setOnClickListener(v -> toggleCalendarMode());
|
binding.btnToggleCalendarModeAdoption.setOnClickListener(v -> toggleCalendarMode());
|
||||||
|
|
||||||
@@ -141,20 +131,24 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilterAdoption.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
||||||
if (binding.layoutFilterAdoption.getVisibility() == View.GONE) {
|
binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption);
|
||||||
binding.layoutFilterAdoption.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilterAdoption.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilterAdoption.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilterAdoption.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset filters when closing
|
binding.btnToggleFilterAdoption.setOnClickListener(v -> {
|
||||||
|
boolean isVisible = binding.layoutFilterAdoption.getVisibility() == View.VISIBLE;
|
||||||
|
binding.layoutFilterAdoption.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
binding.btnToggleFilterAdoption.setImageResource(isVisible ?
|
||||||
|
android.R.drawable.ic_menu_search :
|
||||||
|
android.R.drawable.ic_menu_close_clear_cancel);
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
binding.etSearchAdoption.setText("");
|
binding.etSearchAdoption.setText("");
|
||||||
binding.spinnerStatusAdoption.setSelection(0);
|
binding.spinnerStatusAdoption.setSelection(0);
|
||||||
binding.spinnerStoreAdoption.setSelection(0);
|
binding.spinnerStoreAdoption.setSelection(0);
|
||||||
selectedCalendarDay = null;
|
selectedCalendarDay = null;
|
||||||
binding.calendarViewAdoption.clearSelection();
|
binding.calendarViewAdoption.clearSelection();
|
||||||
|
loadAdoptions();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -214,13 +208,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
* Sets up the search bar for filtering
|
* Sets up the search bar for filtering
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchAdoption.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchAdoption, this::loadAdoptions);
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadAdoptions();
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,91 +5,77 @@ import android.os.Bundle;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
|
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.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 TextView tvTotalRevenue, tvTotalTransactions, tvAvgTransaction, tvTotalItems;
|
private FragmentAnalyticsBinding binding;
|
||||||
private LinearLayout llTopRevenue, llTopQuantity, llPaymentMethods, llEmployeePerformance, llDailyRevenue;
|
private SaleViewModel saleViewModel;
|
||||||
private Button btnRefresh;
|
|
||||||
private ImageButton hamburger;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_analytics, container, false);
|
binding = FragmentAnalyticsBinding.inflate(inflater, container, false);
|
||||||
|
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
||||||
|
|
||||||
initViews(view);
|
|
||||||
loadAnalytics();
|
loadAnalytics();
|
||||||
|
|
||||||
btnRefresh.setOnClickListener(v -> loadAnalytics());
|
binding.btnRefreshAnalytics.setOnClickListener(v -> loadAnalytics());
|
||||||
hamburger.setOnClickListener(v -> openDrawer());
|
|
||||||
|
|
||||||
return view;
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerAnalytics, this);
|
||||||
|
|
||||||
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openDrawer() {
|
@Override
|
||||||
Fragment parent = getParentFragment();
|
public void onDestroyView() {
|
||||||
if (parent != null) {
|
super.onDestroyView();
|
||||||
Fragment grandParent = parent.getParentFragment();
|
binding = null;
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initViews(View v) {
|
|
||||||
tvTotalRevenue = v.findViewById(R.id.tvTotalRevenue);
|
|
||||||
tvTotalTransactions = v.findViewById(R.id.tvTotalTransactions);
|
|
||||||
tvAvgTransaction = v.findViewById(R.id.tvAvgTransaction);
|
|
||||||
tvTotalItems = v.findViewById(R.id.tvTotalItems);
|
|
||||||
llTopRevenue = v.findViewById(R.id.llTopRevenue);
|
|
||||||
llTopQuantity = v.findViewById(R.id.llTopQuantity);
|
|
||||||
llPaymentMethods = v.findViewById(R.id.llPaymentMethods);
|
|
||||||
llEmployeePerformance = v.findViewById(R.id.llEmployeePerformance);
|
|
||||||
llDailyRevenue = v.findViewById(R.id.llDailyRevenue);
|
|
||||||
btnRefresh = v.findViewById(R.id.btnRefreshAnalytics);
|
|
||||||
hamburger = v.findViewById(R.id.btnHamburgerAnalytics);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAnalytics() {
|
private void loadAnalytics() {
|
||||||
// Clear all sections
|
// Clear all sections
|
||||||
llTopRevenue.removeAllViews();
|
binding.llTopRevenue.removeAllViews();
|
||||||
llTopQuantity.removeAllViews();
|
binding.llTopQuantity.removeAllViews();
|
||||||
llPaymentMethods.removeAllViews();
|
binding.llPaymentMethods.removeAllViews();
|
||||||
llEmployeePerformance.removeAllViews();
|
binding.llEmployeePerformance.removeAllViews();
|
||||||
llDailyRevenue.removeAllViews();
|
binding.llDailyRevenue.removeAllViews();
|
||||||
|
|
||||||
// Show loading
|
// Show loading
|
||||||
tvTotalRevenue.setText("Loading...");
|
binding.tvTotalRevenue.setText("Loading...");
|
||||||
tvTotalTransactions.setText("...");
|
binding.tvTotalTransactions.setText("...");
|
||||||
tvAvgTransaction.setText("...");
|
binding.tvAvgTransaction.setText("...");
|
||||||
tvTotalItems.setText("...");
|
binding.tvTotalItems.setText("...");
|
||||||
|
|
||||||
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000)
|
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());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
Log.e("Analytics", resource.message != null ? resource.message : "Error loading sales");
|
||||||
showError("Failed to load sales data");
|
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");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,10 +107,10 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
? totalRevenue.divide(BigDecimal.valueOf(totalTx), 2, RoundingMode.HALF_UP)
|
? totalRevenue.divide(BigDecimal.valueOf(totalTx), 2, RoundingMode.HALF_UP)
|
||||||
: BigDecimal.ZERO;
|
: BigDecimal.ZERO;
|
||||||
|
|
||||||
tvTotalRevenue.setText("$" + totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
binding.tvTotalRevenue.setText("$" + totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
||||||
tvTotalTransactions.setText(String.valueOf(totalTx));
|
binding.tvTotalTransactions.setText(String.valueOf(totalTx));
|
||||||
tvAvgTransaction.setText("$" + avgTx);
|
binding.tvAvgTransaction.setText("$" + avgTx);
|
||||||
tvTotalItems.setText(String.valueOf(totalItems));
|
binding.tvTotalItems.setText(String.valueOf(totalItems));
|
||||||
|
|
||||||
// ── Top Products by Revenue ───────────────────────────
|
// ── Top Products by Revenue ───────────────────────────
|
||||||
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
||||||
@@ -150,28 +136,28 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
topRevenue.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
topRevenue.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||||
BigDecimal maxRevenue = topRevenue.isEmpty() ? BigDecimal.ONE : topRevenue.get(0).getValue();
|
BigDecimal maxRevenue = topRevenue.isEmpty() ? BigDecimal.ONE : topRevenue.get(0).getValue();
|
||||||
|
|
||||||
llTopRevenue.removeAllViews();
|
binding.llTopRevenue.removeAllViews();
|
||||||
for (int i = 0; i < Math.min(5, topRevenue.size()); i++) {
|
for (int i = 0; i < Math.min(5, topRevenue.size()); i++) {
|
||||||
Map.Entry<String, BigDecimal> e = topRevenue.get(i);
|
Map.Entry<String, BigDecimal> e = topRevenue.get(i);
|
||||||
addBarRow(llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
addBarRow(binding.llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
||||||
}
|
}
|
||||||
if (topRevenue.isEmpty())
|
if (topRevenue.isEmpty())
|
||||||
addEmptyRow(llTopRevenue, "No data");
|
addEmptyRow(binding.llTopRevenue, "No data");
|
||||||
|
|
||||||
// Sort by quantity desc, take top 5
|
// Sort by quantity desc, take top 5
|
||||||
List<Map.Entry<String, Integer>> topQuantity = new ArrayList<>(quantityByProduct.entrySet());
|
List<Map.Entry<String, Integer>> topQuantity = new ArrayList<>(quantityByProduct.entrySet());
|
||||||
topQuantity.sort((a, b) -> b.getValue() - a.getValue());
|
topQuantity.sort((a, b) -> b.getValue() - a.getValue());
|
||||||
int maxQty = topQuantity.isEmpty() ? 1 : topQuantity.get(0).getValue();
|
int maxQty = topQuantity.isEmpty() ? 1 : topQuantity.get(0).getValue();
|
||||||
|
|
||||||
llTopQuantity.removeAllViews();
|
binding.llTopQuantity.removeAllViews();
|
||||||
for (int i = 0; i < Math.min(5, topQuantity.size()); i++) {
|
for (int i = 0; i < Math.min(5, topQuantity.size()); i++) {
|
||||||
Map.Entry<String, Integer> e = topQuantity.get(i);
|
Map.Entry<String, Integer> e = topQuantity.get(i);
|
||||||
addBarRow(llTopQuantity, e.getKey(), e.getValue() + " units",
|
addBarRow(binding.llTopQuantity, e.getKey(), e.getValue() + " units",
|
||||||
(float) e.getValue() / maxQty, "#4ecdc4");
|
(float) e.getValue() / maxQty, "#4ecdc4");
|
||||||
}
|
}
|
||||||
if (topQuantity.isEmpty())
|
if (topQuantity.isEmpty())
|
||||||
addEmptyRow(llTopQuantity, "No data");
|
addEmptyRow(binding.llTopQuantity, "No data");
|
||||||
|
|
||||||
// ── Payment Methods ───────────────────────────────────
|
// ── Payment Methods ───────────────────────────────────
|
||||||
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
||||||
@@ -183,16 +169,16 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
int maxPayment = paymentCount.values().stream().max(Integer::compare).orElse(1);
|
int maxPayment = paymentCount.values().stream().max(Integer::compare).orElse(1);
|
||||||
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
||||||
int ci = 0;
|
int ci = 0;
|
||||||
llPaymentMethods.removeAllViews();
|
binding.llPaymentMethods.removeAllViews();
|
||||||
for (Map.Entry<String, Integer> e : paymentCount.entrySet()) {
|
for (Map.Entry<String, Integer> e : paymentCount.entrySet()) {
|
||||||
addBarRow(llPaymentMethods, e.getKey(),
|
addBarRow(binding.llPaymentMethods, e.getKey(),
|
||||||
e.getValue() + " transactions",
|
e.getValue() + " transactions",
|
||||||
(float) e.getValue() / maxPayment,
|
(float) e.getValue() / maxPayment,
|
||||||
paymentColors[ci % paymentColors.length]);
|
paymentColors[ci % paymentColors.length]);
|
||||||
ci++;
|
ci++;
|
||||||
}
|
}
|
||||||
if (paymentCount.isEmpty())
|
if (paymentCount.isEmpty())
|
||||||
addEmptyRow(llPaymentMethods, "No data");
|
addEmptyRow(binding.llPaymentMethods, "No data");
|
||||||
|
|
||||||
// ── Employee Performance ──────────────────────────────
|
// ── Employee Performance ──────────────────────────────
|
||||||
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
||||||
@@ -206,15 +192,15 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
empList.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
empList.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||||
BigDecimal maxEmp = empList.isEmpty() ? BigDecimal.ONE : empList.get(0).getValue();
|
BigDecimal maxEmp = empList.isEmpty() ? BigDecimal.ONE : empList.get(0).getValue();
|
||||||
|
|
||||||
llEmployeePerformance.removeAllViews();
|
binding.llEmployeePerformance.removeAllViews();
|
||||||
for (Map.Entry<String, BigDecimal> e : empList) {
|
for (Map.Entry<String, BigDecimal> e : empList) {
|
||||||
addBarRow(llEmployeePerformance, e.getKey(),
|
addBarRow(binding.llEmployeePerformance, e.getKey(),
|
||||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
e.getValue().floatValue() / maxEmp.floatValue(),
|
e.getValue().floatValue() / maxEmp.floatValue(),
|
||||||
"#1a759f");
|
"#1a759f");
|
||||||
}
|
}
|
||||||
if (empList.isEmpty())
|
if (empList.isEmpty())
|
||||||
addEmptyRow(llEmployeePerformance, "No data");
|
addEmptyRow(binding.llEmployeePerformance, "No data");
|
||||||
|
|
||||||
// ── Daily Revenue (last 7 days) ───────────────────────
|
// ── Daily Revenue (last 7 days) ───────────────────────
|
||||||
Map<String, BigDecimal> dailyRevenue = new TreeMap<>();
|
Map<String, BigDecimal> dailyRevenue = new TreeMap<>();
|
||||||
@@ -247,26 +233,25 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
if (maxDaily.compareTo(BigDecimal.ZERO) == 0)
|
if (maxDaily.compareTo(BigDecimal.ZERO) == 0)
|
||||||
maxDaily = BigDecimal.ONE;
|
maxDaily = BigDecimal.ONE;
|
||||||
|
|
||||||
llDailyRevenue.removeAllViews();
|
binding.llDailyRevenue.removeAllViews();
|
||||||
for (Map.Entry<String, BigDecimal> e : dailyRevenue.entrySet()) {
|
for (Map.Entry<String, BigDecimal> e : dailyRevenue.entrySet()) {
|
||||||
// Show just MM-DD
|
// Show just MM-DD
|
||||||
String label = e.getKey().length() >= 10
|
String label = e.getKey().length() >= 10
|
||||||
? e.getKey().substring(5)
|
? e.getKey().substring(5)
|
||||||
: e.getKey();
|
: e.getKey();
|
||||||
addBarRow(llDailyRevenue, label,
|
addBarRow(binding.llDailyRevenue, label,
|
||||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
e.getValue().floatValue() / maxDaily.floatValue(),
|
e.getValue().floatValue() / maxDaily.floatValue(),
|
||||||
"#ff6b35");
|
"#ff6b35");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
@@ -286,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);
|
||||||
@@ -294,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));
|
||||||
@@ -315,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"));
|
||||||
@@ -323,12 +305,12 @@ 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;
|
||||||
tvTotalRevenue.setText("Error");
|
binding.tvTotalRevenue.setText("Error");
|
||||||
tvTotalTransactions.setText("—");
|
binding.tvTotalTransactions.setText("—");
|
||||||
tvAvgTransaction.setText("—");
|
binding.tvAvgTransaction.setText("—");
|
||||||
tvTotalItems.setText("—");
|
binding.tvTotalItems.setText("—");
|
||||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ 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 android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -23,10 +21,10 @@ import com.example.petstoremobile.adapters.AppointmentAdapter;
|
|||||||
import com.example.petstoremobile.databinding.FragmentAppointmentBinding;
|
import com.example.petstoremobile.databinding.FragmentAppointmentBinding;
|
||||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.AppointmentViewModel;
|
import com.example.petstoremobile.viewmodels.AppointmentViewModel;
|
||||||
import com.example.petstoremobile.utils.EventDecorator;
|
import com.example.petstoremobile.utils.EventDecorator;
|
||||||
import com.example.petstoremobile.viewmodels.AuthViewModel;
|
import com.example.petstoremobile.viewmodels.AuthViewModel;
|
||||||
@@ -94,15 +92,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
|
|
||||||
binding.fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1));
|
binding.fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1));
|
||||||
|
|
||||||
binding.btnHamburger.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
binding.btnToggleCalendarMode.setOnClickListener(v -> toggleCalendarMode());
|
binding.btnToggleCalendarMode.setOnClickListener(v -> toggleCalendarMode());
|
||||||
|
|
||||||
@@ -171,21 +161,26 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment,
|
||||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
binding.spinnerStatus, binding.spinnerStore);
|
||||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilter.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset filters when closing
|
// Add additional reset logic for elements specific to this fragment
|
||||||
|
binding.btnToggleFilter.setOnClickListener(v -> {
|
||||||
|
boolean isVisible = binding.layoutFilter.getVisibility() == View.VISIBLE;
|
||||||
|
binding.layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
binding.btnToggleFilter.setImageResource(isVisible ?
|
||||||
|
android.R.drawable.ic_menu_search :
|
||||||
|
android.R.drawable.ic_menu_close_clear_cancel);
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
binding.etSearchAppointment.setText("");
|
binding.etSearchAppointment.setText("");
|
||||||
binding.spinnerStatus.setSelection(0);
|
binding.spinnerStatus.setSelection(0);
|
||||||
binding.spinnerStore.setSelection(0);
|
binding.spinnerStore.setSelection(0);
|
||||||
binding.btnMyAppointments.setChecked(false);
|
binding.btnMyAppointments.setChecked(false);
|
||||||
selectedCalendarDay = null;
|
selectedCalendarDay = null;
|
||||||
binding.calendarView.clearSelection();
|
binding.calendarView.clearSelection();
|
||||||
|
loadAppointmentData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -214,10 +209,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
*/
|
*/
|
||||||
private void updateCalendarDecorators() {
|
private void updateCalendarDecorators() {
|
||||||
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
||||||
|
SimpleDateFormat displayFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
for (AppointmentDTO appointment : appointmentList) {
|
for (AppointmentDTO appointment : appointmentList) {
|
||||||
try {
|
try {
|
||||||
//Get the appointment date
|
//Get the appointment date
|
||||||
Date date = dateFormat.parse(appointment.getAppointmentDate());
|
Date date = displayFormat.parse(appointment.getAppointmentDate());
|
||||||
//if the date is not null, add it to the hashset
|
//if the date is not null, add it to the hashset
|
||||||
if (date != null) {
|
if (date != null) {
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
@@ -237,13 +233,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
* Configures the search bar for filtering.
|
* Configures the search bar for filtering.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchAppointment.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchAppointment, this::loadAppointmentData);
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadAppointmentData();
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -22,8 +20,8 @@ import com.example.petstoremobile.adapters.InventoryAdapter;
|
|||||||
import com.example.petstoremobile.databinding.FragmentInventoryBinding;
|
import com.example.petstoremobile.databinding.FragmentInventoryBinding;
|
||||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
@@ -79,15 +77,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
|
|
||||||
binding.fabAddInventory.setOnClickListener(v -> openDetail(null));
|
binding.fabAddInventory.setOnClickListener(v -> openDetail(null));
|
||||||
|
|
||||||
binding.btnHamburger.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -115,32 +105,14 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory, binding.spinnerStore);
|
||||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
|
||||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilter.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset filters when closing
|
|
||||||
binding.etSearchInventory.setText("");
|
|
||||||
binding.spinnerStore.setSelection(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the search bar for filtering.
|
* Sets up the search bar for filtering.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchInventory.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchInventory, () -> loadInventory(true));
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadInventory(true);
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ 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 android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -19,13 +17,14 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.PetAdapter;
|
import com.example.petstoremobile.adapters.PetAdapter;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentPetBinding;
|
import com.example.petstoremobile.databinding.FragmentPetBinding;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
import com.example.petstoremobile.viewmodels.PetViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||||
|
|
||||||
@@ -48,6 +47,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the fragment and its associated ViewModels.
|
* Initializes the fragment and its associated ViewModels.
|
||||||
@@ -78,15 +78,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
|
|
||||||
binding.fabAddPet.setOnClickListener(v -> openPetDetails());
|
binding.fabAddPet.setOnClickListener(v -> openPetDetails());
|
||||||
|
|
||||||
binding.btnHamburger.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -124,34 +116,15 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
||||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
binding.spinnerStatus, binding.spinnerSpecies, binding.spinnerStore);
|
||||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilter.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset filters when closing
|
|
||||||
binding.etSearchPet.setText("");
|
|
||||||
binding.spinnerStatus.setSelection(0);
|
|
||||||
binding.spinnerSpecies.setSelection(0);
|
|
||||||
binding.spinnerStore.setSelection(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the search bar.
|
* Configures the search bar.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchPet.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchPet, this::loadPetData);
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadPetData();
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -272,6 +245,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new PetAdapter(petList, this);
|
adapter = new PetAdapter(petList, this);
|
||||||
adapter.setBaseUrl(baseUrl);
|
adapter.setBaseUrl(baseUrl);
|
||||||
|
adapter.setToken(tokenManager.getToken());
|
||||||
binding.recyclerViewPets.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewPets.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.recyclerViewPets.setAdapter(adapter);
|
binding.recyclerViewPets.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ 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 android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -22,9 +20,9 @@ import com.example.petstoremobile.adapters.ProductAdapter;
|
|||||||
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -71,15 +69,7 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
|
|
||||||
binding.fabAddProduct.setOnClickListener(v -> openProductDetails(-1));
|
binding.fabAddProduct.setOnClickListener(v -> openProductDetails(-1));
|
||||||
|
|
||||||
binding.btnHamburgerProduct.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerProduct, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -104,32 +94,15 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
|
||||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
binding.etSearchProduct, binding.spinnerCategory);
|
||||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilter.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset filters when closing
|
|
||||||
binding.etSearchProduct.setText("");
|
|
||||||
binding.spinnerCategory.setSelection(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the search bar for triggering data load from backend.
|
* Configures the search bar for triggering data load from backend.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchProduct.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchProduct, this::loadProductData);
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadProductData();
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -22,10 +20,10 @@ import com.example.petstoremobile.databinding.FragmentProductSupplierBinding;
|
|||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
|
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
||||||
@@ -79,15 +77,7 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
|
|
||||||
binding.fabAddPS.setOnClickListener(v -> openDetail(-1));
|
binding.fabAddPS.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
binding.btnHamburgerPS.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerPS, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -125,20 +115,8 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS,
|
||||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
binding.spinnerProduct, binding.spinnerSupplier);
|
||||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilter.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset filters when closing
|
|
||||||
binding.etSearchPS.setText("");
|
|
||||||
binding.spinnerProduct.setSelection(0);
|
|
||||||
binding.spinnerSupplier.setSelection(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,13 +132,7 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
* Configures the search bar for filtering.
|
* Configures the search bar for filtering.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchPS.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchPS, this::loadData);
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -21,9 +19,9 @@ import com.example.petstoremobile.adapters.PurchaseOrderAdapter;
|
|||||||
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
||||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
|
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||||
|
|
||||||
@@ -67,15 +65,7 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
|
|
||||||
binding.btnHamburgerPO.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerPO, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -100,32 +90,14 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO, binding.spinnerStore);
|
||||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
|
||||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilter.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset filters when closing
|
|
||||||
binding.etSearchPO.setText("");
|
|
||||||
binding.spinnerStore.setSelection(0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the search bar for filtering.
|
* Configures the search bar for filtering.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchPO.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchPO, this::loadData);
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadData();
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,19 +7,23 @@ import androidx.fragment.app.Fragment;
|
|||||||
import androidx.lifecycle.ViewModelProvider;
|
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 android.text.Editable;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.text.TextWatcher;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.SaleAdapter;
|
import com.example.petstoremobile.adapters.SaleAdapter;
|
||||||
import com.example.petstoremobile.databinding.FragmentSaleBinding;
|
import com.example.petstoremobile.databinding.FragmentSaleBinding;
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.SaleViewModel;
|
import com.example.petstoremobile.viewmodels.SaleViewModel;
|
||||||
|
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -29,11 +33,20 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
||||||
|
|
||||||
|
private static final String TAG = "SaleFragment";
|
||||||
|
private static final int PAGE_SIZE = 200;
|
||||||
|
|
||||||
private FragmentSaleBinding binding;
|
private FragmentSaleBinding binding;
|
||||||
private List<SaleDTO> saleList = new ArrayList<>();
|
private final List<SaleDTO> saleList = new ArrayList<>();
|
||||||
private List<SaleDTO> filteredList = new ArrayList<>();
|
private final List<StoreDTO> storeList = new ArrayList<>();
|
||||||
private SaleAdapter adapter;
|
private SaleAdapter adapter;
|
||||||
private SaleViewModel saleViewModel;
|
private SaleViewModel saleViewModel;
|
||||||
|
private StoreViewModel storeViewModel;
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
private int currentPage = 0;
|
||||||
|
private boolean isLastPage = false;
|
||||||
|
private boolean isLoading = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -46,21 +59,17 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
||||||
|
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||||
|
|
||||||
setupRecyclerView();
|
setupRecyclerView();
|
||||||
setupSearch();
|
setupSearch();
|
||||||
|
setupStoreFilter();
|
||||||
|
setupPaymentMethodFilter();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
loadSales();
|
setupFilterToggle();
|
||||||
|
loadSales(true);
|
||||||
|
|
||||||
binding.btnHamburger.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
binding.fabAddSale.setOnClickListener(v ->
|
binding.fabAddSale.setOnClickListener(v ->
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail));
|
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail));
|
||||||
@@ -69,6 +78,37 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
|
NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
loadStoreData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFilterToggle() {
|
||||||
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
||||||
|
binding.spinnerPaymentMethod, binding.spinnerStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupStoreFilter() {
|
||||||
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadStoreData() {
|
||||||
|
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
storeList.clear();
|
||||||
|
storeList.addAll(resource.data.getContent());
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
||||||
|
StoreDTO::getStoreName, "Stores", null, StoreDTO::getStoreId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupPaymentMethodFilter() {
|
||||||
|
String[] paymentMethods = {"Payments", "Cash", "Card"};
|
||||||
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
@@ -76,61 +116,91 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new SaleAdapter(filteredList, this);
|
adapter = new SaleAdapter(saleList, this);
|
||||||
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.recyclerViewSales.setAdapter(adapter);
|
binding.recyclerViewSales.setAdapter(adapter);
|
||||||
|
|
||||||
|
binding.recyclerViewSales.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (dy <= 0) return;
|
||||||
|
LinearLayoutManager lm = (LinearLayoutManager) binding.recyclerViewSales.getLayoutManager();
|
||||||
|
if (lm != null) {
|
||||||
|
int visible = lm.getChildCount();
|
||||||
|
int total = lm.getItemCount();
|
||||||
|
int firstVis = lm.findFirstVisibleItemPosition();
|
||||||
|
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
||||||
|
loadSales(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchSale.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchSale, () -> loadSales(true));
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
filterSales(s.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void filterSales(String query) {
|
|
||||||
filteredList.clear();
|
|
||||||
if (query.isEmpty()) {
|
|
||||||
filteredList.addAll(saleList);
|
|
||||||
} else {
|
|
||||||
String lower = query.toLowerCase();
|
|
||||||
for (SaleDTO s : saleList) {
|
|
||||||
if ((s.getEmployeeName() != null && s.getEmployeeName().toLowerCase().contains(lower))
|
|
||||||
|| (s.getSaleDate() != null && s.getSaleDate().toLowerCase().contains(lower))
|
|
||||||
|| (s.getPaymentMethod() != null && s.getPaymentMethod().toLowerCase().contains(lower))
|
|
||||||
|| (s.getSaleId() != null && String.valueOf(s.getSaleId()).contains(lower))) {
|
|
||||||
filteredList.add(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (adapter != null) adapter.notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshSale.setOnRefreshListener(() -> {
|
binding.swipeRefreshSale.setOnRefreshListener(() -> loadSales(true));
|
||||||
loadSales();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSales() {
|
private void loadSales(boolean reset) {
|
||||||
saleViewModel.getAllSales(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
if (isLoading) return;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
currentPage = 0;
|
||||||
|
isLastPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String query = binding.etSearchSale != null ? binding.etSearchSale.getText().toString().trim() : "";
|
||||||
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
|
String paymentMethod = null;
|
||||||
|
if (binding.spinnerPaymentMethod.getSelectedItemPosition() > 0) {
|
||||||
|
paymentMethod = (String) binding.spinnerPaymentMethod.getSelectedItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
Long storeId = null;
|
||||||
|
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
||||||
|
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||||
|
}
|
||||||
|
|
||||||
|
saleViewModel.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, "saleDate,desc").observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
|
||||||
|
switch (resource.status) {
|
||||||
|
case LOADING:
|
||||||
|
isLoading = true;
|
||||||
|
binding.swipeRefreshSale.setRefreshing(true);
|
||||||
|
break;
|
||||||
|
case SUCCESS:
|
||||||
|
isLoading = false;
|
||||||
binding.swipeRefreshSale.setRefreshing(false);
|
binding.swipeRefreshSale.setRefreshing(false);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.data != null) {
|
||||||
saleList.clear();
|
if (reset) saleList.clear();
|
||||||
saleList.addAll(resource.data.getContent());
|
saleList.addAll(resource.data.getContent());
|
||||||
filterSales(binding.etSearchSale.getText() != null
|
adapter.notifyDataSetChanged();
|
||||||
? binding.etSearchSale.getText().toString() : "");
|
isLastPage = resource.data.isLast();
|
||||||
|
if (!isLastPage) currentPage++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
isLoading = false;
|
||||||
|
binding.swipeRefreshSale.setRefreshing(false);
|
||||||
|
Log.e(TAG, "Error loading sales: " + resource.message);
|
||||||
|
if (getContext() != null) {
|
||||||
|
Toast.makeText(getContext(), "Failed to load sales: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSaleClick(int position) {
|
public void onSaleClick(int position) {
|
||||||
SaleDTO sale = filteredList.get(position);
|
if (position < 0 || position >= saleList.size()) return;
|
||||||
|
SaleDTO sale = saleList.get(position);
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (sale.getSaleId() != null) {
|
if (sale.getSaleId() != null) {
|
||||||
args.putLong("saleId", sale.getSaleId());
|
args.putLong("saleId", sale.getSaleId());
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -21,9 +19,8 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.adapters.ServiceAdapter;
|
import com.example.petstoremobile.adapters.ServiceAdapter;
|
||||||
import com.example.petstoremobile.databinding.FragmentServiceBinding;
|
import com.example.petstoremobile.databinding.FragmentServiceBinding;
|
||||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ServiceViewModel;
|
import com.example.petstoremobile.viewmodels.ServiceViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -77,15 +74,7 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
|
|
||||||
binding.fabAddService.setOnClickListener(v -> openDetail(null));
|
binding.fabAddService.setOnClickListener(v -> openDetail(null));
|
||||||
|
|
||||||
binding.btnHamburger.setOnClickListener(v -> {
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -113,31 +102,14 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService);
|
||||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
|
||||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilter.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset filters when closing
|
|
||||||
binding.etSearchService.setText("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up the search bar for filtering.
|
* Sets up the search bar for filtering.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchService.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchService, () -> loadServices(true));
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadServices(true);
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,85 +1,63 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.*;
|
|
||||||
import android.util.Log;
|
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 androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
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.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.viewmodels.EmployeeViewModel;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
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 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;
|
||||||
private SwipeRefreshLayout swipeRefresh;
|
|
||||||
private EditText etSearch;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_staff, container, false);
|
binding = FragmentStaffBinding.inflate(inflater, container, false);
|
||||||
|
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
|
||||||
|
|
||||||
setupRecyclerView(view);
|
setupRecyclerView();
|
||||||
setupSearch(view);
|
setupSearch();
|
||||||
setupSwipeRefresh(view);
|
setupSwipeRefresh();
|
||||||
loadStaff();
|
loadStaff();
|
||||||
|
|
||||||
FloatingActionButton fab = view.findViewById(R.id.fabAddStaff);
|
binding.fabAddStaff.setOnClickListener(v -> openDetail(-1));
|
||||||
fab.setOnClickListener(v -> openDetail(-1));
|
|
||||||
|
|
||||||
ImageButton hamburger = view.findViewById(R.id.btnHamburgerStaff);
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerStaff, this);
|
||||||
hamburger.setOnClickListener(v -> openDrawer());
|
UIUtils.setupFilterToggle(binding.btnToggleFilterStaff, binding.layoutFilterStaff, binding.etSearchStaff);
|
||||||
|
|
||||||
return view;
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openDrawer() {
|
private void setupRecyclerView() {
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupRecyclerView(View view) {
|
|
||||||
RecyclerView rv = view.findViewById(R.id.recyclerViewStaff);
|
|
||||||
adapter = new EmployeeAdapter(filteredList, this);
|
adapter = new EmployeeAdapter(filteredList, this);
|
||||||
rv.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewStaff.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
rv.setAdapter(adapter);
|
binding.recyclerViewStaff.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSearch(View view) {
|
private void setupSearch() {
|
||||||
etSearch = view.findViewById(R.id.etSearchStaff);
|
UIUtils.attachSearch(binding.etSearchStaff, () -> filter(binding.etSearchStaff.getText().toString()));
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
|
||||||
public void beforeTextChanged(CharSequence s, int a, int b, int c) {}
|
|
||||||
public void afterTextChanged(Editable s) {}
|
|
||||||
public void onTextChanged(CharSequence s, int a, int b, int c) {
|
|
||||||
filter(s.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh() {
|
||||||
swipeRefresh = view.findViewById(R.id.swipeRefreshStaff);
|
binding.swipeRefreshStaff.setOnRefreshListener(this::loadStaff);
|
||||||
swipeRefresh.setOnRefreshListener(this::loadStaff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filter(String query) {
|
private void filter(String query) {
|
||||||
@@ -101,24 +79,29 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadStaff() {
|
private void loadStaff() {
|
||||||
if (swipeRefresh != null) swipeRefresh.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 (swipeRefresh != null) swipeRefresh.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(etSearch != null ? etSearch.getText().toString() : "");
|
filter(binding != null ? binding.etSearchStaff.getText().toString() : "");
|
||||||
} else {
|
}
|
||||||
Toast.makeText(getContext(), "Failed to load staff",
|
break;
|
||||||
|
case ERROR:
|
||||||
|
binding.swipeRefreshStaff.setRefreshing(false);
|
||||||
|
if (getContext() != null) {
|
||||||
|
Toast.makeText(getContext(), resource.message != null ? resource.message : "Failed to load staff",
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case LOADING:
|
||||||
|
binding.swipeRefreshStaff.setRefreshing(true);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
public void onFailure(Call<PageResponse<EmployeeDTO>> c, Throwable t) {
|
|
||||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
|
|
||||||
Log.e("StaffFragment", t.getMessage());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -144,4 +127,10 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
public void onEmployeeClick(int position) {
|
public void onEmployeeClick(int position) {
|
||||||
openDetail(position);
|
openDetail(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ 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 android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -21,9 +19,9 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.adapters.SupplierAdapter;
|
import com.example.petstoremobile.adapters.SupplierAdapter;
|
||||||
import com.example.petstoremobile.databinding.FragmentSupplierBinding;
|
import com.example.petstoremobile.databinding.FragmentSupplierBinding;
|
||||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -67,16 +65,7 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
//Add button to opens the add dialog
|
//Add button to opens the add dialog
|
||||||
binding.fabAddSupplier.setOnClickListener(v -> openSupplierDetails(-1));
|
binding.fabAddSupplier.setOnClickListener(v -> openSupplierDetails(-1));
|
||||||
|
|
||||||
//Make the hamburger button open the drawer from listFragment
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
binding.btnHamburger.setOnClickListener(v -> {
|
|
||||||
Fragment parent = getParentFragment();
|
|
||||||
if (parent != null) {
|
|
||||||
Fragment grandParent = parent.getParentFragment();
|
|
||||||
if (grandParent instanceof ListFragment) {
|
|
||||||
((ListFragment) grandParent).openDrawer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -104,31 +93,14 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
* Sets up the filter toggle button to show/hide the filter layout.
|
||||||
*/
|
*/
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier);
|
||||||
if (binding.layoutFilter.getVisibility() == View.GONE) {
|
|
||||||
binding.layoutFilter.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
} else {
|
|
||||||
binding.layoutFilter.setVisibility(View.GONE);
|
|
||||||
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
|
|
||||||
|
|
||||||
// Reset search when closing
|
|
||||||
binding.etSearchSupplier.setText("");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures the search bar for filtering.
|
* Configures the search bar for filtering.
|
||||||
*/
|
*/
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
binding.etSearchSupplier.addTextChangedListener(new TextWatcher() {
|
UIUtils.attachSearch(binding.etSearchSupplier, this::loadSupplierData);
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
loadSupplierData();
|
|
||||||
}
|
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -415,7 +415,7 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
} else if (errorMessage.toLowerCase().contains("not available")) {
|
} else if (errorMessage.toLowerCase().contains("not available")) {
|
||||||
showNoAvailabilityDialog();
|
showNoAvailabilityDialog();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Operation failed", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Something went wrong", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Something went wrong", Toast.LENGTH_SHORT).show();
|
||||||
|
|||||||
@@ -220,6 +220,13 @@ public class PetDetailFragment extends Fragment {
|
|||||||
binding.tvPetId.setText("ID: " + petId);
|
binding.tvPetId.setText("ID: " + petId);
|
||||||
binding.tvPetId.setVisibility(View.VISIBLE);
|
binding.tvPetId.setVisibility(View.VISIBLE);
|
||||||
binding.btnDeletePet.setVisibility(View.VISIBLE);
|
binding.btnDeletePet.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Disable species and breed fields in edit mode
|
||||||
|
binding.etPetSpecies.setEnabled(false);
|
||||||
|
binding.etPetBreed.setEnabled(false);
|
||||||
|
binding.etPetSpecies.setAlpha(0.5f);
|
||||||
|
binding.etPetBreed.setAlpha(0.5f);
|
||||||
|
|
||||||
loadPetData();
|
loadPetData();
|
||||||
} else {
|
} else {
|
||||||
// Pet is being added
|
// Pet is being added
|
||||||
@@ -229,6 +236,12 @@ public class PetDetailFragment extends Fragment {
|
|||||||
binding.tvPetId.setVisibility(View.GONE);
|
binding.tvPetId.setVisibility(View.GONE);
|
||||||
binding.btnDeletePet.setVisibility(View.GONE);
|
binding.btnDeletePet.setVisibility(View.GONE);
|
||||||
binding.btnSavePet.setText("Add");
|
binding.btnSavePet.setText("Add");
|
||||||
|
|
||||||
|
// Enable species and breed fields in edit mode
|
||||||
|
binding.etPetSpecies.setEnabled(true);
|
||||||
|
binding.etPetBreed.setEnabled(true);
|
||||||
|
binding.etPetSpecies.setAlpha(1.0f);
|
||||||
|
binding.etPetBreed.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,16 +347,20 @@ public class PetDetailFragment extends Fragment {
|
|||||||
if ("Available".equalsIgnoreCase(status)) {
|
if ("Available".equalsIgnoreCase(status)) {
|
||||||
binding.spinnerCustomer.setSelection(0);
|
binding.spinnerCustomer.setSelection(0);
|
||||||
binding.spinnerCustomer.setEnabled(false);
|
binding.spinnerCustomer.setEnabled(false);
|
||||||
|
binding.spinnerCustomer.setAlpha(0.5f);
|
||||||
} else {
|
} else {
|
||||||
binding.spinnerCustomer.setEnabled(true);
|
binding.spinnerCustomer.setEnabled(true);
|
||||||
|
binding.spinnerCustomer.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Disable the store spinner if the status is "Owned"
|
//Disable the store spinner if the status is "Owned"
|
||||||
if ("Owned".equalsIgnoreCase(status)) {
|
if ("Owned".equalsIgnoreCase(status)) {
|
||||||
binding.spinnerStore.setSelection(0);
|
binding.spinnerStore.setSelection(0);
|
||||||
binding.spinnerStore.setEnabled(false);
|
binding.spinnerStore.setEnabled(false);
|
||||||
|
binding.spinnerStore.setAlpha(0.5f);
|
||||||
} else {
|
} else {
|
||||||
binding.spinnerStore.setEnabled(true);
|
binding.spinnerStore.setEnabled(true);
|
||||||
|
binding.spinnerStore.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.api.*;
|
import com.example.petstoremobile.api.*;
|
||||||
import com.example.petstoremobile.api.auth.TokenManager;
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
@@ -73,7 +74,11 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onImagePicked(Uri uri) {
|
public void onImagePicked(Uri uri) {
|
||||||
photoUri = uri;
|
photoUri = uri;
|
||||||
Glide.with(ProductDetailFragment.this).load(uri).into(binding.ivProductImage);
|
Glide.with(ProductDetailFragment.this)
|
||||||
|
.load(uri)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.into(binding.ivProductImage);
|
||||||
hasImage = true;
|
hasImage = true;
|
||||||
isImageChanged = true;
|
isImageChanged = true;
|
||||||
isImageRemoved = false;
|
isImageRemoved = false;
|
||||||
|
|||||||
@@ -7,25 +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.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 EditText etSaleId;
|
private FragmentRefundBinding binding;
|
||||||
private Button btnLoadSale, btnProcessRefund, btnBack;
|
private SaleViewModel saleViewModel;
|
||||||
private TextView tvSaleInfo, tvRefundTotal;
|
|
||||||
private LinearLayout llOriginalItems, llRefundItems;
|
|
||||||
private LinearLayout cardOriginalItems, cardRefundItems, cardPayment;
|
|
||||||
private Spinner spinnerPayment;
|
|
||||||
|
|
||||||
private SaleDTO currentSale;
|
private SaleDTO currentSale;
|
||||||
private List<SaleDTO> allSales = new ArrayList<>();
|
private List<SaleDTO> allSales = new ArrayList<>();
|
||||||
|
|
||||||
@@ -60,8 +57,9 @@ public class RefundFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_refund, container, false);
|
binding = FragmentRefundBinding.inflate(inflater, container, false);
|
||||||
initViews(view);
|
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
||||||
|
|
||||||
setupSpinner();
|
setupSpinner();
|
||||||
loadAllSales();
|
loadAllSales();
|
||||||
|
|
||||||
@@ -69,59 +67,47 @@ public class RefundFragment extends Fragment {
|
|||||||
Bundle args = getArguments();
|
Bundle args = getArguments();
|
||||||
if (args != null && args.containsKey("saleId")) {
|
if (args != null && args.containsKey("saleId")) {
|
||||||
long saleId = args.getLong("saleId");
|
long saleId = args.getLong("saleId");
|
||||||
etSaleId.setText(String.valueOf(saleId));
|
binding.etRefundSaleId.setText(String.valueOf(saleId));
|
||||||
// Auto-load after sales are fetched
|
// Auto-load after sales are fetched
|
||||||
}
|
}
|
||||||
|
|
||||||
btnLoadSale.setOnClickListener(v -> loadSale());
|
binding.btnLoadSale.setOnClickListener(v -> loadSale());
|
||||||
btnProcessRefund.setOnClickListener(v -> processRefund());
|
binding.btnProcessRefund.setOnClickListener(v -> processRefund());
|
||||||
btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnRefundBack.setOnClickListener(v -> navigateBack());
|
||||||
|
|
||||||
return view;
|
return binding.getRoot();
|
||||||
}
|
|
||||||
|
|
||||||
private void initViews(View v) {
|
|
||||||
etSaleId = v.findViewById(R.id.etRefundSaleId);
|
|
||||||
btnLoadSale = v.findViewById(R.id.btnLoadSale);
|
|
||||||
btnProcessRefund= v.findViewById(R.id.btnProcessRefund);
|
|
||||||
btnBack = v.findViewById(R.id.btnRefundBack);
|
|
||||||
tvSaleInfo = v.findViewById(R.id.tvSaleInfo);
|
|
||||||
tvRefundTotal = v.findViewById(R.id.tvRefundTotal);
|
|
||||||
llOriginalItems = v.findViewById(R.id.llOriginalItems);
|
|
||||||
llRefundItems = v.findViewById(R.id.llRefundItems);
|
|
||||||
cardOriginalItems = v.findViewById(R.id.cardOriginalItems);
|
|
||||||
cardRefundItems = v.findViewById(R.id.cardRefundItems);
|
|
||||||
cardPayment = v.findViewById(R.id.cardPayment);
|
|
||||||
spinnerPayment = v.findViewById(R.id.spinnerRefundPayment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSpinner() {
|
private void setupSpinner() {
|
||||||
spinnerPayment.setAdapter(new ArrayAdapter<>(requireContext(),
|
binding.spinnerRefundPayment.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||||
android.R.layout.simple_spinner_item, PAYMENT_METHODS));
|
android.R.layout.simple_spinner_item, PAYMENT_METHODS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAllSales() {
|
private void loadAllSales() {
|
||||||
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000)
|
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) {
|
||||||
|
allSales = resource.data.getContent();
|
||||||
// Auto-load if saleId was pre-filled
|
// Auto-load if saleId was pre-filled
|
||||||
Bundle args = getArguments();
|
Bundle args = getArguments();
|
||||||
if (args != null && args.containsKey("saleId")) {
|
if (args != null && args.containsKey("saleId")) {
|
||||||
loadSale();
|
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());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSale() {
|
private void loadSale() {
|
||||||
String idStr = etSaleId.getText().toString().trim();
|
String idStr = binding.etRefundSaleId.getText().toString().trim();
|
||||||
if (idStr.isEmpty()) {
|
if (idStr.isEmpty()) {
|
||||||
Toast.makeText(getContext(), "Enter a Sale ID", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Enter a Sale ID", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
@@ -156,8 +142,8 @@ public class RefundFragment extends Fragment {
|
|||||||
currentSale = found;
|
currentSale = found;
|
||||||
|
|
||||||
// Show sale info
|
// Show sale info
|
||||||
tvSaleInfo.setVisibility(View.VISIBLE);
|
binding.tvSaleInfo.setVisibility(View.VISIBLE);
|
||||||
tvSaleInfo.setText("Sale #" + currentSale.getSaleId()
|
binding.tvSaleInfo.setText("Sale #" + currentSale.getSaleId()
|
||||||
+ " | " + (currentSale.getSaleDate() != null
|
+ " | " + (currentSale.getSaleDate() != null
|
||||||
? currentSale.getSaleDate().substring(0, 10) : "")
|
? currentSale.getSaleDate().substring(0, 10) : "")
|
||||||
+ " | Employee: " + (currentSale.getEmployeeName() != null
|
+ " | Employee: " + (currentSale.getEmployeeName() != null
|
||||||
@@ -169,7 +155,7 @@ public class RefundFragment extends Fragment {
|
|||||||
if (currentSale.getPaymentMethod() != null) {
|
if (currentSale.getPaymentMethod() != null) {
|
||||||
for (int i = 0; i < PAYMENT_METHODS.length; i++) {
|
for (int i = 0; i < PAYMENT_METHODS.length; i++) {
|
||||||
if (PAYMENT_METHODS[i].equalsIgnoreCase(currentSale.getPaymentMethod())) {
|
if (PAYMENT_METHODS[i].equalsIgnoreCase(currentSale.getPaymentMethod())) {
|
||||||
spinnerPayment.setSelection(i); break;
|
binding.spinnerRefundPayment.setSelection(i); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,10 +173,10 @@ public class RefundFragment extends Fragment {
|
|||||||
refundCart.clear();
|
refundCart.clear();
|
||||||
|
|
||||||
// Show cards
|
// Show cards
|
||||||
cardOriginalItems.setVisibility(View.VISIBLE);
|
binding.cardOriginalItems.setVisibility(View.VISIBLE);
|
||||||
cardRefundItems.setVisibility(View.VISIBLE);
|
binding.cardRefundItems.setVisibility(View.VISIBLE);
|
||||||
cardPayment.setVisibility(View.VISIBLE);
|
binding.cardPayment.setVisibility(View.VISIBLE);
|
||||||
btnProcessRefund.setVisibility(View.VISIBLE);
|
binding.btnProcessRefund.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
renderOriginalItems();
|
renderOriginalItems();
|
||||||
renderRefundCart();
|
renderRefundCart();
|
||||||
@@ -233,10 +219,10 @@ public class RefundFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void renderOriginalItems() {
|
private void renderOriginalItems() {
|
||||||
llOriginalItems.removeAllViews();
|
binding.llOriginalItems.removeAllViews();
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
addTableHeader(llOriginalItems);
|
addTableHeader(binding.llOriginalItems);
|
||||||
|
|
||||||
for (RefundItem item : availableItems) {
|
for (RefundItem item : availableItems) {
|
||||||
// Calculate pending in cart
|
// Calculate pending in cart
|
||||||
@@ -254,23 +240,23 @@ public class RefundFragment extends Fragment {
|
|||||||
true, // show add button
|
true, // show add button
|
||||||
() -> showQuantityDialog(item)
|
() -> showQuantityDialog(item)
|
||||||
);
|
);
|
||||||
llOriginalItems.addView(row);
|
binding.llOriginalItems.addView(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderRefundCart() {
|
private void renderRefundCart() {
|
||||||
llRefundItems.removeAllViews();
|
binding.llRefundItems.removeAllViews();
|
||||||
|
|
||||||
if (refundCart.isEmpty()) {
|
if (refundCart.isEmpty()) {
|
||||||
TextView empty = new TextView(getContext());
|
TextView empty = new TextView(getContext());
|
||||||
empty.setText("No items added to refund yet");
|
empty.setText("No items added to refund yet");
|
||||||
empty.setTextColor(0xFF888780);
|
empty.setTextColor(0xFF888780);
|
||||||
empty.setTextSize(13f);
|
empty.setTextSize(13f);
|
||||||
llRefundItems.addView(empty);
|
binding.llRefundItems.addView(empty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addTableHeader(llRefundItems);
|
addTableHeader(binding.llRefundItems);
|
||||||
|
|
||||||
for (RefundItem item : refundCart) {
|
for (RefundItem item : refundCart) {
|
||||||
LinearLayout row = buildItemRow(
|
LinearLayout row = buildItemRow(
|
||||||
@@ -285,11 +271,12 @@ public class RefundFragment extends Fragment {
|
|||||||
updateRefundTotal();
|
updateRefundTotal();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
llRefundItems.addView(row);
|
binding.llRefundItems.addView(row);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -310,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);
|
||||||
@@ -428,7 +416,7 @@ public class RefundFragment extends Fragment {
|
|||||||
private void updateRefundTotal() {
|
private void updateRefundTotal() {
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
for (RefundItem item : refundCart) total = total.add(item.getTotal());
|
for (RefundItem item : refundCart) total = total.add(item.getTotal());
|
||||||
tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP));
|
binding.tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRefund() {
|
private void processRefund() {
|
||||||
@@ -442,7 +430,7 @@ public class RefundFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String payment = PAYMENT_METHODS[spinnerPayment.getSelectedItemPosition()];
|
String payment = PAYMENT_METHODS[binding.spinnerRefundPayment.getSelectedItemPosition()];
|
||||||
|
|
||||||
// Confirm dialog
|
// Confirm dialog
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
@@ -478,33 +466,34 @@ 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 {
|
break;
|
||||||
String err = r.errorBody().string();
|
case ERROR:
|
||||||
Log.e("REFUND", "Error: " + err);
|
Log.e("REFUND", "Error: " + resource.message);
|
||||||
Toast.makeText(getContext(), "Error: " + err,
|
Toast.makeText(getContext(), "Error: " + resource.message,
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
} catch (Exception e) {
|
break;
|
||||||
Log.e("REFUND", "Failed to read error");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
public void onFailure(Call<SaleDTO> c, Throwable t) {
|
|
||||||
Log.e("REFUND", "Failure: " + t.getMessage());
|
|
||||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,28 @@ 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.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.*;
|
import com.example.petstoremobile.databinding.FragmentSaleDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.*;
|
||||||
|
import com.example.petstoremobile.viewmodels.*;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
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 TextView tvMode, tvSaleDetailId, tvTotal;
|
private FragmentSaleDetailBinding binding;
|
||||||
private Spinner spinnerStore, spinnerCustomer, spinnerPayment, spinnerProduct;
|
private SaleViewModel saleViewModel;
|
||||||
private EditText etQuantity;
|
private StoreViewModel storeViewModel;
|
||||||
private Button btnAddItem, btnSave, btnBack, btnRefund;
|
private CustomerViewModel customerViewModel;
|
||||||
private LinearLayout llItems;
|
private ProductViewModel productViewModel;
|
||||||
|
|
||||||
private boolean viewOnly = false;
|
private boolean viewOnly = false;
|
||||||
private long saleId = -1;
|
private long saleId = -1;
|
||||||
@@ -31,13 +36,20 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
private List<ProductDTO> productList = new ArrayList<>();
|
private List<ProductDTO> productList = new ArrayList<>();
|
||||||
private List<SaleDTO.SaleItemDTO> cartItems = new ArrayList<>();
|
private List<SaleDTO.SaleItemDTO> cartItems = new ArrayList<>();
|
||||||
|
|
||||||
private final String[] PAYMENT_METHODS = { "Cash", "Card" };
|
private final String[] PAYMENT_METHODS = { "Cash", "Card"};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_sale_detail, container, false);
|
binding = FragmentSaleDetailBinding.inflate(inflater, container, false);
|
||||||
initViews(view);
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPaymentMethod, PAYMENT_METHODS);
|
||||||
|
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
if (!viewOnly) {
|
if (!viewOnly) {
|
||||||
@@ -45,30 +57,11 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
setupAddItem();
|
setupAddItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnSaleBack.setOnClickListener(v -> navigateBack());
|
||||||
btnSave.setOnClickListener(v -> saveSale());
|
binding.btnSaveSale.setOnClickListener(v -> saveSale());
|
||||||
btnRefund.setOnClickListener(v -> showRefundDialog());
|
binding.btnRefundSale.setOnClickListener(v -> showRefundDialog());
|
||||||
|
|
||||||
return view;
|
return binding.getRoot();
|
||||||
}
|
|
||||||
|
|
||||||
private void initViews(View v) {
|
|
||||||
tvMode = v.findViewById(R.id.tvSaleMode);
|
|
||||||
tvSaleDetailId = v.findViewById(R.id.tvSaleDetailId);
|
|
||||||
tvTotal = v.findViewById(R.id.tvSaleDetailTotal);
|
|
||||||
spinnerStore = v.findViewById(R.id.spinnerSaleStore);
|
|
||||||
spinnerCustomer = v.findViewById(R.id.spinnerSaleCustomer);
|
|
||||||
spinnerPayment = v.findViewById(R.id.spinnerPaymentMethod);
|
|
||||||
spinnerProduct = v.findViewById(R.id.spinnerSaleProduct);
|
|
||||||
etQuantity = v.findViewById(R.id.etSaleQuantity);
|
|
||||||
btnAddItem = v.findViewById(R.id.btnAddItem);
|
|
||||||
btnSave = v.findViewById(R.id.btnSaveSale);
|
|
||||||
btnBack = v.findViewById(R.id.btnSaleBack);
|
|
||||||
btnRefund = v.findViewById(R.id.btnRefundSale);
|
|
||||||
llItems = v.findViewById(R.id.llSaleItems);
|
|
||||||
|
|
||||||
spinnerPayment.setAdapter(new ArrayAdapter<>(requireContext(),
|
|
||||||
android.R.layout.simple_spinner_item, PAYMENT_METHODS));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
@@ -76,31 +69,31 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
if (a != null && a.containsKey("saleId")) {
|
if (a != null && a.containsKey("saleId")) {
|
||||||
saleId = a.getLong("saleId");
|
saleId = a.getLong("saleId");
|
||||||
viewOnly = a.getBoolean("viewOnly", false);
|
viewOnly = a.getBoolean("viewOnly", false);
|
||||||
tvMode.setText("Sale #" + saleId);
|
binding.tvSaleMode.setText("Sale #" + saleId);
|
||||||
tvSaleDetailId.setText("ID: " + saleId);
|
binding.tvSaleDetailId.setText("ID: " + saleId);
|
||||||
|
|
||||||
// Show refund button for existing non-refund sales
|
// Show refund button for existing non-refund sales
|
||||||
if (!a.getBoolean("isRefund", false)) {
|
if (!a.getBoolean("isRefund", false)) {
|
||||||
btnRefund.setVisibility(View.VISIBLE);
|
binding.btnRefundSale.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide save and input controls for view only
|
// Hide save and input controls for view only
|
||||||
if (viewOnly) {
|
if (viewOnly) {
|
||||||
btnSave.setVisibility(View.GONE);
|
binding.btnSaveSale.setVisibility(View.GONE);
|
||||||
spinnerStore.setEnabled(false);
|
binding.spinnerSaleStore.setEnabled(false);
|
||||||
spinnerCustomer.setEnabled(false);
|
binding.spinnerSaleCustomer.setEnabled(false);
|
||||||
spinnerPayment.setEnabled(false);
|
binding.spinnerPaymentMethod.setEnabled(false);
|
||||||
spinnerProduct.setEnabled(false);
|
binding.llAddItemRow.setVisibility(View.GONE);
|
||||||
etQuantity.setEnabled(false);
|
binding.llExtraInfo.setVisibility(View.VISIBLE);
|
||||||
btnAddItem.setEnabled(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load sale details
|
// Load sale details
|
||||||
loadSaleDetails();
|
loadSaleDetails();
|
||||||
} else {
|
} else {
|
||||||
tvMode.setText("New Sale");
|
binding.tvSaleMode.setText("New Sale");
|
||||||
tvSaleDetailId.setVisibility(View.GONE);
|
binding.tvSaleDetailId.setVisibility(View.GONE);
|
||||||
btnRefund.setVisibility(View.GONE);
|
binding.btnRefundSale.setVisibility(View.GONE);
|
||||||
|
binding.llExtraInfo.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,70 +104,77 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadStores() {
|
private void loadStores() {
|
||||||
// Hardcoded since store endpoint is admin only
|
storeViewModel.getAllStores(0, 50).observe(getViewLifecycleOwner(), resource -> {
|
||||||
storeList = new ArrayList<>();
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
storeList.add(new StoreDTO(1L, "Downtown Branch"));
|
storeList = resource.data.getContent();
|
||||||
List<String> names = new ArrayList<>();
|
if (binding != null) {
|
||||||
names.add("-- Select Store --");
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, storeList,
|
||||||
names.add("Downtown Branch");
|
StoreDTO::getStoreName, "-- Select Store --", -1L, StoreDTO::getStoreId);
|
||||||
spinnerStore.setAdapter(new ArrayAdapter<>(requireContext(),
|
}
|
||||||
android.R.layout.simple_spinner_item, names));
|
} else if (storeList.isEmpty()) {
|
||||||
|
storeList = Collections.singletonList(new StoreDTO(1L, "Downtown Branch"));
|
||||||
|
if (binding != null) {
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, storeList,
|
||||||
|
StoreDTO::getStoreName, "-- Select Store --", -1L, StoreDTO::getStoreId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
public void onResponse(Call<PageResponse<CustomerDTO>> c,
|
customerList = resource.data.getContent();
|
||||||
Response<PageResponse<CustomerDTO>> r) {
|
if (binding != null) {
|
||||||
if (r.isSuccessful() && r.body() != null) {
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, customerList,
|
||||||
customerList = r.body().getContent();
|
CustomerDTO::getFullName, "-- No Customer --", -1L, CustomerDTO::getCustomerId);
|
||||||
List<String> names = new ArrayList<>();
|
|
||||||
names.add("-- No Customer --");
|
|
||||||
for (CustomerDTO cu : customerList)
|
|
||||||
names.add(cu.getFirstName() + " " + cu.getLastName());
|
|
||||||
spinnerCustomer.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 == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
public void onResponse(Call<PageResponse<ProductDTO>> c,
|
productList = resource.data.getContent();
|
||||||
Response<PageResponse<ProductDTO>> r) {
|
if (binding != null) {
|
||||||
if (r.isSuccessful() && r.body() != null) {
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, productList,
|
||||||
productList = r.body().getContent();
|
ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId);
|
||||||
List<String> names = new ArrayList<>();
|
|
||||||
names.add("-- Select Product --");
|
|
||||||
for (ProductDTO p : productList)
|
|
||||||
names.add(p.getProdName());
|
|
||||||
spinnerProduct.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 == 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());
|
||||||
tvTotal.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
|
||||||
// Display items
|
// Display items
|
||||||
if (sale.getItems() != null) {
|
if (sale.getItems() != null) {
|
||||||
llItems.removeAllViews();
|
binding.llSaleItems.removeAllViews();
|
||||||
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||||
addItemRow(item.getProductName(),
|
addItemRow(item.getProductName(),
|
||||||
Math.abs(item.getQuantity()),
|
Math.abs(item.getQuantity()),
|
||||||
@@ -183,33 +183,29 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onFailure(Call<SaleDTO> c, Throwable t) {
|
|
||||||
Log.e("SaleDetail", "Load failed: " + t.getMessage());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupAddItem() {
|
private void setupAddItem() {
|
||||||
btnAddItem.setOnClickListener(v -> {
|
binding.btnAddItem.setOnClickListener(v -> {
|
||||||
if (spinnerProduct.getSelectedItemPosition() == 0) {
|
if (binding.spinnerSaleProduct.getSelectedItemPosition() == 0) {
|
||||||
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String qtyStr = etQuantity.getText().toString().trim();
|
String qtyStr = binding.etSaleQuantity.getText().toString().trim();
|
||||||
if (qtyStr.isEmpty()) {
|
if (qtyStr.isEmpty()) {
|
||||||
etQuantity.setError("Enter quantity");
|
binding.etSaleQuantity.setError("Enter quantity");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int qty;
|
int qty;
|
||||||
try {
|
try {
|
||||||
qty = Integer.parseInt(qtyStr);
|
qty = Integer.parseInt(qtyStr);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
etQuantity.setError("Invalid quantity");
|
binding.etSaleQuantity.setError("Invalid quantity");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProductDTO product = productList.get(spinnerProduct.getSelectedItemPosition() - 1);
|
ProductDTO product = productList.get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1);
|
||||||
|
|
||||||
// Check if product already in cart
|
// Check if product already in cart
|
||||||
for (SaleDTO.SaleItemDTO existing : cartItems) {
|
for (SaleDTO.SaleItemDTO existing : cartItems) {
|
||||||
@@ -223,11 +219,12 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
cartItems.add(item);
|
cartItems.add(item);
|
||||||
addItemRow(product.getProdName(), qty, product.getProdPrice());
|
addItemRow(product.getProdName(), qty, product.getProdPrice());
|
||||||
updateTotal();
|
updateTotal();
|
||||||
etQuantity.setText("");
|
binding.etSaleQuantity.setText("");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -250,14 +247,12 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
row.addView(tvName);
|
row.addView(tvName);
|
||||||
row.addView(tvQty);
|
row.addView(tvQty);
|
||||||
row.addView(tvPrice);
|
row.addView(tvPrice);
|
||||||
llItems.addView(row);
|
binding.llSaleItems.addView(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTotal() {
|
private void updateTotal() {
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
int productIdx = 0;
|
|
||||||
for (SaleDTO.SaleItemDTO item : cartItems) {
|
for (SaleDTO.SaleItemDTO item : cartItems) {
|
||||||
if (productIdx < productList.size()) {
|
|
||||||
for (ProductDTO p : productList) {
|
for (ProductDTO p : productList) {
|
||||||
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
|
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
|
||||||
total = total.add(p.getProdPrice()
|
total = total.add(p.getProdPrice()
|
||||||
@@ -266,12 +261,12 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
binding.tvSaleSubtotal.setText("$" + total);
|
||||||
tvTotal.setText("Total: $" + total);
|
binding.tvSaleDetailTotal.setText("Total: $" + total);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveSale() {
|
private void saveSale() {
|
||||||
if (spinnerStore.getSelectedItemPosition() == 0) {
|
if (binding.spinnerSaleStore.getSelectedItemPosition() == 0) {
|
||||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -280,13 +275,13 @@ 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[spinnerPayment.getSelectedItemPosition()];
|
String payment = PAYMENT_METHODS[binding.spinnerPaymentMethod.getSelectedItemPosition()];
|
||||||
|
|
||||||
// Optional customer
|
// Optional customer
|
||||||
Long customerId = null;
|
Long customerId = null;
|
||||||
if (spinnerCustomer.getSelectedItemPosition() > 0) {
|
if (binding.spinnerSaleCustomer.getSelectedItemPosition() > 0) {
|
||||||
customerId = customerList.get(spinnerCustomer.getSelectedItemPosition() - 1)
|
customerId = customerList.get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1)
|
||||||
.getCustomerId();
|
.getCustomerId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,71 +293,38 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
null,
|
null,
|
||||||
customerId);
|
customerId);
|
||||||
|
|
||||||
Log.d("SALE_SAVE", "storeId=" + store.getStoreId()
|
saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
+ " payment=" + payment
|
if (resource != null) {
|
||||||
+ " items=" + cartItems.size()
|
switch (resource.status) {
|
||||||
+ " customerId=" + customerId);
|
case SUCCESS:
|
||||||
|
|
||||||
RetrofitClient.getSaleApi(requireContext()).createSale(dto)
|
|
||||||
.enqueue(new Callback<SaleDTO>() {
|
|
||||||
public void onResponse(Call<SaleDTO> c, Response<SaleDTO> r) {
|
|
||||||
if (r.isSuccessful()) {
|
|
||||||
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);
|
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
|
||||||
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() {
|
||||||
|
DialogUtils.showConfirmDialog(requireContext(), "Process Refund",
|
||||||
|
"Are you sure you want to process a refund for this sale?", () -> {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putLong("saleId", saleId);
|
args.putLong("saleId", saleId);
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_refund, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_refund, args);
|
||||||
}
|
|
||||||
|
|
||||||
private void submitRefund() {
|
|
||||||
RefundDTO refundDTO = new RefundDTO(saleId, "Refund requested from mobile app");
|
|
||||||
RetrofitClient.getRefundApi(requireContext()).createRefund(refundDTO)
|
|
||||||
.enqueue(new Callback<RefundDTO>() {
|
|
||||||
public void onResponse(Call<RefundDTO> c, Response<RefundDTO> r) {
|
|
||||||
if (r.isSuccessful()) {
|
|
||||||
Toast.makeText(getContext(), "Refund request submitted!",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
btnRefund.setVisibility(View.GONE);
|
|
||||||
} 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onFailure(Call<RefundDTO> c, Throwable t) {
|
|
||||||
Log.e("REFUND", "Failure: " + t.getMessage());
|
|
||||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +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.databinding.FragmentStaffDetailBinding;
|
||||||
import com.example.petstoremobile.api.RetrofitClient;
|
|
||||||
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 TextView tvMode, tvStaffId;
|
private FragmentStaffDetailBinding binding;
|
||||||
private EditText etUsername, etPassword, etFirstName, etLastName, etEmail, etPhone;
|
private EmployeeViewModel employeeViewModel;
|
||||||
private Spinner spinnerRole, spinnerStatus;
|
|
||||||
private Button btnSave, btnDelete, btnBack;
|
|
||||||
|
|
||||||
private long employeeId = -1;
|
private long employeeId = -1;
|
||||||
private boolean isEditing = false;
|
private boolean isEditing = false;
|
||||||
|
|
||||||
@@ -30,37 +29,22 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_staff_detail, container, false);
|
binding = FragmentStaffDetailBinding.inflate(inflater, container, false);
|
||||||
initViews(view);
|
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
|
||||||
|
|
||||||
setupSpinners();
|
setupSpinners();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnStaffBack.setOnClickListener(v -> navigateBack());
|
||||||
btnSave.setOnClickListener(v -> save());
|
binding.btnSaveStaff.setOnClickListener(v -> save());
|
||||||
btnDelete.setOnClickListener(v -> confirmDelete());
|
binding.btnDeleteStaff.setOnClickListener(v -> confirmDelete());
|
||||||
return view;
|
return binding.getRoot();
|
||||||
}
|
|
||||||
|
|
||||||
private void initViews(View v) {
|
|
||||||
tvMode = v.findViewById(R.id.tvStaffMode);
|
|
||||||
tvStaffId = v.findViewById(R.id.tvStaffId);
|
|
||||||
etUsername = v.findViewById(R.id.etStaffUsername);
|
|
||||||
etPassword = v.findViewById(R.id.etStaffPassword);
|
|
||||||
etFirstName = v.findViewById(R.id.etStaffFirstName);
|
|
||||||
etLastName = v.findViewById(R.id.etStaffLastName);
|
|
||||||
etEmail = v.findViewById(R.id.etStaffEmail);
|
|
||||||
etPhone = v.findViewById(R.id.etStaffPhone);
|
|
||||||
spinnerRole = v.findViewById(R.id.spinnerStaffRole);
|
|
||||||
spinnerStatus = v.findViewById(R.id.spinnerStaffStatus);
|
|
||||||
btnSave = v.findViewById(R.id.btnSaveStaff);
|
|
||||||
btnDelete = v.findViewById(R.id.btnDeleteStaff);
|
|
||||||
btnBack = v.findViewById(R.id.btnStaffBack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
spinnerRole.setAdapter(new ArrayAdapter<>(requireContext(),
|
binding.spinnerStaffRole.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||||
android.R.layout.simple_spinner_item, ROLES));
|
android.R.layout.simple_spinner_item, ROLES));
|
||||||
spinnerStatus.setAdapter(new ArrayAdapter<>(requireContext(),
|
binding.spinnerStaffStatus.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||||
android.R.layout.simple_spinner_item, STATUSES));
|
android.R.layout.simple_spinner_item, STATUSES));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,61 +54,61 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
isEditing = true;
|
isEditing = true;
|
||||||
employeeId = a.getLong("employeeId", -1);
|
employeeId = a.getLong("employeeId", -1);
|
||||||
|
|
||||||
tvMode.setText("Edit Staff Account");
|
binding.tvStaffMode.setText("Edit Staff Account");
|
||||||
tvStaffId.setText("ID: " + employeeId);
|
binding.tvStaffId.setText("ID: " + employeeId);
|
||||||
tvStaffId.setVisibility(View.VISIBLE);
|
binding.tvStaffId.setVisibility(View.VISIBLE);
|
||||||
etUsername.setText(a.getString("username", ""));
|
binding.etStaffUsername.setText(a.getString("username", ""));
|
||||||
etFirstName.setText(a.getString("firstName", ""));
|
binding.etStaffFirstName.setText(a.getString("firstName", ""));
|
||||||
etLastName.setText(a.getString("lastName", ""));
|
binding.etStaffLastName.setText(a.getString("lastName", ""));
|
||||||
etEmail.setText(a.getString("email", "")); // ← was showing fullName
|
binding.etStaffEmail.setText(a.getString("email", ""));
|
||||||
etPhone.setText(a.getString("phone", ""));
|
binding.etStaffPhone.setText(a.getString("phone", ""));
|
||||||
btnDelete.setVisibility(View.VISIBLE);
|
binding.btnDeleteStaff.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// Pre-fill role
|
// Pre-fill role
|
||||||
String role = a.getString("role", "STAFF");
|
String role = a.getString("role", "STAFF");
|
||||||
for (int i = 0; i < ROLES.length; i++) {
|
for (int i = 0; i < ROLES.length; i++) {
|
||||||
if (ROLES[i].equals(role)) {
|
if (ROLES[i].equals(role)) {
|
||||||
spinnerRole.setSelection(i);
|
binding.spinnerStaffRole.setSelection(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pre-fill status
|
// Pre-fill status
|
||||||
boolean active = a.getBoolean("active", true);
|
boolean active = a.getBoolean("active", true);
|
||||||
spinnerStatus.setSelection(active ? 0 : 1);
|
binding.spinnerStaffStatus.setSelection(active ? 0 : 1);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
isEditing = false;
|
isEditing = false;
|
||||||
employeeId = -1;
|
employeeId = -1;
|
||||||
tvMode.setText("Add Staff Account");
|
binding.tvStaffMode.setText("Add Staff Account");
|
||||||
btnDelete.setVisibility(View.GONE);
|
binding.btnDeleteStaff.setVisibility(View.GONE);
|
||||||
tvStaffId.setVisibility(View.GONE);
|
binding.tvStaffId.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
String username = etUsername.getText() != null ? etUsername.getText().toString().trim() : "";
|
String username = binding.etStaffUsername.getText() != null ? binding.etStaffUsername.getText().toString().trim() : "";
|
||||||
String password = etPassword.getText() != null ? etPassword.getText().toString().trim() : "";
|
String password = binding.etStaffPassword.getText() != null ? binding.etStaffPassword.getText().toString().trim() : "";
|
||||||
String firstName = etFirstName.getText() != null ? etFirstName.getText().toString().trim() : "";
|
String firstName = binding.etStaffFirstName.getText() != null ? binding.etStaffFirstName.getText().toString().trim() : "";
|
||||||
String lastName = etLastName.getText() != null ? etLastName.getText().toString().trim() : "";
|
String lastName = binding.etStaffLastName.getText() != null ? binding.etStaffLastName.getText().toString().trim() : "";
|
||||||
String email = etEmail.getText() != null ? etEmail.getText().toString().trim() : "";
|
String email = binding.etStaffEmail.getText() != null ? binding.etStaffEmail.getText().toString().trim() : "";
|
||||||
String phone = etPhone.getText() != null ? etPhone.getText().toString().trim() : "";
|
String phone = binding.etStaffPhone.getText() != null ? binding.etStaffPhone.getText().toString().trim() : "";
|
||||||
String role = ROLES[spinnerRole.getSelectedItemPosition()];
|
String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()];
|
||||||
boolean active = spinnerStatus.getSelectedItemPosition() == 0;
|
boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0;
|
||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
if (username.isEmpty()) { etUsername.setError("Required"); return; }
|
if (username.isEmpty()) { binding.etStaffUsername.setError("Required"); return; }
|
||||||
if (!isEditing && password.isEmpty()) {
|
if (!isEditing && password.isEmpty()) {
|
||||||
etPassword.setError("Required for new account"); return;
|
binding.etStaffPassword.setError("Required for new account"); return;
|
||||||
}
|
}
|
||||||
if (!isEditing && password.length() < 6) {
|
if (!isEditing && password.length() < 6) {
|
||||||
etPassword.setError("At least 6 characters"); return;
|
binding.etStaffPassword.setError("At least 6 characters"); return;
|
||||||
}
|
}
|
||||||
if (firstName.isEmpty()) { etFirstName.setError("Required"); return; }
|
if (firstName.isEmpty()) { binding.etStaffFirstName.setError("Required"); return; }
|
||||||
if (lastName.isEmpty()) { etLastName.setError("Required"); return; }
|
if (lastName.isEmpty()) { binding.etStaffLastName.setError("Required"); return; }
|
||||||
if (email.isEmpty()) { etEmail.setError("Required"); return; }
|
if (email.isEmpty()) { binding.etStaffEmail.setError("Required"); return; }
|
||||||
if (phone.isEmpty()) { etPhone.setError("Required"); return; }
|
if (phone.isEmpty()) { binding.etStaffPhone.setError("Required"); return; }
|
||||||
|
|
||||||
EmployeeDTO dto = new EmployeeDTO(
|
EmployeeDTO dto = new EmployeeDTO(
|
||||||
username,
|
username,
|
||||||
@@ -137,41 +121,35 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
active
|
active
|
||||||
);
|
);
|
||||||
|
|
||||||
Log.d("STAFF_SAVE", "isEditing=" + isEditing
|
|
||||||
+ " employeeId=" + employeeId
|
|
||||||
+ " username=" + username);
|
|
||||||
|
|
||||||
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();
|
||||||
|
|
||||||
private Callback<EmployeeDTO> simpleCallback(String msg) {
|
|
||||||
return new Callback<>() {
|
|
||||||
public void onResponse(Call<EmployeeDTO> c, Response<EmployeeDTO> r) {
|
|
||||||
Log.d("STAFF_SAVE", "Response: " + r.code());
|
|
||||||
if (r.isSuccessful()) {
|
|
||||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
|
||||||
navigateBack();
|
navigateBack();
|
||||||
|
break;
|
||||||
|
case ERROR:
|
||||||
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
employeeViewModel.createEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
String err = r.errorBody().string();
|
if (resource != null) {
|
||||||
Log.e("STAFF_SAVE", "Error: " + err);
|
switch (resource.status) {
|
||||||
Toast.makeText(getContext(), "Error " + r.code() + ": " + err,
|
case SUCCESS:
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.makeText(getContext(), "Staff account created", Toast.LENGTH_SHORT).show();
|
||||||
} catch (Exception e) {
|
navigateBack();
|
||||||
Log.e("STAFF_SAVE", "Failed to read error");
|
break;
|
||||||
|
case ERROR:
|
||||||
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
public void onFailure(Call<EmployeeDTO> c, Throwable t) {
|
|
||||||
Log.e("STAFF_SAVE", "Failure: " + t.getMessage());
|
|
||||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
@@ -179,15 +157,17 @@ 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();
|
||||||
@@ -196,4 +176,10 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,12 +131,31 @@ public class PetProfileFragment extends Fragment {
|
|||||||
binding.tvPetPrice.setText("$0.00");
|
binding.tvPetPrice.setText("$0.00");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display owner name if available, otherwise show No Owner
|
String status = pet.getPetStatus();
|
||||||
|
|
||||||
|
// Display owner name only if the pet is Adopted or Owned
|
||||||
|
if ("Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status)) {
|
||||||
|
binding.layoutPetOwner.setVisibility(View.VISIBLE);
|
||||||
if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) {
|
if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) {
|
||||||
binding.tvPetOwner.setText(pet.getCustomerName());
|
binding.tvPetOwner.setText(pet.getCustomerName());
|
||||||
} else {
|
} else {
|
||||||
binding.tvPetOwner.setText("No Owner");
|
binding.tvPetOwner.setText("No Owner");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
binding.layoutPetOwner.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display store name only if the pet is Adopted or Available
|
||||||
|
if ("Available".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) {
|
||||||
|
binding.layoutPetStore.setVisibility(View.VISIBLE);
|
||||||
|
if (pet.getStoreName() != null && !pet.getStoreName().isEmpty()) {
|
||||||
|
binding.tvPetStore.setText(pet.getStoreName());
|
||||||
|
} else {
|
||||||
|
binding.tvPetStore.setText("No Store");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.layoutPetStore.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Failed to load pet data: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Failed to load pet data: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ public class SaleRepository extends BaseRepository {
|
|||||||
this.saleApi = saleApi;
|
this.saleApi = saleApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size) {
|
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, String sortBy) {
|
||||||
return executeCall(saleApi.getAllSales(page, size));
|
return executeCall(saleApi.getAllSales(page, size, query, paymentMethod, storeId, sortBy));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
|
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
|
||||||
|
|||||||
@@ -1,18 +1,76 @@
|
|||||||
package com.example.petstoremobile.utils;
|
package com.example.petstoremobile.utils;
|
||||||
|
|
||||||
import android.telephony.PhoneNumberFormattingTextWatcher;
|
import android.telephony.PhoneNumberFormattingTextWatcher;
|
||||||
|
import android.text.Editable;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import com.example.petstoremobile.fragments.ListFragment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for shared UI component logic and formatting.
|
* Utility class for shared UI component logic and formatting.
|
||||||
*/
|
*/
|
||||||
public class UIUtils {
|
public class UIUtils {
|
||||||
/**
|
/**
|
||||||
* Formats an EditText for to phone format
|
* Formats an EditText to phone format.
|
||||||
*/
|
*/
|
||||||
public static void formatPhoneInput(EditText editText) {
|
public static void formatPhoneInput(EditText editText) {
|
||||||
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher("CA"));
|
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher("CA"));
|
||||||
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(14)});
|
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(14)});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up a toggle for a filter layout, including icon changes and field resets.
|
||||||
|
*/
|
||||||
|
public static void setupFilterToggle(ImageButton btnToggle, View layoutFilter, EditText etSearch, Spinner... spinners) {
|
||||||
|
btnToggle.setOnClickListener(v -> {
|
||||||
|
boolean isVisible = layoutFilter.getVisibility() == View.VISIBLE;
|
||||||
|
layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
// Use Android default icons or app-specific ones if available
|
||||||
|
btnToggle.setImageResource(isVisible ?
|
||||||
|
android.R.drawable.ic_menu_search :
|
||||||
|
android.R.drawable.ic_menu_close_clear_cancel);
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
|
if (etSearch != null) etSearch.setText("");
|
||||||
|
for (Spinner spinner : spinners) {
|
||||||
|
if (spinner != null) spinner.setSelection(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simplifies setting up the hamburger menu to open the navigation drawer.
|
||||||
|
*/
|
||||||
|
public static void setupHamburgerMenu(ImageButton btnHamburger, Fragment fragment) {
|
||||||
|
btnHamburger.setOnClickListener(v -> {
|
||||||
|
Fragment parent = fragment.getParentFragment();
|
||||||
|
if (parent != null) {
|
||||||
|
Fragment grandParent = parent.getParentFragment();
|
||||||
|
if (grandParent instanceof ListFragment) {
|
||||||
|
((ListFragment) grandParent).openDrawer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches a simplified TextWatcher to an EditText for search functionality.
|
||||||
|
*/
|
||||||
|
public static void attachSearch(EditText etSearch, Runnable onQueryChanged) {
|
||||||
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
|
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
onQueryChanged.run();
|
||||||
|
}
|
||||||
|
@Override public void afterTextChanged(Editable s) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ public class SaleViewModel extends ViewModel {
|
|||||||
this.saleRepository = saleRepository;
|
this.saleRepository = saleRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size) {
|
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, String sortBy) {
|
||||||
return saleRepository.getAllSales(page, size);
|
return saleRepository.getAllSales(page, size, query, paymentMethod, storeId, sortBy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
|
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
|
||||||
|
|||||||
@@ -222,13 +222,15 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutPetOwner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:background="@color/white"
|
android:background="@color/white"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:padding="16dp">
|
android:padding="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -250,6 +252,37 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutPetStore"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="STORE"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPetStore"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="No Store"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -29,35 +29,112 @@
|
|||||||
android:contentDescription="Open menu"/>
|
android:contentDescription="Open menu"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:text="Sales"
|
android:text="Sales"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnToggleFilter"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:contentDescription="Toggle filter"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<EditText
|
<LinearLayout
|
||||||
android:id="@+id/etSearchSale"
|
android:id="@+id/layoutFilter"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:background="@color/primary_dark"
|
||||||
|
android:elevation="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:background="@drawable/bg_search_bar"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:alpha="0.6"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etSearchSale"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
android:hint="Search by employee or store..."
|
android:hint="Search by employee or store..."
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:drawableStart="@android:drawable/ic_menu_search"
|
android:background="@android:color/transparent"
|
||||||
android:drawablePadding="8dp"
|
android:textColor="@color/text_dark"
|
||||||
android:background="@android:color/white"
|
android:textColorHint="#99000000"
|
||||||
android:padding="12dp"
|
android:textSize="14sp"
|
||||||
android:textColor="@color/text_dark"/>
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStore"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_spinner"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginEnd="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerPaymentMethod"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_spinner"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:layout_marginStart="4dp"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnOpenRefund"
|
android:id="@+id/btnOpenRefund"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="44dp"
|
||||||
android:text="Refund"
|
android:layout_weight="1"
|
||||||
|
android:text="Process Refund"
|
||||||
android:backgroundTint="@color/accent_coral"
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:textColor="@color/white"/>
|
android:textColor="@color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:insetTop="0dp"
|
||||||
|
android:insetBottom="0dp"
|
||||||
|
android:layout_marginStart="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swipeRefreshSale"
|
android:id="@+id/swipeRefreshSale"
|
||||||
@@ -68,7 +145,8 @@
|
|||||||
android:id="@+id/recyclerViewSales"
|
android:id="@+id/recyclerViewSales"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="8dp"/>
|
android:padding="8dp"
|
||||||
|
android:clipToPadding="false"/>
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
@@ -85,5 +163,4 @@
|
|||||||
app:srcCompat="@android:drawable/ic_input_add"
|
app:srcCompat="@android:drawable/ic_input_add"
|
||||||
app:tint="@color/white"/>
|
app:tint="@color/white"/>
|
||||||
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@@ -46,7 +46,6 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="24dp">
|
android:padding="24dp">
|
||||||
|
|
||||||
<!-- Sale Info Card -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -65,7 +64,6 @@
|
|||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
<!-- Store -->
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -80,7 +78,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<!-- Customer (optional) -->
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -110,6 +107,54 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llExtraInfo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Channel"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleChannel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="—"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Points Earned"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSalePoints"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Items Card -->
|
<!-- Items Card -->
|
||||||
@@ -130,8 +175,8 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:layout_marginBottom="12dp"/>
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
<!-- Item row -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/llAddItemRow"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
@@ -164,14 +209,80 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Items list container -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/llSaleItems"
|
android:id="@+id/llSaleItems"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"/>
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
<!-- Total -->
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#EEEEEE"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Subtotal:"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleSubtotal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="$0.00"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llCouponDiscount"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Coupon Discount:"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleCouponDiscount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="-$0.00"
|
||||||
|
android:textColor="@color/status_adopted"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llEmployeeDiscount"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Employee Discount:"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleEmployeeDiscount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="-$0.00"
|
||||||
|
android:textColor="@color/status_adopted"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSaleDetailTotal"
|
android:id="@+id/tvSaleDetailTotal"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -182,6 +293,7 @@
|
|||||||
android:textColor="@color/accent_coral"
|
android:textColor="@color/accent_coral"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginTop="12dp"/>
|
android:layout_marginTop="12dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/headerStaff"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="56dp"
|
||||||
android:background="@color/primary_dark"
|
android:background="@color/primary_dark"
|
||||||
@@ -34,22 +35,63 @@
|
|||||||
android:text="Staff Accounts"
|
android:text="Staff Accounts"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnToggleFilterStaff"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:contentDescription="Toggle filter"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutFilterStaff"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:background="@color/primary_dark"
|
||||||
|
android:elevation="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:background="@drawable/bg_search_bar"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:alpha="0.6"/>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etSearchStaff"
|
android:id="@+id/etSearchStaff"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_margin="8dp"
|
android:hint="Search staff..."
|
||||||
android:hint="Search by name, username or email..."
|
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:drawableStart="@android:drawable/ic_menu_search"
|
android:background="@android:color/transparent"
|
||||||
android:drawablePadding="8dp"
|
android:textColor="@color/text_dark"
|
||||||
android:background="@android:color/white"
|
android:textColorHint="#99000000"
|
||||||
android:padding="12dp"
|
android:textSize="14sp"
|
||||||
android:textColor="@color/text_dark"/>
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swipeRefreshStaff"
|
android:id="@+id/swipeRefreshStaff"
|
||||||
@@ -78,5 +120,3 @@
|
|||||||
app:tint="@color/white"/>
|
app:tint="@color/white"/>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,33 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.cardview.widget.CardView
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="8dp"
|
|
||||||
app:cardCornerRadius="8dp"
|
|
||||||
app:cardElevation="2dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp">
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:background="@color/white">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/ivEmployeeProfile"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/placeholder"
|
||||||
|
app:shapeAppearanceOverlay="@style/CircleImageView"
|
||||||
|
app:strokeWidth="2dp"
|
||||||
|
app:strokeColor="#BDBDBD"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:contentDescription="Employee Profile Image" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -25,75 +35,83 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvEmployeeFullName"
|
android:id="@+id/tvEmployeeFullName"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="16sp"
|
android:layout_weight="1"
|
||||||
android:textStyle="bold"
|
android:ellipsize="end"
|
||||||
android:textColor="@color/text_dark"/>
|
android:maxLines="1"
|
||||||
|
android:text="Full Name"
|
||||||
<TextView
|
android:textColor="@color/text_dark"
|
||||||
android:id="@+id/tvEmployeeUsername"
|
android:textSize="18sp"
|
||||||
android:layout_width="wrap_content"
|
android:textStyle="bold" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="13sp"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:layout_marginTop="2dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvEmployeeEmail"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:layout_marginTop="2dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvEmployeePhone"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:layout_marginTop="2dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="end"
|
|
||||||
android:layout_gravity="center_vertical">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvEmployeeRole"
|
android:id="@+id/tvEmployeeRole"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="10dp"
|
android:paddingStart="8dp"
|
||||||
android:paddingEnd="10dp"
|
android:paddingTop="3dp"
|
||||||
android:paddingTop="4dp"
|
android:paddingEnd="8dp"
|
||||||
android:paddingBottom="4dp"
|
android:paddingBottom="3dp"
|
||||||
android:textSize="11sp"
|
android:text="ROLE"
|
||||||
|
android:textAllCaps="true"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:textSize="11sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEmployeeUsername"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:text="@username"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginTop="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEmployeeEmail"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="email@example.com"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvEmployeeStatus"
|
android:id="@+id/tvEmployeeStatus"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingStart="10dp"
|
android:text="Active"
|
||||||
android:paddingEnd="10dp"
|
android:textColor="@color/accent_coral"
|
||||||
android:paddingTop="4dp"
|
android:textSize="13sp"
|
||||||
android:paddingBottom="4dp"
|
android:textStyle="bold" />
|
||||||
android:textSize="11sp"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#F0F0F0"
|
||||||
|
android:layout_marginTop="12dp"/>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -1,23 +1,64 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.cardview.widget.CardView
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:orientation="horizontal"
|
||||||
app:cardCornerRadius="8dp"
|
android:background="@color/white"
|
||||||
app:cardElevation="2dp">
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbSelectSale"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical">
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleId"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="Sale #—"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleTotal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="$0.00"
|
||||||
|
android:textColor="#4CAF50"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="4dp">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@@ -25,74 +66,58 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvSaleId"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSaleEmployee"
|
android:id="@+id/tvSaleEmployee"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="13sp"
|
android:text="By: —"
|
||||||
android:textColor="@color/text_light"
|
android:textColor="#888888"
|
||||||
android:layout_marginTop="2dp"/>
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSaleDate"
|
android:id="@+id/tvSaleDate"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="12sp"
|
android:layout_marginTop="2dp"
|
||||||
android:textColor="@color/text_light"
|
android:text="—"
|
||||||
android:layout_marginTop="2dp"/>
|
android:textColor="#888888"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSalePayment"
|
android:id="@+id/tvSalePayment"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="12sp"
|
android:layout_marginTop="2dp"
|
||||||
android:textColor="@color/text_light"
|
android:text="—"
|
||||||
android:layout_marginTop="2dp"/>
|
android:textColor="#888888"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="end"
|
|
||||||
android:layout_gravity="center_vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvSaleTotal"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/accent_coral"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSaleRefundBadge"
|
android:id="@+id/tvSaleRefundBadge"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/status_adopted"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingTop="3dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingBottom="3dp"
|
||||||
android:text="REFUND"
|
android:text="REFUND"
|
||||||
android:textSize="10sp"
|
android:textAllCaps="true"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:background="@color/accent_coral"
|
android:textSize="11sp"
|
||||||
android:paddingStart="6dp"
|
android:visibility="gone"
|
||||||
android:paddingEnd="6dp"
|
android:layout_gravity="bottom"/>
|
||||||
android:paddingTop="2dp"
|
|
||||||
android:paddingBottom="2dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#F0F0F0"
|
||||||
|
android:layout_marginTop="12dp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.cardview.widget.CardView>
|
</LinearLayout>
|
||||||
101
android/app/src/res/layout/item_sale.xml
Normal file
101
android/app/src/res/layout/item_sale.xml
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleId"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Sale #123"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleTotal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="$0.00"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent_coral"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleEmployee"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="By: Employee"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#888888"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="2023-01-01"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:layout_marginTop="2dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSalePayment"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Cash"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:layout_marginTop="2dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleRefundBadge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="REFUND"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:background="@color/accent_coral"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="2dp"
|
||||||
|
android:paddingBottom="2dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#F0F0F0"
|
||||||
|
android:layout_marginTop="12dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -25,8 +25,10 @@ public class SaleController {
|
|||||||
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<Page<SaleResponse>> getAllSales(
|
public ResponseEntity<Page<SaleResponse>> getAllSales(
|
||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
|
@RequestParam(required = false) String paymentMethod,
|
||||||
|
@RequestParam(required = false) Long storeId,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
return ResponseEntity.ok(saleService.getAllSales(q, pageable));
|
return ResponseEntity.ok(saleService.getAllSales(q, paymentMethod, storeId, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -14,10 +14,14 @@ import java.util.List;
|
|||||||
public interface SaleRepository extends JpaRepository<Sale, Long> {
|
public interface SaleRepository extends JpaRepository<Sale, Long> {
|
||||||
|
|
||||||
@Query("SELECT s FROM Sale s WHERE " +
|
@Query("SELECT s FROM Sale s WHERE " +
|
||||||
|
"(:q IS NULL OR (" +
|
||||||
"LOWER(s.employee.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(s.employee.firstName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(s.employee.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"LOWER(s.employee.lastName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||||
"LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))")
|
"LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))" +
|
||||||
Page<Sale> searchSales(@Param("q") String query, Pageable pageable);
|
")) AND " +
|
||||||
|
"(:paymentMethod IS NULL OR LOWER(s.paymentMethod) = LOWER(:paymentMethod)) AND " +
|
||||||
|
"(:storeId IS NULL OR s.store.storeId = :storeId)")
|
||||||
|
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, Pageable pageable);
|
||||||
|
|
||||||
List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
|
List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,13 +39,8 @@ public class SaleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Page<SaleResponse> getAllSales(String query, Pageable pageable) {
|
public Page<SaleResponse> getAllSales(String query, String paymentMethod, Long storeId, Pageable pageable) {
|
||||||
Page<Sale> sales;
|
Page<Sale> sales = saleRepository.searchSales(normalizeFilter(query), normalizeFilter(paymentMethod), storeId, pageable);
|
||||||
if (query != null && !query.trim().isEmpty()) {
|
|
||||||
sales = saleRepository.searchSales(query, pageable);
|
|
||||||
} else {
|
|
||||||
sales = saleRepository.findAll(pageable);
|
|
||||||
}
|
|
||||||
return sales.map(this::mapToResponse);
|
return sales.map(this::mapToResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,6 +231,14 @@ public class SaleService {
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeFilter(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String trimmed = value.trim();
|
||||||
|
return trimmed.isEmpty() ? null : trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
String normalizePaymentMethod(String paymentMethod) {
|
String normalizePaymentMethod(String paymentMethod) {
|
||||||
if (paymentMethod == null) {
|
if (paymentMethod == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user