Merge remote-tracking branch 'origin/main' into web-products
This commit is contained in:
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
9
.idea/group-2-threaded-project-petshop.iml
generated
Normal file
9
.idea/group-2-threaded-project-petshop.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/group-2-threaded-project-petshop.iml" filepath="$PROJECT_DIR$/.idea/group-2-threaded-project-petshop.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,7 +1,23 @@
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
}
|
||||
|
||||
val localProperties = Properties().apply {
|
||||
val file = rootProject.file("local.properties")
|
||||
if (file.exists()) {
|
||||
file.inputStream().use { load(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun quoted(value: String): String = "\"$value\""
|
||||
|
||||
val emulatorBackendUrl =
|
||||
(localProperties.getProperty("petstore.backend.emulatorUrl") ?: "http://10.0.2.2:8080/").trim()
|
||||
val deviceBackendUrl =
|
||||
(localProperties.getProperty("petstore.backend.deviceUrl") ?: "http://10.0.0.200:8080/").trim()
|
||||
|
||||
android {
|
||||
namespace = "com.example.petstoremobile"
|
||||
compileSdk = 36
|
||||
@@ -14,6 +30,13 @@ android {
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
buildConfigField("String", "EMULATOR_BACKEND_URL", quoted(emulatorBackendUrl))
|
||||
buildConfigField("String", "DEVICE_BACKEND_URL", quoted(deviceBackendUrl))
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -39,7 +62,7 @@ dependencies {
|
||||
|
||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
|
||||
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
@@ -62,4 +85,4 @@ dependencies {
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.ext.junit)
|
||||
androidTestImplementation(libs.espresso.core)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ public class HomeActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
EdgeToEdge.enable(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_home);
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||
|
||||
@@ -36,6 +36,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
EdgeToEdge.enable(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Check if user is already logged in
|
||||
@@ -52,7 +53,6 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
EdgeToEdge.enable(this);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||
@@ -132,15 +132,43 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Toast.makeText(MainActivity.this, "Login failed", Toast.LENGTH_SHORT).show();
|
||||
tvLoginStatus.setText("Login failed");
|
||||
String errorMessage;
|
||||
switch (response.code()) {
|
||||
case 401:
|
||||
errorMessage = "Invalid username or password";
|
||||
break;
|
||||
case 500:
|
||||
errorMessage = "Server error. Please try again later.";
|
||||
break;
|
||||
case 503:
|
||||
errorMessage = "Service unavailable. Backend may be starting up.";
|
||||
break;
|
||||
default:
|
||||
errorMessage = "Login failed (Error " + response.code() + ")";
|
||||
}
|
||||
Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show();
|
||||
tvLoginStatus.setText(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<AuthDTO.LoginResponse> call, Throwable t) {
|
||||
Toast.makeText(MainActivity.this, "Login failed", Toast.LENGTH_SHORT).show();
|
||||
tvLoginStatus.setText("Login failed");
|
||||
Log.e("MainActivity", "Login request failed", t);
|
||||
|
||||
String errorMessage;
|
||||
if (t instanceof java.net.ConnectException ||
|
||||
t instanceof java.net.SocketTimeoutException ||
|
||||
t instanceof java.net.UnknownHostException) {
|
||||
errorMessage = "Cannot connect to server at " + RetrofitClient.BASE_URL +
|
||||
". Please check if the backend is running.";
|
||||
} else if (t instanceof java.io.IOException) {
|
||||
errorMessage = "Network error. Please check your connection.";
|
||||
} else {
|
||||
errorMessage = "Login failed: " + t.getMessage();
|
||||
}
|
||||
|
||||
Toast.makeText(MainActivity.this, errorMessage, Toast.LENGTH_LONG).show();
|
||||
tvLoginStatus.setText(errorMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,79 +1,79 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.*;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.models.Adoption;
|
||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||
import java.util.List;
|
||||
|
||||
public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.AdoptionViewHolder> {
|
||||
|
||||
private List<Adoption> adoptionList;
|
||||
private OnAdoptionClickListener adoptionClickListener;
|
||||
private List<AdoptionDTO> adoptionList;
|
||||
private OnAdoptionClickListener listener;
|
||||
|
||||
// Interface for adoption click on recycler view
|
||||
public interface OnAdoptionClickListener {
|
||||
void onAdoptionClick(int position);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
public AdoptionAdapter(List<Adoption> adoptionList, OnAdoptionClickListener adoptionClickListener) {
|
||||
public AdoptionAdapter(List<AdoptionDTO> adoptionList, OnAdoptionClickListener listener) {
|
||||
this.adoptionList = adoptionList;
|
||||
this.adoptionClickListener = adoptionClickListener;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
// Get the controls of each row in recycler view
|
||||
public static class AdoptionViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvAdopterName, tvPetName, tvAdoptionDate, tvAdoptionStatus;
|
||||
TextView tvCustomerName, tvPetName, tvDate, tvFee, tvStatus;
|
||||
|
||||
public AdoptionViewHolder(@NonNull View v) {
|
||||
super(v);
|
||||
tvAdopterName = v.findViewById(R.id.tvAdopterName);
|
||||
tvPetName = v.findViewById(R.id.tvAdoptionPetName);
|
||||
tvAdoptionDate = v.findViewById(R.id.tvAdoptionDate);
|
||||
tvAdoptionStatus = v.findViewById(R.id.tvAdoptionStatus);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new row view
|
||||
@NonNull
|
||||
@Override
|
||||
public AdoptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_adoption, parent, false);
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_adoption, parent, false);
|
||||
return new AdoptionViewHolder(v);
|
||||
}
|
||||
|
||||
// Populate the row with adoption data
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AdoptionViewHolder holder, int position) {
|
||||
Adoption adoption = adoptionList.get(position);
|
||||
AdoptionDTO a = adoptionList.get(position);
|
||||
|
||||
holder.tvAdopterName.setText(adoption.getAdopterName());
|
||||
holder.tvPetName.setText("Pet: " + adoption.getPetName());
|
||||
holder.tvAdoptionDate.setText("Date: " + adoption.getAdoptionDate());
|
||||
holder.tvAdoptionStatus.setText(adoption.getStatus());
|
||||
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() : "");
|
||||
|
||||
// Set the status color depending on adoption status
|
||||
if (adoption.getStatus().equals("Approved")) {
|
||||
holder.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
||||
} else if (adoption.getStatus().equals("Pending")) {
|
||||
holder.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#FF9800"));
|
||||
} else {
|
||||
holder.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
||||
String status = a.getAdoptionStatus() != null ? a.getAdoptionStatus() : "";
|
||||
holder.tvStatus.setText(status);
|
||||
|
||||
switch (status) {
|
||||
case "Approved":
|
||||
holder.tvStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
||||
break;
|
||||
case "Pending":
|
||||
holder.tvStatus.setBackgroundColor(Color.parseColor("#FF9800"));
|
||||
break;
|
||||
case "Rejected":
|
||||
holder.tvStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
||||
break;
|
||||
default:
|
||||
holder.tvStatus.setBackgroundColor(Color.parseColor("#9E9E9E"));
|
||||
break;
|
||||
}
|
||||
|
||||
// When a row is clicked, open the detail view
|
||||
holder.itemView.setOnClickListener(v -> adoptionClickListener.onAdoptionClick(position));
|
||||
holder.itemView.setOnClickListener(v -> listener.onAdoptionClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return adoptionList.size();
|
||||
}
|
||||
}
|
||||
public int getItemCount() { return adoptionList.size(); }
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -10,26 +8,24 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.models.Appointment;
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.AppointmentViewHolder> {
|
||||
|
||||
private List<Appointment> appointmentList;
|
||||
private List<AppointmentDTO> appointmentList;
|
||||
private OnAppointmentClickListener appointmentClickListener;
|
||||
|
||||
// Interface for appointment click on recycler view
|
||||
public interface OnAppointmentClickListener {
|
||||
void onAppointmentClick(int position);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
public AppointmentAdapter(List<Appointment> appointmentList, OnAppointmentClickListener appointmentClickListener) {
|
||||
public AppointmentAdapter(List<AppointmentDTO> appointmentList,
|
||||
OnAppointmentClickListener appointmentClickListener) {
|
||||
this.appointmentList = appointmentList;
|
||||
this.appointmentClickListener = appointmentClickListener;
|
||||
}
|
||||
|
||||
// Get the controls of each row in recycler view
|
||||
public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvCustomerName, tvPetName, tvServiceType, tvDateTime, tvAppointmentStatus;
|
||||
|
||||
@@ -43,7 +39,6 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new row view
|
||||
@NonNull
|
||||
@Override
|
||||
public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
@@ -51,31 +46,34 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
||||
return new AppointmentViewHolder(v);
|
||||
}
|
||||
|
||||
// Populate the row with appointment data
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int position) {
|
||||
Appointment appointment = appointmentList.get(position);
|
||||
AppointmentDTO a = appointmentList.get(position);
|
||||
|
||||
holder.tvCustomerName.setText(appointment.getCustomerName());
|
||||
holder.tvPetName.setText("Pet: " + appointment.getPetName());
|
||||
holder.tvServiceType.setText(appointment.getServiceType());
|
||||
holder.tvDateTime.setText(appointment.getAppointmentDate() + " at " + appointment.getAppointmentTime());
|
||||
holder.tvAppointmentStatus.setText(appointment.getStatus());
|
||||
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() : "") +
|
||||
" at " + (a.getAppointmentTime() != null ? a.getAppointmentTime() : ""));
|
||||
|
||||
// Set the status color depending on appointment status
|
||||
switch (appointment.getStatus()) {
|
||||
case "Confirmed":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
||||
String status = a.getStatus() != null ? a.getStatus() : "";
|
||||
holder.tvAppointmentStatus.setText(status);
|
||||
|
||||
switch (status) {
|
||||
case "Booked":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#2196F3")); // blue
|
||||
break;
|
||||
case "Pending":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#FF9800"));
|
||||
case "Completed":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#4CAF50")); // green
|
||||
break;
|
||||
case "Cancelled":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#F44336")); // red
|
||||
break;
|
||||
default:
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#9E9E9E")); // gray
|
||||
break;
|
||||
}
|
||||
|
||||
// When a row is clicked, open the detail view
|
||||
holder.itemView.setOnClickListener(v -> appointmentClickListener.onAppointmentClick(position));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.example.petstoremobile.R;
|
||||
import java.util.List;
|
||||
|
||||
// A class that overrides the arrayAdapter so the text color changes based on theme
|
||||
public class BlackTextArrayAdapter<T> extends ArrayAdapter<T> {
|
||||
public BlackTextArrayAdapter(@NonNull Context context, int resource, @NonNull T[] objects) {
|
||||
super(context, resource, objects);
|
||||
}
|
||||
|
||||
public BlackTextArrayAdapter(@NonNull Context context, int resource, @NonNull List<T> objects) {
|
||||
super(context, resource, objects);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.white));
|
||||
if (view instanceof TextView) {
|
||||
((TextView) view).setTextColor(ContextCompat.getColor(getContext(), R.color.spinner_text));
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
View view = super.getDropDownView(position, convertView, parent);
|
||||
view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.white));
|
||||
if (view instanceof TextView) {
|
||||
((TextView) view).setTextColor(ContextCompat.getColor(getContext(), R.color.spinner_text));
|
||||
}
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,150 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
|
||||
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.models.Inventory;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> {
|
||||
|
||||
private List<Inventory> inventoryList;
|
||||
private OnInventoryClickListener inventoryClickListener;
|
||||
private final List<InventoryDTO> inventoryList;
|
||||
private final OnInventoryClickListener clickListener;
|
||||
private final List<Long> selectedIds = new ArrayList<>();
|
||||
private boolean selectionMode = false;
|
||||
|
||||
// Interface for inventory click on recycler view
|
||||
public interface OnInventoryClickListener {
|
||||
void onInventoryClick(int position);
|
||||
|
||||
void onSelectionChanged(int selectedCount);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
public InventoryAdapter(List<Inventory> inventoryList, OnInventoryClickListener inventoryClickListener) {
|
||||
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
|
||||
this.inventoryList = inventoryList;
|
||||
this.inventoryClickListener = inventoryClickListener;
|
||||
this.clickListener = clickListener;
|
||||
}
|
||||
|
||||
// Get the controls of each row in recycler view
|
||||
public static class InventoryViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvItemName, tvCategory, tvQuantity, tvUnitPrice, tvSupplier;
|
||||
// Matches desktop table columns: Inventory ID, Product ID, Product Name,
|
||||
// Quantity
|
||||
TextView tvInventoryId, tvProdId, tvProductName, tvQuantity;
|
||||
CheckBox checkBox;
|
||||
|
||||
public InventoryViewHolder(@NonNull View v) {
|
||||
super(v);
|
||||
tvItemName = v.findViewById(R.id.tvItemName);
|
||||
tvCategory = v.findViewById(R.id.tvCategory);
|
||||
tvInventoryId = v.findViewById(R.id.tvInventoryId);
|
||||
tvProdId = v.findViewById(R.id.tvProdId);
|
||||
tvProductName = v.findViewById(R.id.tvProductName);
|
||||
tvQuantity = v.findViewById(R.id.tvQuantity);
|
||||
tvUnitPrice = v.findViewById(R.id.tvUnitPrice);
|
||||
tvSupplier = v.findViewById(R.id.tvInvSupplier);
|
||||
checkBox = v.findViewById(R.id.cbSelectInventory);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new row view
|
||||
@NonNull
|
||||
@Override
|
||||
public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_inventory, parent, false);
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_inventory, parent, false);
|
||||
return new InventoryViewHolder(v);
|
||||
}
|
||||
|
||||
// Populate the row with inventory data
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int position) {
|
||||
Inventory inventory = inventoryList.get(position);
|
||||
InventoryDTO inv = inventoryList.get(position);
|
||||
|
||||
holder.tvItemName.setText(inventory.getItemName());
|
||||
holder.tvCategory.setText(inventory.getCategory());
|
||||
holder.tvQuantity.setText("Qty: " + inventory.getQuantity());
|
||||
holder.tvUnitPrice.setText("$" + String.format("%.2f", inventory.getUnitPrice()));
|
||||
holder.tvSupplier.setText("Supplier: " + inventory.getSupplier());
|
||||
// Column: Inventory ID
|
||||
String invIdStr = inv.getInventoryId() != null ? String.valueOf(inv.getInventoryId()) : "—";
|
||||
holder.tvInventoryId.setText("Inv ID: " + invIdStr);
|
||||
|
||||
// Highlight low stock items in red
|
||||
if (inventory.getQuantity() <= 5) {
|
||||
// Column: Product ID
|
||||
String prodIdStr = inv.getProdId() != null ? String.valueOf(inv.getProdId()) : "—";
|
||||
holder.tvProdId.setText("Prod ID: " + prodIdStr);
|
||||
|
||||
// Column: Product Name
|
||||
holder.tvProductName.setText(inv.getProductName() != null ? inv.getProductName() : "—");
|
||||
|
||||
// Column: Quantity
|
||||
int qty = inv.getQuantity() != null ? inv.getQuantity() : 0;
|
||||
holder.tvQuantity.setText(String.valueOf(qty));
|
||||
|
||||
// Low stock = red, normal = green (like desktop reorder concept)
|
||||
if (qty <= 5) {
|
||||
holder.tvQuantity.setTextColor(Color.parseColor("#F44336"));
|
||||
} else {
|
||||
holder.tvQuantity.setTextColor(Color.parseColor("#4CAF50"));
|
||||
}
|
||||
|
||||
// When a row is clicked, open the detail view
|
||||
holder.itemView.setOnClickListener(v -> inventoryClickListener.onInventoryClick(position));
|
||||
// Bulk delete selection mode
|
||||
if (selectionMode) {
|
||||
holder.checkBox.setVisibility(View.VISIBLE);
|
||||
holder.checkBox.setChecked(inv.getInventoryId() != null
|
||||
&& selectedIds.contains(inv.getInventoryId()));
|
||||
} else {
|
||||
holder.checkBox.setVisibility(View.GONE);
|
||||
holder.checkBox.setChecked(false);
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
if (selectionMode) {
|
||||
toggleSelection(inv.getInventoryId(), holder.checkBox);
|
||||
} else {
|
||||
clickListener.onInventoryClick(holder.getAdapterPosition());
|
||||
}
|
||||
});
|
||||
|
||||
holder.itemView.setOnLongClickListener(v -> {
|
||||
if (!selectionMode) {
|
||||
selectionMode = true;
|
||||
toggleSelection(inv.getInventoryId(), holder.checkBox);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,16 @@ 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.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.PetApi;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import java.util.List;
|
||||
|
||||
@@ -30,6 +36,7 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
||||
// Get the controls of each row in recycler view
|
||||
public static class PetViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvPetName, tvPetSpeciesBreed, tvPetAge, tvPetPrice, tvPetStatus;
|
||||
ImageView ivPetProfile;
|
||||
|
||||
public PetViewHolder(@NonNull View v) {
|
||||
super(v);
|
||||
@@ -38,6 +45,7 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
||||
tvPetAge = v.findViewById(R.id.tvPetAge);
|
||||
tvPetPrice = v.findViewById(R.id.tvPetPrice);
|
||||
tvPetStatus = v.findViewById(R.id.tvPetStatus);
|
||||
ivPetProfile = v.findViewById(R.id.ivPetProfile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +82,17 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
||||
holder.tvPetStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
||||
}
|
||||
|
||||
// Load pet image using Glide with circle crop
|
||||
String imageUrl = RetrofitClient.BASE_URL + String.format(PetApi.PET_IMAGE_PATH, pet.getPetId());
|
||||
Glide.with(holder.itemView.getContext())
|
||||
.load(imageUrl)
|
||||
.circleCrop()
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.skipMemoryCache(true)
|
||||
.placeholder(R.drawable.placeholder)
|
||||
.error(R.drawable.placeholder)
|
||||
.into(holder.ivPetProfile);
|
||||
|
||||
//when a row is clicked, open the detail view
|
||||
holder.itemView.setOnClickListener(v -> petClickListener.onPetClick(position));
|
||||
}
|
||||
|
||||
@@ -1,72 +1,57 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.*;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.models.Product;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import java.util.List;
|
||||
|
||||
public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductViewHolder> {
|
||||
|
||||
private List<Product> productList;
|
||||
private OnProductClickListener productClickListener;
|
||||
private List<ProductDTO> productList;
|
||||
private OnProductClickListener listener;
|
||||
|
||||
// Interface for product click on recycler view
|
||||
public interface OnProductClickListener {
|
||||
void onProductClick(int position);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
public ProductAdapter(List<Product> productList, OnProductClickListener productClickListener) {
|
||||
public ProductAdapter(List<ProductDTO> productList, OnProductClickListener listener) {
|
||||
this.productList = productList;
|
||||
this.productClickListener = productClickListener;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
// Get the controls of each row in recycler view
|
||||
public static class ProductViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvProductName, tvProductDesc, tvCategory, tvProductPrice, tvStockQuantity;
|
||||
TextView tvName, tvCategory, tvDesc, tvPrice;
|
||||
|
||||
public ProductViewHolder(@NonNull View v) {
|
||||
super(v);
|
||||
tvProductName = v.findViewById(R.id.tvProductName);
|
||||
tvProductDesc = v.findViewById(R.id.tvProductDesc);
|
||||
tvName = v.findViewById(R.id.tvProductName);
|
||||
tvCategory = v.findViewById(R.id.tvProductCategory);
|
||||
tvProductPrice = v.findViewById(R.id.tvProductPrice);
|
||||
tvStockQuantity = v.findViewById(R.id.tvStockQuantity);
|
||||
tvDesc = v.findViewById(R.id.tvProductDesc);
|
||||
tvPrice = v.findViewById(R.id.tvProductPrice);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new row view
|
||||
@NonNull
|
||||
@Override
|
||||
public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_product, parent, false);
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.item_product, parent, false);
|
||||
return new ProductViewHolder(v);
|
||||
}
|
||||
|
||||
// Populate the row with product data
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ProductViewHolder holder, int position) {
|
||||
Product product = productList.get(position);
|
||||
|
||||
holder.tvProductName.setText(product.getProductName());
|
||||
holder.tvProductDesc.setText(product.getProductDesc());
|
||||
holder.tvCategory.setText(product.getCategory());
|
||||
holder.tvProductPrice.setText("$" + String.format("%.2f", product.getProductPrice()));
|
||||
holder.tvStockQuantity.setText("Stock: " + product.getStockQuantity());
|
||||
|
||||
// When a row is clicked, open the detail view
|
||||
holder.itemView.setOnClickListener(v -> productClickListener.onProductClick(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() : "");
|
||||
holder.itemView.setOnClickListener(v -> listener.onProductClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return productList.size();
|
||||
}
|
||||
}
|
||||
|
||||
public int getItemCount() { return productList.size(); }
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
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.ProductSupplierDTO;
|
||||
import java.util.List;
|
||||
|
||||
public class ProductSupplierAdapter extends RecyclerView.Adapter<ProductSupplierAdapter.PSViewHolder> {
|
||||
|
||||
private List<ProductSupplierDTO> list;
|
||||
private OnProductSupplierClickListener listener;
|
||||
|
||||
public interface OnProductSupplierClickListener {
|
||||
void onProductSupplierClick(int position);
|
||||
}
|
||||
|
||||
public ProductSupplierAdapter(List<ProductSupplierDTO> list, OnProductSupplierClickListener listener) {
|
||||
this.list = list;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public static class PSViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvProductName, tvSupplierName, tvCost;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() { return list.size(); }
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
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.PurchaseOrderDTO;
|
||||
import java.util.List;
|
||||
|
||||
public class PurchaseOrderAdapter extends RecyclerView.Adapter<PurchaseOrderAdapter.POViewHolder> {
|
||||
|
||||
private List<PurchaseOrderDTO> list;
|
||||
private OnPurchaseOrderClickListener listener;
|
||||
|
||||
public interface OnPurchaseOrderClickListener {
|
||||
void onPurchaseOrderClick(int position);
|
||||
}
|
||||
|
||||
public PurchaseOrderAdapter(List<PurchaseOrderDTO> list, OnPurchaseOrderClickListener listener) {
|
||||
this.list = list;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public static class POViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvId, tvSupplier, tvDate, tvStatus;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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() : ""));
|
||||
|
||||
String status = po.getStatus() != null ? po.getStatus() : "";
|
||||
holder.tvStatus.setText(status);
|
||||
|
||||
switch (status) {
|
||||
case "Completed":
|
||||
holder.tvStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
||||
break;
|
||||
case "Pending":
|
||||
holder.tvStatus.setBackgroundColor(Color.parseColor("#FF9800"));
|
||||
break;
|
||||
case "Cancelled":
|
||||
holder.tvStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
||||
break;
|
||||
default:
|
||||
holder.tvStatus.setBackgroundColor(Color.parseColor("#9E9E9E"));
|
||||
break;
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> listener.onPurchaseOrderClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return list.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
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 java.util.List;
|
||||
|
||||
public class SaleAdapter extends RecyclerView.Adapter<SaleAdapter.SaleViewHolder> {
|
||||
|
||||
private List<Sale> saleList;
|
||||
private OnSaleClickListener saleClickListener;
|
||||
|
||||
public interface OnSaleClickListener {
|
||||
void onSaleClick(int position);
|
||||
}
|
||||
|
||||
public SaleAdapter(List<Sale> saleList, OnSaleClickListener saleClickListener) {
|
||||
this.saleList = saleList;
|
||||
this.saleClickListener = saleClickListener;
|
||||
}
|
||||
|
||||
public static class SaleViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvSaleId, tvItemName, tvEmployeeName, tvSaleDate, tvTotal, tvPaymentMethod, tvRefundBadge;
|
||||
|
||||
public SaleViewHolder(@NonNull View v) {
|
||||
super(v);
|
||||
tvSaleId = v.findViewById(R.id.tvSaleId);
|
||||
tvItemName = v.findViewById(R.id.tvSaleItemName);
|
||||
tvEmployeeName = v.findViewById(R.id.tvSaleEmployee);
|
||||
tvSaleDate = v.findViewById(R.id.tvSaleDate);
|
||||
tvTotal = v.findViewById(R.id.tvSaleTotal);
|
||||
tvPaymentMethod = v.findViewById(R.id.tvSalePayment);
|
||||
tvRefundBadge = v.findViewById(R.id.tvRefundBadge);
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull SaleViewHolder holder, int position) {
|
||||
Sale sale = saleList.get(position);
|
||||
|
||||
holder.tvSaleId.setText("ID: " + sale.getSaleId());
|
||||
holder.tvItemName.setText(sale.getItemName());
|
||||
holder.tvEmployeeName.setText("By: " + sale.getEmployeeName());
|
||||
holder.tvSaleDate.setText(sale.getSaleDate());
|
||||
holder.tvTotal.setText("$" + String.format("%.2f", sale.getTotal()));
|
||||
holder.tvPaymentMethod.setText(sale.getPaymentMethod());
|
||||
|
||||
// Show refund badge if it's a refund
|
||||
if (sale.isRefund()) {
|
||||
holder.tvRefundBadge.setVisibility(View.VISIBLE);
|
||||
holder.tvRefundBadge.setBackgroundColor(Color.parseColor("#F44336"));
|
||||
holder.tvTotal.setTextColor(Color.parseColor("#F44336"));
|
||||
} else {
|
||||
holder.tvRefundBadge.setVisibility(View.GONE);
|
||||
holder.tvTotal.setTextColor(Color.parseColor("#4CAF50"));
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> saleClickListener.onSaleClick(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return saleList.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface AdoptionApi {
|
||||
|
||||
@GET("api/v1/adoptions")
|
||||
Call<PageResponse<AdoptionDTO>> getAllAdoptions(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
|
||||
@GET("api/v1/adoptions/{id}")
|
||||
Call<AdoptionDTO> getAdoptionById(@Path("id") Long id);
|
||||
|
||||
@POST("api/v1/adoptions")
|
||||
Call<AdoptionDTO> createAdoption(@Body AdoptionDTO adoption);
|
||||
|
||||
@PUT("api/v1/adoptions/{id}")
|
||||
Call<AdoptionDTO> updateAdoption(@Path("id") Long id, @Body AdoptionDTO adoption);
|
||||
|
||||
@DELETE("api/v1/adoptions/{id}")
|
||||
Call<Void> deleteAdoption(@Path("id") Long id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface AppointmentApi {
|
||||
|
||||
@GET("api/v1/appointments")
|
||||
Call<PageResponse<AppointmentDTO>> getAllAppointments(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
|
||||
@GET("api/v1/appointments/{id}")
|
||||
Call<AppointmentDTO> getAppointmentById(@Path("id") Long id);
|
||||
|
||||
@POST("api/v1/appointments")
|
||||
Call<AppointmentDTO> createAppointment(@Body AppointmentDTO appointment);
|
||||
|
||||
@PUT("api/v1/appointments/{id}")
|
||||
Call<AppointmentDTO> updateAppointment(@Path("id") Long id, @Body AppointmentDTO appointment);
|
||||
|
||||
@DELETE("api/v1/appointments/{id}")
|
||||
Call<Void> deleteAppointment(@Path("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface CategoryApi {
|
||||
|
||||
@GET("api/v1/categories")
|
||||
Call<PageResponse<CategoryDTO>> getAllCategories(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
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.POST;
|
||||
import retrofit2.http.PUT;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface InventoryApi {
|
||||
|
||||
// GET /api/v1/inventory?q=...&page=...&size=...
|
||||
@GET("api/v1/inventory")
|
||||
Call<PageResponse<InventoryDTO>> getAllInventory(
|
||||
@Query("q") String query,
|
||||
@Query("page") int page,
|
||||
@Query("size") int size,
|
||||
@Query("sort") String sort);
|
||||
|
||||
// GET /api/v1/inventory/{id}
|
||||
@GET("api/v1/inventory/{id}")
|
||||
Call<InventoryDTO> getInventoryById(@Path("id") Long id);
|
||||
|
||||
// POST /api/v1/inventory
|
||||
@POST("api/v1/inventory")
|
||||
Call<InventoryDTO> createInventory(@Body InventoryRequest request);
|
||||
|
||||
// PUT /api/v1/inventory/{id}
|
||||
@PUT("api/v1/inventory/{id}")
|
||||
Call<InventoryDTO> updateInventory(@Path("id") Long id, @Body InventoryRequest 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")
|
||||
Call<Void> bulkDeleteInventory(@Body BulkDeleteRequest request);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.*;
|
||||
|
||||
public interface ProductApi {
|
||||
|
||||
@GET("api/v1/products")
|
||||
Call<PageResponse<ProductDTO>> getAllProducts(
|
||||
@Query("q") String query,
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
|
||||
@GET("api/v1/products/{id}")
|
||||
Call<ProductDTO> getProductById(@Path("id") Long id);
|
||||
|
||||
@POST("api/v1/products")
|
||||
Call<ProductDTO> createProduct(@Body ProductDTO product);
|
||||
|
||||
@PUT("api/v1/products/{id}")
|
||||
Call<ProductDTO> updateProduct(@Path("id") Long id, @Body ProductDTO product);
|
||||
|
||||
@DELETE("api/v1/products/{id}")
|
||||
Call<Void> deleteProduct(@Path("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.*;
|
||||
|
||||
public interface ProductSupplierApi {
|
||||
|
||||
@GET("api/v1/product-suppliers")
|
||||
Call<PageResponse<ProductSupplierDTO>> getAllProductSuppliers(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
|
||||
@POST("api/v1/product-suppliers")
|
||||
Call<ProductSupplierDTO> createProductSupplier(@Body ProductSupplierDTO dto);
|
||||
|
||||
@PUT("api/v1/product-suppliers/{productId}/{supplierId}")
|
||||
Call<ProductSupplierDTO> updateProductSupplier(
|
||||
@Path("productId") Long productId,
|
||||
@Path("supplierId") Long supplierId,
|
||||
@Body ProductSupplierDTO dto);
|
||||
|
||||
@DELETE("api/v1/product-suppliers/{productId}/{supplierId}")
|
||||
Call<Void> deleteProductSupplier(
|
||||
@Path("productId") Long productId,
|
||||
@Path("supplierId") Long supplierId);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface PurchaseOrderApi {
|
||||
|
||||
@GET("api/v1/purchase-orders")
|
||||
Call<PageResponse<PurchaseOrderDTO>> getAllPurchaseOrders(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
|
||||
@GET("api/v1/purchase-orders/{id}")
|
||||
Call<PurchaseOrderDTO> getPurchaseOrderById(@Path("id") Long id);
|
||||
}
|
||||
@@ -2,7 +2,9 @@ package com.example.petstoremobile.api;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.example.petstoremobile.BuildConfig;
|
||||
import com.example.petstoremobile.api.auth.AuthApi;
|
||||
import com.example.petstoremobile.api.auth.AuthInterceptor;
|
||||
|
||||
@@ -11,24 +13,32 @@ import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import retrofit2.Retrofit;
|
||||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
//Retrofit client Used for API calls
|
||||
public class RetrofitClient {
|
||||
private static final String TAG = "RetrofitClient";
|
||||
public static final String BASE_URL = getBaseUrl();
|
||||
|
||||
// Helper function to determine BASE_URL based on whether we are testing on an emulator or a real device
|
||||
private static String getBaseUrl() {
|
||||
if (Build.FINGERPRINT.contains("generic")
|
||||
|| Build.FINGERPRINT.contains("unknown")
|
||||
String url = isEmulator() ? BuildConfig.EMULATOR_BACKEND_URL : BuildConfig.DEVICE_BACKEND_URL;
|
||||
Log.i(TAG, "Using backend URL: " + url + " (emulator=" + isEmulator() + ")");
|
||||
return url;
|
||||
}
|
||||
|
||||
private static boolean isEmulator() {
|
||||
return Build.FINGERPRINT.startsWith("generic")
|
||||
|| Build.FINGERPRINT.startsWith("unknown")
|
||||
|| Build.MODEL.contains("google_sdk")
|
||||
|| Build.MODEL.contains("Emulator")
|
||||
|| Build.MODEL.contains("Android SDK built for x86")
|
||||
|| Build.MANUFACTURER.contains("Genymotion")
|
||||
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|
||||
|| "google_sdk".equals(Build.PRODUCT)) {
|
||||
return "http://10.0.2.2:8080/"; //emulator testing
|
||||
} else {
|
||||
return "http://10.0.0.200:8080/"; //Hardware testing
|
||||
}
|
||||
|| Build.HARDWARE.contains("goldfish")
|
||||
|| Build.HARDWARE.contains("ranchu")
|
||||
|| Build.PRODUCT.contains("sdk")
|
||||
|| Build.PRODUCT.contains("sdk_gphone")
|
||||
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"));
|
||||
}
|
||||
|
||||
private static Retrofit retrofit = null;
|
||||
@@ -41,6 +51,9 @@ public class RetrofitClient {
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.addInterceptor(interceptor)
|
||||
.addInterceptor(new AuthInterceptor(context))
|
||||
.connectTimeout(30, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.build();
|
||||
|
||||
//build the retrofit object with all needed properties
|
||||
@@ -66,6 +79,34 @@ public class RetrofitClient {
|
||||
return getClient(context).create(SupplierApi.class);
|
||||
}
|
||||
|
||||
public static AdoptionApi getAdoptionApi(Context context) {
|
||||
return getClient(context).create(AdoptionApi.class);
|
||||
}
|
||||
|
||||
public static AppointmentApi getAppointmentApi(Context context) {
|
||||
return getClient(context).create(AppointmentApi.class);
|
||||
}
|
||||
|
||||
public static ProductApi getProductApi(Context context) {
|
||||
return getClient(context).create(ProductApi.class);
|
||||
}
|
||||
|
||||
public static SaleApi getSaleApi(Context context) {
|
||||
return getClient(context).create(SaleApi.class);
|
||||
}
|
||||
|
||||
public static PurchaseOrderApi getPurchaseOrderApi(Context context) {
|
||||
return getClient(context).create(PurchaseOrderApi.class);
|
||||
}
|
||||
|
||||
public static ProductSupplierApi getProductSupplierApi(Context context) {
|
||||
return getClient(context).create(ProductSupplierApi.class);
|
||||
}
|
||||
|
||||
public static InventoryApi getInventoryApi(Context context) {
|
||||
return getClient(context).create(InventoryApi.class);
|
||||
}
|
||||
|
||||
public static AuthApi getAuthApi(Context context) {
|
||||
return getClient(context).create(AuthApi.class);
|
||||
}
|
||||
@@ -82,4 +123,12 @@ public class RetrofitClient {
|
||||
return getClient(context).create(MessageApi.class);
|
||||
}
|
||||
|
||||
public static StoreApi getStoreApi(Context context) {
|
||||
return getClient(context).create(StoreApi.class);
|
||||
}
|
||||
|
||||
public static CategoryApi getCategoryApi(Context context) {
|
||||
return getClient(context).create(CategoryApi.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.DELETE;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.PUT;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface SaleApi {
|
||||
|
||||
@GET("api/v1/sales")
|
||||
Call<PageResponse<SaleDTO>> getAllSales(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
|
||||
@GET("api/v1/sales/{id}")
|
||||
Call<SaleDTO> getSaleById(@Path("id") Long id);
|
||||
|
||||
@POST("api/v1/sales")
|
||||
Call<SaleDTO> createSale(@Body SaleDTO sale);
|
||||
|
||||
@PUT("api/v1/sales/{id}")
|
||||
Call<SaleDTO> updateSale(@Path("id") Long id, @Body SaleDTO sale);
|
||||
|
||||
@DELETE("api/v1/sales/{id}")
|
||||
Call<Void> deleteSale(@Path("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface StoreApi {
|
||||
|
||||
@GET("api/v1/stores")
|
||||
Call<PageResponse<StoreDTO>> getAllStores(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class AdoptionDTO {
|
||||
private Long adoptionId;
|
||||
private Long petId;
|
||||
private String petName;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
private String adoptionDate;
|
||||
private String adoptionStatus;
|
||||
private BigDecimal adoptionFee;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
// Constructor for create/update requests
|
||||
public AdoptionDTO(Long petId, Long customerId, String adoptionDate, String adoptionStatus) {
|
||||
this.petId = petId;
|
||||
this.customerId = customerId;
|
||||
this.adoptionDate = adoptionDate;
|
||||
this.adoptionStatus = adoptionStatus;
|
||||
}
|
||||
|
||||
public Long getAdoptionId() {
|
||||
return adoptionId;
|
||||
}
|
||||
|
||||
public Long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public String getPetName() {
|
||||
return petName;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public String getAdoptionDate() {
|
||||
return adoptionDate;
|
||||
}
|
||||
|
||||
public String getAdoptionStatus() {
|
||||
return adoptionStatus;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return adoptionStatus;
|
||||
}
|
||||
|
||||
public BigDecimal getAdoptionFee() {
|
||||
return adoptionFee;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentDTO {
|
||||
// Response fields (from server)
|
||||
private Long appointmentId;
|
||||
private Long customerId;
|
||||
private String customerName;
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private Long serviceId;
|
||||
private String serviceName;
|
||||
private String appointmentDate;
|
||||
private String appointmentTime;
|
||||
private String appointmentStatus;
|
||||
private List<String> petNames;
|
||||
private List<Long> petIds;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
// Constructor for CREATE/UPDATE request body
|
||||
// Matches AppointmentRequest exactly
|
||||
public AppointmentDTO(Long customerId, Long storeId, Long serviceId,
|
||||
String appointmentDate, String appointmentTime,
|
||||
String appointmentStatus, List<Long> petIds) {
|
||||
this.customerId = customerId;
|
||||
this.storeId = storeId;
|
||||
this.serviceId = serviceId;
|
||||
this.appointmentDate = appointmentDate;
|
||||
this.appointmentTime = appointmentTime;
|
||||
this.appointmentStatus = appointmentStatus;
|
||||
this.petIds = petIds;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public Long getAppointmentId() {
|
||||
return appointmentId;
|
||||
}
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public Long getStoreId() {
|
||||
return storeId;
|
||||
}
|
||||
|
||||
public String getStoreName() {
|
||||
return storeName;
|
||||
}
|
||||
|
||||
public Long getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public String getServiceName() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public String getAppointmentDate() {
|
||||
return appointmentDate;
|
||||
}
|
||||
|
||||
public String getAppointmentTime() {
|
||||
return appointmentTime;
|
||||
}
|
||||
|
||||
public String getAppointmentStatus() {
|
||||
return appointmentStatus;
|
||||
}
|
||||
|
||||
public List<String> getPetNames() {
|
||||
return petNames;
|
||||
}
|
||||
|
||||
public List<Long> getPetIds() {
|
||||
return petIds;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
// Convenience getters for adapter/list display
|
||||
public String getPetName() {
|
||||
return (petNames != null && !petNames.isEmpty()) ? petNames.get(0) : "";
|
||||
}
|
||||
|
||||
public Long getPetID() {
|
||||
return (petIds != null && !petIds.isEmpty()) ? petIds.get(0) : null;
|
||||
}
|
||||
|
||||
public Long getPetId() {
|
||||
return getPetID();
|
||||
}
|
||||
|
||||
// Keep old name so adapter doesn't break
|
||||
public String getServiceType() {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
public Long getServiceID() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
// Status alias
|
||||
public String getStatus() {
|
||||
return appointmentStatus;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BulkDeleteRequest {
|
||||
private List<Long> ids;
|
||||
|
||||
public BulkDeleteRequest() {
|
||||
}
|
||||
|
||||
public BulkDeleteRequest(List<Long> ids) {
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
public List<Long> getIds() {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public void setIds(List<Long> ids) {
|
||||
this.ids = ids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
public class CategoryDTO {
|
||||
private Long categoryId;
|
||||
private String categoryName;
|
||||
private String categoryType;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
public Long getCategoryId() {
|
||||
return categoryId;
|
||||
}
|
||||
|
||||
public String getCategoryName() {
|
||||
return categoryName;
|
||||
}
|
||||
|
||||
public String getCategoryType() {
|
||||
return categoryType;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,38 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class CustomerDTO {
|
||||
@SerializedName("customerId")
|
||||
private Long customerId;
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String email;
|
||||
private String phone;
|
||||
|
||||
public CustomerDTO() {}
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return (firstName != null ? firstName : "") + " " + (lastName != null ? lastName : "");
|
||||
return firstName + " " + lastName;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
public class InventoryDTO {
|
||||
// Response fields (from backend InventoryResponse)
|
||||
private Long inventoryId;
|
||||
private Long prodId;
|
||||
private String productName;
|
||||
private String categoryName;
|
||||
private Integer quantity;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
public InventoryDTO() {
|
||||
}
|
||||
|
||||
// Constructor for create/update requests (matches InventoryRequest)
|
||||
public InventoryDTO(Long prodId, Integer quantity) {
|
||||
this.prodId = prodId;
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public Long getInventoryId() {
|
||||
return inventoryId;
|
||||
}
|
||||
|
||||
public void setInventoryId(Long inventoryId) {
|
||||
this.inventoryId = inventoryId;
|
||||
}
|
||||
|
||||
public Long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(Long prodId) {
|
||||
this.prodId = prodId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public void setProductName(String productName) {
|
||||
this.productName = productName;
|
||||
}
|
||||
|
||||
public String getCategoryName() {
|
||||
return categoryName;
|
||||
}
|
||||
|
||||
public void setCategoryName(String categoryName) {
|
||||
this.categoryName = categoryName;
|
||||
}
|
||||
|
||||
public Integer getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public void setQuantity(Integer quantity) {
|
||||
this.quantity = quantity;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(String createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
|
||||
public void setUpdatedAt(String updatedAt) {
|
||||
this.updatedAt = updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ProductDTO {
|
||||
private Long prodId;
|
||||
private String prodName;
|
||||
private Long categoryId;
|
||||
private String categoryName;
|
||||
private String prodDesc;
|
||||
private BigDecimal prodPrice;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
public ProductDTO() {
|
||||
}
|
||||
|
||||
// Constructor for create/update
|
||||
public ProductDTO(String prodName, Long categoryId, String prodDesc, BigDecimal prodPrice) {
|
||||
this.prodName = prodName;
|
||||
this.categoryId = categoryId;
|
||||
this.prodDesc = prodDesc;
|
||||
this.prodPrice = prodPrice;
|
||||
}
|
||||
|
||||
public Long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public void setProdId(Long prodId) {
|
||||
this.prodId = prodId;
|
||||
}
|
||||
|
||||
public String getProdName() {
|
||||
return prodName;
|
||||
}
|
||||
|
||||
public void setProdName(String prodName) {
|
||||
this.prodName = prodName;
|
||||
}
|
||||
|
||||
public Long getCategoryId() {
|
||||
return categoryId;
|
||||
}
|
||||
|
||||
public void setCategoryId(Long categoryId) {
|
||||
this.categoryId = categoryId;
|
||||
}
|
||||
|
||||
public String getCategoryName() {
|
||||
return categoryName;
|
||||
}
|
||||
|
||||
public void setCategoryName(String categoryName) {
|
||||
this.categoryName = categoryName;
|
||||
}
|
||||
|
||||
public String getProdDesc() {
|
||||
return prodDesc;
|
||||
}
|
||||
|
||||
public void setProdDesc(String prodDesc) {
|
||||
this.prodDesc = prodDesc;
|
||||
}
|
||||
|
||||
public BigDecimal getProdPrice() {
|
||||
return prodPrice;
|
||||
}
|
||||
|
||||
public void setProdPrice(BigDecimal prodPrice) {
|
||||
this.prodPrice = prodPrice;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public class ProductSupplierDTO {
|
||||
private Long productId;
|
||||
private String productName;
|
||||
private Long supplierId;
|
||||
private String supplierName;
|
||||
private BigDecimal cost;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
// Constructor for create/update
|
||||
public ProductSupplierDTO(Long productId, Long supplierId, BigDecimal cost) {
|
||||
this.productId = productId;
|
||||
this.supplierId = supplierId;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
public Long getProductId() {
|
||||
return productId;
|
||||
}
|
||||
|
||||
public String getProductName() {
|
||||
return productName;
|
||||
}
|
||||
|
||||
public Long getSupplierId() {
|
||||
return supplierId;
|
||||
}
|
||||
|
||||
public String getSupplierName() {
|
||||
return supplierName;
|
||||
}
|
||||
|
||||
public BigDecimal getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
public class PurchaseOrderDTO {
|
||||
private Long purchaseOrderId;
|
||||
private Long supId;
|
||||
private String supplierName;
|
||||
private String orderDate;
|
||||
private String status;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
public Long getPurchaseOrderId() {
|
||||
return purchaseOrderId;
|
||||
}
|
||||
|
||||
public Long getSupId() {
|
||||
return supId;
|
||||
}
|
||||
|
||||
public String getSupplierName() {
|
||||
return supplierName;
|
||||
}
|
||||
|
||||
public String getOrderDate() {
|
||||
return orderDate;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
public class SaleDTO {
|
||||
|
||||
private Long saleId;
|
||||
|
||||
private Long productId;
|
||||
private String productName;
|
||||
|
||||
private Integer quantity;
|
||||
private Double price;
|
||||
private Double totalAmount;
|
||||
|
||||
private String saleDate;
|
||||
private String customerName;
|
||||
|
||||
public SaleDTO() {}
|
||||
|
||||
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 String getCustomerName() {
|
||||
return customerName;
|
||||
}
|
||||
|
||||
public void setCustomerName(String customerName) {
|
||||
this.customerName = customerName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
public class StoreDTO {
|
||||
private Long storeId;
|
||||
private String storeName;
|
||||
private String address;
|
||||
private String phone;
|
||||
private String email;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
// Constructor for hardcoded fallback
|
||||
public StoreDTO(Long storeId, String storeName) {
|
||||
this.storeId = storeId;
|
||||
this.storeName = storeName;
|
||||
}
|
||||
|
||||
public Long getStoreId() { return storeId; }
|
||||
public String getStoreName() { return storeName; }
|
||||
public String getAddress() { return address; }
|
||||
public String getPhone() { return phone; }
|
||||
public String getEmail() { return email; }
|
||||
public String getCreatedAt() { return createdAt; }
|
||||
public String getUpdatedAt() { return updatedAt; }
|
||||
}
|
||||
@@ -22,6 +22,9 @@ import com.example.petstoremobile.fragments.listfragments.AdoptionFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.AppointmentFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.ProductFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.ProductSupplierFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.PurchaseOrderFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.SaleFragment;
|
||||
|
||||
//The Fragment for the displaying the list of entities to be viewed
|
||||
public class ListFragment extends Fragment {
|
||||
@@ -32,7 +35,7 @@ public class ListFragment extends Fragment {
|
||||
|
||||
// Adoptions, Appointments, Inventory, Products
|
||||
|
||||
private LinearLayout drawerAdoptions, drawerAppointments, drawerInventory, drawerProducts;
|
||||
private LinearLayout drawerAdoptions, drawerAppointments, drawerInventory, drawerProducts, drawerProductSupplier, drawerPurchaseOrderView, drawerSale;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -49,6 +52,10 @@ public class ListFragment extends Fragment {
|
||||
drawerAppointments = view.findViewById(R.id.drawerAppointments);
|
||||
drawerInventory = view.findViewById(R.id.drawerInventory);
|
||||
drawerProducts = view.findViewById(R.id.drawerProducts);
|
||||
drawerProductSupplier=view.findViewById(R.id.drawerProductSupplier);
|
||||
drawerSale=view.findViewById(R.id.drawerSale);
|
||||
drawerPurchaseOrderView=view.findViewById(R.id.drawerPurchaseOrderView);
|
||||
|
||||
|
||||
// Check user role and restrict access for STAFF
|
||||
String role = TokenManager.getInstance(requireContext()).getRole();
|
||||
@@ -116,7 +123,7 @@ public class ListFragment extends Fragment {
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//Appoinment
|
||||
//Appointment
|
||||
drawerAppointments.setOnClickListener(v -> {
|
||||
loadFragment(new AppointmentFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
@@ -134,6 +141,27 @@ public class ListFragment extends Fragment {
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//ProductSupplier
|
||||
|
||||
drawerProductSupplier.setOnClickListener(v -> {
|
||||
loadFragment(new ProductSupplierFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//Purchase
|
||||
|
||||
drawerPurchaseOrderView.setOnClickListener(v -> {
|
||||
loadFragment(new PurchaseOrderFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//Sale
|
||||
|
||||
drawerSale.setOnClickListener(v -> {
|
||||
loadFragment(new SaleFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
// Added search/filter bar to filter adoptions by adopter name or pet name.
|
||||
// Added pull-to-refresh using SwipeRefreshLayout.
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.*;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.AdoptionAdapter;
|
||||
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.fragments.listfragments.detailfragments.AdoptionDetailFragment;
|
||||
import com.example.petstoremobile.models.Adoption;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener {
|
||||
|
||||
private List<Adoption> adoptionList = new ArrayList<>();
|
||||
private List<Adoption> filteredList = new ArrayList<>();
|
||||
private List<AdoptionDTO> adoptionList = new ArrayList<>();
|
||||
private List<AdoptionDTO> filteredList = new ArrayList<>();
|
||||
private AdoptionAdapter adapter;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private AdoptionApi api;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
private EditText etSearch;
|
||||
private ImageButton hamburger;
|
||||
|
||||
@@ -39,51 +36,58 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_adoption, container, false);
|
||||
|
||||
hamburger = view.findViewById(R.id.btnHamburger);
|
||||
api = RetrofitClient.getAdoptionApi(requireContext());
|
||||
hamburger = view.findViewById(R.id.btnHamburgerAdoption);
|
||||
|
||||
loadAdoptionData();
|
||||
// Replace with actual API call when backend is ready
|
||||
setupRecyclerView(view);
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadAdoptions();
|
||||
|
||||
FloatingActionButton fabAddAdoption = view.findViewById(R.id.fabAddAdoption);
|
||||
fabAddAdoption.setOnClickListener(v -> openAdoptionDetails(-1));
|
||||
FloatingActionButton fab = view.findViewById(R.id.fabAddAdoption);
|
||||
fab.setOnClickListener(v -> openDetail(-1));
|
||||
|
||||
//Make the hamburger button open the drawer from listFragment
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
//if list fragment is found then use its helper function to open the drawer
|
||||
if (listFragment != null) {
|
||||
listFragment.openDrawer();
|
||||
}
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.openDrawer();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
// Filters adoption list by adopter name or pet name
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView rv = view.findViewById(R.id.recyclerViewAdoptions);
|
||||
adapter = new AdoptionAdapter(filteredList, this);
|
||||
rv.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
rv.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchAdoption);
|
||||
etSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
filterAdoptions(s.toString());
|
||||
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());
|
||||
}
|
||||
@Override public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void filterAdoptions(String query) {
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefresh = view.findViewById(R.id.swipeRefreshAdoption);
|
||||
swipeRefresh.setOnRefreshListener(this::loadAdoptions);
|
||||
}
|
||||
|
||||
private void filter(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(adoptionList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (Adoption a : adoptionList) {
|
||||
if (a.getAdopterName().toLowerCase().contains(lower)
|
||||
|| a.getPetName().toLowerCase().contains(lower)
|
||||
|| a.getStatus().toLowerCase().contains(lower)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -91,73 +95,47 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshAdoption);
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
loadAdoptionData(); // TODO: Replace with actual API call
|
||||
filterAdoptions(etSearch.getText().toString());
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
private void loadAdoptions() {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(true);
|
||||
api.getAllAdoptions(0, 100).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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openAdoptionDetails(int position) {
|
||||
AdoptionDetailFragment detailFragment = new AdoptionDetailFragment();
|
||||
private void openDetail(int position) {
|
||||
AdoptionDetailFragment detail = new AdoptionDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("position", position);
|
||||
|
||||
if (position != -1) {
|
||||
Adoption adoption = filteredList.get(position);
|
||||
int realPosition = adoptionList.indexOf(adoption);
|
||||
args.putInt("position", realPosition);
|
||||
args.putInt("adoptionId", adoption.getAdoptionId());
|
||||
args.putString("adopterName", adoption.getAdopterName());
|
||||
args.putString("adopterEmail", adoption.getAdopterEmail());
|
||||
args.putString("adopterPhone", adoption.getAdopterPhone());
|
||||
args.putString("petName", adoption.getPetName());
|
||||
args.putString("adoptionDate", adoption.getAdoptionDate());
|
||||
args.putString("status", adoption.getStatus());
|
||||
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());
|
||||
}
|
||||
|
||||
detailFragment.setArguments(args);
|
||||
detailFragment.setAdoptionFragment(this);
|
||||
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.loadFragment(detailFragment);
|
||||
}
|
||||
|
||||
public void onAdoptionSaved(int position, Adoption adoption) {
|
||||
if (position == -1) {
|
||||
adoptionList.add(adoption);
|
||||
} else {
|
||||
adoptionList.set(position, adoption);
|
||||
}
|
||||
filterAdoptions(etSearch.getText().toString());
|
||||
}
|
||||
|
||||
public void onAdoptionDeleted(int position) {
|
||||
adoptionList.remove(position);
|
||||
filterAdoptions(etSearch.getText().toString());
|
||||
detail.setArguments(args);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.loadFragment(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdoptionClick(int position) {
|
||||
openAdoptionDetails(position);
|
||||
}
|
||||
|
||||
private void loadAdoptionData() {
|
||||
adoptionList.clear();
|
||||
adoptionList.add(new Adoption(1, "Sarah Connor", "sarah@email.com", "555-1234", "Luna", "2026-03-01", "Approved"));
|
||||
adoptionList.add(new Adoption(2, "Tom Hardy", "tom@email.com", "555-5678", "Bella", "2026-03-05", "Pending"));
|
||||
adoptionList.add(new Adoption(3, "Emily Clark", "emily@email.com", "555-9012", "Charlie", "2026-03-07", "Pending"));
|
||||
adoptionList.add(new Adoption(4, "Mike Ross", "mike@email.com", "555-3456", "Milo", "2026-02-20", "Rejected"));
|
||||
filteredList.clear();
|
||||
filteredList.addAll(adoptionList);
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewAdoptions);
|
||||
adapter = new AdoptionAdapter(filteredList, this);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
public void onAdoptionClick(int position) { openDetail(position); }
|
||||
}
|
||||
@@ -1,35 +1,52 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
// Added search/filter bar to filter appointments by customer name or service type.
|
||||
// Added pull-to-refresh using SwipeRefreshLayout.
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
||||
import com.example.petstoremobile.api.AppointmentApi;
|
||||
import com.example.petstoremobile.api.PetApi;
|
||||
import com.example.petstoremobile.api.ServiceApi;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.AppointmentDetailFragment;
|
||||
import com.example.petstoremobile.models.Appointment;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener {
|
||||
|
||||
private List<Appointment> appointmentList = new ArrayList<>(); // full data list
|
||||
private List<Appointment> filteredList = new ArrayList<>(); // filtered display list
|
||||
private List<AppointmentDTO> appointmentList = new ArrayList<>();
|
||||
private List<AppointmentDTO> filteredList = new ArrayList<>();
|
||||
private List<PetDTO> petList = new ArrayList<>();
|
||||
private List<ServiceDTO> serviceList = new ArrayList<>();
|
||||
|
||||
private AppointmentAdapter adapter;
|
||||
private AppointmentApi api;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private EditText etSearch;
|
||||
private ImageButton hamburger;
|
||||
@@ -39,51 +56,57 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_appointment, container, false);
|
||||
|
||||
api = RetrofitClient.getAppointmentApi(requireContext());
|
||||
hamburger = view.findViewById(R.id.btnHamburger);
|
||||
|
||||
loadAppointmentData(); // TODO: Replace with actual API call when backend is ready
|
||||
setupRecyclerView(view);
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadAppointmentData();
|
||||
loadPets();
|
||||
loadServices();
|
||||
|
||||
FloatingActionButton fabAddAppointment = view.findViewById(R.id.fabAddAppointment);
|
||||
fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1));
|
||||
|
||||
//Make the hamburger button open the drawer from listFragment
|
||||
FloatingActionButton fabAdd = view.findViewById(R.id.fabAddAppointment);
|
||||
fabAdd.setOnClickListener(v -> openAppointmentDetails(-1));
|
||||
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
//if list fragment is found then use its helper function to open the drawer
|
||||
if (listFragment != null) {
|
||||
if (listFragment != null)
|
||||
listFragment.openDrawer();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
// Sets up the search bar to filter appointments by customer name or service type
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchAppointment);
|
||||
etSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
filterAppointments(s.toString());
|
||||
}
|
||||
@Override public void afterTextChanged(Editable s) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Filters the appointment list based on the search query
|
||||
private void filterAppointments(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(appointmentList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (Appointment a : appointmentList) {
|
||||
if (a.getCustomerName().toLowerCase().contains(lower)
|
||||
|| a.getServiceType().toLowerCase().contains(lower)
|
||||
|| a.getPetName().toLowerCase().contains(lower)) {
|
||||
for (AppointmentDTO a : appointmentList) {
|
||||
if ((a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lower))
|
||||
|| (a.getServiceType() != null && a.getServiceType().toLowerCase().contains(lower))
|
||||
|| (a.getPetName() != null && a.getPetName().toLowerCase().contains(lower))) {
|
||||
filteredList.add(a);
|
||||
}
|
||||
}
|
||||
@@ -91,43 +114,33 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Sets up pull-to-refresh: reloads data when user swipes down
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshAppointment);
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
loadAppointmentData(); // TODO: Replace with actual API call when backend is ready
|
||||
filterAppointments(etSearch.getText().toString());
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
});
|
||||
swipeRefreshLayout.setOnRefreshListener(this::loadAppointmentData);
|
||||
}
|
||||
|
||||
private void openAppointmentDetails(int position) {
|
||||
AppointmentDetailFragment detailFragment = new AppointmentDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("position", position);
|
||||
|
||||
if (position != -1) {
|
||||
Appointment appointment = filteredList.get(position);
|
||||
// Find the real position in the full list for save/delete callbacks
|
||||
int realPosition = appointmentList.indexOf(appointment);
|
||||
args.putInt("position", realPosition);
|
||||
args.putInt("appointmentId", appointment.getAppointmentId());
|
||||
args.putString("customerName", appointment.getCustomerName());
|
||||
args.putString("petName", appointment.getPetName());
|
||||
args.putString("serviceType", appointment.getServiceType());
|
||||
args.putString("appointmentDate", appointment.getAppointmentDate());
|
||||
args.putString("appointmentTime", appointment.getAppointmentTime());
|
||||
args.putString("status", appointment.getStatus());
|
||||
AppointmentDTO a = filteredList.get(position);
|
||||
args.putLong("appointmentId", a.getAppointmentId());
|
||||
args.putString("appointmentDate", a.getAppointmentDate());
|
||||
args.putString("appointmentTime", a.getAppointmentTime());
|
||||
args.putString("appointmentStatus", a.getAppointmentStatus());
|
||||
// IDs for pre-selecting spinners
|
||||
if (a.getPetID() != null) args.putLong("petId", a.getPetID());
|
||||
if (a.getServiceId() != null) args.putLong("serviceId", a.getServiceId());
|
||||
if (a.getCustomerId() != null) args.putLong("customerId", a.getCustomerId());
|
||||
if (a.getStoreId() != null) args.putLong("storeId", a.getStoreId());
|
||||
}
|
||||
|
||||
detailFragment.setArguments(args);
|
||||
detailFragment.setAppointmentFragment(this);
|
||||
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.loadFragment(detailFragment);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.loadFragment(detailFragment);
|
||||
}
|
||||
|
||||
public void onAppointmentSaved(int position, Appointment appointment) {
|
||||
public void onAppointmentSaved(int position, AppointmentDTO appointment) {
|
||||
if (position == -1) {
|
||||
appointmentList.add(appointment);
|
||||
} else {
|
||||
@@ -146,21 +159,100 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
openAppointmentDetails(position);
|
||||
}
|
||||
|
||||
// Helper function to load hardcoded sample data
|
||||
// Replace with API call
|
||||
private void loadAppointmentData() {
|
||||
appointmentList.clear();
|
||||
appointmentList.add(new Appointment(1, "John Smith", "Buddy", "Grooming", "2026-03-10", "10:00 AM", "Confirmed"));
|
||||
appointmentList.add(new Appointment(2, "Jane Doe", "Luna", "Vet Checkup", "2026-03-11", "02:00 PM", "Pending"));
|
||||
appointmentList.add(new Appointment(3, "Bob Lee", "Max", "Training", "2026-03-12", "11:00 AM", "Confirmed"));
|
||||
appointmentList.add(new Appointment(4, "Alice Brown", "Milo", "Grooming", "2026-03-13", "03:00 PM", "Cancelled"));
|
||||
filteredList.clear();
|
||||
filteredList.addAll(appointmentList);
|
||||
if (swipeRefreshLayout != null)
|
||||
swipeRefreshLayout.setRefreshing(true);
|
||||
api.getAllAppointments(0, 100).enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<AppointmentDTO>> call,
|
||||
Response<PageResponse<AppointmentDTO>> response) {
|
||||
if (swipeRefreshLayout != null)
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
appointmentList.clear();
|
||||
appointmentList.addAll(response.body().getContent());
|
||||
filterAppointments(etSearch != null ? etSearch.getText().toString() : "");
|
||||
} else {
|
||||
Log.e("AppointmentFragment", "Error: " + response.message());
|
||||
Toast.makeText(getContext(), "Failed to load appointments", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<AppointmentDTO>> call, Throwable t) {
|
||||
if (swipeRefreshLayout != null)
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
Log.e("AppointmentFragment", t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Load Pets
|
||||
private void loadPets() {
|
||||
PetApi petApi = RetrofitClient.getPetApi(requireContext());
|
||||
petApi.getAllPets(0,100).enqueue(new Callback<PageResponse<PetDTO>>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<PetDTO>> call, Response<PageResponse<PetDTO>> response) {
|
||||
if (response.isSuccessful() && response.body() !=null) {
|
||||
petList.clear();
|
||||
petList.addAll(response.body().getContent());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<PetDTO>> call, Throwable t) {
|
||||
|
||||
Log.e("AppointmentFragment", "Pet load error:" + t.getMessage());
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load Services
|
||||
|
||||
private void loadServices() {
|
||||
ServiceApi serviceApi = RetrofitClient.getServiceApi(requireContext());
|
||||
|
||||
serviceApi.getAllServices(0,100).enqueue(new Callback<PageResponse<ServiceDTO>>() {
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<ServiceDTO>> call, Response<PageResponse<ServiceDTO>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
serviceList.clear();
|
||||
serviceList.addAll(response.body().getContent());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<ServiceDTO>> call, Throwable t) {
|
||||
Log.e("AppointmentFragmnet", "Service load error: " + t.getMessage());
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getPetName(Long id) {
|
||||
for (PetDTO p : petList) {
|
||||
if (p.getPetId().equals(id)) return p.getPetName();
|
||||
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getServiceName(Long id) {
|
||||
for (ServiceDTO s : serviceList) {
|
||||
if (s.getServiceId().equals(id))return s.getServiceName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewAppointments);
|
||||
adapter = new AppointmentAdapter(filteredList, this); // adapter uses filteredList
|
||||
adapter = new AppointmentAdapter(filteredList, this);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@@ -1,162 +1,379 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
// Added search/filter bar to filter inventory by item name or category.
|
||||
// Added pull-to-refresh using SwipeRefreshLayout.
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.adapters.InventoryAdapter;
|
||||
import com.example.petstoremobile.api.CategoryApi;
|
||||
import com.example.petstoremobile.api.InventoryApi;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.InventoryDetailFragment;
|
||||
import com.example.petstoremobile.models.Inventory;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
||||
|
||||
private List<Inventory> inventoryList = new ArrayList<>();
|
||||
private List<Inventory> filteredList = new ArrayList<>();
|
||||
private static final String TAG = "InventoryFragment";
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
private final List<InventoryDTO> inventoryList = new ArrayList<>();
|
||||
private final List<CategoryDTO> categoryList = new ArrayList<>();
|
||||
private InventoryAdapter adapter;
|
||||
private InventoryApi inventoryApi;
|
||||
private CategoryApi categoryApi;
|
||||
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private EditText etSearch;
|
||||
private Spinner spinnerCategory;
|
||||
private ImageButton hamburger;
|
||||
private Button btnBulkDelete;
|
||||
private TextView tvSelectionCount;
|
||||
|
||||
// Debounce search
|
||||
private final Handler searchHandler = new Handler(Looper.getMainLooper());
|
||||
private Runnable searchRunnable;
|
||||
private String currentQuery = "";
|
||||
|
||||
// Selected category filter — null means "All"
|
||||
private String selectedCategory = null;
|
||||
|
||||
// Pagination
|
||||
private int currentPage = 0;
|
||||
private boolean isLastPage = false;
|
||||
private boolean isLoading = false;
|
||||
|
||||
// Prevent spinner from firing on initial load
|
||||
private boolean spinnerReady = false;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_inventory, container, false);
|
||||
|
||||
hamburger = view.findViewById(R.id.btnHamburger);
|
||||
inventoryApi = RetrofitClient.getInventoryApi(requireContext());
|
||||
categoryApi = RetrofitClient.getCategoryApi(requireContext());
|
||||
|
||||
hamburger = view.findViewById(R.id.btnHamburger);
|
||||
btnBulkDelete = view.findViewById(R.id.btnBulkDelete);
|
||||
tvSelectionCount = view.findViewById(R.id.tvSelectionCount);
|
||||
spinnerCategory = view.findViewById(R.id.spinnerCategory);
|
||||
|
||||
loadInventoryData(); // TODO: Replace with actual API call when backend is ready
|
||||
setupRecyclerView(view);
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadCategories(); // loads categories then triggers loadInventory
|
||||
loadInventory(true);
|
||||
|
||||
FloatingActionButton fabAddInventory = view.findViewById(R.id.fabAddInventory);
|
||||
fabAddInventory.setOnClickListener(v -> openInventoryDetails(-1));
|
||||
view.findViewById(R.id.fabAddInventory)
|
||||
.setOnClickListener(v -> openDetail(null));
|
||||
|
||||
//Make the hamburger button open the drawer from listFragment
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
//if list fragment is found then use its helper function to open the drawer
|
||||
if (listFragment != null) {
|
||||
listFragment.openDrawer();
|
||||
}
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null)
|
||||
lf.openDrawer();
|
||||
});
|
||||
|
||||
btnBulkDelete.setOnClickListener(v -> confirmBulkDelete());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
// Filters inventory list by item name or category
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchInventory);
|
||||
etSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
filterInventory(s.toString());
|
||||
// Categories
|
||||
private void loadCategories() {
|
||||
categoryApi.getAllCategories(0, 100).enqueue(new Callback<PageResponse<CategoryDTO>>() {
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<CategoryDTO>> call,
|
||||
Response<PageResponse<CategoryDTO>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
categoryList.clear();
|
||||
categoryList.addAll(response.body().getContent());
|
||||
setupCategorySpinner();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<CategoryDTO>> call, Throwable t) {
|
||||
Log.e(TAG, "Failed to load categories", t);
|
||||
// Still setup spinner with just "All"
|
||||
setupCategorySpinner();
|
||||
}
|
||||
@Override public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void filterInventory(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(inventoryList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (Inventory i : inventoryList) {
|
||||
if (i.getItemName().toLowerCase().contains(lower)
|
||||
|| i.getCategory().toLowerCase().contains(lower)
|
||||
|| i.getSupplier().toLowerCase().contains(lower)) {
|
||||
filteredList.add(i);
|
||||
private void setupCategorySpinner() {
|
||||
// First item is always "All Categories"
|
||||
List<String> categoryNames = new ArrayList<>();
|
||||
categoryNames.add("All Categories");
|
||||
for (CategoryDTO c : categoryList) {
|
||||
categoryNames.add(c.getCategoryName());
|
||||
}
|
||||
|
||||
BlackTextArrayAdapter<String> spinnerAdapter = new BlackTextArrayAdapter<>(
|
||||
requireContext(),
|
||||
android.R.layout.simple_spinner_item,
|
||||
categoryNames);
|
||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerCategory.setAdapter(spinnerAdapter);
|
||||
|
||||
spinnerCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (!spinnerReady) {
|
||||
// Skip the first automatic trigger on setup
|
||||
spinnerReady = true;
|
||||
return;
|
||||
}
|
||||
if (position == 0) {
|
||||
selectedCategory = null; // "All Categories"
|
||||
} else {
|
||||
selectedCategory = categoryList.get(position - 1).getCategoryName();
|
||||
}
|
||||
loadInventory(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Search
|
||||
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchInventory);
|
||||
etSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (searchRunnable != null)
|
||||
searchHandler.removeCallbacks(searchRunnable);
|
||||
searchRunnable = () -> {
|
||||
currentQuery = s.toString().trim();
|
||||
loadInventory(true);
|
||||
};
|
||||
searchHandler.postDelayed(searchRunnable, 400);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// RecyclerView + infinite scroll
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView rv = view.findViewById(R.id.recyclerViewInventory);
|
||||
adapter = new InventoryAdapter(inventoryList, this);
|
||||
rv.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
rv.setAdapter(adapter);
|
||||
|
||||
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
if (dy <= 0)
|
||||
return;
|
||||
LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager();
|
||||
if (lm == null)
|
||||
return;
|
||||
int visible = lm.getChildCount();
|
||||
int total = lm.getItemCount();
|
||||
int firstVis = lm.findFirstVisibleItemPosition();
|
||||
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
||||
loadInventory(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
}
|
||||
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshInventory);
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
loadInventoryData(); // TODO: Replace with actual API call
|
||||
filterInventory(etSearch.getText().toString());
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
});
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> loadInventory(true));
|
||||
}
|
||||
|
||||
private void openInventoryDetails(int position) {
|
||||
InventoryDetailFragment detailFragment = new InventoryDetailFragment();
|
||||
// Load inventory
|
||||
private void loadInventory(boolean reset) {
|
||||
if (isLoading)
|
||||
return;
|
||||
isLoading = true;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 0;
|
||||
isLastPage = false;
|
||||
}
|
||||
|
||||
// Build query: combine search text + selected category
|
||||
String q = buildQuery();
|
||||
|
||||
inventoryApi.getAllInventory(q, currentPage, PAGE_SIZE, "inventoryId,asc")
|
||||
.enqueue(new Callback<PageResponse<InventoryDTO>>() {
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<InventoryDTO>> call,
|
||||
Response<PageResponse<InventoryDTO>> response) {
|
||||
isLoading = false;
|
||||
if (swipeRefreshLayout != null)
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
PageResponse<InventoryDTO> page = response.body();
|
||||
if (reset)
|
||||
inventoryList.clear();
|
||||
inventoryList.addAll(page.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
isLastPage = page.isLast();
|
||||
if (!isLastPage)
|
||||
currentPage++;
|
||||
} else {
|
||||
Log.e(TAG, "Error " + response.code());
|
||||
Toast.makeText(getContext(), "Failed to load inventory", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<InventoryDTO>> call, Throwable t) {
|
||||
isLoading = false;
|
||||
if (swipeRefreshLayout != null)
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
Log.e(TAG, "Network error", t);
|
||||
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Combines search text and category into one query string for ?q=
|
||||
private String buildQuery() {
|
||||
String q = null;
|
||||
if (!currentQuery.isEmpty() && selectedCategory != null) {
|
||||
// Both active — prioritize search text, category acts as context
|
||||
q = currentQuery;
|
||||
} else if (!currentQuery.isEmpty()) {
|
||||
q = currentQuery;
|
||||
} else if (selectedCategory != null) {
|
||||
q = selectedCategory;
|
||||
}
|
||||
return q;
|
||||
}
|
||||
|
||||
// Bulk delete
|
||||
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();
|
||||
}
|
||||
|
||||
private void bulkDelete(List<Long> ids) {
|
||||
inventoryApi.bulkDeleteInventory(new BulkDeleteRequest(ids))
|
||||
.enqueue(new Callback<Void>() {
|
||||
@Override
|
||||
public void onResponse(Call<Void> call, Response<Void> response) {
|
||||
if (response.isSuccessful()) {
|
||||
adapter.clearSelection();
|
||||
hideBulkDeleteBar();
|
||||
loadInventory(true);
|
||||
Toast.makeText(getContext(), ids.size() + " item(s) deleted", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Delete failed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<Void> call, Throwable t) {
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void hideBulkDeleteBar() {
|
||||
if (btnBulkDelete != null)
|
||||
btnBulkDelete.setVisibility(View.GONE);
|
||||
if (tvSelectionCount != null)
|
||||
tvSelectionCount.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Navigation
|
||||
private void openDetail(InventoryDTO inv) {
|
||||
InventoryDetailFragment detail = new InventoryDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("position", position);
|
||||
|
||||
if (position != -1) {
|
||||
Inventory inventory = filteredList.get(position);
|
||||
int realPosition = inventoryList.indexOf(inventory);
|
||||
args.putInt("position", realPosition);
|
||||
args.putInt("inventoryId", inventory.getInventoryId());
|
||||
args.putString("itemName", inventory.getItemName());
|
||||
args.putString("category", inventory.getCategory());
|
||||
args.putInt("quantity", inventory.getQuantity());
|
||||
args.putDouble("unitPrice", inventory.getUnitPrice());
|
||||
args.putString("supplier", inventory.getSupplier());
|
||||
if (inv != null) {
|
||||
args.putLong("inventoryId", inv.getInventoryId());
|
||||
args.putLong("prodId", inv.getProdId() != null ? inv.getProdId() : -1);
|
||||
args.putString("productName", inv.getProductName());
|
||||
args.putString("categoryName", inv.getCategoryName());
|
||||
args.putInt("quantity", inv.getQuantity() != null ? inv.getQuantity() : 0);
|
||||
}
|
||||
|
||||
detailFragment.setArguments(args);
|
||||
detailFragment.setInventoryFragment(this);
|
||||
detail.setArguments(args);
|
||||
detail.setInventoryFragment(this);
|
||||
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.loadFragment(detailFragment);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null)
|
||||
lf.loadFragment(detail);
|
||||
}
|
||||
|
||||
public void onInventorySaved(int position, Inventory inventory) {
|
||||
if (position == -1) {
|
||||
inventoryList.add(inventory);
|
||||
} else {
|
||||
inventoryList.set(position, inventory);
|
||||
}
|
||||
filterInventory(etSearch.getText().toString());
|
||||
public void onInventoryChanged() {
|
||||
loadInventory(true);
|
||||
}
|
||||
|
||||
public void onInventoryDeleted(int position) {
|
||||
inventoryList.remove(position);
|
||||
filterInventory(etSearch.getText().toString());
|
||||
}
|
||||
// Adapter callbacks
|
||||
|
||||
@Override
|
||||
public void onInventoryClick(int position) {
|
||||
openInventoryDetails(position);
|
||||
if (position >= 0 && position < inventoryList.size()) {
|
||||
openDetail(inventoryList.get(position));
|
||||
}
|
||||
}
|
||||
|
||||
private void loadInventoryData() {
|
||||
inventoryList.clear();
|
||||
inventoryList.add(new Inventory(1, "Dog Food - Large", "Food", 50, 25.99, "PetSupplies Co."));
|
||||
inventoryList.add(new Inventory(2, "Cat Litter", "Hygiene", 30, 12.99, "CleanPaws Ltd."));
|
||||
inventoryList.add(new Inventory(3, "Dog Leash", "Accessories", 4, 15.99, "PetGear Inc."));
|
||||
inventoryList.add(new Inventory(4, "Bird Cage - Medium", "Housing", 8, 79.99, "BirdWorld"));
|
||||
inventoryList.add(new Inventory(5, "Flea Treatment", "Medicine", 2, 34.99, "VetCare Supply"));
|
||||
filteredList.clear();
|
||||
filteredList.addAll(inventoryList);
|
||||
@Override
|
||||
public void onSelectionChanged(int selectedCount) {
|
||||
if (selectedCount > 0) {
|
||||
btnBulkDelete.setVisibility(View.VISIBLE);
|
||||
tvSelectionCount.setVisibility(View.VISIBLE);
|
||||
tvSelectionCount.setText(selectedCount + " selected");
|
||||
} else {
|
||||
hideBulkDeleteBar();
|
||||
}
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewInventory);
|
||||
adapter = new InventoryAdapter(filteredList, this);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.adapters.PetAdapter;
|
||||
import com.example.petstoremobile.api.PetApi;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
@@ -63,7 +64,6 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
setupSearch(view);
|
||||
setupStatusFilter(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadPetData();
|
||||
|
||||
|
||||
//Add button to opens the add dialog
|
||||
@@ -82,6 +82,12 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadPetData();
|
||||
}
|
||||
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchPet);
|
||||
etSearch.addTextChangedListener(new TextWatcher() {
|
||||
@@ -97,7 +103,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
private void setupStatusFilter(View view) {
|
||||
spinnerStatus = view.findViewById(R.id.spinnerStatus);
|
||||
String[] statuses = {"All Statuses", "Available", "Adopted"};
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statuses);
|
||||
BlackTextArrayAdapter<String> adapter = new BlackTextArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, statuses);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerStatus.setAdapter(adapter);
|
||||
|
||||
|
||||
@@ -1,88 +1,87 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
// Added search/filter bar to filter products by name or category.
|
||||
// Added pull-to-refresh using SwipeRefreshLayout.
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.*;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.ProductAdapter;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.ProductDetailFragment;
|
||||
import com.example.petstoremobile.models.Product;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class ProductFragment extends Fragment implements ProductAdapter.OnProductClickListener {
|
||||
|
||||
private List<Product> productList = new ArrayList<>();
|
||||
private List<Product> filteredList = new ArrayList<>();
|
||||
private List<ProductDTO> productList = new ArrayList<>();
|
||||
private List<ProductDTO> filteredList = new ArrayList<>();
|
||||
private ProductAdapter adapter;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
private EditText etSearch;
|
||||
private ImageButton hamburger;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_product, container, false);
|
||||
|
||||
hamburger = view.findViewById(R.id.btnHamburger);
|
||||
|
||||
loadProductData(); // TODO: Replace with actual API call when backend is ready
|
||||
setupRecyclerView(view);
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadProducts();
|
||||
|
||||
FloatingActionButton fabAddProduct = view.findViewById(R.id.fabAddProduct);
|
||||
fabAddProduct.setOnClickListener(v -> openProductDetails(-1));
|
||||
FloatingActionButton fab = view.findViewById(R.id.fabAddProduct);
|
||||
fab.setOnClickListener(v -> openDetail(-1));
|
||||
|
||||
//Make the hamburger button open the drawer from listFragment
|
||||
ImageButton hamburger = view.findViewById(R.id.btnHamburgerProduct);
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
//if list fragment is found then use its helper function to open the drawer
|
||||
if (listFragment != null) {
|
||||
listFragment.openDrawer();
|
||||
}
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.openDrawer();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
// Filters products by name, description, or category
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView rv = view.findViewById(R.id.recyclerViewProducts);
|
||||
adapter = new ProductAdapter(filteredList, this);
|
||||
rv.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
rv.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchProduct);
|
||||
etSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
filterProducts(s.toString());
|
||||
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());
|
||||
}
|
||||
@Override public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void filterProducts(String query) {
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefresh = view.findViewById(R.id.swipeRefreshProduct);
|
||||
swipeRefresh.setOnRefreshListener(this::loadProducts);
|
||||
}
|
||||
|
||||
private void filter(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(productList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (Product p : productList) {
|
||||
if (p.getProductName().toLowerCase().contains(lower)
|
||||
|| p.getCategory().toLowerCase().contains(lower)
|
||||
|| p.getProductDesc().toLowerCase().contains(lower)) {
|
||||
for (ProductDTO p : productList) {
|
||||
if ((p.getProdName() != null && p.getProdName().toLowerCase().contains(lower))
|
||||
|| (p.getCategoryName() != null && p.getCategoryName().toLowerCase().contains(lower))) {
|
||||
filteredList.add(p);
|
||||
}
|
||||
}
|
||||
@@ -90,73 +89,45 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshProduct);
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
loadProductData(); // TODO: Replace with actual API call
|
||||
filterProducts(etSearch.getText().toString());
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
});
|
||||
private void loadProducts() {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(true);
|
||||
RetrofitClient.getProductApi(requireContext()).getAllProducts(null, 0, 100)
|
||||
.enqueue(new Callback<PageResponse<ProductDTO>>() {
|
||||
public void onResponse(Call<PageResponse<ProductDTO>> c,
|
||||
Response<PageResponse<ProductDTO>> r) {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
productList.clear();
|
||||
productList.addAll(r.body().getContent());
|
||||
filter(etSearch != null ? etSearch.getText().toString() : "");
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Failed to load products",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<ProductDTO>> c, Throwable t) {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
|
||||
Log.e("ProductFragment", t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openProductDetails(int position) {
|
||||
ProductDetailFragment detailFragment = new ProductDetailFragment();
|
||||
private void openDetail(int position) {
|
||||
ProductDetailFragment detail = new ProductDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("position", position);
|
||||
|
||||
if (position != -1) {
|
||||
Product product = filteredList.get(position);
|
||||
int realPosition = productList.indexOf(product);
|
||||
args.putInt("position", realPosition);
|
||||
args.putInt("productId", product.getProductId());
|
||||
args.putString("productName", product.getProductName());
|
||||
args.putString("productDesc", product.getProductDesc());
|
||||
args.putString("category", product.getCategory());
|
||||
args.putDouble("productPrice", product.getProductPrice());
|
||||
args.putInt("stockQuantity", product.getStockQuantity());
|
||||
ProductDTO p = filteredList.get(position);
|
||||
args.putLong("prodId", p.getProdId());
|
||||
args.putString("prodName", p.getProdName());
|
||||
args.putString("prodDesc", p.getProdDesc() != null ? p.getProdDesc() : "");
|
||||
args.putString("prodPrice", p.getProdPrice() != null ? p.getProdPrice().toString() : "");
|
||||
args.putLong("categoryId", p.getCategoryId() != null ? p.getCategoryId() : -1);
|
||||
}
|
||||
|
||||
detailFragment.setArguments(args);
|
||||
detailFragment.setProductFragment(this);
|
||||
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.loadFragment(detailFragment);
|
||||
}
|
||||
|
||||
public void onProductSaved(int position, Product product) {
|
||||
if (position == -1) {
|
||||
productList.add(product);
|
||||
} else {
|
||||
productList.set(position, product);
|
||||
}
|
||||
filterProducts(etSearch.getText().toString());
|
||||
}
|
||||
|
||||
public void onProductDeleted(int position) {
|
||||
productList.remove(position);
|
||||
filterProducts(etSearch.getText().toString());
|
||||
detail.setArguments(args);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.loadFragment(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProductClick(int position) {
|
||||
openProductDetails(position);
|
||||
}
|
||||
|
||||
private void loadProductData() {
|
||||
productList.clear();
|
||||
productList.add(new Product(1, "Premium Dog Food", "High protein dry food for adult dogs", "Food", 45.99, 25));
|
||||
productList.add(new Product(2, "Cat Toy Bundle", "Set of 5 interactive toys", "Toys", 19.99, 40));
|
||||
productList.add(new Product(3, "Pet Shampoo", "Gentle formula for all breeds", "Grooming", 12.99, 60));
|
||||
productList.add(new Product(4, "Dog Bed - Large", "Memory foam orthopedic bed", "Bedding", 89.99, 10));
|
||||
productList.add(new Product(5, "Aquarium Starter Kit", "20-gallon tank with filter and light", "Aquatic", 129.99, 5));
|
||||
filteredList.clear();
|
||||
filteredList.addAll(productList);
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewProducts);
|
||||
adapter = new ProductAdapter(filteredList, this);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
public void onProductClick(int position) { openDetail(position); }
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.*;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.ProductSupplierAdapter;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.ProductSupplierDetailFragment;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class ProductSupplierFragment extends Fragment
|
||||
implements ProductSupplierAdapter.OnProductSupplierClickListener {
|
||||
|
||||
private List<ProductSupplierDTO> psList = new ArrayList<>();
|
||||
private List<ProductSupplierDTO> filteredList = new ArrayList<>();
|
||||
private ProductSupplierAdapter adapter;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
private EditText etSearch;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_product_supplier, container, false);
|
||||
|
||||
setupRecyclerView(view);
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadData();
|
||||
|
||||
FloatingActionButton fab = view.findViewById(R.id.fabAddPS);
|
||||
fab.setOnClickListener(v -> openDetail(-1));
|
||||
|
||||
ImageButton hamburger = view.findViewById(R.id.btnHamburgerPS);
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.openDrawer();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView rv = view.findViewById(R.id.recyclerViewPS);
|
||||
adapter = new ProductSupplierAdapter(filteredList, this);
|
||||
rv.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
rv.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchPS);
|
||||
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.swipeRefreshPS);
|
||||
swipeRefresh.setOnRefreshListener(this::loadData);
|
||||
}
|
||||
|
||||
private void filter(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(psList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (ProductSupplierDTO ps : psList) {
|
||||
if ((ps.getProductName() != null && ps.getProductName().toLowerCase().contains(lower))
|
||||
|| (ps.getSupplierName() != null && ps.getSupplierName().toLowerCase().contains(lower))) {
|
||||
filteredList.add(ps);
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(true);
|
||||
RetrofitClient.getProductSupplierApi(requireContext()).getAllProductSuppliers(0, 100)
|
||||
.enqueue(new Callback<PageResponse<ProductSupplierDTO>>() {
|
||||
public void onResponse(Call<PageResponse<ProductSupplierDTO>> c,
|
||||
Response<PageResponse<ProductSupplierDTO>> r) {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
psList.clear();
|
||||
psList.addAll(r.body().getContent());
|
||||
filter(etSearch != null ? etSearch.getText().toString() : "");
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Failed to load",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<ProductSupplierDTO>> c, Throwable t) {
|
||||
if (swipeRefresh != null) swipeRefresh.setRefreshing(false);
|
||||
Log.e("PSFragment", t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openDetail(int position) {
|
||||
ProductSupplierDetailFragment detail = new ProductSupplierDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
if (position != -1) {
|
||||
ProductSupplierDTO ps = filteredList.get(position);
|
||||
args.putLong("productId", ps.getProductId());
|
||||
args.putLong("supplierId", ps.getSupplierId());
|
||||
args.putString("productName", ps.getProductName());
|
||||
args.putString("supplierName", ps.getSupplierName());
|
||||
args.putString("cost", ps.getCost() != null ? ps.getCost().toString() : "");
|
||||
}
|
||||
detail.setArguments(args);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.loadFragment(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProductSupplierClick(int position) { openDetail(position); }
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.*;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.PurchaseOrderAdapter;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.PurchaseOrderDetailFragment;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class PurchaseOrderFragment extends Fragment
|
||||
implements PurchaseOrderAdapter.OnPurchaseOrderClickListener {
|
||||
|
||||
private List<PurchaseOrderDTO> poList = new ArrayList<>();
|
||||
private List<PurchaseOrderDTO> filteredList = new ArrayList<>();
|
||||
private PurchaseOrderAdapter adapter;
|
||||
private SwipeRefreshLayout swipeRefresh;
|
||||
private EditText etSearch;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_purchase_order, container, false);
|
||||
|
||||
setupRecyclerView(view);
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadData();
|
||||
|
||||
ImageButton hamburger = view.findViewById(R.id.btnHamburgerPO);
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null)
|
||||
lf.openDrawer();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView rv = view.findViewById(R.id.recyclerViewPO);
|
||||
adapter = new PurchaseOrderAdapter(filteredList, this);
|
||||
rv.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
rv.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchPO);
|
||||
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.swipeRefreshPO);
|
||||
swipeRefresh.setOnRefreshListener(this::loadData);
|
||||
}
|
||||
|
||||
private void filter(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(poList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (PurchaseOrderDTO po : poList) {
|
||||
if ((po.getSupplierName() != null && po.getSupplierName().toLowerCase().contains(lower))
|
||||
|| (po.getStatus() != null && po.getStatus().toLowerCase().contains(lower))) {
|
||||
filteredList.add(po);
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
if (swipeRefresh != null)
|
||||
swipeRefresh.setRefreshing(true);
|
||||
RetrofitClient.getPurchaseOrderApi(requireContext()).getAllPurchaseOrders(0, 100)
|
||||
.enqueue(new Callback<PageResponse<PurchaseOrderDTO>>() {
|
||||
public void onResponse(Call<PageResponse<PurchaseOrderDTO>> c,
|
||||
Response<PageResponse<PurchaseOrderDTO>> r) {
|
||||
if (swipeRefresh != null)
|
||||
swipeRefresh.setRefreshing(false);
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
poList.clear();
|
||||
poList.addAll(r.body().getContent());
|
||||
filter(etSearch != null ? etSearch.getText().toString() : "");
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Failed to load purchase orders",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
public void onFailure(Call<PageResponse<PurchaseOrderDTO>> c, Throwable t) {
|
||||
if (swipeRefresh != null)
|
||||
swipeRefresh.setRefreshing(false);
|
||||
Log.e("POFragment", t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openDetail(int position) {
|
||||
PurchaseOrderDetailFragment detail = new PurchaseOrderDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
PurchaseOrderDTO po = filteredList.get(position);
|
||||
args.putLong("purchaseOrderId", po.getPurchaseOrderId());
|
||||
args.putString("supplierName", po.getSupplierName());
|
||||
args.putString("orderDate", po.getOrderDate());
|
||||
args.putString("status", po.getStatus());
|
||||
detail.setArguments(args);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null)
|
||||
lf.loadFragment(detail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPurchaseOrderClick(int position) {
|
||||
openDetail(position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.SaleAdapter;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.RefundDetailFragment;
|
||||
import com.example.petstoremobile.models.Sale;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
||||
|
||||
private List<Sale> saleList = new ArrayList<>();
|
||||
private List<Sale> filteredList = new ArrayList<>();
|
||||
private SaleAdapter adapter;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private EditText etSearch;
|
||||
private ImageButton btnHamburger;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_sale, container, false);
|
||||
|
||||
btnHamburger = view.findViewById(R.id.btnHamburger);
|
||||
|
||||
setupRecyclerView(view);
|
||||
loadSaleData();
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
|
||||
// Make the hamburger button open the drawer from listFragment
|
||||
if (btnHamburger != null) {
|
||||
btnHamburger.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) {
|
||||
listFragment.openDrawer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchSale);
|
||||
etSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
filterSales(s.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void filterSales(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(saleList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (Sale s : saleList) {
|
||||
if (s.getItemName().toLowerCase().contains(lower)
|
||||
|| s.getEmployeeName().toLowerCase().contains(lower)
|
||||
|| s.getSaleDate().toLowerCase().contains(lower)
|
||||
|| s.getPaymentMethod().toLowerCase().contains(lower)
|
||||
|| String.valueOf(s.getSaleId()).contains(lower)) {
|
||||
filteredList.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshSale);
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
loadSaleData();
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
});
|
||||
}
|
||||
|
||||
// When a sale row is clicked, open the refund screen for that sale
|
||||
@Override
|
||||
public void onSaleClick(int position) {
|
||||
Sale sale = filteredList.get(position);
|
||||
RefundDetailFragment refundFragment = new RefundDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("saleId", sale.getSaleId());
|
||||
args.putString("saleDate", sale.getSaleDate());
|
||||
args.putString("employeeName", sale.getEmployeeName());
|
||||
args.putDouble("total", sale.getTotal());
|
||||
args.putString("paymentMethod", sale.getPaymentMethod());
|
||||
refundFragment.setArguments(args);
|
||||
refundFragment.setSaleFragment(this);
|
||||
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null)
|
||||
listFragment.loadFragment(refundFragment);
|
||||
}
|
||||
|
||||
public void reloadSales() {
|
||||
loadSaleData();
|
||||
}
|
||||
|
||||
// TODO: Replace with actual API call - GET v1/sales
|
||||
private void loadSaleData() {
|
||||
saleList.clear();
|
||||
saleList.add(new Sale(1, "2026-03-01", "John Smith", "Premium Dog Food", 2, 45.99, 91.98, "Card", false));
|
||||
saleList.add(new Sale(2, "2026-03-02", "Jane Doe", "Cat Toy Bundle", 1, 19.99, 19.99, "Cash", false));
|
||||
saleList.add(new Sale(3, "2026-03-03", "John Smith", "Pet Shampoo", 3, 12.99, 38.97, "Card", false));
|
||||
saleList.add(new Sale(4, "2026-03-04", "Jane Doe", "Dog Bed - Large", 1, 89.99, 89.99, "Cash", true));
|
||||
filteredList.clear();
|
||||
filteredList.addAll(saleList);
|
||||
if (adapter != null)
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewSales);
|
||||
adapter = new SaleAdapter(filteredList, this);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
@@ -1,172 +1,258 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
|
||||
// Uses InputValidator for detailed field validation and ActivityLogger to log all changes.
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.api.*;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.AdoptionFragment;
|
||||
import com.example.petstoremobile.models.Adoption;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class AdoptionDetailFragment extends Fragment {
|
||||
|
||||
private TextView tvMode, tvAdoptionId;
|
||||
private EditText etAdopterName, etAdopterEmail, etAdopterPhone, etPetName, etAdoptionDate;
|
||||
private Spinner spinnerAdoptionStatus;
|
||||
private Button btnSaveAdoption, btnDeleteAdoption, btnBack;
|
||||
private int adoptionId;
|
||||
private int position;
|
||||
private boolean isEditing = false;
|
||||
private AdoptionFragment adoptionFragment;
|
||||
private EditText etAdoptionDate;
|
||||
private Spinner spinnerPet, spinnerCustomer, spinnerStatus;
|
||||
private Button btnSave, btnDelete, btnBack;
|
||||
|
||||
// Set the adoption fragment as parent so we refer back when save or delete is done
|
||||
public void setAdoptionFragment(AdoptionFragment fragment) {
|
||||
this.adoptionFragment = fragment;
|
||||
}
|
||||
private long adoptionId = -1;
|
||||
private boolean isEditing = false;
|
||||
private long preselectedPetId = -1;
|
||||
private long preselectedCustomerId = -1;
|
||||
|
||||
private List<PetDTO> petList = new ArrayList<>();
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
|
||||
private final String[] STATUSES = {"Pending", "Approved", "Rejected"};
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_adoption_detail, container, false);
|
||||
|
||||
initViews(view);
|
||||
setupSpinner();
|
||||
setupSpinners();
|
||||
setupDatePicker();
|
||||
loadData();
|
||||
handleArguments();
|
||||
|
||||
btnBack.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
});
|
||||
btnSaveAdoption.setOnClickListener(v -> saveAdoption());
|
||||
btnDeleteAdoption.setOnClickListener(v -> deleteAdoption());
|
||||
|
||||
btnBack.setOnClickListener(v -> navigateBack());
|
||||
btnSave.setOnClickListener(v -> saveAdoption());
|
||||
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||
return view;
|
||||
}
|
||||
|
||||
// Validates all fields using InputValidator, then saves the adoption record
|
||||
private void saveAdoption() {
|
||||
if (!InputValidator.isNotEmpty(etAdopterName, "Adopter Name")) return;
|
||||
if (!InputValidator.isValidEmail(etAdopterEmail)) return;
|
||||
if (!InputValidator.isValidPhone(etAdopterPhone)) return;
|
||||
if (!InputValidator.isNotEmpty(etPetName, "Pet Name")) return;
|
||||
if (!InputValidator.isValidDate(etAdoptionDate)) return;
|
||||
private void initViews(View v) {
|
||||
tvMode = v.findViewById(R.id.tvAdoptionMode);
|
||||
tvAdoptionId = v.findViewById(R.id.tvAdoptionId);
|
||||
etAdoptionDate = v.findViewById(R.id.etAdoptionDate);
|
||||
spinnerPet = v.findViewById(R.id.spinnerAdoptionPet);
|
||||
spinnerCustomer= v.findViewById(R.id.spinnerAdoptionCustomer);
|
||||
spinnerStatus = v.findViewById(R.id.spinnerAdoptionStatus);
|
||||
btnSave = v.findViewById(R.id.btnSaveAdoption);
|
||||
btnDelete = v.findViewById(R.id.btnDeleteAdoption);
|
||||
btnBack = v.findViewById(R.id.btnAdoptionBack);
|
||||
}
|
||||
|
||||
String adopterName = etAdopterName.getText().toString().trim();
|
||||
String adopterEmail = etAdopterEmail.getText().toString().trim();
|
||||
String adopterPhone = etAdopterPhone.getText().toString().trim();
|
||||
String petName = etPetName.getText().toString().trim();
|
||||
String adoptionDate = etAdoptionDate.getText().toString().trim();
|
||||
String status = spinnerAdoptionStatus.getSelectedItem().toString();
|
||||
private void setupSpinners() {
|
||||
spinnerStatus.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, STATUSES));
|
||||
}
|
||||
|
||||
try {
|
||||
if (isEditing) {
|
||||
// TODO: Replace with actual API PUT call when backend is ready
|
||||
Adoption updated = new Adoption(adoptionId, adopterName, adopterEmail, adopterPhone, petName, adoptionDate, status);
|
||||
if (adoptionFragment != null) adoptionFragment.onAdoptionSaved(position, updated);
|
||||
ActivityLogger.logChange(requireContext(), "Adoption", "UPDATED", adoptionId);
|
||||
Toast.makeText(getContext(), "Adoption record updated.", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// TODO: Replace with actual API POST call when backend is ready
|
||||
Adoption newAdoption = new Adoption(0, adopterName, adopterEmail, adopterPhone, petName, adoptionDate, status);
|
||||
if (adoptionFragment != null) adoptionFragment.onAdoptionSaved(-1, newAdoption);
|
||||
ActivityLogger.log(requireContext(), "Added new Adoption record for: " + adopterName + " adopting " + petName);
|
||||
Toast.makeText(getContext(), "Adoption record added.", Toast.LENGTH_SHORT).show();
|
||||
private void setupDatePicker() {
|
||||
etAdoptionDate.setOnClickListener(v -> {
|
||||
Calendar c = Calendar.getInstance();
|
||||
new DatePickerDialog(requireContext(),
|
||||
(dp, y, m, d) -> etAdoptionDate.setText(
|
||||
String.format("%04d-%02d-%02d", y, m + 1, d)),
|
||||
c.get(Calendar.YEAR),
|
||||
c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH)).show();
|
||||
});
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
loadPets();
|
||||
loadCustomers();
|
||||
}
|
||||
|
||||
private void loadPets() {
|
||||
RetrofitClient.getPetApi(requireContext()).getAllPets(0, 200)
|
||||
.enqueue(new Callback<PageResponse<PetDTO>>() {
|
||||
public void onResponse(Call<PageResponse<PetDTO>> c,
|
||||
Response<PageResponse<PetDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
petList = r.body().getContent();
|
||||
populatePetSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<PetDTO>> c, Throwable t) {
|
||||
Log.e("ADOPTION", "Pet load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populatePetSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Pet --");
|
||||
for (PetDTO p : petList) names.add(p.getPetName());
|
||||
spinnerPet.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedPetId != -1) {
|
||||
for (int i = 0; i < petList.size(); i++) {
|
||||
if (petList.get(i).getPetId().equals(preselectedPetId)) {
|
||||
spinnerPet.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "AdoptionDetailFragment.saveAdoption", e);
|
||||
Toast.makeText(getContext(), "Error saving adoption record.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes the adoption record and logs the action
|
||||
private void deleteAdoption() {
|
||||
try {
|
||||
// TODO: Replace with actual API DELETE call when backend is ready
|
||||
if (adoptionFragment != null) adoptionFragment.onAdoptionDeleted(position);
|
||||
ActivityLogger.logChange(requireContext(), "Adoption", "DELETED", adoptionId);
|
||||
Toast.makeText(getContext(), "Adoption record deleted.", Toast.LENGTH_SHORT).show();
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "AdoptionDetailFragment.deleteAdoption", e);
|
||||
Toast.makeText(getContext(), "Error deleting adoption record.", Toast.LENGTH_SHORT).show();
|
||||
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();
|
||||
populateCustomerSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<CustomerDTO>> c, Throwable t) {
|
||||
Log.e("ADOPTION", "Customer load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateCustomerSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Customer --");
|
||||
for (CustomerDTO c : customerList)
|
||||
names.add(c.getFirstName() + " " + c.getLastName());
|
||||
spinnerCustomer.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedCustomerId != -1) {
|
||||
for (int i = 0; i < customerList.size(); i++) {
|
||||
if (customerList.get(i).getCustomerId().equals(preselectedCustomerId)) {
|
||||
spinnerCustomer.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
if (getArguments() != null && getArguments().containsKey("adoptionId")) {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("adoptionId")) {
|
||||
isEditing = true;
|
||||
adoptionId = getArguments().getInt("adoptionId");
|
||||
position = getArguments().getInt("position");
|
||||
adoptionId = a.getLong("adoptionId");
|
||||
preselectedPetId = a.getLong("petId", -1);
|
||||
preselectedCustomerId = a.getLong("customerId", -1);
|
||||
|
||||
tvMode.setText("Edit Adoption");
|
||||
tvAdoptionId.setText("ID: " + adoptionId);
|
||||
etAdopterName.setText(getArguments().getString("adopterName"));
|
||||
etAdopterEmail.setText(getArguments().getString("adopterEmail"));
|
||||
etAdopterPhone.setText(getArguments().getString("adopterPhone"));
|
||||
etPetName.setText(getArguments().getString("petName"));
|
||||
etAdoptionDate.setText(getArguments().getString("adoptionDate"));
|
||||
String status = getArguments().getString("status");
|
||||
if ("Approved".equals(status)) spinnerAdoptionStatus.setSelection(0);
|
||||
else if ("Pending".equals(status)) spinnerAdoptionStatus.setSelection(1);
|
||||
else spinnerAdoptionStatus.setSelection(2);
|
||||
btnDeleteAdoption.setVisibility(View.VISIBLE);
|
||||
tvAdoptionId.setVisibility(View.VISIBLE);
|
||||
etAdoptionDate.setText(a.getString("adoptionDate"));
|
||||
btnDelete.setVisibility(View.VISIBLE);
|
||||
|
||||
// Pre-fill status
|
||||
String status = a.getString("adoptionStatus", "Pending");
|
||||
for (int i = 0; i < STATUSES.length; i++) {
|
||||
if (STATUSES[i].equals(status)) {
|
||||
spinnerStatus.setSelection(i); break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isEditing = false;
|
||||
tvMode.setText("Add Adoption");
|
||||
btnDelete.setVisibility(View.GONE);
|
||||
tvAdoptionId.setVisibility(View.GONE);
|
||||
btnDeleteAdoption.setVisibility(View.GONE);
|
||||
btnSaveAdoption.setText("Add");
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews(View view) {
|
||||
tvMode = view.findViewById(R.id.tvAdoptionMode);
|
||||
tvAdoptionId = view.findViewById(R.id.tvAdoptionId);
|
||||
etAdopterName = view.findViewById(R.id.etAdopterName);
|
||||
etAdopterEmail = view.findViewById(R.id.etAdopterEmail);
|
||||
etAdopterPhone = view.findViewById(R.id.etAdopterPhone);
|
||||
etPetName = view.findViewById(R.id.etAdoptionPetName);
|
||||
etAdoptionDate = view.findViewById(R.id.etAdoptionDate);
|
||||
spinnerAdoptionStatus = view.findViewById(R.id.spinnerAdoptionStatus);
|
||||
btnSaveAdoption = view.findViewById(R.id.btnSaveAdoption);
|
||||
btnDeleteAdoption = view.findViewById(R.id.btnDeleteAdoption);
|
||||
btnBack = view.findViewById(R.id.btnAdoptionBack);
|
||||
private void saveAdoption() {
|
||||
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (spinnerPet.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
String date = etAdoptionDate.getText().toString().trim();
|
||||
if (date.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
|
||||
CustomerDTO customer = customerList.get(spinnerCustomer.getSelectedItemPosition() - 1);
|
||||
PetDTO pet = petList.get(spinnerPet.getSelectedItemPosition() - 1);
|
||||
String status = STATUSES[spinnerStatus.getSelectedItemPosition()];
|
||||
|
||||
AdoptionDTO dto = new AdoptionDTO(
|
||||
pet.getPetId(),
|
||||
customer.getCustomerId(),
|
||||
date,
|
||||
status
|
||||
);
|
||||
|
||||
Log.d("ADOPTION_SAVE", "petId=" + pet.getPetId()
|
||||
+ " customerId=" + customer.getCustomerId()
|
||||
+ " date=" + date + " status=" + status);
|
||||
|
||||
AdoptionApi api = RetrofitClient.getAdoptionApi(requireContext());
|
||||
if (isEditing) {
|
||||
api.updateAdoption(adoptionId, dto).enqueue(simpleCallback("Updated"));
|
||||
} else {
|
||||
api.createAdoption(dto).enqueue(simpleCallback("Saved"));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupSpinner() {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(requireContext(),
|
||||
android.R.layout.simple_spinner_item,
|
||||
new String[]{"Approved", "Pending", "Rejected"}) {
|
||||
|
||||
//Override the getView method for the spinner to make the text color darker for more readability
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
((TextView) view).setTextColor(ContextCompat.getColor(requireContext(), R.color.text_dark));
|
||||
return view;
|
||||
private Callback<AdoptionDTO> simpleCallback(String msg) {
|
||||
return new Callback<>() {
|
||||
public void onResponse(Call<AdoptionDTO> c, Response<AdoptionDTO> r) {
|
||||
Log.d("ADOPTION_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("ADOPTION_SAVE", "Error: " + err);
|
||||
Toast.makeText(getContext(), "Error " + r.code(), Toast.LENGTH_SHORT).show();
|
||||
} catch (Exception e) {
|
||||
Log.e("ADOPTION_SAVE", "Failed to read error");
|
||||
}
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<AdoptionDTO> c, Throwable t) {
|
||||
Log.e("ADOPTION_SAVE", "Failure: " + t.getMessage());
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerAdoptionStatus.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete Adoption?")
|
||||
.setPositiveButton("Yes", (d, w) ->
|
||||
RetrofitClient.getAdoptionApi(requireContext())
|
||||
.deleteAdoption(adoptionId)
|
||||
.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() {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
@@ -1,178 +1,444 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
|
||||
// Uses InputValidator for detailed field validation and ActivityLogger to log all changes.
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.api.*;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.AppointmentFragment;
|
||||
import com.example.petstoremobile.models.Appointment;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class AppointmentDetailFragment extends Fragment {
|
||||
|
||||
private TextView tvMode, tvAppointmentId;
|
||||
private EditText etCustomerName, etPetName, etServiceType, etAppointmentDate, etAppointmentTime;
|
||||
private Spinner spinnerStatus;
|
||||
private Button btnSaveAppointment, btnDeleteAppointment, btnBack;
|
||||
private int appointmentId;
|
||||
private int position;
|
||||
private boolean isEditing = false;
|
||||
private AppointmentFragment appointmentFragment;
|
||||
private EditText etAppointmentDate;
|
||||
private Spinner spinnerPet, spinnerService, spinnerStatus, spinnerHour, spinnerMinute;
|
||||
private Spinner spinnerCustomer, spinnerStore;
|
||||
private Button btnSave, btnDelete, btnBack;
|
||||
|
||||
// Set the appointment fragment as parent so we refer back when save or delete is done
|
||||
public void setAppointmentFragment(AppointmentFragment fragment) {
|
||||
this.appointmentFragment = fragment;
|
||||
}
|
||||
private long appointmentId = -1;
|
||||
private boolean isEditing = false;
|
||||
private long preselectedPetId = -1;
|
||||
private long preselectedServiceId = -1;
|
||||
private long preselectedCustomerId = -1;
|
||||
private long preselectedStoreId = -1;
|
||||
|
||||
private List<PetDTO> petList = new ArrayList<>();
|
||||
private List<ServiceDTO> serviceList = new ArrayList<>();
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private List<AppointmentDTO> allAppointments = new ArrayList<>();
|
||||
|
||||
private final Integer[] HOURS = {9,10,11,12,13,14,15,16,17};
|
||||
private final Integer[] MINUTES = {0,15,30,45};
|
||||
private final String[] STATUSES = {"Booked","Completed","Cancelled"};
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_appointment_detail, container, false);
|
||||
|
||||
initViews(view);
|
||||
setupSpinner();
|
||||
setupSpinners();
|
||||
setupDatePicker();
|
||||
loadData();
|
||||
handleArguments();
|
||||
|
||||
btnBack.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) {
|
||||
listFragment.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
});
|
||||
btnSaveAppointment.setOnClickListener(v -> saveAppointment());
|
||||
btnDeleteAppointment.setOnClickListener(v -> deleteAppointment());
|
||||
|
||||
btnBack.setOnClickListener(v -> navigateBack());
|
||||
btnSave.setOnClickListener(v -> saveAppointment());
|
||||
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||
return view;
|
||||
}
|
||||
|
||||
// Validates all fields using InputValidator, then saves the appointment
|
||||
private void saveAppointment() {
|
||||
// Validate all inputs using InputValidator utility
|
||||
if (!InputValidator.isNotEmpty(etCustomerName, "Customer Name")) return;
|
||||
if (!InputValidator.isNotEmpty(etPetName, "Pet Name")) return;
|
||||
if (!InputValidator.isNotEmpty(etServiceType, "Service Type")) return;
|
||||
if (!InputValidator.isValidDate(etAppointmentDate)) return;
|
||||
if (!InputValidator.isValidTime(etAppointmentTime)) return;
|
||||
private void initViews(View v) {
|
||||
tvMode = v.findViewById(R.id.tvApptMode);
|
||||
tvAppointmentId = v.findViewById(R.id.tvAppointmentId);
|
||||
etAppointmentDate= v.findViewById(R.id.etAppointmentDate);
|
||||
spinnerPet = v.findViewById(R.id.spinnerPet);
|
||||
spinnerService = v.findViewById(R.id.spinnerService);
|
||||
spinnerStatus = v.findViewById(R.id.spinnerAppointmentStatus);
|
||||
spinnerHour = v.findViewById(R.id.spinnerHour);
|
||||
spinnerMinute = v.findViewById(R.id.spinnerMinute);
|
||||
spinnerCustomer = v.findViewById(R.id.spinnerCustomer);
|
||||
spinnerStore = v.findViewById(R.id.spinnerStore);
|
||||
btnSave = v.findViewById(R.id.btnSaveAppointment);
|
||||
btnDelete = v.findViewById(R.id.btnDeleteAppointment);
|
||||
btnBack = v.findViewById(R.id.btnApptBack);
|
||||
}
|
||||
|
||||
String customerName = etCustomerName.getText().toString().trim();
|
||||
String petName = etPetName.getText().toString().trim();
|
||||
String serviceType = etServiceType.getText().toString().trim();
|
||||
String date = etAppointmentDate.getText().toString().trim();
|
||||
String time = etAppointmentTime.getText().toString().trim();
|
||||
String status = spinnerStatus.getSelectedItem().toString();
|
||||
private void setupSpinners() {
|
||||
spinnerStatus.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, STATUSES));
|
||||
|
||||
try {
|
||||
if (isEditing) {
|
||||
// TODO: Replace with actual API PUT call when backend is ready
|
||||
Appointment updated = new Appointment(appointmentId, customerName, petName, serviceType, date, time, status);
|
||||
if (appointmentFragment != null) appointmentFragment.onAppointmentSaved(position, updated);
|
||||
ActivityLogger.logChange(requireContext(), "Appointment", "UPDATED", appointmentId);
|
||||
Toast.makeText(getContext(), "Appointment updated.", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// TODO: Replace with actual API POST call when backend is ready
|
||||
Appointment newAppt = new Appointment(0, customerName, petName, serviceType, date, time, status);
|
||||
if (appointmentFragment != null) appointmentFragment.onAppointmentSaved(-1, newAppt);
|
||||
ActivityLogger.log(requireContext(), "Added new Appointment for customer: " + customerName);
|
||||
Toast.makeText(getContext(), "Appointment added.", Toast.LENGTH_SHORT).show();
|
||||
String[] hours = new String[HOURS.length];
|
||||
for (int i = 0; i < HOURS.length; i++)
|
||||
hours[i] = String.format("%02d:00", HOURS[i]);
|
||||
spinnerHour.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, hours));
|
||||
spinnerMinute.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, new String[]{"00","15","30","45"}));
|
||||
}
|
||||
|
||||
private void setupDatePicker() {
|
||||
etAppointmentDate.setOnClickListener(v -> {
|
||||
Calendar c = Calendar.getInstance();
|
||||
DatePickerDialog d = new DatePickerDialog(requireContext(),
|
||||
(dp,y,m,d1) -> etAppointmentDate.setText(
|
||||
String.format("%04d-%02d-%02d", y, m+1, d1)),
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH));
|
||||
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
||||
d.show();
|
||||
});
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
loadPets();
|
||||
loadServices();
|
||||
loadCustomers();
|
||||
loadStores();
|
||||
loadAllAppointments();
|
||||
}
|
||||
|
||||
private void loadPets() {
|
||||
RetrofitClient.getPetApi(requireContext()).getAllPets(0, 200)
|
||||
.enqueue(new Callback<PageResponse<PetDTO>>() {
|
||||
public void onResponse(Call<PageResponse<PetDTO>> c, Response<PageResponse<PetDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
petList = r.body().getContent();
|
||||
populatePetSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<PetDTO>> c, Throwable t) {
|
||||
Log.e("APPT", "Pet load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populatePetSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Pet --");
|
||||
for (PetDTO p : petList) names.add(p.getPetName());
|
||||
spinnerPet.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedPetId != -1) {
|
||||
for (int i = 0; i < petList.size(); i++) {
|
||||
if (petList.get(i).getPetId().equals(preselectedPetId)) {
|
||||
spinnerPet.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
// Go back to list
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "AppointmentDetailFragment.saveAppointment", e);
|
||||
Toast.makeText(getContext(), "Error saving appointment.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes the appointment and logs the action
|
||||
private void deleteAppointment() {
|
||||
try {
|
||||
// TODO: Replace with actual API DELETE call when backend is ready
|
||||
if (appointmentFragment != null) appointmentFragment.onAppointmentDeleted(position);
|
||||
ActivityLogger.logChange(requireContext(), "Appointment", "DELETED", appointmentId);
|
||||
Toast.makeText(getContext(), "Appointment deleted.", Toast.LENGTH_SHORT).show();
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "AppointmentDetailFragment.deleteAppointment", e);
|
||||
Toast.makeText(getContext(), "Error deleting appointment.", Toast.LENGTH_SHORT).show();
|
||||
private void loadServices() {
|
||||
RetrofitClient.getServiceApi(requireContext()).getAllServices(0, 200)
|
||||
.enqueue(new Callback<PageResponse<ServiceDTO>>() {
|
||||
public void onResponse(Call<PageResponse<ServiceDTO>> c, Response<PageResponse<ServiceDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
serviceList = r.body().getContent();
|
||||
populateServiceSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<ServiceDTO>> c, Throwable t) {
|
||||
Log.e("APPT", "Service load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateServiceSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Service --");
|
||||
for (ServiceDTO s : serviceList) names.add(s.getServiceName());
|
||||
spinnerService.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedServiceId != -1) {
|
||||
for (int i = 0; i < serviceList.size(); i++) {
|
||||
if (serviceList.get(i).getServiceId().equals(preselectedServiceId)) {
|
||||
spinnerService.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determines if the fragment is in add or edit mode and populates fields accordingly
|
||||
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();
|
||||
populateCustomerSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<CustomerDTO>> c, Throwable t) {
|
||||
Log.e("APPT", "Customer load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateCustomerSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Customer --");
|
||||
for (CustomerDTO c : customerList)
|
||||
names.add(c.getFirstName() + " " + c.getLastName());
|
||||
spinnerCustomer.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedCustomerId != -1) {
|
||||
for (int i = 0; i < customerList.size(); i++) {
|
||||
if (customerList.get(i).getCustomerId().equals(preselectedCustomerId)) {
|
||||
spinnerCustomer.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadStores() {
|
||||
RetrofitClient.getStoreApi(requireContext()).getAllStores(0, 50)
|
||||
.enqueue(new Callback<PageResponse<StoreDTO>>() {
|
||||
public void onResponse(Call<PageResponse<StoreDTO>> c, Response<PageResponse<StoreDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
storeList = r.body().getContent();
|
||||
populateStoreSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<StoreDTO>> c, Throwable t) {
|
||||
Log.e("APPT", "Store load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateStoreSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Store --");
|
||||
for (StoreDTO s : storeList) names.add(s.getStoreName());
|
||||
spinnerStore.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedStoreId != -1) {
|
||||
for (int i = 0; i < storeList.size(); i++) {
|
||||
if (storeList.get(i).getStoreId().equals(preselectedStoreId)) {
|
||||
spinnerStore.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAllAppointments() {
|
||||
RetrofitClient.getAppointmentApi(requireContext()).getAllAppointments(0, 500)
|
||||
.enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
||||
public void onResponse(Call<PageResponse<AppointmentDTO>> c, Response<PageResponse<AppointmentDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null)
|
||||
allAppointments = r.body().getContent();
|
||||
}
|
||||
public void onFailure(Call<PageResponse<AppointmentDTO>> c, Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
if (getArguments() != null && getArguments().containsKey("appointmentId")) {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("appointmentId")) {
|
||||
isEditing = true;
|
||||
appointmentId = getArguments().getInt("appointmentId");
|
||||
position = getArguments().getInt("position");
|
||||
appointmentId = a.getLong("appointmentId");
|
||||
preselectedPetId = a.getLong("petId", -1);
|
||||
preselectedServiceId= a.getLong("serviceId", -1);
|
||||
preselectedCustomerId = a.getLong("customerId", -1);
|
||||
preselectedStoreId = a.getLong("storeId", -1);
|
||||
|
||||
tvMode.setText("Edit Appointment");
|
||||
tvAppointmentId.setText("ID: " + appointmentId);
|
||||
etCustomerName.setText(getArguments().getString("customerName"));
|
||||
etPetName.setText(getArguments().getString("petName"));
|
||||
etServiceType.setText(getArguments().getString("serviceType"));
|
||||
etAppointmentDate.setText(getArguments().getString("appointmentDate"));
|
||||
etAppointmentTime.setText(getArguments().getString("appointmentTime"));
|
||||
String status = getArguments().getString("status");
|
||||
if ("Confirmed".equals(status)) spinnerStatus.setSelection(0);
|
||||
else if ("Pending".equals(status)) spinnerStatus.setSelection(1);
|
||||
else spinnerStatus.setSelection(2);
|
||||
btnDeleteAppointment.setVisibility(View.VISIBLE);
|
||||
tvAppointmentId.setVisibility(View.VISIBLE);
|
||||
etAppointmentDate.setText(a.getString("appointmentDate"));
|
||||
btnDelete.setVisibility(View.VISIBLE);
|
||||
|
||||
// Pre-fill time spinners
|
||||
String time = a.getString("appointmentTime", "09:00");
|
||||
if (time.length() > 5) time = time.substring(0, 5);
|
||||
String[] parts = time.split(":");
|
||||
if (parts.length == 2) {
|
||||
int hour = Integer.parseInt(parts[0]);
|
||||
int min = Integer.parseInt(parts[1]);
|
||||
for (int i = 0; i < HOURS.length; i++)
|
||||
if (HOURS[i] == hour) { spinnerHour.setSelection(i); break; }
|
||||
for (int i = 0; i < MINUTES.length; i++)
|
||||
if (MINUTES[i] == min) { spinnerMinute.setSelection(i); break; }
|
||||
}
|
||||
|
||||
// Pre-fill status
|
||||
String status = a.getString("appointmentStatus", "Booked");
|
||||
for (int i = 0; i < STATUSES.length; i++)
|
||||
if (STATUSES[i].equals(status)) { spinnerStatus.setSelection(i); break; }
|
||||
|
||||
} else {
|
||||
isEditing = false;
|
||||
tvMode.setText("Add Appointment");
|
||||
btnDelete.setVisibility(View.GONE);
|
||||
tvAppointmentId.setVisibility(View.GONE);
|
||||
btnDeleteAppointment.setVisibility(View.GONE);
|
||||
btnSaveAppointment.setText("Add");
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews(View view) {
|
||||
tvMode = view.findViewById(R.id.tvApptMode);
|
||||
tvAppointmentId = view.findViewById(R.id.tvAppointmentId);
|
||||
etCustomerName = view.findViewById(R.id.etCustomerName);
|
||||
etPetName = view.findViewById(R.id.etApptPetName);
|
||||
etServiceType = view.findViewById(R.id.etServiceType);
|
||||
etAppointmentDate = view.findViewById(R.id.etAppointmentDate);
|
||||
etAppointmentTime = view.findViewById(R.id.etAppointmentTime);
|
||||
spinnerStatus = view.findViewById(R.id.spinnerAppointmentStatus);
|
||||
btnSaveAppointment = view.findViewById(R.id.btnSaveAppointment);
|
||||
btnDeleteAppointment = view.findViewById(R.id.btnDeleteAppointment);
|
||||
btnBack = view.findViewById(R.id.btnApptBack);
|
||||
private void saveAppointment() {
|
||||
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (spinnerStore.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (spinnerPet.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (spinnerService.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a service", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
String date = etAppointmentDate.getText().toString().trim();
|
||||
if (date.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
|
||||
CustomerDTO customer = customerList.get(spinnerCustomer.getSelectedItemPosition() - 1);
|
||||
StoreDTO store = storeList.get(spinnerStore.getSelectedItemPosition() - 1);
|
||||
PetDTO pet = petList.get(spinnerPet.getSelectedItemPosition() - 1);
|
||||
ServiceDTO service = serviceList.get(spinnerService.getSelectedItemPosition() - 1);
|
||||
|
||||
String time = String.format("%02d:%02d",
|
||||
HOURS[spinnerHour.getSelectedItemPosition()],
|
||||
MINUTES[spinnerMinute.getSelectedItemPosition()]);
|
||||
String status = STATUSES[spinnerStatus.getSelectedItemPosition()];
|
||||
|
||||
|
||||
// Validate future date+time if status is Booked
|
||||
if ("Booked".equalsIgnoreCase(status)) {
|
||||
try {
|
||||
String[] dateParts = date.split("-");
|
||||
String[] timeParts = time.split(":");
|
||||
Calendar selected = Calendar.getInstance();
|
||||
selected.set(
|
||||
Integer.parseInt(dateParts[0]),
|
||||
Integer.parseInt(dateParts[1]) - 1,
|
||||
Integer.parseInt(dateParts[2]),
|
||||
Integer.parseInt(timeParts[0]),
|
||||
Integer.parseInt(timeParts[1]),
|
||||
0
|
||||
);
|
||||
if (selected.before(Calendar.getInstance())) {
|
||||
showErrorDialog("Invalid Time",
|
||||
"Booked appointments must be in the future. " +
|
||||
"Please select a future date and time.");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("APPT_SAVE", "Date parse error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Build DTO with all required IDs
|
||||
AppointmentDTO dto = new AppointmentDTO(
|
||||
customer.getCustomerId(),
|
||||
store.getStoreId(),
|
||||
service.getServiceId(),
|
||||
date,
|
||||
time,
|
||||
status,
|
||||
Collections.singletonList(pet.getPetId())
|
||||
);
|
||||
|
||||
Log.d("APPT_SAVE", "customerId=" + customer.getCustomerId()
|
||||
+ " storeId=" + store.getStoreId()
|
||||
+ " serviceId=" + service.getServiceId()
|
||||
+ " petId=" + pet.getPetId()
|
||||
+ " date=" + date + " time=" + time);
|
||||
|
||||
AppointmentApi api = RetrofitClient.getAppointmentApi(requireContext());
|
||||
if (isEditing) {
|
||||
api.updateAppointment(appointmentId, dto).enqueue(simpleCallback("Updated"));
|
||||
} else {
|
||||
api.createAppointment(dto).enqueue(simpleCallback("Saved"));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupSpinner() {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(requireContext(),
|
||||
android.R.layout.simple_spinner_item,
|
||||
new String[]{"Confirmed", "Pending", "Cancelled"}) {
|
||||
private Callback<AppointmentDTO> simpleCallback(String msg) {
|
||||
return new Callback<>() {
|
||||
public void onResponse(Call<AppointmentDTO> c, Response<AppointmentDTO> r) {
|
||||
Log.d("APPT_SAVE", "Response: " + r.code());
|
||||
if (r.isSuccessful()) {
|
||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else {
|
||||
try {
|
||||
String errorBody = r.errorBody().string();
|
||||
Log.e("APPT_SAVE", "Error: " + errorBody);
|
||||
|
||||
//Override the getView method for the spinner to make the text color darker for more readability
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
((TextView) view).setTextColor(ContextCompat.getColor(requireContext(), R.color.text_dark));
|
||||
return view;
|
||||
// Show proper dialog based on error type
|
||||
if (errorBody.toLowerCase().contains("future")) {
|
||||
showErrorDialog("Invalid Date/Time",
|
||||
"Booked appointments must be scheduled in the future. " +
|
||||
"Please select a future date and time.");
|
||||
//------------------------------------------
|
||||
} else if (errorBody.toLowerCase().contains("not available") ||
|
||||
errorBody.toLowerCase().contains("time is not available")) {
|
||||
showNoAvailabilityDialog();
|
||||
} else if (r.code() == 404) {
|
||||
showErrorDialog("Not Found",
|
||||
"The selected pet, customer or service was not found.");
|
||||
} else if (r.code() == 403) {
|
||||
showErrorDialog("Access Denied",
|
||||
"You don't have permission to perform this action.");
|
||||
} else if (r.code() == 400) {
|
||||
showErrorDialog("Invalid Request", errorBody);
|
||||
} else {
|
||||
showErrorDialog("Error", "Something went wrong. Please try again.");
|
||||
}
|
||||
//-----------------------------
|
||||
} catch (Exception e) {
|
||||
Log.e("APPT_SAVE", "Failed to read error body");
|
||||
showErrorDialog("Error", "Something went wrong. Please try again.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onFailure(Call<AppointmentDTO> c, Throwable t) {
|
||||
Log.e("APPT_SAVE", "Failure: " + t.getMessage());
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerStatus.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
private void showNoAvailabilityDialog() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("No Availability")
|
||||
.setMessage("This time slot is already booked for the selected service and store. Please choose a different time or date.")
|
||||
.setPositiveButton("Change Time", (d, w) -> d.dismiss())
|
||||
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack())
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showErrorDialog(String title, String message) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton("OK", null)
|
||||
.show();
|
||||
}
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete Appointment?")
|
||||
.setPositiveButton("Yes", (d, w) ->
|
||||
RetrofitClient.getAppointmentApi(requireContext())
|
||||
.deleteAppointment(appointmentId)
|
||||
.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() {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,66 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
// Uses InputValidator for detailed field validation and ActivityLogger to log all changes.
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
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.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.api.InventoryApi;
|
||||
import com.example.petstoremobile.api.ProductApi;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.InventoryRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
||||
import com.example.petstoremobile.models.Inventory;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class InventoryDetailFragment extends Fragment {
|
||||
|
||||
private TextView tvMode, tvInventoryId;
|
||||
private EditText etItemName, etCategory, etQuantity, etUnitPrice, etSupplier;
|
||||
private Button btnSaveInventory, btnDeleteInventory, btnBack;
|
||||
private int inventoryId;
|
||||
private int position;
|
||||
private boolean isEditing = false;
|
||||
private TextView tvMode, tvInventoryId, tvProductInfo;
|
||||
private AutoCompleteTextView etProductSearch;
|
||||
private android.widget.EditText etQuantity;
|
||||
private Button btnSave, btnDelete, btnBack;
|
||||
|
||||
private InventoryApi inventoryApi;
|
||||
private ProductApi productApi;
|
||||
private InventoryFragment inventoryFragment;
|
||||
|
||||
// Set the inventory fragment as parent so we refer back when save or delete is done
|
||||
private boolean isEditing = false;
|
||||
private long inventoryId = -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;
|
||||
|
||||
public void setInventoryFragment(InventoryFragment fragment) {
|
||||
this.inventoryFragment = fragment;
|
||||
}
|
||||
@@ -38,102 +70,271 @@ public class InventoryDetailFragment extends Fragment {
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_inventory_detail, container, false);
|
||||
|
||||
inventoryApi = RetrofitClient.getInventoryApi(requireContext());
|
||||
productApi = RetrofitClient.getProductApi(requireContext());
|
||||
|
||||
initViews(view);
|
||||
setupProductSearch();
|
||||
handleArguments();
|
||||
|
||||
btnBack.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
});
|
||||
btnSaveInventory.setOnClickListener(v -> saveInventory());
|
||||
btnDeleteInventory.setOnClickListener(v -> deleteInventory());
|
||||
btnBack.setOnClickListener(v -> navigateBack());
|
||||
btnSave.setOnClickListener(v -> saveInventory());
|
||||
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
// Validates all fields using InputValidator, then saves the inventory item
|
||||
private void saveInventory() {
|
||||
if (!InputValidator.isNotEmpty(etItemName, "Item Name")) return;
|
||||
if (!InputValidator.isNotEmpty(etCategory, "Category")) return;
|
||||
if (!InputValidator.isPositiveInteger(etQuantity, "Quantity")) return;
|
||||
if (!InputValidator.isPositiveDecimal(etUnitPrice, "Unit Price")) return;
|
||||
if (!InputValidator.isNotEmpty(etSupplier, "Supplier")) return;
|
||||
|
||||
String itemName = etItemName.getText().toString().trim();
|
||||
String category = etCategory.getText().toString().trim();
|
||||
int quantity = Integer.parseInt(etQuantity.getText().toString().trim());
|
||||
double unitPrice = Double.parseDouble(etUnitPrice.getText().toString().trim());
|
||||
String supplier = etSupplier.getText().toString().trim();
|
||||
|
||||
try {
|
||||
if (isEditing) {
|
||||
// TODO: Replace with actual API PUT call when backend is ready
|
||||
Inventory updated = new Inventory(inventoryId, itemName, category, quantity, unitPrice, supplier);
|
||||
if (inventoryFragment != null) inventoryFragment.onInventorySaved(position, updated);
|
||||
ActivityLogger.logChange(requireContext(), "Inventory", "UPDATED", inventoryId);
|
||||
Toast.makeText(getContext(), "Inventory item updated.", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// TODO: Replace with actual API POST call when backend is ready
|
||||
Inventory newItem = new Inventory(0, itemName, category, quantity, unitPrice, supplier);
|
||||
if (inventoryFragment != null) inventoryFragment.onInventorySaved(-1, newItem);
|
||||
ActivityLogger.log(requireContext(), "Added new Inventory item: " + itemName);
|
||||
Toast.makeText(getContext(), "Inventory item added.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "InventoryDetailFragment.saveInventory", e);
|
||||
Toast.makeText(getContext(), "Error saving inventory item.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes the inventory item and logs the action
|
||||
private void deleteInventory() {
|
||||
try {
|
||||
// TODO: Replace with actual API DELETE call when backend is ready
|
||||
if (inventoryFragment != null) inventoryFragment.onInventoryDeleted(position);
|
||||
ActivityLogger.logChange(requireContext(), "Inventory", "DELETED", inventoryId);
|
||||
Toast.makeText(getContext(), "Inventory item deleted.", Toast.LENGTH_SHORT).show();
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "InventoryDetailFragment.deleteInventory", e);
|
||||
Toast.makeText(getContext(), "Error deleting inventory item.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
if (getArguments() != null && getArguments().containsKey("inventoryId")) {
|
||||
isEditing = true;
|
||||
inventoryId = getArguments().getInt("inventoryId");
|
||||
position = getArguments().getInt("position");
|
||||
tvMode.setText("Edit Inventory Item");
|
||||
tvInventoryId.setText("ID: " + inventoryId);
|
||||
etItemName.setText(getArguments().getString("itemName"));
|
||||
etCategory.setText(getArguments().getString("category"));
|
||||
etQuantity.setText(String.valueOf(getArguments().getInt("quantity")));
|
||||
etUnitPrice.setText(String.valueOf(getArguments().getDouble("unitPrice")));
|
||||
etSupplier.setText(getArguments().getString("supplier"));
|
||||
btnDeleteInventory.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
isEditing = false;
|
||||
tvMode.setText("Add Inventory Item");
|
||||
tvInventoryId.setVisibility(View.GONE);
|
||||
btnDeleteInventory.setVisibility(View.GONE);
|
||||
btnSaveInventory.setText("Add");
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews(View view) {
|
||||
tvMode = view.findViewById(R.id.tvInventoryMode);
|
||||
tvInventoryId = view.findViewById(R.id.tvInventoryId);
|
||||
etItemName = view.findViewById(R.id.etItemName);
|
||||
etCategory = view.findViewById(R.id.etInventoryCategory);
|
||||
tvProductInfo = view.findViewById(R.id.tvProductInfo);
|
||||
etProductSearch = view.findViewById(R.id.etProductSearch);
|
||||
etQuantity = view.findViewById(R.id.etQuantity);
|
||||
etUnitPrice = view.findViewById(R.id.etUnitPrice);
|
||||
etSupplier = view.findViewById(R.id.etInventorySupplier);
|
||||
btnSaveInventory = view.findViewById(R.id.btnSaveInventory);
|
||||
btnDeleteInventory = view.findViewById(R.id.btnDeleteInventory);
|
||||
btnSave = view.findViewById(R.id.btnSaveInventory);
|
||||
btnDelete = view.findViewById(R.id.btnDeleteInventory);
|
||||
btnBack = view.findViewById(R.id.btnInventoryBack);
|
||||
|
||||
// Setup dropdown adapter
|
||||
dropdownAdapter = new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_dropdown_item_1line, new ArrayList<>());
|
||||
etProductSearch.setAdapter(dropdownAdapter);
|
||||
etProductSearch.setThreshold(1); // start showing after 1 character
|
||||
}
|
||||
}
|
||||
|
||||
// Product search dropdown
|
||||
private void setupProductSearch() {
|
||||
etProductSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int i, int i1, int i2) {
|
||||
}
|
||||
|
||||
@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;
|
||||
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
|
||||
etProductSearch.setOnItemClickListener((parent, view, position, id) -> {
|
||||
if (position < productSuggestions.size()) {
|
||||
selectedProduct = productSuggestions.get(position);
|
||||
// Show product details below the search box
|
||||
tvProductInfo.setText(
|
||||
"ID: " + selectedProduct.getProdId()
|
||||
+ " • " + selectedProduct.getCategoryName());
|
||||
tvProductInfo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void searchProducts(String query) {
|
||||
productApi.getAllProducts(query, 0, 20).enqueue(new Callback<PageResponse<ProductDTO>>() {
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<ProductDTO>> call,
|
||||
Response<PageResponse<ProductDTO>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
productSuggestions.clear();
|
||||
productSuggestions.addAll(response.body().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();
|
||||
etProductSearch.showDropDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<ProductDTO>> call, Throwable t) {
|
||||
Toast.makeText(getContext(), "Failed to load products", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Arguments (edit mode)
|
||||
|
||||
private void handleArguments() {
|
||||
Bundle args = getArguments();
|
||||
if (args != null && args.containsKey("inventoryId")) {
|
||||
isEditing = true;
|
||||
inventoryId = args.getLong("inventoryId");
|
||||
|
||||
tvMode.setText("Edit Inventory");
|
||||
tvInventoryId.setText("Inventory ID: " + inventoryId);
|
||||
tvInventoryId.setVisibility(View.VISIBLE);
|
||||
|
||||
// Pre-fill search box with existing product name
|
||||
String productName = args.getString("productName", "");
|
||||
long prodId = args.getLong("prodId", -1);
|
||||
etProductSearch.setText(productName);
|
||||
|
||||
// Show existing product info
|
||||
if (prodId != -1) {
|
||||
tvProductInfo.setText(
|
||||
"ID: " + prodId
|
||||
+ " • " + args.getString("categoryName", ""));
|
||||
tvProductInfo.setVisibility(View.VISIBLE);
|
||||
|
||||
// Build a minimal ProductDTO so selectedProduct is not null on save
|
||||
selectedProduct = new ProductDTO(productName, null, null, null);
|
||||
selectedProduct.setProdId(prodId);
|
||||
}
|
||||
|
||||
etQuantity.setText(String.valueOf(args.getInt("quantity", 0)));
|
||||
btnDelete.setVisibility(View.VISIBLE);
|
||||
btnSave.setText("Save");
|
||||
} else {
|
||||
isEditing = false;
|
||||
tvMode.setText("Add Inventory");
|
||||
tvInventoryId.setVisibility(View.GONE);
|
||||
tvProductInfo.setVisibility(View.GONE);
|
||||
btnDelete.setVisibility(View.GONE);
|
||||
btnSave.setText("Add");
|
||||
}
|
||||
}
|
||||
|
||||
// Save
|
||||
private void saveInventory() {
|
||||
if (selectedProduct == null) {
|
||||
etProductSearch.setError("Please select a product from the list");
|
||||
etProductSearch.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
String quantityStr = etQuantity.getText().toString().trim();
|
||||
if (quantityStr.isEmpty()) {
|
||||
etQuantity.setError("Quantity is required");
|
||||
etQuantity.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
int quantity;
|
||||
try {
|
||||
quantity = Integer.parseInt(quantityStr);
|
||||
} catch (NumberFormatException e) {
|
||||
etQuantity.setError("Invalid quantity");
|
||||
return;
|
||||
}
|
||||
|
||||
if (quantity < 0) {
|
||||
etQuantity.setError("Quantity must be 0 or more");
|
||||
etQuantity.requestFocus();
|
||||
return;
|
||||
}
|
||||
|
||||
InventoryRequest request = new InventoryRequest(selectedProduct.getProdId(), quantity);
|
||||
setButtonsEnabled(false);
|
||||
|
||||
if (isEditing) {
|
||||
inventoryApi.updateInventory(inventoryId, request).enqueue(new Callback<InventoryDTO>() {
|
||||
@Override
|
||||
public void onResponse(Call<InventoryDTO> call, Response<InventoryDTO> response) {
|
||||
setButtonsEnabled(true);
|
||||
if (response.isSuccessful()) {
|
||||
Toast.makeText(getContext(), "Inventory updated", Toast.LENGTH_SHORT).show();
|
||||
notifyParentAndGoBack();
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Update failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<InventoryDTO> call, Throwable t) {
|
||||
setButtonsEnabled(true);
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
inventoryApi.createInventory(request).enqueue(new Callback<InventoryDTO>() {
|
||||
@Override
|
||||
public void onResponse(Call<InventoryDTO> call, Response<InventoryDTO> response) {
|
||||
setButtonsEnabled(true);
|
||||
if (response.isSuccessful()) {
|
||||
Toast.makeText(getContext(), "Inventory created", Toast.LENGTH_SHORT).show();
|
||||
notifyParentAndGoBack();
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Create failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<InventoryDTO> call, Throwable t) {
|
||||
setButtonsEnabled(true);
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delete
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete inventory item?")
|
||||
.setMessage("This cannot be undone.")
|
||||
.setPositiveButton("Delete", (d, w) -> deleteInventory())
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void deleteInventory() {
|
||||
setButtonsEnabled(false);
|
||||
inventoryApi.deleteInventory(inventoryId).enqueue(new Callback<Void>() {
|
||||
@Override
|
||||
public void onResponse(Call<Void> call, Response<Void> response) {
|
||||
setButtonsEnabled(true);
|
||||
if (response.isSuccessful()) {
|
||||
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
|
||||
notifyParentAndGoBack();
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Delete failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<Void> call, Throwable t) {
|
||||
setButtonsEnabled(true);
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
private void notifyParentAndGoBack() {
|
||||
if (inventoryFragment != null)
|
||||
inventoryFragment.onInventoryChanged();
|
||||
navigateBack();
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null)
|
||||
lf.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
|
||||
private void setButtonsEnabled(boolean enabled) {
|
||||
btnSave.setEnabled(enabled);
|
||||
btnDelete.setEnabled(enabled);
|
||||
btnBack.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.api.PetApi;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
@@ -234,19 +235,9 @@ public class PetDetailFragment extends Fragment {
|
||||
|
||||
//helper function to set up the spinner menu for pet status
|
||||
private void setupSpinner() {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(requireContext(),
|
||||
BlackTextArrayAdapter<String> adapter = new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item,
|
||||
new String[]{"Available", "Adopted"}) {
|
||||
|
||||
//Override the getView method for the spinner to make the text color darker for more readability
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
((TextView) view).setTextColor(ContextCompat.getColor(requireContext(), R.color.text_dark));
|
||||
return view;
|
||||
}
|
||||
};
|
||||
new String[]{"Available", "Adopted"});
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerPetStatus.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@@ -1,139 +1,191 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
// Uses InputValidator for detailed field validation and ActivityLogger to log all changes.
|
||||
|
||||
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 android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.api.*;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.ProductFragment;
|
||||
import com.example.petstoremobile.models.Product;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class ProductDetailFragment extends Fragment {
|
||||
|
||||
private TextView tvMode, tvProductId;
|
||||
private EditText etProductName, etProductDesc, etCategory, etProductPrice, etStockQuantity;
|
||||
private Button btnSaveProduct, btnDeleteProduct, btnBack;
|
||||
private int productId;
|
||||
private int position;
|
||||
private boolean isEditing = false;
|
||||
private ProductFragment productFragment;
|
||||
private EditText etProductName, etProductDesc, etProductPrice;
|
||||
private Spinner spinnerCategory;
|
||||
private Button btnSave, btnDelete, btnBack;
|
||||
|
||||
// Set the product fragment as parent so we refer back when save or delete is done
|
||||
public void setProductFragment(ProductFragment fragment) {
|
||||
this.productFragment = fragment;
|
||||
}
|
||||
private long prodId = -1;
|
||||
private boolean isEditing = false;
|
||||
private long preselectedCategoryId = -1;
|
||||
|
||||
private List<CategoryDTO> categoryList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_product_detail, container, false);
|
||||
|
||||
initViews(view);
|
||||
loadCategories();
|
||||
handleArguments();
|
||||
|
||||
btnBack.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
});
|
||||
btnSaveProduct.setOnClickListener(v -> saveProduct());
|
||||
btnDeleteProduct.setOnClickListener(v -> deleteProduct());
|
||||
|
||||
btnBack.setOnClickListener(v -> navigateBack());
|
||||
btnSave.setOnClickListener(v -> saveProduct());
|
||||
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||
return view;
|
||||
}
|
||||
|
||||
// Validates all fields using InputValidator, then saves the product
|
||||
private void saveProduct() {
|
||||
if (!InputValidator.isNotEmpty(etProductName, "Product Name")) return;
|
||||
if (!InputValidator.isNotEmpty(etProductDesc, "Description")) return;
|
||||
if (!InputValidator.isNotEmpty(etCategory, "Category")) return;
|
||||
if (!InputValidator.isPositiveDecimal(etProductPrice, "Price")) return;
|
||||
if (!InputValidator.isPositiveInteger(etStockQuantity, "Stock Quantity")) return;
|
||||
|
||||
String productName = etProductName.getText().toString().trim();
|
||||
String productDesc = etProductDesc.getText().toString().trim();
|
||||
String category = etCategory.getText().toString().trim();
|
||||
double productPrice = Double.parseDouble(etProductPrice.getText().toString().trim());
|
||||
int stockQuantity = Integer.parseInt(etStockQuantity.getText().toString().trim());
|
||||
|
||||
try {
|
||||
if (isEditing) {
|
||||
// TODO: Replace with actual API PUT call when backend is ready
|
||||
Product updated = new Product(productId, productName, productDesc, category, productPrice, stockQuantity);
|
||||
if (productFragment != null) productFragment.onProductSaved(position, updated);
|
||||
ActivityLogger.logChange(requireContext(), "Product", "UPDATED", productId);
|
||||
Toast.makeText(getContext(), "Product updated.", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// TODO: Replace with actual API POST call when backend is ready
|
||||
Product newProduct = new Product(0, productName, productDesc, category, productPrice, stockQuantity);
|
||||
if (productFragment != null) productFragment.onProductSaved(-1, newProduct);
|
||||
ActivityLogger.log(requireContext(), "Added new Product: " + productName);
|
||||
Toast.makeText(getContext(), "Product added.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "ProductDetailFragment.saveProduct", e);
|
||||
Toast.makeText(getContext(), "Error saving product.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
private void initViews(View v) {
|
||||
tvMode = v.findViewById(R.id.tvProductMode);
|
||||
tvProductId = v.findViewById(R.id.tvProductId);
|
||||
etProductName = v.findViewById(R.id.etProductName);
|
||||
etProductDesc = v.findViewById(R.id.etProductDesc);
|
||||
etProductPrice = v.findViewById(R.id.etProductPrice);
|
||||
spinnerCategory = v.findViewById(R.id.spinnerProductCategory);
|
||||
btnSave = v.findViewById(R.id.btnSaveProduct);
|
||||
btnDelete = v.findViewById(R.id.btnDeleteProduct);
|
||||
btnBack = v.findViewById(R.id.btnProductBack);
|
||||
}
|
||||
|
||||
// Deletes the product and logs the action
|
||||
private void deleteProduct() {
|
||||
try {
|
||||
// TODO: Replace with actual API DELETE call when backend is ready
|
||||
if (productFragment != null) productFragment.onProductDeleted(position);
|
||||
ActivityLogger.logChange(requireContext(), "Product", "DELETED", productId);
|
||||
Toast.makeText(getContext(), "Product deleted.", Toast.LENGTH_SHORT).show();
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "ProductDetailFragment.deleteProduct", e);
|
||||
Toast.makeText(getContext(), "Error deleting product.", Toast.LENGTH_SHORT).show();
|
||||
private void loadCategories() {
|
||||
RetrofitClient.getCategoryApi(requireContext()).getAllCategories(0, 100)
|
||||
.enqueue(new Callback<PageResponse<CategoryDTO>>() {
|
||||
public void onResponse(Call<PageResponse<CategoryDTO>> c,
|
||||
Response<PageResponse<CategoryDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
categoryList = r.body().getContent();
|
||||
populateCategorySpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<CategoryDTO>> c, Throwable t) {
|
||||
Log.e("ProductDetail", "Category load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateCategorySpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Category --");
|
||||
for (CategoryDTO c : categoryList) names.add(c.getCategoryName());
|
||||
spinnerCategory.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedCategoryId != -1) {
|
||||
for (int i = 0; i < categoryList.size(); i++) {
|
||||
if (categoryList.get(i).getCategoryId().equals(preselectedCategoryId)) {
|
||||
spinnerCategory.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
if (getArguments() != null && getArguments().containsKey("productId")) {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("prodId")) {
|
||||
isEditing = true;
|
||||
productId = getArguments().getInt("productId");
|
||||
position = getArguments().getInt("position");
|
||||
prodId = a.getLong("prodId");
|
||||
preselectedCategoryId = a.getLong("categoryId", -1);
|
||||
|
||||
tvMode.setText("Edit Product");
|
||||
tvProductId.setText("ID: " + productId);
|
||||
etProductName.setText(getArguments().getString("productName"));
|
||||
etProductDesc.setText(getArguments().getString("productDesc"));
|
||||
etCategory.setText(getArguments().getString("category"));
|
||||
etProductPrice.setText(String.valueOf(getArguments().getDouble("productPrice")));
|
||||
etStockQuantity.setText(String.valueOf(getArguments().getInt("stockQuantity")));
|
||||
btnDeleteProduct.setVisibility(View.VISIBLE);
|
||||
tvProductId.setText("ID: " + prodId);
|
||||
tvProductId.setVisibility(View.VISIBLE);
|
||||
etProductName.setText(a.getString("prodName"));
|
||||
etProductDesc.setText(a.getString("prodDesc"));
|
||||
etProductPrice.setText(a.getString("prodPrice"));
|
||||
btnDelete.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
isEditing = false;
|
||||
tvMode.setText("Add Product");
|
||||
btnDelete.setVisibility(View.GONE);
|
||||
tvProductId.setVisibility(View.GONE);
|
||||
btnDeleteProduct.setVisibility(View.GONE);
|
||||
btnSaveProduct.setText("Add");
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews(View view) {
|
||||
tvMode = view.findViewById(R.id.tvProductMode);
|
||||
tvProductId = view.findViewById(R.id.tvProductId);
|
||||
etProductName = view.findViewById(R.id.etProductName);
|
||||
etProductDesc = view.findViewById(R.id.etProductDesc);
|
||||
etCategory = view.findViewById(R.id.etProductCategory);
|
||||
etProductPrice = view.findViewById(R.id.etProductPrice);
|
||||
etStockQuantity = view.findViewById(R.id.etStockQuantity);
|
||||
btnSaveProduct = view.findViewById(R.id.btnSaveProduct);
|
||||
btnDeleteProduct = view.findViewById(R.id.btnDeleteProduct);
|
||||
btnBack = view.findViewById(R.id.btnProductBack);
|
||||
private void saveProduct() {
|
||||
String name = etProductName.getText().toString().trim();
|
||||
String desc = etProductDesc.getText().toString().trim();
|
||||
String priceStr = etProductPrice.getText().toString().trim();
|
||||
|
||||
if (name.isEmpty()) {
|
||||
etProductName.setError("Enter product name"); return;
|
||||
}
|
||||
if (spinnerCategory.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a category", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (priceStr.isEmpty()) {
|
||||
etProductPrice.setError("Enter price"); return;
|
||||
}
|
||||
|
||||
CategoryDTO category = categoryList.get(spinnerCategory.getSelectedItemPosition() - 1);
|
||||
BigDecimal price;
|
||||
try {
|
||||
price = new BigDecimal(priceStr);
|
||||
} catch (Exception e) {
|
||||
etProductPrice.setError("Invalid price"); return;
|
||||
}
|
||||
|
||||
ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price);
|
||||
|
||||
Log.d("PRODUCT_SAVE", "name=" + name + " categoryId=" + category.getCategoryId()
|
||||
+ " price=" + price);
|
||||
|
||||
ProductApi api = RetrofitClient.getProductApi(requireContext());
|
||||
if (isEditing) {
|
||||
api.updateProduct(prodId, dto).enqueue(simpleCallback("Updated"));
|
||||
} else {
|
||||
api.createProduct(dto).enqueue(simpleCallback("Saved"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Callback<ProductDTO> simpleCallback(String msg) {
|
||||
return new Callback<>() {
|
||||
public void onResponse(Call<ProductDTO> c, Response<ProductDTO> r) {
|
||||
if (r.isSuccessful()) {
|
||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else {
|
||||
try {
|
||||
String err = r.errorBody().string();
|
||||
Log.e("PRODUCT_SAVE", "Error: " + err);
|
||||
Toast.makeText(getContext(), "Error " + r.code(), Toast.LENGTH_SHORT).show();
|
||||
} catch (Exception e) {
|
||||
Log.e("PRODUCT_SAVE", "Failed to read error");
|
||||
}
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<ProductDTO> c, Throwable t) {
|
||||
Log.e("PRODUCT_SAVE", "Failure: " + t.getMessage());
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete Product?")
|
||||
.setPositiveButton("Yes", (d, w) ->
|
||||
RetrofitClient.getProductApi(requireContext())
|
||||
.deleteProduct(prodId)
|
||||
.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() {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.api.*;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class ProductSupplierDetailFragment extends Fragment {
|
||||
|
||||
private TextView tvMode;
|
||||
private Spinner spinnerProduct, spinnerSupplier;
|
||||
private EditText etCost;
|
||||
private Button btnSave, btnDelete, btnBack;
|
||||
|
||||
private boolean isEditing = false;
|
||||
private long editProductId = -1;
|
||||
private long editSupplierId = -1;
|
||||
private long preselectedProductId = -1;
|
||||
private long preselectedSupplierId = -1;
|
||||
|
||||
private List<ProductDTO> productList = new ArrayList<>();
|
||||
private List<SupplierDTO> supplierList = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_product_supplier_detail, container, false);
|
||||
initViews(view);
|
||||
loadData();
|
||||
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.tvPSMode);
|
||||
spinnerProduct = v.findViewById(R.id.spinnerPSProduct);
|
||||
spinnerSupplier = v.findViewById(R.id.spinnerPSSupplier);
|
||||
etCost = v.findViewById(R.id.etPSCost);
|
||||
btnSave = v.findViewById(R.id.btnSavePS);
|
||||
btnDelete = v.findViewById(R.id.btnDeletePS);
|
||||
btnBack = v.findViewById(R.id.btnPSBack);
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
loadProducts();
|
||||
loadSuppliers();
|
||||
}
|
||||
|
||||
private void loadProducts() {
|
||||
RetrofitClient.getProductApi(requireContext()).getAllProducts(null, 0, 200)
|
||||
.enqueue(new Callback<PageResponse<ProductDTO>>() {
|
||||
public void onResponse(Call<PageResponse<ProductDTO>> c,
|
||||
Response<PageResponse<ProductDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
productList = r.body().getContent();
|
||||
populateProductSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<ProductDTO>> c, Throwable t) {
|
||||
Log.e("PSDetail", "Product load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateProductSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Product --");
|
||||
for (ProductDTO p : productList) names.add(p.getProdName());
|
||||
spinnerProduct.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedProductId != -1) {
|
||||
for (int i = 0; i < productList.size(); i++) {
|
||||
if (productList.get(i).getProdId().equals(preselectedProductId)) {
|
||||
spinnerProduct.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadSuppliers() {
|
||||
RetrofitClient.getSupplierApi(requireContext()).getAllSuppliers(0, 200)
|
||||
.enqueue(new Callback<PageResponse<SupplierDTO>>() {
|
||||
public void onResponse(Call<PageResponse<SupplierDTO>> c,
|
||||
Response<PageResponse<SupplierDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
supplierList = r.body().getContent();
|
||||
populateSupplierSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<SupplierDTO>> c, Throwable t) {
|
||||
Log.e("PSDetail", "Supplier load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateSupplierSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Supplier --");
|
||||
for (SupplierDTO s : supplierList) names.add(s.getSupCompany());
|
||||
spinnerSupplier.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedSupplierId != -1) {
|
||||
for (int i = 0; i < supplierList.size(); i++) {
|
||||
if (supplierList.get(i).getSupId().equals(preselectedSupplierId)) {
|
||||
spinnerSupplier.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("productId")) {
|
||||
isEditing = true;
|
||||
editProductId = a.getLong("productId");
|
||||
editSupplierId = a.getLong("supplierId");
|
||||
preselectedProductId = editProductId;
|
||||
preselectedSupplierId = editSupplierId;
|
||||
etCost.setText(a.getString("cost"));
|
||||
tvMode.setText("Edit Product Supplier");
|
||||
btnDelete.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
tvMode.setText("Add Product Supplier");
|
||||
btnDelete.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void save() {
|
||||
if (spinnerProduct.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (spinnerSupplier.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a supplier", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
String costStr = etCost.getText().toString().trim();
|
||||
if (costStr.isEmpty()) {
|
||||
etCost.setError("Enter cost"); return;
|
||||
}
|
||||
|
||||
ProductDTO product = productList.get(spinnerProduct.getSelectedItemPosition() - 1);
|
||||
SupplierDTO supplier = supplierList.get(spinnerSupplier.getSelectedItemPosition() - 1);
|
||||
BigDecimal cost;
|
||||
try {
|
||||
cost = new BigDecimal(costStr);
|
||||
} catch (Exception e) {
|
||||
etCost.setError("Invalid cost"); return;
|
||||
}
|
||||
|
||||
ProductSupplierDTO dto = new ProductSupplierDTO(
|
||||
product.getProdId(), supplier.getSupId(), cost);
|
||||
|
||||
ProductSupplierApi api = RetrofitClient.getProductSupplierApi(requireContext());
|
||||
if (isEditing) {
|
||||
api.updateProductSupplier(editProductId, editSupplierId, dto)
|
||||
.enqueue(simpleCallback("Updated"));
|
||||
} else {
|
||||
api.createProductSupplier(dto).enqueue(simpleCallback("Saved"));
|
||||
}
|
||||
}
|
||||
|
||||
private Callback<ProductSupplierDTO> simpleCallback(String msg) {
|
||||
return new Callback<>() {
|
||||
public void onResponse(Call<ProductSupplierDTO> c, Response<ProductSupplierDTO> r) {
|
||||
if (r.isSuccessful()) {
|
||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else {
|
||||
try {
|
||||
String err = r.errorBody().string();
|
||||
Log.e("PS_SAVE", "Error: " + err);
|
||||
Toast.makeText(getContext(), "Error " + r.code(), Toast.LENGTH_SHORT).show();
|
||||
} catch (Exception e) {
|
||||
Log.e("PS_SAVE", "Failed to read error");
|
||||
}
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<ProductSupplierDTO> c, Throwable t) {
|
||||
Log.e("PS_SAVE", "Failure: " + t.getMessage());
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete?")
|
||||
.setPositiveButton("Yes", (d, w) ->
|
||||
RetrofitClient.getProductSupplierApi(requireContext())
|
||||
.deleteProductSupplier(editProductId, editSupplierId)
|
||||
.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() {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
|
||||
public class PurchaseOrderDetailFragment extends Fragment {
|
||||
|
||||
private TextView tvId, tvSupplier, tvDate, tvStatus;
|
||||
private Button btnBack;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_purchase_order_detail, container, false);
|
||||
|
||||
tvId = view.findViewById(R.id.tvPODetailId);
|
||||
tvSupplier = view.findViewById(R.id.tvPODetailSupplier);
|
||||
tvDate = view.findViewById(R.id.tvPODetailDate);
|
||||
tvStatus = view.findViewById(R.id.tvPODetailStatus);
|
||||
btnBack = view.findViewById(R.id.btnPOBack);
|
||||
|
||||
Bundle a = getArguments();
|
||||
if (a != null) {
|
||||
tvId.setText("PO #" + a.getLong("purchaseOrderId"));
|
||||
tvSupplier.setText(a.getString("supplierName"));
|
||||
tvDate.setText(a.getString("orderDate"));
|
||||
|
||||
String status = a.getString("status", "");
|
||||
tvStatus.setText(status);
|
||||
switch (status) {
|
||||
case "Completed":
|
||||
tvStatus.setTextColor(Color.parseColor("#4CAF50")); break;
|
||||
case "Pending":
|
||||
tvStatus.setTextColor(Color.parseColor("#FF9800")); break;
|
||||
case "Cancelled":
|
||||
tvStatus.setTextColor(Color.parseColor("#F44336")); break;
|
||||
default:
|
||||
tvStatus.setTextColor(Color.parseColor("#9E9E9E")); break;
|
||||
}
|
||||
}
|
||||
|
||||
btnBack.setOnClickListener(v -> {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.getChildFragmentManager().popBackStack();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.SaleFragment;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
|
||||
public class RefundDetailFragment extends Fragment {
|
||||
|
||||
private EditText etRefundSaleId, etRefundReason;
|
||||
private TextView tvSaleInfo;
|
||||
private Spinner spinnerRefundPayment;
|
||||
private Button btnLoadSale, btnProcessRefund, btnBack;
|
||||
private int saleId;
|
||||
private SaleFragment saleFragment;
|
||||
|
||||
public void setSaleFragment(SaleFragment fragment) {
|
||||
this.saleFragment = fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_refund_detail, container, false);
|
||||
|
||||
initViews(view);
|
||||
setupSpinner();
|
||||
handleArguments();
|
||||
|
||||
btnBack.setOnClickListener(v -> goBack());
|
||||
btnLoadSale.setOnClickListener(v -> loadSaleDetails());
|
||||
btnProcessRefund.setOnClickListener(v -> processRefund());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void loadSaleDetails() {
|
||||
String idText = etRefundSaleId.getText().toString().trim();
|
||||
if (idText.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Enter a Sale ID", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int id = Integer.parseInt(idText);
|
||||
// TODO: Replace with actual API call - GET v1/sales/{id}
|
||||
// For now show placeholder info
|
||||
tvSaleInfo.setText("Sale ID: " + id + " loaded. Enter reason and payment method to process refund.");
|
||||
tvSaleInfo.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
|
||||
} catch (NumberFormatException e) {
|
||||
Toast.makeText(getContext(), "Invalid Sale ID", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void processRefund() {
|
||||
if (!InputValidator.isNotEmpty(etRefundSaleId, "Sale ID"))
|
||||
return;
|
||||
if (!InputValidator.isNotEmpty(etRefundReason, "Refund Reason"))
|
||||
return;
|
||||
|
||||
String idText = etRefundSaleId.getText().toString().trim();
|
||||
String reason = etRefundReason.getText().toString().trim();
|
||||
String payment = spinnerRefundPayment.getSelectedItem().toString();
|
||||
|
||||
try {
|
||||
int id = Integer.parseInt(idText);
|
||||
// TODO: Replace with actual API call - POST v1/refunds
|
||||
ActivityLogger.log(requireContext(), "Processed refund for Sale ID: " + id + " - Reason: " + reason);
|
||||
Toast.makeText(getContext(), "Refund processed for Sale ID: " + id, Toast.LENGTH_SHORT).show();
|
||||
if (saleFragment != null)
|
||||
saleFragment.reloadSales();
|
||||
goBack();
|
||||
} catch (NumberFormatException e) {
|
||||
Toast.makeText(getContext(), "Invalid Sale ID", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
if (getArguments() != null && getArguments().containsKey("saleId")) {
|
||||
saleId = getArguments().getInt("saleId");
|
||||
etRefundSaleId.setText(String.valueOf(saleId));
|
||||
String info = "Sale Date: " + getArguments().getString("saleDate")
|
||||
+ " | Employee: " + getArguments().getString("employeeName")
|
||||
+ " | Total: $" + String.format("%.2f", getArguments().getDouble("total"))
|
||||
+ " | Payment: " + getArguments().getString("paymentMethod");
|
||||
tvSaleInfo.setText(info);
|
||||
tvSaleInfo.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
|
||||
|
||||
// Pre-select payment method
|
||||
String payment = getArguments().getString("paymentMethod");
|
||||
ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinnerRefundPayment.getAdapter();
|
||||
if (adapter != null && payment != null) {
|
||||
int pos = adapter.getPosition(payment);
|
||||
if (pos >= 0)
|
||||
spinnerRefundPayment.setSelection(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void goBack() {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null)
|
||||
listFragment.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
|
||||
private void initViews(View view) {
|
||||
etRefundSaleId = view.findViewById(R.id.etRefundSaleId);
|
||||
etRefundReason = view.findViewById(R.id.etRefundReason);
|
||||
tvSaleInfo = view.findViewById(R.id.tvSaleInfo);
|
||||
spinnerRefundPayment = view.findViewById(R.id.spinnerRefundPayment);
|
||||
btnLoadSale = view.findViewById(R.id.btnLoadSale);
|
||||
btnProcessRefund = view.findViewById(R.id.btnProcessRefund);
|
||||
btnBack = view.findViewById(R.id.btnRefundBack);
|
||||
}
|
||||
|
||||
private void setupSpinner() {
|
||||
BlackTextArrayAdapter<String> adapter = new BlackTextArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item,
|
||||
new String[] { "Cash", "Card", "Debit" });
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerRefundPayment.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.example.petstoremobile.models;
|
||||
|
||||
public class ProductSupplier {
|
||||
private int supId;
|
||||
private int prodId;
|
||||
private String supCompany;
|
||||
private String prodName;
|
||||
private double cost;
|
||||
|
||||
public ProductSupplier(int supId, int prodId, String supCompany, String prodName, double cost) {
|
||||
this.supId = supId;
|
||||
this.prodId = prodId;
|
||||
this.supCompany = supCompany;
|
||||
this.prodName = prodName;
|
||||
this.cost = cost;
|
||||
}
|
||||
|
||||
public int getSupId() {
|
||||
return supId;
|
||||
}
|
||||
|
||||
public int getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public String getSupCompany() {
|
||||
return supCompany;
|
||||
}
|
||||
|
||||
public String getProdName() {
|
||||
return prodName;
|
||||
}
|
||||
|
||||
public double getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public void setSupCompany(String supCompany) {
|
||||
this.supCompany = supCompany;
|
||||
}
|
||||
|
||||
public void setProdName(String prodName) {
|
||||
this.prodName = prodName;
|
||||
}
|
||||
|
||||
public void setCost(double cost) {
|
||||
this.cost = cost;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.example.petstoremobile.models;
|
||||
|
||||
public class PurchaseOrder {
|
||||
private int purchaseOrderId;
|
||||
private String supplierName;
|
||||
private String orderDate;
|
||||
private String status;
|
||||
|
||||
public PurchaseOrder(int purchaseOrderId, String supplierName, String orderDate, String status) {
|
||||
this.purchaseOrderId = purchaseOrderId;
|
||||
this.supplierName = supplierName;
|
||||
this.orderDate = orderDate;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public int getPurchaseOrderId() {
|
||||
return purchaseOrderId;
|
||||
}
|
||||
|
||||
public String getSupplierName() {
|
||||
return supplierName;
|
||||
}
|
||||
|
||||
public String getOrderDate() {
|
||||
return orderDate;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.example.petstoremobile.models;
|
||||
|
||||
public class Sale {
|
||||
private int saleId;
|
||||
private String saleDate;
|
||||
private String employeeName;
|
||||
private String itemName;
|
||||
private int quantity;
|
||||
private double unitPrice;
|
||||
private double total;
|
||||
private String paymentMethod;
|
||||
private boolean isRefund;
|
||||
|
||||
public Sale(int saleId, String saleDate, String employeeName, String itemName,
|
||||
int quantity, double unitPrice, double total, String paymentMethod, boolean isRefund) {
|
||||
this.saleId = saleId;
|
||||
this.saleDate = saleDate;
|
||||
this.employeeName = employeeName;
|
||||
this.itemName = itemName;
|
||||
this.quantity = quantity;
|
||||
this.unitPrice = unitPrice;
|
||||
this.total = total;
|
||||
this.paymentMethod = paymentMethod;
|
||||
this.isRefund = isRefund;
|
||||
}
|
||||
|
||||
public int getSaleId() {
|
||||
return saleId;
|
||||
}
|
||||
|
||||
public String getSaleDate() {
|
||||
return saleDate;
|
||||
}
|
||||
|
||||
public String getEmployeeName() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public String getItemName() {
|
||||
return itemName;
|
||||
}
|
||||
|
||||
public int getQuantity() {
|
||||
return quantity;
|
||||
}
|
||||
|
||||
public double getUnitPrice() {
|
||||
return unitPrice;
|
||||
}
|
||||
|
||||
public double getTotal() {
|
||||
return total;
|
||||
}
|
||||
|
||||
public String getPaymentMethod() {
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
public boolean isRefund() {
|
||||
return isRefund;
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,14 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:background="@color/primary_dark">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
android:layout_weight="1"
|
||||
android:background="@color/background_grey"/>
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/bottom_navigation"
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
@@ -21,7 +20,7 @@
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburger"
|
||||
android:id="@+id/btnHamburgerAdoption"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
@@ -43,7 +42,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by adopter or pet name..."
|
||||
android:hint="Search by customer or pet..."
|
||||
android:inputType="text"
|
||||
android:drawableStart="@android:drawable/ic_menu_search"
|
||||
android:drawablePadding="8dp"
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white" />
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -65,74 +65,37 @@
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<!-- Customer -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adopter Name"
|
||||
android:text="Customer"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAdopterName"
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter adopter name"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Pet -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adopter Email"
|
||||
android:text="Pet"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAdopterEmail"
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionPet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter email address"
|
||||
android:inputType="textEmailAddress"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adopter Phone"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAdopterPhone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter phone number"
|
||||
android:inputType="phone"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAdoptionPetName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter pet name"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Adoption Date -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -145,11 +108,14 @@
|
||||
android:id="@+id/etAdoptionDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="YYYY-MM-DD"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
android:hint="Tap to select date"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -161,7 +127,8 @@
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -65,54 +65,76 @@
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
|
||||
|
||||
<!-- Customer -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Customer Name"
|
||||
android:text="Customer"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etCustomerName"
|
||||
<Spinner
|
||||
android:id="@+id/spinnerCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter customer name"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Store -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet Name"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etApptPetName"
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter pet name"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Pet -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Service Type"
|
||||
android:text="Pet"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etServiceType"
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="e.g. Grooming, Vet Checkup"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Service -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Service"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
|
||||
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerService"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Appointment Date -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -125,10 +147,14 @@
|
||||
android:id="@+id/etAppointmentDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="YYYY-MM-DD"
|
||||
android:inputType="text"
|
||||
android:hint="Tap to select date"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Appointment Time-->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -137,14 +163,45 @@
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAppointmentTime"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="e.g. 10:00 AM"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hour:"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginEnd="6dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerHour"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Min:"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginEnd="6dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerMinute"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Header -->
|
||||
<LinearLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
@@ -38,19 +39,68 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchInventory"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by item name or category..."
|
||||
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"/>
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchInventory"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_weight="1"
|
||||
android:hint="Search by product or category…"
|
||||
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"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerCategory"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@android:color/white"
|
||||
android:padding="10dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Bulk-delete action bar (hidden until long-press) -->
|
||||
<LinearLayout
|
||||
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">
|
||||
|
||||
<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"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<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"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Inventory list -->
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshInventory"
|
||||
android:layout_width="match_parent"
|
||||
@@ -60,7 +110,8 @@
|
||||
android:id="@+id/recyclerViewInventory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"/>
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
@@ -73,7 +124,7 @@
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:contentDescription="Add Inventory Item"
|
||||
android:contentDescription="Add Inventory"
|
||||
app:srcCompat="@android:drawable/ic_input_add"
|
||||
app:tint="@color/white"/>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<!-- Header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
@@ -19,7 +20,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Inventory Item"
|
||||
android:text="Add Inventory"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
@@ -28,10 +29,10 @@
|
||||
android:id="@+id/btnDeleteInventory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white" />
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -51,52 +52,51 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Inventory ID — edit mode only -->
|
||||
<TextView
|
||||
android:id="@+id/tvInventoryId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ID: #0"
|
||||
android:text="Inventory ID: —"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<!-- Product search label -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Item Name"
|
||||
android:text="Product"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etItemName"
|
||||
<!-- AutoComplete search box -->
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/etProductSearch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter item name"
|
||||
android:hint="Search product name…"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Category"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:completionThreshold="1"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInventoryCategory"
|
||||
<!-- Selected product info (ID + category) shown after picking -->
|
||||
<TextView
|
||||
android:id="@+id/tvProductInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="e.g. Food, Toys, Medicine"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:textColor="#888888"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<!-- Quantity label -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -105,44 +105,13 @@
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Quantity input -->
|
||||
<EditText
|
||||
android:id="@+id/etQuantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter quantity"
|
||||
android:inputType="number"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Unit Price"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etUnitPrice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter unit price"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supplier"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etInventorySupplier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter supplier name"
|
||||
android:inputType="text"/>
|
||||
android:inputType="number"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -150,6 +119,7 @@
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<!-- Bottom buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
android:orientation="vertical"
|
||||
android:background="@color/primary_dark">
|
||||
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -133,6 +135,30 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<!-- Appointments -->
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerAppointments"
|
||||
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="Appointments"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Adoptions -->
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerAdoptions"
|
||||
android:layout_width="match_parent"
|
||||
@@ -152,25 +178,9 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerAppointments"
|
||||
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="Appointments"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Inventory -->
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerInventory"
|
||||
android:layout_width="match_parent"
|
||||
@@ -190,6 +200,9 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<!-- Product -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerProducts"
|
||||
android:layout_width="match_parent"
|
||||
@@ -209,6 +222,78 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<!-- Sale -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerSale"
|
||||
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="Sale"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- PurchaseOrder -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerPurchaseOrderView"
|
||||
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="PurchaseOrder"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ProductSupplier -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/drawerProductSupplier"
|
||||
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="ProductSupplier"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
@@ -12,7 +12,6 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
@@ -21,7 +20,7 @@
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburger"
|
||||
android:id="@+id/btnHamburgerProduct"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
@@ -43,7 +42,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by product name or category..."
|
||||
android:hint="Search by name or category..."
|
||||
android:inputType="text"
|
||||
android:drawableStart="@android:drawable/ic_menu_search"
|
||||
android:drawablePadding="8dp"
|
||||
|
||||
@@ -28,10 +28,9 @@
|
||||
android:id="@+id/btnDeleteProduct"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white" />
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -65,6 +64,7 @@
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<!-- Product Name -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -81,6 +81,22 @@
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Category -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Category"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerProductCategory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Description -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -93,27 +109,12 @@
|
||||
android:id="@+id/etProductDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="Enter product description"
|
||||
android:hint="Enter description"
|
||||
android:inputType="textMultiLine"
|
||||
android:minLines="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Category"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etProductCategory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="e.g. Food, Toys, Grooming"
|
||||
android:inputType="text"
|
||||
android:minLines="2"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Price -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -126,24 +127,9 @@
|
||||
android:id="@+id/etProductPrice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter price"
|
||||
android:hint="0.00"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Stock Quantity"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etStockQuantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter stock quantity"
|
||||
android:inputType="number"/>
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<?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/btnHamburgerPS"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product Suppliers"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchPS"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by product or supplier..."
|
||||
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/swipeRefreshPS"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewPS"
|
||||
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/fabAddPS"
|
||||
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 Product Supplier"
|
||||
app:srcCompat="@android:drawable/ic_input_add"
|
||||
app:tint="@color/white"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,138 @@
|
||||
<?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/tvPSMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Product Supplier"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeletePS"
|
||||
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">
|
||||
|
||||
<!-- Product -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPSProduct"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Supplier -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supplier"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPSSupplier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Cost -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cost"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPSCost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="0.00"
|
||||
android:inputType="numberDecimal"
|
||||
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/btnPSBack"
|
||||
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/btnSavePS"
|
||||
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>
|
||||
67
android/app/src/main/res/layout/fragment_purchase_order.xml
Normal file
67
android/app/src/main/res/layout/fragment_purchase_order.xml
Normal file
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
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/btnHamburgerPO"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Purchase Orders"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchPO"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by supplier or status..."
|
||||
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/swipeRefreshPO"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewPO"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,124 @@
|
||||
<?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="Purchase Order Details"
|
||||
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="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/tvPODetailId"
|
||||
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="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supplier"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailSupplier"
|
||||
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"
|
||||
android:text="Order Date"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPOBack"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
154
android/app/src/main/res/layout/fragment_refund_detail.xml
Normal file
154
android/app/src/main/res/layout/fragment_refund_detail.xml
Normal file
@@ -0,0 +1,154 @@
|
||||
<?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="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:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sale ID"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<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"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<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="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load a sale to see details"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund Reason"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etRefundReason"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter reason for refund"
|
||||
android:inputType="textMultiLine"
|
||||
android:lines="3"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<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/spinnerRefundPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</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/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"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
69
android/app/src/main/res/layout/fragment_sale.xml
Normal file
69
android/app/src/main/res/layout/fragment_sale.xml
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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:id="@+id/header"
|
||||
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/btnHamburger"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sales"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etSearchSale"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:hint="Search by item, employee or date..."
|
||||
android: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/swipeRefreshSale"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerViewSales"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"/>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -1,24 +1,29 @@
|
||||
<?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:padding="16dp"
|
||||
android:background="@android:color/white">
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="@color/white">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAdopterName"
|
||||
android:id="@+id/tvAdoptionCustomerName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Adopter Name"
|
||||
android:textColor="#000000"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Customer Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@@ -26,14 +31,14 @@
|
||||
android:id="@+id/tvAdoptionStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#4CAF50"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="Status"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="12sp" />
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -42,23 +47,43 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Pet: name"
|
||||
android:textColor="#666666"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Pet Name"
|
||||
android:textColor="#888888"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAdoptionDate"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Date: "
|
||||
android:textColor="#666666"
|
||||
android:textSize="14sp" />
|
||||
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="#EEEEEE"
|
||||
android:background="#F0F0F0"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -2,80 +2,91 @@
|
||||
<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:background="@color/white"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
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/tvItemName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Item Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvUnitPrice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCategory"
|
||||
<!-- Checkbox (visible only in bulk-delete selection mode) -->
|
||||
<CheckBox
|
||||
android:id="@+id/cbSelectInventory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Category"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp" />
|
||||
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/tvQuantity"
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Qty: 0"
|
||||
android:textSize="13sp" />
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProductName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
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/tvQuantity"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInvSupplier"
|
||||
android:id="@+id/tvInventoryId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supplier: "
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Inv ID: —"
|
||||
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/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>
|
||||
|
||||
<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>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
@@ -15,68 +16,96 @@
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetName"
|
||||
<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="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" />
|
||||
android:orientation="vertical">
|
||||
|
||||
<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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
</LinearLayout>
|
||||
<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/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" />
|
||||
<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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="8dp">
|
||||
</LinearLayout>
|
||||
|
||||
<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/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" />
|
||||
|
||||
<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
|
||||
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>
|
||||
|
||||
|
||||
@@ -9,34 +9,27 @@
|
||||
android:paddingBottom="16dp"
|
||||
android:background="@color/white">
|
||||
|
||||
<LinearLayout
|
||||
<TextView
|
||||
android:id="@+id/tvProductName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
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/tvProductName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
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/tvProductPrice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/tvProductCategory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Category"
|
||||
android:textColor="#888888"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProductDesc"
|
||||
@@ -44,8 +37,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Description"
|
||||
android:maxLines="2"
|
||||
android:text="Product Description"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp" />
|
||||
|
||||
@@ -57,21 +50,14 @@
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProductCategory"
|
||||
android:id="@+id/tvProductPrice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Category"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvStockQuantity"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Stock: 0"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp" />
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
59
android/app/src/main/res/layout/item_product_supplier.xml
Normal file
59
android/app/src/main/res/layout/item_product_supplier.xml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="@color/white">
|
||||
|
||||
<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"
|
||||
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>
|
||||
80
android/app/src/main/res/layout/item_purchase_order.xml
Normal file
80
android/app/src/main/res/layout/item_purchase_order.xml
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="@color/white">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPOId"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="PO #000"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPOStatus"
|
||||
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/tvPOSupplier"
|
||||
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/tvPODate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
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>
|
||||
104
android/app/src/main/res/layout/item_sale.xml
Normal file
104
android/app/src/main/res/layout/item_sale.xml
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:background="@color/white">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleItemName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="Item Name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRefundBadge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="REFUND"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="11sp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleEmployee"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="#888888"
|
||||
android:textSize="13sp"
|
||||
android:text="By: Employee"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleId"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="#888888"
|
||||
android:textSize="12sp"
|
||||
android:text="ID: 0"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleDate"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="#888888"
|
||||
android:textSize="12sp"
|
||||
android:text="Date"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSalePayment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#888888"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Cash"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:text="$0.00"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -11,4 +11,5 @@
|
||||
<color name="status_available">#2ECC71</color>
|
||||
<color name="status_adopted">#E74C3C</color>
|
||||
<color name="accent_blue">#3498DB</color>
|
||||
<color name="spinner_text">#000000</color>
|
||||
</resources>
|
||||
@@ -2,4 +2,5 @@
|
||||
<string name="app_name">Leons Pet Store</string>
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
<string name="pet_profile_image_desc">Pet Profile Image</string>
|
||||
</resources>
|
||||
@@ -10,6 +10,11 @@
|
||||
<item name="android:statusBarColor">@color/primary_dark</item>
|
||||
<item name="android:windowLightStatusBar">false</item>
|
||||
</style>
|
||||
|
||||
<style name="CircleImageView" parent="">
|
||||
<item name="cornerSize">50%</item>
|
||||
</style>
|
||||
|
||||
<style name="Widget.App.EditText" parent="Widget.AppCompat.EditText">
|
||||
<item name="android:textColor">@color/text_dark</item>
|
||||
</style>
|
||||
|
||||
@@ -9,3 +9,10 @@
|
||||
# Copy this file to local.properties and update the path below:
|
||||
|
||||
sdk.dir=/path/to/your/android/sdk
|
||||
|
||||
# Optional backend overrides for Android development:
|
||||
# Emulator default should usually stay 10.0.2.2 for the local machine.
|
||||
petstore.backend.emulatorUrl=http\://10.0.2.2\:8080/
|
||||
|
||||
# Physical device example. Replace with the machine IP running the backend.
|
||||
petstore.backend.deviceUrl=http\://10.0.0.200\:8080/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info": {
|
||||
"name": "PetShop API Complete Collection",
|
||||
"name": "PetShop Complete Collection",
|
||||
"_postman_id": "petshop-api-complete-v1",
|
||||
"description": "Complete API collection with all 95+ verified endpoints",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
|
||||
@@ -33,6 +33,7 @@ public class DevStackApplication {
|
||||
docker.ensureDockerAvailable();
|
||||
docker.startDatabase();
|
||||
context = new SpringApplicationBuilder(BackendApplication.class)
|
||||
.profiles("local")
|
||||
.initializers(new FlywayContextInitializer())
|
||||
.run(args);
|
||||
context.addApplicationListener(event -> {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.petshop.backend.config;
|
||||
|
||||
import com.petshop.backend.repository.PetRepository;
|
||||
import com.petshop.backend.repository.ProductRepository;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
@Component
|
||||
@Profile("local")
|
||||
public class LocalCatalogSeedInitializer implements CommandLineRunner {
|
||||
|
||||
private final DataSource dataSource;
|
||||
private final PetRepository petRepository;
|
||||
private final ProductRepository productRepository;
|
||||
|
||||
public LocalCatalogSeedInitializer(DataSource dataSource, PetRepository petRepository, ProductRepository productRepository) {
|
||||
this.dataSource = dataSource;
|
||||
this.petRepository = petRepository;
|
||||
this.productRepository = productRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(String... args) {
|
||||
if (petRepository.count() > 6 || productRepository.count() > 6) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResourceDatabasePopulator populator = new ResourceDatabasePopulator(false, false, "UTF-8",
|
||||
new ClassPathResource("dev/expand_pet_product_seed.sql"));
|
||||
populator.execute(dataSource);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,40 @@
|
||||
package com.petshop.backend.controller;
|
||||
|
||||
import com.petshop.backend.dto.analytics.DashboardResponse;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
import com.petshop.backend.service.AnalyticsService;
|
||||
import com.petshop.backend.util.AuthenticationHelper;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/analytics")
|
||||
@PreAuthorize("hasRole('ADMIN')")
|
||||
@PreAuthorize("hasAnyRole('ADMIN', 'STAFF')")
|
||||
public class AnalyticsController {
|
||||
|
||||
private final AnalyticsService analyticsService;
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public AnalyticsController(AnalyticsService analyticsService) {
|
||||
public AnalyticsController(AnalyticsService analyticsService, UserRepository userRepository) {
|
||||
this.analyticsService = analyticsService;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@GetMapping("/dashboard")
|
||||
public ResponseEntity<DashboardResponse> getDashboard(
|
||||
@RequestParam(defaultValue = "30") int days,
|
||||
@RequestParam(defaultValue = "10") int top) {
|
||||
return ResponseEntity.ok(analyticsService.getDashboardData(days, top));
|
||||
if (days < 1 || days > 365) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "days must be between 1 and 365");
|
||||
}
|
||||
if (top < 1 || top > 50) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "top must be between 1 and 50");
|
||||
}
|
||||
User user = AuthenticationHelper.getAuthenticatedUser(userRepository);
|
||||
return ResponseEntity.ok(analyticsService.getDashboardData(days, top, user));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ public class CategoryController {
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<CategoryResponse>> getAllCategories(
|
||||
@RequestParam(required = false) String q,
|
||||
@RequestParam(required = false) String type,
|
||||
Pageable pageable) {
|
||||
return ResponseEntity.ok(categoryService.getAllCategories(q, pageable));
|
||||
return ResponseEntity.ok(categoryService.getAllCategories(q, type, pageable));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.petshop.backend.dto.chat.ConversationRequest;
|
||||
import com.petshop.backend.dto.chat.ConversationResponse;
|
||||
import com.petshop.backend.dto.chat.MessageRequest;
|
||||
import com.petshop.backend.dto.chat.MessageResponse;
|
||||
import com.petshop.backend.dto.chat.UpdateConversationRequest;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.CustomerRepository;
|
||||
import com.petshop.backend.repository.UserRepository;
|
||||
@@ -96,4 +97,13 @@ public class ChatController {
|
||||
chatRealtimeService.publishConversationUpdate(id);
|
||||
return ResponseEntity.ok(conversation);
|
||||
}
|
||||
|
||||
@PutMapping("/conversations/{id}")
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
public ResponseEntity<ConversationResponse> updateConversation(@PathVariable Long id, @Valid @RequestBody UpdateConversationRequest request) {
|
||||
User user = getCurrentUser();
|
||||
ConversationResponse conversation = chatService.updateConversation(id, user.getId(), user.getRole(), request);
|
||||
chatRealtimeService.publishConversationUpdate(id);
|
||||
return ResponseEntity.ok(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,29 @@ public class DropdownController {
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/product-categories")
|
||||
public ResponseEntity<List<DropdownOption>> getProductCategories() {
|
||||
return ResponseEntity.ok(
|
||||
categoryRepository.findAll().stream()
|
||||
.filter(c -> "product".equalsIgnoreCase(c.getCategoryType()))
|
||||
.map(c -> new DropdownOption(c.getCategoryId(), c.getCategoryName()))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/pet-species")
|
||||
public ResponseEntity<List<DropdownOption>> getPetSpecies() {
|
||||
return ResponseEntity.ok(
|
||||
petRepository.findAll().stream()
|
||||
.map(p -> p.getPetSpecies())
|
||||
.filter(species -> species != null && !species.isBlank())
|
||||
.distinct()
|
||||
.sorted(String.CASE_INSENSITIVE_ORDER)
|
||||
.map(species -> new DropdownOption(null, species))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
@GetMapping("/stores")
|
||||
public ResponseEntity<List<DropdownOption>> getStores() {
|
||||
return ResponseEntity.ok(
|
||||
|
||||
@@ -25,8 +25,10 @@ public class PetController {
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<PetResponse>> getAllPets(
|
||||
@RequestParam(required = false) String q,
|
||||
@RequestParam(required = false) String species,
|
||||
@RequestParam(required = false) String status,
|
||||
Pageable pageable) {
|
||||
return ResponseEntity.ok(petService.getAllPets(q, pageable));
|
||||
return ResponseEntity.ok(petService.getAllPets(q, species, status, pageable));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
|
||||
@@ -25,8 +25,9 @@ public class ProductController {
|
||||
@GetMapping
|
||||
public ResponseEntity<Page<ProductResponse>> getAllProducts(
|
||||
@RequestParam(required = false) String q,
|
||||
@RequestParam(required = false) Long categoryId,
|
||||
Pageable pageable) {
|
||||
return ResponseEntity.ok(productService.getAllProducts(q, pageable));
|
||||
return ResponseEntity.ok(productService.getAllProducts(q, categoryId, pageable));
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
|
||||
@@ -9,15 +9,19 @@ public class DashboardResponse {
|
||||
private InventorySummary inventorySummary;
|
||||
private List<TopProduct> topProducts;
|
||||
private List<DailySales> dailySales;
|
||||
private List<PaymentMethodData> paymentMethods;
|
||||
private List<EmployeePerformanceData> employeePerformance;
|
||||
|
||||
public DashboardResponse() {
|
||||
}
|
||||
|
||||
public DashboardResponse(SalesSummary salesSummary, InventorySummary inventorySummary, List<TopProduct> topProducts, List<DailySales> dailySales) {
|
||||
public DashboardResponse(SalesSummary salesSummary, InventorySummary inventorySummary, List<TopProduct> topProducts, List<DailySales> dailySales, List<PaymentMethodData> paymentMethods, List<EmployeePerformanceData> employeePerformance) {
|
||||
this.salesSummary = salesSummary;
|
||||
this.inventorySummary = inventorySummary;
|
||||
this.topProducts = topProducts;
|
||||
this.dailySales = dailySales;
|
||||
this.paymentMethods = paymentMethods;
|
||||
this.employeePerformance = employeePerformance;
|
||||
}
|
||||
|
||||
public SalesSummary getSalesSummary() {
|
||||
@@ -52,17 +56,33 @@ public class DashboardResponse {
|
||||
this.dailySales = dailySales;
|
||||
}
|
||||
|
||||
public List<PaymentMethodData> getPaymentMethods() {
|
||||
return paymentMethods;
|
||||
}
|
||||
|
||||
public void setPaymentMethods(List<PaymentMethodData> paymentMethods) {
|
||||
this.paymentMethods = paymentMethods;
|
||||
}
|
||||
|
||||
public List<EmployeePerformanceData> getEmployeePerformance() {
|
||||
return employeePerformance;
|
||||
}
|
||||
|
||||
public void setEmployeePerformance(List<EmployeePerformanceData> employeePerformance) {
|
||||
this.employeePerformance = employeePerformance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DashboardResponse that = (DashboardResponse) o;
|
||||
return Objects.equals(salesSummary, that.salesSummary) && Objects.equals(inventorySummary, that.inventorySummary) && Objects.equals(topProducts, that.topProducts) && Objects.equals(dailySales, that.dailySales);
|
||||
return Objects.equals(salesSummary, that.salesSummary) && Objects.equals(inventorySummary, that.inventorySummary) && Objects.equals(topProducts, that.topProducts) && Objects.equals(dailySales, that.dailySales) && Objects.equals(paymentMethods, that.paymentMethods) && Objects.equals(employeePerformance, that.employeePerformance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales);
|
||||
return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales, paymentMethods, employeePerformance);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,6 +92,8 @@ public class DashboardResponse {
|
||||
", inventorySummary=" + inventorySummary +
|
||||
", topProducts=" + topProducts +
|
||||
", dailySales=" + dailySales +
|
||||
", paymentMethods=" + paymentMethods +
|
||||
", employeePerformance=" + employeePerformance +
|
||||
'}';
|
||||
}
|
||||
|
||||
@@ -80,15 +102,17 @@ public class DashboardResponse {
|
||||
private Long totalSales;
|
||||
private BigDecimal totalRefunds;
|
||||
private Long totalRefundCount;
|
||||
private Long totalItemsSold;
|
||||
|
||||
public SalesSummary() {
|
||||
}
|
||||
|
||||
public SalesSummary(BigDecimal totalRevenue, Long totalSales, BigDecimal totalRefunds, Long totalRefundCount) {
|
||||
public SalesSummary(BigDecimal totalRevenue, Long totalSales, BigDecimal totalRefunds, Long totalRefundCount, Long totalItemsSold) {
|
||||
this.totalRevenue = totalRevenue;
|
||||
this.totalSales = totalSales;
|
||||
this.totalRefunds = totalRefunds;
|
||||
this.totalRefundCount = totalRefundCount;
|
||||
this.totalItemsSold = totalItemsSold;
|
||||
}
|
||||
|
||||
public BigDecimal getTotalRevenue() {
|
||||
@@ -123,17 +147,25 @@ public class DashboardResponse {
|
||||
this.totalRefundCount = totalRefundCount;
|
||||
}
|
||||
|
||||
public Long getTotalItemsSold() {
|
||||
return totalItemsSold;
|
||||
}
|
||||
|
||||
public void setTotalItemsSold(Long totalItemsSold) {
|
||||
this.totalItemsSold = totalItemsSold;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SalesSummary that = (SalesSummary) o;
|
||||
return Objects.equals(totalRevenue, that.totalRevenue) && Objects.equals(totalSales, that.totalSales) && Objects.equals(totalRefunds, that.totalRefunds) && Objects.equals(totalRefundCount, that.totalRefundCount);
|
||||
return Objects.equals(totalRevenue, that.totalRevenue) && Objects.equals(totalSales, that.totalSales) && Objects.equals(totalRefunds, that.totalRefunds) && Objects.equals(totalRefundCount, that.totalRefundCount) && Objects.equals(totalItemsSold, that.totalItemsSold);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount);
|
||||
return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount, totalItemsSold);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,10 +175,69 @@ public class DashboardResponse {
|
||||
", totalSales=" + totalSales +
|
||||
", totalRefunds=" + totalRefunds +
|
||||
", totalRefundCount=" + totalRefundCount +
|
||||
", totalItemsSold=" + totalItemsSold +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class PaymentMethodData {
|
||||
private String paymentMethod;
|
||||
private Long count;
|
||||
|
||||
public PaymentMethodData() {
|
||||
}
|
||||
|
||||
public PaymentMethodData(String paymentMethod, Long count) {
|
||||
this.paymentMethod = paymentMethod;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public String getPaymentMethod() {
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
public void setPaymentMethod(String paymentMethod) {
|
||||
this.paymentMethod = paymentMethod;
|
||||
}
|
||||
|
||||
public Long getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(Long count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
public static class EmployeePerformanceData {
|
||||
private String employeeName;
|
||||
private BigDecimal revenue;
|
||||
|
||||
public EmployeePerformanceData() {
|
||||
}
|
||||
|
||||
public EmployeePerformanceData(String employeeName, BigDecimal revenue) {
|
||||
this.employeeName = employeeName;
|
||||
this.revenue = revenue;
|
||||
}
|
||||
|
||||
public String getEmployeeName() {
|
||||
return employeeName;
|
||||
}
|
||||
|
||||
public void setEmployeeName(String employeeName) {
|
||||
this.employeeName = employeeName;
|
||||
}
|
||||
|
||||
public BigDecimal getRevenue() {
|
||||
return revenue;
|
||||
}
|
||||
|
||||
public void setRevenue(BigDecimal revenue) {
|
||||
this.revenue = revenue;
|
||||
}
|
||||
}
|
||||
|
||||
public static class InventorySummary {
|
||||
private Long totalProducts;
|
||||
private Long lowStockProducts;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.petshop.backend.dto.chat;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public class UpdateConversationRequest {
|
||||
@NotBlank(message = "Status is required")
|
||||
@Pattern(regexp = "^(OPEN|CLOSED)$", message = "Status must be OPEN or CLOSED")
|
||||
private String status;
|
||||
|
||||
public UpdateConversationRequest() {
|
||||
}
|
||||
|
||||
public UpdateConversationRequest(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ public interface CategoryRepository extends JpaRepository<Category, Long> {
|
||||
Optional<Category> findByCategoryName(String categoryName);
|
||||
|
||||
@Query("SELECT c FROM Category c WHERE " +
|
||||
"LOWER(c.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||
"LOWER(c.categoryType) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||
Page<Category> searchCategories(@Param("q") String query, Pageable pageable);
|
||||
"(:q IS NULL OR LOWER(c.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(c.categoryType) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||
"(:type IS NULL OR LOWER(c.categoryType) = LOWER(:type))")
|
||||
Page<Category> searchCategories(@Param("q") String query, @Param("type") String type, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ import org.springframework.stereotype.Repository;
|
||||
public interface PetRepository extends JpaRepository<Pet, Long> {
|
||||
|
||||
@Query("SELECT p FROM Pet p WHERE " +
|
||||
"LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||
"LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||
"LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||
Page<Pet> searchPets(@Param("q") String query, Pageable pageable);
|
||||
"(:q IS NULL OR LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status))")
|
||||
Page<Pet> searchPets(@Param("q") String query, @Param("species") String species, @Param("status") String status, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.springframework.stereotype.Repository;
|
||||
public interface ProductRepository extends JpaRepository<Product, Long> {
|
||||
|
||||
@Query("SELECT p FROM Product p WHERE " +
|
||||
"LOWER(p.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
||||
"LOWER(p.prodDesc) LIKE LOWER(CONCAT('%', :q, '%'))")
|
||||
Page<Product> searchProducts(@Param("q") String query, Pageable pageable);
|
||||
"(:q IS NULL OR LOWER(p.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.prodDesc, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||
"(:categoryId IS NULL OR p.category.categoryId = :categoryId)")
|
||||
Page<Product> searchProducts(@Param("q") String query, @Param("categoryId") Long categoryId, Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.petshop.backend.service;
|
||||
|
||||
import com.petshop.backend.dto.analytics.DashboardResponse;
|
||||
import com.petshop.backend.entity.Employee;
|
||||
import com.petshop.backend.entity.Inventory;
|
||||
import com.petshop.backend.entity.Product;
|
||||
import com.petshop.backend.entity.Sale;
|
||||
import com.petshop.backend.entity.User;
|
||||
import com.petshop.backend.repository.EmployeeRepository;
|
||||
import com.petshop.backend.repository.InventoryRepository;
|
||||
import com.petshop.backend.repository.ProductRepository;
|
||||
import com.petshop.backend.repository.SaleRepository;
|
||||
@@ -23,28 +26,33 @@ public class AnalyticsService {
|
||||
private final SaleRepository saleRepository;
|
||||
private final InventoryRepository inventoryRepository;
|
||||
private final ProductRepository productRepository;
|
||||
private final EmployeeRepository employeeRepository;
|
||||
|
||||
public AnalyticsService(SaleRepository saleRepository,
|
||||
InventoryRepository inventoryRepository, ProductRepository productRepository) {
|
||||
InventoryRepository inventoryRepository, ProductRepository productRepository, EmployeeRepository employeeRepository) {
|
||||
this.saleRepository = saleRepository;
|
||||
this.inventoryRepository = inventoryRepository;
|
||||
this.productRepository = productRepository;
|
||||
this.employeeRepository = employeeRepository;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public DashboardResponse getDashboardData(int days, int top) {
|
||||
public DashboardResponse getDashboardData(int days, int top, User user) {
|
||||
LocalDateTime startDate = LocalDateTime.now().minusDays(days);
|
||||
|
||||
List<Sale> sales = saleRepository.findAll().stream()
|
||||
.filter(sale -> sale.getSaleDate().isAfter(startDate))
|
||||
.filter(sale -> includeSaleForUser(sale, user))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales);
|
||||
DashboardResponse.InventorySummary inventorySummary = calculateInventorySummary();
|
||||
DashboardResponse.InventorySummary inventorySummary = user.getRole() == User.Role.ADMIN ? calculateInventorySummary() : null;
|
||||
List<DashboardResponse.TopProduct> topProducts = calculateTopProducts(sales, top);
|
||||
List<DashboardResponse.DailySales> dailySales = calculateDailySales(sales, days);
|
||||
List<DashboardResponse.PaymentMethodData> paymentMethods = calculatePaymentMethods(sales);
|
||||
List<DashboardResponse.EmployeePerformanceData> employeePerformance = calculateEmployeePerformance(sales, user);
|
||||
|
||||
return new DashboardResponse(salesSummary, inventorySummary, topProducts, dailySales);
|
||||
return new DashboardResponse(salesSummary, inventorySummary, topProducts, dailySales, paymentMethods, employeePerformance);
|
||||
}
|
||||
|
||||
private DashboardResponse.SalesSummary calculateSalesSummary(List<Sale> sales) {
|
||||
@@ -66,7 +74,13 @@ public class AnalyticsService {
|
||||
.filter(Sale::getIsRefund)
|
||||
.count();
|
||||
|
||||
return new DashboardResponse.SalesSummary(totalRevenue, totalSales, totalRefunds, totalRefundCount);
|
||||
Long totalItemsSold = sales.stream()
|
||||
.filter(sale -> !sale.getIsRefund())
|
||||
.flatMap(sale -> sale.getItems().stream())
|
||||
.mapToLong(item -> item.getQuantity())
|
||||
.sum();
|
||||
|
||||
return new DashboardResponse.SalesSummary(totalRevenue, totalSales, totalRefunds, totalRefundCount, totalItemsSold);
|
||||
}
|
||||
|
||||
private DashboardResponse.InventorySummary calculateInventorySummary() {
|
||||
@@ -93,6 +107,9 @@ public class AnalyticsService {
|
||||
Map<Long, DashboardResponse.TopProduct> productSalesMap = new HashMap<>();
|
||||
|
||||
for (Sale sale : sales) {
|
||||
if (sale.getIsRefund()) {
|
||||
continue;
|
||||
}
|
||||
for (var item : sale.getItems()) {
|
||||
Long productId = item.getProduct().getProdId();
|
||||
String productName = item.getProduct().getProdName();
|
||||
@@ -128,6 +145,9 @@ public class AnalyticsService {
|
||||
}
|
||||
|
||||
for (Sale sale : sales) {
|
||||
if (sale.getIsRefund()) {
|
||||
continue;
|
||||
}
|
||||
LocalDate saleDate = sale.getSaleDate().toLocalDate();
|
||||
if (dailySalesMap.containsKey(saleDate)) {
|
||||
DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate);
|
||||
@@ -138,4 +158,50 @@ public class AnalyticsService {
|
||||
|
||||
return new ArrayList<>(dailySalesMap.values());
|
||||
}
|
||||
|
||||
private List<DashboardResponse.PaymentMethodData> calculatePaymentMethods(List<Sale> sales) {
|
||||
return sales.stream()
|
||||
.filter(sale -> !sale.getIsRefund())
|
||||
.collect(Collectors.groupingBy(
|
||||
sale -> sale.getPaymentMethod() == null ? "Unknown" : sale.getPaymentMethod(),
|
||||
TreeMap::new,
|
||||
Collectors.counting()))
|
||||
.entrySet().stream()
|
||||
.map(entry -> new DashboardResponse.PaymentMethodData(entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private List<DashboardResponse.EmployeePerformanceData> calculateEmployeePerformance(List<Sale> sales, User user) {
|
||||
Map<String, BigDecimal> employeeRevenue = new TreeMap<>();
|
||||
|
||||
for (Sale sale : sales) {
|
||||
if (sale.getIsRefund()) {
|
||||
continue;
|
||||
}
|
||||
String employeeName = sale.getEmployee().getFirstName() + " " + sale.getEmployee().getLastName();
|
||||
employeeRevenue.merge(employeeName, sale.getTotalAmount(), BigDecimal::add);
|
||||
}
|
||||
|
||||
if (user.getRole() == User.Role.STAFF && employeeRevenue.isEmpty()) {
|
||||
Employee employee = employeeRepository.findByUserId(user.getId()).orElse(null);
|
||||
if (employee != null) {
|
||||
String employeeName = employee.getFirstName() + " " + employee.getLastName();
|
||||
employeeRevenue.put(employeeName, BigDecimal.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
return employeeRevenue.entrySet().stream()
|
||||
.map(entry -> new DashboardResponse.EmployeePerformanceData(entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private boolean includeSaleForUser(Sale sale, User user) {
|
||||
if (user.getRole() == User.Role.ADMIN) {
|
||||
return true;
|
||||
}
|
||||
if (user.getRole() == User.Role.STAFF) {
|
||||
return sale.getEmployee() != null && sale.getEmployee().getUserId() != null && sale.getEmployee().getUserId().equals(user.getId());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user