Appointments
validation, time slots for booking appointment , date, store. connected to service Type, pet name, customer name. Loads from database
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
package com.example.petstoremobile.adapters;
|
||||
|
||||
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -10,26 +8,24 @@ import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.models.Appointment;
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import java.util.List;
|
||||
|
||||
public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.AppointmentViewHolder> {
|
||||
|
||||
private List<Appointment> appointmentList;
|
||||
private List<AppointmentDTO> appointmentList;
|
||||
private OnAppointmentClickListener appointmentClickListener;
|
||||
|
||||
// Interface for appointment click on recycler view
|
||||
public interface OnAppointmentClickListener {
|
||||
void onAppointmentClick(int position);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
public AppointmentAdapter(List<Appointment> appointmentList, OnAppointmentClickListener appointmentClickListener) {
|
||||
public AppointmentAdapter(List<AppointmentDTO> appointmentList,
|
||||
OnAppointmentClickListener appointmentClickListener) {
|
||||
this.appointmentList = appointmentList;
|
||||
this.appointmentClickListener = appointmentClickListener;
|
||||
}
|
||||
|
||||
// Get the controls of each row in recycler view
|
||||
public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvCustomerName, tvPetName, tvServiceType, tvDateTime, tvAppointmentStatus;
|
||||
|
||||
@@ -43,7 +39,6 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new row view
|
||||
@NonNull
|
||||
@Override
|
||||
public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
@@ -51,31 +46,34 @@ public class AppointmentAdapter extends RecyclerView.Adapter<AppointmentAdapter.
|
||||
return new AppointmentViewHolder(v);
|
||||
}
|
||||
|
||||
// Populate the row with appointment data
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int position) {
|
||||
Appointment appointment = appointmentList.get(position);
|
||||
AppointmentDTO a = appointmentList.get(position);
|
||||
|
||||
holder.tvCustomerName.setText(appointment.getCustomerName());
|
||||
holder.tvPetName.setText("Pet: " + appointment.getPetName());
|
||||
holder.tvServiceType.setText(appointment.getServiceType());
|
||||
holder.tvDateTime.setText(appointment.getAppointmentDate() + " at " + appointment.getAppointmentTime());
|
||||
holder.tvAppointmentStatus.setText(appointment.getStatus());
|
||||
holder.tvCustomerName.setText(a.getCustomerName() != null ? a.getCustomerName() : "");
|
||||
holder.tvPetName.setText("Pet: " + (a.getPetName() != null ? a.getPetName() : ""));
|
||||
holder.tvServiceType.setText(a.getServiceType() != null ? a.getServiceType() : "");
|
||||
holder.tvDateTime.setText((a.getAppointmentDate() != null ? a.getAppointmentDate() : "") +
|
||||
" at " + (a.getAppointmentTime() != null ? a.getAppointmentTime() : ""));
|
||||
|
||||
// Set the status color depending on appointment status
|
||||
switch (appointment.getStatus()) {
|
||||
case "Confirmed":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#4CAF50"));
|
||||
String status = a.getStatus() != null ? a.getStatus() : "";
|
||||
holder.tvAppointmentStatus.setText(status);
|
||||
|
||||
switch (status) {
|
||||
case "Booked":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#2196F3")); // blue
|
||||
break;
|
||||
case "Pending":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#FF9800"));
|
||||
case "Completed":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#4CAF50")); // green
|
||||
break;
|
||||
case "Cancelled":
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#F44336")); // red
|
||||
break;
|
||||
default:
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
||||
holder.tvAppointmentStatus.setBackgroundColor(Color.parseColor("#9E9E9E")); // gray
|
||||
break;
|
||||
}
|
||||
|
||||
// When a row is clicked, open the detail view
|
||||
holder.itemView.setOnClickListener(v -> appointmentClickListener.onAppointmentClick(position));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,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,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;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +1,38 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class CustomerDTO {
|
||||
@SerializedName("customerId")
|
||||
private Long customerId;
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String email;
|
||||
private String phone;
|
||||
|
||||
public CustomerDTO() {}
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
public Long getCustomerId() {
|
||||
return customerId;
|
||||
}
|
||||
|
||||
public void setCustomerId(Long customerId) {
|
||||
this.customerId = customerId;
|
||||
}
|
||||
|
||||
public String getFirstName() {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
public void setFirstName(String firstName) {
|
||||
this.firstName = firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return lastName;
|
||||
}
|
||||
|
||||
public void setLastName(String lastName) {
|
||||
this.lastName = lastName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return (firstName != null ? firstName : "") + " " + (lastName != null ? lastName : "");
|
||||
return firstName + " " + lastName;
|
||||
}
|
||||
|
||||
public String getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public String getUpdatedAt() {
|
||||
return updatedAt;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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; }
|
||||
}
|
||||
@@ -21,6 +21,9 @@ import com.example.petstoremobile.fragments.listfragments.AdoptionFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.AppointmentFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.ProductFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.ProductSupplierFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.PurchaseOrderFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.SaleFragment;
|
||||
|
||||
//The Fragment for the displaying the list of entities to be viewed
|
||||
public class ListFragment extends Fragment {
|
||||
@@ -31,7 +34,7 @@ public class ListFragment extends Fragment {
|
||||
|
||||
// Adoptions, Appointments, Inventory, Products
|
||||
|
||||
private LinearLayout drawerAdoptions, drawerAppointments, drawerInventory, drawerProducts;
|
||||
private LinearLayout drawerAdoptions, drawerAppointments, drawerInventory, drawerProducts, drawerProductSupplier, drawerPurchaseOrderView, drawerSale;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -48,6 +51,10 @@ public class ListFragment extends Fragment {
|
||||
drawerAppointments = view.findViewById(R.id.drawerAppointments);
|
||||
drawerInventory = view.findViewById(R.id.drawerInventory);
|
||||
drawerProducts = view.findViewById(R.id.drawerProducts);
|
||||
drawerProductSupplier=view.findViewById(R.id.drawerProductSupplier);
|
||||
drawerSale=view.findViewById(R.id.drawerSale);
|
||||
drawerPurchaseOrderView=view.findViewById(R.id.drawerPurchaseOrderView);
|
||||
|
||||
|
||||
//needed to disable touches on the innerContainer while the drawer is open
|
||||
touchBlocker = view.findViewById(R.id.touchBlocker);
|
||||
@@ -108,7 +115,7 @@ public class ListFragment extends Fragment {
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//Appoinment
|
||||
//Appointment
|
||||
drawerAppointments.setOnClickListener(v -> {
|
||||
loadFragment(new AppointmentFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
@@ -126,6 +133,27 @@ public class ListFragment extends Fragment {
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//ProductSupplier
|
||||
|
||||
drawerProductSupplier.setOnClickListener(v -> {
|
||||
loadFragment(new ProductSupplierFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//Purchase
|
||||
|
||||
drawerPurchaseOrderView.setOnClickListener(v -> {
|
||||
loadFragment(new PurchaseOrderFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
//Sale
|
||||
|
||||
drawerSale.setOnClickListener(v -> {
|
||||
loadFragment(new SaleFragment());
|
||||
drawerLayout.closeDrawers();
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,52 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
// Added search/filter bar to filter appointments by customer name or service type.
|
||||
// Added pull-to-refresh using SwipeRefreshLayout.
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
||||
import com.example.petstoremobile.api.AppointmentApi;
|
||||
import com.example.petstoremobile.api.PetApi;
|
||||
import com.example.petstoremobile.api.ServiceApi;
|
||||
import com.example.petstoremobile.api.RetrofitClient;
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.AppointmentDetailFragment;
|
||||
import com.example.petstoremobile.models.Appointment;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class AppointmentFragment extends Fragment implements AppointmentAdapter.OnAppointmentClickListener {
|
||||
|
||||
private List<Appointment> appointmentList = new ArrayList<>(); // full data list
|
||||
private List<Appointment> filteredList = new ArrayList<>(); // filtered display list
|
||||
private List<AppointmentDTO> appointmentList = new ArrayList<>();
|
||||
private List<AppointmentDTO> filteredList = new ArrayList<>();
|
||||
private List<PetDTO> petList = new ArrayList<>();
|
||||
private List<ServiceDTO> serviceList = new ArrayList<>();
|
||||
|
||||
private AppointmentAdapter adapter;
|
||||
private AppointmentApi api;
|
||||
private SwipeRefreshLayout swipeRefreshLayout;
|
||||
private EditText etSearch;
|
||||
private ImageButton hamburger;
|
||||
@@ -39,51 +56,57 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_appointment, container, false);
|
||||
|
||||
api = RetrofitClient.getAppointmentApi(requireContext());
|
||||
hamburger = view.findViewById(R.id.btnHamburger);
|
||||
|
||||
loadAppointmentData(); // TODO: Replace with actual API call when backend is ready
|
||||
setupRecyclerView(view);
|
||||
setupSearch(view);
|
||||
setupSwipeRefresh(view);
|
||||
loadAppointmentData();
|
||||
loadPets();
|
||||
loadServices();
|
||||
|
||||
FloatingActionButton fabAddAppointment = view.findViewById(R.id.fabAddAppointment);
|
||||
fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1));
|
||||
|
||||
//Make the hamburger button open the drawer from listFragment
|
||||
FloatingActionButton fabAdd = view.findViewById(R.id.fabAddAppointment);
|
||||
fabAdd.setOnClickListener(v -> openAppointmentDetails(-1));
|
||||
|
||||
hamburger.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
//if list fragment is found then use its helper function to open the drawer
|
||||
if (listFragment != null) {
|
||||
if (listFragment != null)
|
||||
listFragment.openDrawer();
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
// Sets up the search bar to filter appointments by customer name or service type
|
||||
private void setupSearch(View view) {
|
||||
etSearch = view.findViewById(R.id.etSearchAppointment);
|
||||
etSearch.addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
filterAppointments(s.toString());
|
||||
}
|
||||
@Override public void afterTextChanged(Editable s) {}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Filters the appointment list based on the search query
|
||||
private void filterAppointments(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(appointmentList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (Appointment a : appointmentList) {
|
||||
if (a.getCustomerName().toLowerCase().contains(lower)
|
||||
|| a.getServiceType().toLowerCase().contains(lower)
|
||||
|| a.getPetName().toLowerCase().contains(lower)) {
|
||||
for (AppointmentDTO a : appointmentList) {
|
||||
if ((a.getCustomerName() != null && a.getCustomerName().toLowerCase().contains(lower))
|
||||
|| (a.getServiceType() != null && a.getServiceType().toLowerCase().contains(lower))
|
||||
|| (a.getPetName() != null && a.getPetName().toLowerCase().contains(lower))) {
|
||||
filteredList.add(a);
|
||||
}
|
||||
}
|
||||
@@ -91,43 +114,33 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Sets up pull-to-refresh: reloads data when user swipes down
|
||||
private void setupSwipeRefresh(View view) {
|
||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshAppointment);
|
||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
||||
loadAppointmentData(); // TODO: Replace with actual API call when backend is ready
|
||||
filterAppointments(etSearch.getText().toString());
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
});
|
||||
swipeRefreshLayout.setOnRefreshListener(this::loadAppointmentData);
|
||||
}
|
||||
|
||||
private void openAppointmentDetails(int position) {
|
||||
AppointmentDetailFragment detailFragment = new AppointmentDetailFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt("position", position);
|
||||
|
||||
if (position != -1) {
|
||||
Appointment appointment = filteredList.get(position);
|
||||
// Find the real position in the full list for save/delete callbacks
|
||||
int realPosition = appointmentList.indexOf(appointment);
|
||||
args.putInt("position", realPosition);
|
||||
args.putInt("appointmentId", appointment.getAppointmentId());
|
||||
args.putString("customerName", appointment.getCustomerName());
|
||||
args.putString("petName", appointment.getPetName());
|
||||
args.putString("serviceType", appointment.getServiceType());
|
||||
args.putString("appointmentDate", appointment.getAppointmentDate());
|
||||
args.putString("appointmentTime", appointment.getAppointmentTime());
|
||||
args.putString("status", appointment.getStatus());
|
||||
AppointmentDTO a = filteredList.get(position);
|
||||
args.putLong("appointmentId", a.getAppointmentId());
|
||||
args.putString("appointmentDate", a.getAppointmentDate());
|
||||
args.putString("appointmentTime", a.getAppointmentTime());
|
||||
args.putString("appointmentStatus", a.getAppointmentStatus());
|
||||
// IDs for pre-selecting spinners
|
||||
if (a.getPetID() != null) args.putLong("petId", a.getPetID());
|
||||
if (a.getServiceId() != null) args.putLong("serviceId", a.getServiceId());
|
||||
if (a.getCustomerId() != null) args.putLong("customerId", a.getCustomerId());
|
||||
if (a.getStoreId() != null) args.putLong("storeId", a.getStoreId());
|
||||
}
|
||||
|
||||
detailFragment.setArguments(args);
|
||||
detailFragment.setAppointmentFragment(this);
|
||||
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.loadFragment(detailFragment);
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.loadFragment(detailFragment);
|
||||
}
|
||||
|
||||
public void onAppointmentSaved(int position, Appointment appointment) {
|
||||
public void onAppointmentSaved(int position, AppointmentDTO appointment) {
|
||||
if (position == -1) {
|
||||
appointmentList.add(appointment);
|
||||
} else {
|
||||
@@ -146,21 +159,100 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
openAppointmentDetails(position);
|
||||
}
|
||||
|
||||
// Helper function to load hardcoded sample data
|
||||
// Replace with API call
|
||||
private void loadAppointmentData() {
|
||||
appointmentList.clear();
|
||||
appointmentList.add(new Appointment(1, "John Smith", "Buddy", "Grooming", "2026-03-10", "10:00 AM", "Confirmed"));
|
||||
appointmentList.add(new Appointment(2, "Jane Doe", "Luna", "Vet Checkup", "2026-03-11", "02:00 PM", "Pending"));
|
||||
appointmentList.add(new Appointment(3, "Bob Lee", "Max", "Training", "2026-03-12", "11:00 AM", "Confirmed"));
|
||||
appointmentList.add(new Appointment(4, "Alice Brown", "Milo", "Grooming", "2026-03-13", "03:00 PM", "Cancelled"));
|
||||
filteredList.clear();
|
||||
filteredList.addAll(appointmentList);
|
||||
if (swipeRefreshLayout != null)
|
||||
swipeRefreshLayout.setRefreshing(true);
|
||||
api.getAllAppointments(0, 100).enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<AppointmentDTO>> call,
|
||||
Response<PageResponse<AppointmentDTO>> response) {
|
||||
if (swipeRefreshLayout != null)
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
appointmentList.clear();
|
||||
appointmentList.addAll(response.body().getContent());
|
||||
filterAppointments(etSearch != null ? etSearch.getText().toString() : "");
|
||||
} else {
|
||||
Log.e("AppointmentFragment", "Error: " + response.message());
|
||||
Toast.makeText(getContext(), "Failed to load appointments", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<AppointmentDTO>> call, Throwable t) {
|
||||
if (swipeRefreshLayout != null)
|
||||
swipeRefreshLayout.setRefreshing(false);
|
||||
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
Log.e("AppointmentFragment", t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Load Pets
|
||||
private void loadPets() {
|
||||
PetApi petApi = RetrofitClient.getPetApi(requireContext());
|
||||
petApi.getAllPets(0,100).enqueue(new Callback<PageResponse<PetDTO>>() {
|
||||
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<PetDTO>> call, Response<PageResponse<PetDTO>> response) {
|
||||
if (response.isSuccessful() && response.body() !=null) {
|
||||
petList.clear();
|
||||
petList.addAll(response.body().getContent());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<PetDTO>> call, Throwable t) {
|
||||
|
||||
Log.e("AppointmentFragment", "Pet load error:" + t.getMessage());
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Load Services
|
||||
|
||||
private void loadServices() {
|
||||
ServiceApi serviceApi = RetrofitClient.getServiceApi(requireContext());
|
||||
|
||||
serviceApi.getAllServices(0,100).enqueue(new Callback<PageResponse<ServiceDTO>>() {
|
||||
@Override
|
||||
public void onResponse(Call<PageResponse<ServiceDTO>> call, Response<PageResponse<ServiceDTO>> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
serviceList.clear();
|
||||
serviceList.addAll(response.body().getContent());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PageResponse<ServiceDTO>> call, Throwable t) {
|
||||
Log.e("AppointmentFragmnet", "Service load error: " + t.getMessage());
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String getPetName(Long id) {
|
||||
for (PetDTO p : petList) {
|
||||
if (p.getPetId().equals(id)) return p.getPetName();
|
||||
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String getServiceName(Long id) {
|
||||
for (ServiceDTO s : serviceList) {
|
||||
if (s.getServiceId().equals(id))return s.getServiceName();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private void setupRecyclerView(View view) {
|
||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewAppointments);
|
||||
adapter = new AppointmentAdapter(filteredList, this); // adapter uses filteredList
|
||||
adapter = new AppointmentAdapter(filteredList, this);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
recyclerView.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@@ -1,178 +1,443 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
|
||||
// Uses InputValidator for detailed field validation and ActivityLogger to log all changes.
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.app.DatePickerDialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.api.*;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
import com.example.petstoremobile.fragments.listfragments.AppointmentFragment;
|
||||
import com.example.petstoremobile.models.Appointment;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import java.util.*;
|
||||
import retrofit2.*;
|
||||
|
||||
public class AppointmentDetailFragment extends Fragment {
|
||||
|
||||
private TextView tvMode, tvAppointmentId;
|
||||
private EditText etCustomerName, etPetName, etServiceType, etAppointmentDate, etAppointmentTime;
|
||||
private Spinner spinnerStatus;
|
||||
private Button btnSaveAppointment, btnDeleteAppointment, btnBack;
|
||||
private int appointmentId;
|
||||
private int position;
|
||||
private boolean isEditing = false;
|
||||
private AppointmentFragment appointmentFragment;
|
||||
private EditText etAppointmentDate;
|
||||
private Spinner spinnerPet, spinnerService, spinnerStatus, spinnerHour, spinnerMinute;
|
||||
private Spinner spinnerCustomer, spinnerStore;
|
||||
private Button btnSave, btnDelete, btnBack;
|
||||
|
||||
// Set the appointment fragment as parent so we refer back when save or delete is done
|
||||
public void setAppointmentFragment(AppointmentFragment fragment) {
|
||||
this.appointmentFragment = fragment;
|
||||
}
|
||||
private long appointmentId = -1;
|
||||
private boolean isEditing = false;
|
||||
private long preselectedPetId = -1;
|
||||
private long preselectedServiceId = -1;
|
||||
private long preselectedCustomerId = -1;
|
||||
private long preselectedStoreId = -1;
|
||||
|
||||
private List<PetDTO> petList = new ArrayList<>();
|
||||
private List<ServiceDTO> serviceList = new ArrayList<>();
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private List<AppointmentDTO> allAppointments = new ArrayList<>();
|
||||
|
||||
private final Integer[] HOURS = {9,10,11,12,13,14,15,16,17};
|
||||
private final Integer[] MINUTES = {0,15,30,45};
|
||||
private final String[] STATUSES = {"Booked","Completed","Cancelled"};
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_appointment_detail, container, false);
|
||||
|
||||
initViews(view);
|
||||
setupSpinner();
|
||||
setupSpinners();
|
||||
setupDatePicker();
|
||||
loadData();
|
||||
handleArguments();
|
||||
|
||||
btnBack.setOnClickListener(v -> {
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) {
|
||||
listFragment.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
});
|
||||
btnSaveAppointment.setOnClickListener(v -> saveAppointment());
|
||||
btnDeleteAppointment.setOnClickListener(v -> deleteAppointment());
|
||||
|
||||
btnBack.setOnClickListener(v -> navigateBack());
|
||||
btnSave.setOnClickListener(v -> saveAppointment());
|
||||
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||
return view;
|
||||
}
|
||||
|
||||
// Validates all fields using InputValidator, then saves the appointment
|
||||
private void saveAppointment() {
|
||||
// Validate all inputs using InputValidator utility
|
||||
if (!InputValidator.isNotEmpty(etCustomerName, "Customer Name")) return;
|
||||
if (!InputValidator.isNotEmpty(etPetName, "Pet Name")) return;
|
||||
if (!InputValidator.isNotEmpty(etServiceType, "Service Type")) return;
|
||||
if (!InputValidator.isValidDate(etAppointmentDate)) return;
|
||||
if (!InputValidator.isValidTime(etAppointmentTime)) return;
|
||||
private void initViews(View v) {
|
||||
tvMode = v.findViewById(R.id.tvApptMode);
|
||||
tvAppointmentId = v.findViewById(R.id.tvAppointmentId);
|
||||
etAppointmentDate= v.findViewById(R.id.etAppointmentDate);
|
||||
spinnerPet = v.findViewById(R.id.spinnerPet);
|
||||
spinnerService = v.findViewById(R.id.spinnerService);
|
||||
spinnerStatus = v.findViewById(R.id.spinnerAppointmentStatus);
|
||||
spinnerHour = v.findViewById(R.id.spinnerHour);
|
||||
spinnerMinute = v.findViewById(R.id.spinnerMinute);
|
||||
spinnerCustomer = v.findViewById(R.id.spinnerCustomer);
|
||||
spinnerStore = v.findViewById(R.id.spinnerStore);
|
||||
btnSave = v.findViewById(R.id.btnSaveAppointment);
|
||||
btnDelete = v.findViewById(R.id.btnDeleteAppointment);
|
||||
btnBack = v.findViewById(R.id.btnApptBack);
|
||||
}
|
||||
|
||||
String customerName = etCustomerName.getText().toString().trim();
|
||||
String petName = etPetName.getText().toString().trim();
|
||||
String serviceType = etServiceType.getText().toString().trim();
|
||||
String date = etAppointmentDate.getText().toString().trim();
|
||||
String time = etAppointmentTime.getText().toString().trim();
|
||||
String status = spinnerStatus.getSelectedItem().toString();
|
||||
private void setupSpinners() {
|
||||
spinnerStatus.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, STATUSES));
|
||||
|
||||
try {
|
||||
if (isEditing) {
|
||||
// TODO: Replace with actual API PUT call when backend is ready
|
||||
Appointment updated = new Appointment(appointmentId, customerName, petName, serviceType, date, time, status);
|
||||
if (appointmentFragment != null) appointmentFragment.onAppointmentSaved(position, updated);
|
||||
ActivityLogger.logChange(requireContext(), "Appointment", "UPDATED", appointmentId);
|
||||
Toast.makeText(getContext(), "Appointment updated.", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
// TODO: Replace with actual API POST call when backend is ready
|
||||
Appointment newAppt = new Appointment(0, customerName, petName, serviceType, date, time, status);
|
||||
if (appointmentFragment != null) appointmentFragment.onAppointmentSaved(-1, newAppt);
|
||||
ActivityLogger.log(requireContext(), "Added new Appointment for customer: " + customerName);
|
||||
Toast.makeText(getContext(), "Appointment added.", Toast.LENGTH_SHORT).show();
|
||||
String[] hours = new String[HOURS.length];
|
||||
for (int i = 0; i < HOURS.length; i++)
|
||||
hours[i] = String.format("%02d:00", HOURS[i]);
|
||||
spinnerHour.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, hours));
|
||||
spinnerMinute.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, new String[]{"00","15","30","45"}));
|
||||
}
|
||||
|
||||
private void setupDatePicker() {
|
||||
etAppointmentDate.setOnClickListener(v -> {
|
||||
Calendar c = Calendar.getInstance();
|
||||
DatePickerDialog d = new DatePickerDialog(requireContext(),
|
||||
(dp,y,m,d1) -> etAppointmentDate.setText(
|
||||
String.format("%04d-%02d-%02d", y, m+1, d1)),
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH));
|
||||
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
||||
d.show();
|
||||
});
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
loadPets();
|
||||
loadServices();
|
||||
loadCustomers();
|
||||
loadStores();
|
||||
loadAllAppointments();
|
||||
}
|
||||
|
||||
private void loadPets() {
|
||||
RetrofitClient.getPetApi(requireContext()).getAllPets(0, 200)
|
||||
.enqueue(new Callback<PageResponse<PetDTO>>() {
|
||||
public void onResponse(Call<PageResponse<PetDTO>> c, Response<PageResponse<PetDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
petList = r.body().getContent();
|
||||
populatePetSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<PetDTO>> c, Throwable t) {
|
||||
Log.e("APPT", "Pet load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populatePetSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Pet --");
|
||||
for (PetDTO p : petList) names.add(p.getPetName());
|
||||
spinnerPet.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedPetId != -1) {
|
||||
for (int i = 0; i < petList.size(); i++) {
|
||||
if (petList.get(i).getPetId().equals(preselectedPetId)) {
|
||||
spinnerPet.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
// Go back to list
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "AppointmentDetailFragment.saveAppointment", e);
|
||||
Toast.makeText(getContext(), "Error saving appointment.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes the appointment and logs the action
|
||||
private void deleteAppointment() {
|
||||
try {
|
||||
// TODO: Replace with actual API DELETE call when backend is ready
|
||||
if (appointmentFragment != null) appointmentFragment.onAppointmentDeleted(position);
|
||||
ActivityLogger.logChange(requireContext(), "Appointment", "DELETED", appointmentId);
|
||||
Toast.makeText(getContext(), "Appointment deleted.", Toast.LENGTH_SHORT).show();
|
||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.logException(requireContext(), "AppointmentDetailFragment.deleteAppointment", e);
|
||||
Toast.makeText(getContext(), "Error deleting appointment.", Toast.LENGTH_SHORT).show();
|
||||
private void loadServices() {
|
||||
RetrofitClient.getServiceApi(requireContext()).getAllServices(0, 200)
|
||||
.enqueue(new Callback<PageResponse<ServiceDTO>>() {
|
||||
public void onResponse(Call<PageResponse<ServiceDTO>> c, Response<PageResponse<ServiceDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null) {
|
||||
serviceList = r.body().getContent();
|
||||
populateServiceSpinner();
|
||||
}
|
||||
}
|
||||
public void onFailure(Call<PageResponse<ServiceDTO>> c, Throwable t) {
|
||||
Log.e("APPT", "Service load failed: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void populateServiceSpinner() {
|
||||
List<String> names = new ArrayList<>();
|
||||
names.add("-- Select Service --");
|
||||
for (ServiceDTO s : serviceList) names.add(s.getServiceName());
|
||||
spinnerService.setAdapter(new ArrayAdapter<>(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 ArrayAdapter<>(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 ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, names));
|
||||
if (preselectedStoreId != -1) {
|
||||
for (int i = 0; i < storeList.size(); i++) {
|
||||
if (storeList.get(i).getStoreId().equals(preselectedStoreId)) {
|
||||
spinnerStore.setSelection(i + 1); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAllAppointments() {
|
||||
RetrofitClient.getAppointmentApi(requireContext()).getAllAppointments(0, 500)
|
||||
.enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
||||
public void onResponse(Call<PageResponse<AppointmentDTO>> c, Response<PageResponse<AppointmentDTO>> r) {
|
||||
if (r.isSuccessful() && r.body() != null)
|
||||
allAppointments = r.body().getContent();
|
||||
}
|
||||
public void onFailure(Call<PageResponse<AppointmentDTO>> c, Throwable t) {}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
if (getArguments() != null && getArguments().containsKey("appointmentId")) {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("appointmentId")) {
|
||||
isEditing = true;
|
||||
appointmentId = getArguments().getInt("appointmentId");
|
||||
position = getArguments().getInt("position");
|
||||
appointmentId = a.getLong("appointmentId");
|
||||
preselectedPetId = a.getLong("petId", -1);
|
||||
preselectedServiceId= a.getLong("serviceId", -1);
|
||||
preselectedCustomerId = a.getLong("customerId", -1);
|
||||
preselectedStoreId = a.getLong("storeId", -1);
|
||||
|
||||
tvMode.setText("Edit Appointment");
|
||||
tvAppointmentId.setText("ID: " + appointmentId);
|
||||
etCustomerName.setText(getArguments().getString("customerName"));
|
||||
etPetName.setText(getArguments().getString("petName"));
|
||||
etServiceType.setText(getArguments().getString("serviceType"));
|
||||
etAppointmentDate.setText(getArguments().getString("appointmentDate"));
|
||||
etAppointmentTime.setText(getArguments().getString("appointmentTime"));
|
||||
String status = getArguments().getString("status");
|
||||
if ("Confirmed".equals(status)) spinnerStatus.setSelection(0);
|
||||
else if ("Pending".equals(status)) spinnerStatus.setSelection(1);
|
||||
else spinnerStatus.setSelection(2);
|
||||
btnDeleteAppointment.setVisibility(View.VISIBLE);
|
||||
tvAppointmentId.setVisibility(View.VISIBLE);
|
||||
etAppointmentDate.setText(a.getString("appointmentDate"));
|
||||
btnDelete.setVisibility(View.VISIBLE);
|
||||
|
||||
// Pre-fill time spinners
|
||||
String time = a.getString("appointmentTime", "09:00");
|
||||
if (time.length() > 5) time = time.substring(0, 5);
|
||||
String[] parts = time.split(":");
|
||||
if (parts.length == 2) {
|
||||
int hour = Integer.parseInt(parts[0]);
|
||||
int min = Integer.parseInt(parts[1]);
|
||||
for (int i = 0; i < HOURS.length; i++)
|
||||
if (HOURS[i] == hour) { spinnerHour.setSelection(i); break; }
|
||||
for (int i = 0; i < MINUTES.length; i++)
|
||||
if (MINUTES[i] == min) { spinnerMinute.setSelection(i); break; }
|
||||
}
|
||||
|
||||
// Pre-fill status
|
||||
String status = a.getString("appointmentStatus", "Booked");
|
||||
for (int i = 0; i < STATUSES.length; i++)
|
||||
if (STATUSES[i].equals(status)) { spinnerStatus.setSelection(i); break; }
|
||||
|
||||
} else {
|
||||
isEditing = false;
|
||||
tvMode.setText("Add Appointment");
|
||||
btnDelete.setVisibility(View.GONE);
|
||||
tvAppointmentId.setVisibility(View.GONE);
|
||||
btnDeleteAppointment.setVisibility(View.GONE);
|
||||
btnSaveAppointment.setText("Add");
|
||||
}
|
||||
}
|
||||
|
||||
private void initViews(View view) {
|
||||
tvMode = view.findViewById(R.id.tvApptMode);
|
||||
tvAppointmentId = view.findViewById(R.id.tvAppointmentId);
|
||||
etCustomerName = view.findViewById(R.id.etCustomerName);
|
||||
etPetName = view.findViewById(R.id.etApptPetName);
|
||||
etServiceType = view.findViewById(R.id.etServiceType);
|
||||
etAppointmentDate = view.findViewById(R.id.etAppointmentDate);
|
||||
etAppointmentTime = view.findViewById(R.id.etAppointmentTime);
|
||||
spinnerStatus = view.findViewById(R.id.spinnerAppointmentStatus);
|
||||
btnSaveAppointment = view.findViewById(R.id.btnSaveAppointment);
|
||||
btnDeleteAppointment = view.findViewById(R.id.btnDeleteAppointment);
|
||||
btnBack = view.findViewById(R.id.btnApptBack);
|
||||
private void saveAppointment() {
|
||||
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (spinnerStore.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (spinnerPet.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (spinnerService.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a service", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
String date = etAppointmentDate.getText().toString().trim();
|
||||
if (date.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
|
||||
CustomerDTO customer = customerList.get(spinnerCustomer.getSelectedItemPosition() - 1);
|
||||
StoreDTO store = storeList.get(spinnerStore.getSelectedItemPosition() - 1);
|
||||
PetDTO pet = petList.get(spinnerPet.getSelectedItemPosition() - 1);
|
||||
ServiceDTO service = serviceList.get(spinnerService.getSelectedItemPosition() - 1);
|
||||
|
||||
String time = String.format("%02d:%02d",
|
||||
HOURS[spinnerHour.getSelectedItemPosition()],
|
||||
MINUTES[spinnerMinute.getSelectedItemPosition()]);
|
||||
String status = STATUSES[spinnerStatus.getSelectedItemPosition()];
|
||||
|
||||
|
||||
// Validate future date+time if status is Booked
|
||||
if ("Booked".equalsIgnoreCase(status)) {
|
||||
try {
|
||||
String[] dateParts = date.split("-");
|
||||
String[] timeParts = time.split(":");
|
||||
Calendar selected = Calendar.getInstance();
|
||||
selected.set(
|
||||
Integer.parseInt(dateParts[0]),
|
||||
Integer.parseInt(dateParts[1]) - 1,
|
||||
Integer.parseInt(dateParts[2]),
|
||||
Integer.parseInt(timeParts[0]),
|
||||
Integer.parseInt(timeParts[1]),
|
||||
0
|
||||
);
|
||||
if (selected.before(Calendar.getInstance())) {
|
||||
showErrorDialog("Invalid Time",
|
||||
"Booked appointments must be in the future. " +
|
||||
"Please select a future date and time.");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("APPT_SAVE", "Date parse error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Build DTO with all required IDs
|
||||
AppointmentDTO dto = new AppointmentDTO(
|
||||
customer.getCustomerId(),
|
||||
store.getStoreId(),
|
||||
service.getServiceId(),
|
||||
date,
|
||||
time,
|
||||
status,
|
||||
Collections.singletonList(pet.getPetId())
|
||||
);
|
||||
|
||||
Log.d("APPT_SAVE", "customerId=" + customer.getCustomerId()
|
||||
+ " storeId=" + store.getStoreId()
|
||||
+ " serviceId=" + service.getServiceId()
|
||||
+ " petId=" + pet.getPetId()
|
||||
+ " date=" + date + " time=" + time);
|
||||
|
||||
AppointmentApi api = RetrofitClient.getAppointmentApi(requireContext());
|
||||
if (isEditing) {
|
||||
api.updateAppointment(appointmentId, dto).enqueue(simpleCallback("Updated"));
|
||||
} else {
|
||||
api.createAppointment(dto).enqueue(simpleCallback("Saved"));
|
||||
}
|
||||
}
|
||||
|
||||
private void setupSpinner() {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<String>(requireContext(),
|
||||
android.R.layout.simple_spinner_item,
|
||||
new String[]{"Confirmed", "Pending", "Cancelled"}) {
|
||||
private Callback<AppointmentDTO> simpleCallback(String msg) {
|
||||
return new Callback<>() {
|
||||
public void onResponse(Call<AppointmentDTO> c, Response<AppointmentDTO> r) {
|
||||
Log.d("APPT_SAVE", "Response: " + r.code());
|
||||
if (r.isSuccessful()) {
|
||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else {
|
||||
try {
|
||||
String errorBody = r.errorBody().string();
|
||||
Log.e("APPT_SAVE", "Error: " + errorBody);
|
||||
|
||||
//Override the getView method for the spinner to make the text color darker for more readability
|
||||
@NonNull
|
||||
@Override
|
||||
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
|
||||
View view = super.getView(position, convertView, parent);
|
||||
((TextView) view).setTextColor(ContextCompat.getColor(requireContext(), R.color.text_dark));
|
||||
return view;
|
||||
// Show proper dialog based on error type
|
||||
if (errorBody.toLowerCase().contains("future")) {
|
||||
showErrorDialog("Invalid Date/Time",
|
||||
"Booked appointments must be scheduled in the future. " +
|
||||
"Please select a future date and time.");
|
||||
//------------------------------------------
|
||||
} else if (errorBody.toLowerCase().contains("not available") ||
|
||||
errorBody.toLowerCase().contains("time is not available")) {
|
||||
showNoAvailabilityDialog();
|
||||
} else if (r.code() == 404) {
|
||||
showErrorDialog("Not Found",
|
||||
"The selected pet, customer or service was not found.");
|
||||
} else if (r.code() == 403) {
|
||||
showErrorDialog("Access Denied",
|
||||
"You don't have permission to perform this action.");
|
||||
} else if (r.code() == 400) {
|
||||
showErrorDialog("Invalid Request", errorBody);
|
||||
} else {
|
||||
showErrorDialog("Error", "Something went wrong. Please try again.");
|
||||
}
|
||||
//-----------------------------
|
||||
} catch (Exception e) {
|
||||
Log.e("APPT_SAVE", "Failed to read error body");
|
||||
showErrorDialog("Error", "Something went wrong. Please try again.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onFailure(Call<AppointmentDTO> c, Throwable t) {
|
||||
Log.e("APPT_SAVE", "Failure: " + t.getMessage());
|
||||
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinnerStatus.setAdapter(adapter);
|
||||
}
|
||||
}
|
||||
|
||||
private void showNoAvailabilityDialog() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("No Availability")
|
||||
.setMessage("This time slot is already booked for the selected service and store. Please choose a different time or date.")
|
||||
.setPositiveButton("Change Time", (d, w) -> d.dismiss())
|
||||
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack())
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void showErrorDialog(String title, String message) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(title)
|
||||
.setMessage(message)
|
||||
.setPositiveButton("OK", null)
|
||||
.show();
|
||||
}
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete Appointment?")
|
||||
.setPositiveButton("Yes", (d, w) ->
|
||||
RetrofitClient.getAppointmentApi(requireContext())
|
||||
.deleteAppointment(appointmentId)
|
||||
.enqueue(new Callback<Void>() {
|
||||
public void onResponse(Call<Void> c, Response<Void> r) { navigateBack(); }
|
||||
public void onFailure(Call<Void> c, Throwable t) {
|
||||
Toast.makeText(getContext(), "Delete failed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}))
|
||||
.setNegativeButton("No", null).show();
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
ListFragment lf = (ListFragment) getParentFragment();
|
||||
if (lf != null) lf.getChildFragmentManager().popBackStack();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user