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 {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
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 {
|
android {
|
||||||
namespace = "com.example.petstoremobile"
|
namespace = "com.example.petstoremobile"
|
||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
@@ -14,6 +30,13 @@ android {
|
|||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
buildConfigField("String", "EMULATOR_BACKEND_URL", quoted(emulatorBackendUrl))
|
||||||
|
buildConfigField("String", "DEVICE_BACKEND_URL", quoted(deviceBackendUrl))
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
buildConfig = true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -39,7 +62,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
implementation("com.squareup.retrofit2:retrofit:2.9.0")
|
||||||
implementation("com.squareup.retrofit2:converter-gson: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.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
|
||||||
implementation("com.google.android.material:material:1.11.0")
|
implementation("com.google.android.material:material:1.11.0")
|
||||||
@@ -62,4 +85,4 @@ dependencies {
|
|||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.ext.junit)
|
androidTestImplementation(libs.ext.junit)
|
||||||
androidTestImplementation(libs.espresso.core)
|
androidTestImplementation(libs.espresso.core)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ public class HomeActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_home);
|
setContentView(R.layout.activity_home);
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
EdgeToEdge.enable(this);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Check if user is already logged in
|
// Check if user is already logged in
|
||||||
@@ -52,7 +53,6 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EdgeToEdge.enable(this);
|
|
||||||
setContentView(R.layout.activity_main);
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
@@ -132,15 +132,43 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(MainActivity.this, "Login failed", Toast.LENGTH_SHORT).show();
|
String errorMessage;
|
||||||
tvLoginStatus.setText("Login failed");
|
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
|
@Override
|
||||||
public void onFailure(Call<AuthDTO.LoginResponse> call, Throwable t) {
|
public void onFailure(Call<AuthDTO.LoginResponse> call, Throwable t) {
|
||||||
Toast.makeText(MainActivity.this, "Login failed", Toast.LENGTH_SHORT).show();
|
Log.e("MainActivity", "Login request failed", t);
|
||||||
tvLoginStatus.setText("Login failed");
|
|
||||||
|
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;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.view.LayoutInflater;
|
import android.view.*;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.models.Adoption;
|
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.AdoptionViewHolder> {
|
public class AdoptionAdapter extends RecyclerView.Adapter<AdoptionAdapter.AdoptionViewHolder> {
|
||||||
|
|
||||||
private List<Adoption> adoptionList;
|
private List<AdoptionDTO> adoptionList;
|
||||||
private OnAdoptionClickListener adoptionClickListener;
|
private OnAdoptionClickListener listener;
|
||||||
|
|
||||||
// Interface for adoption click on recycler view
|
|
||||||
public interface OnAdoptionClickListener {
|
public interface OnAdoptionClickListener {
|
||||||
void onAdoptionClick(int position);
|
void onAdoptionClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
public AdoptionAdapter(List<AdoptionDTO> adoptionList, OnAdoptionClickListener listener) {
|
||||||
public AdoptionAdapter(List<Adoption> adoptionList, OnAdoptionClickListener adoptionClickListener) {
|
|
||||||
this.adoptionList = adoptionList;
|
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 {
|
public static class AdoptionViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView tvAdopterName, tvPetName, tvAdoptionDate, tvAdoptionStatus;
|
TextView tvCustomerName, tvPetName, tvDate, tvFee, tvStatus;
|
||||||
|
|
||||||
public AdoptionViewHolder(@NonNull View v) {
|
public AdoptionViewHolder(@NonNull View v) {
|
||||||
super(v);
|
super(v);
|
||||||
tvAdopterName = v.findViewById(R.id.tvAdopterName);
|
tvCustomerName = v.findViewById(R.id.tvAdoptionCustomerName);
|
||||||
tvPetName = v.findViewById(R.id.tvAdoptionPetName);
|
tvPetName = v.findViewById(R.id.tvAdoptionPetName);
|
||||||
tvAdoptionDate = v.findViewById(R.id.tvAdoptionDate);
|
tvDate = v.findViewById(R.id.tvAdoptionDate);
|
||||||
tvAdoptionStatus = v.findViewById(R.id.tvAdoptionStatus);
|
tvFee = v.findViewById(R.id.tvAdoptionFee);
|
||||||
|
tvStatus = v.findViewById(R.id.tvAdoptionStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new row view
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public AdoptionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
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);
|
return new AdoptionViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate the row with adoption data
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull AdoptionViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull AdoptionViewHolder holder, int position) {
|
||||||
Adoption adoption = adoptionList.get(position);
|
AdoptionDTO a = adoptionList.get(position);
|
||||||
|
|
||||||
holder.tvAdopterName.setText(adoption.getAdopterName());
|
holder.tvCustomerName.setText(a.getCustomerName() != null ? a.getCustomerName() : "");
|
||||||
holder.tvPetName.setText("Pet: " + adoption.getPetName());
|
holder.tvPetName.setText("Pet: " + (a.getPetName() != null ? a.getPetName() : ""));
|
||||||
holder.tvAdoptionDate.setText("Date: " + adoption.getAdoptionDate());
|
holder.tvDate.setText("Date: " + (a.getAdoptionDate() != null ? a.getAdoptionDate() : ""));
|
||||||
holder.tvAdoptionStatus.setText(adoption.getStatus());
|
holder.tvFee.setText(a.getAdoptionFee() != null ? "$" + a.getAdoptionFee() : "");
|
||||||
|
|
||||||
// Set the status color depending on adoption status
|
String status = a.getAdoptionStatus() != null ? a.getAdoptionStatus() : "";
|
||||||
if (adoption.getStatus().equals("Approved")) {
|
holder.tvStatus.setText(status);
|
||||||
holder.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
|
||||||
} else if (adoption.getStatus().equals("Pending")) {
|
switch (status) {
|
||||||
holder.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#FF9800"));
|
case "Approved":
|
||||||
} else {
|
holder.tvStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
||||||
holder.tvAdoptionStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
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 -> listener.onAdoptionClick(position));
|
||||||
holder.itemView.setOnClickListener(v -> adoptionClickListener.onAdoptionClick(position));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() { return adoptionList.size(); }
|
||||||
return adoptionList.size();
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -10,26 +8,24 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.models.Appointment;
|
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.AppointmentViewHolder> {
|
public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.AppointmentViewHolder> {
|
||||||
|
|
||||||
private List<Appointment> appointmentList;
|
private List<AppointmentDTO> appointmentList;
|
||||||
private OnAppointmentClickListener appointmentClickListener;
|
private OnAppointmentClickListener appointmentClickListener;
|
||||||
|
|
||||||
// Interface for appointment click on recycler view
|
|
||||||
public interface OnAppointmentClickListener {
|
public interface OnAppointmentClickListener {
|
||||||
void onAppointmentClick(int position);
|
void onAppointmentClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
public AppointmentAdapter(List<AppointmentDTO> appointmentList,
|
||||||
public AppointmentAdapter(List<Appointment> appointmentList, OnAppointmentClickListener appointmentClickListener) {
|
OnAppointmentClickListener appointmentClickListener) {
|
||||||
this.appointmentList = appointmentList;
|
this.appointmentList = appointmentList;
|
||||||
this.appointmentClickListener = appointmentClickListener;
|
this.appointmentClickListener = appointmentClickListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the controls of each row in recycler view
|
|
||||||
public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
|
public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView tvCustomerName, tvPetName, tvServiceType, tvDateTime, tvAppointmentStatus;
|
TextView tvCustomerName, tvPetName, tvServiceType, tvDateTime, tvAppointmentStatus;
|
||||||
|
|
||||||
@@ -43,7 +39,6 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new row view
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
@@ -51,31 +46,34 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
|||||||
return new AppointmentViewHolder(v);
|
return new AppointmentViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate the row with appointment data
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int position) {
|
||||||
Appointment appointment = appointmentList.get(position);
|
AppointmentDTO a = appointmentList.get(position);
|
||||||
|
|
||||||
holder.tvCustomerName.setText(appointment.getCustomerName());
|
holder.tvCustomerName.setText(a.getCustomerName() != null ? a.getCustomerName() : "");
|
||||||
holder.tvPetName.setText("Pet: " + appointment.getPetName());
|
holder.tvPetName.setText("Pet: " + (a.getPetName() != null ? a.getPetName() : ""));
|
||||||
holder.tvServiceType.setText(appointment.getServiceType());
|
holder.tvServiceType.setText(a.getServiceType() != null ? a.getServiceType() : "");
|
||||||
holder.tvDateTime.setText(appointment.getAppointmentDate() + " at " + appointment.getAppointmentTime());
|
holder.tvDateTime.setText((a.getAppointmentDate() != null ? a.getAppointmentDate() : "") +
|
||||||
holder.tvAppointmentStatus.setText(appointment.getStatus());
|
" at " + (a.getAppointmentTime() != null ? a.getAppointmentTime() : ""));
|
||||||
|
|
||||||
// Set the status color depending on appointment status
|
String status = a.getStatus() != null ? a.getStatus() : "";
|
||||||
switch (appointment.getStatus()) {
|
holder.tvAppointmentStatus.setText(status);
|
||||||
case "Confirmed":
|
|
||||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
switch (status) {
|
||||||
|
case "Booked":
|
||||||
|
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#2196F3")); // blue
|
||||||
break;
|
break;
|
||||||
case "Pending":
|
case "Completed":
|
||||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#FF9800"));
|
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#4CAF50")); // green
|
||||||
|
break;
|
||||||
|
case "Cancelled":
|
||||||
|
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#F44336")); // red
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#9E9E9E")); // gray
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a row is clicked, open the detail view
|
|
||||||
holder.itemView.setOnClickListener(v -> appointmentClickListener.onAppointmentClick(position));
|
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;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.models.Inventory;
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> {
|
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> {
|
||||||
|
|
||||||
private List<Inventory> inventoryList;
|
private final List<InventoryDTO> inventoryList;
|
||||||
private OnInventoryClickListener inventoryClickListener;
|
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 {
|
public interface OnInventoryClickListener {
|
||||||
void onInventoryClick(int position);
|
void onInventoryClick(int position);
|
||||||
|
|
||||||
|
void onSelectionChanged(int selectedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
|
||||||
public InventoryAdapter(List<Inventory> inventoryList, OnInventoryClickListener inventoryClickListener) {
|
|
||||||
this.inventoryList = inventoryList;
|
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 {
|
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) {
|
public InventoryViewHolder(@NonNull View v) {
|
||||||
super(v);
|
super(v);
|
||||||
tvItemName = v.findViewById(R.id.tvItemName);
|
tvInventoryId = v.findViewById(R.id.tvInventoryId);
|
||||||
tvCategory = v.findViewById(R.id.tvCategory);
|
tvProdId = v.findViewById(R.id.tvProdId);
|
||||||
|
tvProductName = v.findViewById(R.id.tvProductName);
|
||||||
tvQuantity = v.findViewById(R.id.tvQuantity);
|
tvQuantity = v.findViewById(R.id.tvQuantity);
|
||||||
tvUnitPrice = v.findViewById(R.id.tvUnitPrice);
|
checkBox = v.findViewById(R.id.cbSelectInventory);
|
||||||
tvSupplier = v.findViewById(R.id.tvInvSupplier);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new row view
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
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);
|
return new InventoryViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate the row with inventory data
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int position) {
|
||||||
Inventory inventory = inventoryList.get(position);
|
InventoryDTO inv = inventoryList.get(position);
|
||||||
|
|
||||||
holder.tvItemName.setText(inventory.getItemName());
|
// Column: Inventory ID
|
||||||
holder.tvCategory.setText(inventory.getCategory());
|
String invIdStr = inv.getInventoryId() != null ? String.valueOf(inv.getInventoryId()) : "—";
|
||||||
holder.tvQuantity.setText("Qty: " + inventory.getQuantity());
|
holder.tvInventoryId.setText("Inv ID: " + invIdStr);
|
||||||
holder.tvUnitPrice.setText("$" + String.format("%.2f", inventory.getUnitPrice()));
|
|
||||||
holder.tvSupplier.setText("Supplier: " + inventory.getSupplier());
|
|
||||||
|
|
||||||
// Highlight low stock items in red
|
// Column: Product ID
|
||||||
if (inventory.getQuantity() <= 5) {
|
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"));
|
holder.tvQuantity.setTextColor(Color.parseColor("#F44336"));
|
||||||
} else {
|
} else {
|
||||||
holder.tvQuantity.setTextColor(Color.parseColor("#4CAF50"));
|
holder.tvQuantity.setTextColor(Color.parseColor("#4CAF50"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a row is clicked, open the detail view
|
// Bulk delete selection mode
|
||||||
holder.itemView.setOnClickListener(v -> inventoryClickListener.onInventoryClick(position));
|
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
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
return inventoryList.size();
|
return inventoryList.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,16 @@ import android.graphics.Color;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.api.PetApi;
|
||||||
|
import com.example.petstoremobile.api.RetrofitClient;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
import java.util.List;
|
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
|
// Get the controls of each row in recycler view
|
||||||
public static class PetViewHolder extends RecyclerView.ViewHolder {
|
public static class PetViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView tvPetName, tvPetSpeciesBreed, tvPetAge, tvPetPrice, tvPetStatus;
|
TextView tvPetName, tvPetSpeciesBreed, tvPetAge, tvPetPrice, tvPetStatus;
|
||||||
|
ImageView ivPetProfile;
|
||||||
|
|
||||||
public PetViewHolder(@NonNull View v) {
|
public PetViewHolder(@NonNull View v) {
|
||||||
super(v);
|
super(v);
|
||||||
@@ -38,6 +45,7 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
|||||||
tvPetAge = v.findViewById(R.id.tvPetAge);
|
tvPetAge = v.findViewById(R.id.tvPetAge);
|
||||||
tvPetPrice = v.findViewById(R.id.tvPetPrice);
|
tvPetPrice = v.findViewById(R.id.tvPetPrice);
|
||||||
tvPetStatus = v.findViewById(R.id.tvPetStatus);
|
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"));
|
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
|
//when a row is clicked, open the detail view
|
||||||
holder.itemView.setOnClickListener(v -> petClickListener.onPetClick(position));
|
holder.itemView.setOnClickListener(v -> petClickListener.onPetClick(position));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +1,57 @@
|
|||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
|
import android.view.*;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.models.Product;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductViewHolder> {
|
public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductViewHolder> {
|
||||||
|
|
||||||
private List<Product> productList;
|
private List<ProductDTO> productList;
|
||||||
private OnProductClickListener productClickListener;
|
private OnProductClickListener listener;
|
||||||
|
|
||||||
// Interface for product click on recycler view
|
|
||||||
public interface OnProductClickListener {
|
public interface OnProductClickListener {
|
||||||
void onProductClick(int position);
|
void onProductClick(int position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
public ProductAdapter(List<ProductDTO> productList, OnProductClickListener listener) {
|
||||||
public ProductAdapter(List<Product> productList, OnProductClickListener productClickListener) {
|
|
||||||
this.productList = productList;
|
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 {
|
public static class ProductViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView tvProductName, tvProductDesc, tvCategory, tvProductPrice, tvStockQuantity;
|
TextView tvName, tvCategory, tvDesc, tvPrice;
|
||||||
|
|
||||||
public ProductViewHolder(@NonNull View v) {
|
public ProductViewHolder(@NonNull View v) {
|
||||||
super(v);
|
super(v);
|
||||||
tvProductName = v.findViewById(R.id.tvProductName);
|
tvName = v.findViewById(R.id.tvProductName);
|
||||||
tvProductDesc = v.findViewById(R.id.tvProductDesc);
|
|
||||||
tvCategory = v.findViewById(R.id.tvProductCategory);
|
tvCategory = v.findViewById(R.id.tvProductCategory);
|
||||||
tvProductPrice = v.findViewById(R.id.tvProductPrice);
|
tvDesc = v.findViewById(R.id.tvProductDesc);
|
||||||
tvStockQuantity = v.findViewById(R.id.tvStockQuantity);
|
tvPrice = v.findViewById(R.id.tvProductPrice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new row view
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public ProductViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
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);
|
return new ProductViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate the row with product data
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ProductViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull ProductViewHolder holder, int position) {
|
||||||
Product product = productList.get(position);
|
ProductDTO p = productList.get(position);
|
||||||
|
holder.tvName.setText(p.getProdName() != null ? p.getProdName() : "");
|
||||||
holder.tvProductName.setText(product.getProductName());
|
holder.tvCategory.setText("Category: " + (p.getCategoryName() != null ? p.getCategoryName() : ""));
|
||||||
holder.tvProductDesc.setText(product.getProductDesc());
|
holder.tvDesc.setText(p.getProdDesc() != null ? p.getProdDesc() : "");
|
||||||
holder.tvCategory.setText(product.getCategory());
|
holder.tvPrice.setText(p.getProdPrice() != null ? "$" + p.getProdPrice() : "");
|
||||||
holder.tvProductPrice.setText("$" + String.format("%.2f", product.getProductPrice()));
|
holder.itemView.setOnClickListener(v -> listener.onProductClick(position));
|
||||||
holder.tvStockQuantity.setText("Stock: " + product.getStockQuantity());
|
|
||||||
|
|
||||||
// When a row is clicked, open the detail view
|
|
||||||
holder.itemView.setOnClickListener(v -> productClickListener.onProductClick(position));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemCount() {
|
public int getItemCount() { return productList.size(); }
|
||||||
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.content.Context;
|
||||||
import android.os.Build;
|
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.AuthApi;
|
||||||
import com.example.petstoremobile.api.auth.AuthInterceptor;
|
import com.example.petstoremobile.api.auth.AuthInterceptor;
|
||||||
|
|
||||||
@@ -11,24 +13,32 @@ import okhttp3.logging.HttpLoggingInterceptor;
|
|||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
//Retrofit client Used for API calls
|
//Retrofit client Used for API calls
|
||||||
public class RetrofitClient {
|
public class RetrofitClient {
|
||||||
|
private static final String TAG = "RetrofitClient";
|
||||||
public static final String BASE_URL = getBaseUrl();
|
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
|
// Helper function to determine BASE_URL based on whether we are testing on an emulator or a real device
|
||||||
private static String getBaseUrl() {
|
private static String getBaseUrl() {
|
||||||
if (Build.FINGERPRINT.contains("generic")
|
String url = isEmulator() ? BuildConfig.EMULATOR_BACKEND_URL : BuildConfig.DEVICE_BACKEND_URL;
|
||||||
|| Build.FINGERPRINT.contains("unknown")
|
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("google_sdk")
|
||||||
|| Build.MODEL.contains("Emulator")
|
|| Build.MODEL.contains("Emulator")
|
||||||
|| Build.MODEL.contains("Android SDK built for x86")
|
|| Build.MODEL.contains("Android SDK built for x86")
|
||||||
|| Build.MANUFACTURER.contains("Genymotion")
|
|| Build.MANUFACTURER.contains("Genymotion")
|
||||||
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"))
|
|| Build.HARDWARE.contains("goldfish")
|
||||||
|| "google_sdk".equals(Build.PRODUCT)) {
|
|| Build.HARDWARE.contains("ranchu")
|
||||||
return "http://10.0.2.2:8080/"; //emulator testing
|
|| Build.PRODUCT.contains("sdk")
|
||||||
} else {
|
|| Build.PRODUCT.contains("sdk_gphone")
|
||||||
return "http://10.0.0.200:8080/"; //Hardware testing
|
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Retrofit retrofit = null;
|
private static Retrofit retrofit = null;
|
||||||
@@ -41,6 +51,9 @@ public class RetrofitClient {
|
|||||||
OkHttpClient client = new OkHttpClient.Builder()
|
OkHttpClient client = new OkHttpClient.Builder()
|
||||||
.addInterceptor(interceptor)
|
.addInterceptor(interceptor)
|
||||||
.addInterceptor(new AuthInterceptor(context))
|
.addInterceptor(new AuthInterceptor(context))
|
||||||
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(30, TimeUnit.SECONDS)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
//build the retrofit object with all needed properties
|
//build the retrofit object with all needed properties
|
||||||
@@ -66,6 +79,34 @@ public class RetrofitClient {
|
|||||||
return getClient(context).create(SupplierApi.class);
|
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) {
|
public static AuthApi getAuthApi(Context context) {
|
||||||
return getClient(context).create(AuthApi.class);
|
return getClient(context).create(AuthApi.class);
|
||||||
}
|
}
|
||||||
@@ -82,4 +123,12 @@ public class RetrofitClient {
|
|||||||
return getClient(context).create(MessageApi.class);
|
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;
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
|
|
||||||
public class CustomerDTO {
|
public class CustomerDTO {
|
||||||
@SerializedName("customerId")
|
|
||||||
private Long customerId;
|
private Long customerId;
|
||||||
|
|
||||||
private String firstName;
|
private String firstName;
|
||||||
private String lastName;
|
private String lastName;
|
||||||
private String email;
|
private String email;
|
||||||
private String phone;
|
private String createdAt;
|
||||||
|
private String updatedAt;
|
||||||
public CustomerDTO() {}
|
|
||||||
|
|
||||||
public Long getCustomerId() {
|
public Long getCustomerId() {
|
||||||
return customerId;
|
return customerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCustomerId(Long customerId) {
|
|
||||||
this.customerId = customerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFirstName() {
|
public String getFirstName() {
|
||||||
return firstName;
|
return firstName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFirstName(String firstName) {
|
|
||||||
this.firstName = firstName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLastName() {
|
public String getLastName() {
|
||||||
return lastName;
|
return lastName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLastName(String lastName) {
|
|
||||||
this.lastName = lastName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEmail() {
|
public String getEmail() {
|
||||||
return email;
|
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() {
|
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.AppointmentFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.ProductFragment;
|
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
|
//The Fragment for the displaying the list of entities to be viewed
|
||||||
public class ListFragment extends Fragment {
|
public class ListFragment extends Fragment {
|
||||||
@@ -32,7 +35,7 @@ public class ListFragment extends Fragment {
|
|||||||
|
|
||||||
// Adoptions, Appointments, Inventory, Products
|
// Adoptions, Appointments, Inventory, Products
|
||||||
|
|
||||||
private LinearLayout drawerAdoptions, drawerAppointments, drawerInventory, drawerProducts;
|
private LinearLayout drawerAdoptions, drawerAppointments, drawerInventory, drawerProducts, drawerProductSupplier, drawerPurchaseOrderView, drawerSale;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,6 +52,10 @@ public class ListFragment extends Fragment {
|
|||||||
drawerAppointments = view.findViewById(R.id.drawerAppointments);
|
drawerAppointments = view.findViewById(R.id.drawerAppointments);
|
||||||
drawerInventory = view.findViewById(R.id.drawerInventory);
|
drawerInventory = view.findViewById(R.id.drawerInventory);
|
||||||
drawerProducts = view.findViewById(R.id.drawerProducts);
|
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
|
// Check user role and restrict access for STAFF
|
||||||
String role = TokenManager.getInstance(requireContext()).getRole();
|
String role = TokenManager.getInstance(requireContext()).getRole();
|
||||||
@@ -116,7 +123,7 @@ public class ListFragment extends Fragment {
|
|||||||
drawerLayout.closeDrawers();
|
drawerLayout.closeDrawers();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Appoinment
|
//Appointment
|
||||||
drawerAppointments.setOnClickListener(v -> {
|
drawerAppointments.setOnClickListener(v -> {
|
||||||
loadFragment(new AppointmentFragment());
|
loadFragment(new AppointmentFragment());
|
||||||
drawerLayout.closeDrawers();
|
drawerLayout.closeDrawers();
|
||||||
@@ -134,6 +141,27 @@ public class ListFragment extends Fragment {
|
|||||||
drawerLayout.closeDrawers();
|
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;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,36 +1,33 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
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.os.Bundle;
|
||||||
|
import android.text.*;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
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.R;
|
||||||
import com.example.petstoremobile.adapters.AdoptionAdapter;
|
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.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.AdoptionDetailFragment;
|
import com.example.petstoremobile.fragments.listfragments.detailfragments.AdoptionDetailFragment;
|
||||||
import com.example.petstoremobile.models.Adoption;
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
import retrofit2.*;
|
||||||
|
|
||||||
public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener {
|
public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdoptionClickListener {
|
||||||
|
|
||||||
private List<Adoption> adoptionList = new ArrayList<>();
|
private List<AdoptionDTO> adoptionList = new ArrayList<>();
|
||||||
private List<Adoption> filteredList = new ArrayList<>();
|
private List<AdoptionDTO> filteredList = new ArrayList<>();
|
||||||
private AdoptionAdapter adapter;
|
private AdoptionAdapter adapter;
|
||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private AdoptionApi api;
|
||||||
|
private SwipeRefreshLayout swipeRefresh;
|
||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
private ImageButton hamburger;
|
private ImageButton hamburger;
|
||||||
|
|
||||||
@@ -39,51 +36,58 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_adoption, container, false);
|
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);
|
setupRecyclerView(view);
|
||||||
setupSearch(view);
|
setupSearch(view);
|
||||||
setupSwipeRefresh(view);
|
setupSwipeRefresh(view);
|
||||||
|
loadAdoptions();
|
||||||
|
|
||||||
FloatingActionButton fabAddAdoption = view.findViewById(R.id.fabAddAdoption);
|
FloatingActionButton fab = view.findViewById(R.id.fabAddAdoption);
|
||||||
fabAddAdoption.setOnClickListener(v -> openAdoptionDetails(-1));
|
fab.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
//Make the hamburger button open the drawer from listFragment
|
|
||||||
hamburger.setOnClickListener(v -> {
|
hamburger.setOnClickListener(v -> {
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
//if list fragment is found then use its helper function to open the drawer
|
if (lf != null) lf.openDrawer();
|
||||||
if (listFragment != null) {
|
|
||||||
listFragment.openDrawer();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return view;
|
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) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchAdoption);
|
etSearch = view.findViewById(R.id.etSearchAdoption);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
public void beforeTextChanged(CharSequence s, int a, int b, int c) {}
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
public void afterTextChanged(Editable s) {}
|
||||||
filterAdoptions(s.toString());
|
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();
|
filteredList.clear();
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
filteredList.addAll(adoptionList);
|
filteredList.addAll(adoptionList);
|
||||||
} else {
|
} else {
|
||||||
String lower = query.toLowerCase();
|
String lower = query.toLowerCase();
|
||||||
for (Adoption a : adoptionList) {
|
for (AdoptionDTO a : adoptionList) {
|
||||||
if (a.getAdopterName().toLowerCase().contains(lower)
|
if ((a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lower))
|
||||||
|| a.getPetName().toLowerCase().contains(lower)
|
|| (a.getPetName() != null && a.getPetName().toLowerCase().contains(lower))
|
||||||
|| a.getStatus().toLowerCase().contains(lower)) {
|
|| (a.getAdoptionStatus() != null && a.getAdoptionStatus().toLowerCase().contains(lower))) {
|
||||||
filteredList.add(a);
|
filteredList.add(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,73 +95,47 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSwipeRefresh(View view) {
|
private void loadAdoptions() {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshAdoption);
|
if (swipeRefresh != null) swipeRefresh.setRefreshing(true);
|
||||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
api.getAllAdoptions(0, 100).enqueue(new Callback<PageResponse<AdoptionDTO>>() {
|
||||||
loadAdoptionData(); // TODO: Replace with actual API call
|
public void onResponse(Call<PageResponse<AdoptionDTO>> c,
|
||||||
filterAdoptions(etSearch.getText().toString());
|
Response<PageResponse<AdoptionDTO>> r) {
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
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) {
|
private void openDetail(int position) {
|
||||||
AdoptionDetailFragment detailFragment = new AdoptionDetailFragment();
|
AdoptionDetailFragment detail = new AdoptionDetailFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putInt("position", position);
|
|
||||||
|
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
Adoption adoption = filteredList.get(position);
|
AdoptionDTO a = filteredList.get(position);
|
||||||
int realPosition = adoptionList.indexOf(adoption);
|
args.putLong("adoptionId", a.getAdoptionId());
|
||||||
args.putInt("position", realPosition);
|
args.putLong("petId", a.getPetId() != null ? a.getPetId() : -1);
|
||||||
args.putInt("adoptionId", adoption.getAdoptionId());
|
args.putLong("customerId", a.getCustomerId() != null ? a.getCustomerId() : -1);
|
||||||
args.putString("adopterName", adoption.getAdopterName());
|
args.putString("adoptionDate", a.getAdoptionDate());
|
||||||
args.putString("adopterEmail", adoption.getAdopterEmail());
|
args.putString("adoptionStatus", a.getAdoptionStatus());
|
||||||
args.putString("adopterPhone", adoption.getAdopterPhone());
|
|
||||||
args.putString("petName", adoption.getPetName());
|
|
||||||
args.putString("adoptionDate", adoption.getAdoptionDate());
|
|
||||||
args.putString("status", adoption.getStatus());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detailFragment.setArguments(args);
|
detail.setArguments(args);
|
||||||
detailFragment.setAdoptionFragment(this);
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
|
if (lf != null) lf.loadFragment(detail);
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAdoptionClick(int position) {
|
public void onAdoptionClick(int position) { openDetail(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +1,52 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
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 android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
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.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.AppointmentDetailFragment;
|
import com.example.petstoremobile.fragments.listfragments.detailfragments.AppointmentDetailFragment;
|
||||||
import com.example.petstoremobile.models.Appointment;
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener {
|
public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener {
|
||||||
|
|
||||||
private List<Appointment> appointmentList = new ArrayList<>(); // full data list
|
private List<AppointmentDTO> appointmentList = new ArrayList<>();
|
||||||
private List<Appointment> filteredList = new ArrayList<>(); // filtered display list
|
private List<AppointmentDTO> filteredList = new ArrayList<>();
|
||||||
|
private List<PetDTO> petList = new ArrayList<>();
|
||||||
|
private List<ServiceDTO> serviceList = new ArrayList<>();
|
||||||
|
|
||||||
private AppointmentAdapter adapter;
|
private AppointmentAdapter adapter;
|
||||||
|
private AppointmentApi api;
|
||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
private ImageButton hamburger;
|
private ImageButton hamburger;
|
||||||
@@ -39,51 +56,57 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_appointment, container, false);
|
View view = inflater.inflate(R.layout.fragment_appointment, container, false);
|
||||||
|
|
||||||
|
api = RetrofitClient.getAppointmentApi(requireContext());
|
||||||
hamburger = view.findViewById(R.id.btnHamburger);
|
hamburger = view.findViewById(R.id.btnHamburger);
|
||||||
|
|
||||||
loadAppointmentData(); // TODO: Replace with actual API call when backend is ready
|
|
||||||
setupRecyclerView(view);
|
setupRecyclerView(view);
|
||||||
setupSearch(view);
|
setupSearch(view);
|
||||||
setupSwipeRefresh(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 -> {
|
hamburger.setOnClickListener(v -> {
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
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();
|
listFragment.openDrawer();
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets up the search bar to filter appointments by customer name or service type
|
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchAppointment);
|
etSearch = view.findViewById(R.id.etSearchAppointment);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
@Override
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
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());
|
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) {
|
private void filterAppointments(String query) {
|
||||||
filteredList.clear();
|
filteredList.clear();
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
filteredList.addAll(appointmentList);
|
filteredList.addAll(appointmentList);
|
||||||
} else {
|
} else {
|
||||||
String lower = query.toLowerCase();
|
String lower = query.toLowerCase();
|
||||||
for (Appointment a : appointmentList) {
|
for (AppointmentDTO a : appointmentList) {
|
||||||
if (a.getCustomerName().toLowerCase().contains(lower)
|
if ((a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lower))
|
||||||
|| a.getServiceType().toLowerCase().contains(lower)
|
|| (a.getServiceType() != null && a.getServiceType().toLowerCase().contains(lower))
|
||||||
|| a.getPetName().toLowerCase().contains(lower)) {
|
|| (a.getPetName() != null && a.getPetName().toLowerCase().contains(lower))) {
|
||||||
filteredList.add(a);
|
filteredList.add(a);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,43 +114,33 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets up pull-to-refresh: reloads data when user swipes down
|
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshAppointment);
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshAppointment);
|
||||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
swipeRefreshLayout.setOnRefreshListener(this::loadAppointmentData);
|
||||||
loadAppointmentData(); // TODO: Replace with actual API call when backend is ready
|
|
||||||
filterAppointments(etSearch.getText().toString());
|
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openAppointmentDetails(int position) {
|
private void openAppointmentDetails(int position) {
|
||||||
AppointmentDetailFragment detailFragment = new AppointmentDetailFragment();
|
AppointmentDetailFragment detailFragment = new AppointmentDetailFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putInt("position", position);
|
|
||||||
|
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
Appointment appointment = filteredList.get(position);
|
AppointmentDTO a = filteredList.get(position);
|
||||||
// Find the real position in the full list for save/delete callbacks
|
args.putLong("appointmentId", a.getAppointmentId());
|
||||||
int realPosition = appointmentList.indexOf(appointment);
|
args.putString("appointmentDate", a.getAppointmentDate());
|
||||||
args.putInt("position", realPosition);
|
args.putString("appointmentTime", a.getAppointmentTime());
|
||||||
args.putInt("appointmentId", appointment.getAppointmentId());
|
args.putString("appointmentStatus", a.getAppointmentStatus());
|
||||||
args.putString("customerName", appointment.getCustomerName());
|
// IDs for pre-selecting spinners
|
||||||
args.putString("petName", appointment.getPetName());
|
if (a.getPetID() != null) args.putLong("petId", a.getPetID());
|
||||||
args.putString("serviceType", appointment.getServiceType());
|
if (a.getServiceId() != null) args.putLong("serviceId", a.getServiceId());
|
||||||
args.putString("appointmentDate", appointment.getAppointmentDate());
|
if (a.getCustomerId() != null) args.putLong("customerId", a.getCustomerId());
|
||||||
args.putString("appointmentTime", appointment.getAppointmentTime());
|
if (a.getStoreId() != null) args.putLong("storeId", a.getStoreId());
|
||||||
args.putString("status", appointment.getStatus());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detailFragment.setArguments(args);
|
detailFragment.setArguments(args);
|
||||||
detailFragment.setAppointmentFragment(this);
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
|
if (lf != null) lf.loadFragment(detailFragment);
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
|
||||||
if (listFragment != null) listFragment.loadFragment(detailFragment);
|
|
||||||
}
|
}
|
||||||
|
public void onAppointmentSaved(int position, AppointmentDTO appointment) {
|
||||||
public void onAppointmentSaved(int position, Appointment appointment) {
|
|
||||||
if (position == -1) {
|
if (position == -1) {
|
||||||
appointmentList.add(appointment);
|
appointmentList.add(appointment);
|
||||||
} else {
|
} else {
|
||||||
@@ -146,21 +159,100 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
openAppointmentDetails(position);
|
openAppointmentDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to load hardcoded sample data
|
|
||||||
// Replace with API call
|
|
||||||
private void loadAppointmentData() {
|
private void loadAppointmentData() {
|
||||||
appointmentList.clear();
|
if (swipeRefreshLayout != null)
|
||||||
appointmentList.add(new Appointment(1, "John Smith", "Buddy", "Grooming", "2026-03-10", "10:00 AM", "Confirmed"));
|
swipeRefreshLayout.setRefreshing(true);
|
||||||
appointmentList.add(new Appointment(2, "Jane Doe", "Luna", "Vet Checkup", "2026-03-11", "02:00 PM", "Pending"));
|
api.getAllAppointments(0, 100).enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
||||||
appointmentList.add(new Appointment(3, "Bob Lee", "Max", "Training", "2026-03-12", "11:00 AM", "Confirmed"));
|
@Override
|
||||||
appointmentList.add(new Appointment(4, "Alice Brown", "Milo", "Grooming", "2026-03-13", "03:00 PM", "Cancelled"));
|
public void onResponse(Call<PageResponse<AppointmentDTO>> call,
|
||||||
filteredList.clear();
|
Response<PageResponse<AppointmentDTO>> response) {
|
||||||
filteredList.addAll(appointmentList);
|
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) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewAppointments);
|
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.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,162 +1,379 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
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.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.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
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.R;
|
||||||
|
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||||
import com.example.petstoremobile.adapters.InventoryAdapter;
|
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.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.InventoryDetailFragment;
|
import com.example.petstoremobile.fragments.listfragments.detailfragments.InventoryDetailFragment;
|
||||||
import com.example.petstoremobile.models.Inventory;
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
||||||
|
|
||||||
private List<Inventory> inventoryList = new ArrayList<>();
|
private static final String TAG = "InventoryFragment";
|
||||||
private List<Inventory> filteredList = new ArrayList<>();
|
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 InventoryAdapter adapter;
|
||||||
|
private InventoryApi inventoryApi;
|
||||||
|
private CategoryApi categoryApi;
|
||||||
|
|
||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
|
private Spinner spinnerCategory;
|
||||||
private ImageButton hamburger;
|
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
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_inventory, container, false);
|
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);
|
setupRecyclerView(view);
|
||||||
setupSearch(view);
|
setupSearch(view);
|
||||||
setupSwipeRefresh(view);
|
setupSwipeRefresh(view);
|
||||||
|
loadCategories(); // loads categories then triggers loadInventory
|
||||||
|
loadInventory(true);
|
||||||
|
|
||||||
FloatingActionButton fabAddInventory = view.findViewById(R.id.fabAddInventory);
|
view.findViewById(R.id.fabAddInventory)
|
||||||
fabAddInventory.setOnClickListener(v -> openInventoryDetails(-1));
|
.setOnClickListener(v -> openDetail(null));
|
||||||
|
|
||||||
//Make the hamburger button open the drawer from listFragment
|
|
||||||
hamburger.setOnClickListener(v -> {
|
hamburger.setOnClickListener(v -> {
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
//if list fragment is found then use its helper function to open the drawer
|
if (lf != null)
|
||||||
if (listFragment != null) {
|
lf.openDrawer();
|
||||||
listFragment.openDrawer();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btnBulkDelete.setOnClickListener(v -> confirmBulkDelete());
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters inventory list by item name or category
|
// Categories
|
||||||
private void setupSearch(View view) {
|
private void loadCategories() {
|
||||||
etSearch = view.findViewById(R.id.etSearchInventory);
|
categoryApi.getAllCategories(0, 100).enqueue(new Callback<PageResponse<CategoryDTO>>() {
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
@Override
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
public void onResponse(Call<PageResponse<CategoryDTO>> call,
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
Response<PageResponse<CategoryDTO>> response) {
|
||||||
filterInventory(s.toString());
|
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) {
|
private void setupCategorySpinner() {
|
||||||
filteredList.clear();
|
// First item is always "All Categories"
|
||||||
if (query.isEmpty()) {
|
List<String> categoryNames = new ArrayList<>();
|
||||||
filteredList.addAll(inventoryList);
|
categoryNames.add("All Categories");
|
||||||
} else {
|
for (CategoryDTO c : categoryList) {
|
||||||
String lower = query.toLowerCase();
|
categoryNames.add(c.getCategoryName());
|
||||||
for (Inventory i : inventoryList) {
|
}
|
||||||
if (i.getItemName().toLowerCase().contains(lower)
|
|
||||||
|| i.getCategory().toLowerCase().contains(lower)
|
BlackTextArrayAdapter<String> spinnerAdapter = new BlackTextArrayAdapter<>(
|
||||||
|| i.getSupplier().toLowerCase().contains(lower)) {
|
requireContext(),
|
||||||
filteredList.add(i);
|
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) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshInventory);
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshInventory);
|
||||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
swipeRefreshLayout.setOnRefreshListener(() -> loadInventory(true));
|
||||||
loadInventoryData(); // TODO: Replace with actual API call
|
|
||||||
filterInventory(etSearch.getText().toString());
|
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openInventoryDetails(int position) {
|
// Load inventory
|
||||||
InventoryDetailFragment detailFragment = new InventoryDetailFragment();
|
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();
|
Bundle args = new Bundle();
|
||||||
args.putInt("position", position);
|
|
||||||
|
|
||||||
if (position != -1) {
|
if (inv != null) {
|
||||||
Inventory inventory = filteredList.get(position);
|
args.putLong("inventoryId", inv.getInventoryId());
|
||||||
int realPosition = inventoryList.indexOf(inventory);
|
args.putLong("prodId", inv.getProdId() != null ? inv.getProdId() : -1);
|
||||||
args.putInt("position", realPosition);
|
args.putString("productName", inv.getProductName());
|
||||||
args.putInt("inventoryId", inventory.getInventoryId());
|
args.putString("categoryName", inv.getCategoryName());
|
||||||
args.putString("itemName", inventory.getItemName());
|
args.putInt("quantity", inv.getQuantity() != null ? inv.getQuantity() : 0);
|
||||||
args.putString("category", inventory.getCategory());
|
|
||||||
args.putInt("quantity", inventory.getQuantity());
|
|
||||||
args.putDouble("unitPrice", inventory.getUnitPrice());
|
|
||||||
args.putString("supplier", inventory.getSupplier());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detailFragment.setArguments(args);
|
detail.setArguments(args);
|
||||||
detailFragment.setInventoryFragment(this);
|
detail.setInventoryFragment(this);
|
||||||
|
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
if (listFragment != null) listFragment.loadFragment(detailFragment);
|
if (lf != null)
|
||||||
|
lf.loadFragment(detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onInventorySaved(int position, Inventory inventory) {
|
public void onInventoryChanged() {
|
||||||
if (position == -1) {
|
loadInventory(true);
|
||||||
inventoryList.add(inventory);
|
|
||||||
} else {
|
|
||||||
inventoryList.set(position, inventory);
|
|
||||||
}
|
|
||||||
filterInventory(etSearch.getText().toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onInventoryDeleted(int position) {
|
// Adapter callbacks
|
||||||
inventoryList.remove(position);
|
|
||||||
filterInventory(etSearch.getText().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInventoryClick(int position) {
|
public void onInventoryClick(int position) {
|
||||||
openInventoryDetails(position);
|
if (position >= 0 && position < inventoryList.size()) {
|
||||||
|
openDetail(inventoryList.get(position));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadInventoryData() {
|
@Override
|
||||||
inventoryList.clear();
|
public void onSelectionChanged(int selectedCount) {
|
||||||
inventoryList.add(new Inventory(1, "Dog Food - Large", "Food", 50, 25.99, "PetSupplies Co."));
|
if (selectedCount > 0) {
|
||||||
inventoryList.add(new Inventory(2, "Cat Litter", "Hygiene", 30, 12.99, "CleanPaws Ltd."));
|
btnBulkDelete.setVisibility(View.VISIBLE);
|
||||||
inventoryList.add(new Inventory(3, "Dog Leash", "Accessories", 4, 15.99, "PetGear Inc."));
|
tvSelectionCount.setVisibility(View.VISIBLE);
|
||||||
inventoryList.add(new Inventory(4, "Bird Cage - Medium", "Housing", 8, 79.99, "BirdWorld"));
|
tvSelectionCount.setText(selectedCount + " selected");
|
||||||
inventoryList.add(new Inventory(5, "Flea Treatment", "Medicine", 2, 34.99, "VetCare Supply"));
|
} else {
|
||||||
filteredList.clear();
|
hideBulkDeleteBar();
|
||||||
filteredList.addAll(inventoryList);
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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 android.widget.Toast;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||||
import com.example.petstoremobile.adapters.PetAdapter;
|
import com.example.petstoremobile.adapters.PetAdapter;
|
||||||
import com.example.petstoremobile.api.PetApi;
|
import com.example.petstoremobile.api.PetApi;
|
||||||
import com.example.petstoremobile.api.RetrofitClient;
|
import com.example.petstoremobile.api.RetrofitClient;
|
||||||
@@ -63,7 +64,6 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
setupSearch(view);
|
setupSearch(view);
|
||||||
setupStatusFilter(view);
|
setupStatusFilter(view);
|
||||||
setupSwipeRefresh(view);
|
setupSwipeRefresh(view);
|
||||||
loadPetData();
|
|
||||||
|
|
||||||
|
|
||||||
//Add button to opens the add dialog
|
//Add button to opens the add dialog
|
||||||
@@ -82,6 +82,12 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
loadPetData();
|
||||||
|
}
|
||||||
|
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchPet);
|
etSearch = view.findViewById(R.id.etSearchPet);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -97,7 +103,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
private void setupStatusFilter(View view) {
|
private void setupStatusFilter(View view) {
|
||||||
spinnerStatus = view.findViewById(R.id.spinnerStatus);
|
spinnerStatus = view.findViewById(R.id.spinnerStatus);
|
||||||
String[] statuses = {"All Statuses", "Available", "Adopted"};
|
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);
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
spinnerStatus.setAdapter(adapter);
|
spinnerStatus.setAdapter(adapter);
|
||||||
|
|
||||||
|
|||||||
@@ -1,88 +1,87 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
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.os.Bundle;
|
||||||
|
import android.text.*;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
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.R;
|
||||||
import com.example.petstoremobile.adapters.ProductAdapter;
|
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.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.ProductDetailFragment;
|
import com.example.petstoremobile.fragments.listfragments.detailfragments.ProductDetailFragment;
|
||||||
import com.example.petstoremobile.models.Product;
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
import retrofit2.*;
|
||||||
|
|
||||||
public class ProductFragment extends Fragment implements ProductAdapter.OnProductClickListener {
|
public class ProductFragment extends Fragment implements ProductAdapter.OnProductClickListener {
|
||||||
|
|
||||||
private List<Product> productList = new ArrayList<>();
|
private List<ProductDTO> productList = new ArrayList<>();
|
||||||
private List<Product> filteredList = new ArrayList<>();
|
private List<ProductDTO> filteredList = new ArrayList<>();
|
||||||
private ProductAdapter adapter;
|
private ProductAdapter adapter;
|
||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefresh;
|
||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
private ImageButton hamburger;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_product, container, false);
|
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);
|
setupRecyclerView(view);
|
||||||
setupSearch(view);
|
setupSearch(view);
|
||||||
setupSwipeRefresh(view);
|
setupSwipeRefresh(view);
|
||||||
|
loadProducts();
|
||||||
|
|
||||||
FloatingActionButton fabAddProduct = view.findViewById(R.id.fabAddProduct);
|
FloatingActionButton fab = view.findViewById(R.id.fabAddProduct);
|
||||||
fabAddProduct.setOnClickListener(v -> openProductDetails(-1));
|
fab.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
//Make the hamburger button open the drawer from listFragment
|
ImageButton hamburger = view.findViewById(R.id.btnHamburgerProduct);
|
||||||
hamburger.setOnClickListener(v -> {
|
hamburger.setOnClickListener(v -> {
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
//if list fragment is found then use its helper function to open the drawer
|
if (lf != null) lf.openDrawer();
|
||||||
if (listFragment != null) {
|
|
||||||
listFragment.openDrawer();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return view;
|
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) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchProduct);
|
etSearch = view.findViewById(R.id.etSearchProduct);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
public void beforeTextChanged(CharSequence s, int a, int b, int c) {}
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
public void afterTextChanged(Editable s) {}
|
||||||
filterProducts(s.toString());
|
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();
|
filteredList.clear();
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
filteredList.addAll(productList);
|
filteredList.addAll(productList);
|
||||||
} else {
|
} else {
|
||||||
String lower = query.toLowerCase();
|
String lower = query.toLowerCase();
|
||||||
for (Product p : productList) {
|
for (ProductDTO p : productList) {
|
||||||
if (p.getProductName().toLowerCase().contains(lower)
|
if ((p.getProdName() != null && p.getProdName().toLowerCase().contains(lower))
|
||||||
|| p.getCategory().toLowerCase().contains(lower)
|
|| (p.getCategoryName() != null && p.getCategoryName().toLowerCase().contains(lower))) {
|
||||||
|| p.getProductDesc().toLowerCase().contains(lower)) {
|
|
||||||
filteredList.add(p);
|
filteredList.add(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,73 +89,45 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSwipeRefresh(View view) {
|
private void loadProducts() {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshProduct);
|
if (swipeRefresh != null) swipeRefresh.setRefreshing(true);
|
||||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
RetrofitClient.getProductApi(requireContext()).getAllProducts(null, 0, 100)
|
||||||
loadProductData(); // TODO: Replace with actual API call
|
.enqueue(new Callback<PageResponse<ProductDTO>>() {
|
||||||
filterProducts(etSearch.getText().toString());
|
public void onResponse(Call<PageResponse<ProductDTO>> c,
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
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) {
|
private void openDetail(int position) {
|
||||||
ProductDetailFragment detailFragment = new ProductDetailFragment();
|
ProductDetailFragment detail = new ProductDetailFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putInt("position", position);
|
|
||||||
|
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
Product product = filteredList.get(position);
|
ProductDTO p = filteredList.get(position);
|
||||||
int realPosition = productList.indexOf(product);
|
args.putLong("prodId", p.getProdId());
|
||||||
args.putInt("position", realPosition);
|
args.putString("prodName", p.getProdName());
|
||||||
args.putInt("productId", product.getProductId());
|
args.putString("prodDesc", p.getProdDesc() != null ? p.getProdDesc() : "");
|
||||||
args.putString("productName", product.getProductName());
|
args.putString("prodPrice", p.getProdPrice() != null ? p.getProdPrice().toString() : "");
|
||||||
args.putString("productDesc", product.getProductDesc());
|
args.putLong("categoryId", p.getCategoryId() != null ? p.getCategoryId() : -1);
|
||||||
args.putString("category", product.getCategory());
|
|
||||||
args.putDouble("productPrice", product.getProductPrice());
|
|
||||||
args.putInt("stockQuantity", product.getStockQuantity());
|
|
||||||
}
|
}
|
||||||
|
detail.setArguments(args);
|
||||||
detailFragment.setArguments(args);
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
detailFragment.setProductFragment(this);
|
if (lf != null) lf.loadFragment(detail);
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProductClick(int position) {
|
public void onProductClick(int position) { openDetail(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
// Uses InputValidator for detailed field validation and ActivityLogger to log all changes.
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
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.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.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.AdoptionFragment;
|
import java.util.*;
|
||||||
import com.example.petstoremobile.models.Adoption;
|
import retrofit2.*;
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
|
||||||
|
|
||||||
public class AdoptionDetailFragment extends Fragment {
|
public class AdoptionDetailFragment extends Fragment {
|
||||||
|
|
||||||
private TextView tvMode, tvAdoptionId;
|
private TextView tvMode, tvAdoptionId;
|
||||||
private EditText etAdopterName, etAdopterEmail, etAdopterPhone, etPetName, etAdoptionDate;
|
private EditText etAdoptionDate;
|
||||||
private Spinner spinnerAdoptionStatus;
|
private Spinner spinnerPet, spinnerCustomer, spinnerStatus;
|
||||||
private Button btnSaveAdoption, btnDeleteAdoption, btnBack;
|
private Button btnSave, btnDelete, btnBack;
|
||||||
private int adoptionId;
|
|
||||||
private int position;
|
|
||||||
private boolean isEditing = false;
|
|
||||||
private AdoptionFragment adoptionFragment;
|
|
||||||
|
|
||||||
// Set the adoption fragment as parent so we refer back when save or delete is done
|
private long adoptionId = -1;
|
||||||
public void setAdoptionFragment(AdoptionFragment fragment) {
|
private boolean isEditing = false;
|
||||||
this.adoptionFragment = fragment;
|
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
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_adoption_detail, container, false);
|
View view = inflater.inflate(R.layout.fragment_adoption_detail, container, false);
|
||||||
|
|
||||||
initViews(view);
|
initViews(view);
|
||||||
setupSpinner();
|
setupSpinners();
|
||||||
|
setupDatePicker();
|
||||||
|
loadData();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
btnBack.setOnClickListener(v -> {
|
btnBack.setOnClickListener(v -> navigateBack());
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
btnSave.setOnClickListener(v -> saveAdoption());
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||||
});
|
|
||||||
btnSaveAdoption.setOnClickListener(v -> saveAdoption());
|
|
||||||
btnDeleteAdoption.setOnClickListener(v -> deleteAdoption());
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates all fields using InputValidator, then saves the adoption record
|
private void initViews(View v) {
|
||||||
private void saveAdoption() {
|
tvMode = v.findViewById(R.id.tvAdoptionMode);
|
||||||
if (!InputValidator.isNotEmpty(etAdopterName, "Adopter Name")) return;
|
tvAdoptionId = v.findViewById(R.id.tvAdoptionId);
|
||||||
if (!InputValidator.isValidEmail(etAdopterEmail)) return;
|
etAdoptionDate = v.findViewById(R.id.etAdoptionDate);
|
||||||
if (!InputValidator.isValidPhone(etAdopterPhone)) return;
|
spinnerPet = v.findViewById(R.id.spinnerAdoptionPet);
|
||||||
if (!InputValidator.isNotEmpty(etPetName, "Pet Name")) return;
|
spinnerCustomer= v.findViewById(R.id.spinnerAdoptionCustomer);
|
||||||
if (!InputValidator.isValidDate(etAdoptionDate)) return;
|
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();
|
private void setupSpinners() {
|
||||||
String adopterEmail = etAdopterEmail.getText().toString().trim();
|
spinnerStatus.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||||
String adopterPhone = etAdopterPhone.getText().toString().trim();
|
android.R.layout.simple_spinner_item, STATUSES));
|
||||||
String petName = etPetName.getText().toString().trim();
|
}
|
||||||
String adoptionDate = etAdoptionDate.getText().toString().trim();
|
|
||||||
String status = spinnerAdoptionStatus.getSelectedItem().toString();
|
|
||||||
|
|
||||||
try {
|
private void setupDatePicker() {
|
||||||
if (isEditing) {
|
etAdoptionDate.setOnClickListener(v -> {
|
||||||
// TODO: Replace with actual API PUT call when backend is ready
|
Calendar c = Calendar.getInstance();
|
||||||
Adoption updated = new Adoption(adoptionId, adopterName, adopterEmail, adopterPhone, petName, adoptionDate, status);
|
new DatePickerDialog(requireContext(),
|
||||||
if (adoptionFragment != null) adoptionFragment.onAdoptionSaved(position, updated);
|
(dp, y, m, d) -> etAdoptionDate.setText(
|
||||||
ActivityLogger.logChange(requireContext(), "Adoption", "UPDATED", adoptionId);
|
String.format("%04d-%02d-%02d", y, m + 1, d)),
|
||||||
Toast.makeText(getContext(), "Adoption record updated.", Toast.LENGTH_SHORT).show();
|
c.get(Calendar.YEAR),
|
||||||
} else {
|
c.get(Calendar.MONTH),
|
||||||
// TODO: Replace with actual API POST call when backend is ready
|
c.get(Calendar.DAY_OF_MONTH)).show();
|
||||||
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 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 loadCustomers() {
|
||||||
private void deleteAdoption() {
|
RetrofitClient.getCustomerApi(requireContext()).getAllCustomers(0, 200)
|
||||||
try {
|
.enqueue(new Callback<PageResponse<CustomerDTO>>() {
|
||||||
// TODO: Replace with actual API DELETE call when backend is ready
|
public void onResponse(Call<PageResponse<CustomerDTO>> c,
|
||||||
if (adoptionFragment != null) adoptionFragment.onAdoptionDeleted(position);
|
Response<PageResponse<CustomerDTO>> r) {
|
||||||
ActivityLogger.logChange(requireContext(), "Adoption", "DELETED", adoptionId);
|
if (r.isSuccessful() && r.body() != null) {
|
||||||
Toast.makeText(getContext(), "Adoption record deleted.", Toast.LENGTH_SHORT).show();
|
customerList = r.body().getContent();
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
populateCustomerSpinner();
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
}
|
||||||
} catch (Exception e) {
|
}
|
||||||
ActivityLogger.logException(requireContext(), "AdoptionDetailFragment.deleteAdoption", e);
|
public void onFailure(Call<PageResponse<CustomerDTO>> c, Throwable t) {
|
||||||
Toast.makeText(getContext(), "Error deleting adoption record.", Toast.LENGTH_SHORT).show();
|
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() {
|
private void handleArguments() {
|
||||||
if (getArguments() != null && getArguments().containsKey("adoptionId")) {
|
Bundle a = getArguments();
|
||||||
|
if (a != null && a.containsKey("adoptionId")) {
|
||||||
isEditing = true;
|
isEditing = true;
|
||||||
adoptionId = getArguments().getInt("adoptionId");
|
adoptionId = a.getLong("adoptionId");
|
||||||
position = getArguments().getInt("position");
|
preselectedPetId = a.getLong("petId", -1);
|
||||||
|
preselectedCustomerId = a.getLong("customerId", -1);
|
||||||
|
|
||||||
tvMode.setText("Edit Adoption");
|
tvMode.setText("Edit Adoption");
|
||||||
tvAdoptionId.setText("ID: " + adoptionId);
|
tvAdoptionId.setText("ID: " + adoptionId);
|
||||||
etAdopterName.setText(getArguments().getString("adopterName"));
|
tvAdoptionId.setVisibility(View.VISIBLE);
|
||||||
etAdopterEmail.setText(getArguments().getString("adopterEmail"));
|
etAdoptionDate.setText(a.getString("adoptionDate"));
|
||||||
etAdopterPhone.setText(getArguments().getString("adopterPhone"));
|
btnDelete.setVisibility(View.VISIBLE);
|
||||||
etPetName.setText(getArguments().getString("petName"));
|
|
||||||
etAdoptionDate.setText(getArguments().getString("adoptionDate"));
|
// Pre-fill status
|
||||||
String status = getArguments().getString("status");
|
String status = a.getString("adoptionStatus", "Pending");
|
||||||
if ("Approved".equals(status)) spinnerAdoptionStatus.setSelection(0);
|
for (int i = 0; i < STATUSES.length; i++) {
|
||||||
else if ("Pending".equals(status)) spinnerAdoptionStatus.setSelection(1);
|
if (STATUSES[i].equals(status)) {
|
||||||
else spinnerAdoptionStatus.setSelection(2);
|
spinnerStatus.setSelection(i); break;
|
||||||
btnDeleteAdoption.setVisibility(View.VISIBLE);
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
isEditing = false;
|
|
||||||
tvMode.setText("Add Adoption");
|
tvMode.setText("Add Adoption");
|
||||||
|
btnDelete.setVisibility(View.GONE);
|
||||||
tvAdoptionId.setVisibility(View.GONE);
|
tvAdoptionId.setVisibility(View.GONE);
|
||||||
btnDeleteAdoption.setVisibility(View.GONE);
|
|
||||||
btnSaveAdoption.setText("Add");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initViews(View view) {
|
private void saveAdoption() {
|
||||||
tvMode = view.findViewById(R.id.tvAdoptionMode);
|
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
||||||
tvAdoptionId = view.findViewById(R.id.tvAdoptionId);
|
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||||
etAdopterName = view.findViewById(R.id.etAdopterName);
|
}
|
||||||
etAdopterEmail = view.findViewById(R.id.etAdopterEmail);
|
if (spinnerPet.getSelectedItemPosition() == 0) {
|
||||||
etAdopterPhone = view.findViewById(R.id.etAdopterPhone);
|
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
||||||
etPetName = view.findViewById(R.id.etAdoptionPetName);
|
}
|
||||||
etAdoptionDate = view.findViewById(R.id.etAdoptionDate);
|
String date = etAdoptionDate.getText().toString().trim();
|
||||||
spinnerAdoptionStatus = view.findViewById(R.id.spinnerAdoptionStatus);
|
if (date.isEmpty()) {
|
||||||
btnSaveAdoption = view.findViewById(R.id.btnSaveAdoption);
|
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
|
||||||
btnDeleteAdoption = view.findViewById(R.id.btnDeleteAdoption);
|
}
|
||||||
btnBack = view.findViewById(R.id.btnAdoptionBack);
|
|
||||||
|
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() {
|
private Callback<AdoptionDTO> simpleCallback(String msg) {
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(requireContext(),
|
return new Callback<>() {
|
||||||
android.R.layout.simple_spinner_item,
|
public void onResponse(Call<AdoptionDTO> c, Response<AdoptionDTO> r) {
|
||||||
new String[]{"Approved", "Pending", "Rejected"}) {
|
Log.d("ADOPTION_SAVE", "Response: " + r.code());
|
||||||
|
if (r.isSuccessful()) {
|
||||||
//Override the getView method for the spinner to make the text color darker for more readability
|
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||||
@NonNull
|
navigateBack();
|
||||||
@Override
|
} else {
|
||||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
try {
|
||||||
View view = super.getView(position, convertView, parent);
|
String err = r.errorBody().string();
|
||||||
((TextView) view).setTextColor(ContextCompat.getColor(requireContext(), R.color.text_dark));
|
Log.e("ADOPTION_SAVE", "Error: " + err);
|
||||||
return view;
|
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;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
// Uses InputValidator for detailed field validation and ActivityLogger to log all changes.
|
|
||||||
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
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.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.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.AppointmentFragment;
|
import java.util.*;
|
||||||
import com.example.petstoremobile.models.Appointment;
|
import retrofit2.*;
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
|
||||||
|
|
||||||
public class AppointmentDetailFragment extends Fragment {
|
public class AppointmentDetailFragment extends Fragment {
|
||||||
|
|
||||||
private TextView tvMode, tvAppointmentId;
|
private TextView tvMode, tvAppointmentId;
|
||||||
private EditText etCustomerName, etPetName, etServiceType, etAppointmentDate, etAppointmentTime;
|
private EditText etAppointmentDate;
|
||||||
private Spinner spinnerStatus;
|
private Spinner spinnerPet, spinnerService, spinnerStatus, spinnerHour, spinnerMinute;
|
||||||
private Button btnSaveAppointment, btnDeleteAppointment, btnBack;
|
private Spinner spinnerCustomer, spinnerStore;
|
||||||
private int appointmentId;
|
private Button btnSave, btnDelete, btnBack;
|
||||||
private int position;
|
|
||||||
private boolean isEditing = false;
|
|
||||||
private AppointmentFragment appointmentFragment;
|
|
||||||
|
|
||||||
// Set the appointment fragment as parent so we refer back when save or delete is done
|
private long appointmentId = -1;
|
||||||
public void setAppointmentFragment(AppointmentFragment fragment) {
|
private boolean isEditing = false;
|
||||||
this.appointmentFragment = fragment;
|
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
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.fragment_appointment_detail, container, false);
|
View view = inflater.inflate(R.layout.fragment_appointment_detail, container, false);
|
||||||
|
|
||||||
initViews(view);
|
initViews(view);
|
||||||
setupSpinner();
|
setupSpinners();
|
||||||
|
setupDatePicker();
|
||||||
|
loadData();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
btnBack.setOnClickListener(v -> {
|
btnBack.setOnClickListener(v -> navigateBack());
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
btnSave.setOnClickListener(v -> saveAppointment());
|
||||||
if (listFragment != null) {
|
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||||
listFragment.getChildFragmentManager().popBackStack();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
btnSaveAppointment.setOnClickListener(v -> saveAppointment());
|
|
||||||
btnDeleteAppointment.setOnClickListener(v -> deleteAppointment());
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates all fields using InputValidator, then saves the appointment
|
private void initViews(View v) {
|
||||||
private void saveAppointment() {
|
tvMode = v.findViewById(R.id.tvApptMode);
|
||||||
// Validate all inputs using InputValidator utility
|
tvAppointmentId = v.findViewById(R.id.tvAppointmentId);
|
||||||
if (!InputValidator.isNotEmpty(etCustomerName, "Customer Name")) return;
|
etAppointmentDate= v.findViewById(R.id.etAppointmentDate);
|
||||||
if (!InputValidator.isNotEmpty(etPetName, "Pet Name")) return;
|
spinnerPet = v.findViewById(R.id.spinnerPet);
|
||||||
if (!InputValidator.isNotEmpty(etServiceType, "Service Type")) return;
|
spinnerService = v.findViewById(R.id.spinnerService);
|
||||||
if (!InputValidator.isValidDate(etAppointmentDate)) return;
|
spinnerStatus = v.findViewById(R.id.spinnerAppointmentStatus);
|
||||||
if (!InputValidator.isValidTime(etAppointmentTime)) return;
|
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();
|
private void setupSpinners() {
|
||||||
String petName = etPetName.getText().toString().trim();
|
spinnerStatus.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||||
String serviceType = etServiceType.getText().toString().trim();
|
android.R.layout.simple_spinner_item, STATUSES));
|
||||||
String date = etAppointmentDate.getText().toString().trim();
|
|
||||||
String time = etAppointmentTime.getText().toString().trim();
|
|
||||||
String status = spinnerStatus.getSelectedItem().toString();
|
|
||||||
|
|
||||||
try {
|
String[] hours = new String[HOURS.length];
|
||||||
if (isEditing) {
|
for (int i = 0; i < HOURS.length; i++)
|
||||||
// TODO: Replace with actual API PUT call when backend is ready
|
hours[i] = String.format("%02d:00", HOURS[i]);
|
||||||
Appointment updated = new Appointment(appointmentId, customerName, petName, serviceType, date, time, status);
|
spinnerHour.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||||
if (appointmentFragment != null) appointmentFragment.onAppointmentSaved(position, updated);
|
android.R.layout.simple_spinner_item, hours));
|
||||||
ActivityLogger.logChange(requireContext(), "Appointment", "UPDATED", appointmentId);
|
spinnerMinute.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||||
Toast.makeText(getContext(), "Appointment updated.", Toast.LENGTH_SHORT).show();
|
android.R.layout.simple_spinner_item, new String[]{"00","15","30","45"}));
|
||||||
} else {
|
}
|
||||||
// TODO: Replace with actual API POST call when backend is ready
|
|
||||||
Appointment newAppt = new Appointment(0, customerName, petName, serviceType, date, time, status);
|
private void setupDatePicker() {
|
||||||
if (appointmentFragment != null) appointmentFragment.onAppointmentSaved(-1, newAppt);
|
etAppointmentDate.setOnClickListener(v -> {
|
||||||
ActivityLogger.log(requireContext(), "Added new Appointment for customer: " + customerName);
|
Calendar c = Calendar.getInstance();
|
||||||
Toast.makeText(getContext(), "Appointment added.", Toast.LENGTH_SHORT).show();
|
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 loadServices() {
|
||||||
private void deleteAppointment() {
|
RetrofitClient.getServiceApi(requireContext()).getAllServices(0, 200)
|
||||||
try {
|
.enqueue(new Callback<PageResponse<ServiceDTO>>() {
|
||||||
// TODO: Replace with actual API DELETE call when backend is ready
|
public void onResponse(Call<PageResponse<ServiceDTO>> c, Response<PageResponse<ServiceDTO>> r) {
|
||||||
if (appointmentFragment != null) appointmentFragment.onAppointmentDeleted(position);
|
if (r.isSuccessful() && r.body() != null) {
|
||||||
ActivityLogger.logChange(requireContext(), "Appointment", "DELETED", appointmentId);
|
serviceList = r.body().getContent();
|
||||||
Toast.makeText(getContext(), "Appointment deleted.", Toast.LENGTH_SHORT).show();
|
populateServiceSpinner();
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
}
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
}
|
||||||
} catch (Exception e) {
|
public void onFailure(Call<PageResponse<ServiceDTO>> c, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "AppointmentDetailFragment.deleteAppointment", e);
|
Log.e("APPT", "Service load failed: " + t.getMessage());
|
||||||
Toast.makeText(getContext(), "Error deleting appointment.", Toast.LENGTH_SHORT).show();
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
private void handleArguments() {
|
||||||
if (getArguments() != null && getArguments().containsKey("appointmentId")) {
|
Bundle a = getArguments();
|
||||||
|
if (a != null && a.containsKey("appointmentId")) {
|
||||||
isEditing = true;
|
isEditing = true;
|
||||||
appointmentId = getArguments().getInt("appointmentId");
|
appointmentId = a.getLong("appointmentId");
|
||||||
position = getArguments().getInt("position");
|
preselectedPetId = a.getLong("petId", -1);
|
||||||
|
preselectedServiceId= a.getLong("serviceId", -1);
|
||||||
|
preselectedCustomerId = a.getLong("customerId", -1);
|
||||||
|
preselectedStoreId = a.getLong("storeId", -1);
|
||||||
|
|
||||||
tvMode.setText("Edit Appointment");
|
tvMode.setText("Edit Appointment");
|
||||||
tvAppointmentId.setText("ID: " + appointmentId);
|
tvAppointmentId.setText("ID: " + appointmentId);
|
||||||
etCustomerName.setText(getArguments().getString("customerName"));
|
tvAppointmentId.setVisibility(View.VISIBLE);
|
||||||
etPetName.setText(getArguments().getString("petName"));
|
etAppointmentDate.setText(a.getString("appointmentDate"));
|
||||||
etServiceType.setText(getArguments().getString("serviceType"));
|
btnDelete.setVisibility(View.VISIBLE);
|
||||||
etAppointmentDate.setText(getArguments().getString("appointmentDate"));
|
|
||||||
etAppointmentTime.setText(getArguments().getString("appointmentTime"));
|
// Pre-fill time spinners
|
||||||
String status = getArguments().getString("status");
|
String time = a.getString("appointmentTime", "09:00");
|
||||||
if ("Confirmed".equals(status)) spinnerStatus.setSelection(0);
|
if (time.length() > 5) time = time.substring(0, 5);
|
||||||
else if ("Pending".equals(status)) spinnerStatus.setSelection(1);
|
String[] parts = time.split(":");
|
||||||
else spinnerStatus.setSelection(2);
|
if (parts.length == 2) {
|
||||||
btnDeleteAppointment.setVisibility(View.VISIBLE);
|
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 {
|
} else {
|
||||||
isEditing = false;
|
|
||||||
tvMode.setText("Add Appointment");
|
tvMode.setText("Add Appointment");
|
||||||
|
btnDelete.setVisibility(View.GONE);
|
||||||
tvAppointmentId.setVisibility(View.GONE);
|
tvAppointmentId.setVisibility(View.GONE);
|
||||||
btnDeleteAppointment.setVisibility(View.GONE);
|
|
||||||
btnSaveAppointment.setText("Add");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initViews(View view) {
|
private void saveAppointment() {
|
||||||
tvMode = view.findViewById(R.id.tvApptMode);
|
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
||||||
tvAppointmentId = view.findViewById(R.id.tvAppointmentId);
|
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||||
etCustomerName = view.findViewById(R.id.etCustomerName);
|
}
|
||||||
etPetName = view.findViewById(R.id.etApptPetName);
|
if (spinnerStore.getSelectedItemPosition() == 0) {
|
||||||
etServiceType = view.findViewById(R.id.etServiceType);
|
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return;
|
||||||
etAppointmentDate = view.findViewById(R.id.etAppointmentDate);
|
}
|
||||||
etAppointmentTime = view.findViewById(R.id.etAppointmentTime);
|
if (spinnerPet.getSelectedItemPosition() == 0) {
|
||||||
spinnerStatus = view.findViewById(R.id.spinnerAppointmentStatus);
|
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
||||||
btnSaveAppointment = view.findViewById(R.id.btnSaveAppointment);
|
}
|
||||||
btnDeleteAppointment = view.findViewById(R.id.btnDeleteAppointment);
|
if (spinnerService.getSelectedItemPosition() == 0) {
|
||||||
btnBack = view.findViewById(R.id.btnApptBack);
|
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() {
|
private Callback<AppointmentDTO> simpleCallback(String msg) {
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(requireContext(),
|
return new Callback<>() {
|
||||||
android.R.layout.simple_spinner_item,
|
public void onResponse(Call<AppointmentDTO> c, Response<AppointmentDTO> r) {
|
||||||
new String[]{"Confirmed", "Pending", "Cancelled"}) {
|
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
|
// Show proper dialog based on error type
|
||||||
@NonNull
|
if (errorBody.toLowerCase().contains("future")) {
|
||||||
@Override
|
showErrorDialog("Invalid Date/Time",
|
||||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
"Booked appointments must be scheduled in the future. " +
|
||||||
View view = super.getView(position, convertView, parent);
|
"Please select a future date and time.");
|
||||||
((TextView) view).setTextColor(ContextCompat.getColor(requireContext(), R.color.text_dark));
|
//------------------------------------------
|
||||||
return view;
|
} 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;
|
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.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.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.AutoCompleteTextView;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
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.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
||||||
import com.example.petstoremobile.models.Inventory;
|
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import java.util.ArrayList;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class InventoryDetailFragment extends Fragment {
|
public class InventoryDetailFragment extends Fragment {
|
||||||
|
|
||||||
private TextView tvMode, tvInventoryId;
|
private TextView tvMode, tvInventoryId, tvProductInfo;
|
||||||
private EditText etItemName, etCategory, etQuantity, etUnitPrice, etSupplier;
|
private AutoCompleteTextView etProductSearch;
|
||||||
private Button btnSaveInventory, btnDeleteInventory, btnBack;
|
private android.widget.EditText etQuantity;
|
||||||
private int inventoryId;
|
private Button btnSave, btnDelete, btnBack;
|
||||||
private int position;
|
|
||||||
private boolean isEditing = false;
|
private InventoryApi inventoryApi;
|
||||||
|
private ProductApi productApi;
|
||||||
private InventoryFragment inventoryFragment;
|
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) {
|
public void setInventoryFragment(InventoryFragment fragment) {
|
||||||
this.inventoryFragment = fragment;
|
this.inventoryFragment = fragment;
|
||||||
}
|
}
|
||||||
@@ -38,102 +70,271 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_inventory_detail, container, false);
|
View view = inflater.inflate(R.layout.fragment_inventory_detail, container, false);
|
||||||
|
|
||||||
|
inventoryApi = RetrofitClient.getInventoryApi(requireContext());
|
||||||
|
productApi = RetrofitClient.getProductApi(requireContext());
|
||||||
|
|
||||||
initViews(view);
|
initViews(view);
|
||||||
|
setupProductSearch();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
btnBack.setOnClickListener(v -> {
|
btnBack.setOnClickListener(v -> navigateBack());
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
btnSave.setOnClickListener(v -> saveInventory());
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||||
});
|
|
||||||
btnSaveInventory.setOnClickListener(v -> saveInventory());
|
|
||||||
btnDeleteInventory.setOnClickListener(v -> deleteInventory());
|
|
||||||
|
|
||||||
return view;
|
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) {
|
private void initViews(View view) {
|
||||||
tvMode = view.findViewById(R.id.tvInventoryMode);
|
tvMode = view.findViewById(R.id.tvInventoryMode);
|
||||||
tvInventoryId = view.findViewById(R.id.tvInventoryId);
|
tvInventoryId = view.findViewById(R.id.tvInventoryId);
|
||||||
etItemName = view.findViewById(R.id.etItemName);
|
tvProductInfo = view.findViewById(R.id.tvProductInfo);
|
||||||
etCategory = view.findViewById(R.id.etInventoryCategory);
|
etProductSearch = view.findViewById(R.id.etProductSearch);
|
||||||
etQuantity = view.findViewById(R.id.etQuantity);
|
etQuantity = view.findViewById(R.id.etQuantity);
|
||||||
etUnitPrice = view.findViewById(R.id.etUnitPrice);
|
btnSave = view.findViewById(R.id.btnSaveInventory);
|
||||||
etSupplier = view.findViewById(R.id.etInventorySupplier);
|
btnDelete = view.findViewById(R.id.btnDeleteInventory);
|
||||||
btnSaveInventory = view.findViewById(R.id.btnSaveInventory);
|
|
||||||
btnDeleteInventory = view.findViewById(R.id.btnDeleteInventory);
|
|
||||||
btnBack = view.findViewById(R.id.btnInventoryBack);
|
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 android.widget.Toast;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||||
import com.example.petstoremobile.api.PetApi;
|
import com.example.petstoremobile.api.PetApi;
|
||||||
import com.example.petstoremobile.api.RetrofitClient;
|
import com.example.petstoremobile.api.RetrofitClient;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
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
|
//helper function to set up the spinner menu for pet status
|
||||||
private void setupSpinner() {
|
private void setupSpinner() {
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(requireContext(),
|
BlackTextArrayAdapter<String> adapter = new BlackTextArrayAdapter<>(requireContext(),
|
||||||
android.R.layout.simple_spinner_item,
|
android.R.layout.simple_spinner_item,
|
||||||
new String[]{"Available", "Adopted"}) {
|
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
spinnerPetStatus.setAdapter(adapter);
|
spinnerPetStatus.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,139 +1,191 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
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.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.*;
|
||||||
|
import android.widget.*;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.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.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.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.ProductFragment;
|
import java.math.BigDecimal;
|
||||||
import com.example.petstoremobile.models.Product;
|
import java.util.*;
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import retrofit2.*;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
|
||||||
|
|
||||||
public class ProductDetailFragment extends Fragment {
|
public class ProductDetailFragment extends Fragment {
|
||||||
|
|
||||||
private TextView tvMode, tvProductId;
|
private TextView tvMode, tvProductId;
|
||||||
private EditText etProductName, etProductDesc, etCategory, etProductPrice, etStockQuantity;
|
private EditText etProductName, etProductDesc, etProductPrice;
|
||||||
private Button btnSaveProduct, btnDeleteProduct, btnBack;
|
private Spinner spinnerCategory;
|
||||||
private int productId;
|
private Button btnSave, btnDelete, btnBack;
|
||||||
private int position;
|
|
||||||
private boolean isEditing = false;
|
|
||||||
private ProductFragment productFragment;
|
|
||||||
|
|
||||||
// Set the product fragment as parent so we refer back when save or delete is done
|
private long prodId = -1;
|
||||||
public void setProductFragment(ProductFragment fragment) {
|
private boolean isEditing = false;
|
||||||
this.productFragment = fragment;
|
private long preselectedCategoryId = -1;
|
||||||
}
|
|
||||||
|
private List<CategoryDTO> categoryList = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_product_detail, container, false);
|
View view = inflater.inflate(R.layout.fragment_product_detail, container, false);
|
||||||
|
|
||||||
initViews(view);
|
initViews(view);
|
||||||
|
loadCategories();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
btnBack.setOnClickListener(v -> {
|
btnBack.setOnClickListener(v -> navigateBack());
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
btnSave.setOnClickListener(v -> saveProduct());
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||||
});
|
|
||||||
btnSaveProduct.setOnClickListener(v -> saveProduct());
|
|
||||||
btnDeleteProduct.setOnClickListener(v -> deleteProduct());
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates all fields using InputValidator, then saves the product
|
private void initViews(View v) {
|
||||||
private void saveProduct() {
|
tvMode = v.findViewById(R.id.tvProductMode);
|
||||||
if (!InputValidator.isNotEmpty(etProductName, "Product Name")) return;
|
tvProductId = v.findViewById(R.id.tvProductId);
|
||||||
if (!InputValidator.isNotEmpty(etProductDesc, "Description")) return;
|
etProductName = v.findViewById(R.id.etProductName);
|
||||||
if (!InputValidator.isNotEmpty(etCategory, "Category")) return;
|
etProductDesc = v.findViewById(R.id.etProductDesc);
|
||||||
if (!InputValidator.isPositiveDecimal(etProductPrice, "Price")) return;
|
etProductPrice = v.findViewById(R.id.etProductPrice);
|
||||||
if (!InputValidator.isPositiveInteger(etStockQuantity, "Stock Quantity")) return;
|
spinnerCategory = v.findViewById(R.id.spinnerProductCategory);
|
||||||
|
btnSave = v.findViewById(R.id.btnSaveProduct);
|
||||||
String productName = etProductName.getText().toString().trim();
|
btnDelete = v.findViewById(R.id.btnDeleteProduct);
|
||||||
String productDesc = etProductDesc.getText().toString().trim();
|
btnBack = v.findViewById(R.id.btnProductBack);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deletes the product and logs the action
|
private void loadCategories() {
|
||||||
private void deleteProduct() {
|
RetrofitClient.getCategoryApi(requireContext()).getAllCategories(0, 100)
|
||||||
try {
|
.enqueue(new Callback<PageResponse<CategoryDTO>>() {
|
||||||
// TODO: Replace with actual API DELETE call when backend is ready
|
public void onResponse(Call<PageResponse<CategoryDTO>> c,
|
||||||
if (productFragment != null) productFragment.onProductDeleted(position);
|
Response<PageResponse<CategoryDTO>> r) {
|
||||||
ActivityLogger.logChange(requireContext(), "Product", "DELETED", productId);
|
if (r.isSuccessful() && r.body() != null) {
|
||||||
Toast.makeText(getContext(), "Product deleted.", Toast.LENGTH_SHORT).show();
|
categoryList = r.body().getContent();
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
populateCategorySpinner();
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
}
|
||||||
} catch (Exception e) {
|
}
|
||||||
ActivityLogger.logException(requireContext(), "ProductDetailFragment.deleteProduct", e);
|
public void onFailure(Call<PageResponse<CategoryDTO>> c, Throwable t) {
|
||||||
Toast.makeText(getContext(), "Error deleting product.", Toast.LENGTH_SHORT).show();
|
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() {
|
private void handleArguments() {
|
||||||
if (getArguments() != null && getArguments().containsKey("productId")) {
|
Bundle a = getArguments();
|
||||||
|
if (a != null && a.containsKey("prodId")) {
|
||||||
isEditing = true;
|
isEditing = true;
|
||||||
productId = getArguments().getInt("productId");
|
prodId = a.getLong("prodId");
|
||||||
position = getArguments().getInt("position");
|
preselectedCategoryId = a.getLong("categoryId", -1);
|
||||||
|
|
||||||
tvMode.setText("Edit Product");
|
tvMode.setText("Edit Product");
|
||||||
tvProductId.setText("ID: " + productId);
|
tvProductId.setText("ID: " + prodId);
|
||||||
etProductName.setText(getArguments().getString("productName"));
|
tvProductId.setVisibility(View.VISIBLE);
|
||||||
etProductDesc.setText(getArguments().getString("productDesc"));
|
etProductName.setText(a.getString("prodName"));
|
||||||
etCategory.setText(getArguments().getString("category"));
|
etProductDesc.setText(a.getString("prodDesc"));
|
||||||
etProductPrice.setText(String.valueOf(getArguments().getDouble("productPrice")));
|
etProductPrice.setText(a.getString("prodPrice"));
|
||||||
etStockQuantity.setText(String.valueOf(getArguments().getInt("stockQuantity")));
|
btnDelete.setVisibility(View.VISIBLE);
|
||||||
btnDeleteProduct.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
} else {
|
||||||
isEditing = false;
|
|
||||||
tvMode.setText("Add Product");
|
tvMode.setText("Add Product");
|
||||||
|
btnDelete.setVisibility(View.GONE);
|
||||||
tvProductId.setVisibility(View.GONE);
|
tvProductId.setVisibility(View.GONE);
|
||||||
btnDeleteProduct.setVisibility(View.GONE);
|
|
||||||
btnSaveProduct.setText("Add");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initViews(View view) {
|
private void saveProduct() {
|
||||||
tvMode = view.findViewById(R.id.tvProductMode);
|
String name = etProductName.getText().toString().trim();
|
||||||
tvProductId = view.findViewById(R.id.tvProductId);
|
String desc = etProductDesc.getText().toString().trim();
|
||||||
etProductName = view.findViewById(R.id.etProductName);
|
String priceStr = etProductPrice.getText().toString().trim();
|
||||||
etProductDesc = view.findViewById(R.id.etProductDesc);
|
|
||||||
etCategory = view.findViewById(R.id.etProductCategory);
|
if (name.isEmpty()) {
|
||||||
etProductPrice = view.findViewById(R.id.etProductPrice);
|
etProductName.setError("Enter product name"); return;
|
||||||
etStockQuantity = view.findViewById(R.id.etStockQuantity);
|
}
|
||||||
btnSaveProduct = view.findViewById(R.id.btnSaveProduct);
|
if (spinnerCategory.getSelectedItemPosition() == 0) {
|
||||||
btnDeleteProduct = view.findViewById(R.id.btnDeleteProduct);
|
Toast.makeText(getContext(), "Select a category", Toast.LENGTH_SHORT).show(); return;
|
||||||
btnBack = view.findViewById(R.id.btnProductBack);
|
}
|
||||||
|
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_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@color/background_grey">
|
android:background="@color/primary_dark">
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/fragment_container"
|
android:id="@+id/fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"/>
|
android:layout_weight="1"
|
||||||
|
android:background="@color/background_grey"/>
|
||||||
|
|
||||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
android:id="@+id/bottom_navigation"
|
android:id="@+id/bottom_navigation"
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/header"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="56dp"
|
||||||
android:background="@color/primary_dark"
|
android:background="@color/primary_dark"
|
||||||
@@ -21,7 +20,7 @@
|
|||||||
android:paddingEnd="16dp">
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/btnHamburger"
|
android:id="@+id/btnHamburgerAdoption"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:src="@drawable/baseline_menu_36"
|
android:src="@drawable/baseline_menu_36"
|
||||||
@@ -43,7 +42,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:hint="Search by adopter or pet name..."
|
android:hint="Search by customer or pet..."
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:drawableStart="@android:drawable/ic_menu_search"
|
android:drawableStart="@android:drawable/ic_menu_search"
|
||||||
android:drawablePadding="8dp"
|
android:drawablePadding="8dp"
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:backgroundTint="@color/accent_coral"
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:text="Delete"
|
android:text="Delete"
|
||||||
android:textColor="@color/white" />
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -65,74 +65,37 @@
|
|||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<!-- Customer -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Adopter Name"
|
android:text="Customer"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<Spinner
|
||||||
android:id="@+id/etAdopterName"
|
android:id="@+id/spinnerAdoptionCustomer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter adopter name"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:inputType="text"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
|
<!-- Pet -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Adopter Email"
|
android:text="Pet"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<Spinner
|
||||||
android:id="@+id/etAdopterEmail"
|
android:id="@+id/spinnerAdoptionPet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter email address"
|
android:layout_marginBottom="16dp"/>
|
||||||
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"/>
|
|
||||||
|
|
||||||
|
<!-- Adoption Date -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -145,11 +108,14 @@
|
|||||||
android:id="@+id/etAdoptionDate"
|
android:id="@+id/etAdoptionDate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="YYYY-MM-DD"
|
android:hint="Tap to select date"
|
||||||
android:inputType="text"
|
android:inputType="none"
|
||||||
android:layout_marginBottom="16dp"
|
android:focusable="false"
|
||||||
android:textColor="@color/text_dark"/>
|
android:clickable="true"
|
||||||
|
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -161,7 +127,8 @@
|
|||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerAdoptionStatus"
|
android:id="@+id/spinnerAdoptionStatus"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -65,54 +65,76 @@
|
|||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Customer -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Customer Name"
|
android:text="Customer"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<Spinner
|
||||||
android:id="@+id/etCustomerName"
|
android:id="@+id/spinnerCustomer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter customer name"
|
|
||||||
android:inputType="text"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Store -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Pet Name"
|
android:text="Store"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<Spinner
|
||||||
android:id="@+id/etApptPetName"
|
android:id="@+id/spinnerStore"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter pet name"
|
|
||||||
android:inputType="text"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Pet -->
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Service Type"
|
android:text="Pet"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/etServiceType"
|
<Spinner
|
||||||
|
android:id="@+id/spinnerPet"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="e.g. Grooming, Vet Checkup"
|
|
||||||
android:inputType="text"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
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
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -125,10 +147,14 @@
|
|||||||
android:id="@+id/etAppointmentDate"
|
android:id="@+id/etAppointmentDate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="YYYY-MM-DD"
|
android:hint="Tap to select date"
|
||||||
android:inputType="text"
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Appointment Time-->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -137,14 +163,45 @@
|
|||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<LinearLayout
|
||||||
android:id="@+id/etAppointmentTime"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="e.g. 10:00 AM"
|
android:orientation="horizontal"
|
||||||
android:inputType="text"
|
android:layout_marginBottom="16dp"
|
||||||
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
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/header"
|
android:id="@+id/header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -38,19 +39,68 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<EditText
|
<LinearLayout
|
||||||
android:id="@+id/etSearchInventory"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:orientation="horizontal"
|
||||||
android:hint="Search by item name or category..."
|
android:padding="8dp"
|
||||||
android:inputType="text"
|
android:gravity="center_vertical">
|
||||||
android:drawableStart="@android:drawable/ic_menu_search"
|
|
||||||
android:drawablePadding="8dp"
|
|
||||||
android:background="@android:color/white"
|
|
||||||
android:padding="12dp"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
|
<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
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swipeRefreshInventory"
|
android:id="@+id/swipeRefreshInventory"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -60,7 +110,8 @@
|
|||||||
android:id="@+id/recyclerViewInventory"
|
android:id="@+id/recyclerViewInventory"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="8dp"/>
|
android:padding="8dp"
|
||||||
|
android:clipToPadding="false"/>
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
@@ -73,7 +124,7 @@
|
|||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:backgroundTint="@color/accent_coral"
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:contentDescription="Add Inventory Item"
|
android:contentDescription="Add Inventory"
|
||||||
app:srcCompat="@android:drawable/ic_input_add"
|
app:srcCompat="@android:drawable/ic_input_add"
|
||||||
app:tint="@color/white"/>
|
app:tint="@color/white"/>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@color/background_grey">
|
android:background="@color/background_grey">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="56dp"
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="Add Inventory Item"
|
android:text="Add Inventory"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"/>
|
||||||
@@ -28,10 +29,10 @@
|
|||||||
android:id="@+id/btnDeleteInventory"
|
android:id="@+id/btnDeleteInventory"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:text="Delete"
|
android:text="Delete"
|
||||||
android:textColor="@color/white" />
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -51,52 +52,51 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:background="@drawable/rounded_card"
|
||||||
android:padding="16dp"
|
android:padding="16dp">
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
|
<!-- Inventory ID — edit mode only -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvInventoryId"
|
android:id="@+id/tvInventoryId"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="ID: #0"
|
android:text="Inventory ID: —"
|
||||||
android:textColor="@color/text_light"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="11sp"
|
android:textSize="11sp"
|
||||||
android:textStyle="italic"
|
android:textStyle="italic"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:layout_marginBottom="12dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<!-- Product search label -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Item Name"
|
android:text="Product"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<!-- AutoComplete search box -->
|
||||||
android:id="@+id/etItemName"
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/etProductSearch"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter item name"
|
android:hint="Search product name…"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:completionThreshold="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"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<!-- Selected product info (ID + category) shown after picking -->
|
||||||
android:id="@+id/etInventoryCategory"
|
<TextView
|
||||||
|
android:id="@+id/tvProductInfo"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="e.g. Food, Toys, Medicine"
|
android:textColor="#888888"
|
||||||
android:inputType="text"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<!-- Quantity label -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -105,44 +105,13 @@
|
|||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<!-- Quantity input -->
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etQuantity"
|
android:id="@+id/etQuantity"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter quantity"
|
android:hint="Enter quantity"
|
||||||
android:inputType="number"
|
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"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -150,6 +119,7 @@
|
|||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- Bottom buttons -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -33,6 +33,8 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@color/primary_dark">
|
android:background="@color/primary_dark">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -133,6 +135,30 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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
|
<LinearLayout
|
||||||
android:id="@+id/drawerAdoptions"
|
android:id="@+id/drawerAdoptions"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -152,25 +178,9 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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
|
<LinearLayout
|
||||||
android:id="@+id/drawerInventory"
|
android:id="@+id/drawerInventory"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -190,6 +200,9 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Product -->
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/drawerProducts"
|
android:id="@+id/drawerProducts"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -209,6 +222,78 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</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>
|
</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>
|
</androidx.drawerlayout.widget.DrawerLayout>
|
||||||
@@ -12,7 +12,6 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/header"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="56dp"
|
||||||
android:background="@color/primary_dark"
|
android:background="@color/primary_dark"
|
||||||
@@ -21,7 +20,7 @@
|
|||||||
android:paddingEnd="16dp">
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/btnHamburger"
|
android:id="@+id/btnHamburgerProduct"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:src="@drawable/baseline_menu_36"
|
android:src="@drawable/baseline_menu_36"
|
||||||
@@ -43,7 +42,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:hint="Search by product name or category..."
|
android:hint="Search by name or category..."
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:drawableStart="@android:drawable/ic_menu_search"
|
android:drawableStart="@android:drawable/ic_menu_search"
|
||||||
android:drawablePadding="8dp"
|
android:drawablePadding="8dp"
|
||||||
|
|||||||
@@ -28,10 +28,9 @@
|
|||||||
android:id="@+id/btnDeleteProduct"
|
android:id="@+id/btnDeleteProduct"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:text="Delete"
|
android:text="Delete"
|
||||||
android:textColor="@color/white" />
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -65,6 +64,7 @@
|
|||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<!-- Product Name -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -81,6 +81,22 @@
|
|||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:layout_marginBottom="16dp"/>
|
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
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -93,27 +109,12 @@
|
|||||||
android:id="@+id/etProductDesc"
|
android:id="@+id/etProductDesc"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"
|
android:hint="Enter description"
|
||||||
android:hint="Enter product description"
|
|
||||||
android:inputType="textMultiLine"
|
android:inputType="textMultiLine"
|
||||||
android:minLines="1" />
|
android:minLines="2"
|
||||||
|
|
||||||
<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:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Price -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -126,24 +127,9 @@
|
|||||||
android:id="@+id/etProductPrice"
|
android:id="@+id/etProductPrice"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter price"
|
android:hint="0.00"
|
||||||
android:inputType="numberDecimal"
|
android:inputType="numberDecimal"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
<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"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp"
|
android:paddingStart="16dp"
|
||||||
android:background="@android:color/white">
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:background="@color/white">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvAdopterName"
|
android:id="@+id/tvAdoptionCustomerName"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="Adopter Name"
|
android:ellipsize="end"
|
||||||
android:textColor="#000000"
|
android:maxLines="1"
|
||||||
|
android:text="Customer Name"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
@@ -26,14 +31,14 @@
|
|||||||
android:id="@+id/tvAdoptionStatus"
|
android:id="@+id/tvAdoptionStatus"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="#4CAF50"
|
|
||||||
android:paddingStart="8dp"
|
android:paddingStart="8dp"
|
||||||
android:paddingTop="4dp"
|
android:paddingTop="3dp"
|
||||||
android:paddingEnd="8dp"
|
android:paddingEnd="8dp"
|
||||||
android:paddingBottom="4dp"
|
android:paddingBottom="3dp"
|
||||||
android:text="Status"
|
android:text="Status"
|
||||||
android:textColor="#FFFFFF"
|
android:textAllCaps="true"
|
||||||
android:textSize="12sp" />
|
android:textColor="@color/white"
|
||||||
|
android:textSize="11sp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -42,23 +47,43 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:text="Pet: name"
|
android:ellipsize="end"
|
||||||
android:textColor="#666666"
|
android:maxLines="1"
|
||||||
|
android:text="Pet Name"
|
||||||
|
android:textColor="#888888"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tvAdoptionDate"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:orientation="horizontal"
|
||||||
android:text="Date: "
|
android:gravity="center_vertical"
|
||||||
android:textColor="#666666"
|
android:layout_marginTop="8dp">
|
||||||
android:textSize="14sp" />
|
|
||||||
|
<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
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="#EEEEEE"
|
android:background="#F0F0F0"
|
||||||
android:layout_marginTop="12dp"/>
|
android:layout_marginTop="12dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -2,80 +2,91 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="horizontal"
|
||||||
|
android:background="@color/white"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="16dp"
|
||||||
android:paddingBottom="16dp"
|
android:paddingBottom="16dp"
|
||||||
android:background="@color/white">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<!-- Checkbox (visible only in bulk-delete selection mode) -->
|
||||||
android:layout_width="match_parent"
|
<CheckBox
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/cbSelectInventory"
|
||||||
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"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:text="Category"
|
android:visibility="gone"
|
||||||
android:textColor="#888888"
|
android:clickable="false"
|
||||||
android:textSize="13sp" />
|
android:focusable="false"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical">
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:layout_marginTop="8dp">
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tvQuantity"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:orientation="horizontal"
|
||||||
android:text="Qty: 0"
|
android:gravity="center_vertical">
|
||||||
android:textSize="13sp" />
|
|
||||||
|
<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
|
<TextView
|
||||||
android:id="@+id/tvInvSupplier"
|
android:id="@+id/tvInventoryId"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Supplier: "
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="Inv ID: —"
|
||||||
android:textColor="#888888"
|
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>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="#F0F0F0"
|
|
||||||
android:layout_marginTop="12dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
@@ -15,68 +16,96 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
<TextView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/tvPetName"
|
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_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:ellipsize="end"
|
android:orientation="vertical">
|
||||||
android:maxLines="1"
|
|
||||||
android:text="Pet Name"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tvPetStatus"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:orientation="horizontal"
|
||||||
android:paddingStart="8dp"
|
android:gravity="center_vertical">
|
||||||
android:paddingTop="3dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:paddingBottom="3dp"
|
|
||||||
android:text="Status"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="11sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
<TextView
|
||||||
|
android:id="@+id/tvPetName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android: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
|
<TextView
|
||||||
android:id="@+id/tvPetSpeciesBreed"
|
android:id="@+id/tvPetStatus"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:paddingStart="8dp"
|
||||||
android:ellipsize="end"
|
android:paddingTop="3dp"
|
||||||
android:maxLines="1"
|
android:paddingEnd="8dp"
|
||||||
android:text="Breed"
|
android:paddingBottom="3dp"
|
||||||
android:textColor="#888888"
|
android:text="Status"
|
||||||
android:textSize="14sp" />
|
android:textAllCaps="true"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="11sp" />
|
||||||
|
|
||||||
<LinearLayout
|
</LinearLayout>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:layout_marginTop="8dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvPetPrice"
|
android:id="@+id/tvPetSpeciesBreed"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_marginTop="4dp"
|
||||||
android:text="$126.00"
|
android:ellipsize="end"
|
||||||
android:textColor="@color/accent_coral"
|
android:maxLines="1"
|
||||||
android:textSize="16sp"
|
android:text="Breed"
|
||||||
android:textStyle="bold" />
|
android:textColor="#888888"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tvPetAge"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:orientation="horizontal"
|
||||||
android:text="1"
|
android:gravity="center_vertical"
|
||||||
android:textColor="#888888"
|
android:layout_marginTop="8dp">
|
||||||
android:textSize="13sp" />
|
|
||||||
|
<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>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -9,34 +9,27 @@
|
|||||||
android:paddingBottom="16dp"
|
android:paddingBottom="16dp"
|
||||||
android:background="@color/white">
|
android:background="@color/white">
|
||||||
|
|
||||||
<LinearLayout
|
<TextView
|
||||||
|
android:id="@+id/tvProductName"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:ellipsize="end"
|
||||||
android:gravity="center_vertical">
|
android:maxLines="1"
|
||||||
|
android:text="Product Name"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvProductName"
|
android:id="@+id/tvProductCategory"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_marginTop="4dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="Product Name"
|
android:text="Category"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="#888888"
|
||||||
android:textSize="18sp"
|
android:textSize="14sp" />
|
||||||
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
|
<TextView
|
||||||
android:id="@+id/tvProductDesc"
|
android:id="@+id/tvProductDesc"
|
||||||
@@ -44,8 +37,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="2"
|
||||||
android:text="Description"
|
android:text="Product Description"
|
||||||
android:textColor="#888888"
|
android:textColor="#888888"
|
||||||
android:textSize="13sp" />
|
android:textSize="13sp" />
|
||||||
|
|
||||||
@@ -57,21 +50,14 @@
|
|||||||
android:layout_marginTop="8dp">
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvProductCategory"
|
android:id="@+id/tvProductPrice"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="Category"
|
android:text="$0.00"
|
||||||
android:textColor="#888888"
|
android:textColor="@color/accent_coral"
|
||||||
android:textSize="13sp" />
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
<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" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</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_available">#2ECC71</color>
|
||||||
<color name="status_adopted">#E74C3C</color>
|
<color name="status_adopted">#E74C3C</color>
|
||||||
<color name="accent_blue">#3498DB</color>
|
<color name="accent_blue">#3498DB</color>
|
||||||
|
<color name="spinner_text">#000000</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -2,4 +2,5 @@
|
|||||||
<string name="app_name">Leons Pet Store</string>
|
<string name="app_name">Leons Pet Store</string>
|
||||||
<!-- TODO: Remove or change this placeholder text -->
|
<!-- TODO: Remove or change this placeholder text -->
|
||||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
|
<string name="pet_profile_image_desc">Pet Profile Image</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -10,6 +10,11 @@
|
|||||||
<item name="android:statusBarColor">@color/primary_dark</item>
|
<item name="android:statusBarColor">@color/primary_dark</item>
|
||||||
<item name="android:windowLightStatusBar">false</item>
|
<item name="android:windowLightStatusBar">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="CircleImageView" parent="">
|
||||||
|
<item name="cornerSize">50%</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="Widget.App.EditText" parent="Widget.AppCompat.EditText">
|
<style name="Widget.App.EditText" parent="Widget.AppCompat.EditText">
|
||||||
<item name="android:textColor">@color/text_dark</item>
|
<item name="android:textColor">@color/text_dark</item>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,3 +9,10 @@
|
|||||||
# Copy this file to local.properties and update the path below:
|
# Copy this file to local.properties and update the path below:
|
||||||
|
|
||||||
sdk.dir=/path/to/your/android/sdk
|
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": {
|
"info": {
|
||||||
"name": "PetShop API Complete Collection",
|
"name": "PetShop Complete Collection",
|
||||||
"_postman_id": "petshop-api-complete-v1",
|
"_postman_id": "petshop-api-complete-v1",
|
||||||
"description": "Complete API collection with all 95+ verified endpoints",
|
"description": "Complete API collection with all 95+ verified endpoints",
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public class DevStackApplication {
|
|||||||
docker.ensureDockerAvailable();
|
docker.ensureDockerAvailable();
|
||||||
docker.startDatabase();
|
docker.startDatabase();
|
||||||
context = new SpringApplicationBuilder(BackendApplication.class)
|
context = new SpringApplicationBuilder(BackendApplication.class)
|
||||||
|
.profiles("local")
|
||||||
.initializers(new FlywayContextInitializer())
|
.initializers(new FlywayContextInitializer())
|
||||||
.run(args);
|
.run(args);
|
||||||
context.addApplicationListener(event -> {
|
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;
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
import com.petshop.backend.dto.analytics.DashboardResponse;
|
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.service.AnalyticsService;
|
||||||
|
import com.petshop.backend.util.AuthenticationHelper;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/analytics")
|
@RequestMapping("/api/v1/analytics")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasAnyRole('ADMIN', 'STAFF')")
|
||||||
public class AnalyticsController {
|
public class AnalyticsController {
|
||||||
|
|
||||||
private final AnalyticsService analyticsService;
|
private final AnalyticsService analyticsService;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
|
||||||
public AnalyticsController(AnalyticsService analyticsService) {
|
public AnalyticsController(AnalyticsService analyticsService, UserRepository userRepository) {
|
||||||
this.analyticsService = analyticsService;
|
this.analyticsService = analyticsService;
|
||||||
|
this.userRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/dashboard")
|
@GetMapping("/dashboard")
|
||||||
public ResponseEntity<DashboardResponse> getDashboard(
|
public ResponseEntity<DashboardResponse> getDashboard(
|
||||||
@RequestParam(defaultValue = "30") int days,
|
@RequestParam(defaultValue = "30") int days,
|
||||||
@RequestParam(defaultValue = "10") int top) {
|
@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
|
@GetMapping
|
||||||
public ResponseEntity<Page<CategoryResponse>> getAllCategories(
|
public ResponseEntity<Page<CategoryResponse>> getAllCategories(
|
||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
|
@RequestParam(required = false) String type,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
return ResponseEntity.ok(categoryService.getAllCategories(q, pageable));
|
return ResponseEntity.ok(categoryService.getAllCategories(q, type, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@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.ConversationResponse;
|
||||||
import com.petshop.backend.dto.chat.MessageRequest;
|
import com.petshop.backend.dto.chat.MessageRequest;
|
||||||
import com.petshop.backend.dto.chat.MessageResponse;
|
import com.petshop.backend.dto.chat.MessageResponse;
|
||||||
|
import com.petshop.backend.dto.chat.UpdateConversationRequest;
|
||||||
import com.petshop.backend.entity.User;
|
import com.petshop.backend.entity.User;
|
||||||
import com.petshop.backend.repository.CustomerRepository;
|
import com.petshop.backend.repository.CustomerRepository;
|
||||||
import com.petshop.backend.repository.UserRepository;
|
import com.petshop.backend.repository.UserRepository;
|
||||||
@@ -96,4 +97,13 @@ public class ChatController {
|
|||||||
chatRealtimeService.publishConversationUpdate(id);
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
return ResponseEntity.ok(conversation);
|
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")
|
@GetMapping("/stores")
|
||||||
public ResponseEntity<List<DropdownOption>> getStores() {
|
public ResponseEntity<List<DropdownOption>> getStores() {
|
||||||
return ResponseEntity.ok(
|
return ResponseEntity.ok(
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ public class PetController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<PetResponse>> getAllPets(
|
public ResponseEntity<Page<PetResponse>> getAllPets(
|
||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
|
@RequestParam(required = false) String species,
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
return ResponseEntity.ok(petService.getAllPets(q, pageable));
|
return ResponseEntity.ok(petService.getAllPets(q, species, status, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ public class ProductController {
|
|||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<Page<ProductResponse>> getAllProducts(
|
public ResponseEntity<Page<ProductResponse>> getAllProducts(
|
||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
|
@RequestParam(required = false) Long categoryId,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
return ResponseEntity.ok(productService.getAllProducts(q, pageable));
|
return ResponseEntity.ok(productService.getAllProducts(q, categoryId, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -9,15 +9,19 @@ public class DashboardResponse {
|
|||||||
private InventorySummary inventorySummary;
|
private InventorySummary inventorySummary;
|
||||||
private List<TopProduct> topProducts;
|
private List<TopProduct> topProducts;
|
||||||
private List<DailySales> dailySales;
|
private List<DailySales> dailySales;
|
||||||
|
private List<PaymentMethodData> paymentMethods;
|
||||||
|
private List<EmployeePerformanceData> employeePerformance;
|
||||||
|
|
||||||
public DashboardResponse() {
|
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.salesSummary = salesSummary;
|
||||||
this.inventorySummary = inventorySummary;
|
this.inventorySummary = inventorySummary;
|
||||||
this.topProducts = topProducts;
|
this.topProducts = topProducts;
|
||||||
this.dailySales = dailySales;
|
this.dailySales = dailySales;
|
||||||
|
this.paymentMethods = paymentMethods;
|
||||||
|
this.employeePerformance = employeePerformance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SalesSummary getSalesSummary() {
|
public SalesSummary getSalesSummary() {
|
||||||
@@ -52,17 +56,33 @@ public class DashboardResponse {
|
|||||||
this.dailySales = dailySales;
|
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
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
DashboardResponse that = (DashboardResponse) o;
|
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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales);
|
return Objects.hash(salesSummary, inventorySummary, topProducts, dailySales, paymentMethods, employeePerformance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -72,6 +92,8 @@ public class DashboardResponse {
|
|||||||
", inventorySummary=" + inventorySummary +
|
", inventorySummary=" + inventorySummary +
|
||||||
", topProducts=" + topProducts +
|
", topProducts=" + topProducts +
|
||||||
", dailySales=" + dailySales +
|
", dailySales=" + dailySales +
|
||||||
|
", paymentMethods=" + paymentMethods +
|
||||||
|
", employeePerformance=" + employeePerformance +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,15 +102,17 @@ public class DashboardResponse {
|
|||||||
private Long totalSales;
|
private Long totalSales;
|
||||||
private BigDecimal totalRefunds;
|
private BigDecimal totalRefunds;
|
||||||
private Long totalRefundCount;
|
private Long totalRefundCount;
|
||||||
|
private Long totalItemsSold;
|
||||||
|
|
||||||
public SalesSummary() {
|
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.totalRevenue = totalRevenue;
|
||||||
this.totalSales = totalSales;
|
this.totalSales = totalSales;
|
||||||
this.totalRefunds = totalRefunds;
|
this.totalRefunds = totalRefunds;
|
||||||
this.totalRefundCount = totalRefundCount;
|
this.totalRefundCount = totalRefundCount;
|
||||||
|
this.totalItemsSold = totalItemsSold;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getTotalRevenue() {
|
public BigDecimal getTotalRevenue() {
|
||||||
@@ -123,17 +147,25 @@ public class DashboardResponse {
|
|||||||
this.totalRefundCount = totalRefundCount;
|
this.totalRefundCount = totalRefundCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Long getTotalItemsSold() {
|
||||||
|
return totalItemsSold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTotalItemsSold(Long totalItemsSold) {
|
||||||
|
this.totalItemsSold = totalItemsSold;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
SalesSummary that = (SalesSummary) o;
|
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
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount);
|
return Objects.hash(totalRevenue, totalSales, totalRefunds, totalRefundCount, totalItemsSold);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -143,10 +175,69 @@ public class DashboardResponse {
|
|||||||
", totalSales=" + totalSales +
|
", totalSales=" + totalSales +
|
||||||
", totalRefunds=" + totalRefunds +
|
", totalRefunds=" + totalRefunds +
|
||||||
", totalRefundCount=" + totalRefundCount +
|
", 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 {
|
public static class InventorySummary {
|
||||||
private Long totalProducts;
|
private Long totalProducts;
|
||||||
private Long lowStockProducts;
|
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);
|
Optional<Category> findByCategoryName(String categoryName);
|
||||||
|
|
||||||
@Query("SELECT c FROM Category c WHERE " +
|
@Query("SELECT c FROM Category c WHERE " +
|
||||||
"LOWER(c.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"(:q IS NULL OR LOWER(c.categoryName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(c.categoryType) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
"LOWER(c.categoryType) LIKE LOWER(CONCAT('%', :q, '%'))")
|
"(:type IS NULL OR LOWER(c.categoryType) = LOWER(:type))")
|
||||||
Page<Category> searchCategories(@Param("q") String query, Pageable pageable);
|
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> {
|
public interface PetRepository extends JpaRepository<Pet, Long> {
|
||||||
|
|
||||||
@Query("SELECT p FROM Pet p WHERE " +
|
@Query("SELECT p FROM Pet p WHERE " +
|
||||||
"LOWER(p.petName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"(: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 " +
|
||||||
"LOWER(p.petSpecies) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"(:species IS NULL OR LOWER(p.petSpecies) = LOWER(:species)) AND " +
|
||||||
"LOWER(p.petBreed) LIKE LOWER(CONCAT('%', :q, '%'))")
|
"(:status IS NULL OR LOWER(p.petStatus) = LOWER(:status))")
|
||||||
Page<Pet> searchPets(@Param("q") String query, Pageable pageable);
|
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> {
|
public interface ProductRepository extends JpaRepository<Product, Long> {
|
||||||
|
|
||||||
@Query("SELECT p FROM Product p WHERE " +
|
@Query("SELECT p FROM Product p WHERE " +
|
||||||
"LOWER(p.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR " +
|
"(:q IS NULL OR LOWER(p.prodName) LIKE LOWER(CONCAT('%', :q, '%')) OR LOWER(COALESCE(p.prodDesc, '')) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
"LOWER(p.prodDesc) LIKE LOWER(CONCAT('%', :q, '%'))")
|
"(:categoryId IS NULL OR p.category.categoryId = :categoryId)")
|
||||||
Page<Product> searchProducts(@Param("q") String query, Pageable pageable);
|
Page<Product> searchProducts(@Param("q") String query, @Param("categoryId") Long categoryId, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.petshop.backend.service;
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
import com.petshop.backend.dto.analytics.DashboardResponse;
|
import com.petshop.backend.dto.analytics.DashboardResponse;
|
||||||
|
import com.petshop.backend.entity.Employee;
|
||||||
import com.petshop.backend.entity.Inventory;
|
import com.petshop.backend.entity.Inventory;
|
||||||
import com.petshop.backend.entity.Product;
|
import com.petshop.backend.entity.Product;
|
||||||
import com.petshop.backend.entity.Sale;
|
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.InventoryRepository;
|
||||||
import com.petshop.backend.repository.ProductRepository;
|
import com.petshop.backend.repository.ProductRepository;
|
||||||
import com.petshop.backend.repository.SaleRepository;
|
import com.petshop.backend.repository.SaleRepository;
|
||||||
@@ -23,28 +26,33 @@ public class AnalyticsService {
|
|||||||
private final SaleRepository saleRepository;
|
private final SaleRepository saleRepository;
|
||||||
private final InventoryRepository inventoryRepository;
|
private final InventoryRepository inventoryRepository;
|
||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
|
private final EmployeeRepository employeeRepository;
|
||||||
|
|
||||||
public AnalyticsService(SaleRepository saleRepository,
|
public AnalyticsService(SaleRepository saleRepository,
|
||||||
InventoryRepository inventoryRepository, ProductRepository productRepository) {
|
InventoryRepository inventoryRepository, ProductRepository productRepository, EmployeeRepository employeeRepository) {
|
||||||
this.saleRepository = saleRepository;
|
this.saleRepository = saleRepository;
|
||||||
this.inventoryRepository = inventoryRepository;
|
this.inventoryRepository = inventoryRepository;
|
||||||
this.productRepository = productRepository;
|
this.productRepository = productRepository;
|
||||||
|
this.employeeRepository = employeeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@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);
|
LocalDateTime startDate = LocalDateTime.now().minusDays(days);
|
||||||
|
|
||||||
List<Sale> sales = saleRepository.findAll().stream()
|
List<Sale> sales = saleRepository.findAll().stream()
|
||||||
.filter(sale -> sale.getSaleDate().isAfter(startDate))
|
.filter(sale -> sale.getSaleDate().isAfter(startDate))
|
||||||
|
.filter(sale -> includeSaleForUser(sale, user))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
DashboardResponse.SalesSummary salesSummary = calculateSalesSummary(sales);
|
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.TopProduct> topProducts = calculateTopProducts(sales, top);
|
||||||
List<DashboardResponse.DailySales> dailySales = calculateDailySales(sales, days);
|
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) {
|
private DashboardResponse.SalesSummary calculateSalesSummary(List<Sale> sales) {
|
||||||
@@ -66,7 +74,13 @@ public class AnalyticsService {
|
|||||||
.filter(Sale::getIsRefund)
|
.filter(Sale::getIsRefund)
|
||||||
.count();
|
.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() {
|
private DashboardResponse.InventorySummary calculateInventorySummary() {
|
||||||
@@ -93,6 +107,9 @@ public class AnalyticsService {
|
|||||||
Map<Long, DashboardResponse.TopProduct> productSalesMap = new HashMap<>();
|
Map<Long, DashboardResponse.TopProduct> productSalesMap = new HashMap<>();
|
||||||
|
|
||||||
for (Sale sale : sales) {
|
for (Sale sale : sales) {
|
||||||
|
if (sale.getIsRefund()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (var item : sale.getItems()) {
|
for (var item : sale.getItems()) {
|
||||||
Long productId = item.getProduct().getProdId();
|
Long productId = item.getProduct().getProdId();
|
||||||
String productName = item.getProduct().getProdName();
|
String productName = item.getProduct().getProdName();
|
||||||
@@ -128,6 +145,9 @@ public class AnalyticsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (Sale sale : sales) {
|
for (Sale sale : sales) {
|
||||||
|
if (sale.getIsRefund()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
LocalDate saleDate = sale.getSaleDate().toLocalDate();
|
LocalDate saleDate = sale.getSaleDate().toLocalDate();
|
||||||
if (dailySalesMap.containsKey(saleDate)) {
|
if (dailySalesMap.containsKey(saleDate)) {
|
||||||
DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate);
|
DashboardResponse.DailySales dailySale = dailySalesMap.get(saleDate);
|
||||||
@@ -138,4 +158,50 @@ public class AnalyticsService {
|
|||||||
|
|
||||||
return new ArrayList<>(dailySalesMap.values());
|
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