Merge branch 'morefiles'

This commit is contained in:
2026-04-07 21:43:20 -06:00
111 changed files with 5915 additions and 2015 deletions

View File

@@ -1,79 +1,128 @@
package com.example.petstoremobile.adapters;
import android.graphics.Color;
import android.view.*;
import android.widget.TextView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemAdoptionBinding;
import com.example.petstoremobile.dtos.AdoptionDTO;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.SelectionHelper;
import java.util.List;
public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.AdoptionViewHolder> {
public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.AdoptionViewHolder> implements BulkDeleteHandler.SelectableAdapter {
private List<AdoptionDTO> adoptionList;
private OnAdoptionClickListener listener;
private final SelectionHelper selectionHelper;
public interface OnAdoptionClickListener {
void onAdoptionClick(int position);
void onSelectionChanged(int count);
}
public AdoptionAdapter(List<AdoptionDTO> adoptionList, OnAdoptionClickListener listener) {
this.adoptionList = adoptionList;
this.listener = listener;
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
@Override
public void onSelectionChanged(int count) {
listener.onSelectionChanged(count);
}
@Override
public void onSelectionModeToggle(boolean selectionMode) {
notifyDataSetChanged();
}
});
}
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
public static class AdoptionViewHolder extends RecyclerView.ViewHolder {
TextView tvCustomerName, tvPetName, tvDate, tvFee, tvStatus;
final ItemAdoptionBinding binding;
public AdoptionViewHolder(@NonNull View v) {
super(v);
tvCustomerName = v.findViewById(R.id.tvAdoptionCustomerName);
tvPetName = v.findViewById(R.id.tvAdoptionPetName);
tvDate = v.findViewById(R.id.tvAdoptionDate);
tvFee = v.findViewById(R.id.tvAdoptionFee);
tvStatus = v.findViewById(R.id.tvAdoptionStatus);
public AdoptionViewHolder(@NonNull ItemAdoptionBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public AdoptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_adoption, parent, false);
return new AdoptionViewHolder(v);
ItemAdoptionBinding binding = ItemAdoptionBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new AdoptionViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull AdoptionViewHolder holder, int position) {
AdoptionDTO a = adoptionList.get(position);
ItemAdoptionBinding binding = holder.binding;
binding.tvAdoptionCustomerName.setText(a.getCustomerName() != null ? a.getCustomerName() : "");
binding.tvAdoptionPetName.setText("Pet: " + (a.getPetName() != null ? a.getPetName() : ""));
binding.tvAdoptionStaffName.setText("Staff: " + (a.getEmployeeName() != null ? a.getEmployeeName() : "N/A"));
binding.tvAdoptionDate.setText("Date: " + (a.getAdoptionDate() != null ? a.getAdoptionDate() : ""));
binding.tvAdoptionFee.setText(a.getAdoptionFee() != null ? "$" + a.getAdoptionFee() : "");
holder.tvCustomerName.setText(a.getCustomerName() != null ? a.getCustomerName() : "");
holder.tvPetName.setText("Pet: " + (a.getPetName() != null ? a.getPetName() : ""));
holder.tvDate.setText("Date: " + (a.getAdoptionDate() != null ? a.getAdoptionDate() : ""));
holder.tvFee.setText(a.getAdoptionFee() != null ? "$" + a.getAdoptionFee() : "");
String status = a.getAdoptionStatus() != null ? a.getAdoptionStatus() : "";
holder.tvStatus.setText(status);
binding.tvAdoptionStatus.setText(status);
switch (status) {
case "Completed":
holder.tvStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
binding.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
break;
case "Pending":
holder.tvStatus.setBackgroundColor(Color.parseColor("#FF9800"));
binding.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#FF9800"));
break;
case "Rejected":
holder.tvStatus.setBackgroundColor(Color.parseColor("#F44336"));
case "Cancelled":
binding.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#F44336"));
break;
default:
holder.tvStatus.setBackgroundColor(Color.parseColor("#9E9E9E"));
binding.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#9E9E9E"));
break;
}
holder.itemView.setOnClickListener(v -> listener.onAdoptionClick(position));
String key = String.valueOf(a.getAdoptionId());
// Bulk delete selection mode
if (selectionHelper.isInSelectionMode()) {
binding.cbSelectAdoption.setVisibility(View.VISIBLE);
binding.cbSelectAdoption.setChecked(selectionHelper.isSelected(key));
} else {
binding.cbSelectAdoption.setVisibility(View.GONE);
binding.cbSelectAdoption.setChecked(false);
}
holder.itemView.setOnClickListener(v -> {
if (selectionHelper.isInSelectionMode()) {
selectionHelper.toggleSelection(key);
notifyItemChanged(position);
} else {
listener.onAdoptionClick(position);
}
});
holder.itemView.setOnLongClickListener(v -> {
if (!selectionHelper.isInSelectionMode()) {
selectionHelper.startSelection(key);
}
return true;
});
}
@Override
public int getItemCount() { return adoptionList.size(); }
}
}

View File

@@ -4,77 +4,124 @@ import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemAppointmentBinding;
import com.example.petstoremobile.dtos.AppointmentDTO;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.SelectionHelper;
import java.util.List;
public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.AppointmentViewHolder> {
public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.AppointmentViewHolder> implements BulkDeleteHandler.SelectableAdapter {
private List<AppointmentDTO> appointmentList;
private OnAppointmentClickListener appointmentClickListener;
private final SelectionHelper selectionHelper;
public interface OnAppointmentClickListener {
void onAppointmentClick(int position);
void onSelectionChanged(int count);
}
public AppointmentAdapter(List<AppointmentDTO> appointmentList,
OnAppointmentClickListener appointmentClickListener) {
this.appointmentList = appointmentList;
this.appointmentClickListener = appointmentClickListener;
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
@Override
public void onSelectionChanged(int count) {
appointmentClickListener.onSelectionChanged(count);
}
@Override
public void onSelectionModeToggle(boolean selectionMode) {
notifyDataSetChanged();
}
});
}
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
TextView tvCustomerName, tvPetName, tvServiceType, tvDateTime, tvAppointmentStatus;
private final ItemAppointmentBinding binding;
public AppointmentViewHolder(@NonNull View v) {
super(v);
tvCustomerName = v.findViewById(R.id.tvCustomerName);
tvPetName = v.findViewById(R.id.tvApptPetName);
tvServiceType = v.findViewById(R.id.tvServiceType);
tvDateTime = v.findViewById(R.id.tvDateTime);
tvAppointmentStatus = v.findViewById(R.id.tvAppointmentStatus);
public AppointmentViewHolder(@NonNull ItemAppointmentBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_appointment, parent, false);
return new AppointmentViewHolder(v);
ItemAppointmentBinding binding = ItemAppointmentBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new AppointmentViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int position) {
AppointmentDTO a = appointmentList.get(position);
ItemAppointmentBinding binding = holder.binding;
holder.tvCustomerName.setText(a.getCustomerName() != null ? a.getCustomerName() : "");
holder.tvPetName.setText("Pet: " + (a.getPetName() != null ? a.getPetName() : ""));
holder.tvServiceType.setText(a.getServiceType() != null ? a.getServiceType() : "");
holder.tvDateTime.setText((a.getAppointmentDate() != null ? a.getAppointmentDate() : "") +
binding.tvCustomerName.setText(a.getCustomerName() != null ? a.getCustomerName() : "");
binding.tvApptPetName.setText("Pet: " + (a.getPetName() != null ? a.getPetName() : ""));
binding.tvServiceType.setText(a.getServiceType() != null ? a.getServiceType() : "");
binding.tvStaffName.setText("Staff: " + (a.getEmployeeName() != null ? a.getEmployeeName() : "Unassigned"));
binding.tvDateTime.setText((a.getAppointmentDate() != null ? a.getAppointmentDate() : "") +
" at " + (a.getAppointmentTime() != null ? a.getAppointmentTime() : ""));
String status = a.getStatus() != null ? a.getStatus() : "";
holder.tvAppointmentStatus.setText(status);
binding.tvAppointmentStatus.setText(status);
switch (status.toUpperCase()) {
case "BOOKED":
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#2196F3")); // blue
binding.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#2196F3")); // blue
break;
case "COMPLETED":
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#4CAF50")); // green
binding.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#4CAF50")); // green
break;
case "CANCELLED":
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#F44336")); // red
binding.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#F44336")); // red
break;
default:
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#9E9E9E")); // gray
binding.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#9E9E9E")); // gray
break;
}
holder.itemView.setOnClickListener(v -> appointmentClickListener.onAppointmentClick(position));
String key = String.valueOf(a.getAppointmentId());
// Bulk delete selection mode
if (selectionHelper.isInSelectionMode()) {
binding.cbSelectAppointment.setVisibility(View.VISIBLE);
binding.cbSelectAppointment.setChecked(selectionHelper.isSelected(key));
} else {
binding.cbSelectAppointment.setVisibility(View.GONE);
binding.cbSelectAppointment.setChecked(false);
}
holder.itemView.setOnClickListener(v -> {
if (selectionHelper.isInSelectionMode()) {
selectionHelper.toggleSelection(key);
notifyItemChanged(position);
} else {
appointmentClickListener.onAppointmentClick(position);
}
});
holder.itemView.setOnLongClickListener(v -> {
if (!selectionHelper.isInSelectionMode()) {
selectionHelper.startSelection(key);
}
return true;
});
}
@Override

View File

@@ -1,14 +1,12 @@
package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemChatBinding;
import com.example.petstoremobile.models.Chat;
import java.util.List;
@@ -30,15 +28,15 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder
@NonNull
@Override
public ChatViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_chat, parent, false);
return new ChatViewHolder(view);
ItemChatBinding binding = ItemChatBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ChatViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ChatViewHolder holder, int position) {
Chat chat = chatList.get(position);
holder.tvCustomerName.setText(chat.getCustomerName());
holder.tvLastMessage.setText(chat.getLastMessage());
holder.binding.tvCustomerName.setText(chat.getCustomerName());
holder.binding.tvLastMessage.setText(chat.getLastMessage());
holder.itemView.setOnClickListener(v -> listener.onChatClick(chat));
}
@@ -48,12 +46,11 @@ public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.ChatViewHolder
}
public static class ChatViewHolder extends RecyclerView.ViewHolder {
TextView tvCustomerName, tvLastMessage;
final ItemChatBinding binding;
public ChatViewHolder(@NonNull View itemView) {
super(itemView);
tvCustomerName = itemView.findViewById(R.id.tvCustomerName);
tvLastMessage = itemView.findViewById(R.id.tvLastMessage);
public ChatViewHolder(@NonNull ItemChatBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
}

View File

@@ -0,0 +1,74 @@
package com.example.petstoremobile.adapters;
import android.graphics.Color;
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.dtos.EmployeeDTO;
import java.util.List;
public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.EmployeeViewHolder> {
private List<EmployeeDTO> list;
private OnEmployeeClickListener listener;
public interface OnEmployeeClickListener {
void onEmployeeClick(int position);
}
public EmployeeAdapter(List<EmployeeDTO> list, OnEmployeeClickListener listener) {
this.list = list;
this.listener = listener;
}
public static class EmployeeViewHolder extends RecyclerView.ViewHolder {
TextView tvFullName, tvUsername, tvEmail, tvPhone, tvRole, tvStatus;
public EmployeeViewHolder(@NonNull View v) {
super(v);
tvFullName = v.findViewById(R.id.tvEmployeeFullName);
tvUsername = v.findViewById(R.id.tvEmployeeUsername);
tvEmail = v.findViewById(R.id.tvEmployeeEmail);
tvPhone = v.findViewById(R.id.tvEmployeePhone);
tvRole = v.findViewById(R.id.tvEmployeeRole);
tvStatus = v.findViewById(R.id.tvEmployeeStatus);
}
}
@NonNull
@Override
public EmployeeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_employee, parent, false);
return new EmployeeViewHolder(v);
}
@Override
public void onBindViewHolder(@NonNull EmployeeViewHolder holder, int position) {
EmployeeDTO e = list.get(position);
holder.tvFullName.setText(e.getFullName() != null ? e.getFullName() : "");
holder.tvUsername.setText("@" + (e.getUsername() != null ? e.getUsername() : ""));
holder.tvEmail.setText(e.getEmail() != null ? e.getEmail() : "");
holder.tvPhone.setText(e.getPhone() != null ? e.getPhone() : "");
// Role badge
String role = e.getRole() != null ? e.getRole() : "";
holder.tvRole.setText(role);
holder.tvRole.setBackgroundColor(
"ADMIN".equals(role) ? Color.parseColor("#1a759f") : Color.parseColor("#577590"));
// Status badge
boolean active = Boolean.TRUE.equals(e.getActive());
holder.tvStatus.setText(active ? "Active" : "Inactive");
holder.tvStatus.setBackgroundColor(
active ? Color.parseColor("#4CAF50") : Color.parseColor("#F44336"));
holder.itemView.setOnClickListener(v -> listener.onEmployeeClick(position));
}
@Override
public int getItemCount() { return list.size(); }
}

View File

@@ -4,24 +4,22 @@ import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemInventoryBinding;
import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.SelectionHelper;
import java.util.ArrayList;
import java.util.List;
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> {
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> implements BulkDeleteHandler.SelectableAdapter {
private final List<InventoryDTO> inventoryList;
private final OnInventoryClickListener clickListener;
private final List<Long> selectedIds = new ArrayList<>();
private boolean selectionMode = false;
private final SelectionHelper selectionHelper;
public interface OnInventoryClickListener {
void onInventoryClick(int position);
@@ -32,119 +30,97 @@ public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.Inve
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
this.inventoryList = inventoryList;
this.clickListener = clickListener;
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
@Override
public void onSelectionChanged(int count) {
clickListener.onSelectionChanged(count);
}
@Override
public void onSelectionModeToggle(boolean selectionMode) {
notifyDataSetChanged();
}
});
}
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
public static class InventoryViewHolder extends RecyclerView.ViewHolder {
// Matches desktop table columns: Inventory ID, Product ID, Product Name,
// Quantity
TextView tvInventoryId, tvProdId, tvProductName, tvQuantity;
CheckBox checkBox;
final ItemInventoryBinding binding;
public InventoryViewHolder(@NonNull View v) {
super(v);
tvInventoryId = v.findViewById(R.id.tvInventoryId);
tvProdId = v.findViewById(R.id.tvProdId);
tvProductName = v.findViewById(R.id.tvProductName);
tvQuantity = v.findViewById(R.id.tvQuantity);
checkBox = v.findViewById(R.id.cbSelectInventory);
public InventoryViewHolder(@NonNull ItemInventoryBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_inventory, parent, false);
return new InventoryViewHolder(v);
ItemInventoryBinding binding = ItemInventoryBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new InventoryViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int position) {
InventoryDTO inv = inventoryList.get(position);
// Column: Inventory ID
String invIdStr = inv.getInventoryId() != null ? String.valueOf(inv.getInventoryId()) : "";
holder.tvInventoryId.setText("Inv ID: " + invIdStr);
// Column: Product ID
String prodIdStr = inv.getProdId() != null ? String.valueOf(inv.getProdId()) : "";
holder.tvProdId.setText("Prod ID: " + prodIdStr);
ItemInventoryBinding binding = holder.binding;
// Column: Product Name
holder.tvProductName.setText(inv.getProductName() != null ? inv.getProductName() : "");
binding.tvProductName.setText(inv.getProductName() != null ? inv.getProductName() : "");
// Column: Store Name
binding.tvInventoryStore.setText("Store: " + (inv.getStoreName() != null ? inv.getStoreName() : ""));
// Column: Quantity
int qty = inv.getQuantity() != null ? inv.getQuantity() : 0;
holder.tvQuantity.setText(String.valueOf(qty));
binding.tvQuantity.setText("Stock: " + qty);
// Low stock = red, normal = green (like desktop reorder concept)
if (qty <= 5) {
holder.tvQuantity.setTextColor(Color.parseColor("#F44336"));
binding.tvQuantity.setTextColor(Color.parseColor("#F44336"));
} else {
holder.tvQuantity.setTextColor(Color.parseColor("#4CAF50"));
binding.tvQuantity.setTextColor(Color.parseColor("#4CAF50"));
}
String key = String.valueOf(inv.getInventoryId());
// Bulk delete selection mode
if (selectionMode) {
holder.checkBox.setVisibility(View.VISIBLE);
holder.checkBox.setChecked(inv.getInventoryId() != null
&& selectedIds.contains(inv.getInventoryId()));
if (selectionHelper.isInSelectionMode()) {
binding.cbSelectInventory.setVisibility(View.VISIBLE);
binding.cbSelectInventory.setChecked(selectionHelper.isSelected(key));
} else {
holder.checkBox.setVisibility(View.GONE);
holder.checkBox.setChecked(false);
binding.cbSelectInventory.setVisibility(View.GONE);
binding.cbSelectInventory.setChecked(false);
}
holder.itemView.setOnClickListener(v -> {
if (selectionMode) {
toggleSelection(inv.getInventoryId(), holder.checkBox);
if (selectionHelper.isInSelectionMode()) {
selectionHelper.toggleSelection(key);
notifyItemChanged(position);
} else {
clickListener.onInventoryClick(holder.getAdapterPosition());
}
});
holder.itemView.setOnLongClickListener(v -> {
if (!selectionMode) {
selectionMode = true;
toggleSelection(inv.getInventoryId(), holder.checkBox);
notifyDataSetChanged();
if (!selectionHelper.isInSelectionMode()) {
selectionHelper.startSelection(key);
}
return true;
});
}
private void toggleSelection(Long id, CheckBox checkBox) {
if (id == null)
return;
if (selectedIds.contains(id)) {
selectedIds.remove(id);
checkBox.setChecked(false);
} else {
selectedIds.add(id);
checkBox.setChecked(true);
}
clickListener.onSelectionChanged(selectedIds.size());
if (selectedIds.isEmpty()) {
selectionMode = false;
notifyDataSetChanged();
}
}
public List<Long> getSelectedIds() {
return new ArrayList<>(selectedIds);
}
public void clearSelection() {
selectedIds.clear();
selectionMode = false;
notifyDataSetChanged();
}
public boolean isInSelectionMode() {
return selectionMode;
}
@Override
public int getItemCount() {
return inventoryList.size();
}
}
}

View File

@@ -12,6 +12,8 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.LazyHeaders;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemMessageReceivedBinding;
import com.example.petstoremobile.databinding.ItemMessageSentBinding;
import com.example.petstoremobile.models.Message;
import java.util.List;
@@ -51,11 +53,11 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inf = LayoutInflater.from(parent.getContext());
if (viewType == TYPE_SENT) {
View v = inf.inflate(R.layout.item_message_sent, parent, false);
return new SentHolder(v);
ItemMessageSentBinding binding = ItemMessageSentBinding.inflate(inf, parent, false);
return new SentHolder(binding);
} else {
View v = inf.inflate(R.layout.item_message_received, parent, false);
return new ReceivedHolder(v);
ItemMessageReceivedBinding binding = ItemMessageReceivedBinding.inflate(inf, parent, false);
return new ReceivedHolder(binding);
}
}
@@ -69,32 +71,26 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
@Override public int getItemCount() { return messages.size(); }
static class SentHolder extends RecyclerView.ViewHolder {
TextView tvMessage, tvAttachmentName;
ImageView ivAttachment;
SentHolder(View v) {
super(v);
tvMessage = v.findViewById(R.id.tvMessageContent);
tvAttachmentName = v.findViewById(R.id.tvAttachmentName);
ivAttachment = v.findViewById(R.id.ivAttachment);
final ItemMessageSentBinding binding;
SentHolder(ItemMessageSentBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(Message m, String token) {
tvMessage.setText(m.getContent());
displayAttachment(m, ivAttachment, tvAttachmentName, token);
binding.tvMessageContent.setText(m.getContent());
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token);
}
}
static class ReceivedHolder extends RecyclerView.ViewHolder {
TextView tvMessage, tvAttachmentName;
ImageView ivAttachment;
ReceivedHolder(View v) {
super(v);
tvMessage = v.findViewById(R.id.tvMessageContent);
tvAttachmentName = v.findViewById(R.id.tvAttachmentName);
ivAttachment = v.findViewById(R.id.ivAttachment);
final ItemMessageReceivedBinding binding;
ReceivedHolder(ItemMessageReceivedBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
void bind(Message m, String token) {
tvMessage.setText(m.getContent());
displayAttachment(m, ivAttachment, tvAttachmentName, token);
binding.tvMessageContent.setText(m.getContent());
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token);
}
}

View File

@@ -4,33 +4,48 @@ import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.api.PetApi;
import com.example.petstoremobile.databinding.ItemPetBinding;
import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.GlideUtils;
import com.example.petstoremobile.utils.SelectionHelper;
import java.util.List;
public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> implements BulkDeleteHandler.SelectableAdapter {
private List<PetDTO> petList;
private OnPetClickListener petClickListener;
private String baseUrl;
private String token;
private final SelectionHelper selectionHelper;
// Interface for pet click on recycler view
public interface OnPetClickListener {
void onPetClick(int position);
void onSelectionChanged(int selectedCount);
}
//Constructor
public PetAdapter(List<PetDTO> petList, OnPetClickListener petClickListener) {
this.petList = petList;
this.petClickListener = petClickListener;
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
@Override
public void onSelectionChanged(int count) {
petClickListener.onSelectionChanged(count);
}
@Override
public void onSelectionModeToggle(boolean selectionMode) {
notifyDataSetChanged();
}
});
}
public void setBaseUrl(String baseUrl) {
@@ -41,19 +56,23 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
this.token = token;
}
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
// Get the controls of each row in recycler view
public static class PetViewHolder extends RecyclerView.ViewHolder {
TextView tvPetName, tvPetSpeciesBreed, tvPetAge, tvPetPrice, tvPetStatus;
ImageView ivPetProfile;
private final ItemPetBinding binding;
public PetViewHolder(@NonNull View v) {
super(v);
tvPetName = v.findViewById(R.id.tvPetName);
tvPetSpeciesBreed = v.findViewById(R.id.tvPetSpeciesBreed);
tvPetAge = v.findViewById(R.id.tvPetAge);
tvPetPrice = v.findViewById(R.id.tvPetPrice);
tvPetStatus = v.findViewById(R.id.tvPetStatus);
ivPetProfile = v.findViewById(R.id.ivPetProfile);
public PetViewHolder(@NonNull ItemPetBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@@ -61,49 +80,75 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
@NonNull
@Override
public PetViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_pet, parent, false);
return new PetViewHolder(v);
ItemPetBinding binding = ItemPetBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new PetViewHolder(binding);
}
//populate the row with pet data
@Override
public void onBindViewHolder(@NonNull PetViewHolder holder, int position) {
PetDTO pet = petList.get(position);
ItemPetBinding binding = holder.binding;
holder.tvPetName.setText(pet.getPetName());
holder.tvPetSpeciesBreed.setText(pet.getPetSpecies() + " - " + pet.getPetBreed());
holder.tvPetAge.setText("Age: " + pet.getPetAge() + " yr(s)");
binding.tvPetName.setText(pet.getPetName());
binding.tvPetSpeciesBreed.setText(pet.getPetSpecies() + " - " + pet.getPetBreed());
binding.tvPetAge.setText("Age: " + pet.getPetAge() + " yr(s)");
Double price = pet.getPetPrice();
if (price != null) {
holder.tvPetPrice.setText("$" + String.format("%.2f", price));
binding.tvPetPrice.setText("$" + String.format("%.2f", price));
} else {
holder.tvPetPrice.setText("$0.00");
binding.tvPetPrice.setText("$0.00");
}
holder.tvPetStatus.setText(pet.getPetStatus());
binding.tvPetStatus.setText(pet.getPetStatus());
//Set the status color depending on availability. If available, green, otherwise red
if (pet.getPetStatus() != null && pet.getPetStatus().equals("Available")) {
holder.tvPetStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
binding.tvPetStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
} else {
holder.tvPetStatus.setBackgroundColor(Color.parseColor("#F44336"));
binding.tvPetStatus.setBackgroundColor(Color.parseColor("#F44336"));
}
// Load pet image using Glide
if (baseUrl != null) {
String imageUrl = baseUrl + String.format(PetApi.PET_IMAGE_PATH, pet.getPetId());
GlideUtils.loadImageWithTokenCircle(holder.itemView.getContext(), holder.ivPetProfile, imageUrl, token, R.drawable.placeholder);
GlideUtils.loadImageWithTokenCircle(holder.itemView.getContext(), binding.ivPetProfile, imageUrl, token, R.drawable.placeholder);
} else {
holder.ivPetProfile.setImageResource(R.drawable.placeholder);
binding.ivPetProfile.setImageResource(R.drawable.placeholder);
}
String key = String.valueOf(pet.getPetId());
// Bulk delete selection mode
if (selectionHelper.isInSelectionMode()) {
binding.cbSelectPet.setVisibility(View.VISIBLE);
binding.cbSelectPet.setChecked(selectionHelper.isSelected(key));
} else {
binding.cbSelectPet.setVisibility(View.GONE);
binding.cbSelectPet.setChecked(false);
}
//when a row is clicked, open the detail view
holder.itemView.setOnClickListener(v -> petClickListener.onPetClick(position));
holder.itemView.setOnClickListener(v -> {
if (selectionHelper.isInSelectionMode()) {
selectionHelper.toggleSelection(key);
notifyItemChanged(position);
} else {
petClickListener.onPetClick(position);
}
});
holder.itemView.setOnLongClickListener(v -> {
if (!selectionHelper.isInSelectionMode()) {
selectionHelper.startSelection(key);
}
return true;
});
}
@Override
public int getItemCount() {
return petList.size();
}
}
}

View File

@@ -1,13 +1,12 @@
package com.example.petstoremobile.adapters;
import android.view.*;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.api.ProductApi;
import com.example.petstoremobile.databinding.ItemProductBinding;
import com.example.petstoremobile.dtos.ProductDTO;
import com.example.petstoremobile.utils.GlideUtils;
import java.util.List;
@@ -37,41 +36,37 @@ public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductV
}
public static class ProductViewHolder extends RecyclerView.ViewHolder {
TextView tvName, tvCategory, tvDesc, tvPrice;
ImageView ivProductImage;
final ItemProductBinding binding;
public ProductViewHolder(@NonNull View v) {
super(v);
tvName = v.findViewById(R.id.tvProductName);
tvCategory = v.findViewById(R.id.tvProductCategory);
tvDesc = v.findViewById(R.id.tvProductDesc);
tvPrice = v.findViewById(R.id.tvProductPrice);
ivProductImage = v.findViewById(R.id.ivProductImage);
public ProductViewHolder(@NonNull ItemProductBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_product, parent, false);
return new ProductViewHolder(v);
ItemProductBinding binding = ItemProductBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ProductViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull ProductViewHolder holder, int position) {
ProductDTO p = productList.get(position);
holder.tvName.setText(p.getProdName() != null ? p.getProdName() : "");
holder.tvCategory.setText("Category: " + (p.getCategoryName() != null ? p.getCategoryName() : ""));
holder.tvDesc.setText(p.getProdDesc() != null ? p.getProdDesc() : "");
holder.tvPrice.setText(p.getProdPrice() != null ? "$" + p.getProdPrice() : "");
ItemProductBinding binding = holder.binding;
binding.tvProductName.setText(p.getProdName() != null ? p.getProdName() : "");
binding.tvProductCategory.setText("Category: " + (p.getCategoryName() != null ? p.getCategoryName() : ""));
binding.tvProductDesc.setText(p.getProdDesc() != null ? p.getProdDesc() : "");
binding.tvProductPrice.setText(p.getProdPrice() != null ? "$" + p.getProdPrice() : "");
// Load product image using Glide
if (baseUrl != null) {
String imageUrl = baseUrl + String.format(ProductApi.PRODUCT_IMAGE_PATH, p.getProdId());
GlideUtils.loadImageWithTokenCircle(holder.itemView.getContext(), holder.ivProductImage, imageUrl, token, R.drawable.placeholder);
GlideUtils.loadImageWithTokenCircle(holder.itemView.getContext(), binding.ivProductImage, imageUrl, token, R.drawable.placeholder);
} else {
holder.ivProductImage.setImageResource(R.drawable.placeholder);
binding.ivProductImage.setImageResource(R.drawable.placeholder);
}
holder.itemView.setOnClickListener(v -> listener.onProductClick(position));

View File

@@ -1,55 +1,109 @@
package com.example.petstoremobile.adapters;
import android.view.*;
import android.widget.TextView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemProductSupplierBinding;
import com.example.petstoremobile.dtos.ProductSupplierDTO;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.SelectionHelper;
import java.util.List;
public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplierAdapter.PSViewHolder> {
public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplierAdapter.PSViewHolder> implements BulkDeleteHandler.SelectableAdapter {
private List<ProductSupplierDTO> list;
private OnProductSupplierClickListener listener;
private final List<ProductSupplierDTO> list;
private final OnProductSupplierClickListener listener;
private final SelectionHelper selectionHelper;
public interface OnProductSupplierClickListener {
void onProductSupplierClick(int position);
void onSelectionChanged(int count);
}
public ProductSupplierAdapter(List<ProductSupplierDTO> list, OnProductSupplierClickListener listener) {
this.list = list;
this.listener = listener;
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
@Override
public void onSelectionChanged(int count) {
listener.onSelectionChanged(count);
}
@Override
public void onSelectionModeToggle(boolean selectionMode) {
notifyDataSetChanged();
}
});
}
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
public static class PSViewHolder extends RecyclerView.ViewHolder {
TextView tvProductName, tvSupplierName, tvCost;
final ItemProductSupplierBinding binding;
public PSViewHolder(@NonNull View v) {
super(v);
tvProductName = v.findViewById(R.id.tvPSProductName);
tvSupplierName = v.findViewById(R.id.tvPSSupplierName);
tvCost = v.findViewById(R.id.tvPSCost);
public PSViewHolder(@NonNull ItemProductSupplierBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public PSViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_product_supplier, parent, false);
return new PSViewHolder(v);
ItemProductSupplierBinding binding = ItemProductSupplierBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new PSViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull PSViewHolder holder, int position) {
ProductSupplierDTO ps = list.get(position);
holder.tvProductName.setText(ps.getProductName() != null ? ps.getProductName() : "");
holder.tvSupplierName.setText("Supplier: " + (ps.getSupplierName() != null ? ps.getSupplierName() : ""));
holder.tvCost.setText(ps.getCost() != null ? "Cost: $" + ps.getCost() : "");
holder.itemView.setOnClickListener(v -> listener.onProductSupplierClick(position));
ItemProductSupplierBinding binding = holder.binding;
binding.tvPSProductName.setText(ps.getProductName() != null ? ps.getProductName() : "");
binding.tvPSSupplierName.setText("Supplier: " + (ps.getSupplierName() != null ? ps.getSupplierName() : ""));
binding.tvPSCost.setText(ps.getCost() != null ? "Cost: $" + ps.getCost() : "");
String key = ps.getProductId() + "-" + ps.getSupplierId();
// Bulk delete selection mode
if (selectionHelper.isInSelectionMode()) {
binding.cbSelectProductSupplier.setVisibility(View.VISIBLE);
binding.cbSelectProductSupplier.setChecked(selectionHelper.isSelected(key));
} else {
binding.cbSelectProductSupplier.setVisibility(View.GONE);
binding.cbSelectProductSupplier.setChecked(false);
}
holder.itemView.setOnClickListener(v -> {
if (selectionHelper.isInSelectionMode()) {
selectionHelper.toggleSelection(key);
notifyItemChanged(position);
} else {
listener.onProductSupplierClick(position);
}
});
holder.itemView.setOnLongClickListener(v -> {
if (!selectionHelper.isInSelectionMode()) {
selectionHelper.startSelection(key);
}
return true;
});
}
@Override
public int getItemCount() { return list.size(); }
}
}

View File

@@ -1,11 +1,11 @@
package com.example.petstoremobile.adapters;
import android.graphics.Color;
import android.view.*;
import android.widget.TextView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemPurchaseOrderBinding;
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
import java.util.List;
@@ -24,47 +24,49 @@ public class PurchaseOrderAdapter extends RecyclerView.Adapter<PurchaseOrderAdap
}
public static class POViewHolder extends RecyclerView.ViewHolder {
TextView tvId, tvSupplier, tvDate, tvStatus;
final ItemPurchaseOrderBinding binding;
public POViewHolder(@NonNull View v) {
super(v);
tvId = v.findViewById(R.id.tvPOId);
tvSupplier = v.findViewById(R.id.tvPOSupplier);
tvDate = v.findViewById(R.id.tvPODate);
tvStatus = v.findViewById(R.id.tvPOStatus);
public POViewHolder(@NonNull ItemPurchaseOrderBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public POViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_purchase_order, parent, false);
return new POViewHolder(v);
ItemPurchaseOrderBinding binding = ItemPurchaseOrderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new POViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull POViewHolder holder, int position) {
PurchaseOrderDTO po = list.get(position);
holder.tvId.setText("PO #" + (po.getPurchaseOrderId() != null ? po.getPurchaseOrderId() : ""));
holder.tvSupplier.setText("Supplier: " + (po.getSupplierName() != null ? po.getSupplierName() : ""));
holder.tvDate.setText("Date: " + (po.getOrderDate() != null ? po.getOrderDate() : ""));
ItemPurchaseOrderBinding binding = holder.binding;
binding.tvPOId.setText("PO #" + (po.getPurchaseOrderId() != null ? po.getPurchaseOrderId() : ""));
binding.tvPOSupplier.setText("Supplier: " + (po.getSupplierName() != null ? po.getSupplierName() : ""));
binding.tvPOStore.setText("Store: " + (po.getStoreName() != null ? po.getStoreName() : ""));
binding.tvPODate.setText("Date: " + (po.getOrderDate() != null ? po.getOrderDate() : ""));
String status = po.getStatus() != null ? po.getStatus() : "";
holder.tvStatus.setText(status);
binding.tvPOStatus.setText(status);
switch (status) {
case "Completed":
holder.tvStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
switch (status.toUpperCase()) {
case "RECEIVED":
binding.tvPOStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
break;
case "Pending":
holder.tvStatus.setBackgroundColor(Color.parseColor("#FF9800"));
case "PLACED":
binding.tvPOStatus.setBackgroundColor(Color.parseColor("#2196F3"));
break;
case "Cancelled":
holder.tvStatus.setBackgroundColor(Color.parseColor("#F44336"));
case "PENDING":
binding.tvPOStatus.setBackgroundColor(Color.parseColor("#FF9800"));
break;
case "CANCELLED":
binding.tvPOStatus.setBackgroundColor(Color.parseColor("#F44336"));
break;
default:
holder.tvStatus.setBackgroundColor(Color.parseColor("#9E9E9E"));
binding.tvPOStatus.setBackgroundColor(Color.parseColor("#9E9E9E"));
break;
}

View File

@@ -4,71 +4,63 @@ import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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.databinding.ItemSaleBinding;
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;
final ItemSaleBinding binding;
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);
tvTotal = v.findViewById(R.id.tvSaleTotal);
tvPaymentMethod = v.findViewById(R.id.tvSalePayment);
tvRefundBadge = v.findViewById(R.id.tvRefundBadge);
public SaleViewHolder(@NonNull ItemSaleBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@NonNull
@Override
public SaleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_sale, parent, false);
return new SaleViewHolder(v);
ItemSaleBinding binding = ItemSaleBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new SaleViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull SaleViewHolder holder, int position) {
Sale sale = saleList.get(position);
SaleDTO s = saleList.get(position);
ItemSaleBinding binding = holder.binding;
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());
binding.tvSaleId.setText("Sale #" + (s.getSaleId() != null ? s.getSaleId() : ""));
binding.tvSaleEmployee.setText("By: " + (s.getEmployeeName() != null ? s.getEmployeeName() : ""));
binding.tvSaleDate.setText(s.getSaleDate() != null ? s.getSaleDate().substring(0, Math.min(10, s.getSaleDate().length())) : "");
binding.tvSalePayment.setText(s.getPaymentMethod() != null ? s.getPaymentMethod() : "");
binding.tvSaleTotal.setText(s.getTotalAmount() != null ? "$" + s.getTotalAmount() : "");
// Show refund badge if it's a refund
if (sale.isRefund()) {
holder.tvRefundBadge.setVisibility(View.VISIBLE);
holder.tvRefundBadge.setBackgroundColor(Color.parseColor("#F44336"));
holder.tvTotal.setTextColor(Color.parseColor("#F44336"));
if (Boolean.TRUE.equals(s.getIsRefund())) {
binding.tvSaleRefundBadge.setVisibility(View.VISIBLE);
binding.tvSaleRefundBadge.setBackgroundColor(Color.parseColor("#F44336"));
binding.tvSaleTotal.setTextColor(Color.parseColor("#F44336"));
} else {
holder.tvRefundBadge.setVisibility(View.GONE);
holder.tvTotal.setTextColor(Color.parseColor("#4CAF50"));
binding.tvSaleRefundBadge.setVisibility(View.GONE);
binding.tvSaleTotal.setTextColor(Color.parseColor("#4CAF50"));
}
holder.itemView.setOnClickListener(v -> saleClickListener.onSaleClick(position));
holder.itemView.setOnClickListener(v -> listener.onSaleClick(position));
}
@Override

View File

@@ -3,39 +3,69 @@ package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemServiceBinding;
import com.example.petstoremobile.dtos.ServiceDTO;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.SelectionHelper;
import java.util.List;
public class ServiceAdapter extends RecyclerView.Adapter<ServiceAdapter.ServiceViewHolder> {
/**
* Adapter class for displaying a list of services in a RecyclerView.
*/
public class ServiceAdapter extends RecyclerView.Adapter<ServiceAdapter.ServiceViewHolder> implements BulkDeleteHandler.SelectableAdapter {
private List<ServiceDTO> serviceList;
private OnServiceClickListener serviceClickListener;
private final List<ServiceDTO> serviceList;
private final OnServiceClickListener clickListener;
private final SelectionHelper selectionHelper;
// Interface for service click on recycler view
/**
* Interface for handling clicks on service items.
*/
public interface OnServiceClickListener {
void onServiceClick(int position);
void onSelectionChanged(int count);
}
//Constructor
public ServiceAdapter(List<ServiceDTO> serviceList, OnServiceClickListener serviceClickListener) {
this.serviceList = serviceList;
this.serviceClickListener = serviceClickListener;
public ServiceAdapter(List<ServiceDTO> serviceList, OnServiceClickListener clickListener) {
this.serviceList = serviceList;
this.clickListener = clickListener;
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
@Override
public void onSelectionChanged(int count) {
clickListener.onSelectionChanged(count);
}
@Override
public void onSelectionModeToggle(boolean selectionMode) {
notifyDataSetChanged();
}
});
}
// Get the controls of each row in recycler view
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
/**
* ViewHolder class for service items.
*/
public static class ServiceViewHolder extends RecyclerView.ViewHolder {
TextView tvServiceName, tvServiceDesc, tvServiceDuration, tvServicePrice;
final ItemServiceBinding binding;
public ServiceViewHolder(@NonNull View v) {
super(v);
tvServiceName = v.findViewById(R.id.tvServiceName);
tvServiceDesc = v.findViewById(R.id.tvServiceDesc);
tvServiceDuration = v.findViewById(R.id.tvServiceDuration);
tvServicePrice = v.findViewById(R.id.tvServicePrice);
public ServiceViewHolder(@NonNull ItemServiceBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@@ -43,26 +73,51 @@ public class ServiceAdapter extends RecyclerView.Adapter<ServiceAdapter.ServiceV
@NonNull
@Override
public ServiceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_service, parent, false);
return new ServiceViewHolder(v);
ItemServiceBinding binding = ItemServiceBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ServiceViewHolder(binding);
}
//populate the row with service data
@Override
public void onBindViewHolder(@NonNull ServiceViewHolder holder, int position) {
ServiceDTO service = serviceList.get(position);
ItemServiceBinding binding = holder.binding;
holder.tvServiceName.setText(service.getServiceName());
holder.tvServiceDesc.setText(service.getServiceDesc());
holder.tvServiceDuration.setText("Duration: " + service.getServiceDuration() + " min");
holder.tvServicePrice.setText("$" + String.format("%.2f", service.getServicePrice()));
binding.tvServiceName.setText(service.getServiceName());
binding.tvServiceDesc.setText(service.getServiceDesc());
binding.tvServiceDuration.setText(service.getServiceDuration() != null ? service.getServiceDuration() + " mins" : "0 mins");
binding.tvServicePrice.setText(service.getServicePrice() != null ? "$" + String.format("%.2f", service.getServicePrice()) : "$0.00");
//when a row is clicked, open the detail view
holder.itemView.setOnClickListener(v -> serviceClickListener.onServiceClick(position));
String key = String.valueOf(service.getServiceId());
// Bulk delete selection mode
if (selectionHelper.isInSelectionMode()) {
binding.cbSelectService.setVisibility(View.VISIBLE);
binding.cbSelectService.setChecked(selectionHelper.isSelected(key));
} else {
binding.cbSelectService.setVisibility(View.GONE);
binding.cbSelectService.setChecked(false);
}
holder.itemView.setOnClickListener(v -> {
if (selectionHelper.isInSelectionMode()) {
selectionHelper.toggleSelection(key);
notifyItemChanged(position);
} else {
clickListener.onServiceClick(position);
}
});
holder.itemView.setOnLongClickListener(v -> {
if (!selectionHelper.isInSelectionMode()) {
selectionHelper.startSelection(key);
}
return true;
});
}
@Override
public int getItemCount() {
return serviceList.size();
}
}
}

View File

@@ -3,39 +3,63 @@ package com.example.petstoremobile.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.databinding.ItemSupplierBinding;
import com.example.petstoremobile.dtos.SupplierDTO;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.SelectionHelper;
import java.util.List;
public class SupplierAdapter extends RecyclerView.Adapter<SupplierAdapter.SupplierViewHolder> {
public class SupplierAdapter extends RecyclerView.Adapter<SupplierAdapter.SupplierViewHolder> implements BulkDeleteHandler.SelectableAdapter {
private List<SupplierDTO> supplierList;
private OnSupplierClickListener supplierClickListener;
private final List<SupplierDTO> supplierList;
private final OnSupplierClickListener supplierClickListener;
private final SelectionHelper selectionHelper;
// Interface for supplier click on recycler view
public interface OnSupplierClickListener {
void onSupplierClick(int position);
void onSelectionChanged(int count);
}
//Constructor
public SupplierAdapter(List<SupplierDTO> supplierList, OnSupplierClickListener supplierClickListener) {
this.supplierList = supplierList;
this.supplierClickListener = supplierClickListener;
this.selectionHelper = new SelectionHelper(new SelectionHelper.SelectionListener() {
@Override
public void onSelectionChanged(int count) {
supplierClickListener.onSelectionChanged(count);
}
@Override
public void onSelectionModeToggle(boolean selectionMode) {
notifyDataSetChanged();
}
});
}
@Override
public List<String> getSelectedKeys() {
return selectionHelper.getSelectedKeys();
}
@Override
public void clearSelection() {
selectionHelper.clearSelection();
}
// Get the controls of each row in recycler view
public static class SupplierViewHolder extends RecyclerView.ViewHolder {
TextView tvSupCompany, tvSupContactName, tvSupEmail, tvSupPhone;
final ItemSupplierBinding binding;
public SupplierViewHolder(@NonNull View v) {
super(v);
tvSupCompany = v.findViewById(R.id.tvSupCompany);
tvSupContactName = v.findViewById(R.id.tvSupContactName);
tvSupEmail = v.findViewById(R.id.tvSupEmail);
tvSupPhone = v.findViewById(R.id.tvSupPhone);
public SupplierViewHolder(@NonNull ItemSupplierBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}
@@ -43,26 +67,52 @@ public class SupplierAdapter extends RecyclerView.Adapter<SupplierAdapter.Suppli
@NonNull
@Override
public SupplierViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_supplier, parent, false);
return new SupplierViewHolder(v);
ItemSupplierBinding binding = ItemSupplierBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new SupplierViewHolder(binding);
}
//populate the row with supplier data
@Override
public void onBindViewHolder(@NonNull SupplierViewHolder holder, int position) {
SupplierDTO supplier = supplierList.get(position);
ItemSupplierBinding binding = holder.binding;
holder.tvSupCompany.setText(supplier.getSupCompany());
holder.tvSupContactName.setText(supplier.getSupContactFirstName() + " " + supplier.getSupContactLastName());
holder.tvSupEmail.setText(supplier.getSupEmail());
holder.tvSupPhone.setText(supplier.getSupPhone());
binding.tvSupCompany.setText(supplier.getSupCompany());
binding.tvSupContactName.setText(supplier.getSupContactFirstName() + " " + supplier.getSupContactLastName());
binding.tvSupEmail.setText(supplier.getSupEmail());
binding.tvSupPhone.setText(supplier.getSupPhone());
String key = String.valueOf(supplier.getSupId());
// Bulk delete selection mode
if (selectionHelper.isInSelectionMode()) {
binding.cbSelectSupplier.setVisibility(View.VISIBLE);
binding.cbSelectSupplier.setChecked(selectionHelper.isSelected(key));
} else {
binding.cbSelectSupplier.setVisibility(View.GONE);
binding.cbSelectSupplier.setChecked(false);
}
//when a row is clicked, open the detail view
holder.itemView.setOnClickListener(v -> supplierClickListener.onSupplierClick(position));
holder.itemView.setOnClickListener(v -> {
if (selectionHelper.isInSelectionMode()) {
selectionHelper.toggleSelection(key);
notifyItemChanged(position);
} else {
supplierClickListener.onSupplierClick(position);
}
});
holder.itemView.setOnLongClickListener(v -> {
if (!selectionHelper.isInSelectionMode()) {
selectionHelper.startSelection(key);
}
return true;
});
}
@Override
public int getItemCount() {
return supplierList.size();
}
}
}

View File

@@ -1,12 +1,14 @@
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.AdoptionDTO;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
@@ -17,7 +19,12 @@ public interface AdoptionApi {
@GET("api/v1/adoptions")
Call<PageResponse<AdoptionDTO>> getAllAdoptions(
@Query("page") int page,
@Query("size") int size);
@Query("size") int size,
@Query("q") String query,
@Query("status") String status,
@Query("storeId") Long storeId,
@Query("date") String date,
@Query("employeeId") Long employeeId);
@GET("api/v1/adoptions/{id}")
Call<AdoptionDTO> getAdoptionById(@Path("id") Long id);
@@ -30,5 +37,7 @@ public interface AdoptionApi {
@DELETE("api/v1/adoptions/{id}")
Call<Void> deleteAdoption(@Path("id") Long id);
}
@HTTP(method = "DELETE", path = "api/v1/adoptions", hasBody = true)
Call<Void> bulkDeleteAdoptions(@Body BulkDeleteRequest request);
}

View File

@@ -1,12 +1,14 @@
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.AppointmentDTO;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
@@ -35,4 +37,7 @@ public interface AppointmentApi {
@DELETE("api/v1/appointments/{id}")
Call<Void> deleteAppointment(@Path("id") Long id);
@HTTP(method = "DELETE", path = "api/v1/appointments", hasBody = true)
Call<Void> bulkDeleteAppointments(@Body BulkDeleteRequest request);
}

View File

@@ -0,0 +1,32 @@
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.EmployeeDTO;
import com.example.petstoremobile.dtos.PageResponse;
import retrofit2.Call;
import retrofit2.http.*;
public interface EmployeeApi {
@GET("api/v1/employees")
Call<PageResponse<EmployeeDTO>> getAllEmployees(
@Query("page") int page,
@Query("size") int size);
@GET("api/v1/employees/{id}")
Call<EmployeeDTO> getEmployeeById(@Path("id") Long id);
@POST("api/v1/employees")
Call<EmployeeDTO> createEmployee(@Body EmployeeDTO employee);
@PUT("api/v1/employees/{id}")
Call<EmployeeDTO> updateEmployee(@Path("id") Long id, @Body EmployeeDTO employee);
@DELETE("api/v1/employees/{id}")
Call<Void> deleteEmployee(@Path("id") Long id);
}

View File

@@ -2,13 +2,13 @@ package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.InventoryRequest;
import com.example.petstoremobile.dtos.PageResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
@@ -30,17 +30,17 @@ public interface InventoryApi {
// POST /api/v1/inventory
@POST("api/v1/inventory")
Call<InventoryDTO> createInventory(@Body InventoryRequest request);
Call<InventoryDTO> createInventory(@Body InventoryDTO request);
// PUT /api/v1/inventory/{id}
@PUT("api/v1/inventory/{id}")
Call<InventoryDTO> updateInventory(@Path("id") Long id, @Body InventoryRequest request);
Call<InventoryDTO> updateInventory(@Path("id") Long id, @Body InventoryDTO request);
// DELETE /api/v1/inventory/{id}
@DELETE("api/v1/inventory/{id}")
Call<Void> deleteInventory(@Path("id") Long id);
// DELETE /api/v1/inventory (bulk delete)
@DELETE("api/v1/inventory")
@HTTP(method = "DELETE", path = "api/v1/inventory", hasBody = true)
Call<Void> bulkDeleteInventory(@Body BulkDeleteRequest request);
}

View File

@@ -1,5 +1,6 @@
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.PetDTO;
@@ -8,6 +9,7 @@ import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.PUT;
@@ -48,6 +50,10 @@ public interface PetApi {
@DELETE("api/v1/pets/{id}")
Call<Void> deletePet(@Path("id") Long id);
// Bulk delete pets
@HTTP(method = "DELETE", path = "api/v1/pets", hasBody = true)
Call<Void> bulkDeletePets(@Body BulkDeleteRequest request);
// Upload pet image
@Multipart
@POST("api/v1/pets/{id}/image")

View File

@@ -1,5 +1,6 @@
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ProductSupplierDTO;
import retrofit2.Call;
@@ -34,4 +35,6 @@ public interface ProductSupplierApi {
Call<Void> deleteProductSupplier(
@Path("productId") Long productId,
@Path("supplierId") Long supplierId);
@HTTP(method = "DELETE", path = "api/v1/product-suppliers", hasBody = true)
Call<Void> bulkDeleteProductSuppliers(@Body BulkDeleteRequest request);
}

View File

@@ -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);
}

View File

@@ -132,4 +132,12 @@ 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);
}
}

View File

@@ -1,5 +1,6 @@
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ServiceDTO;
@@ -7,6 +8,7 @@ import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
@@ -38,4 +40,8 @@ public interface ServiceApi {
// Delete service
@DELETE("api/v1/services/{id}")
Call<Void> deleteService(@Path("id") Long id);
// Bulk delete services
@HTTP(method = "DELETE", path = "api/v1/services", hasBody = true)
Call<Void> bulkDeleteServices(@Body BulkDeleteRequest request);
}

View File

@@ -1,5 +1,6 @@
package com.example.petstoremobile.api;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SupplierDTO;
@@ -7,6 +8,7 @@ import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.HTTP;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
@@ -38,4 +40,8 @@ public interface SupplierApi {
// Delete supplier
@DELETE("api/v1/suppliers/{id}")
Call<Void> deleteSupplier(@Path("id") Long id);
// Bulk delete suppliers
@HTTP(method = "DELETE", path = "api/v1/suppliers", hasBody = true)
Call<Void> bulkDeleteSuppliers(@Body BulkDeleteRequest request);
}

View File

@@ -179,4 +179,16 @@ public class NetworkModule {
public static UserApi provideUserApi(Retrofit retrofit) {
return retrofit.create(UserApi.class);
}
@Provides
@Singleton
public static EmployeeApi provideEmployeeApi(Retrofit retrofit) {
return retrofit.create(EmployeeApi.class);
}
@Provides
@Singleton
public static RefundApi provideRefundApi(Retrofit retrofit) {
return retrofit.create(RefundApi.class);
}
}

View File

@@ -3,6 +3,7 @@ package com.example.petstoremobile.dtos;
import java.math.BigDecimal;
public class AdoptionDTO {
private Long adoptionId;
private Long petId;
private String petName;
@@ -18,80 +19,58 @@ public class AdoptionDTO {
private String createdAt;
private String updatedAt;
public AdoptionDTO(Long petId, Long customerId, Long sourceStoreId, String adoptionDate, String adoptionStatus) {
this.petId = petId;
this.customerId = customerId;
this.sourceStoreId = sourceStoreId;
this.adoptionDate = adoptionDate;
this.adoptionStatus = adoptionStatus;
}
public AdoptionDTO() {}
public AdoptionDTO(Long petId, Long customerId, Long employeeId, Long sourceStoreId, String adoptionDate, String adoptionStatus) {
public AdoptionDTO(Long petId, Long customerId, Long employeeId, Long sourceStoreId,
String adoptionDate, String adoptionStatus, BigDecimal adoptionFee) {
this.petId = petId;
this.customerId = customerId;
this.employeeId = employeeId;
this.sourceStoreId = sourceStoreId;
this.adoptionDate = adoptionDate;
this.adoptionStatus = adoptionStatus;
this.adoptionFee = adoptionFee;
}
public Long getAdoptionId() {
return adoptionId;
}
public Long getAdoptionId() { return adoptionId; }
public void setAdoptionId(Long adoptionId) { this.adoptionId = adoptionId; }
public Long getPetId() {
return petId;
}
public Long getPetId() { return petId; }
public void setPetId(Long petId) { this.petId = petId; }
public String getPetName() {
return petName;
}
public String getPetName() { return petName; }
public void setPetName(String petName) { this.petName = petName; }
public Long getCustomerId() {
return customerId;
}
public Long getCustomerId() { return customerId; }
public void setCustomerId(Long customerId) { this.customerId = customerId; }
public String getCustomerName() {
return customerName;
}
public String getCustomerName() { return customerName; }
public void setCustomerName(String customerName) { this.customerName = customerName; }
public Long getEmployeeId() {
return employeeId;
}
public Long getEmployeeId() { return employeeId; }
public void setEmployeeId(Long employeeId) { this.employeeId = employeeId; }
public String getEmployeeName() {
return employeeName;
}
public String getEmployeeName() { return employeeName; }
public void setEmployeeName(String employeeName) { this.employeeName = employeeName; }
public Long getSourceStoreId() {
return sourceStoreId;
}
public Long getSourceStoreId() { return sourceStoreId; }
public void setSourceStoreId(Long sourceStoreId) { this.sourceStoreId = sourceStoreId; }
public String getSourceStoreName() {
return sourceStoreName;
}
public String getSourceStoreName() { return sourceStoreName; }
public void setSourceStoreName(String sourceStoreName) { this.sourceStoreName = sourceStoreName; }
public String getAdoptionDate() {
return adoptionDate;
}
public String getAdoptionDate() { return adoptionDate; }
public void setAdoptionDate(String adoptionDate) { this.adoptionDate = adoptionDate; }
public String getAdoptionStatus() {
return adoptionStatus;
}
public String getAdoptionStatus() { return adoptionStatus; }
public void setAdoptionStatus(String adoptionStatus) { this.adoptionStatus = adoptionStatus; }
public String getStatus() {
return adoptionStatus;
}
public BigDecimal getAdoptionFee() { return adoptionFee; }
public void setAdoptionFee(BigDecimal adoptionFee) { this.adoptionFee = adoptionFee; }
public BigDecimal getAdoptionFee() {
return adoptionFee;
}
public String getCreatedAt() { return createdAt; }
public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
public String getCreatedAt() {
return createdAt;
}
public String getUpdatedAt() {
return updatedAt;
}
public String getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; }
}

View File

@@ -1,7 +1,7 @@
package com.example.petstoremobile.dtos;
public class AppointmentDTO {
private Long appointmentId;
private Long customerId;
private String customerName;
@@ -38,83 +38,43 @@ public class AppointmentDTO {
this.petId = petId;
}
public Long getAppointmentId() {
return appointmentId;
}
public Long getAppointmentId() { return appointmentId; }
public Long getCustomerId() {
return customerId;
}
public Long getCustomerId() { return customerId; }
public String getCustomerName() {
return customerName;
}
public String getCustomerName() { return customerName; }
public Long getStoreId() {
return storeId;
}
public Long getStoreId() { return storeId; }
public String getStoreName() {
return storeName;
}
public String getStoreName() { return storeName; }
public Long getServiceId() {
return serviceId;
}
public Long getServiceId() { return serviceId; }
public String getServiceName() {
return serviceName;
}
public String getServiceName() { return serviceName; }
public Long getEmployeeId() {
return employeeId;
}
public Long getEmployeeId() { return employeeId; }
public String getEmployeeName() {
return employeeName;
}
public String getEmployeeName() { return employeeName; }
public String getAppointmentDate() {
return appointmentDate;
}
public String getAppointmentDate() { return appointmentDate; }
public String getAppointmentTime() {
return appointmentTime;
}
public String getAppointmentTime() { return appointmentTime; }
public String getAppointmentStatus() {
return appointmentStatus;
}
public String getAppointmentStatus() { return appointmentStatus; }
public String getPetName() {
return petName;
}
public String getPetName() { return petName; }
public Long getPetId() {
return petId;
}
public Long getPetId() { return petId; }
public String getCreatedAt() {
return createdAt;
}
public String getCreatedAt() { return createdAt; }
public String getUpdatedAt() {
return updatedAt;
}
public String getUpdatedAt() { return updatedAt; }
public Long getPetID() {
return petId;
}
public Long getPetID() { return petId; }
public String getServiceType() {
return serviceName;
}
public String getServiceType() { return serviceName; }
public Long getServiceID() {
return serviceId;
}
public Long getServiceID() { return serviceId; }
public String getStatus() {
return appointmentStatus;
}
public String getStatus() { return appointmentStatus; }
}

View File

@@ -3,20 +3,20 @@ package com.example.petstoremobile.dtos;
import java.util.List;
public class BulkDeleteRequest {
private List<Long> ids;
private List<String> ids;
public BulkDeleteRequest() {
}
public BulkDeleteRequest(List<Long> ids) {
public BulkDeleteRequest(List<String> ids) {
this.ids = ids;
}
public List<Long> getIds() {
public List<String> getIds() {
return ids;
}
public void setIds(List<Long> ids) {
public void setIds(List<String> ids) {
this.ids = ids;
}
}

View File

@@ -0,0 +1,102 @@
package com.example.petstoremobile.dtos;
public class EmployeeDTO {
private long EmployeeId;
private Long userId;
private String username;
private String firstName;
private String lastName;
private String fullName;
private String email;
private String phone;
private String role;
private Boolean active;
private String createAt;
private String updatedAt;
// Constructor for create and update the employee
public EmployeeDTO(String username, String password, String firstName, String lastName,
String email, String phone, String role, boolean active) {
this.username = username;
this.password = password;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.phone = phone;
this.role = role;
this.active = active;
}
// password field for request only
private String password;
public long getEmployeeId() {
return EmployeeId;
}
public Long getUserId() {
return userId;
}
public String getUsername() {
return username;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public String getFullName() {
return fullName;
}
public String getEmail() {
return email;
}
public String getPhone() {
return phone;
}
public String getRole() {
return role;
}
public Boolean getActive() {
return active;
}
public String getCreateAt() {
return createAt;
}
public String getUpdatedAt() {
return updatedAt;
}
public String getPassword() {
return password;
}
}

View File

@@ -6,6 +6,8 @@ public class InventoryDTO {
private Long prodId;
private String productName;
private String categoryName;
private Long storeId;
private String storeName;
private Integer quantity;
private String createdAt;
private String updatedAt;
@@ -14,8 +16,9 @@ public class InventoryDTO {
}
// Constructor for create/update requests (matches InventoryRequest)
public InventoryDTO(Long prodId, Integer quantity) {
public InventoryDTO(Long prodId, Long storeId, Integer quantity) {
this.prodId = prodId;
this.storeId = storeId;
this.quantity = quantity;
}
@@ -51,6 +54,22 @@ public class InventoryDTO {
this.categoryName = categoryName;
}
public Long getStoreId() {
return storeId;
}
public void setStoreId(Long storeId) {
this.storeId = storeId;
}
public String getStoreName() {
return storeName;
}
public void setStoreName(String storeName) {
this.storeName = storeName;
}
public Integer getQuantity() {
return quantity;
}

View File

@@ -1,31 +0,0 @@
package com.example.petstoremobile.dtos;
public class InventoryRequest {
private Long prodId;
private Integer quantity;
public InventoryRequest() {
}
public InventoryRequest(Long prodId, Integer quantity) {
this.prodId = prodId;
this.quantity = quantity;
}
public Long getProdId() {
return prodId;
}
public void setProdId(Long prodId) {
this.prodId = prodId;
}
public Integer getQuantity() {
return quantity;
}
public void setQuantity(Integer quantity) {
this.quantity = quantity;
}
}

View File

@@ -4,6 +4,8 @@ public class PurchaseOrderDTO {
private Long purchaseOrderId;
private Long supId;
private String supplierName;
private Long storeId;
private String storeName;
private String orderDate;
private String status;
private String createdAt;
@@ -21,6 +23,14 @@ public class PurchaseOrderDTO {
return supplierName;
}
public Long getStoreId() {
return storeId;
}
public String getStoreName() {
return storeName;
}
public String getOrderDate() {
return orderDate;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -47,6 +47,13 @@ public class ListFragment extends Fragment {
binding.drawerInventory.setVisibility(View.GONE);
}
// Only show for ADMIN
if ("ADMIN".equalsIgnoreCase(role)) {
binding.drawerStaff.setVisibility(View.VISIBLE);
} else {
binding.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
binding.drawerLayout.addDrawerListener(new DrawerLayout.DrawerListener() {
@@ -83,6 +90,8 @@ public class ListFragment extends Fragment {
binding.drawerProductSupplier.setOnClickListener(v -> navigateTo(R.id.nav_product_supplier));
binding.drawerPurchaseOrderView.setOnClickListener(v -> navigateTo(R.id.nav_purchase_order));
binding.drawerSale.setOnClickListener(v -> navigateTo(R.id.nav_sale));
binding.drawerStaff.setOnClickListener(v -> navigateTo(R.id.nav_staff));
binding.drawerAnalytics.setOnClickListener(v -> navigateTo(R.id.nav_analytics));
return binding.getRoot();
}

View File

@@ -1,252 +1,142 @@
package com.example.petstoremobile.fragments.listfragments;
import android.graphics.Color;
import android.os.Bundle;
import android.text.*;
import android.util.Log;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.AdoptionAdapter;
import com.example.petstoremobile.databinding.FragmentAdoptionBinding;
import com.example.petstoremobile.api.AdoptionApi;
import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.dtos.AdoptionDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.viewmodels.AdoptionViewModel;
import com.example.petstoremobile.utils.EventDecorator;
import com.prolificinteractive.materialcalendarview.CalendarDay;
import com.prolificinteractive.materialcalendarview.CalendarMode;
import com.prolificinteractive.materialcalendarview.MaterialCalendarView;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import com.example.petstoremobile.fragments.listfragments.detailfragments.AdoptionDetailFragment;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.*;
import retrofit2.*;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener {
private FragmentAdoptionBinding binding;
private List<AdoptionDTO> adoptionList = new ArrayList<>();
private List<AdoptionDTO> filteredList = new ArrayList<>();
private AdoptionAdapter adapter;
private AdoptionViewModel viewModel;
private CalendarDay selectedCalendarDay;
private boolean isMonthMode = false;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
private AdoptionApi api;
private SwipeRefreshLayout swipeRefresh;
private EditText etSearch;
private ImageButton hamburger;
/**
* Initializes the fragment and its ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(AdoptionViewModel.class);
}
/**
* Sets up the fragment's UI components, including RecyclerView, Search, SwipeRefresh, and Calendar.
*/
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentAdoptionBinding.inflate(inflater, container, false);
View view = inflater.inflate(R.layout.fragment_adoption, container, false);
setupRecyclerView();
setupSearch();
setupSwipeRefresh();
setupCalendar();
api = RetrofitClient.getAdoptionApi(requireContext());
hamburger = view.findViewById(R.id.btnHamburgerAdoption);
setupRecyclerView(view);
setupSearch(view);
setupSwipeRefresh(view);
loadAdoptions();
binding.fabAddAdoption.setOnClickListener(v -> openDetail(-1));
FloatingActionButton fab = view.findViewById(R.id.fabAddAdoption);
fab.setOnClickListener(v -> openDetail(-1));
binding.btnHamburgerAdoption.setOnClickListener(v -> {
Fragment parent = getParentFragment();
if (parent != null) {
Fragment grandParent = parent.getParentFragment();
if (grandParent instanceof ListFragment) {
((ListFragment) grandParent).openDrawer();
}
}
hamburger.setOnClickListener(v -> {
ListFragment lf = (ListFragment) getParentFragment();
if (lf != null) lf.openDrawer();
});
binding.btnToggleCalendarModeAdoption.setOnClickListener(v -> toggleCalendarMode());
return binding.getRoot();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
/**
* Toggles the calendar display between week and month modes.
*/
private void toggleCalendarMode() {
isMonthMode = !isMonthMode;
binding.calendarViewAdoption.state().edit()
.setCalendarDisplayMode(isMonthMode ? CalendarMode.MONTHS : CalendarMode.WEEKS)
.commit();
}
/**
* Sets up the date selection listener for the calendar.
*/
private void setupCalendar() {
binding.calendarViewAdoption.setOnDateChangedListener((widget, date, selected) -> {
if (selected) {
if (date.equals(selectedCalendarDay)) {
selectedCalendarDay = null;
binding.calendarViewAdoption.clearSelection();
} else {
selectedCalendarDay = date;
}
} else {
selectedCalendarDay = null;
}
filter(binding.etSearchAdoption.getText().toString());
});
}
/**
* Updates the calendar decorators to highlight days with adoptions.
*/
private void updateCalendarDecorators() {
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
for (AdoptionDTO adoption : adoptionList) {
try {
if (adoption.getAdoptionDate() != null) {
Date date = dateFormat.parse(adoption.getAdoptionDate());
if (date != null) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
datesWithAdoptions.add(CalendarDay.from(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH) + 1, cal.get(Calendar.DAY_OF_MONTH)));
}
}
} catch (ParseException e) {
Log.e("AdoptionFragment", "Error parsing date: " + adoption.getAdoptionDate());
}
}
binding.calendarViewAdoption.removeDecorators();
binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
}
/**
* Initializes the RecyclerView for displaying adoptions.
*/
private void setupRecyclerView() {
private void setupRecyclerView(View view) {
RecyclerView rv = view.findViewById(R.id.recyclerViewAdoptions);
adapter = new AdoptionAdapter(filteredList, this);
binding.recyclerViewAdoptions.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerViewAdoptions.setAdapter(adapter);
rv.setLayoutManager(new LinearLayoutManager(getContext()));
rv.setAdapter(adapter);
}
/**
* Sets up the search bar for filtering
*/
private void setupSearch() {
binding.etSearchAdoption.addTextChangedListener(new android.text.TextWatcher() {
private void setupSearch(View view) {
etSearch = view.findViewById(R.id.etSearchAdoption);
etSearch.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int a, int b, int c) {}
public void afterTextChanged(android.text.Editable s) {}
public void afterTextChanged(Editable s) {}
public void onTextChanged(CharSequence s, int a, int b, int c) {
filter(s.toString());
}
});
}
/**
* Sets up the SwipeRefreshLayout to reload adoption data.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshAdoption.setOnRefreshListener(this::loadAdoptions);
private void setupSwipeRefresh(View view) {
swipeRefresh = view.findViewById(R.id.swipeRefreshAdoption);
swipeRefresh.setOnRefreshListener(this::loadAdoptions);
}
/**
* Filters the adoption list based on search query and selected calendar date.
*/
private void filter(String query) {
filteredList.clear();
String lowerQuery = query.toLowerCase();
String selectedDateString = null;
if (selectedCalendarDay != null) {
selectedDateString = String.format(Locale.getDefault(), "%04d-%02d-%02d",
selectedCalendarDay.getYear(), selectedCalendarDay.getMonth(), selectedCalendarDay.getDay());
}
for (AdoptionDTO a : adoptionList) {
boolean matchesSearch = query.isEmpty() ||
(a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lowerQuery)) ||
(a.getPetName() != null && a.getPetName().toLowerCase().contains(lowerQuery)) ||
(a.getAdoptionStatus() != null && a.getAdoptionStatus().toLowerCase().contains(lowerQuery));
boolean matchesDate = (selectedDateString == null) ||
(a.getAdoptionDate() != null && a.getAdoptionDate().startsWith(selectedDateString));
if (matchesSearch && matchesDate) {
filteredList.add(a);
if (query.isEmpty()) {
filteredList.addAll(adoptionList);
} else {
String lower = query.toLowerCase();
for (AdoptionDTO a : adoptionList) {
if ((a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lower))
|| (a.getPetName() != null && a.getPetName().toLowerCase().contains(lower))
|| (a.getAdoptionStatus() != null && a.getAdoptionStatus().toLowerCase().contains(lower))) {
filteredList.add(a);
}
}
}
adapter.notifyDataSetChanged();
}
/**
* Fetches the adoption list from the server through the ViewModel.
*/
private void loadAdoptions() {
//Load all adoptions from the backend using viewModel
viewModel.getAllAdoptions(0, 500).observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
// Check the status to see if the resource is loaded and display the data
switch (resource.status) {
case LOADING:
// Show loading indicator
binding.swipeRefreshAdoption.setRefreshing(true);
break;
case SUCCESS:
// Hide loading indicator and display data
binding.swipeRefreshAdoption.setRefreshing(false);
if (resource.data != null) {
adoptionList.clear();
adoptionList.addAll(resource.data.getContent());
updateCalendarDecorators();
filter(binding.etSearchAdoption != null ? binding.etSearchAdoption.getText().toString() : "");
}
break;
case ERROR:
// Hide loading indicator and toast error message
binding.swipeRefreshAdoption.setRefreshing(false);
Toast.makeText(getContext(), "Failed to load adoptions: " + resource.message, Toast.LENGTH_SHORT).show();
Log.e("AdoptionFragment", "Error loading adoptions: " + resource.message);
break;
if (swipeRefresh != null) swipeRefresh.setRefreshing(true);
api.getAllAdoptions(0, 100, null, null, null, null, null).enqueue(new Callback<PageResponse<AdoptionDTO>>() {
public void onResponse(Call<PageResponse<AdoptionDTO>> c,
Response<PageResponse<AdoptionDTO>> r) {
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
if (r.isSuccessful() && r.body() != null) {
adoptionList.clear();
adoptionList.addAll(r.body().getContent());
filter(etSearch != null ? etSearch.getText().toString() : "");
} else {
Toast.makeText(getContext(), "Failed to load adoptions", Toast.LENGTH_SHORT).show();
Log.e("AdoptionFragment", "Error: " + r.message());
}
}
public void onFailure(Call<PageResponse<AdoptionDTO>> c, Throwable t) {
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
Log.e("AdoptionFragment", t.getMessage());
}
});
}
/**
* Navigates to the adoption detail screen for a specific adoption or to create a new one.
*/
private void openDetail(int position) {
Bundle args = new Bundle();
if (position != -1) {
AdoptionDTO a = filteredList.get(position);
args.putLong("adoptionId", a.getAdoptionId());
}
args.putLong("petId", a.getPetId() != null ? a.getPetId() : -1);
args.putLong("customerId", a.getCustomerId() != null ? a.getCustomerId() : -1);
args.putString("adoptionDate", a.getAdoptionDate());
args.putString("adoptionStatus", a.getAdoptionStatus());
if (a.getEmployeeId() != null)
args.putLong("employeeId", a.getEmployeeId());}
NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args);
}
/**
* Handles item click in the adoption list.
*/
@Override
public void onAdoptionClick(int position) { openDetail(position); }
@Override
public void onSelectionChanged(int count) {}
}

View File

@@ -0,0 +1,334 @@
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 -> openDrawer());
return view;
}
private void openDrawer() {
Fragment parent = getParentFragment();
if (parent != null) {
Fragment grandParent = parent.getParentFragment();
if (grandParent instanceof ListFragment) {
((ListFragment) grandParent).openDrawer();
}
}
}
private void initViews(View v) {
tvTotalRevenue = v.findViewById(R.id.tvTotalRevenue);
tvTotalTransactions = v.findViewById(R.id.tvTotalTransactions);
tvAvgTransaction = v.findViewById(R.id.tvAvgTransaction);
tvTotalItems = v.findViewById(R.id.tvTotalItems);
llTopRevenue = v.findViewById(R.id.llTopRevenue);
llTopQuantity = v.findViewById(R.id.llTopQuantity);
llPaymentMethods = v.findViewById(R.id.llPaymentMethods);
llEmployeePerformance = v.findViewById(R.id.llEmployeePerformance);
llDailyRevenue = v.findViewById(R.id.llDailyRevenue);
btnRefresh = v.findViewById(R.id.btnRefreshAnalytics);
hamburger = v.findViewById(R.id.btnHamburgerAnalytics);
}
private void loadAnalytics() {
// 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();
}
}

View File

@@ -16,16 +16,15 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Toast;
import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.AppointmentAdapter;
import com.example.petstoremobile.adapters.WhiteTextArrayAdapter;
import com.example.petstoremobile.databinding.FragmentAppointmentBinding;
import com.example.petstoremobile.dtos.AppointmentDTO;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.viewmodels.AppointmentViewModel;
@@ -57,6 +56,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
private AppointmentViewModel appointmentViewModel;
private StoreViewModel storeViewModel;
private AuthViewModel authViewModel;
private BulkDeleteHandler bulkDeleteHandler;
private CalendarDay selectedCalendarDay;
private boolean isMonthMode = false;
@@ -90,6 +90,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
setupCalendar();
setupFilterToggle();
setupMyAppointmentFilter();
setupBulkDelete();
binding.fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1));
@@ -110,6 +111,19 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
return binding.getRoot();
}
private void setupBulkDelete() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
binding.layoutBulkDelete,
binding.tvSelectionCount,
binding.btnBulkDelete,
adapter,
"appointment",
appointmentViewModel::bulkDeleteAppointments,
this::loadAppointmentData
);
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -237,30 +251,14 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
*/
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Booked", "Completed", "Cancelled", "Missed"};
WhiteTextArrayAdapter<String> adapter = new WhiteTextArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statuses);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerStatus.setAdapter(adapter);
binding.spinnerStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadAppointmentData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadAppointmentData);
}
/**
* Configures the store filter spinner.
*/
private void setupStoreFilter() {
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadAppointmentData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadAppointmentData);
}
/**
@@ -303,6 +301,13 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
openAppointmentDetails(position);
}
@Override
public void onSelectionChanged(int count) {
if (bulkDeleteHandler != null) {
bulkDeleteHandler.onSelectionChanged(count);
}
}
/**
* Fetches appointment data from the server with all active filters.
*/
@@ -366,4 +371,4 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
binding.recyclerViewAppointments.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerViewAppointments.setAdapter(adapter);
}
}
}

View File

@@ -7,7 +7,6 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -24,6 +23,7 @@ import com.example.petstoremobile.databinding.FragmentInventoryBinding;
import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.viewmodels.InventoryViewModel;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
@@ -44,6 +44,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
private List<StoreDTO> storeList = new ArrayList<>();
private InventoryAdapter adapter;
private InventoryViewModel viewModel;
private BulkDeleteHandler bulkDeleteHandler;
// Pagination
private int currentPage = 0;
@@ -72,6 +73,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
setupStoreFilter();
setupSwipeRefresh();
setupFilterToggle();
setupBulkDelete();
loadInventory(true);
loadStoreData();
@@ -87,11 +89,22 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
}
});
binding.btnBulkDelete.setOnClickListener(v -> confirmBulkDelete());
return binding.getRoot();
}
private void setupBulkDelete() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
binding.layoutBulkDelete,
binding.tvSelectionCount,
binding.btnBulkDelete,
adapter,
"inventory item",
viewModel::bulkDeleteInventory,
() -> loadInventory(true)
);
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -134,13 +147,7 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
* Configures the store filter spinner.
*/
private void setupStoreFilter() {
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadInventory(true);
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadInventory(true));
}
/**
@@ -243,50 +250,6 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
});
}
/**
* Displays a confirmation dialog before performing a bulk deletion of selected items.
*/
private void confirmBulkDelete() {
List<Long> ids = adapter.getSelectedIds();
if (ids.isEmpty())
return;
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
.setTitle("Delete " + ids.size() + " item(s)?")
.setMessage("This cannot be undone.")
.setPositiveButton("Delete", (d, w) -> bulkDelete(ids))
.setNegativeButton("Cancel", null)
.show();
}
/**
* Executes the bulk deletion of inventory items through the ViewModel.
*/
private void bulkDelete(List<Long> ids) {
viewModel.bulkDeleteInventory(ids).observe(getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status != Resource.Status.LOADING) {
if (resource.status == Resource.Status.SUCCESS) {
adapter.clearSelection();
hideBulkDeleteBar();
loadInventory(true);
Toast.makeText(getContext(), ids.size() + " item(s) deleted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
}
}
});
}
/**
* Hides the bulk deletion UI bar.
*/
private void hideBulkDeleteBar() {
if (binding != null) {
binding.btnBulkDelete.setVisibility(View.GONE);
binding.tvSelectionCount.setVisibility(View.GONE);
}
}
/**
* Navigates to the inventory detail screen for a specific item or to add a new one.
*/
@@ -322,12 +285,8 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
*/
@Override
public void onSelectionChanged(int selectedCount) {
if (selectedCount > 0) {
binding.btnBulkDelete.setVisibility(View.VISIBLE);
binding.tvSelectionCount.setVisibility(View.VISIBLE);
binding.tvSelectionCount.setText(selectedCount + " selected");
} else {
hideBulkDeleteBar();
if (bulkDeleteHandler != null) {
bulkDeleteHandler.onSelectionChanged(selectedCount);
}
}
}

View File

@@ -15,16 +15,15 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Toast;
import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.PetAdapter;
import com.example.petstoremobile.adapters.WhiteTextArrayAdapter;
import com.example.petstoremobile.databinding.FragmentPetBinding;
import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.viewmodels.PetViewModel;
@@ -46,6 +45,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
private PetAdapter adapter;
private PetViewModel viewModel;
private StoreViewModel storeViewModel;
private BulkDeleteHandler bulkDeleteHandler;
@Inject @Named("baseUrl") String baseUrl;
@@ -74,6 +74,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
setupStoreFilter();
setupSwipeRefresh();
setupFilterToggle();
setupBulkDelete();
binding.fabAddPet.setOnClickListener(v -> openPetDetails());
@@ -90,6 +91,19 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
return binding.getRoot();
}
private void setupBulkDelete() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
binding.layoutBulkDelete,
binding.tvSelectionCount,
binding.btnBulkDelete,
adapter,
"pet",
viewModel::bulkDeletePets,
this::loadPetData
);
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -145,17 +159,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
*/
private void setupStatusFilter() {
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned"};
WhiteTextArrayAdapter<String> adapter = new WhiteTextArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statuses);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerStatus.setAdapter(adapter);
binding.spinnerStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadPetData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadPetData);
}
/**
@@ -163,30 +167,14 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
*/
private void setupSpeciesFilter() {
String[] species = {"All Species", "Dog", "Cat", "Bird", "Rabbit", "Fish", "Hamster"};
WhiteTextArrayAdapter<String> adapter = new WhiteTextArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, species);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
binding.spinnerSpecies.setAdapter(adapter);
binding.spinnerSpecies.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadPetData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, species, this::loadPetData);
}
/**
* Configures the store filter spinner.
*/
private void setupStoreFilter() {
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadPetData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadPetData);
}
/**
@@ -231,6 +219,13 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
openPetProfile(position);
}
@Override
public void onSelectionChanged(int selectedCount) {
if (bulkDeleteHandler != null) {
bulkDeleteHandler.onSelectionChanged(selectedCount);
}
}
/**
* Fetches pet data from the server with all active filters.
*/

View File

@@ -15,7 +15,6 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Toast;
import com.example.petstoremobile.R;
@@ -137,13 +136,7 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
* Configures the category filter spinner.
*/
private void setupCategoryFilter() {
binding.spinnerCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadProductData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupFilterSpinner(binding.spinnerCategory, this::loadProductData);
}
/**
@@ -173,7 +166,7 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
Bundle args = new Bundle();
if (position != -1) {
ProductDTO product = productList.get(position);
args.putLong("productId", product.getProdId());
args.putLong("prodId", product.getProdId());
}
NavHostFragment.findNavController(this).navigate(R.id.nav_product_detail, args);
}

View File

@@ -7,7 +7,6 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -24,6 +23,7 @@ import com.example.petstoremobile.dtos.ProductDTO;
import com.example.petstoremobile.dtos.ProductSupplierDTO;
import com.example.petstoremobile.dtos.SupplierDTO;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
@@ -48,6 +48,7 @@ public class ProductSupplierFragment extends Fragment
private ProductSupplierViewModel viewModel;
private ProductViewModel productViewModel;
private SupplierViewModel supplierViewModel;
private BulkDeleteHandler bulkDeleteHandler;
/**
* Initializes the fragment and its associated ViewModels.
@@ -74,6 +75,7 @@ public class ProductSupplierFragment extends Fragment
setupSupplierFilter();
setupSwipeRefresh();
setupFilterToggle();
setupBulkDelete();
binding.fabAddPS.setOnClickListener(v -> openDetail(-1));
@@ -90,6 +92,19 @@ public class ProductSupplierFragment extends Fragment
return binding.getRoot();
}
private void setupBulkDelete() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
binding.layoutBulkDelete,
binding.tvSelectionCount,
binding.btnBulkDelete,
adapter,
"relationship",
viewModel::bulkDeleteProductSuppliers,
this::loadData
);
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -152,26 +167,14 @@ public class ProductSupplierFragment extends Fragment
* Configures the product filter spinner.
*/
private void setupProductFilter() {
binding.spinnerProduct.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupFilterSpinner(binding.spinnerProduct, this::loadData);
}
/**
* Configures the supplier filter spinner.
*/
private void setupSupplierFilter() {
binding.spinnerSupplier.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupFilterSpinner(binding.spinnerSupplier, this::loadData);
}
/**
@@ -265,4 +268,11 @@ public class ProductSupplierFragment extends Fragment
*/
@Override
public void onProductSupplierClick(int position) { openDetail(position); }
@Override
public void onSelectionChanged(int count) {
if (bulkDeleteHandler != null) {
bulkDeleteHandler.onSelectionChanged(count);
}
}
}

View File

@@ -7,7 +7,6 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -133,13 +132,7 @@ public class PurchaseOrderFragment extends Fragment
* Configures the store filter spinner.
*/
private void setupStoreFilter() {
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
loadData();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadData);
}
/**

View File

@@ -2,7 +2,9 @@ package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import android.text.Editable;
@@ -13,38 +15,43 @@ import android.view.ViewGroup;
import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.SaleAdapter;
import com.example.petstoremobile.api.SaleApi;
import com.example.petstoremobile.databinding.FragmentSaleBinding;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.models.Sale;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.viewmodels.SaleViewModel;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
private FragmentSaleBinding binding;
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;
@Inject SaleApi api;
private SaleViewModel saleViewModel;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentSaleBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
setupRecyclerView();
loadSaleData();
setupSearch();
setupSwipeRefresh();
loadSales();
// Make the hamburger button open the drawer from listFragment
binding.btnHamburger.setOnClickListener(v -> {
Fragment parent = getParentFragment();
if (parent != null) {
@@ -55,7 +62,11 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
}
});
return binding.getRoot();
binding.fabAddSale.setOnClickListener(v ->
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail));
binding.btnOpenRefund.setOnClickListener(v ->
NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
}
@Override
@@ -64,19 +75,20 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
binding = null;
}
private void setupRecyclerView() {
adapter = new SaleAdapter(filteredList, this);
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerViewSales.setAdapter(adapter);
}
private void setupSearch() {
binding.etSearchSale.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void afterTextChanged(Editable s) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
filterSales(s.toString());
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
@@ -86,60 +98,46 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
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.getSaleDate() != null && s.getSaleDate().toLowerCase().contains(lower))
|| (s.getPaymentMethod() != null && s.getPaymentMethod().toLowerCase().contains(lower))
|| (s.getSaleId() != null && String.valueOf(s.getSaleId()).contains(lower))) {
filteredList.add(s);
}
}
}
adapter.notifyDataSetChanged();
if (adapter != null) adapter.notifyDataSetChanged();
}
private void setupSwipeRefresh() {
binding.swipeRefreshSale.setOnRefreshListener(() -> {
loadSaleData();
binding.swipeRefreshSale.setRefreshing(false);
loadSales();
});
}
private void loadSales() {
saleViewModel.getAllSales(0, 200).observe(getViewLifecycleOwner(), resource -> {
binding.swipeRefreshSale.setRefreshing(false);
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
saleList.clear();
saleList.addAll(resource.data.getContent());
filterSales(binding.etSearchSale.getText() != null
? binding.etSearchSale.getText().toString() : "");
}
});
}
// When a sale row is clicked, open the refund screen for that sale
@Override
public void onSaleClick(int position) {
Sale sale = filteredList.get(position);
SaleDTO sale = filteredList.get(position);
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());
NavHostFragment.findNavController(this).navigate(R.id.nav_refund_detail, args);
}
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() {
adapter = new SaleAdapter(filteredList, this);
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerViewSales.setAdapter(adapter);
if (sale.getSaleId() != null) {
args.putLong("saleId", sale.getSaleId());
}
if (sale.getIsRefund() != null) {
args.putBoolean("isRefund", sale.getIsRefund());
}
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail, args);
}
}

View File

@@ -1,14 +1,6 @@
package com.example.petstoremobile.fragments.listfragments;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
@@ -17,11 +9,21 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.ServiceAdapter;
import com.example.petstoremobile.databinding.FragmentServiceBinding;
import com.example.petstoremobile.dtos.ServiceDTO;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.viewmodels.ServiceViewModel;
import java.util.ArrayList;
@@ -29,16 +31,28 @@ import java.util.List;
import dagger.hilt.android.AndroidEntryPoint;
/**
* Fragment class for displaying a list of services in a RecyclerView.
*/
@AndroidEntryPoint
public class ServiceFragment extends Fragment implements ServiceAdapter.OnServiceClickListener {
private static final String TAG = "ServiceFragment";
private static final int PAGE_SIZE = 20;
private FragmentServiceBinding binding;
private List<ServiceDTO> serviceList = new ArrayList<>();
private final List<ServiceDTO> serviceList = new ArrayList<>();
private ServiceAdapter adapter;
private ServiceViewModel viewModel;
private BulkDeleteHandler bulkDeleteHandler;
// Pagination
private int currentPage = 0;
private boolean isLastPage = false;
private boolean isLoading = false;
/**
* Initializes the fragment and its associated ServiceViewModel.
* Initializes the fragment and its associated ViewModel.
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
@@ -58,12 +72,11 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
setupSearch();
setupSwipeRefresh();
setupFilterToggle();
loadServiceData();
setupBulkDelete();
loadServices(true);
//Add button to opens the add dialog
binding.fabAddService.setOnClickListener(v -> openServiceDetails(-1));
binding.fabAddService.setOnClickListener(v -> openDetail(null));
//Make the hamburger button open the drawer from listFragment
binding.btnHamburger.setOnClickListener(v -> {
Fragment parent = getParentFragment();
if (parent != null) {
@@ -77,6 +90,19 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
return binding.getRoot();
}
private void setupBulkDelete() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
binding.layoutBulkDelete,
binding.tvSelectionCount,
binding.btnBulkDelete,
adapter,
"service",
viewModel::bulkDeleteServices,
() -> loadServices(true)
);
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -95,100 +121,121 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
binding.layoutFilter.setVisibility(View.GONE);
binding.btnToggleFilter.setImageResource(android.R.drawable.ic_menu_search);
// Reset search when closing
// Reset filters when closing
binding.etSearchService.setText("");
}
});
}
/**
* Configures the search bar for filtering.
* Sets up the search bar for filtering.
*/
private void setupSearch() {
binding.etSearchService.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) {
loadServiceData();
loadServices(true);
}
@Override public void afterTextChanged(Editable s) {}
});
}
/**
* Sets up the SwipeRefreshLayout to allow manual reloading of service data.
* Initializes the RecyclerView with a layout manager and adapter.
*/
private void setupRecyclerView() {
adapter = new ServiceAdapter(serviceList, this);
binding.recyclerViewServices.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerViewServices.setAdapter(adapter);
binding.recyclerViewServices.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
if (dy <= 0) return;
LinearLayoutManager lm = (LinearLayoutManager) binding.recyclerViewServices.getLayoutManager();
if (lm == null) return;
int visible = lm.getChildCount();
int total = lm.getItemCount();
int firstVis = lm.findFirstVisibleItemPosition();
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
loadServices(false);
}
}
});
}
/**
* Sets up the SwipeRefreshLayout.
*/
private void setupSwipeRefresh() {
binding.swipeRefreshService.setOnRefreshListener(this::loadServiceData);
binding.swipeRefreshService.setOnRefreshListener(() -> loadServices(true));
}
/**
* Navigates to the service detail screen for editing an existing service or adding a new one.
* Fetches a page of services from the API.
*/
private void openServiceDetails(int position) {
//Make a bundle to pass data to the detail fragment
Bundle args = new Bundle();
private void loadServices(boolean reset) {
if (isLoading) return;
//if editing a service, add the service id to the bundle
if (position != -1) {
ServiceDTO service = serviceList.get(position);
args.putLong("serviceId", service.getServiceId());
if (reset) {
currentPage = 0;
isLastPage = false;
}
NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail, args);
}
/**
* Handles item click in the service list.
*/
@Override
public void onServiceClick(int position) {
openServiceDetails(position);
}
/**
* Fetches all service data from the server through the ViewModel and updates the UI.
*/
private void loadServiceData() {
String query = binding.etSearchService != null ? binding.etSearchService.getText().toString().trim() : "";
String query = binding.etSearchService.getText().toString().trim();
if (query.isEmpty()) query = null;
//Load services from the backend with query and default sort
viewModel.getAllServices(0, 100, query, "serviceName").observe(getViewLifecycleOwner(), resource -> {
viewModel.getAllServices(currentPage, PAGE_SIZE, query, "serviceName").observe(getViewLifecycleOwner(), resource -> {
if (resource == null) return;
// Check the status to see if the resource is loaded and display the data
switch (resource.status) {
case LOADING:
// Show loading indicator
isLoading = true;
binding.swipeRefreshService.setRefreshing(true);
break;
case SUCCESS:
// Hide loading indicator and display data
isLoading = false;
binding.swipeRefreshService.setRefreshing(false);
if (resource.data != null) {
serviceList.clear();
if (reset) serviceList.clear();
serviceList.addAll(resource.data.getContent());
adapter.notifyDataSetChanged();
isLastPage = resource.data.isLast();
if (!isLastPage) currentPage++;
}
break;
case ERROR:
// Hide loading indicator and toast error message
isLoading = false;
binding.swipeRefreshService.setRefreshing(false);
if (getContext() != null) {
Toast.makeText(getContext(), "Failed to load services: " + resource.message, Toast.LENGTH_SHORT).show();
}
Log.e("ServiceFragment", "Error loading services: " + resource.message);
Log.e(TAG, "Error: " + resource.message);
Toast.makeText(getContext(), "Failed to load services: " + resource.message, Toast.LENGTH_SHORT).show();
break;
}
});
}
/**
* Initializes the RecyclerView with a layout manager and adapter for services.
* Navigates to the service detail screen.
*/
private void setupRecyclerView() {
adapter = new ServiceAdapter(serviceList, this);
binding.recyclerViewServices.setLayoutManager(new LinearLayoutManager(getContext()));
binding.recyclerViewServices.setAdapter(adapter);
private void openDetail(ServiceDTO service) {
Bundle args = new Bundle();
if (service != null) {
args.putLong("serviceId", service.getServiceId());
}
NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail, args);
}
@Override
public void onServiceClick(int position) {
if (position >= 0 && position < serviceList.size()) {
openDetail(serviceList.get(position));
}
}
@Override
public void onSelectionChanged(int count) {
if (bulkDeleteHandler != null) {
bulkDeleteHandler.onSelectionChanged(count);
}
}
}

View File

@@ -0,0 +1,147 @@
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.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.EmployeeAdapter;
import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.dtos.EmployeeDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.fragments.ListFragment;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.*;
import retrofit2.*;
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
private List<EmployeeDTO> employeeList = new ArrayList<>();
private List<EmployeeDTO> filteredList = new ArrayList<>();
private EmployeeAdapter adapter;
private SwipeRefreshLayout swipeRefresh;
private EditText etSearch;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_staff, container, false);
setupRecyclerView(view);
setupSearch(view);
setupSwipeRefresh(view);
loadStaff();
FloatingActionButton fab = view.findViewById(R.id.fabAddStaff);
fab.setOnClickListener(v -> openDetail(-1));
ImageButton hamburger = view.findViewById(R.id.btnHamburgerStaff);
hamburger.setOnClickListener(v -> openDrawer());
return view;
}
private void openDrawer() {
Fragment parent = getParentFragment();
if (parent != null) {
Fragment grandParent = parent.getParentFragment();
if (grandParent instanceof ListFragment) {
((ListFragment) grandParent).openDrawer();
}
}
}
private void setupRecyclerView(View view) {
RecyclerView rv = view.findViewById(R.id.recyclerViewStaff);
adapter = new EmployeeAdapter(filteredList, this);
rv.setLayoutManager(new LinearLayoutManager(getContext()));
rv.setAdapter(adapter);
}
private void setupSearch(View view) {
etSearch = view.findViewById(R.id.etSearchStaff);
etSearch.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int a, int b, int c) {}
public void afterTextChanged(Editable s) {}
public void onTextChanged(CharSequence s, int a, int b, int c) {
filter(s.toString());
}
});
}
private void setupSwipeRefresh(View view) {
swipeRefresh = view.findViewById(R.id.swipeRefreshStaff);
swipeRefresh.setOnRefreshListener(this::loadStaff);
}
private void filter(String query) {
filteredList.clear();
if (query.isEmpty()) {
filteredList.addAll(employeeList);
} else {
String lower = query.toLowerCase();
for (EmployeeDTO e : employeeList) {
if ((e.getFullName() != null && e.getFullName().toLowerCase().contains(lower))
|| (e.getUsername() != null && e.getUsername().toLowerCase().contains(lower))
|| (e.getEmail() != null && e.getEmail().toLowerCase().contains(lower))
|| (e.getPhone() != null && e.getPhone().toLowerCase().contains(lower))) {
filteredList.add(e);
}
}
}
adapter.notifyDataSetChanged();
}
private void loadStaff() {
if (swipeRefresh != null) swipeRefresh.setRefreshing(true);
RetrofitClient.getEmployeeApi(requireContext()).getAllEmployees(0, 100)
.enqueue(new Callback<PageResponse<EmployeeDTO>>() {
public void onResponse(Call<PageResponse<EmployeeDTO>> c,
Response<PageResponse<EmployeeDTO>> r) {
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
if (r.isSuccessful() && r.body() != null) {
employeeList.clear();
employeeList.addAll(r.body().getContent());
filter(etSearch != null ? etSearch.getText().toString() : "");
} else {
Toast.makeText(getContext(), "Failed to load staff",
Toast.LENGTH_SHORT).show();
}
}
public void onFailure(Call<PageResponse<EmployeeDTO>> c, Throwable t) {
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
Log.e("StaffFragment", t.getMessage());
}
});
}
private void openDetail(int position) {
Bundle args = new Bundle();
if (position != -1) {
EmployeeDTO e = filteredList.get(position);
args.putLong("employeeId", e.getEmployeeId());
args.putString("username", e.getUsername() != null ? e.getUsername() : "");
args.putString("firstName", e.getFirstName() != null ? e.getFirstName() : "");
args.putString("lastName", e.getLastName() != null ? e.getLastName() : "");
args.putString("email", e.getEmail() != null ? e.getEmail() : "");
args.putString("phone", e.getPhone() != null ? e.getPhone() : "");
args.putString("role", e.getRole() != null ? e.getRole() : "STAFF");
args.putBoolean("active", Boolean.TRUE.equals(e.getActive()));
args.putBoolean("isEditing", true);
}
NavHostFragment.findNavController(this).navigate(R.id.nav_staff_detail, args);
}
@Override
public void onEmployeeClick(int position) {
openDetail(position);
}
}

View File

@@ -22,6 +22,8 @@ import com.example.petstoremobile.adapters.SupplierAdapter;
import com.example.petstoremobile.databinding.FragmentSupplierBinding;
import com.example.petstoremobile.dtos.SupplierDTO;
import com.example.petstoremobile.fragments.ListFragment;
import com.example.petstoremobile.utils.BulkDeleteHandler;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.viewmodels.SupplierViewModel;
import java.util.ArrayList;
@@ -36,6 +38,7 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
private List<SupplierDTO> supplierList = new ArrayList<>();
private SupplierAdapter adapter;
private SupplierViewModel viewModel;
private BulkDeleteHandler bulkDeleteHandler;
/**
* Initializes the fragment and its associated SupplierViewModel.
@@ -58,6 +61,7 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
setupSearch();
setupSwipeRefresh();
setupFilterToggle();
setupBulkDelete();
loadSupplierData();
//Add button to opens the add dialog
@@ -77,6 +81,19 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
return binding.getRoot();
}
private void setupBulkDelete() {
bulkDeleteHandler = new BulkDeleteHandler(
this,
binding.layoutBulkDelete,
binding.tvSelectionCount,
binding.btnBulkDelete,
adapter,
"supplier",
viewModel::bulkDeleteSuppliers,
this::loadSupplierData
);
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -146,6 +163,13 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
openSupplierDetails(position);
}
@Override
public void onSelectionChanged(int count) {
if (bulkDeleteHandler != null) {
bulkDeleteHandler.onSelectionChanged(count);
}
}
/**
* Fetches all supplier data from the server through the ViewModel and updates the UI.
*/

View File

@@ -19,7 +19,9 @@ import com.example.petstoremobile.viewmodels.AdoptionViewModel;
import com.example.petstoremobile.viewmodels.CustomerViewModel;
import com.example.petstoremobile.viewmodels.PetViewModel;
import com.example.petstoremobile.viewmodels.StoreViewModel;
import com.example.petstoremobile.viewmodels.UserViewModel;
import java.math.BigDecimal;
import java.util.*;
import dagger.hilt.android.AndroidEntryPoint;
@@ -37,10 +39,12 @@ public class AdoptionDetailFragment extends Fragment {
private long preselectedPetId = -1;
private long preselectedCustomerId = -1;
private long preselectedStoreId = -1;
private long preselectedEmployeeId = -1;
private List<PetDTO> petList = new ArrayList<>();
private List<CustomerDTO> customerList = new ArrayList<>();
private List<StoreDTO> storeList = new ArrayList<>();
private List<UserDTO> employeeList = new ArrayList<>();
private final String[] STATUSES = {"Pending", "Completed", "Cancelled"};
@@ -48,6 +52,7 @@ public class AdoptionDetailFragment extends Fragment {
private PetViewModel petViewModel;
private CustomerViewModel customerViewModel;
private StoreViewModel storeViewModel;
private UserViewModel userViewModel;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -56,6 +61,7 @@ public class AdoptionDetailFragment extends Fragment {
petViewModel = new ViewModelProvider(this).get(PetViewModel.class);
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
}
@Override
@@ -113,6 +119,7 @@ public class AdoptionDetailFragment extends Fragment {
loadPets();
loadCustomers();
loadStores();
loadEmployees();
}
/**
@@ -179,6 +186,27 @@ public class AdoptionDetailFragment extends Fragment {
preselectedStoreId, StoreDTO::getStoreId);
}
/**
* Loads the list of employees from the API.
*/
private void loadEmployees() {
userViewModel.getUsers("STAFF", 0, 100).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
employeeList = resource.data.getContent();
refreshEmployeeSpinner();
}
});
}
/**
* Populates the employee selection spinner with data.
*/
private void refreshEmployeeSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, employeeList,
UserDTO::getFullName, "-- Select Staff --",
preselectedEmployeeId, UserDTO::getId);
}
/**
* Handles arguments to determine if the fragment is in edit or add mode.
*/
@@ -210,12 +238,16 @@ public class AdoptionDetailFragment extends Fragment {
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
preselectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
preselectedStoreId = a.getSourceStoreId() != null ? a.getSourceStoreId() : -1;
preselectedEmployeeId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
binding.etAdoptionDate.setText(a.getAdoptionDate());
binding.etAdoptionFee.setText(a.getAdoptionFee() != null ? a.getAdoptionFee().toString() : "");
SpinnerUtils.setSelectionByValue(binding.spinnerAdoptionStatus, a.getAdoptionStatus());
refreshPetSpinner();
refreshCustomerSpinner();
refreshStoreSpinner();
refreshEmployeeSpinner();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Failed to load adoption: " + resource.message, Toast.LENGTH_SHORT).show();
}
@@ -240,17 +272,36 @@ public class AdoptionDetailFragment extends Fragment {
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
}
BigDecimal fee = BigDecimal.ZERO;
String feeStr = binding.etAdoptionFee.getText().toString().trim();
if (!feeStr.isEmpty()) {
try {
fee = new BigDecimal(feeStr);
} catch (NumberFormatException e) {
Toast.makeText(getContext(), "Invalid fee format", Toast.LENGTH_SHORT).show();
return;
}
}
CustomerDTO customer = customerList.get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
PetDTO pet = petList.get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
StoreDTO store = storeList.get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
Long employeeId = null;
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0) {
employeeId = employeeList.get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
}
String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()];
AdoptionDTO dto = new AdoptionDTO(
pet.getPetId(),
customer.getCustomerId(),
employeeId,
store.getStoreId(),
date,
status
status,
fee
);
if (isEditing) {

View File

@@ -1,14 +1,9 @@
package com.example.petstoremobile.fragments.listfragments.detailfragments;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -18,15 +13,16 @@ import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
import com.example.petstoremobile.databinding.FragmentInventoryDetailBinding;
import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.InventoryRequest;
import com.example.petstoremobile.dtos.ProductDTO;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.utils.InputValidator;
import com.example.petstoremobile.utils.Resource;
import com.example.petstoremobile.utils.SpinnerUtils;
import com.example.petstoremobile.viewmodels.InventoryViewModel;
import com.example.petstoremobile.viewmodels.ProductViewModel;
import com.example.petstoremobile.viewmodels.StoreViewModel;
import java.util.ArrayList;
import java.util.List;
@@ -43,20 +39,15 @@ public class InventoryDetailFragment extends Fragment {
private InventoryViewModel inventoryViewModel;
private ProductViewModel productViewModel;
private StoreViewModel storeViewModel;
private boolean isEditing = false;
private long inventoryId = -1;
private long preselectedStoreId = -1;
private long preselectedProductId = -1;
// The product selected from the dropdown
private ProductDTO selectedProduct = null;
// For debouncing product search
private final Handler searchHandler = new Handler(Looper.getMainLooper());
private Runnable searchRunnable;
// Dropdown list
private final List<ProductDTO> productSuggestions = new ArrayList<>();
private ArrayAdapter<String> dropdownAdapter;
private List<StoreDTO> storeList = new ArrayList<>();
private List<ProductDTO> productList = new ArrayList<>();
/**
* Initializes the view models.
@@ -66,6 +57,7 @@ public class InventoryDetailFragment extends Fragment {
super.onCreate(savedInstanceState);
inventoryViewModel = new ViewModelProvider(this).get(InventoryViewModel.class);
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
}
/**
@@ -85,94 +77,64 @@ public class InventoryDetailFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setupProductSearch();
loadSpinnersData();
handleArguments();
binding.btnInventoryBack.setOnClickListener(v -> navigateBack());
binding.btnSaveInventory.setOnClickListener(v -> saveInventory());
binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete());
// Setup dropdown adapter
dropdownAdapter = new BlackTextArrayAdapter<>(requireContext(),
android.R.layout.simple_dropdown_item_1line, new ArrayList<>());
binding.etProductSearch.setAdapter(dropdownAdapter);
binding.etProductSearch.setThreshold(1); // start showing after 1 character
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (searchRunnable != null) {
searchHandler.removeCallbacks(searchRunnable);
}
binding = null;
}
/**
* Sets up the product search dropdown.
* Fetches required data for spinners from the backend.
*/
private void setupProductSearch() {
binding.etProductSearch.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int i, int i1, int i2) {
}
private void loadSpinnersData() {
loadStores();
loadProducts();
}
@Override public void afterTextChanged(Editable s) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Clear selected product when user is typing again
selectedProduct = null;
binding.tvProductInfo.setVisibility(View.GONE);
if (searchRunnable != null)
searchHandler.removeCallbacks(searchRunnable);
String query = s.toString().trim();
if (query.isEmpty())
return;
searchRunnable = () -> searchProducts(query);
searchHandler.postDelayed(searchRunnable, 400);
}
});
// When user picks an item from the dropdown
binding.etProductSearch.setOnItemClickListener((parent, view, position, id) -> {
if (position < productSuggestions.size()) {
selectedProduct = productSuggestions.get(position);
// Show product details below the search box
binding.tvProductInfo.setText(
"ID: " + selectedProduct.getProdId()
+ "" + selectedProduct.getCategoryName());
binding.tvProductInfo.setVisibility(View.VISIBLE);
/**
* Loads the list of stores for the spinner.
*/
private void loadStores() {
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
storeList = resource.data.getContent();
refreshStoreSpinner();
}
});
}
private void refreshStoreSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, storeList,
StoreDTO::getStoreName, "-- Select Store --",
preselectedStoreId, StoreDTO::getStoreId);
}
/**
* Searches for products matching the query from the backend.
* Loads the list of products for the spinner.
*/
private void searchProducts(String query) {
if (getView() == null) return;
productViewModel.getAllProducts(query, null, 0, 20, "prodName").observe(getViewLifecycleOwner(), resource -> {
private void loadProducts() {
productViewModel.getAllProducts(null, null, 0, 500, "prodName").observe(getViewLifecycleOwner(), resource -> {
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
productSuggestions.clear();
productSuggestions.addAll(resource.data.getContent());
// Build display strings: "Product Name (ID: X)"
List<String> names = new ArrayList<>();
for (ProductDTO p : productSuggestions) {
names.add(p.getProdName() + " (ID: " + p.getProdId() + ")");
}
dropdownAdapter.clear();
dropdownAdapter.addAll(names);
dropdownAdapter.notifyDataSetChanged();
binding.etProductSearch.showDropDown();
productList = resource.data.getContent();
refreshProductSpinner();
}
});
}
private void refreshProductSpinner() {
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, productList,
ProductDTO::getProdName, "-- Select Product --",
preselectedProductId, ProductDTO::getProdId);
}
/**
* Handles fragment arguments to determine if we are in edit or add mode.
*/
@@ -193,7 +155,6 @@ public class InventoryDetailFragment extends Fragment {
isEditing = false;
binding.tvInventoryMode.setText("Add Inventory");
binding.tvInventoryId.setVisibility(View.GONE);
binding.tvProductInfo.setVisibility(View.GONE);
binding.btnDeleteInventory.setVisibility(View.GONE);
binding.btnSaveInventory.setText("Add");
}
@@ -207,20 +168,12 @@ public class InventoryDetailFragment extends Fragment {
if (resource == null) return;
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
InventoryDTO inv = resource.data;
binding.etProductSearch.setText(inv.getProductName());
binding.etQuantity.setText(String.valueOf(inv.getQuantity()));
if (inv.getProdId() != null) {
binding.tvProductInfo.setText(
"ID: " + inv.getProdId()
+ "" + inv.getCategoryName());
binding.tvProductInfo.setVisibility(View.VISIBLE);
selectedProduct = new ProductDTO();
selectedProduct.setProdId(inv.getProdId());
selectedProduct.setProdName(inv.getProductName());
selectedProduct.setCategoryName(inv.getCategoryName());
}
preselectedStoreId = inv.getStoreId() != null ? inv.getStoreId() : -1;
preselectedProductId = inv.getProdId() != null ? inv.getProdId() : -1;
refreshStoreSpinner();
refreshProductSpinner();
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Failed to load inventory: " + resource.message, Toast.LENGTH_SHORT).show();
}
@@ -231,9 +184,12 @@ public class InventoryDetailFragment extends Fragment {
* Validates input and saves the current inventory item details to the backend.
*/
private void saveInventory() {
if (selectedProduct == null) {
binding.etProductSearch.setError("Please select a product from the list");
binding.etProductSearch.requestFocus();
if (binding.spinnerInventoryStore.getSelectedItemPosition() == 0) {
Toast.makeText(getContext(), "Please select a store", Toast.LENGTH_SHORT).show();
return;
}
if (binding.spinnerInventoryProduct.getSelectedItemPosition() == 0) {
Toast.makeText(getContext(), "Please select a product", Toast.LENGTH_SHORT).show();
return;
}
@@ -243,8 +199,10 @@ public class InventoryDetailFragment extends Fragment {
}
int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim());
StoreDTO store = storeList.get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
ProductDTO product = productList.get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
InventoryRequest request = new InventoryRequest(selectedProduct.getProdId(), quantity);
InventoryDTO request = new InventoryDTO(product.getProdId(), store.getStoreId(), quantity);
setButtonsEnabled(false);
if (isEditing) {

View File

@@ -73,19 +73,27 @@ public class PurchaseOrderDetailFragment extends Fragment {
PurchaseOrderDTO po = resource.data;
binding.tvPODetailId.setText("PO #" + po.getPurchaseOrderId());
binding.tvPODetailSupplier.setText(po.getSupplierName());
binding.tvPODetailStore.setText(po.getStoreName() != null ? po.getStoreName() : "N/A");
binding.tvPODetailDate.setText(po.getOrderDate());
String status = po.getStatus() != null ? po.getStatus() : "";
binding.tvPODetailStatus.setText(status);
switch (status) {
case "Completed":
binding.tvPODetailStatus.setTextColor(Color.parseColor("#4CAF50")); break;
case "Pending":
binding.tvPODetailStatus.setTextColor(Color.parseColor("#FF9800")); break;
case "Cancelled":
binding.tvPODetailStatus.setTextColor(Color.parseColor("#F44336")); break;
switch (status.toUpperCase()) {
case "RECEIVED":
binding.tvPODetailStatus.setTextColor(Color.parseColor("#4CAF50"));
break;
case "PLACED":
binding.tvPODetailStatus.setTextColor(Color.parseColor("#2196F3"));
break;
case "PENDING":
binding.tvPODetailStatus.setTextColor(Color.parseColor("#FF9800"));
break;
case "CANCELLED":
binding.tvPODetailStatus.setTextColor(Color.parseColor("#F44336"));
break;
default:
binding.tvPODetailStatus.setTextColor(Color.parseColor("#9E9E9E")); break;
binding.tvPODetailStatus.setTextColor(Color.parseColor("#9E9E9E"));
break;
}
} else if (resource.status == Resource.Status.ERROR) {
Toast.makeText(getContext(), "Failed to load order: " + resource.message, Toast.LENGTH_SHORT).show();

View File

@@ -1,126 +0,0 @@
package com.example.petstoremobile.fragments.listfragments.detailfragments;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.example.petstoremobile.api.SaleApi;
import com.example.petstoremobile.databinding.FragmentRefundDetailBinding;
import com.example.petstoremobile.fragments.listfragments.SaleFragment;
import com.example.petstoremobile.utils.ActivityLogger;
import com.example.petstoremobile.utils.InputValidator;
import com.example.petstoremobile.utils.SpinnerUtils;
import javax.inject.Inject;
import dagger.hilt.android.AndroidEntryPoint;
@AndroidEntryPoint
public class RefundDetailFragment extends Fragment {
private FragmentRefundDetailBinding binding;
private int saleId;
private SaleFragment saleFragment;
@Inject SaleApi saleApi;
public void setSaleFragment(SaleFragment fragment) {
this.saleFragment = fragment;
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentRefundDetailBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setupSpinner();
handleArguments();
binding.btnRefundBack.setOnClickListener(v -> goBack());
binding.btnLoadSale.setOnClickListener(v -> loadSaleDetails());
binding.btnProcessRefund.setOnClickListener(v -> processRefund());
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
private void loadSaleDetails() {
String idText = binding.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
binding.tvSaleInfo.setText("Sale ID: " + id + " loaded. Enter reason and payment method to process refund.");
binding.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(binding.etRefundSaleId, "Sale ID"))
return;
if (!InputValidator.isNotEmpty(binding.etRefundReason, "Refund Reason"))
return;
String idText = binding.etRefundSaleId.getText().toString().trim();
String reason = binding.etRefundReason.getText().toString().trim();
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");
binding.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");
binding.tvSaleInfo.setText(info);
binding.tvSaleInfo.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
// Pre-select payment method
SpinnerUtils.setSelectionByValue(binding.spinnerRefundPayment, getArguments().getString("paymentMethod"));
}
}
private void goBack() {
NavHostFragment.findNavController(this).popBackStack();
}
private void setupSpinner() {
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerRefundPayment,
new String[] { "Cash", "Card", "Debit" });
}
}

View File

@@ -0,0 +1,510 @@
package com.example.petstoremobile.fragments.listfragments.detailfragments;
import android.app.AlertDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.*;
import android.widget.*;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R;
import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.dtos.PageResponse;
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() {
NavHostFragment.findNavController(this).popBackStack();
}
}

View File

@@ -0,0 +1,368 @@
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 androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R;
import com.example.petstoremobile.api.*;
import com.example.petstoremobile.dtos.*;
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, null, 0, 200, null)
.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() {
Bundle args = new Bundle();
args.putLong("saleId", saleId);
NavHostFragment.findNavController(this).navigate(R.id.nav_refund, args);
}
private void submitRefund() {
RefundDTO refundDTO = new RefundDTO(saleId, "Refund requested from mobile app");
RetrofitClient.getRefundApi(requireContext()).createRefund(refundDTO)
.enqueue(new Callback<RefundDTO>() {
public void onResponse(Call<RefundDTO> c, Response<RefundDTO> r) {
if (r.isSuccessful()) {
Toast.makeText(getContext(), "Refund request submitted!",
Toast.LENGTH_SHORT).show();
btnRefund.setVisibility(View.GONE);
} else {
try {
String err = r.errorBody().string();
Log.e("REFUND", "Error: " + err);
Toast.makeText(getContext(), "Error: " + err,
Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e("REFUND", "Failed to read error");
}
}
}
public void onFailure(Call<RefundDTO> c, Throwable t) {
Log.e("REFUND", "Failure: " + t.getMessage());
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
}
});
}
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
}

View File

@@ -0,0 +1,199 @@
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 androidx.navigation.fragment.NavHostFragment;
import com.example.petstoremobile.R;
import com.example.petstoremobile.api.EmployeeApi;
import com.example.petstoremobile.api.RetrofitClient;
import com.example.petstoremobile.dtos.EmployeeDTO;
import retrofit2.*;
public class StaffDetailFragment extends Fragment {
private TextView tvMode, tvStaffId;
private EditText etUsername, etPassword, etFirstName, etLastName, etEmail, etPhone;
private Spinner spinnerRole, spinnerStatus;
private Button btnSave, btnDelete, btnBack;
private long employeeId = -1;
private boolean isEditing = false;
private final String[] ROLES = {"STAFF", "ADMIN"};
private final String[] STATUSES = {"Active", "Inactive"};
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_staff_detail, container, false);
initViews(view);
setupSpinners();
handleArguments();
btnBack.setOnClickListener(v -> navigateBack());
btnSave.setOnClickListener(v -> save());
btnDelete.setOnClickListener(v -> confirmDelete());
return view;
}
private void initViews(View v) {
tvMode = v.findViewById(R.id.tvStaffMode);
tvStaffId = v.findViewById(R.id.tvStaffId);
etUsername = v.findViewById(R.id.etStaffUsername);
etPassword = v.findViewById(R.id.etStaffPassword);
etFirstName = v.findViewById(R.id.etStaffFirstName);
etLastName = v.findViewById(R.id.etStaffLastName);
etEmail = v.findViewById(R.id.etStaffEmail);
etPhone = v.findViewById(R.id.etStaffPhone);
spinnerRole = v.findViewById(R.id.spinnerStaffRole);
spinnerStatus = v.findViewById(R.id.spinnerStaffStatus);
btnSave = v.findViewById(R.id.btnSaveStaff);
btnDelete = v.findViewById(R.id.btnDeleteStaff);
btnBack = v.findViewById(R.id.btnStaffBack);
}
private void setupSpinners() {
spinnerRole.setAdapter(new ArrayAdapter<>(requireContext(),
android.R.layout.simple_spinner_item, ROLES));
spinnerStatus.setAdapter(new ArrayAdapter<>(requireContext(),
android.R.layout.simple_spinner_item, STATUSES));
}
private void handleArguments() {
Bundle a = getArguments();
if (a != null && a.getBoolean("isEditing", false)) {
isEditing = true;
employeeId = a.getLong("employeeId", -1);
tvMode.setText("Edit Staff Account");
tvStaffId.setText("ID: " + employeeId);
tvStaffId.setVisibility(View.VISIBLE);
etUsername.setText(a.getString("username", ""));
etFirstName.setText(a.getString("firstName", ""));
etLastName.setText(a.getString("lastName", ""));
etEmail.setText(a.getString("email", "")); // ← was showing fullName
etPhone.setText(a.getString("phone", ""));
btnDelete.setVisibility(View.VISIBLE);
// Pre-fill role
String role = a.getString("role", "STAFF");
for (int i = 0; i < ROLES.length; i++) {
if (ROLES[i].equals(role)) {
spinnerRole.setSelection(i);
break;
}
}
// Pre-fill status
boolean active = a.getBoolean("active", true);
spinnerStatus.setSelection(active ? 0 : 1);
} else {
isEditing = false;
employeeId = -1;
tvMode.setText("Add Staff Account");
btnDelete.setVisibility(View.GONE);
tvStaffId.setVisibility(View.GONE);
}
}
private void save() {
String username = etUsername.getText() != null ? etUsername.getText().toString().trim() : "";
String password = etPassword.getText() != null ? etPassword.getText().toString().trim() : "";
String firstName = etFirstName.getText() != null ? etFirstName.getText().toString().trim() : "";
String lastName = etLastName.getText() != null ? etLastName.getText().toString().trim() : "";
String email = etEmail.getText() != null ? etEmail.getText().toString().trim() : "";
String phone = etPhone.getText() != null ? etPhone.getText().toString().trim() : "";
String role = ROLES[spinnerRole.getSelectedItemPosition()];
boolean active = spinnerStatus.getSelectedItemPosition() == 0;
// Validation
if (username.isEmpty()) { etUsername.setError("Required"); return; }
if (!isEditing && password.isEmpty()) {
etPassword.setError("Required for new account"); return;
}
if (!isEditing && password.length() < 6) {
etPassword.setError("At least 6 characters"); return;
}
if (firstName.isEmpty()) { etFirstName.setError("Required"); return; }
if (lastName.isEmpty()) { etLastName.setError("Required"); return; }
if (email.isEmpty()) { etEmail.setError("Required"); return; }
if (phone.isEmpty()) { etPhone.setError("Required"); return; }
EmployeeDTO dto = new EmployeeDTO(
username,
password.isEmpty() ? null : password,
firstName,
lastName,
email,
phone,
role,
active
);
Log.d("STAFF_SAVE", "isEditing=" + isEditing
+ " employeeId=" + employeeId
+ " username=" + username);
EmployeeApi api = RetrofitClient.getEmployeeApi(requireContext());
if (isEditing && employeeId > 0) {
api.updateEmployee(employeeId, dto).enqueue(simpleCallback("Updated successfully"));
} else {
api.createEmployee(dto).enqueue(simpleCallback("Staff account created"));
}
}
private Callback<EmployeeDTO> simpleCallback(String msg) {
return new Callback<>() {
public void onResponse(Call<EmployeeDTO> c, Response<EmployeeDTO> r) {
Log.d("STAFF_SAVE", "Response: " + r.code());
if (r.isSuccessful()) {
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
navigateBack();
} else {
try {
String err = r.errorBody().string();
Log.e("STAFF_SAVE", "Error: " + err);
Toast.makeText(getContext(), "Error " + r.code() + ": " + err,
Toast.LENGTH_LONG).show();
} catch (Exception e) {
Log.e("STAFF_SAVE", "Failed to read error");
}
}
}
public void onFailure(Call<EmployeeDTO> c, Throwable t) {
Log.e("STAFF_SAVE", "Failure: " + t.getMessage());
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
}
};
}
private void confirmDelete() {
new AlertDialog.Builder(requireContext())
.setTitle("Delete Staff Account?")
.setMessage("This will permanently delete this staff account.")
.setPositiveButton("Yes", (d, w) ->
RetrofitClient.getEmployeeApi(requireContext())
.deleteEmployee(employeeId)
.enqueue(new Callback<Void>() {
public void onResponse(Call<Void> c, Response<Void> r) {
navigateBack();
}
public void onFailure(Call<Void> c, Throwable t) {
Toast.makeText(getContext(), "Delete failed",
Toast.LENGTH_SHORT).show();
}
}))
.setNegativeButton("No", null).show();
}
private void navigateBack() {
NavHostFragment.findNavController(this).popBackStack();
}
}

View File

@@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.AdoptionApi;
import com.example.petstoremobile.dtos.AdoptionDTO;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.utils.Resource;
@@ -23,8 +24,8 @@ public class AdoptionRepository extends BaseRepository {
/**
* Retrieves a paginated list of all adoptions from the API.
*/
public LiveData<Resource<PageResponse<AdoptionDTO>>> getAllAdoptions(int page, int size) {
return executeCall(adoptionApi.getAllAdoptions(page, size));
public LiveData<Resource<PageResponse<AdoptionDTO>>> getAllAdoptions(int page, int size, String query, String status, Long storeId, String date, Long employeeId) {
return executeCall(adoptionApi.getAllAdoptions(page, size, query, status, storeId, date, employeeId));
}
/**
@@ -54,4 +55,11 @@ public class AdoptionRepository extends BaseRepository {
public LiveData<Resource<Void>> deleteAdoption(Long id) {
return executeCall(adoptionApi.deleteAdoption(id));
}
/**
* Sends a request to the API to delete multiple adoption records.
*/
public LiveData<Resource<Void>> bulkDeleteAdoptions(BulkDeleteRequest request) {
return executeCall(adoptionApi.bulkDeleteAdoptions(request));
}
}

View File

@@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.AppointmentApi;
import com.example.petstoremobile.dtos.AppointmentDTO;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.utils.Resource;
@@ -54,4 +55,11 @@ public class AppointmentRepository extends BaseRepository {
public LiveData<Resource<Void>> deleteAppointment(Long id) {
return executeCall(appointmentApi.deleteAppointment(id));
}
/**
* Sends a request to the API to delete multiple appointment records.
*/
public LiveData<Resource<Void>> bulkDeleteAppointments(BulkDeleteRequest request) {
return executeCall(appointmentApi.bulkDeleteAppointments(request));
}
}

View File

@@ -0,0 +1,42 @@
package com.example.petstoremobile.repositories;
import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.EmployeeApi;
import com.example.petstoremobile.dtos.EmployeeDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.utils.Resource;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class EmployeeRepository extends BaseRepository {
private final EmployeeApi employeeApi;
@Inject
public EmployeeRepository(EmployeeApi employeeApi) {
super("EmployeeRepository");
this.employeeApi = employeeApi;
}
public LiveData<Resource<PageResponse<EmployeeDTO>>> getAllEmployees(int page, int size) {
return executeCall(employeeApi.getAllEmployees(page, size));
}
public LiveData<Resource<EmployeeDTO>> getEmployeeById(Long id) {
return executeCall(employeeApi.getEmployeeById(id));
}
public LiveData<Resource<EmployeeDTO>> createEmployee(EmployeeDTO dto) {
return executeCall(employeeApi.createEmployee(dto));
}
public LiveData<Resource<EmployeeDTO>> updateEmployee(Long id, EmployeeDTO dto) {
return executeCall(employeeApi.updateEmployee(id, dto));
}
public LiveData<Resource<Void>> deleteEmployee(Long id) {
return executeCall(employeeApi.deleteEmployee(id));
}
}

View File

@@ -5,7 +5,6 @@ import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.InventoryApi;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.InventoryRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.utils.Resource;
@@ -39,11 +38,11 @@ public class InventoryRepository extends BaseRepository {
/**
* Sends a request to the API to create a new inventory record.
*/
public LiveData<Resource<InventoryDTO>> createInventory(InventoryRequest request) {
public LiveData<Resource<InventoryDTO>> createInventory(InventoryDTO request) {
return executeCall(inventoryApi.createInventory(request));
}
public LiveData<Resource<InventoryDTO>> updateInventory(Long id, InventoryRequest request) {
public LiveData<Resource<InventoryDTO>> updateInventory(Long id, InventoryDTO request) {
return executeCall(inventoryApi.updateInventory(id, request));
}

View File

@@ -3,6 +3,7 @@ package com.example.petstoremobile.repositories;
import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.PetApi;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.utils.Resource;
@@ -57,6 +58,13 @@ public class PetRepository extends BaseRepository {
return executeCall(petApi.deletePet(id));
}
/**
* Sends a request to the API to delete multiple pet records.
*/
public LiveData<Resource<Void>> bulkDeletePets(BulkDeleteRequest request) {
return executeCall(petApi.bulkDeletePets(request));
}
/**
* Uploads an image file for a specific pet via the API.
*/

View File

@@ -3,6 +3,7 @@ package com.example.petstoremobile.repositories;
import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.ProductSupplierApi;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ProductSupplierDTO;
import com.example.petstoremobile.utils.Resource;
@@ -54,4 +55,8 @@ public class ProductSupplierRepository extends BaseRepository {
public LiveData<Resource<Void>> deleteProductSupplier(Long productId, Long supplierId) {
return executeCall(api.deleteProductSupplier(productId, supplierId));
}
public LiveData<Resource<Void>> bulkDeleteProductSuppliers(BulkDeleteRequest request) {
return executeCall(api.bulkDeleteProductSuppliers(request));
}
}

View File

@@ -12,25 +12,25 @@ import javax.inject.Singleton;
@Singleton
public class PurchaseOrderRepository extends BaseRepository {
private final PurchaseOrderApi api;
private final PurchaseOrderApi purchaseOrderApi;
@Inject
public PurchaseOrderRepository(PurchaseOrderApi api) {
super("PurchaseOrderRepo");
this.api = api;
public PurchaseOrderRepository(PurchaseOrderApi purchaseOrderApi) {
super("PurchaseOrderRepository");
this.purchaseOrderApi = purchaseOrderApi;
}
/**
* Retrieves a paginated list of all purchase orders from the API.
*/
public LiveData<Resource<PageResponse<PurchaseOrderDTO>>> getAllPurchaseOrders(int page, int size, String query, Long storeId, String sort) {
return executeCall(api.getAllPurchaseOrders(page, size, query, storeId, sort));
return executeCall(purchaseOrderApi.getAllPurchaseOrders(page, size, query, storeId, sort));
}
/**
* Retrieves a specific purchase order by its ID from the API.
*/
public LiveData<Resource<PurchaseOrderDTO>> getPurchaseOrderById(Long id) {
return executeCall(api.getPurchaseOrderById(id));
return executeCall(purchaseOrderApi.getPurchaseOrderById(id));
}
}
}

View File

@@ -0,0 +1,34 @@
package com.example.petstoremobile.repositories;
import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.SaleApi;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.utils.Resource;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class SaleRepository extends BaseRepository {
private final SaleApi saleApi;
@Inject
public SaleRepository(SaleApi saleApi) {
super("SaleRepository");
this.saleApi = saleApi;
}
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size) {
return executeCall(saleApi.getAllSales(page, size));
}
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
return executeCall(saleApi.getSaleById(id));
}
public LiveData<Resource<SaleDTO>> createSale(SaleDTO sale) {
return executeCall(saleApi.createSale(sale));
}
}

View File

@@ -3,6 +3,7 @@ package com.example.petstoremobile.repositories;
import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.ServiceApi;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ServiceDTO;
import com.example.petstoremobile.utils.Resource;
@@ -54,4 +55,11 @@ public class ServiceRepository extends BaseRepository {
public LiveData<Resource<Void>> deleteService(Long id) {
return executeCall(serviceApi.deleteService(id));
}
/**
* Sends a request to the API to delete multiple services.
*/
public LiveData<Resource<Void>> bulkDeleteServices(BulkDeleteRequest request) {
return executeCall(serviceApi.bulkDeleteServices(request));
}
}

View File

@@ -3,6 +3,7 @@ package com.example.petstoremobile.repositories;
import androidx.lifecycle.LiveData;
import com.example.petstoremobile.api.SupplierApi;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SupplierDTO;
import com.example.petstoremobile.utils.Resource;
@@ -54,4 +55,11 @@ public class SupplierRepository extends BaseRepository {
public LiveData<Resource<Void>> deleteSupplier(Long id) {
return executeCall(supplierApi.deleteSupplier(id));
}
/**
* Sends a request to the API to delete multiple supplier records.
*/
public LiveData<Resource<Void>> bulkDeleteSuppliers(BulkDeleteRequest request) {
return executeCall(supplierApi.bulkDeleteSuppliers(request));
}
}

View File

@@ -0,0 +1,109 @@
package com.example.petstoremobile.utils;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData;
import java.util.List;
/**
* A helper class to handle the UI and logic for bulk deletion across different fragments.
* Now supports String keys to accommodate both simple and composite keys.
*/
public class BulkDeleteHandler {
/**
* Interface that adapters must implement to support bulk selection.
*/
public interface SelectableAdapter {
List<String> getSelectedKeys();
void clearSelection();
}
/**
* Functional interface for the API call execution.
*/
public interface BulkDeleteOperation {
LiveData<Resource<Void>> execute(List<String> keys);
}
private final Fragment fragment;
private final View layoutBar;
private final TextView tvCount;
private final SelectableAdapter adapter;
private final BulkDeleteOperation operation;
private final Runnable onSuccess;
private final String itemName;
public BulkDeleteHandler(Fragment fragment,
View layoutBar,
TextView tvCount,
Button btnDelete,
SelectableAdapter adapter,
String itemName,
BulkDeleteOperation operation,
Runnable onSuccess) {
this.fragment = fragment;
this.layoutBar = layoutBar;
this.tvCount = tvCount;
this.adapter = adapter;
this.operation = operation;
this.onSuccess = onSuccess;
this.itemName = itemName;
btnDelete.setOnClickListener(v -> confirmDelete());
}
/**
* Updates the UI when the selection count changes.
*/
public void onSelectionChanged(int selectedCount) {
if (selectedCount > 0) {
layoutBar.setVisibility(View.VISIBLE);
tvCount.setText(selectedCount + " selected");
} else {
hideBar();
}
}
/**
* Hides the bulk delete bar and resets state.
*/
public void hideBar() {
if (layoutBar != null) {
layoutBar.setVisibility(View.GONE);
}
}
/**
* Shows the confirmation dialog.
*/
private void confirmDelete() {
List<String> keys = adapter.getSelectedKeys();
if (keys.isEmpty()) return;
DialogUtils.showBulkDeleteConfirmDialog(fragment.requireContext(), keys.size(), () -> performDelete(keys));
}
/**
* Executes the deletion via the provided operation.
*/
private void performDelete(List<String> keys) {
operation.execute(keys).observe(fragment.getViewLifecycleOwner(), resource -> {
if (resource != null && resource.status != Resource.Status.LOADING) {
if (resource.status == Resource.Status.SUCCESS) {
adapter.clearSelection();
hideBar();
onSuccess.run();
Toast.makeText(fragment.getContext(), keys.size() + " " + itemName + "(s) deleted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(fragment.getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
}
}
});
}
}

View File

@@ -34,6 +34,18 @@ public class DialogUtils {
showConfirmDialog(context, "Delete " + itemName + "?", "Are you sure you want to delete this " + itemName.toLowerCase() + "? This action cannot be undone.", callback);
}
/**
* Shows a confirmation dialog with specific "Delete" and "Cancel" buttons.
*/
public static void showBulkDeleteConfirmDialog(Context context, int count, DialogCallback callback) {
new AlertDialog.Builder(context)
.setTitle("Delete " + count + " item(s)?")
.setMessage("This cannot be undone.")
.setPositiveButton("Delete", (dialog, which) -> callback.onConfirm())
.setNegativeButton("Cancel", null)
.show();
}
/**
* Shows a simple information or error dialog with an "OK" button.
*/

View File

@@ -0,0 +1,68 @@
package com.example.petstoremobile.utils;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class to manage selection state in Adapters for bulk operations.
* Uses String keys to support both simple Long IDs and composite keys (e.g., "id1-id2").
*/
public class SelectionHelper {
private final List<String> selectedKeys = new ArrayList<>();
private boolean selectionMode = false;
private final SelectionListener listener;
public interface SelectionListener {
void onSelectionChanged(int count);
void onSelectionModeToggle(boolean selectionMode);
}
public SelectionHelper(SelectionListener listener) {
this.listener = listener;
}
public void toggleSelection(String key) {
if (key == null) return;
if (selectedKeys.contains(key)) {
selectedKeys.remove(key);
} else {
selectedKeys.add(key);
}
listener.onSelectionChanged(selectedKeys.size());
if (selectedKeys.isEmpty() && selectionMode) {
selectionMode = false;
listener.onSelectionModeToggle(false);
}
}
public void startSelection(String key) {
if (key == null) return;
selectionMode = true;
selectedKeys.add(key);
listener.onSelectionChanged(selectedKeys.size());
listener.onSelectionModeToggle(true);
}
public boolean isSelected(String key) {
return selectedKeys.contains(key);
}
public boolean isInSelectionMode() {
return selectionMode;
}
public List<String> getSelectedKeys() {
return new ArrayList<>(selectedKeys);
}
public void clearSelection() {
selectedKeys.clear();
selectionMode = false;
listener.onSelectionChanged(0);
listener.onSelectionModeToggle(false);
}
}

View File

@@ -1,6 +1,8 @@
package com.example.petstoremobile.utils;
import android.content.Context;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
@@ -70,6 +72,30 @@ public class SpinnerUtils {
}
}
/**
* Sets up a simple string spinner for filtering with a callback.
*/
public static void setupStringFilterSpinner(Context context, Spinner spinner, String[] items, Runnable onSelectionChanged) {
WhiteTextArrayAdapter<String> adapter = new WhiteTextArrayAdapter<>(context,
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
setupFilterSpinner(spinner, onSelectionChanged);
}
/**
* Attaches an item selected listener to a spinner that triggers a callback.
*/
public static void setupFilterSpinner(Spinner spinner, Runnable onSelectionChanged) {
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
onSelectionChanged.run();
}
@Override public void onNothingSelected(AdapterView<?> parent) {}
});
}
/**
* Sets the selection of a spinner based on a string value.
*/

View File

@@ -4,10 +4,13 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.AdoptionDTO;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.repositories.AdoptionRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -22,10 +25,10 @@ public class AdoptionViewModel extends ViewModel {
}
/**
* Fetches a paginated list of all adoptions.
* Fetches a paginated list of all adoptions with filters.
*/
public LiveData<Resource<PageResponse<AdoptionDTO>>> getAllAdoptions(int page, int size) {
return repository.getAllAdoptions(page, size);
public LiveData<Resource<PageResponse<AdoptionDTO>>> getAllAdoptions(int page, int size, String query, String status, Long storeId, String date, Long employeeId) {
return repository.getAllAdoptions(page, size, query, status, storeId, date, employeeId);
}
/**
@@ -55,4 +58,11 @@ public class AdoptionViewModel extends ViewModel {
public LiveData<Resource<Void>> deleteAdoption(Long id) {
return repository.deleteAdoption(id);
}
/**
* Deletes multiple adoption records.
*/
public LiveData<Resource<Void>> bulkDeleteAdoptions(List<String> ids) {
return repository.bulkDeleteAdoptions(new BulkDeleteRequest(ids));
}
}

View File

@@ -4,10 +4,13 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.AppointmentDTO;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.repositories.AppointmentRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -55,4 +58,11 @@ public class AppointmentViewModel extends ViewModel {
public LiveData<Resource<Void>> deleteAppointment(Long id) {
return repository.deleteAppointment(id);
}
/**
* Deletes multiple appointment records.
*/
public LiveData<Resource<Void>> bulkDeleteAppointments(List<String> ids) {
return repository.bulkDeleteAppointments(new BulkDeleteRequest(ids));
}
}

View File

@@ -0,0 +1,43 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.EmployeeDTO;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.repositories.EmployeeRepository;
import com.example.petstoremobile.utils.Resource;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class EmployeeViewModel extends ViewModel {
private final EmployeeRepository employeeRepository;
@Inject
public EmployeeViewModel(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public LiveData<Resource<PageResponse<EmployeeDTO>>> getAllEmployees(int page, int size) {
return employeeRepository.getAllEmployees(page, size);
}
public LiveData<Resource<EmployeeDTO>> getEmployeeById(Long id) {
return employeeRepository.getEmployeeById(id);
}
public LiveData<Resource<EmployeeDTO>> createEmployee(EmployeeDTO dto) {
return employeeRepository.createEmployee(dto);
}
public LiveData<Resource<EmployeeDTO>> updateEmployee(Long id, EmployeeDTO dto) {
return employeeRepository.updateEmployee(id, dto);
}
public LiveData<Resource<Void>> deleteEmployee(Long id) {
return employeeRepository.deleteEmployee(id);
}
}

View File

@@ -6,7 +6,6 @@ import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.CategoryDTO;
import com.example.petstoremobile.dtos.InventoryDTO;
import com.example.petstoremobile.dtos.InventoryRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.StoreDTO;
import com.example.petstoremobile.repositories.CategoryRepository;
@@ -50,14 +49,14 @@ public class InventoryViewModel extends ViewModel {
/**
* Creates a new inventory record.
*/
public LiveData<Resource<InventoryDTO>> createInventory(InventoryRequest request) {
public LiveData<Resource<InventoryDTO>> createInventory(InventoryDTO request) {
return inventoryRepository.createInventory(request);
}
/**
* Updates an existing inventory record by ID.
*/
public LiveData<Resource<InventoryDTO>> updateInventory(Long id, InventoryRequest request) {
public LiveData<Resource<InventoryDTO>> updateInventory(Long id, InventoryDTO request) {
return inventoryRepository.updateInventory(id, request);
}
@@ -71,7 +70,7 @@ public class InventoryViewModel extends ViewModel {
/**
* Deletes multiple inventory records in a single request.
*/
public LiveData<Resource<Void>> bulkDeleteInventory(List<Long> ids) {
public LiveData<Resource<Void>> bulkDeleteInventory(List<String> ids) {
return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids));
}

View File

@@ -3,11 +3,14 @@ package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.PetDTO;
import com.example.petstoremobile.repositories.PetRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -57,6 +60,13 @@ public class PetViewModel extends ViewModel {
return repository.deletePet(id);
}
/**
* Deletes multiple pet records.
*/
public LiveData<Resource<Void>> bulkDeletePets(List<String> ids) {
return repository.bulkDeletePets(new BulkDeleteRequest(ids));
}
/**
* Uploads an image for a specific pet.
*/

View File

@@ -3,11 +3,14 @@ package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ProductSupplierDTO;
import com.example.petstoremobile.repositories.ProductSupplierRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -48,4 +51,8 @@ public class ProductSupplierViewModel extends ViewModel {
public LiveData<Resource<Void>> deleteProductSupplier(Long productId, Long supplierId) {
return repository.deleteProductSupplier(productId, supplierId);
}
public LiveData<Resource<Void>> bulkDeleteProductSuppliers(List<String> ids) {
return repository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids));
}
}

View File

@@ -0,0 +1,35 @@
package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SaleDTO;
import com.example.petstoremobile.repositories.SaleRepository;
import com.example.petstoremobile.utils.Resource;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@HiltViewModel
public class SaleViewModel extends ViewModel {
private final SaleRepository saleRepository;
@Inject
public SaleViewModel(SaleRepository saleRepository) {
this.saleRepository = saleRepository;
}
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size) {
return saleRepository.getAllSales(page, size);
}
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
return saleRepository.getSaleById(id);
}
public LiveData<Resource<SaleDTO>> createSale(SaleDTO sale) {
return saleRepository.createSale(sale);
}
}

View File

@@ -3,11 +3,14 @@ package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.ServiceDTO;
import com.example.petstoremobile.repositories.ServiceRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -55,4 +58,11 @@ public class ServiceViewModel extends ViewModel {
public LiveData<Resource<Void>> deleteService(Long id) {
return repository.deleteService(id);
}
/**
* Deletes multiple services.
*/
public LiveData<Resource<Void>> bulkDeleteServices(List<String> ids) {
return repository.bulkDeleteServices(new BulkDeleteRequest(ids));
}
}

View File

@@ -3,11 +3,14 @@ package com.example.petstoremobile.viewmodels;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import com.example.petstoremobile.dtos.BulkDeleteRequest;
import com.example.petstoremobile.dtos.PageResponse;
import com.example.petstoremobile.dtos.SupplierDTO;
import com.example.petstoremobile.repositories.SupplierRepository;
import com.example.petstoremobile.utils.Resource;
import java.util.List;
import javax.inject.Inject;
import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -55,4 +58,11 @@ public class SupplierViewModel extends ViewModel {
public LiveData<Resource<Void>> deleteSupplier(Long id) {
return repository.deleteSupplier(id);
}
/**
* Deletes multiple supplier records.
*/
public LiveData<Resource<Void>> bulkDeleteSuppliers(List<String> ids) {
return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids));
}
}

View File

@@ -12,6 +12,7 @@
android:orientation="vertical">
<LinearLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="56dp"
android:background="@color/primary_dark"
@@ -34,7 +35,8 @@
android:text="Adoptions"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold"/>
android:textStyle="bold"
android:layout_marginStart="8dp"/>
<ImageButton
android:id="@+id/btnToggleCalendarModeAdoption"
@@ -45,6 +47,120 @@
app:tint="@color/white"
android:contentDescription="Toggle Calendar Mode"/>
<ImageButton
android:id="@+id/btnToggleFilterAdoption"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@android:drawable/ic_menu_search"
android:background="?attr/selectableItemBackgroundBorderless"
app:tint="@color/white"
android:contentDescription="Toggle filter"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutFilterAdoption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:visibility="gone"
android:background="@color/primary_dark"
android:elevation="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="44dp"
android:background="@drawable/bg_search_bar"
android:gravity="center_vertical"
android:paddingStart="12dp"
android:paddingEnd="12dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@android:drawable/ic_menu_search"
android:alpha="0.6"/>
<EditText
android:id="@+id/etSearchAdoption"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Search by customer or pet..."
android:inputType="text"
android:background="@android:color/transparent"
android:textColor="@color/text_dark"
android:textColorHint="#99000000"
android:textSize="14sp"
android:paddingStart="8dp"
android:paddingEnd="8dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="8dp">
<Spinner
android:id="@+id/spinnerStatusAdoption"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:background="@drawable/bg_spinner"
android:paddingStart="12dp"
android:paddingEnd="8dp"/>
<View
android:layout_width="8dp"
android:layout_height="0dp"/>
<Spinner
android:id="@+id/spinnerStoreAdoption"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_weight="1"
android:background="@drawable/bg_spinner"
android:paddingStart="12dp"
android:paddingEnd="8dp"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutBulkDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/primary_medium"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:visibility="gone">
<TextView
android:id="@+id/tvSelectionCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 selected"
android:textColor="@color/white"/>
<Button
android:id="@+id/btnBulkDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Selected"
android:backgroundTint="@color/accent_coral"
android:textColor="@color/white"/>
</LinearLayout>
<com.prolificinteractive.materialcalendarview.MaterialCalendarView
@@ -57,19 +173,6 @@
app:mcv_calendarMode="week"
app:mcv_tileHeight="40dp" />
<EditText
android:id="@+id/etSearchAdoption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:hint="Search by customer or pet..."
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/swipeRefreshAdoption"
android:layout_width="match_parent"
@@ -96,4 +199,4 @@
app:srcCompat="@android:drawable/ic_input_add"
app:tint="@color/white"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -95,6 +95,21 @@
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<!-- Employee -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Handled By (Staff)"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<Spinner
android:id="@+id/spinnerAdoptionEmployee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -129,6 +144,23 @@
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
android:layout_marginBottom="16dp"/>
<!-- Adoption Fee -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adoption Fee"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etAdoptionFee"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="0.00"
android:inputType="numberDecimal"
android:layout_marginBottom="16dp"/>
<!-- Status -->
<TextView
android:layout_width="wrap_content"

View 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>

View File

@@ -144,6 +144,37 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layoutBulkDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/primary_medium"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:visibility="gone">
<TextView
android:id="@+id/tvSelectionCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 selected"
android:textColor="@color/white"/>
<Button
android:id="@+id/btnBulkDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Selected"
android:backgroundTint="@color/accent_coral"
android:textColor="@color/white"/>
</LinearLayout>
<com.prolificinteractive.materialcalendarview.MaterialCalendarView
android:id="@+id/calendarView"
android:layout_width="match_parent"

View File

@@ -106,6 +106,7 @@
<!-- Bulk-delete action bar (hidden until long-press) -->
<LinearLayout
android:id="@+id/layoutBulkDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
@@ -114,7 +115,8 @@
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp">
android:paddingBottom="4dp"
android:visibility="gone">
<TextView
android:id="@+id/tvSelectionCount"
@@ -122,8 +124,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 selected"
android:textColor="@color/white"
android:visibility="gone"/>
android:textColor="@color/white"/>
<Button
android:id="@+id/btnBulkDelete"
@@ -131,8 +132,7 @@
android:layout_height="wrap_content"
android:text="Delete Selected"
android:backgroundTint="@color/accent_coral"
android:textColor="@color/white"
android:visibility="gone"/>
android:textColor="@color/white"/>
</LinearLayout>

View File

@@ -67,7 +67,23 @@
android:layout_marginBottom="12dp"
android:visibility="gone"/>
<!-- Product search label -->
<!-- Store selection label -->
<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"/>
<!-- Store Spinner -->
<Spinner
android:id="@+id/spinnerInventoryStore"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<!-- Product selection label -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -76,25 +92,12 @@
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<!-- AutoComplete search box -->
<AutoCompleteTextView
android:id="@+id/etProductSearch"
<!-- Product Spinner -->
<Spinner
android:id="@+id/spinnerInventoryProduct"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Search product name…"
android:inputType="text"
android:completionThreshold="1"
android:layout_marginBottom="4dp"/>
<!-- Selected product info (ID + category) shown after picking -->
<TextView
android:id="@+id/tvProductInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#888888"
android:textSize="12sp"
android:layout_marginBottom="16dp"
android:visibility="gone"/>
android:layout_marginBottom="16dp"/>
<!-- Quantity label -->
<TextView

View File

@@ -63,7 +63,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"/>
@@ -216,6 +216,41 @@
android:textSize="15sp"/>
</LinearLayout>
<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>
<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
android:id="@+id/drawerPurchaseOrderView"
android:layout_width="match_parent"

View File

@@ -132,6 +132,37 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layoutBulkDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/primary_medium"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:visibility="gone">
<TextView
android:id="@+id/tvSelectionCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 selected"
android:textColor="@color/white"/>
<Button
android:id="@+id/btnBulkDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Selected"
android:backgroundTint="@color/accent_coral"
android:textColor="@color/white"/>
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshPet"
android:layout_width="match_parent"

View File

@@ -123,6 +123,37 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layoutBulkDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/primary_medium"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:visibility="gone">
<TextView
android:id="@+id/tvSelectionCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 selected"
android:textColor="@color/white"/>
<Button
android:id="@+id/btnBulkDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Selected"
android:backgroundTint="@color/accent_coral"
android:textColor="@color/white"/>
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshPS"
android:layout_width="match_parent"

View File

@@ -70,6 +70,22 @@
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Store"
android:textColor="@color/text_light"
android:textSize="12sp"/>
<TextView
android:id="@+id/tvPODetailStore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_dark"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View 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>

View File

@@ -43,7 +43,7 @@
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"
@@ -51,6 +51,14 @@
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"
android:layout_width="match_parent"
@@ -66,4 +74,16 @@
</LinearLayout>
<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>

View 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>

View File

@@ -93,6 +93,37 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layoutBulkDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/primary_medium"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:visibility="gone">
<TextView
android:id="@+id/tvSelectionCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 selected"
android:textColor="@color/white"/>
<Button
android:id="@+id/btnBulkDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Selected"
android:backgroundTint="@color/accent_coral"
android:textColor="@color/white"/>
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshService"
android:layout_width="match_parent"

View 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>

View File

@@ -0,0 +1,233 @@
<?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/tvStaffMode"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Add Staff Account"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold"/>
<Button
android:id="@+id/btnDeleteStaff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="@color/accent_coral"
android:text="Delete"
android:textColor="@color/white"/>
</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">
<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/tvStaffId"
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"/>
<!-- Username -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etStaffUsername"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter username"
android:inputType="text"
android:layout_marginBottom="16dp"/>
<!-- Password -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Password"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etStaffPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter password (leave blank to keep current)"
android:inputType="textPassword"
android:layout_marginBottom="16dp"/>
<!-- First Name -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="First Name"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etStaffFirstName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter first name"
android:inputType="textPersonName"
android:layout_marginBottom="16dp"/>
<!-- Last Name -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Last Name"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etStaffLastName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter last name"
android:inputType="textPersonName"
android:layout_marginBottom="16dp"/>
<!-- Email -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Email"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etStaffEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter email"
android:inputType="textEmailAddress"
android:layout_marginBottom="16dp"/>
<!-- Phone -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Phone"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<EditText
android:id="@+id/etStaffPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter phone number"
android:inputType="phone"
android:layout_marginBottom="16dp"/>
<!-- Role -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Role"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<Spinner
android:id="@+id/spinnerStaffRole"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"/>
<!-- Status -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status"
android:textColor="@color/text_dark"
android:textSize="12sp"
android:layout_marginBottom="4dp"/>
<Spinner
android:id="@+id/spinnerStaffStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"/>
</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/btnStaffBack"
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/btnSaveStaff"
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>

View File

@@ -93,6 +93,37 @@
</LinearLayout>
<LinearLayout
android:id="@+id/layoutBulkDelete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@color/primary_medium"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:visibility="gone">
<TextView
android:id="@+id/tvSelectionCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="0 selected"
android:textColor="@color/white"/>
<Button
android:id="@+id/btnBulkDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delete Selected"
android:backgroundTint="@color/accent_coral"
android:textColor="@color/white"/>
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshSupplier"
android:layout_width="match_parent"

View File

@@ -2,88 +2,117 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="@color/white">
android:background="@color/white"
android:gravity="center_vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvAdoptionCustomerName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:text="Customer Name"
android:textColor="@color/text_dark"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvAdoptionStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="3dp"
android:paddingEnd="8dp"
android:paddingBottom="3dp"
android:text="Status"
android:textAllCaps="true"
android:textColor="@color/white"
android:textSize="11sp" />
</LinearLayout>
<TextView
android:id="@+id/tvAdoptionPetName"
<CheckBox
android:id="@+id/cbSelectAdoption"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Pet Name"
android:textColor="#888888"
android:textSize="14sp" />
android:layout_marginEnd="12dp"
android:visibility="gone"
android:clickable="false"
android:focusable="false"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp">
android:orientation="vertical">
<TextView
android:id="@+id/tvAdoptionFee"
android:layout_width="0dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="$0.00"
android:textColor="@color/accent_coral"
android:textSize="16sp"
android:textStyle="bold" />
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvAdoptionCustomerName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:text="Customer Name"
android:textColor="@color/text_dark"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvAdoptionStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="3dp"
android:paddingEnd="8dp"
android:paddingBottom="3dp"
android:text="Status"
android:textAllCaps="true"
android:textColor="@color/white"
android:textSize="11sp" />
</LinearLayout>
<TextView
android:id="@+id/tvAdoptionDate"
android:id="@+id/tvAdoptionPetName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Date"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Pet Name"
android:textColor="#888888"
android:textSize="13sp" />
android:textSize="14sp" />
<TextView
android:id="@+id/tvAdoptionStaffName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Staff: "
android:textColor="#888888"
android:textSize="13sp"
android:textStyle="italic" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/tvAdoptionFee"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="$0.00"
android:textColor="@color/accent_coral"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvAdoptionDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Date"
android:textColor="#888888"
android:textSize="13sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#F0F0F0"
android:layout_marginTop="12dp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#F0F0F0"
android:layout_marginTop="12dp"/>
</LinearLayout>
</LinearLayout>

View File

@@ -1,74 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:orientation="horizontal"
android:padding="16dp"
android:background="@android:color/white">
android:background="@android:color/white"
android:gravity="center_vertical">
<CheckBox
android:id="@+id/cbSelectAppointment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:visibility="gone"
android:clickable="false"
android:focusable="false"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="vertical">
<TextView
android:id="@+id/tvCustomerName"
android:layout_width="0dp"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Customer Name"
android:textColor="#000000"
android:textSize="18sp"
android:textStyle="bold" />
android:orientation="horizontal">
<TextView
android:id="@+id/tvCustomerName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Customer Name"
android:textColor="#000000"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvAppointmentStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#4CAF50"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
android:text="Status"
android:textColor="#FFFFFF"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/tvAppointmentStatus"
android:id="@+id/tvApptPetName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#4CAF50"
android:paddingStart="8dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp"
android:text="Status"
android:textColor="#FFFFFF"
android:textSize="12sp" />
android:layout_marginTop="4dp"
android:text="Pet: name"
android:textColor="#666666"
android:textSize="14sp" />
<TextView
android:id="@+id/tvServiceType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Service type"
android:textColor="#666666"
android:textSize="14sp" />
<TextView
android:id="@+id/tvStaffName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Staff: name"
android:textColor="#666666"
android:textSize="14sp" />
<TextView
android:id="@+id/tvDateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Date and time"
android:textColor="#2196F3"
android:textSize="14sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#EEEEEE"
android:layout_marginTop="12dp"/>
</LinearLayout>
<TextView
android:id="@+id/tvApptPetName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pet: name"
android:textColor="#666666"
android:textSize="14sp" />
<TextView
android:id="@+id/tvServiceType"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:text="Service type"
android:textColor="#666666"
android:textSize="14sp" />
<TextView
android:id="@+id/tvDateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Date and time"
android:textColor="#2196F3"
android:textSize="14sp"
android:textStyle="bold" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#EEEEEE"
android:layout_marginTop="12dp"/>
</LinearLayout>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<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:layout_margin="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvEmployeeFullName"
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/tvEmployeeUsername"
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/tvEmployeeEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@color/text_light"
android:layout_marginTop="2dp"/>
<TextView
android:id="@+id/tvEmployeePhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textColor="@color/text_light"
android:layout_marginTop="2dp"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="end"
android:layout_gravity="center_vertical">
<TextView
android:id="@+id/tvEmployeeRole"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:textSize="11sp"
android:textColor="@color/white"
android:layout_marginBottom="4dp"/>
<TextView
android:id="@+id/tvEmployeeStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:textSize="11sp"
android:textColor="@color/white"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -47,7 +47,7 @@
android:id="@+id/tvQuantity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:text="Stock: 0"
android:textColor="@color/text_dark"
android:textSize="16sp"
android:textStyle="bold" />
@@ -55,31 +55,14 @@
</LinearLayout>
<TextView
android:id="@+id/tvInventoryId"
android:id="@+id/tvInventoryStore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Inv ID: —"
android:layout_marginTop="2dp"
android:text="Store: —"
android:textColor="#888888"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/tvProdId"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Prod ID: —"
android:textColor="#888888"
android:textSize="13sp" />
</LinearLayout>
android:textSize="14sp"
android:textStyle="italic" />
<View
android:layout_width="match_parent"

View File

@@ -3,116 +3,133 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="@color/white">
android:background="@color/white"
android:gravity="center_vertical">
<CheckBox
android:id="@+id/cbSelectPet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:visibility="gone"
android:clickable="false"
android:focusable="false"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/ivPetProfile"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="16dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder"
app:shapeAppearanceOverlay="@style/CircleImageView"
app:strokeWidth="2dp"
app:strokeColor="#BDBDBD"
android:padding="2dp"
android:contentDescription="@string/pet_profile_image_desc" />
android:orientation="vertical">
<LinearLayout
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
android:orientation="horizontal"
android:gravity="center_vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/ivPetProfile"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginEnd="16dp"
android:scaleType="centerCrop"
android:src="@drawable/placeholder"
app:shapeAppearanceOverlay="@style/CircleImageView"
app:strokeWidth="2dp"
app:strokeColor="#BDBDBD"
android:padding="2dp"
android:contentDescription="@string/pet_profile_image_desc" />
<LinearLayout
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
android:layout_weight="1"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvPetName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="1"
android:text="Pet Name"
android:textColor="@color/text_dark"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPetStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="3dp"
android:paddingEnd="8dp"
android:paddingBottom="3dp"
android:text="Status"
android:textAllCaps="true"
android:textColor="@color/white"
android:textSize="11sp" />
</LinearLayout>
<TextView
android:id="@+id/tvPetName"
android:layout_width="0dp"
android:id="@+id/tvPetSpeciesBreed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Pet Name"
android:textColor="@color/text_dark"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPetStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="3dp"
android:paddingEnd="8dp"
android:paddingBottom="3dp"
android:text="Status"
android:textAllCaps="true"
android:textColor="@color/white"
android:textSize="11sp" />
</LinearLayout>
<TextView
android:id="@+id/tvPetSpeciesBreed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Breed"
android:textColor="#888888"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/tvPetPrice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="$126.00"
android:textColor="@color/accent_coral"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPetAge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:text="Breed"
android:textColor="#888888"
android:textSize="13sp" />
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/tvPetPrice"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="$126.00"
android:textColor="@color/accent_coral"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPetAge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1"
android:textColor="#888888"
android:textSize="13sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#F0F0F0"
android:layout_marginTop="12dp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#F0F0F0"
android:layout_marginTop="12dp"/>
</LinearLayout>

View File

@@ -2,58 +2,75 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:paddingBottom="16dp"
android:background="@color/white">
android:background="@color/white"
android:gravity="center_vertical">
<TextView
android:id="@+id/tvPSProductName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:text="Product Name"
android:textColor="@color/text_dark"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPSSupplierName"
<CheckBox
android:id="@+id/cbSelectProductSupplier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Supplier Name"
android:textColor="#888888"
android:textSize="14sp" />
android:layout_marginEnd="12dp"
android:visibility="gone"
android:clickable="false"
android:focusable="false"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp">
android:orientation="vertical">
<TextView
android:id="@+id/tvPSCost"
android:layout_width="0dp"
android:id="@+id/tvPSProductName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="$0.00"
android:textColor="@color/accent_coral"
android:textSize="16sp"
android:ellipsize="end"
android:maxLines="1"
android:text="Product Name"
android:textColor="@color/text_dark"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvPSSupplierName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Supplier Name"
android:textColor="#888888"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="8dp">
<TextView
android:id="@+id/tvPSCost"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="$0.00"
android:textColor="@color/accent_coral"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#F0F0F0"
android:layout_marginTop="12dp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#F0F0F0"
android:layout_marginTop="12dp"/>
</LinearLayout>

View File

@@ -53,6 +53,17 @@
android:textColor="#888888"
android:textSize="14sp" />
<TextView
android:id="@+id/tvPOStore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:ellipsize="end"
android:maxLines="1"
android:text="Store Name"
android:textColor="#888888"
android:textSize="14sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

Some files were not shown because too many files have changed in this diff Show More