Close chat #169
@@ -90,16 +90,24 @@ public class HomeActivity extends AppCompatActivity {
|
||||
loadFragment(chatFragment);
|
||||
bottomNav.setSelectedItemId(R.id.nav_chat);
|
||||
} else {
|
||||
loadFragment(new ListFragment());
|
||||
bottomNav.setSelectedItemId(R.id.nav_list);
|
||||
new android.os.Handler().postDelayed(() -> {
|
||||
loadFragment(new ListFragment());
|
||||
bottomNav.setSelectedItemId(R.id.nav_list);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to start the notification service in the background
|
||||
// to receive notifications when a new conversation is created
|
||||
private void startNotificationService() {
|
||||
Intent serviceIntent = new Intent(this, ChatNotificationService.class);
|
||||
startService(serviceIntent);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Intent serviceIntent = new Intent(this, ChatNotificationService.class);
|
||||
startService(serviceIntent);
|
||||
} catch (Exception e) {
|
||||
Log.e("HomeActivity", "Failed to start notification service: " + e.getMessage());
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
//Helper function to request for notification permission
|
||||
|
||||
@@ -1,78 +1,70 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.*;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.models.Sale;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import java.util.List;
|
||||
|
||||
public class SaleAdapter extends RecyclerView.Adapter<SaleAdapter.SaleViewHolder> {
|
||||
|
||||
private List<Sale> saleList;
|
||||
private OnSaleClickListener saleClickListener;
|
||||
private List<SaleDTO> saleList;
|
||||
private OnSaleClickListener listener;
|
||||
|
||||
public interface OnSaleClickListener {
|
||||
void onSaleClick(int position);
|
||||
}
|
||||
|
||||
public SaleAdapter(List<Sale> saleList, OnSaleClickListener saleClickListener) {
|
||||
public SaleAdapter(List<SaleDTO> saleList, OnSaleClickListener listener) {
|
||||
this.saleList = saleList;
|
||||
this.saleClickListener = saleClickListener;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public static class SaleViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvSaleId, tvItemName, tvEmployeeName, tvSaleDate, tvTotal, tvPaymentMethod, tvRefundBadge;
|
||||
TextView tvId, tvEmployee, tvDate, tvPayment, tvTotal, tvRefundBadge;
|
||||
|
||||
public SaleViewHolder(@NonNull View v) {
|
||||
super(v);
|
||||
tvSaleId = v.findViewById(R.id.tvSaleId);
|
||||
tvItemName = v.findViewById(R.id.tvSaleItemName);
|
||||
tvEmployeeName = v.findViewById(R.id.tvSaleEmployee);
|
||||
tvSaleDate = v.findViewById(R.id.tvSaleDate);
|
||||
tvId = v.findViewById(R.id.tvSaleId);
|
||||
tvEmployee = v.findViewById(R.id.tvSaleEmployee);
|
||||
tvDate = v.findViewById(R.id.tvSaleDate);
|
||||
tvPayment = v.findViewById(R.id.tvSalePayment);
|
||||
tvTotal = v.findViewById(R.id.tvSaleTotal);
|
||||
tvPaymentMethod = v.findViewById(R.id.tvSalePayment);
|
||||
tvRefundBadge = v.findViewById(R.id.tvRefundBadge);
|
||||
tvRefundBadge = v.findViewById(R.id.tvSaleRefundBadge);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public SaleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_sale, parent, false);
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_sale, parent, false);
|
||||
return new SaleViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SaleViewHolder holder, int position) {
|
||||
Sale sale = saleList.get(position);
|
||||
SaleDTO s = saleList.get(position);
|
||||
holder.tvId.setText("Sale #" + (s.getSaleId() != null ? s.getSaleId() : ""));
|
||||
holder.tvEmployee.setText("By: " + (s.getEmployeeName() != null ? s.getEmployeeName() : ""));
|
||||
holder.tvDate.setText(s.getSaleDate() != null ? s.getSaleDate().substring(0, 10) : "");
|
||||
holder.tvPayment.setText(s.getPaymentMethod() != null ? s.getPaymentMethod() : "");
|
||||
holder.tvTotal.setText(s.getTotalAmount() != null ? "$" + s.getTotalAmount() : "");
|
||||
|
||||
holder.tvSaleId.setText("ID: " + sale.getSaleId());
|
||||
holder.tvItemName.setText(sale.getItemName());
|
||||
holder.tvEmployeeName.setText("By: " + sale.getEmployeeName());
|
||||
holder.tvSaleDate.setText(sale.getSaleDate());
|
||||
holder.tvTotal.setText("$" + String.format("%.2f", sale.getTotal()));
|
||||
holder.tvPaymentMethod.setText(sale.getPaymentMethod());
|
||||
|
||||
// Show refund badge if it's a refund
|
||||
if (sale.isRefund()) {
|
||||
// Show refund badge
|
||||
if (Boolean.TRUE.equals(s.getIsRefund())) {
|
||||
holder.tvRefundBadge.setVisibility(View.VISIBLE);
|
||||
holder.tvRefundBadge.setBackgroundColor(Color.parseColor("#F44336"));
|
||||
holder.tvTotal.setTextColor(Color.parseColor("#F44336"));
|
||||
} else {
|
||||
holder.tvRefundBadge.setVisibility(View.GONE);
|
||||
holder.tvTotal.setTextColor(Color.parseColor("#4CAF50"));
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> saleClickListener.onSaleClick(position));
|
||||
holder.itemView.setOnClickListener(v -> listener.onSaleClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return saleList.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.RefundDTO;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface RefundApi {
|
||||
|
||||
@GET("api/v1/refunds")
|
||||
Call<List<RefundDTO>> getAllRefunds();
|
||||
|
||||
@GET("api/v1/refunds/{id}")
|
||||
Call<RefundDTO> getRefundById(@Path("id") Long id);
|
||||
|
||||
@POST("api/v1/refunds")
|
||||
Call<RefundDTO> createRefund(@Body RefundDTO refund);
|
||||
|
||||
@PUT("api/v1/refunds/{id}")
|
||||
Call<RefundDTO> updateRefund(@Path("id") Long id, @Body RefundDTO refund);
|
||||
|
||||
@DELETE("api/v1/refunds/{id}")
|
||||
Call<Void> deleteRefund(@Path("id") Long id);
|
||||
}
|
||||
@@ -28,9 +28,14 @@ public class RetrofitClient {
|
||||
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|
||||
|| "google_sdk".equals(Build.PRODUCT)) {
|
||||
return "http://10.0.2.2:8080/"; //emulator testing
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
return "http://10.0.0.200:8080/"; //Hardware testing
|
||||
}
|
||||
|
||||
/*else {
|
||||
return "http://192.168.1.148:8080/"; //Hardware testing
|
||||
} */
|
||||
}
|
||||
|
||||
private static Retrofit retrofit = null;
|
||||
@@ -123,4 +128,11 @@ public class RetrofitClient {
|
||||
return getClient(context).create(CategoryApi.class);
|
||||
}
|
||||
|
||||
public static RefundApi getRefundApi(Context context) {
|
||||
return getClient(context).create(RefundApi.class);
|
||||
}
|
||||
public static EmployeeApi getEmployeeApi(Context context) {
|
||||
return getClient(context).create(EmployeeApi.class);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,15 +2,8 @@ package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.*;
|
||||
|
||||
public interface SaleApi {
|
||||
|
||||
@@ -24,10 +17,4 @@ public interface SaleApi {
|
||||
|
||||
@POST("api/v1/sales")
|
||||
Call<SaleDTO> createSale(@Body SaleDTO sale);
|
||||
|
||||
@PUT("api/v1/sales/{id}")
|
||||
Call<SaleDTO> updateSale(@Path("id") Long id, @Body SaleDTO sale);
|
||||
|
||||
@DELETE("api/v1/sales/{id}")
|
||||
Call<Void> deleteSale(@Path("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class RefundDTO {
|
||||
// Response fields
|
||||
private Long id;
|
||||
private Long saleId;
|
||||
private Long customerId;
|
||||
private BigDecimal amount;
|
||||
private String reason;
|
||||
private String status;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
// Constructor for create request
|
||||
public RefundDTO(Long saleId, String reason) {
|
||||
this.saleId = saleId;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
// Constructor for update request
|
||||
public RefundDTO(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Long getSaleId() {
|
||||
return saleId;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public BigDecimal getAmount() {
|
||||
return amount;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,121 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class SaleDTO {
|
||||
|
||||
// Response fields
|
||||
private Long saleId;
|
||||
|
||||
private Long productId;
|
||||
private String productName;
|
||||
|
||||
private Integer quantity;
|
||||
private Double price;
|
||||
private Double totalAmount;
|
||||
|
||||
private String saleDate;
|
||||
private String customerName;
|
||||
private Long employeeId;
|
||||
private String employeeName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private BigDecimal totalAmount;
|
||||
private String paymentMethod;
|
||||
private Boolean isRefund;
|
||||
private Long originalSaleId;
|
||||
private List<SaleItemDTO> items;
|
||||
private String createdAt;
|
||||
|
||||
public SaleDTO() {}
|
||||
// Request fields
|
||||
private Long customerId;
|
||||
|
||||
// Constructor for create request
|
||||
public SaleDTO(Long storeId, String paymentMethod, List<SaleItemDTO> items,
|
||||
Boolean isRefund, Long originalSaleId, Long customerId) {
|
||||
this.storeId = storeId;
|
||||
this.paymentMethod = paymentMethod;
|
||||
this.items = items;
|
||||
this.isRefund = isRefund;
|
||||
this.originalSaleId = originalSaleId;
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public Long getSaleId() {
|
||||
return saleId;
|
||||
}
|
||||
|
||||
public void setSaleId(Long saleId) {
|
||||
this.saleId = saleId;
|
||||
}
|
||||
|
||||
public Long getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public void setProductId(Long productId) {
|
||||
this.productId = productId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName) {
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Double getPrice() {
|
||||
return price;
|
||||
}
|
||||
|
||||
public void setPrice(Double price) {
|
||||
this.price = price;
|
||||
}
|
||||
|
||||
public Double getTotalAmount() {
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
public void setTotalAmount(Double totalAmount) {
|
||||
this.totalAmount = totalAmount;
|
||||
}
|
||||
|
||||
public String getSaleDate() {
|
||||
return saleDate;
|
||||
}
|
||||
|
||||
public void setSaleDate(String saleDate) {
|
||||
this.saleDate = saleDate;
|
||||
public Long getEmployeeId() {
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
public String getEmployeeName() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalAmount() {
|
||||
return totalAmount;
|
||||
}
|
||||
|
||||
public String getPaymentMethod() {
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
public Boolean getIsRefund() {
|
||||
return isRefund;
|
||||
}
|
||||
|
||||
public Long getOriginalSaleId() {
|
||||
return originalSaleId;
|
||||
}
|
||||
|
||||
public List<SaleItemDTO> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
// Nested SaleItemDTO
|
||||
public static class SaleItemDTO {
|
||||
private Long saleItemId;
|
||||
private Long prodId;
|
||||
private String productName;
|
||||
private Integer quantity;
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
// Constructor for request
|
||||
public SaleItemDTO(Long prodId, Integer quantity) {
|
||||
this.prodId = prodId;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Long getSaleItemId() {
|
||||
return saleItemId;
|
||||
}
|
||||
|
||||
public Long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public BigDecimal getUnitPrice() {
|
||||
return unitPrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.fragments.listfragments.PetFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.ServiceFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.StaffFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.SupplierFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.AdoptionFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.AppointmentFragment;
|
||||
@@ -25,6 +26,8 @@ import com.example.petstoremobile.fragments.listfragments.ProductFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.ProductSupplierFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.PurchaseOrderFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.SaleFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.AnalyticsFragment;
|
||||
|
||||
|
||||
//The Fragment for the displaying the list of entities to be viewed
|
||||
public class ListFragment extends Fragment {
|
||||
@@ -37,6 +40,12 @@ public class ListFragment extends Fragment {
|
||||
|
||||
private LinearLayout drawerAdoptions, drawerAppointments, drawerInventory, drawerProducts, drawerProductSupplier, drawerPurchaseOrderView, drawerSale;
|
||||
|
||||
private LinearLayout drawerAnalytics;
|
||||
|
||||
//Staff
|
||||
|
||||
private LinearLayout drawerStaff;
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
@@ -54,7 +63,9 @@ public class ListFragment extends Fragment {
|
||||
drawerProducts = view.findViewById(R.id.drawerProducts);
|
||||
drawerProductSupplier=view.findViewById(R.id.drawerProductSupplier);
|
||||
drawerSale=view.findViewById(R.id.drawerSale);
|
||||
drawerAnalytics = view.findViewById(R.id.drawerAnalytics);
|
||||
drawerPurchaseOrderView=view.findViewById(R.id.drawerPurchaseOrderView);
|
||||
drawerStaff = view.findViewById(R.id.drawerStaff);
|
||||
|
||||
|
||||
// Check user role and restrict access for STAFF
|
||||
@@ -72,6 +83,13 @@ public class ListFragment extends Fragment {
|
||||
loadFragment(new PetFragment());
|
||||
}
|
||||
|
||||
// Only show for ADMIN
|
||||
if ("ADMIN".equalsIgnoreCase(role)) {
|
||||
drawerStaff.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
drawerStaff.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
//add Listeners to the drawer so user won't be able to interact with the innerContainer (the list fragments)
|
||||
//while the drawer is open
|
||||
drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
|
||||
@@ -162,6 +180,21 @@ public class ListFragment extends Fragment {
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//Analytics
|
||||
|
||||
drawerAnalytics.setOnClickListener(v -> {
|
||||
loadFragment(new AnalyticsFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
// Click listener
|
||||
drawerStaff.setOnClickListener(v -> {
|
||||
loadFragment(new StaffFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class AnalyticsFragment extends Fragment {
|
||||
|
||||
private TextView tvTotalRevenue, tvTotalTransactions, tvAvgTransaction, tvTotalItems;
|
||||
private LinearLayout llTopRevenue, llTopQuantity, llPaymentMethods, llEmployeePerformance, llDailyRevenue;
|
||||
private Button btnRefresh;
|
||||
private ImageButton hamburger;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_analytics, container, false);
|
||||
|
||||
initViews(view);
|
||||
loadAnalytics();
|
||||
|
||||
btnRefresh.setOnClickListener(v -> loadAnalytics());
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null)
|
||||
lf.openDrawer();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
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() {
|
||||
// Clear all sections
|
||||
llTopRevenue.removeAllViews();
|
||||
llTopQuantity.removeAllViews();
|
||||
llPaymentMethods.removeAllViews();
|
||||
llEmployeePerformance.removeAllViews();
|
||||
llDailyRevenue.removeAllViews();
|
||||
|
||||
// Show loading
|
||||
tvTotalRevenue.setText("Loading...");
|
||||
tvTotalTransactions.setText("...");
|
||||
tvAvgTransaction.setText("...");
|
||||
tvTotalItems.setText("...");
|
||||
|
||||
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000)
|
||||
.enqueue(new Callback<PageResponse<SaleDTO>>() {
|
||||
public void onResponse(Call<PageResponse<SaleDTO>> c,
|
||||
Response<PageResponse<SaleDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
computeAndDisplay(r.body().getContent());
|
||||
} else {
|
||||
showError("Failed to load sales data");
|
||||
}
|
||||
}
|
||||
|
||||
public void onFailure(Call<PageResponse<SaleDTO>> c, Throwable t) {
|
||||
Log.e("Analytics", t.getMessage());
|
||||
showError("Network error");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void computeAndDisplay(List<SaleDTO> sales) {
|
||||
// Filter out refunds for most metrics
|
||||
List<SaleDTO> regularSales = new ArrayList<>();
|
||||
for (SaleDTO s : sales) {
|
||||
if (!Boolean.TRUE.equals(s.getIsRefund()))
|
||||
regularSales.add(s);
|
||||
}
|
||||
|
||||
// ── Summary ──────────────────────────────────────────
|
||||
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||
int totalItems = 0;
|
||||
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getTotalAmount() != null)
|
||||
totalRevenue = totalRevenue.add(s.getTotalAmount());
|
||||
if (s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
if (item.getQuantity() != null)
|
||||
totalItems += Math.abs(item.getQuantity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int totalTx = regularSales.size();
|
||||
BigDecimal avgTx = totalTx > 0
|
||||
? totalRevenue.divide(BigDecimal.valueOf(totalTx), 2, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO;
|
||||
|
||||
tvTotalRevenue.setText("$" + totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
||||
tvTotalTransactions.setText(String.valueOf(totalTx));
|
||||
tvAvgTransaction.setText("$" + avgTx);
|
||||
tvTotalItems.setText(String.valueOf(totalItems));
|
||||
|
||||
// ── Top Products by Revenue ───────────────────────────
|
||||
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
||||
Map<String, Integer> quantityByProduct = new LinkedHashMap<>();
|
||||
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
String name = item.getProductName() != null ? item.getProductName() : "Unknown";
|
||||
int qty = item.getQuantity() != null ? Math.abs(item.getQuantity()) : 0;
|
||||
BigDecimal lineTotal = item.getUnitPrice() != null
|
||||
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty))
|
||||
: BigDecimal.ZERO;
|
||||
|
||||
revenueByProduct.merge(name, lineTotal, BigDecimal::add);
|
||||
quantityByProduct.merge(name, qty, Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by revenue desc, take top 5
|
||||
List<Map.Entry<String, BigDecimal>> topRevenue = new ArrayList<>(revenueByProduct.entrySet());
|
||||
topRevenue.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||
BigDecimal maxRevenue = topRevenue.isEmpty() ? BigDecimal.ONE : topRevenue.get(0).getValue();
|
||||
|
||||
llTopRevenue.removeAllViews();
|
||||
for (int i = 0; i < Math.min(5, topRevenue.size()); i++) {
|
||||
Map.Entry<String, BigDecimal> e = topRevenue.get(i);
|
||||
addBarRow(llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
||||
}
|
||||
if (topRevenue.isEmpty())
|
||||
addEmptyRow(llTopRevenue, "No data");
|
||||
|
||||
// Sort by quantity desc, take top 5
|
||||
List<Map.Entry<String, Integer>> topQuantity = new ArrayList<>(quantityByProduct.entrySet());
|
||||
topQuantity.sort((a, b) -> b.getValue() - a.getValue());
|
||||
int maxQty = topQuantity.isEmpty() ? 1 : topQuantity.get(0).getValue();
|
||||
|
||||
llTopQuantity.removeAllViews();
|
||||
for (int i = 0; i < Math.min(5, topQuantity.size()); i++) {
|
||||
Map.Entry<String, Integer> e = topQuantity.get(i);
|
||||
addBarRow(llTopQuantity, e.getKey(), e.getValue() + " units",
|
||||
(float) e.getValue() / maxQty, "#4ecdc4");
|
||||
}
|
||||
if (topQuantity.isEmpty())
|
||||
addEmptyRow(llTopQuantity, "No data");
|
||||
|
||||
// ── Payment Methods ───────────────────────────────────
|
||||
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
||||
for (SaleDTO s : regularSales) {
|
||||
String method = s.getPaymentMethod() != null ? s.getPaymentMethod() : "Unknown";
|
||||
paymentCount.merge(method, 1, Integer::sum);
|
||||
}
|
||||
|
||||
int maxPayment = paymentCount.values().stream().max(Integer::compare).orElse(1);
|
||||
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
||||
int ci = 0;
|
||||
llPaymentMethods.removeAllViews();
|
||||
for (Map.Entry<String, Integer> e : paymentCount.entrySet()) {
|
||||
addBarRow(llPaymentMethods, e.getKey(),
|
||||
e.getValue() + " transactions",
|
||||
(float) e.getValue() / maxPayment,
|
||||
paymentColors[ci % paymentColors.length]);
|
||||
ci++;
|
||||
}
|
||||
if (paymentCount.isEmpty())
|
||||
addEmptyRow(llPaymentMethods, "No data");
|
||||
|
||||
// ── Employee Performance ──────────────────────────────
|
||||
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
||||
for (SaleDTO s : regularSales) {
|
||||
String emp = s.getEmployeeName() != null ? s.getEmployeeName() : "Unknown";
|
||||
if (s.getTotalAmount() != null)
|
||||
employeeRevenue.merge(emp, s.getTotalAmount(), BigDecimal::add);
|
||||
}
|
||||
|
||||
List<Map.Entry<String, BigDecimal>> empList = new ArrayList<>(employeeRevenue.entrySet());
|
||||
empList.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||
BigDecimal maxEmp = empList.isEmpty() ? BigDecimal.ONE : empList.get(0).getValue();
|
||||
|
||||
llEmployeePerformance.removeAllViews();
|
||||
for (Map.Entry<String, BigDecimal> e : empList) {
|
||||
addBarRow(llEmployeePerformance, e.getKey(),
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxEmp.floatValue(),
|
||||
"#1a759f");
|
||||
}
|
||||
if (empList.isEmpty())
|
||||
addEmptyRow(llEmployeePerformance, "No data");
|
||||
|
||||
// ── Daily Revenue (last 7 days) ───────────────────────
|
||||
Map<String, BigDecimal> dailyRevenue = new TreeMap<>();
|
||||
|
||||
// Initialize last 7 days
|
||||
Calendar cal = Calendar.getInstance();
|
||||
for (int i = 6; i >= 0; i--) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.add(Calendar.DAY_OF_YEAR, -i);
|
||||
String key = String.format("%04d-%02d-%02d",
|
||||
day.get(Calendar.YEAR),
|
||||
day.get(Calendar.MONTH) + 1,
|
||||
day.get(Calendar.DAY_OF_MONTH));
|
||||
dailyRevenue.put(key, BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getSaleDate() != null && s.getTotalAmount() != null) {
|
||||
String date = s.getSaleDate().length() >= 10
|
||||
? s.getSaleDate().substring(0, 10)
|
||||
: s.getSaleDate();
|
||||
if (dailyRevenue.containsKey(date)) {
|
||||
dailyRevenue.merge(date, s.getTotalAmount(), BigDecimal::add);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal maxDaily = dailyRevenue.values().stream()
|
||||
.max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
||||
if (maxDaily.compareTo(BigDecimal.ZERO) == 0)
|
||||
maxDaily = BigDecimal.ONE;
|
||||
|
||||
llDailyRevenue.removeAllViews();
|
||||
for (Map.Entry<String, BigDecimal> e : dailyRevenue.entrySet()) {
|
||||
// Show just MM-DD
|
||||
String label = e.getKey().length() >= 10
|
||||
? e.getKey().substring(5)
|
||||
: e.getKey();
|
||||
addBarRow(llDailyRevenue, label,
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxDaily.floatValue(),
|
||||
"#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) {
|
||||
LinearLayout row = new LinearLayout(getContext());
|
||||
row.setOrientation(LinearLayout.VERTICAL);
|
||||
row.setPadding(0, 6, 0, 6);
|
||||
|
||||
// Label + value row
|
||||
LinearLayout labelRow = new LinearLayout(getContext());
|
||||
labelRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
TextView tvLabel = new TextView(getContext());
|
||||
tvLabel.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvLabel.setText(label);
|
||||
tvLabel.setTextColor(Color.parseColor("#444441"));
|
||||
tvLabel.setTextSize(13f);
|
||||
|
||||
TextView tvValue = new TextView(getContext());
|
||||
tvValue.setText(value);
|
||||
tvValue.setTextColor(Color.parseColor("#444441"));
|
||||
tvValue.setTextSize(13f);
|
||||
tvValue.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
|
||||
|
||||
labelRow.addView(tvLabel);
|
||||
labelRow.addView(tvValue);
|
||||
|
||||
// Bar background
|
||||
LinearLayout barBg = new LinearLayout(getContext());
|
||||
LinearLayout.LayoutParams bgParams = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, 12);
|
||||
bgParams.setMargins(0, 4, 0, 0);
|
||||
barBg.setLayoutParams(bgParams);
|
||||
barBg.setBackgroundColor(Color.parseColor("#EEEEEE"));
|
||||
|
||||
// Bar fill
|
||||
View barFill = new View(getContext());
|
||||
int fillWidth = (int) (ratio * 100);
|
||||
LinearLayout.LayoutParams fillParams = new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.MATCH_PARENT, ratio);
|
||||
barFill.setLayoutParams(fillParams);
|
||||
barFill.setBackgroundColor(Color.parseColor(color));
|
||||
barBg.addView(barFill);
|
||||
|
||||
// Empty space
|
||||
View spacer = new View(getContext());
|
||||
spacer.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.MATCH_PARENT, 1f - ratio));
|
||||
barBg.addView(spacer);
|
||||
|
||||
row.addView(labelRow);
|
||||
row.addView(barBg);
|
||||
parent.addView(row);
|
||||
}
|
||||
|
||||
private void addEmptyRow(LinearLayout parent, String message) {
|
||||
TextView tv = new TextView(getContext());
|
||||
tv.setText(message);
|
||||
tv.setTextColor(Color.parseColor("#888780"));
|
||||
tv.setTextSize(13f);
|
||||
parent.addView(tv);
|
||||
}
|
||||
|
||||
private void showError(String msg) {
|
||||
if (getContext() == null)
|
||||
return;
|
||||
tvTotalRevenue.setText("Error");
|
||||
tvTotalTransactions.setText("—");
|
||||
tvAvgTransaction.setText("—");
|
||||
tvTotalItems.setText("—");
|
||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,32 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.*;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.SaleAdapter;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.RefundDetailFragment;
|
||||
import com.example.petstoremobile.models.Sale;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.SaleDetailFragment;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.RefundFragment;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
||||
|
||||
private List<Sale> saleList = new ArrayList<>();
|
||||
private List<Sale> filteredList = new ArrayList<>();
|
||||
private List<SaleDTO> saleList = new ArrayList<>();
|
||||
private List<SaleDTO> filteredList = new ArrayList<>();
|
||||
private SaleAdapter adapter;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
private EditText etSearch;
|
||||
|
||||
@Override
|
||||
@@ -33,43 +35,63 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
View view = inflater.inflate(R.layout.fragment_sale, container, false);
|
||||
|
||||
setupRecyclerView(view);
|
||||
loadSaleData();
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadSales();
|
||||
|
||||
FloatingActionButton fab = view.findViewById(R.id.fabAddSale);
|
||||
fab.setOnClickListener(v -> openDetail(-1, null));
|
||||
|
||||
ImageButton hamburger = view.findViewById(R.id.btnHamburgerSale);
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.openDrawer();
|
||||
});
|
||||
|
||||
// ← moved inside onCreateView
|
||||
Button btnRefund = view.findViewById(R.id.btnOpenRefund);
|
||||
btnRefund.setOnClickListener(v -> {
|
||||
RefundFragment refundFragment = new RefundFragment();
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.loadFragment(refundFragment);
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView rv = view.findViewById(R.id.recyclerViewSales);
|
||||
adapter = new SaleAdapter(filteredList, this);
|
||||
rv.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
rv.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchSale);
|
||||
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) {
|
||||
filterSales(s.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
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 filterSales(String query) {
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefresh = view.findViewById(R.id.swipeRefreshSale);
|
||||
swipeRefresh.setOnRefreshListener(this::loadSales);
|
||||
}
|
||||
|
||||
private void filter(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(saleList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (Sale s : saleList) {
|
||||
if (s.getItemName().toLowerCase().contains(lower)
|
||||
|| s.getEmployeeName().toLowerCase().contains(lower)
|
||||
|| s.getSaleDate().toLowerCase().contains(lower)
|
||||
|| s.getPaymentMethod().toLowerCase().contains(lower)
|
||||
|| String.valueOf(s.getSaleId()).contains(lower)) {
|
||||
for (SaleDTO s : saleList) {
|
||||
if ((s.getEmployeeName() != null && s.getEmployeeName().toLowerCase().contains(lower))
|
||||
|| (s.getStoreName() != null && s.getStoreName().toLowerCase().contains(lower))
|
||||
|| (s.getPaymentMethod() != null && s.getPaymentMethod().toLowerCase().contains(lower))) {
|
||||
filteredList.add(s);
|
||||
}
|
||||
}
|
||||
@@ -77,54 +99,44 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshSale);
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
loadSaleData();
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
});
|
||||
public void loadSales() {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(true);
|
||||
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 100)
|
||||
.enqueue(new Callback<PageResponse<SaleDTO>>() {
|
||||
public void onResponse(Call<PageResponse<SaleDTO>> c,
|
||||
Response<PageResponse<SaleDTO>> r) {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
saleList.clear();
|
||||
saleList.addAll(r.body().getContent());
|
||||
filter(etSearch != null ? etSearch.getText().toString() : "");
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Failed to load sales",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<SaleDTO>> c, Throwable t) {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
|
||||
Log.e("SaleFragment", t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void openDetail(int position, SaleDTO sale) {
|
||||
SaleDetailFragment detail = new SaleDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
if (position != -1 && sale != null) {
|
||||
args.putLong("saleId", sale.getSaleId());
|
||||
args.putBoolean("isRefund", Boolean.TRUE.equals(sale.getIsRefund()));
|
||||
args.putBoolean("viewOnly", true);
|
||||
}
|
||||
detail.setArguments(args);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.loadFragment(detail);
|
||||
}
|
||||
|
||||
// When a sale row is clicked, open the refund screen for that sale
|
||||
@Override
|
||||
public void onSaleClick(int position) {
|
||||
Sale sale = filteredList.get(position);
|
||||
RefundDetailFragment refundFragment = new RefundDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("saleId", sale.getSaleId());
|
||||
args.putString("saleDate", sale.getSaleDate());
|
||||
args.putString("employeeName", sale.getEmployeeName());
|
||||
args.putDouble("total", sale.getTotal());
|
||||
args.putString("paymentMethod", sale.getPaymentMethod());
|
||||
refundFragment.setArguments(args);
|
||||
refundFragment.setSaleFragment(this);
|
||||
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null)
|
||||
listFragment.loadFragment(refundFragment);
|
||||
openDetail(position, filteredList.get(position));
|
||||
}
|
||||
|
||||
public void reloadSales() {
|
||||
loadSaleData();
|
||||
}
|
||||
|
||||
// TODO: Replace with actual API call - GET v1/sales
|
||||
private void loadSaleData() {
|
||||
saleList.clear();
|
||||
saleList.add(new Sale(1, "2026-03-01", "John Smith", "Premium Dog Food", 2, 45.99, 91.98, "Card", false));
|
||||
saleList.add(new Sale(2, "2026-03-02", "Jane Doe", "Cat Toy Bundle", 1, 19.99, 19.99, "Cash", false));
|
||||
saleList.add(new Sale(3, "2026-03-03", "John Smith", "Pet Shampoo", 3, 12.99, 38.97, "Card", false));
|
||||
saleList.add(new Sale(4, "2026-03-04", "Jane Doe", "Dog Bed - Large", 1, 89.99, 89.99, "Cash", true));
|
||||
filteredList.clear();
|
||||
filteredList.addAll(saleList);
|
||||
if (adapter != null)
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewSales);
|
||||
adapter = new SaleAdapter(filteredList, this);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.SaleFragment;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
|
||||
public class RefundDetailFragment extends Fragment {
|
||||
|
||||
private EditText etRefundSaleId, etRefundReason;
|
||||
private TextView tvSaleInfo;
|
||||
private Spinner spinnerRefundPayment;
|
||||
private Button btnLoadSale, btnProcessRefund, btnBack;
|
||||
private int saleId;
|
||||
private SaleFragment saleFragment;
|
||||
|
||||
public void setSaleFragment(SaleFragment fragment) {
|
||||
this.saleFragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_refund_detail, container, false);
|
||||
|
||||
initViews(view);
|
||||
setupSpinner();
|
||||
handleArguments();
|
||||
|
||||
btnBack.setOnClickListener(v -> goBack());
|
||||
btnLoadSale.setOnClickListener(v -> loadSaleDetails());
|
||||
btnProcessRefund.setOnClickListener(v -> processRefund());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void loadSaleDetails() {
|
||||
String idText = etRefundSaleId.getText().toString().trim();
|
||||
if (idText.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Enter a Sale ID", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int id = Integer.parseInt(idText);
|
||||
// TODO: Replace with actual API call - GET v1/sales/{id}
|
||||
// For now show placeholder info
|
||||
tvSaleInfo.setText("Sale ID: " + id + " loaded. Enter reason and payment method to process refund.");
|
||||
tvSaleInfo.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
|
||||
} catch (NumberFormatException e) {
|
||||
Toast.makeText(getContext(), "Invalid Sale ID", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void processRefund() {
|
||||
if (!InputValidator.isNotEmpty(etRefundSaleId, "Sale ID"))
|
||||
return;
|
||||
if (!InputValidator.isNotEmpty(etRefundReason, "Refund Reason"))
|
||||
return;
|
||||
|
||||
String idText = etRefundSaleId.getText().toString().trim();
|
||||
String reason = etRefundReason.getText().toString().trim();
|
||||
String payment = spinnerRefundPayment.getSelectedItem().toString();
|
||||
|
||||
try {
|
||||
int id = Integer.parseInt(idText);
|
||||
// TODO: Replace with actual API call - POST v1/refunds
|
||||
ActivityLogger.log(requireContext(), "Processed refund for Sale ID: " + id + " - Reason: " + reason);
|
||||
Toast.makeText(getContext(), "Refund processed for Sale ID: " + id, Toast.LENGTH_SHORT).show();
|
||||
if (saleFragment != null)
|
||||
saleFragment.reloadSales();
|
||||
goBack();
|
||||
} catch (NumberFormatException e) {
|
||||
Toast.makeText(getContext(), "Invalid Sale ID", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
if (getArguments() != null && getArguments().containsKey("saleId")) {
|
||||
saleId = getArguments().getInt("saleId");
|
||||
etRefundSaleId.setText(String.valueOf(saleId));
|
||||
String info = "Sale Date: " + getArguments().getString("saleDate")
|
||||
+ " | Employee: " + getArguments().getString("employeeName")
|
||||
+ " | Total: $" + String.format("%.2f", getArguments().getDouble("total"))
|
||||
+ " | Payment: " + getArguments().getString("paymentMethod");
|
||||
tvSaleInfo.setText(info);
|
||||
tvSaleInfo.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
|
||||
|
||||
// Pre-select payment method
|
||||
String payment = getArguments().getString("paymentMethod");
|
||||
ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinnerRefundPayment.getAdapter();
|
||||
if (adapter != null && payment != null) {
|
||||
int pos = adapter.getPosition(payment);
|
||||
if (pos >= 0)
|
||||
spinnerRefundPayment.setSelection(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void goBack() {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null)
|
||||
listFragment.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
|
||||
private void initViews(View view) {
|
||||
etRefundSaleId = view.findViewById(R.id.etRefundSaleId);
|
||||
etRefundReason = view.findViewById(R.id.etRefundReason);
|
||||
tvSaleInfo = view.findViewById(R.id.tvSaleInfo);
|
||||
spinnerRefundPayment = view.findViewById(R.id.spinnerRefundPayment);
|
||||
btnLoadSale = view.findViewById(R.id.btnLoadSale);
|
||||
btnProcessRefund = view.findViewById(R.id.btnProcessRefund);
|
||||
btnBack = view.findViewById(R.id.btnRefundBack);
|
||||
}
|
||||
|
||||
private void setupSpinner() {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item,
|
||||
new String[] { "Cash", "Card", "Debit" });
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerRefundPayment.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class RefundFragment extends Fragment {
|
||||
|
||||
private EditText etSaleId;
|
||||
private Button btnLoadSale, btnProcessRefund, btnBack;
|
||||
private TextView tvSaleInfo, tvRefundTotal;
|
||||
private LinearLayout llOriginalItems, llRefundItems;
|
||||
private LinearLayout cardOriginalItems, cardRefundItems, cardPayment;
|
||||
private Spinner spinnerPayment;
|
||||
|
||||
private SaleDTO currentSale;
|
||||
private List<SaleDTO> allSales = new ArrayList<>();
|
||||
|
||||
// Items available to refund (after accounting for previous refunds)
|
||||
private List<RefundItem> availableItems = new ArrayList<>();
|
||||
// Items user has added to refund cart
|
||||
private List<RefundItem> refundCart = new ArrayList<>();
|
||||
|
||||
private final String[] PAYMENT_METHODS = {"Cash", "Card", "Debit"};
|
||||
|
||||
// Inner class to track refund items
|
||||
static class RefundItem {
|
||||
long prodId;
|
||||
String productName;
|
||||
int quantity;
|
||||
BigDecimal unitPrice;
|
||||
|
||||
RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) {
|
||||
this.prodId = prodId;
|
||||
this.productName = productName;
|
||||
this.quantity = quantity;
|
||||
this.unitPrice = unitPrice;
|
||||
}
|
||||
|
||||
BigDecimal getTotal() {
|
||||
return unitPrice != null
|
||||
? unitPrice.multiply(BigDecimal.valueOf(quantity))
|
||||
: BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_refund, container, false);
|
||||
initViews(view);
|
||||
setupSpinner();
|
||||
loadAllSales();
|
||||
|
||||
// Pre-fill sale ID if passed from SaleFragment
|
||||
Bundle args = getArguments();
|
||||
if (args != null && args.containsKey("saleId")) {
|
||||
long saleId = args.getLong("saleId");
|
||||
etSaleId.setText(String.valueOf(saleId));
|
||||
// Auto-load after sales are fetched
|
||||
}
|
||||
|
||||
btnLoadSale.setOnClickListener(v -> loadSale());
|
||||
btnProcessRefund.setOnClickListener(v -> processRefund());
|
||||
btnBack.setOnClickListener(v -> navigateBack());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
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() {
|
||||
spinnerPayment.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, PAYMENT_METHODS));
|
||||
}
|
||||
|
||||
private void loadAllSales() {
|
||||
RetrofitClient.getSaleApi(requireContext()).getAllSales(0, 1000)
|
||||
.enqueue(new Callback<PageResponse<SaleDTO>>() {
|
||||
public void onResponse(Call<PageResponse<SaleDTO>> c,
|
||||
Response<PageResponse<SaleDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
allSales = r.body().getContent();
|
||||
// Auto-load if saleId was pre-filled
|
||||
Bundle args = getArguments();
|
||||
if (args != null && args.containsKey("saleId")) {
|
||||
loadSale();
|
||||
}
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<SaleDTO>> c, Throwable t) {
|
||||
Log.e("Refund", "Failed to load sales: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSale() {
|
||||
String idStr = etSaleId.getText().toString().trim();
|
||||
if (idStr.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Enter a Sale ID", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
long saleId;
|
||||
try { saleId = Long.parseLong(idStr); }
|
||||
catch (Exception e) {
|
||||
Toast.makeText(getContext(), "Invalid Sale ID", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find sale in loaded list
|
||||
SaleDTO found = null;
|
||||
for (SaleDTO s : allSales) {
|
||||
if (s.getSaleId() != null && s.getSaleId() == saleId) {
|
||||
found = s; break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found == null) {
|
||||
Toast.makeText(getContext(), "Sale #" + saleId + " not found", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (Boolean.TRUE.equals(found.getIsRefund())) {
|
||||
Toast.makeText(getContext(), "Select an original sale, not a refund record",
|
||||
Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
currentSale = found;
|
||||
|
||||
// Show sale info
|
||||
tvSaleInfo.setVisibility(View.VISIBLE);
|
||||
tvSaleInfo.setText("Sale #" + currentSale.getSaleId()
|
||||
+ " | " + (currentSale.getSaleDate() != null
|
||||
? currentSale.getSaleDate().substring(0, 10) : "")
|
||||
+ " | Employee: " + (currentSale.getEmployeeName() != null
|
||||
? currentSale.getEmployeeName() : "")
|
||||
+ " | Total: $" + currentSale.getTotalAmount()
|
||||
+ " | Payment: " + currentSale.getPaymentMethod());
|
||||
|
||||
// Pre-select payment method
|
||||
if (currentSale.getPaymentMethod() != null) {
|
||||
for (int i = 0; i < PAYMENT_METHODS.length; i++) {
|
||||
if (PAYMENT_METHODS[i].equalsIgnoreCase(currentSale.getPaymentMethod())) {
|
||||
spinnerPayment.setSelection(i); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build refundable items accounting for previous refunds
|
||||
buildRefundableItems();
|
||||
|
||||
if (availableItems.isEmpty()) {
|
||||
Toast.makeText(getContext(),
|
||||
"This sale has no remaining refundable items", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset refund cart
|
||||
refundCart.clear();
|
||||
|
||||
// Show cards
|
||||
cardOriginalItems.setVisibility(View.VISIBLE);
|
||||
cardRefundItems.setVisibility(View.VISIBLE);
|
||||
cardPayment.setVisibility(View.VISIBLE);
|
||||
btnProcessRefund.setVisibility(View.VISIBLE);
|
||||
|
||||
renderOriginalItems();
|
||||
renderRefundCart();
|
||||
updateRefundTotal();
|
||||
}
|
||||
|
||||
private void buildRefundableItems() {
|
||||
availableItems.clear();
|
||||
if (currentSale.getItems() == null) return;
|
||||
|
||||
// Find all previous refunds for this sale
|
||||
Map<Long, Integer> alreadyRefunded = new HashMap<>();
|
||||
for (SaleDTO s : allSales) {
|
||||
if (Boolean.TRUE.equals(s.getIsRefund())
|
||||
&& currentSale.getSaleId().equals(s.getOriginalSaleId())
|
||||
&& s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
if (item.getProdId() != null && item.getQuantity() != null) {
|
||||
alreadyRefunded.merge(item.getProdId(),
|
||||
Math.abs(item.getQuantity()), Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build available items
|
||||
for (SaleDTO.SaleItemDTO item : currentSale.getItems()) {
|
||||
if (item.getProdId() == null || item.getQuantity() == null) continue;
|
||||
int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0);
|
||||
int remaining = item.getQuantity() - refunded;
|
||||
if (remaining > 0) {
|
||||
availableItems.add(new RefundItem(
|
||||
item.getProdId(),
|
||||
item.getProductName() != null ? item.getProductName() : "Unknown",
|
||||
remaining,
|
||||
item.getUnitPrice()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderOriginalItems() {
|
||||
llOriginalItems.removeAllViews();
|
||||
|
||||
// Header
|
||||
addTableHeader(llOriginalItems);
|
||||
|
||||
for (RefundItem item : availableItems) {
|
||||
// Calculate pending in cart
|
||||
int pendingQty = 0;
|
||||
for (RefundItem r : refundCart) {
|
||||
if (r.prodId == item.prodId) { pendingQty = r.quantity; break; }
|
||||
}
|
||||
int displayQty = item.quantity - pendingQty;
|
||||
if (displayQty <= 0) continue;
|
||||
|
||||
LinearLayout row = buildItemRow(
|
||||
item.productName,
|
||||
displayQty,
|
||||
item.unitPrice,
|
||||
true, // show add button
|
||||
() -> showQuantityDialog(item)
|
||||
);
|
||||
llOriginalItems.addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderRefundCart() {
|
||||
llRefundItems.removeAllViews();
|
||||
|
||||
if (refundCart.isEmpty()) {
|
||||
TextView empty = new TextView(getContext());
|
||||
empty.setText("No items added to refund yet");
|
||||
empty.setTextColor(0xFF888780);
|
||||
empty.setTextSize(13f);
|
||||
llRefundItems.addView(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
addTableHeader(llRefundItems);
|
||||
|
||||
for (RefundItem item : refundCart) {
|
||||
LinearLayout row = buildItemRow(
|
||||
item.productName,
|
||||
item.quantity,
|
||||
item.unitPrice,
|
||||
false, // show remove button
|
||||
() -> {
|
||||
refundCart.remove(item);
|
||||
renderOriginalItems();
|
||||
renderRefundCart();
|
||||
updateRefundTotal();
|
||||
}
|
||||
);
|
||||
llRefundItems.addView(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void addTableHeader(LinearLayout parent) {
|
||||
LinearLayout header = new LinearLayout(getContext());
|
||||
header.setOrientation(LinearLayout.HORIZONTAL);
|
||||
header.setPadding(0, 0, 0, 8);
|
||||
|
||||
String[] cols = {"Product", "Qty", "Unit Price", ""};
|
||||
float[] weights = {2f, 1f, 1f, 0.8f};
|
||||
for (int i = 0; i < cols.length; i++) {
|
||||
TextView tv = new TextView(getContext());
|
||||
tv.setLayoutParams(new LinearLayout.LayoutParams(0,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, weights[i]));
|
||||
tv.setText(cols[i]);
|
||||
tv.setTextColor(0xFF888780);
|
||||
tv.setTextSize(11f);
|
||||
header.addView(tv);
|
||||
}
|
||||
parent.addView(header);
|
||||
}
|
||||
|
||||
private LinearLayout buildItemRow(String name, int qty, BigDecimal unitPrice,
|
||||
boolean isAdd, Runnable action) {
|
||||
LinearLayout row = new LinearLayout(getContext());
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.setGravity(android.view.Gravity.CENTER_VERTICAL);
|
||||
row.setPadding(0, 8, 0, 8);
|
||||
|
||||
TextView tvName = new TextView(getContext());
|
||||
tvName.setLayoutParams(new LinearLayout.LayoutParams(0,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
||||
tvName.setText(name);
|
||||
tvName.setTextSize(13f);
|
||||
tvName.setTextColor(0xFF3d3d3a);
|
||||
|
||||
TextView tvQty = new TextView(getContext());
|
||||
tvQty.setLayoutParams(new LinearLayout.LayoutParams(0,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvQty.setText(String.valueOf(qty));
|
||||
tvQty.setTextSize(13f);
|
||||
tvQty.setTextColor(0xFF3d3d3a);
|
||||
|
||||
TextView tvPrice = new TextView(getContext());
|
||||
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(0,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvPrice.setText(unitPrice != null ? "$" + unitPrice : "");
|
||||
tvPrice.setTextSize(13f);
|
||||
tvPrice.setTextColor(0xFF3d3d3a);
|
||||
|
||||
Button btn = new Button(getContext());
|
||||
LinearLayout.LayoutParams btnParams = new LinearLayout.LayoutParams(0,
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT, 0.8f);
|
||||
btn.setLayoutParams(btnParams);
|
||||
btn.setText(isAdd ? "Add" : "Remove");
|
||||
btn.setTextSize(11f);
|
||||
btn.setBackgroundColor(isAdd ? 0xFF1a759f : 0xFFE24B4A);
|
||||
btn.setTextColor(0xFFFFFFFF);
|
||||
btn.setPadding(4, 4, 4, 4);
|
||||
btn.setOnClickListener(v -> action.run());
|
||||
|
||||
row.addView(tvName);
|
||||
row.addView(tvQty);
|
||||
row.addView(tvPrice);
|
||||
row.addView(btn);
|
||||
return row;
|
||||
}
|
||||
|
||||
private void showQuantityDialog(RefundItem item) {
|
||||
// Calculate how many are already in cart
|
||||
int inCart = 0;
|
||||
for (RefundItem r : refundCart) {
|
||||
if (r.prodId == item.prodId) { inCart = r.quantity; break; }
|
||||
}
|
||||
int available = item.quantity - inCart;
|
||||
if (available <= 0) {
|
||||
Toast.makeText(getContext(), "All units already added to refund",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build dialog
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
|
||||
builder.setTitle("Refund Quantity");
|
||||
builder.setMessage("Product: " + item.productName
|
||||
+ "\nAvailable: " + available);
|
||||
|
||||
EditText input = new EditText(getContext());
|
||||
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER);
|
||||
input.setText(String.valueOf(available));
|
||||
input.setSelectAllOnFocus(true);
|
||||
builder.setView(input);
|
||||
|
||||
builder.setPositiveButton("Add to Refund", (d, w) -> {
|
||||
String val = input.getText().toString().trim();
|
||||
if (val.isEmpty()) return;
|
||||
int qty;
|
||||
try { qty = Integer.parseInt(val); }
|
||||
catch (Exception e) {
|
||||
Toast.makeText(getContext(), "Invalid quantity", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (qty <= 0) {
|
||||
Toast.makeText(getContext(), "Quantity must be at least 1",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (qty > available) {
|
||||
Toast.makeText(getContext(), "Cannot exceed " + available,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add or merge into cart
|
||||
boolean merged = false;
|
||||
for (int i = 0; i < refundCart.size(); i++) {
|
||||
if (refundCart.get(i).prodId == item.prodId) {
|
||||
RefundItem existing = refundCart.get(i);
|
||||
refundCart.set(i, new RefundItem(existing.prodId,
|
||||
existing.productName,
|
||||
existing.quantity + qty,
|
||||
existing.unitPrice));
|
||||
merged = true; break;
|
||||
}
|
||||
}
|
||||
if (!merged) {
|
||||
refundCart.add(new RefundItem(item.prodId, item.productName,
|
||||
qty, item.unitPrice));
|
||||
}
|
||||
|
||||
renderOriginalItems();
|
||||
renderRefundCart();
|
||||
updateRefundTotal();
|
||||
});
|
||||
|
||||
builder.setNegativeButton("Cancel", null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void updateRefundTotal() {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (RefundItem item : refundCart) total = total.add(item.getTotal());
|
||||
tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
private void processRefund() {
|
||||
if (currentSale == null) {
|
||||
Toast.makeText(getContext(), "Load a sale first", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (refundCart.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Add at least one item to refund",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
String payment = PAYMENT_METHODS[spinnerPayment.getSelectedItemPosition()];
|
||||
|
||||
// Confirm dialog
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (RefundItem item : refundCart) total = total.add(item.getTotal());
|
||||
final BigDecimal finalTotal = total;
|
||||
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Confirm Refund")
|
||||
.setMessage("Process refund for Sale #" + currentSale.getSaleId()
|
||||
+ "?\nRefund amount: $" + finalTotal.setScale(2, RoundingMode.HALF_UP))
|
||||
.setPositiveButton("Yes", (d, w) -> submitRefund(payment))
|
||||
.setNegativeButton("No", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void submitRefund(String payment) {
|
||||
// Build sale items list
|
||||
List<SaleDTO.SaleItemDTO> items = new ArrayList<>();
|
||||
for (RefundItem item : refundCart) {
|
||||
// Backend expects negative quantity for refunds
|
||||
items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity));
|
||||
}
|
||||
|
||||
SaleDTO dto = new SaleDTO(
|
||||
currentSale.getStoreId(),
|
||||
payment,
|
||||
items,
|
||||
true, // isRefund = true
|
||||
currentSale.getSaleId(), // originalSaleId
|
||||
null // no customer needed
|
||||
);
|
||||
|
||||
Log.d("REFUND", "Submitting refund for saleId=" + currentSale.getSaleId()
|
||||
+ " items=" + items.size());
|
||||
|
||||
RetrofitClient.getSaleApi(requireContext()).createSale(dto)
|
||||
.enqueue(new Callback<SaleDTO>() {
|
||||
public void onResponse(Call<SaleDTO> c, Response<SaleDTO> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
Toast.makeText(getContext(),
|
||||
"Refund #" + r.body().getSaleId() + " processed successfully!",
|
||||
Toast.LENGTH_LONG).show();
|
||||
navigateBack();
|
||||
} else {
|
||||
try {
|
||||
String err = r.errorBody().string();
|
||||
Log.e("REFUND", "Error: " + err);
|
||||
Toast.makeText(getContext(), "Error: " + err,
|
||||
Toast.LENGTH_LONG).show();
|
||||
} catch (Exception e) {
|
||||
Log.e("REFUND", "Failed to read error");
|
||||
}
|
||||
}
|
||||
}
|
||||
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() {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.*;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class SaleDetailFragment extends Fragment {
|
||||
|
||||
private TextView tvMode, tvSaleDetailId, tvTotal;
|
||||
private Spinner spinnerStore, spinnerCustomer, spinnerPayment, spinnerProduct;
|
||||
private EditText etQuantity;
|
||||
private Button btnAddItem, btnSave, btnBack, btnRefund;
|
||||
private LinearLayout llItems;
|
||||
|
||||
private boolean viewOnly = false;
|
||||
private long saleId = -1;
|
||||
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
private List<ProductDTO> productList = new ArrayList<>();
|
||||
private List<SaleDTO.SaleItemDTO> cartItems = new ArrayList<>();
|
||||
|
||||
private final String[] PAYMENT_METHODS = { "Cash", "Credit Card", "Debit Card", "Online" };
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_sale_detail, container, false);
|
||||
initViews(view);
|
||||
handleArguments();
|
||||
|
||||
if (!viewOnly) {
|
||||
loadData();
|
||||
setupAddItem();
|
||||
}
|
||||
|
||||
btnBack.setOnClickListener(v -> navigateBack());
|
||||
btnSave.setOnClickListener(v -> saveSale());
|
||||
btnRefund.setOnClickListener(v -> showRefundDialog());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
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() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("saleId")) {
|
||||
saleId = a.getLong("saleId");
|
||||
viewOnly = a.getBoolean("viewOnly", false);
|
||||
tvMode.setText("Sale #" + saleId);
|
||||
tvSaleDetailId.setText("ID: " + saleId);
|
||||
|
||||
// Show refund button for existing non-refund sales
|
||||
if (!a.getBoolean("isRefund", false)) {
|
||||
btnRefund.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// Hide save and input controls for view only
|
||||
if (viewOnly) {
|
||||
btnSave.setVisibility(View.GONE);
|
||||
spinnerStore.setEnabled(false);
|
||||
spinnerCustomer.setEnabled(false);
|
||||
spinnerPayment.setEnabled(false);
|
||||
spinnerProduct.setEnabled(false);
|
||||
etQuantity.setEnabled(false);
|
||||
btnAddItem.setEnabled(false);
|
||||
}
|
||||
|
||||
// Load sale details
|
||||
loadSaleDetails();
|
||||
} else {
|
||||
tvMode.setText("New Sale");
|
||||
tvSaleDetailId.setVisibility(View.GONE);
|
||||
btnRefund.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
loadStores();
|
||||
loadCustomers();
|
||||
loadProducts();
|
||||
}
|
||||
|
||||
private void loadStores() {
|
||||
// Hardcoded since store endpoint is admin only
|
||||
storeList = new ArrayList<>();
|
||||
storeList.add(new StoreDTO(1L, "Downtown Branch"));
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Store --");
|
||||
names.add("Downtown Branch");
|
||||
spinnerStore.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
}
|
||||
|
||||
private void loadCustomers() {
|
||||
RetrofitClient.getCustomerApi(requireContext()).getAllCustomers(0, 200)
|
||||
.enqueue(new Callback<PageResponse<CustomerDTO>>() {
|
||||
public void onResponse(Call<PageResponse<CustomerDTO>> c,
|
||||
Response<PageResponse<CustomerDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
customerList = r.body().getContent();
|
||||
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() {
|
||||
RetrofitClient.getProductApi(requireContext()).getAllProducts(null, 0, 200)
|
||||
.enqueue(new Callback<PageResponse<ProductDTO>>() {
|
||||
public void onResponse(Call<PageResponse<ProductDTO>> c,
|
||||
Response<PageResponse<ProductDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
productList = r.body().getContent();
|
||||
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() {
|
||||
RetrofitClient.getSaleApi(requireContext()).getSaleById(saleId)
|
||||
.enqueue(new Callback<SaleDTO>() {
|
||||
public void onResponse(Call<SaleDTO> c, Response<SaleDTO> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
SaleDTO sale = r.body();
|
||||
tvTotal.setText("Total: $" + sale.getTotalAmount());
|
||||
// Display items
|
||||
if (sale.getItems() != null) {
|
||||
llItems.removeAllViews();
|
||||
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||
addItemRow(item.getProductName(),
|
||||
Math.abs(item.getQuantity()),
|
||||
item.getUnitPrice());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onFailure(Call<SaleDTO> c, Throwable t) {
|
||||
Log.e("SaleDetail", "Load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupAddItem() {
|
||||
btnAddItem.setOnClickListener(v -> {
|
||||
if (spinnerProduct.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
String qtyStr = etQuantity.getText().toString().trim();
|
||||
if (qtyStr.isEmpty()) {
|
||||
etQuantity.setError("Enter quantity");
|
||||
return;
|
||||
}
|
||||
int qty;
|
||||
try {
|
||||
qty = Integer.parseInt(qtyStr);
|
||||
} catch (Exception e) {
|
||||
etQuantity.setError("Invalid quantity");
|
||||
return;
|
||||
}
|
||||
|
||||
ProductDTO product = productList.get(spinnerProduct.getSelectedItemPosition() - 1);
|
||||
|
||||
// Check if product already in cart
|
||||
for (SaleDTO.SaleItemDTO existing : cartItems) {
|
||||
if (existing.getProdId().equals(product.getProdId())) {
|
||||
Toast.makeText(getContext(), "Product already added", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SaleDTO.SaleItemDTO item = new SaleDTO.SaleItemDTO(product.getProdId(), qty);
|
||||
cartItems.add(item);
|
||||
addItemRow(product.getProdName(), qty, product.getProdPrice());
|
||||
updateTotal();
|
||||
etQuantity.setText("");
|
||||
});
|
||||
}
|
||||
|
||||
private void addItemRow(String name, int qty, BigDecimal price) {
|
||||
LinearLayout row = new LinearLayout(getContext());
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.setPadding(0, 8, 0, 8);
|
||||
|
||||
TextView tvName = new TextView(getContext());
|
||||
tvName.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
||||
tvName.setText(name);
|
||||
|
||||
TextView tvQty = new TextView(getContext());
|
||||
tvQty.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvQty.setText("x" + qty);
|
||||
|
||||
TextView tvPrice = new TextView(getContext());
|
||||
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvPrice.setText(price != null ? "$" + price : "");
|
||||
|
||||
row.addView(tvName);
|
||||
row.addView(tvQty);
|
||||
row.addView(tvPrice);
|
||||
llItems.addView(row);
|
||||
}
|
||||
|
||||
private void updateTotal() {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
int productIdx = 0;
|
||||
for (SaleDTO.SaleItemDTO item : cartItems) {
|
||||
if (productIdx < productList.size()) {
|
||||
for (ProductDTO p : productList) {
|
||||
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
|
||||
total = total.add(p.getProdPrice()
|
||||
.multiply(BigDecimal.valueOf(item.getQuantity())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tvTotal.setText("Total: $" + total);
|
||||
}
|
||||
|
||||
private void saveSale() {
|
||||
if (spinnerStore.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (cartItems.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Add at least one item", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
StoreDTO store = storeList.get(0); // only one store
|
||||
String payment = PAYMENT_METHODS[spinnerPayment.getSelectedItemPosition()];
|
||||
|
||||
// Optional customer
|
||||
Long customerId = null;
|
||||
if (spinnerCustomer.getSelectedItemPosition() > 0) {
|
||||
customerId = customerList.get(spinnerCustomer.getSelectedItemPosition() - 1)
|
||||
.getCustomerId();
|
||||
}
|
||||
|
||||
SaleDTO dto = new SaleDTO(
|
||||
store.getStoreId(),
|
||||
payment,
|
||||
cartItems,
|
||||
false,
|
||||
null,
|
||||
customerId);
|
||||
|
||||
Log.d("SALE_SAVE", "storeId=" + store.getStoreId()
|
||||
+ " payment=" + payment
|
||||
+ " items=" + cartItems.size()
|
||||
+ " customerId=" + customerId);
|
||||
|
||||
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();
|
||||
navigateBack();
|
||||
} else {
|
||||
try {
|
||||
String err = r.errorBody().string();
|
||||
Log.e("SALE_SAVE", "Error: " + err);
|
||||
Toast.makeText(getContext(), "Error " + r.code() + ": " + err,
|
||||
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() {
|
||||
RefundFragment refundFragment = new RefundFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("saleId", saleId);
|
||||
refundFragment.setArguments(args);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.loadFragment(refundFragment);
|
||||
}
|
||||
|
||||
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() {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null)
|
||||
lf.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
318
android/app/src/main/res/layout/fragment_analytics.xml
Normal file
318
android/app/src/main/res/layout/fragment_analytics.xml
Normal file
@@ -0,0 +1,318 @@
|
||||
<?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="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburgerAnalytics"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Analytics"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRefreshAnalytics"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refresh"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Summary Cards Row 1 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Total Revenue"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalRevenue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Transactions"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalTransactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Summary Cards Row 2 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Avg Transaction"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAvgTransaction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Items Sold"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalItems"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Top Products by Revenue -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Top Products by Revenue"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTopRevenue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Top Products by Quantity -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Top Products by Quantity"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTopQuantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Payment Methods -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Payment Methods"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llPaymentMethods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Employee Performance -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Employee Performance"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llEmployeePerformance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Daily Revenue -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Daily Revenue (Last 7 Days)"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llDailyRevenue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -61,7 +61,7 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Staff Portal"
|
||||
android:text="pet shop"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="13sp"/>
|
||||
|
||||
@@ -246,6 +246,26 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Analytics -->
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerAnalytics"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Analytics"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- PurchaseOrder -->
|
||||
|
||||
<LinearLayout
|
||||
@@ -289,6 +309,27 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Staff Accounts (Admin only) -->
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerStaff"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Staff Accounts"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
222
android/app/src/main/res/layout/fragment_refund.xml
Normal file
222
android/app/src/main/res/layout/fragment_refund.xml
Normal file
@@ -0,0 +1,222 @@
|
||||
<?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="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Process Refund"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Load Sale Card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load Original Sale"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etRefundSaleId"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Enter Sale ID"
|
||||
android:inputType="number"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnLoadSale"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Original Sale Items Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardOriginalItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Original Sale Items"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llOriginalItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Refund Items Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardRefundItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Items to Refund"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llRefundItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRefundTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund Total: $0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Payment Method Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund Payment Method"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerRefundPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<!-- Bottom Buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRefundBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnProcessRefund"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Process Refund"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
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_height="match_parent"
|
||||
android:background="@color/background_grey">
|
||||
@@ -10,17 +11,52 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburgerSale"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sales"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchSale"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by item, employee or date..."
|
||||
android:hint="Search by employee or store..."
|
||||
android:inputType="text"
|
||||
android:drawableStart="@android:drawable/ic_menu_search"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/white"
|
||||
android:padding="12dp"/>
|
||||
android:padding="12dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOpenRefund"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshSale"
|
||||
@@ -37,4 +73,15 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAddSale"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:contentDescription="Add Sale"
|
||||
app:srcCompat="@android:drawable/ic_input_add"
|
||||
app:tint="@color/white"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
221
android/app/src/main/res/layout/fragment_sale_detail.xml
Normal file
221
android/app/src/main/res/layout/fragment_sale_detail.xml
Normal file
@@ -0,0 +1,221 @@
|
||||
<?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="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="New Sale"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRefundSale"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Sale Info Card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleDetailId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<!-- Store -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerSaleStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Customer (optional) -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Customer (Optional)"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerSaleCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Payment Method -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Payment Method"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPaymentMethod"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Items Card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Items"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<!-- Item row -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerSaleProduct"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSaleQuantity"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Qty"
|
||||
android:inputType="number"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAddItem"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Add"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Items list container -->
|
||||
<LinearLayout
|
||||
android:id="@+id/llSaleItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
<!-- Total -->
|
||||
<TextView
|
||||
android:id="@+id/tvSaleDetailTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Total: $0.00"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaleBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveSale"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
82
android/app/src/main/res/layout/fragment_staff.xml
Normal file
82
android/app/src/main/res/layout/fragment_staff.xml
Normal file
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
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_height="match_parent"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburgerStaff"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Staff Accounts"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchStaff"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by name, username or email..."
|
||||
android:inputType="text"
|
||||
android:drawableStart="@android:drawable/ic_menu_search"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/white"
|
||||
android:padding="12dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshStaff"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewStaff"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAddStaff"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:contentDescription="Add Staff"
|
||||
app:srcCompat="@android:drawable/ic_input_add"
|
||||
app:tint="@color/white"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
||||
@@ -1,104 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.cardview.widget.CardView
|
||||
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_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="@color/white">
|
||||
android:layout_margin="8dp"
|
||||
app:cardCornerRadius="8dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleItemName"
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Item Name"/>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRefundBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="REFUND"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp"
|
||||
android:visibility="gone"/>
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
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
|
||||
android:id="@+id/tvSaleEmployee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="13sp"
|
||||
android:textColor="@color/text_light"
|
||||
android:layout_marginTop="2dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleDate"
|
||||
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/tvSalePayment"
|
||||
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
|
||||
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
|
||||
android:id="@+id/tvSaleRefundBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="REFUND"
|
||||
android:textSize="10sp"
|
||||
android:textColor="@color/white"
|
||||
android:background="@color/accent_coral"
|
||||
android:paddingStart="6dp"
|
||||
android:paddingEnd="6dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleEmployee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp"
|
||||
android:text="By: Employee"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleId"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="#888888"
|
||||
android:textSize="12sp"
|
||||
android:text="ID: 0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleDate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="#888888"
|
||||
android:textSize="12sp"
|
||||
android:text="Date"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSalePayment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#888888"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Cash"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:text="$0.00"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
1
desktop/.idea/misc.xml
generated
1
desktop/.idea/misc.xml
generated
@@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="MavenProjectsManager">
|
||||
|
||||
Reference in New Issue
Block a user