Merge branch 'AttachmentsToChat'
This commit is contained in:
@@ -0,0 +1,137 @@
|
|||||||
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class CouponAdapter extends RecyclerView.Adapter<CouponAdapter.ViewHolder> {
|
||||||
|
private final List<CouponDTO> coupons;
|
||||||
|
private final OnCouponClickListener listener;
|
||||||
|
private boolean selectionMode = false;
|
||||||
|
private final Set<Long> selectedIds = new HashSet<>();
|
||||||
|
|
||||||
|
public interface OnCouponClickListener {
|
||||||
|
void onCouponClick(CouponDTO coupon);
|
||||||
|
void onSelectionChanged(int count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CouponAdapter(List<CouponDTO> coupons, OnCouponClickListener listener) {
|
||||||
|
this.coupons = coupons;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
|
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_coupon, parent, false);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||||
|
CouponDTO coupon = coupons.get(position);
|
||||||
|
holder.tvCouponCode.setText(coupon.getCouponCode());
|
||||||
|
|
||||||
|
String discountText = "";
|
||||||
|
if ("PERCENT".equals(coupon.getDiscountType())) {
|
||||||
|
discountText = coupon.getDiscountValue().stripTrailingZeros().toPlainString() + "% OFF";
|
||||||
|
} else {
|
||||||
|
discountText = "$" + coupon.getDiscountValue().stripTrailingZeros().toPlainString() + " OFF";
|
||||||
|
}
|
||||||
|
holder.tvCouponDiscount.setText(discountText);
|
||||||
|
|
||||||
|
holder.tvCouponMinOrder.setText("Min order: $" + coupon.getMinOrderAmount().stripTrailingZeros().toPlainString());
|
||||||
|
|
||||||
|
if (coupon.getEndsAt() != null) {
|
||||||
|
holder.tvCouponExpiry.setText("Expires: " + coupon.getEndsAt().substring(0, 10));
|
||||||
|
holder.tvCouponExpiry.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
holder.tvCouponExpiry.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Boolean.TRUE.equals(coupon.getActive())) {
|
||||||
|
holder.tvCouponStatus.setText("ACTIVE");
|
||||||
|
holder.tvCouponStatus.setBackgroundTintList(ContextCompat.getColorStateList(holder.itemView.getContext(), R.color.primary_dark));
|
||||||
|
} else {
|
||||||
|
holder.tvCouponStatus.setText("INACTIVE");
|
||||||
|
holder.tvCouponStatus.setBackgroundTintList(ContextCompat.getColorStateList(holder.itemView.getContext(), R.color.accent_coral));
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.cbSelectCoupon.setVisibility(selectionMode ? View.VISIBLE : View.GONE);
|
||||||
|
holder.cbSelectCoupon.setChecked(selectedIds.contains(coupon.getCouponId()));
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
if (selectionMode) {
|
||||||
|
toggleSelection(coupon.getCouponId());
|
||||||
|
} else {
|
||||||
|
listener.onCouponClick(coupon);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
holder.itemView.setOnLongClickListener(v -> {
|
||||||
|
if (!selectionMode) {
|
||||||
|
setSelectionMode(true);
|
||||||
|
toggleSelection(coupon.getCouponId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
holder.cbSelectCoupon.setOnClickListener(v -> toggleSelection(coupon.getCouponId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleSelection(Long id) {
|
||||||
|
if (selectedIds.contains(id)) {
|
||||||
|
selectedIds.remove(id);
|
||||||
|
} else {
|
||||||
|
selectedIds.add(id);
|
||||||
|
}
|
||||||
|
notifyDataSetChanged();
|
||||||
|
listener.onSelectionChanged(selectedIds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectionMode(boolean selectionMode) {
|
||||||
|
this.selectionMode = selectionMode;
|
||||||
|
if (!selectionMode) selectedIds.clear();
|
||||||
|
notifyDataSetChanged();
|
||||||
|
listener.onSelectionChanged(selectedIds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Long> getSelectedIds() {
|
||||||
|
return selectedIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return coupons.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
TextView tvCouponCode, tvCouponDiscount, tvCouponMinOrder, tvCouponExpiry, tvCouponStatus;
|
||||||
|
CheckBox cbSelectCoupon;
|
||||||
|
|
||||||
|
public ViewHolder(@NonNull View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
tvCouponCode = itemView.findViewById(R.id.tvCouponCode);
|
||||||
|
tvCouponDiscount = itemView.findViewById(R.id.tvCouponDiscount);
|
||||||
|
tvCouponMinOrder = itemView.findViewById(R.id.tvCouponMinOrder);
|
||||||
|
tvCouponExpiry = itemView.findViewById(R.id.tvCouponExpiry);
|
||||||
|
tvCouponStatus = itemView.findViewById(R.id.tvCouponStatus);
|
||||||
|
cbSelectCoupon = itemView.findViewById(R.id.cbSelectCoupon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,14 +7,19 @@ 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.api.UserApi;
|
||||||
import com.example.petstoremobile.databinding.ItemEmployeeBinding;
|
import com.example.petstoremobile.databinding.ItemEmployeeBinding;
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.EmployeeViewHolder> {
|
public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.EmployeeViewHolder> {
|
||||||
|
|
||||||
private List<EmployeeDTO> list;
|
private List<EmployeeDTO> list;
|
||||||
private OnEmployeeClickListener listener;
|
private OnEmployeeClickListener listener;
|
||||||
|
private String baseUrl;
|
||||||
|
private String token;
|
||||||
|
|
||||||
public interface OnEmployeeClickListener {
|
public interface OnEmployeeClickListener {
|
||||||
void onEmployeeClick(int position);
|
void onEmployeeClick(int position);
|
||||||
@@ -25,6 +30,14 @@ public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.Employ
|
|||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBaseUrl(String baseUrl) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
public static class EmployeeViewHolder extends RecyclerView.ViewHolder {
|
public static class EmployeeViewHolder extends RecyclerView.ViewHolder {
|
||||||
private final ItemEmployeeBinding binding;
|
private final ItemEmployeeBinding binding;
|
||||||
|
|
||||||
@@ -66,8 +79,13 @@ public class EmployeeAdapter extends RecyclerView.Adapter<EmployeeAdapter.Employ
|
|||||||
binding.tvEmployeeStatus.setText(active ? "Active" : "Inactive");
|
binding.tvEmployeeStatus.setText(active ? "Active" : "Inactive");
|
||||||
binding.tvEmployeeStatus.setTextColor(active ? Color.parseColor("#4CAF50") : Color.parseColor("#F44336"));
|
binding.tvEmployeeStatus.setTextColor(active ? Color.parseColor("#4CAF50") : Color.parseColor("#F44336"));
|
||||||
|
|
||||||
// Placeholder for profile image - matching Pet style
|
// Profile image
|
||||||
|
if (baseUrl != null && e.getId() != null) {
|
||||||
|
String imageUrl = baseUrl + String.format(UserApi.AVATAR_PATH, e.getId());
|
||||||
|
GlideUtils.loadImageWithTokenCircle(holder.itemView.getContext(), binding.ivEmployeeProfile, imageUrl, token, R.drawable.placeholder);
|
||||||
|
} else {
|
||||||
binding.ivEmployeeProfile.setImageResource(R.drawable.placeholder);
|
binding.ivEmployeeProfile.setImageResource(R.drawable.placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
holder.itemView.setOnClickListener(v -> listener.onEmployeeClick(position));
|
holder.itemView.setOnClickListener(v -> listener.onEmployeeClick(position));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ package com.example.petstoremobile.api;
|
|||||||
|
|
||||||
import com.example.petstoremobile.dtos.ConversationDTO;
|
import com.example.petstoremobile.dtos.ConversationDTO;
|
||||||
import com.example.petstoremobile.dtos.MessageDTO;
|
import com.example.petstoremobile.dtos.MessageDTO;
|
||||||
|
import com.example.petstoremobile.dtos.UpdateConversationStatusRequest;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.Body;
|
import retrofit2.http.Body;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
import retrofit2.http.POST;
|
import retrofit2.http.PUT;
|
||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
|
|
||||||
//api calls to get conversations
|
//api calls to get conversations
|
||||||
@@ -20,4 +21,7 @@ public interface ChatApi {
|
|||||||
@GET("api/v1/chat/conversations/{conversationId}")
|
@GET("api/v1/chat/conversations/{conversationId}")
|
||||||
Call<ConversationDTO> getConversationById(@Path("conversationId") Long conversationId);
|
Call<ConversationDTO> getConversationById(@Path("conversationId") Long conversationId);
|
||||||
|
|
||||||
|
@PUT("api/v1/chat/conversations/{conversationId}")
|
||||||
|
Call<ConversationDTO> updateConversationStatus(@Path("conversationId") Long conversationId, @Body UpdateConversationStatusRequest request);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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 CouponApi {
|
||||||
|
|
||||||
|
@GET("api/v1/coupons")
|
||||||
|
Call<PageResponse<CouponDTO>> getAllCoupons(
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("size") int size,
|
||||||
|
@Query("active") Boolean active,
|
||||||
|
@Query("discountType") String discountType,
|
||||||
|
@Query("sort") String sort);
|
||||||
|
|
||||||
|
@GET("api/v1/coupons/{id}")
|
||||||
|
Call<CouponDTO> getCouponById(@Path("id") Long id);
|
||||||
|
|
||||||
|
@GET("api/v1/coupons/code/{code}")
|
||||||
|
Call<CouponDTO> getCouponByCode(@Path("code") String code);
|
||||||
|
|
||||||
|
@POST("api/v1/coupons")
|
||||||
|
Call<CouponDTO> createCoupon(@Body CouponDTO coupon);
|
||||||
|
|
||||||
|
@PUT("api/v1/coupons/{id}")
|
||||||
|
Call<CouponDTO> updateCoupon(@Path("id") Long id, @Body CouponDTO coupon);
|
||||||
|
|
||||||
|
@DELETE("api/v1/coupons/{id}")
|
||||||
|
Call<Void> deleteCoupon(@Path("id") Long id);
|
||||||
|
|
||||||
|
@DELETE("api/v1/coupons")
|
||||||
|
Call<Void> bulkDeleteCoupons(@Query("ids") List<Long> ids);
|
||||||
|
}
|
||||||
@@ -44,6 +44,9 @@ public interface PetApi {
|
|||||||
@GET("api/v1/dropdowns/adoption-pets")
|
@GET("api/v1/dropdowns/adoption-pets")
|
||||||
Call<List<DropdownDTO>> getAdoptionPets();
|
Call<List<DropdownDTO>> getAdoptionPets();
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/pets")
|
||||||
|
Call<List<DropdownDTO>> getPetDropdowns();
|
||||||
|
|
||||||
// Get pet by id
|
// Get pet by id
|
||||||
@GET("api/v1/pets/{id}")
|
@GET("api/v1/pets/{id}")
|
||||||
Call<PetDTO> getPetById(@Path("id") Long id);
|
Call<PetDTO> getPetById(@Path("id") Long id);
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.*;
|
import retrofit2.http.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface ProductApi {
|
public interface ProductApi {
|
||||||
String PRODUCT_IMAGE_PATH = "api/v1/products/%d/image";
|
String PRODUCT_IMAGE_PATH = "api/v1/products/%d/image";
|
||||||
|
|
||||||
@@ -35,4 +38,10 @@ public interface ProductApi {
|
|||||||
|
|
||||||
@DELETE("api/v1/products/{id}/image")
|
@DELETE("api/v1/products/{id}/image")
|
||||||
Call<Void> deleteProductImage(@Path("id") Long id);
|
Call<Void> deleteProductImage(@Path("id") Long id);
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/products")
|
||||||
|
Call<List<DropdownDTO>> getProductDropdowns();
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/categories")
|
||||||
|
Call<List<DropdownDTO>> getCategoryDropdowns();
|
||||||
}
|
}
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
package com.example.petstoremobile.api;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.BuildConfig;
|
|
||||||
import com.example.petstoremobile.api.auth.AuthApi;
|
|
||||||
import com.example.petstoremobile.api.auth.AuthInterceptor;
|
|
||||||
import com.example.petstoremobile.api.auth.TokenManager;
|
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor;
|
|
||||||
import retrofit2.Retrofit;
|
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
//Retrofit client Used for API calls TODO: DELETE THIS FILE AFTER MERGE NOW THAT WE ARE USING HILT AND NETWORKMODULE
|
|
||||||
public class RetrofitClient {
|
|
||||||
private static final String TAG = "RetrofitClient";
|
|
||||||
public static final String BASE_URL = getBaseUrl();
|
|
||||||
|
|
||||||
// Helper function to determine BASE_URL based on whether we are testing on an emulator or a real device
|
|
||||||
private static String getBaseUrl() {
|
|
||||||
String url = isEmulator() ? BuildConfig.EMULATOR_BACKEND_URL : BuildConfig.DEVICE_BACKEND_URL;
|
|
||||||
Log.i(TAG, "Using backend URL: " + url + " (emulator=" + isEmulator() + ")");
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isEmulator() {
|
|
||||||
return Build.FINGERPRINT.startsWith("generic")
|
|
||||||
|| Build.FINGERPRINT.startsWith("unknown")
|
|
||||||
|| Build.MODEL.contains("google_sdk")
|
|
||||||
|| Build.MODEL.contains("Emulator")
|
|
||||||
|| Build.MODEL.contains("Android SDK built for x86")
|
|
||||||
|| Build.MANUFACTURER.contains("Genymotion")
|
|
||||||
|| Build.HARDWARE.contains("goldfish")
|
|
||||||
|| Build.HARDWARE.contains("ranchu")
|
|
||||||
|| Build.PRODUCT.contains("sdk")
|
|
||||||
|| Build.PRODUCT.contains("sdk_gphone")
|
|
||||||
|| (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Retrofit retrofit = null;
|
|
||||||
|
|
||||||
public static Retrofit getClient(Context context) {
|
|
||||||
//create an http logging using an interceptor
|
|
||||||
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
|
||||||
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
|
||||||
|
|
||||||
OkHttpClient client = new OkHttpClient.Builder()
|
|
||||||
.addInterceptor(interceptor)
|
|
||||||
.addInterceptor(new AuthInterceptor(new TokenManager(context)))
|
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.writeTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
//build the retrofit object with all needed properties
|
|
||||||
retrofit = new Retrofit.Builder()
|
|
||||||
.baseUrl(BASE_URL)
|
|
||||||
.addConverterFactory(GsonConverterFactory.create()) //JSON converter
|
|
||||||
.client(client) //logging interceptor - OkHttpClient
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return retrofit;
|
|
||||||
}
|
|
||||||
|
|
||||||
//associate the retrofit object with the API interface
|
|
||||||
public static PetApi getPetApi(Context context) {
|
|
||||||
return getClient(context).create(PetApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ServiceApi getServiceApi(Context context) {
|
|
||||||
return getClient(context).create(ServiceApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SupplierApi getSupplierApi(Context context) {
|
|
||||||
return getClient(context).create(SupplierApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AdoptionApi getAdoptionApi(Context context) {
|
|
||||||
return getClient(context).create(AdoptionApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AppointmentApi getAppointmentApi(Context context) {
|
|
||||||
return getClient(context).create(AppointmentApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ProductApi getProductApi(Context context) {
|
|
||||||
return getClient(context).create(ProductApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static SaleApi getSaleApi(Context context) {
|
|
||||||
return getClient(context).create(SaleApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PurchaseOrderApi getPurchaseOrderApi(Context context) {
|
|
||||||
return getClient(context).create(PurchaseOrderApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ProductSupplierApi getProductSupplierApi(Context context) {
|
|
||||||
return getClient(context).create(ProductSupplierApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InventoryApi getInventoryApi(Context context) {
|
|
||||||
return getClient(context).create(InventoryApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AuthApi getAuthApi(Context context) {
|
|
||||||
return getClient(context).create(AuthApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ChatApi getChatApi(Context context) {
|
|
||||||
return getClient(context).create(ChatApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CustomerApi getCustomerApi(Context context) {
|
|
||||||
return getClient(context).create(CustomerApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MessageApi getMessageApi(Context context) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RefundApi getRefundApi(Context context) {
|
|
||||||
return getClient(context).create(RefundApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EmployeeApi getEmployeeApi(Context context) {
|
|
||||||
return getClient(context).create(EmployeeApi.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,7 @@ public interface SaleApi {
|
|||||||
@Query("q") String query,
|
@Query("q") String query,
|
||||||
@Query("paymentMethod") String paymentMethod,
|
@Query("paymentMethod") String paymentMethod,
|
||||||
@Query("storeId") Long storeId,
|
@Query("storeId") Long storeId,
|
||||||
|
@Query("isRefund") Boolean isRefund,
|
||||||
@Query("sort") String sort);
|
@Query("sort") String sort);
|
||||||
|
|
||||||
@GET("api/v1/sales/{id}")
|
@GET("api/v1/sales/{id}")
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import retrofit2.http.GET;
|
|||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
public interface UserApi {
|
public interface UserApi {
|
||||||
|
String AVATAR_PATH = "api/v1/users/%d/avatar/file";
|
||||||
|
|
||||||
@GET("api/v1/users")
|
@GET("api/v1/users")
|
||||||
Call<PageResponse<UserDTO>> getUsers(@Query("role") String role, @Query("page") int page, @Query("size") int size);
|
Call<PageResponse<UserDTO>> getUsers(@Query("role") String role, @Query("page") int page, @Query("size") int size);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,4 +191,10 @@ public class NetworkModule {
|
|||||||
public static RefundApi provideRefundApi(Retrofit retrofit) {
|
public static RefundApi provideRefundApi(Retrofit retrofit) {
|
||||||
return retrofit.create(RefundApi.class);
|
return retrofit.create(RefundApi.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
public static CouponApi provideCouponApi(Retrofit retrofit) {
|
||||||
|
return retrofit.create(CouponApi.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
public class CouponDTO {
|
||||||
|
private Long couponId;
|
||||||
|
private String couponCode;
|
||||||
|
private String discountType;
|
||||||
|
private BigDecimal discountValue;
|
||||||
|
private BigDecimal minOrderAmount;
|
||||||
|
private Boolean active;
|
||||||
|
private String startsAt;
|
||||||
|
private String endsAt;
|
||||||
|
private Integer usageLimit;
|
||||||
|
private String createdAt;
|
||||||
|
private String updatedAt;
|
||||||
|
|
||||||
|
public CouponDTO() {}
|
||||||
|
|
||||||
|
public Long getCouponId() { return couponId; }
|
||||||
|
public void setCouponId(Long couponId) { this.couponId = couponId; }
|
||||||
|
|
||||||
|
public String getCouponCode() { return couponCode; }
|
||||||
|
public void setCouponCode(String couponCode) { this.couponCode = couponCode; }
|
||||||
|
|
||||||
|
public String getDiscountType() { return discountType; }
|
||||||
|
public void setDiscountType(String discountType) { this.discountType = discountType; }
|
||||||
|
|
||||||
|
public BigDecimal getDiscountValue() { return discountValue; }
|
||||||
|
public void setDiscountValue(BigDecimal discountValue) { this.discountValue = discountValue; }
|
||||||
|
|
||||||
|
public BigDecimal getMinOrderAmount() { return minOrderAmount; }
|
||||||
|
public void setMinOrderAmount(BigDecimal minOrderAmount) { this.minOrderAmount = minOrderAmount; }
|
||||||
|
|
||||||
|
public Boolean getActive() { return active; }
|
||||||
|
public void setActive(Boolean active) { this.active = active; }
|
||||||
|
|
||||||
|
public String getStartsAt() { return startsAt; }
|
||||||
|
public void setStartsAt(String startsAt) { this.startsAt = startsAt; }
|
||||||
|
|
||||||
|
public String getEndsAt() { return endsAt; }
|
||||||
|
public void setEndsAt(String endsAt) { this.endsAt = endsAt; }
|
||||||
|
|
||||||
|
public Integer getUsageLimit() { return usageLimit; }
|
||||||
|
public void setUsageLimit(Integer usageLimit) { this.usageLimit = usageLimit; }
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -2,8 +2,7 @@ package com.example.petstoremobile.dtos;
|
|||||||
|
|
||||||
public class EmployeeDTO {
|
public class EmployeeDTO {
|
||||||
|
|
||||||
private long EmployeeId;
|
private Long id;
|
||||||
private Long userId;
|
|
||||||
private String username;
|
private String username;
|
||||||
private String firstName;
|
private String firstName;
|
||||||
private String lastName;
|
private String lastName;
|
||||||
@@ -11,16 +10,18 @@ public class EmployeeDTO {
|
|||||||
private String email;
|
private String email;
|
||||||
private String phone;
|
private String phone;
|
||||||
private String role;
|
private String role;
|
||||||
|
private String staffRole;
|
||||||
private Boolean active;
|
private Boolean active;
|
||||||
private String createAt;
|
private Integer loyaltyPoints;
|
||||||
|
private Long primaryStoreId;
|
||||||
|
private String createdAt;
|
||||||
private String updatedAt;
|
private String updatedAt;
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public EmployeeDTO() {}
|
||||||
// Constructor for create and update the employee
|
|
||||||
|
|
||||||
|
|
||||||
public EmployeeDTO(String username, String password, String firstName, String lastName,
|
public EmployeeDTO(String username, String password, String firstName, String lastName,
|
||||||
String email, String phone, String role, boolean active) {
|
String email, String phone, String role, String staffRole, boolean active, Long primaryStoreId) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.password = password;
|
this.password = password;
|
||||||
this.firstName = firstName;
|
this.firstName = firstName;
|
||||||
@@ -28,75 +29,128 @@ public class EmployeeDTO {
|
|||||||
this.email = email;
|
this.email = email;
|
||||||
this.phone = phone;
|
this.phone = phone;
|
||||||
this.role = role;
|
this.role = role;
|
||||||
|
this.staffRole = staffRole;
|
||||||
this.active = active;
|
this.active = active;
|
||||||
}
|
this.primaryStoreId = primaryStoreId;
|
||||||
// password field for request only
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
|
|
||||||
public long getEmployeeId() {
|
|
||||||
|
|
||||||
return EmployeeId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getUserId() {
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
return userId;
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
|
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFirstName() {
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
return firstName;
|
return firstName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLastName() {
|
public void setFirstName(String firstName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
return lastName;
|
return lastName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFullName() {
|
public void setLastName(String lastName) {
|
||||||
|
this.lastName = lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEmail() {
|
public void setFullName(String fullName) {
|
||||||
|
this.fullName = fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
public String getPhone() {
|
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
return phone;
|
return phone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRole() {
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRole() {
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean getActive() {
|
public void setRole(String role) {
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStaffRole() {
|
||||||
|
return staffRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStaffRole(String staffRole) {
|
||||||
|
this.staffRole = staffRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getActive() {
|
||||||
return active;
|
return active;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCreateAt() {
|
public void setActive(Boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
}
|
||||||
|
|
||||||
return createAt;
|
public Integer getLoyaltyPoints() {
|
||||||
|
return loyaltyPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoyaltyPoints(Integer loyaltyPoints) {
|
||||||
|
this.loyaltyPoints = loyaltyPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getPrimaryStoreId() {
|
||||||
|
return primaryStoreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPrimaryStoreId(Long primaryStoreId) {
|
||||||
|
this.primaryStoreId = primaryStoreId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(String createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUpdatedAt() {
|
public String getUpdatedAt() {
|
||||||
|
|
||||||
return updatedAt;
|
return updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public void setUpdatedAt(String updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPassword() {
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPassword(String password) {
|
||||||
|
this.password = password;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ public class SaleDTO {
|
|||||||
private String saleDate;
|
private String saleDate;
|
||||||
private Long employeeId;
|
private Long employeeId;
|
||||||
private String employeeName;
|
private String employeeName;
|
||||||
|
private Long customerId;
|
||||||
|
private String customerName;
|
||||||
private Long storeId;
|
private Long storeId;
|
||||||
private String storeName;
|
private String storeName;
|
||||||
private BigDecimal totalAmount;
|
private BigDecimal totalAmount;
|
||||||
@@ -25,9 +27,6 @@ public class SaleDTO {
|
|||||||
private List<SaleItemDTO> items;
|
private List<SaleItemDTO> items;
|
||||||
private String createdAt;
|
private String createdAt;
|
||||||
|
|
||||||
// Request fields
|
|
||||||
private Long customerId;
|
|
||||||
|
|
||||||
// Constructor for create request
|
// Constructor for create request
|
||||||
public SaleDTO(Long storeId, String paymentMethod, List<SaleItemDTO> items,
|
public SaleDTO(Long storeId, String paymentMethod, List<SaleItemDTO> items,
|
||||||
Boolean isRefund, Long originalSaleId, Long customerId) {
|
Boolean isRefund, Long originalSaleId, Long customerId) {
|
||||||
@@ -119,6 +118,10 @@ public class SaleDTO {
|
|||||||
return customerId;
|
return customerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCustomerName() {
|
||||||
|
return customerName;
|
||||||
|
}
|
||||||
|
|
||||||
// Nested SaleItemDTO
|
// Nested SaleItemDTO
|
||||||
public static class SaleItemDTO {
|
public static class SaleItemDTO {
|
||||||
private Long saleItemId;
|
private Long saleItemId;
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
public class UpdateConversationStatusRequest {
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
public UpdateConversationStatusRequest(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatus(String status) {
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,6 +127,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
|
|
||||||
binding.btnAttach.setOnClickListener(v -> selectAttachment());
|
binding.btnAttach.setOnClickListener(v -> selectAttachment());
|
||||||
binding.btnRemoveAttachment.setOnClickListener(v -> removeAttachment());
|
binding.btnRemoveAttachment.setOnClickListener(v -> removeAttachment());
|
||||||
|
binding.btnCloseChat.setOnClickListener(v -> closeChat());
|
||||||
|
|
||||||
setupDrawerToggles();
|
setupDrawerToggles();
|
||||||
setupRecyclerViews();
|
setupRecyclerViews();
|
||||||
@@ -356,6 +357,30 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
viewModel.loadMessageHistory(activeConversationId);
|
viewModel.loadMessageHistory(activeConversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void closeChat() {
|
||||||
|
if (activeConversationId == null) return;
|
||||||
|
|
||||||
|
DialogUtils.showConfirmDialog(requireContext(), "Close Chat",
|
||||||
|
"Are you sure you want to close this chat? This will notify the customer.", () -> {
|
||||||
|
viewModel.sendMessage(activeConversationId, "The Chat has been closed").observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS) {
|
||||||
|
viewModel.addMessageLocally(resource.data);
|
||||||
|
|
||||||
|
viewModel.closeConversation(activeConversationId).observe(getViewLifecycleOwner(), statusResource -> {
|
||||||
|
if (statusResource == null) return;
|
||||||
|
setLoading(statusResource.status == Resource.Status.LOADING);
|
||||||
|
if (statusResource.status == Resource.Status.SUCCESS) {
|
||||||
|
viewModel.loadConversations();
|
||||||
|
setConversationActive(true, "CLOSED");
|
||||||
|
} else if (statusResource.status == Resource.Status.ERROR) {
|
||||||
|
Toast.makeText(requireContext(), "Failed to close chat: " + statusResource.message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
if (activeConversationId == null) return;
|
if (activeConversationId == null) return;
|
||||||
String text = binding.etMessage.getText().toString().trim();
|
String text = binding.etMessage.getText().toString().trim();
|
||||||
@@ -489,6 +514,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
private void setConversationActive(boolean active, String status) {
|
private void setConversationActive(boolean active, String status) {
|
||||||
boolean isClosed = "CLOSED".equalsIgnoreCase(status);
|
boolean isClosed = "CLOSED".equalsIgnoreCase(status);
|
||||||
UIUtils.setViewsEnabled(active && !isClosed, binding.btnSend, binding.etMessage, binding.btnAttach);
|
UIUtils.setViewsEnabled(active && !isClosed, binding.btnSend, binding.etMessage, binding.btnAttach);
|
||||||
|
binding.btnCloseChat.setVisibility(active && !isClosed ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (!active) {
|
if (!active) {
|
||||||
activeConversationId = null;
|
activeConversationId = null;
|
||||||
|
|||||||
@@ -43,15 +43,13 @@ public class ListFragment extends Fragment {
|
|||||||
// Check user role and restrict access for STAFF
|
// Check user role and restrict access for STAFF
|
||||||
String role = tokenManager.getRole();
|
String role = tokenManager.getRole();
|
||||||
if ("STAFF".equalsIgnoreCase(role)) {
|
if ("STAFF".equalsIgnoreCase(role)) {
|
||||||
binding.drawerSuppliers.setVisibility(View.GONE);
|
binding.sectionAdmin.setVisibility(View.GONE);
|
||||||
binding.drawerInventory.setVisibility(View.GONE);
|
} else if ("ADMIN".equalsIgnoreCase(role)) {
|
||||||
}
|
binding.sectionAdmin.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// Only show for ADMIN
|
|
||||||
if ("ADMIN".equalsIgnoreCase(role)) {
|
|
||||||
binding.drawerStaff.setVisibility(View.VISIBLE);
|
binding.drawerStaff.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
binding.drawerStaff.setVisibility(View.GONE);
|
// Default or other roles
|
||||||
|
binding.sectionAdmin.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
//add Listeners to the drawer so user won't be able to interact with the innerContainer (the list fragments)
|
//add Listeners to the drawer so user won't be able to interact with the innerContainer (the list fragments)
|
||||||
@@ -92,6 +90,7 @@ public class ListFragment extends Fragment {
|
|||||||
binding.drawerSale.setOnClickListener(v -> navigateTo(R.id.nav_sale));
|
binding.drawerSale.setOnClickListener(v -> navigateTo(R.id.nav_sale));
|
||||||
binding.drawerStaff.setOnClickListener(v -> navigateTo(R.id.nav_staff));
|
binding.drawerStaff.setOnClickListener(v -> navigateTo(R.id.nav_staff));
|
||||||
binding.drawerAnalytics.setOnClickListener(v -> navigateTo(R.id.nav_analytics));
|
binding.drawerAnalytics.setOnClickListener(v -> navigateTo(R.id.nav_analytics));
|
||||||
|
binding.drawerCoupons.setOnClickListener(v -> navigateTo(R.id.nav_coupon));
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import com.example.petstoremobile.databinding.FragmentAnalyticsBinding;
|
import com.example.petstoremobile.databinding.FragmentAnalyticsBinding;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.AnalyticsViewModel;
|
import com.example.petstoremobile.viewmodels.AnalyticsViewModel;
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
@@ -20,6 +21,10 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
|
|
||||||
private FragmentAnalyticsBinding binding;
|
private FragmentAnalyticsBinding binding;
|
||||||
private AnalyticsViewModel viewModel;
|
private AnalyticsViewModel viewModel;
|
||||||
|
private boolean filtersExpanded = false;
|
||||||
|
|
||||||
|
private static final String[] TOP_N_OPTIONS = {"5", "10", "15", "20"};
|
||||||
|
private static final int[] TOP_N_VALUES = { 5, 10, 15, 20 };
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -27,16 +32,111 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
binding = FragmentAnalyticsBinding.inflate(inflater, container, false);
|
binding = FragmentAnalyticsBinding.inflate(inflater, container, false);
|
||||||
viewModel = new ViewModelProvider(this).get(AnalyticsViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AnalyticsViewModel.class);
|
||||||
|
|
||||||
|
setupFilterPanel();
|
||||||
observeViewModel();
|
observeViewModel();
|
||||||
viewModel.loadAnalytics();
|
viewModel.loadAnalytics();
|
||||||
|
|
||||||
binding.btnRefreshAnalytics.setOnClickListener(v -> viewModel.loadAnalytics());
|
binding.btnRefreshAnalytics.setOnClickListener(v -> viewModel.loadAnalytics());
|
||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerAnalytics, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerAnalytics, this);
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter Panel
|
||||||
|
|
||||||
|
private void setupFilterPanel() {
|
||||||
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerTopN, TOP_N_OPTIONS);
|
||||||
|
|
||||||
|
// Toggle expand/collapse
|
||||||
|
binding.rowFilterHeader.setOnClickListener(v -> toggleFilters());
|
||||||
|
|
||||||
|
// Date pickers
|
||||||
|
binding.etFilterStartDate.setOnClickListener(v ->
|
||||||
|
UIUtils.showDatePicker(requireContext(), binding.etFilterStartDate, this::updateFilterSummary));
|
||||||
|
binding.etFilterEndDate.setOnClickListener(v ->
|
||||||
|
UIUtils.showDatePicker(requireContext(), binding.etFilterEndDate, this::updateFilterSummary));
|
||||||
|
|
||||||
|
// Quick presets
|
||||||
|
binding.btnPresetToday.setOnClickListener(v -> applyPreset(0, 0));
|
||||||
|
binding.btnPreset7D.setOnClickListener(v -> applyPreset(-6, 0));
|
||||||
|
binding.btnPreset30D.setOnClickListener(v -> applyPreset(-29, 0));
|
||||||
|
binding.btnPreset3M.setOnClickListener(v -> applyPreset(-89, 0));
|
||||||
|
binding.btnPreset1Y.setOnClickListener(v -> applyPreset(-364, 0));
|
||||||
|
binding.btnPresetAll.setOnClickListener(v -> {
|
||||||
|
binding.etFilterStartDate.setText("");
|
||||||
|
binding.etFilterEndDate.setText("");
|
||||||
|
updateFilterSummary();
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.btnFilterApply.setOnClickListener(v -> applyFiltersFromUI());
|
||||||
|
binding.btnFilterReset.setOnClickListener(v -> resetFilters());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleFilters() {
|
||||||
|
filtersExpanded = !filtersExpanded;
|
||||||
|
binding.llFilterContent.setVisibility(filtersExpanded ? View.VISIBLE : View.GONE);
|
||||||
|
binding.tvFilterToggleIcon.setText(filtersExpanded ? "▲" : "▼");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyPreset(int startOffset, int endOffset) {
|
||||||
|
binding.etFilterStartDate.setText(getDateString(startOffset));
|
||||||
|
binding.etFilterEndDate.setText(getDateString(endOffset));
|
||||||
|
updateFilterSummary();
|
||||||
|
applyFiltersFromUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyFiltersFromUI() {
|
||||||
|
AnalyticsViewModel.FilterState filter = new AnalyticsViewModel.FilterState();
|
||||||
|
filter.startDate = binding.etFilterStartDate.getText().toString().trim();
|
||||||
|
filter.endDate = binding.etFilterEndDate.getText().toString().trim();
|
||||||
|
|
||||||
|
Object pm = binding.spinnerFilterPayment.getSelectedItem();
|
||||||
|
filter.paymentMethod = pm != null ? pm.toString() : "All";
|
||||||
|
|
||||||
|
int topNPos = binding.spinnerTopN.getSelectedItemPosition();
|
||||||
|
filter.topN = (topNPos >= 0 && topNPos < TOP_N_VALUES.length) ? TOP_N_VALUES[topNPos] : 5;
|
||||||
|
|
||||||
|
updateFilterSummary();
|
||||||
|
viewModel.applyFilter(filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetFilters() {
|
||||||
|
binding.etFilterStartDate.setText("");
|
||||||
|
binding.etFilterEndDate.setText("");
|
||||||
|
binding.spinnerTopN.setSelection(0);
|
||||||
|
// Reset payment method to "All"
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerFilterPayment, "All");
|
||||||
|
updateFilterSummary();
|
||||||
|
viewModel.resetFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateFilterSummary() {
|
||||||
|
String start = binding.etFilterStartDate.getText().toString().trim();
|
||||||
|
String end = binding.etFilterEndDate.getText().toString().trim();
|
||||||
|
if (start.isEmpty() && end.isEmpty()) {
|
||||||
|
binding.tvFilterSummary.setText("All time");
|
||||||
|
} else if (start.isEmpty()) {
|
||||||
|
binding.tvFilterSummary.setText("Up to " + shortDate(end));
|
||||||
|
} else if (end.isEmpty()) {
|
||||||
|
binding.tvFilterSummary.setText("From " + shortDate(start));
|
||||||
|
} else {
|
||||||
|
binding.tvFilterSummary.setText(shortDate(start) + " – " + shortDate(end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String shortDate(String date) {
|
||||||
|
return (date != null && date.length() >= 10) ? date.substring(5) : date;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDateString(int offsetDays) {
|
||||||
|
Calendar c = Calendar.getInstance();
|
||||||
|
c.add(Calendar.DAY_OF_YEAR, offsetDays);
|
||||||
|
return String.format(Locale.US, "%04d-%02d-%02d",
|
||||||
|
c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewModel Observation
|
||||||
|
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay);
|
viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay);
|
||||||
|
|
||||||
@@ -53,6 +153,15 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
viewModel.getErrorMessage().observe(getViewLifecycleOwner(), error -> {
|
viewModel.getErrorMessage().observe(getViewLifecycleOwner(), error -> {
|
||||||
if (error != null) showError(error);
|
if (error != null) showError(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
viewModel.getAvailablePaymentMethods().observe(getViewLifecycleOwner(), methods -> {
|
||||||
|
if (methods == null || methods.isEmpty()) return;
|
||||||
|
String currentSelection = binding.spinnerFilterPayment.getSelectedItem() != null
|
||||||
|
? binding.spinnerFilterPayment.getSelectedItem().toString() : "All";
|
||||||
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerFilterPayment,
|
||||||
|
methods.toArray(new String[0]));
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerFilterPayment, currentSelection);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -61,10 +170,12 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Display
|
||||||
|
|
||||||
private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) {
|
private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) {
|
||||||
if (data == null) return;
|
if (data == null) return;
|
||||||
|
|
||||||
// Summary
|
// Summary cards
|
||||||
binding.tvTotalRevenue.setText("$" + data.totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
binding.tvTotalRevenue.setText("$" + data.totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
||||||
binding.tvTotalTransactions.setText(String.valueOf(data.totalTransactions));
|
binding.tvTotalTransactions.setText(String.valueOf(data.totalTransactions));
|
||||||
binding.tvAvgTransaction.setText("$" + data.avgTransaction);
|
binding.tvAvgTransaction.setText("$" + data.avgTransaction);
|
||||||
@@ -73,11 +184,12 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
// Top Revenue Products
|
// Top Revenue Products
|
||||||
binding.llTopRevenue.removeAllViews();
|
binding.llTopRevenue.removeAllViews();
|
||||||
if (data.topRevenueProducts != null && !data.topRevenueProducts.isEmpty()) {
|
if (data.topRevenueProducts != null && !data.topRevenueProducts.isEmpty()) {
|
||||||
BigDecimal maxRevenue = data.topRevenueProducts.get(0).getValue();
|
BigDecimal maxRev = data.topRevenueProducts.get(0).getValue();
|
||||||
if (maxRevenue.compareTo(BigDecimal.ZERO) == 0) maxRevenue = BigDecimal.ONE;
|
if (maxRev.compareTo(BigDecimal.ZERO) == 0) maxRev = BigDecimal.ONE;
|
||||||
for (Map.Entry<String, BigDecimal> e : data.topRevenueProducts) {
|
for (Map.Entry<String, BigDecimal> e : data.topRevenueProducts) {
|
||||||
addBarRow(binding.llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
addBarRow(binding.llTopRevenue, e.getKey(),
|
||||||
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
|
e.getValue().floatValue() / maxRev.floatValue(), "#ff6b35");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addEmptyRow(binding.llTopRevenue, "No data");
|
addEmptyRow(binding.llTopRevenue, "No data");
|
||||||
@@ -99,15 +211,13 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
// Payment Methods
|
// Payment Methods
|
||||||
binding.llPaymentMethods.removeAllViews();
|
binding.llPaymentMethods.removeAllViews();
|
||||||
if (data.paymentMethodStats != null && !data.paymentMethodStats.isEmpty()) {
|
if (data.paymentMethodStats != null && !data.paymentMethodStats.isEmpty()) {
|
||||||
int maxPayment = data.paymentMethodStats.stream().mapToInt(Map.Entry::getValue).max().orElse(1);
|
int maxPay = data.paymentMethodStats.stream().mapToInt(Map.Entry::getValue).max().orElse(1);
|
||||||
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
String[] payColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
||||||
int ci = 0;
|
int ci = 0;
|
||||||
for (Map.Entry<String, Integer> e : data.paymentMethodStats) {
|
for (Map.Entry<String, Integer> e : data.paymentMethodStats) {
|
||||||
addBarRow(binding.llPaymentMethods, e.getKey(),
|
addBarRow(binding.llPaymentMethods, e.getKey(),
|
||||||
e.getValue() + " transactions",
|
e.getValue() + " transactions",
|
||||||
(float) e.getValue() / maxPayment,
|
(float) e.getValue() / maxPay, payColors[ci++ % payColors.length]);
|
||||||
paymentColors[ci % paymentColors.length]);
|
|
||||||
ci++;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addEmptyRow(binding.llPaymentMethods, "No data");
|
addEmptyRow(binding.llPaymentMethods, "No data");
|
||||||
@@ -116,36 +226,37 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
// Employee Performance
|
// Employee Performance
|
||||||
binding.llEmployeePerformance.removeAllViews();
|
binding.llEmployeePerformance.removeAllViews();
|
||||||
if (data.employeePerformance != null && !data.employeePerformance.isEmpty()) {
|
if (data.employeePerformance != null && !data.employeePerformance.isEmpty()) {
|
||||||
BigDecimal maxEmp = data.employeePerformance.get(data.employeePerformance.size() - 1).getValue();
|
BigDecimal maxEmp = data.employeePerformance.get(0).getValue();
|
||||||
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
||||||
maxEmp = data.employeePerformance.get(0).getValue();
|
|
||||||
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
|
||||||
|
|
||||||
for (Map.Entry<String, BigDecimal> e : data.employeePerformance) {
|
for (Map.Entry<String, BigDecimal> e : data.employeePerformance) {
|
||||||
addBarRow(binding.llEmployeePerformance, e.getKey(),
|
addBarRow(binding.llEmployeePerformance, e.getKey(),
|
||||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
e.getValue().floatValue() / maxEmp.floatValue(),
|
e.getValue().floatValue() / maxEmp.floatValue(), "#1a759f");
|
||||||
"#1a759f");
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addEmptyRow(binding.llEmployeePerformance, "No data");
|
addEmptyRow(binding.llEmployeePerformance, "No data");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Daily Revenue
|
// Daily Revenue
|
||||||
|
binding.tvDailyRevenueTitle.setText(data.dailyRevenueTitle);
|
||||||
binding.llDailyRevenue.removeAllViews();
|
binding.llDailyRevenue.removeAllViews();
|
||||||
if (data.dailyRevenue != null && !data.dailyRevenue.isEmpty()) {
|
if (data.dailyRevenue != null && !data.dailyRevenue.isEmpty()) {
|
||||||
BigDecimal maxDaily = data.dailyRevenue.stream().map(Map.Entry::getValue).max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
BigDecimal maxDaily = data.dailyRevenue.stream()
|
||||||
|
.map(Map.Entry::getValue).max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
||||||
if (maxDaily.compareTo(BigDecimal.ZERO) == 0) maxDaily = BigDecimal.ONE;
|
if (maxDaily.compareTo(BigDecimal.ZERO) == 0) maxDaily = BigDecimal.ONE;
|
||||||
for (Map.Entry<String, BigDecimal> e : data.dailyRevenue) {
|
for (Map.Entry<String, BigDecimal> e : data.dailyRevenue) {
|
||||||
String label = e.getKey().length() >= 10 ? e.getKey().substring(5) : e.getKey();
|
String label = e.getKey().length() >= 10 ? e.getKey().substring(5) : e.getKey();
|
||||||
addBarRow(binding.llDailyRevenue, label,
|
addBarRow(binding.llDailyRevenue, label,
|
||||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
e.getValue().floatValue() / maxDaily.floatValue(),
|
e.getValue().floatValue() / maxDaily.floatValue(), "#ff6b35");
|
||||||
"#ff6b35");
|
}
|
||||||
}
|
} else {
|
||||||
|
addEmptyRow(binding.llDailyRevenue, "No data");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chart Helpers
|
||||||
|
|
||||||
private void addBarRow(LinearLayout parent, String label, String value, float ratio, String color) {
|
private void addBarRow(LinearLayout parent, String label, String value, float ratio, String color) {
|
||||||
if (getContext() == null) return;
|
if (getContext() == null) return;
|
||||||
LinearLayout row = new LinearLayout(getContext());
|
LinearLayout row = new LinearLayout(getContext());
|
||||||
@@ -156,8 +267,7 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
labelRow.setOrientation(LinearLayout.HORIZONTAL);
|
labelRow.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
|
||||||
TextView tvLabel = new TextView(getContext());
|
TextView tvLabel = new TextView(getContext());
|
||||||
tvLabel.setLayoutParams(new LinearLayout.LayoutParams(
|
tvLabel.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
|
||||||
tvLabel.setText(label);
|
tvLabel.setText(label);
|
||||||
tvLabel.setTextColor(Color.parseColor("#444441"));
|
tvLabel.setTextColor(Color.parseColor("#444441"));
|
||||||
tvLabel.setTextSize(13f);
|
tvLabel.setTextSize(13f);
|
||||||
@@ -172,22 +282,19 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
labelRow.addView(tvValue);
|
labelRow.addView(tvValue);
|
||||||
|
|
||||||
LinearLayout barBg = new LinearLayout(getContext());
|
LinearLayout barBg = new LinearLayout(getContext());
|
||||||
LinearLayout.LayoutParams bgParams = new LinearLayout.LayoutParams(
|
LinearLayout.LayoutParams bgParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 12);
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT, 12);
|
|
||||||
bgParams.setMargins(0, 4, 0, 0);
|
bgParams.setMargins(0, 4, 0, 0);
|
||||||
barBg.setLayoutParams(bgParams);
|
barBg.setLayoutParams(bgParams);
|
||||||
barBg.setBackgroundColor(Color.parseColor("#EEEEEE"));
|
barBg.setBackgroundColor(Color.parseColor("#EEEEEE"));
|
||||||
|
|
||||||
|
float safeRatio = Math.max(0f, Math.min(1f, ratio));
|
||||||
View barFill = new View(getContext());
|
View barFill = new View(getContext());
|
||||||
LinearLayout.LayoutParams fillParams = new LinearLayout.LayoutParams(
|
barFill.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, safeRatio));
|
||||||
0, LinearLayout.LayoutParams.MATCH_PARENT, ratio);
|
|
||||||
barFill.setLayoutParams(fillParams);
|
|
||||||
barFill.setBackgroundColor(Color.parseColor(color));
|
barFill.setBackgroundColor(Color.parseColor(color));
|
||||||
barBg.addView(barFill);
|
barBg.addView(barFill);
|
||||||
|
|
||||||
View spacer = new View(getContext());
|
View spacer = new View(getContext());
|
||||||
spacer.setLayoutParams(new LinearLayout.LayoutParams(
|
spacer.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f - safeRatio));
|
||||||
0, LinearLayout.LayoutParams.MATCH_PARENT, 1f - ratio));
|
|
||||||
barBg.addView(spacer);
|
barBg.addView(spacer);
|
||||||
|
|
||||||
row.addView(labelRow);
|
row.addView(labelRow);
|
||||||
@@ -205,8 +312,7 @@ public class AnalyticsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showError(String msg) {
|
private void showError(String msg) {
|
||||||
if (getContext() == null || binding == null)
|
if (getContext() == null || binding == null) return;
|
||||||
return;
|
|
||||||
binding.tvTotalRevenue.setText("Error");
|
binding.tvTotalRevenue.setText("Error");
|
||||||
binding.tvTotalTransactions.setText("—");
|
binding.tvTotalTransactions.setText("—");
|
||||||
binding.tvAvgTransaction.setText("—");
|
binding.tvAvgTransaction.setText("—");
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.adapters.CouponAdapter;
|
||||||
|
import com.example.petstoremobile.databinding.FragmentCouponBinding;
|
||||||
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
import com.example.petstoremobile.viewmodels.CouponListViewModel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
public class CouponFragment extends Fragment implements CouponAdapter.OnCouponClickListener {
|
||||||
|
private FragmentCouponBinding binding;
|
||||||
|
private CouponListViewModel viewModel;
|
||||||
|
private CouponAdapter adapter;
|
||||||
|
private final List<CouponDTO> couponList = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
binding = FragmentCouponBinding.inflate(inflater, container, false);
|
||||||
|
viewModel = new ViewModelProvider(this).get(CouponListViewModel.class);
|
||||||
|
|
||||||
|
setupRecyclerView();
|
||||||
|
setupSearch();
|
||||||
|
setupStatusFilter();
|
||||||
|
setupTypeFilter();
|
||||||
|
setupSwipeRefresh();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
|
viewModel.loadCoupons(0, 100, null, null, null);
|
||||||
|
|
||||||
|
binding.fabAddCoupon.setOnClickListener(v -> openDetail(-1));
|
||||||
|
binding.btnBulkDeleteCoupons.setOnClickListener(v -> confirmBulkDelete());
|
||||||
|
|
||||||
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerCoupon, this);
|
||||||
|
UIUtils.setupFilterToggle(binding.btnToggleFilterCoupon, binding.layoutFilterCoupon, binding.etSearchCoupon,
|
||||||
|
binding.spinnerTypeCoupon, binding.spinnerStatusCoupon);
|
||||||
|
|
||||||
|
return binding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getCoupons().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
couponList.clear();
|
||||||
|
couponList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshCoupon.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new CouponAdapter(couponList, this);
|
||||||
|
binding.recyclerViewCoupon.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
binding.recyclerViewCoupon.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupStatusFilter() {
|
||||||
|
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
||||||
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusCoupon, statuses, this::applyFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupTypeFilter() {
|
||||||
|
String[] types = {"All Types", "FIXED", "PERCENT"};
|
||||||
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerTypeCoupon, types, this::applyFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupSearch() {
|
||||||
|
UIUtils.attachSearch(binding.etSearchCoupon, this::applyFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupSwipeRefresh() {
|
||||||
|
binding.swipeRefreshCoupon.setOnRefreshListener(this::applyFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyFilters() {
|
||||||
|
String statusStr = binding.spinnerStatusCoupon.getSelectedItem() != null ?
|
||||||
|
binding.spinnerStatusCoupon.getSelectedItem().toString() : "All Statuses";
|
||||||
|
Boolean active = null;
|
||||||
|
if (statusStr.equals("Active")) active = true;
|
||||||
|
else if (statusStr.equals("Inactive")) active = false;
|
||||||
|
|
||||||
|
String typeStr = binding.spinnerTypeCoupon.getSelectedItem() != null ?
|
||||||
|
binding.spinnerTypeCoupon.getSelectedItem().toString() : "All Types";
|
||||||
|
String discountType = typeStr.equals("All Types") ? null : typeStr;
|
||||||
|
|
||||||
|
viewModel.loadCoupons(0, 100, active, discountType, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openDetail(long id) {
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putLong("couponId", id);
|
||||||
|
Navigation.findNavController(requireView()).navigate(R.id.couponDetailFragment, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCouponClick(CouponDTO coupon) {
|
||||||
|
openDetail(coupon.getCouponId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSelectionChanged(int count) {
|
||||||
|
binding.btnBulkDeleteCoupons.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmBulkDelete() {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Confirm Bulk Delete")
|
||||||
|
.setMessage("Are you sure you want to delete the selected coupons?")
|
||||||
|
.setPositiveButton("Delete", (dialog, which) -> {
|
||||||
|
List<Long> ids = new ArrayList<>(adapter.getSelectedIds());
|
||||||
|
viewModel.bulkDeleteCoupons(ids).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
|
adapter.setSelectionMode(false);
|
||||||
|
applyFilters();
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
UIUtils.showToast(requireContext(), resource.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import android.view.ViewGroup;
|
|||||||
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.databinding.FragmentProductBinding;
|
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
@@ -74,7 +74,7 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
|
|
||||||
viewModel.getCategories().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getCategories().observe(getViewLifecycleOwner(), list -> {
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCategory, list,
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCategory, list,
|
||||||
CategoryDTO::getCategoryName, "All Categories", -1L, CategoryDTO::getCategoryId);
|
DropdownDTO::getLabel, "All Categories", -1L, DropdownDTO::getId);
|
||||||
});
|
});
|
||||||
|
|
||||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
@@ -111,9 +111,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
Long categoryId = null;
|
Long categoryId = null;
|
||||||
List<CategoryDTO> categories = viewModel.getCategories().getValue();
|
List<DropdownDTO> categories = viewModel.getCategories().getValue();
|
||||||
if (binding.spinnerCategory.getSelectedItemPosition() > 0 && categories != null && !categories.isEmpty()) {
|
if (binding.spinnerCategory.getSelectedItemPosition() > 0 && categories != null && !categories.isEmpty()) {
|
||||||
categoryId = categories.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getCategoryId();
|
categoryId = categories.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.loadProducts(query, categoryId);
|
viewModel.loadProducts(query, categoryId);
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
setupSearch();
|
setupSearch();
|
||||||
setupStoreFilter();
|
setupStoreFilter();
|
||||||
setupPaymentMethodFilter();
|
setupPaymentMethodFilter();
|
||||||
|
setupRefundStatusFilter();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
observeViewModel();
|
observeViewModel();
|
||||||
@@ -75,7 +76,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
|
|
||||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||||
StoreDTO::getStoreName, "Stores", null, StoreDTO::getStoreId);
|
StoreDTO::getStoreName, "All Stores", null, StoreDTO::getStoreId);
|
||||||
});
|
});
|
||||||
|
|
||||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
@@ -91,7 +92,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSale,
|
||||||
binding.spinnerPaymentMethod, binding.spinnerStore);
|
binding.spinnerPaymentMethod, binding.spinnerStore, binding.spinnerRefundStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
@@ -99,10 +100,15 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupPaymentMethodFilter() {
|
private void setupPaymentMethodFilter() {
|
||||||
String[] paymentMethods = {"Payments", "Cash", "Card"};
|
String[] paymentMethods = {"All Payments", "Cash", "Card"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupRefundStatusFilter() {
|
||||||
|
String[] refundStatuses = {"All Status", "Sale", "Refund"};
|
||||||
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerRefundStatus, refundStatuses, () -> loadSales(true));
|
||||||
|
}
|
||||||
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new SaleAdapter(saleList, this);
|
adapter = new SaleAdapter(saleList, this);
|
||||||
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -149,7 +155,12 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.loadSales(reset, query, paymentMethod, storeId);
|
Boolean isRefund = null;
|
||||||
|
if (binding.spinnerRefundStatus.getSelectedItemPosition() > 0) {
|
||||||
|
isRefund = binding.spinnerRefundStatus.getSelectedItemPosition() == 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadSales(reset, query, paymentMethod, storeId, isRefund);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -159,6 +170,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (sale.getSaleId() != null) {
|
if (sale.getSaleId() != null) {
|
||||||
args.putLong("saleId", sale.getSaleId());
|
args.putLong("saleId", sale.getSaleId());
|
||||||
|
args.putBoolean("viewOnly", true);
|
||||||
}
|
}
|
||||||
if (sale.getIsRefund() != null) {
|
if (sale.getIsRefund() != null) {
|
||||||
args.putBoolean("isRefund", sale.getIsRefund());
|
args.putBoolean("isRefund", sale.getIsRefund());
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
|
|
||||||
|
binding.fabAddService.setOnClickListener(v ->
|
||||||
|
NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail));
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,19 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.EmployeeAdapter;
|
import com.example.petstoremobile.adapters.EmployeeAdapter;
|
||||||
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentStaffBinding;
|
import com.example.petstoremobile.databinding.FragmentStaffBinding;
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.StaffListViewModel;
|
import com.example.petstoremobile.viewmodels.StaffListViewModel;
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Named;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
|
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
|
||||||
|
|
||||||
@@ -25,6 +31,9 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
private List<EmployeeDTO> staffList = new ArrayList<>();
|
private List<EmployeeDTO> staffList = new ArrayList<>();
|
||||||
private EmployeeAdapter adapter;
|
private EmployeeAdapter adapter;
|
||||||
|
|
||||||
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -33,15 +42,19 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
|
|
||||||
setupRecyclerView();
|
setupRecyclerView();
|
||||||
setupSearch();
|
setupSearch();
|
||||||
|
setupStatusFilter();
|
||||||
|
setupStoreFilter();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
observeViewModel();
|
observeViewModel();
|
||||||
|
|
||||||
viewModel.loadStaff();
|
viewModel.loadStaff();
|
||||||
|
viewModel.loadStores();
|
||||||
|
|
||||||
binding.fabAddStaff.setOnClickListener(v -> openDetail(-1));
|
binding.fabAddStaff.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerStaff, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerStaff, this);
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilterStaff, binding.layoutFilterStaff, binding.etSearchStaff);
|
UIUtils.setupFilterToggle(binding.btnToggleFilterStaff, binding.layoutFilterStaff, binding.etSearchStaff,
|
||||||
|
binding.spinnerStoreStaff, binding.spinnerStatusStaff);
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
@@ -53,6 +66,11 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStoreStaff, list,
|
||||||
|
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||||
|
});
|
||||||
|
|
||||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
binding.swipeRefreshStaff.setRefreshing(loading);
|
binding.swipeRefreshStaff.setRefreshing(loading);
|
||||||
});
|
});
|
||||||
@@ -60,12 +78,37 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new EmployeeAdapter(staffList, this);
|
adapter = new EmployeeAdapter(staffList, this);
|
||||||
|
adapter.setBaseUrl(baseUrl);
|
||||||
|
adapter.setToken(tokenManager.getToken());
|
||||||
binding.recyclerViewStaff.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewStaff.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.recyclerViewStaff.setAdapter(adapter);
|
binding.recyclerViewStaff.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setupStatusFilter() {
|
||||||
|
String[] statuses = {"All Statuses", "Active", "Inactive"};
|
||||||
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusStaff, statuses, this::applyFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupStoreFilter() {
|
||||||
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreStaff, this::applyFilters);
|
||||||
|
}
|
||||||
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchStaff, () -> viewModel.filter(binding.etSearchStaff.getText().toString()));
|
UIUtils.attachSearch(binding.etSearchStaff, this::applyFilters);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyFilters() {
|
||||||
|
String query = binding.etSearchStaff.getText().toString().trim();
|
||||||
|
String status = binding.spinnerStatusStaff.getSelectedItem() != null ?
|
||||||
|
binding.spinnerStatusStaff.getSelectedItem().toString() : "All Statuses";
|
||||||
|
|
||||||
|
Long storeId = null;
|
||||||
|
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||||
|
if (binding.spinnerStoreStaff.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||||
|
storeId = stores.get(binding.spinnerStoreStaff.getSelectedItemPosition() - 1).getStoreId();
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.filter(query, storeId, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
@@ -76,7 +119,7 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
EmployeeDTO e = staffList.get(position);
|
EmployeeDTO e = staffList.get(position);
|
||||||
args.putLong("employeeId", e.getEmployeeId());
|
args.putLong("employeeId", e.getId());
|
||||||
args.putString("username", e.getUsername() != null ? e.getUsername() : "");
|
args.putString("username", e.getUsername() != null ? e.getUsername() : "");
|
||||||
args.putString("firstName", e.getFirstName() != null ? e.getFirstName() : "");
|
args.putString("firstName", e.getFirstName() != null ? e.getFirstName() : "");
|
||||||
args.putString("lastName", e.getLastName() != null ? e.getLastName() : "");
|
args.putString("lastName", e.getLastName() != null ? e.getLastName() : "");
|
||||||
|
|||||||
@@ -32,13 +32,7 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private FragmentAdoptionDetailBinding binding;
|
private FragmentAdoptionDetailBinding binding;
|
||||||
private AdoptionDetailViewModel viewModel;
|
private AdoptionDetailViewModel viewModel;
|
||||||
|
private boolean isUpdatingUI = false;
|
||||||
private long preselectedPetId = -1;
|
|
||||||
private long preselectedCustomerId = -1;
|
|
||||||
private long preselectedStoreId = -1;
|
|
||||||
private long preselectedEmployeeId = -1;
|
|
||||||
|
|
||||||
private final String[] STATUSES = {"Pending", "Completed", "Cancelled"};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -59,7 +53,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
setupSpinners();
|
setupSpinners();
|
||||||
setupDatePicker();
|
setupDatePicker();
|
||||||
observeViewModel();
|
observeViewModel();
|
||||||
loadSpinnersData();
|
Bundle args = getArguments();
|
||||||
|
boolean isEditing = args != null && args.containsKey("adoptionId");
|
||||||
|
viewModel.loadInitialFormData(isEditing);
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
binding.btnAdoptionBack.setOnClickListener(v -> navigateBack());
|
binding.btnAdoptionBack.setOnClickListener(v -> navigateBack());
|
||||||
@@ -68,14 +64,39 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getPetList().observe(getViewLifecycleOwner(), list -> refreshPetSpinner());
|
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||||
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> refreshCustomerSpinner());
|
|
||||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
viewModel.getPetList().observe(getViewLifecycleOwner(), list -> {
|
||||||
viewModel.getEmployeeList().observe(getViewLifecycleOwner(), list -> refreshEmployeeSpinner());
|
AdoptionDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long petId = state != null ? state.selectedPetId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, list,
|
||||||
|
DropdownDTO::getLabel, "-- Select Pet --", petId, DropdownDTO::getId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
AdoptionDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long customerId = state != null ? state.selectedCustomerId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, list,
|
||||||
|
DropdownDTO::getLabel, "-- Select Customer --", customerId, DropdownDTO::getId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
AdoptionDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long storeId = state != null ? state.selectedStoreId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, list,
|
||||||
|
DropdownDTO::getLabel, "-- Select Store --", storeId, DropdownDTO::getId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getEmployeeList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
AdoptionDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long employeeId = state != null ? state.selectedEmployeeId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, list,
|
||||||
|
DropdownDTO::getLabel, "-- Select Staff --", employeeId, DropdownDTO::getId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,21 +108,12 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, STATUSES);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, new String[]{});
|
||||||
|
|
||||||
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
|
||||||
|
|
||||||
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
if (position > 0) {
|
viewModel.onCustomerSelected(position);
|
||||||
UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet);
|
|
||||||
} else {
|
|
||||||
if (!viewModel.isEditing()) {
|
|
||||||
binding.spinnerAdoptionPet.setSelection(0);
|
|
||||||
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onNothingSelected(AdapterView<?> parent) {}
|
public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
@@ -110,127 +122,97 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
if (position > 0 && viewModel.getStoreList().getValue() != null && position <= viewModel.getStoreList().getValue().size()) {
|
viewModel.onStoreSelected(position);
|
||||||
DropdownDTO selectedStore = viewModel.getStoreList().getValue().get(position - 1);
|
|
||||||
loadEmployees(selectedStore.getId());
|
|
||||||
} else {
|
|
||||||
viewModel.setEmployeeList(new ArrayList<>());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onNothingSelected(AdapterView<?> parent) {}
|
public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAdoptionPet, p -> viewModel.onPetSelected(p));
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAdoptionStatus, p -> notifyDateStatusChange());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupDatePicker() {
|
private void setupDatePicker() {
|
||||||
binding.etAdoptionDate.setOnClickListener(v -> UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, null));
|
binding.etAdoptionDate.setOnClickListener(v ->
|
||||||
|
UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, this::notifyDateStatusChange));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSpinnersData() {
|
private void notifyDateStatusChange() {
|
||||||
viewModel.loadPets().observe(getViewLifecycleOwner(), resource -> {
|
if (isUpdatingUI) return;
|
||||||
if (resource == null) return;
|
String date = binding.etAdoptionDate.getText().toString();
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
Object selected = binding.spinnerAdoptionStatus.getSelectedItem();
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
String status = selected != null ? selected.toString() : "";
|
||||||
viewModel.setPetList(resource.data);
|
viewModel.onDateChanged(date, status);
|
||||||
}
|
|
||||||
});
|
|
||||||
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource == null) return;
|
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
viewModel.setCustomerList(resource.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource == null) return;
|
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
viewModel.setStoreList(resource.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshPetSpinner() {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, viewModel.getPetList().getValue(),
|
|
||||||
DropdownDTO::getLabel, "-- Select Pet --",
|
|
||||||
preselectedPetId, DropdownDTO::getId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshCustomerSpinner() {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, viewModel.getCustomerList().getValue(),
|
|
||||||
DropdownDTO::getLabel, "-- Select Customer --",
|
|
||||||
preselectedCustomerId, DropdownDTO::getId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshStoreSpinner() {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, viewModel.getStoreList().getValue(),
|
|
||||||
DropdownDTO::getLabel, "-- Select Store --",
|
|
||||||
preselectedStoreId, DropdownDTO::getId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadEmployees(Long storeId) {
|
|
||||||
viewModel.loadEmployees(storeId).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource == null) return;
|
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
viewModel.setEmployeeList(resource.data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshEmployeeSpinner() {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, viewModel.getEmployeeList().getValue(),
|
|
||||||
DropdownDTO::getLabel, "-- Select Staff --",
|
|
||||||
preselectedEmployeeId, DropdownDTO::getId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("adoptionId")) {
|
if (a != null && a.containsKey("adoptionId")) {
|
||||||
long adoptionId = a.getLong("adoptionId");
|
viewModel.setAdoptionId(a.getLong("adoptionId"));
|
||||||
viewModel.setAdoptionId(adoptionId);
|
|
||||||
binding.tvAdoptionMode.setText("Edit Adoption");
|
|
||||||
binding.tvAdoptionId.setText(DateTimeUtils.formatId(adoptionId));
|
|
||||||
binding.tvAdoptionId.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnDeleteAdoption.setVisibility(View.VISIBLE);
|
|
||||||
loadAdoptionData();
|
loadAdoptionData();
|
||||||
} else {
|
return;
|
||||||
viewModel.setAdoptionId(-1);
|
|
||||||
binding.tvAdoptionMode.setText("Add Adoption");
|
|
||||||
binding.btnDeleteAdoption.setVisibility(View.GONE);
|
|
||||||
binding.tvAdoptionId.setVisibility(View.GONE);
|
|
||||||
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
|
||||||
}
|
}
|
||||||
|
viewModel.setAdoptionId(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAdoptionData() {
|
private void loadAdoptionData() {
|
||||||
viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.ERROR) {
|
||||||
AdoptionDTO a = resource.data;
|
|
||||||
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
|
||||||
preselectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
|
|
||||||
preselectedStoreId = a.getSourceStoreId() != null ? a.getSourceStoreId() : -1;
|
|
||||||
preselectedEmployeeId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
|
|
||||||
|
|
||||||
binding.etAdoptionDate.setText(a.getAdoptionDate());
|
|
||||||
binding.etAdoptionFee.setText(a.getAdoptionFee() != null ? a.getAdoptionFee().toString() : "");
|
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerAdoptionStatus, a.getAdoptionStatus());
|
|
||||||
|
|
||||||
refreshPetSpinner();
|
|
||||||
refreshCustomerSpinner();
|
|
||||||
refreshStoreSpinner();
|
|
||||||
|
|
||||||
if (preselectedCustomerId != -1) {
|
|
||||||
UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet);
|
|
||||||
}
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Failed to load adoption: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Failed to load adoption: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyViewState(AdoptionDetailViewModel.ViewState state) {
|
||||||
|
isUpdatingUI = true;
|
||||||
|
|
||||||
|
binding.tvAdoptionMode.setText(state.modeTitle);
|
||||||
|
binding.tvAdoptionId.setText(DateTimeUtils.formatId(viewModel.getAdoptionId()));
|
||||||
|
binding.tvAdoptionId.setVisibility(state.isAdoptionIdVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnDeleteAdoption.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnSaveAdoption.setText(state.saveButtonText);
|
||||||
|
binding.btnSaveAdoption.setVisibility(state.isSaveVisible ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerAdoptionCustomer);
|
||||||
|
UIUtils.setViewsEnabled(state.isPetEnabled, binding.spinnerAdoptionPet);
|
||||||
|
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerAdoptionStore);
|
||||||
|
UIUtils.setViewsEnabled(state.isEmployeeEnabled, binding.spinnerAdoptionEmployee);
|
||||||
|
UIUtils.setViewsEnabled(state.isDateEnabled, binding.etAdoptionDate);
|
||||||
|
UIUtils.setViewsEnabled(state.isFeeEnabled, binding.etAdoptionFee);
|
||||||
|
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAdoptionStatus);
|
||||||
|
|
||||||
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, state.availableStatuses);
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerAdoptionStatus, state.selectedStatus);
|
||||||
|
|
||||||
|
if (!state.adoptionDate.isEmpty()) {
|
||||||
|
binding.etAdoptionDate.setText(state.adoptionDate);
|
||||||
|
}
|
||||||
|
if (!state.adoptionFee.isEmpty()) {
|
||||||
|
binding.etAdoptionFee.setText(state.adoptionFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-populate spinners with updated preselected IDs
|
||||||
|
List<DropdownDTO> pets = viewModel.getPetList().getValue();
|
||||||
|
if (pets != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet,
|
||||||
|
pets, DropdownDTO::getLabel, "-- Select Pet --", state.selectedPetId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
List<DropdownDTO> customers = viewModel.getCustomerList().getValue();
|
||||||
|
if (customers != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer,
|
||||||
|
customers, DropdownDTO::getLabel, "-- Select Customer --", state.selectedCustomerId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
List<DropdownDTO> stores = viewModel.getStoreList().getValue();
|
||||||
|
if (stores != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore,
|
||||||
|
stores, DropdownDTO::getLabel, "-- Select Store --", state.selectedStoreId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
List<DropdownDTO> employees = viewModel.getEmployeeList().getValue();
|
||||||
|
if (employees != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee,
|
||||||
|
employees, DropdownDTO::getLabel, "-- Select Staff --", state.selectedEmployeeId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
isUpdatingUI = false;
|
||||||
|
}
|
||||||
|
|
||||||
private void saveAdoption() {
|
private void saveAdoption() {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) return;
|
||||||
@@ -240,8 +222,7 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
BigDecimal fee = BigDecimal.ZERO;
|
BigDecimal fee = BigDecimal.ZERO;
|
||||||
String feeStr = binding.etAdoptionFee.getText().toString().trim();
|
String feeStr = binding.etAdoptionFee.getText().toString().trim();
|
||||||
if (!feeStr.isEmpty()) {
|
if (!feeStr.isEmpty()) {
|
||||||
if (!InputValidator.isPositiveDecimal(binding.etAdoptionFee, "Adoption Fee")) return;
|
try { fee = new BigDecimal(feeStr); } catch (NumberFormatException ignored) {}
|
||||||
fee = new BigDecimal(feeStr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownDTO customer = viewModel.getCustomerList().getValue().get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
DropdownDTO customer = viewModel.getCustomerList().getValue().get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
||||||
@@ -249,22 +230,16 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
|
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
|
||||||
|
|
||||||
Long employeeId = null;
|
Long employeeId = null;
|
||||||
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0) {
|
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0 && viewModel.getEmployeeList().getValue() != null) {
|
||||||
employeeId = viewModel.getEmployeeList().getValue().get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
|
employeeId = viewModel.getEmployeeList().getValue().get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
String adoptionDate = binding.etAdoptionDate.getText().toString().trim();
|
String adoptionDate = binding.etAdoptionDate.getText().toString().trim();
|
||||||
String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()];
|
Object selectedStatus = binding.spinnerAdoptionStatus.getSelectedItem();
|
||||||
|
String status = selectedStatus != null ? selectedStatus.toString().toUpperCase() : "";
|
||||||
|
|
||||||
AdoptionDTO dto = new AdoptionDTO(
|
AdoptionDTO dto = new AdoptionDTO(
|
||||||
pet.getId(),
|
pet.getId(), customer.getId(), employeeId, store.getId(), adoptionDate, status, fee);
|
||||||
customer.getId(),
|
|
||||||
employeeId,
|
|
||||||
store.getId(),
|
|
||||||
adoptionDate,
|
|
||||||
status,
|
|
||||||
fee
|
|
||||||
);
|
|
||||||
|
|
||||||
viewModel.saveAdoption(dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.saveAdoption(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import com.example.petstoremobile.utils.SpinnerUtils;
|
|||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel;
|
import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,12 +37,6 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private FragmentAppointmentDetailBinding binding;
|
private FragmentAppointmentDetailBinding binding;
|
||||||
|
|
||||||
private long preselectedPetId = -1;
|
|
||||||
private long preselectedServiceId = -1;
|
|
||||||
private long preselectedCustomerId = -1;
|
|
||||||
private long preselectedStoreId = -1;
|
|
||||||
private long preselectedStaffId = -1;
|
|
||||||
|
|
||||||
private final Integer[] HOURS = {9, 10, 11, 12, 13, 14, 15, 16, 17};
|
private final Integer[] HOURS = {9, 10, 11, 12, 13, 14, 15, 16, 17};
|
||||||
private final Integer[] MINUTES = {0, 15, 30, 45};
|
private final Integer[] MINUTES = {0, 15, 30, 45};
|
||||||
|
|
||||||
@@ -125,20 +121,35 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||||
|
|
||||||
viewModel.getCustomers().observe(getViewLifecycleOwner(), list ->
|
viewModel.getCustomers().observe(getViewLifecycleOwner(), list -> {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, list, DropdownDTO::getLabel, "-- Select Customer --", preselectedCustomerId, DropdownDTO::getId));
|
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long id = state != null ? state.selectedCustomerId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, list, DropdownDTO::getLabel, "-- Select Customer --", id, DropdownDTO::getId);
|
||||||
|
});
|
||||||
|
|
||||||
viewModel.getStores().observe(getViewLifecycleOwner(), list ->
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId));
|
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long id = state != null ? state.selectedStoreId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", id, DropdownDTO::getId);
|
||||||
|
});
|
||||||
|
|
||||||
viewModel.getServices().observe(getViewLifecycleOwner(), list ->
|
viewModel.getServices().observe(getViewLifecycleOwner(), list -> {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, list, ServiceDTO::getServiceName, "-- Select Service --", preselectedServiceId, ServiceDTO::getServiceId));
|
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long id = state != null ? state.selectedServiceId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, list, ServiceDTO::getServiceName, "-- Select Service --", id, ServiceDTO::getServiceId);
|
||||||
|
});
|
||||||
|
|
||||||
viewModel.getCustomerPets().observe(getViewLifecycleOwner(), list ->
|
viewModel.getCustomerPets().observe(getViewLifecycleOwner(), list -> {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, list, DropdownDTO::getLabel, "-- Select Pet --", preselectedPetId, DropdownDTO::getId));
|
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long id = state != null ? state.selectedPetId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, list, DropdownDTO::getLabel, "-- Select Pet --", id, DropdownDTO::getId);
|
||||||
|
});
|
||||||
|
|
||||||
viewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list ->
|
viewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list -> {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, list, DropdownDTO::getLabel, "-- Select Staff --", preselectedStaffId, DropdownDTO::getId));
|
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
Long id = state != null ? state.selectedStaffId : null;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, list, DropdownDTO::getLabel, "-- Select Staff --", id, DropdownDTO::getId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
@@ -166,10 +177,29 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerMinute);
|
UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerMinute);
|
||||||
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAppointmentStatus);
|
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAppointmentStatus);
|
||||||
|
|
||||||
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
|
|
||||||
String current = selected != null ? selected.toString() : "";
|
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, state.availableStatuses);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, state.availableStatuses);
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, current);
|
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, state.selectedStatus);
|
||||||
|
|
||||||
|
// Re-populate dropdown spinners with current selected IDs from ViewState
|
||||||
|
List<DropdownDTO> customers = viewModel.getCustomers().getValue();
|
||||||
|
if (customers != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer,
|
||||||
|
customers, DropdownDTO::getLabel, "-- Select Customer --", state.selectedCustomerId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
List<DropdownDTO> stores = viewModel.getStores().getValue();
|
||||||
|
if (stores != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore,
|
||||||
|
stores, DropdownDTO::getLabel, "-- Select Store --", state.selectedStoreId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
List<ServiceDTO> services = viewModel.getServices().getValue();
|
||||||
|
if (services != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService,
|
||||||
|
services, ServiceDTO::getServiceName, "-- Select Service --", state.selectedServiceId, ServiceDTO::getServiceId);
|
||||||
|
|
||||||
|
List<DropdownDTO> pets = viewModel.getCustomerPets().getValue();
|
||||||
|
if (pets != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet,
|
||||||
|
pets, DropdownDTO::getLabel, "-- Select Pet --", state.selectedPetId, DropdownDTO::getId);
|
||||||
|
|
||||||
|
List<DropdownDTO> staff = viewModel.getStoreEmployees().getValue();
|
||||||
|
if (staff != null) SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff,
|
||||||
|
staff, DropdownDTO::getLabel, "-- Select Staff --", state.selectedStaffId, DropdownDTO::getId);
|
||||||
|
|
||||||
isUpdatingUI = false;
|
isUpdatingUI = false;
|
||||||
}
|
}
|
||||||
@@ -200,20 +230,15 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
AppointmentDTO a = resource.data;
|
AppointmentDTO a = resource.data;
|
||||||
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
|
||||||
preselectedServiceId = a.getServiceId() != null ? a.getServiceId() : -1;
|
|
||||||
preselectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
|
|
||||||
preselectedStoreId = a.getStoreId() != null ? a.getStoreId() : -1;
|
|
||||||
preselectedStaffId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
|
|
||||||
|
|
||||||
binding.etAppointmentDate.setText(a.getAppointmentDate());
|
binding.etAppointmentDate.setText(a.getAppointmentDate());
|
||||||
parseAndSetTimeSpinners(a.getAppointmentTime() != null ? a.getAppointmentTime() : "09:00");
|
parseAndSetTimeSpinners(a.getAppointmentTime() != null ? a.getAppointmentTime() : "09:00");
|
||||||
|
|
||||||
String status = a.getAppointmentStatus();
|
String status = a.getAppointmentStatus();
|
||||||
if (status != null && !status.isEmpty()) {
|
if (status != null && !status.isEmpty()) {
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, DateTimeUtils.formatStatusFromBackend(status));
|
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, DateTimeUtils.formatStatusFromBackend(status));
|
||||||
}
|
}
|
||||||
notifyDateTimeStatusChange();
|
notifyDateTimeStatusChange();
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
Toast.makeText(getContext(), "Failed to load appointment", Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.databinding.FragmentCouponDetailBinding;
|
||||||
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
import com.example.petstoremobile.viewmodels.CouponDetailViewModel;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
public class CouponDetailFragment extends Fragment {
|
||||||
|
private FragmentCouponDetailBinding binding;
|
||||||
|
private CouponDetailViewModel viewModel;
|
||||||
|
private long couponId = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
binding = FragmentCouponDetailBinding.inflate(inflater, container, false);
|
||||||
|
viewModel = new ViewModelProvider(this).get(CouponDetailViewModel.class);
|
||||||
|
|
||||||
|
if (getArguments() != null) {
|
||||||
|
couponId = getArguments().getLong("couponId", -1);
|
||||||
|
viewModel.setCouponId(couponId, couponId > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupDiscountTypeSpinner();
|
||||||
|
setupDatePicker(binding.etStartsAtDetail, null, () -> {
|
||||||
|
String selectedDate = binding.etStartsAtDetail.getText().toString();
|
||||||
|
if (selectedDate.isEmpty()) {
|
||||||
|
binding.etEndsAtDetail.setText("");
|
||||||
|
} else {
|
||||||
|
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
||||||
|
if (selectedDate.equals(today)) {
|
||||||
|
binding.cbActiveDetail.setChecked(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setupDatePicker(binding.etEndsAtDetail, binding.etStartsAtDetail, null);
|
||||||
|
|
||||||
|
if (couponId > 0) {
|
||||||
|
loadCouponDetails();
|
||||||
|
binding.btnDeleteCouponDetail.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
binding.tvTitleCouponDetail.setText("Add Coupon");
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnBackCouponDetail.setOnClickListener(v -> Navigation.findNavController(v).popBackStack());
|
||||||
|
binding.btnSaveCouponDetail.setOnClickListener(v -> saveCoupon());
|
||||||
|
binding.btnDeleteCouponDetail.setOnClickListener(v -> confirmDelete());
|
||||||
|
|
||||||
|
return binding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupDiscountTypeSpinner() {
|
||||||
|
String[] types = {"FIXED", "PERCENT"};
|
||||||
|
ArrayAdapter<String> adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item, types);
|
||||||
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
binding.spinnerDiscountTypeDetail.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupDatePicker(android.widget.EditText editText, android.widget.EditText dependOn, Runnable onDateSet) {
|
||||||
|
editText.setFocusable(false);
|
||||||
|
editText.setClickable(true);
|
||||||
|
editText.setCursorVisible(false);
|
||||||
|
editText.setOnClickListener(v -> {
|
||||||
|
if (dependOn != null && dependOn.getText().toString().trim().isEmpty()) {
|
||||||
|
UIUtils.showToast(requireContext(), "Please select a start date first");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UIUtils.showDatePicker(requireContext(), editText, onDateSet);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadCouponDetails() {
|
||||||
|
viewModel.loadCoupon(couponId).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
CouponDTO coupon = resource.data;
|
||||||
|
binding.etCouponCodeDetail.setText(coupon.getCouponCode());
|
||||||
|
binding.spinnerDiscountTypeDetail.setSelection(coupon.getDiscountType().equals("FIXED") ? 0 : 1);
|
||||||
|
binding.etDiscountValueDetail.setText(coupon.getDiscountValue().stripTrailingZeros().toPlainString());
|
||||||
|
binding.etMinOrderAmountDetail.setText(coupon.getMinOrderAmount().stripTrailingZeros().toPlainString());
|
||||||
|
binding.etUsageLimitDetail.setText(coupon.getUsageLimit() != null ? String.valueOf(coupon.getUsageLimit()) : "");
|
||||||
|
binding.cbActiveDetail.setChecked(Boolean.TRUE.equals(coupon.getActive()));
|
||||||
|
|
||||||
|
if (coupon.getStartsAt() != null) binding.etStartsAtDetail.setText(coupon.getStartsAt().substring(0, 10));
|
||||||
|
if (coupon.getEndsAt() != null) binding.etEndsAtDetail.setText(coupon.getEndsAt().substring(0, 10));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveCoupon() {
|
||||||
|
if (!InputValidator.isNotEmpty(binding.etCouponCodeDetail, "Coupon Code")) return;
|
||||||
|
if (!InputValidator.isGreaterThanZero(binding.etDiscountValueDetail, "Discount Value")) return;
|
||||||
|
if (!InputValidator.isPositiveDecimal(binding.etMinOrderAmountDetail, "Min Order Amount")) return;
|
||||||
|
|
||||||
|
String startsAt = binding.etStartsAtDetail.getText().toString().trim();
|
||||||
|
String endsAt = binding.etEndsAtDetail.getText().toString().trim();
|
||||||
|
|
||||||
|
if (!startsAt.isEmpty() && !endsAt.isEmpty()) {
|
||||||
|
if (startsAt.compareTo(endsAt) > 0) {
|
||||||
|
binding.etEndsAtDetail.setError("End date must be after start date");
|
||||||
|
UIUtils.showToast(requireContext(), "End date must be after start date");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String usageLimitStr = binding.etUsageLimitDetail.getText().toString().trim();
|
||||||
|
if (!usageLimitStr.isEmpty()) {
|
||||||
|
if (!InputValidator.isPositiveInteger(binding.etUsageLimitDetail, "Usage Limit")) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CouponDTO dto = new CouponDTO();
|
||||||
|
dto.setCouponCode(binding.etCouponCodeDetail.getText().toString().trim().toUpperCase());
|
||||||
|
dto.setDiscountType(binding.spinnerDiscountTypeDetail.getSelectedItem().toString());
|
||||||
|
dto.setDiscountValue(new BigDecimal(binding.etDiscountValueDetail.getText().toString().trim()));
|
||||||
|
dto.setMinOrderAmount(new BigDecimal(binding.etMinOrderAmountDetail.getText().toString().trim()));
|
||||||
|
|
||||||
|
String usageLimit = binding.etUsageLimitDetail.getText().toString().trim();
|
||||||
|
dto.setUsageLimit(usageLimit.isEmpty() ? null : Integer.parseInt(usageLimit));
|
||||||
|
|
||||||
|
dto.setActive(binding.cbActiveDetail.isChecked());
|
||||||
|
|
||||||
|
dto.setStartsAt(startsAt.isEmpty() ? null : startsAt + "T00:00:00");
|
||||||
|
|
||||||
|
dto.setEndsAt(endsAt.isEmpty() ? null : endsAt + "T23:59:59");
|
||||||
|
|
||||||
|
viewModel.saveCoupon(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
|
Navigation.findNavController(requireView()).popBackStack();
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
UIUtils.showToast(requireContext(), resource.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmDelete() {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Delete Coupon")
|
||||||
|
.setMessage("Are you sure you want to delete this coupon?")
|
||||||
|
.setPositiveButton("Delete", (dialog, which) -> {
|
||||||
|
viewModel.deleteCoupon().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
|
Navigation.findNavController(requireView()).popBackStack();
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
UIUtils.showToast(requireContext(), resource.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -92,7 +92,7 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
viewModel.setProductList(resource.data.getContent());
|
viewModel.setProductList(resource.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -105,8 +105,8 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private void refreshProductSpinner() {
|
private void refreshProductSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
|
||||||
ProductDTO::getProdName, "-- Select Product --",
|
DropdownDTO::getLabel, "-- Select Product --",
|
||||||
preselectedProductId, ProductDTO::getProdId);
|
preselectedProductId, DropdownDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
@@ -156,9 +156,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
|
|
||||||
int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim());
|
int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim());
|
||||||
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
|
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
|
||||||
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
|
DropdownDTO product = viewModel.getProductList().getValue().get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
|
||||||
|
|
||||||
InventoryDTO request = new InventoryDTO(product.getProdId(), store.getId(), quantity);
|
InventoryDTO request = new InventoryDTO(product.getId(), store.getId(), quantity);
|
||||||
setButtonsEnabled(false);
|
setButtonsEnabled(false);
|
||||||
|
|
||||||
viewModel.saveInventory(request).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.saveInventory(request).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
|||||||
@@ -41,9 +41,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private FragmentPetDetailBinding binding;
|
private FragmentPetDetailBinding binding;
|
||||||
private PetDetailViewModel viewModel;
|
private PetDetailViewModel viewModel;
|
||||||
|
private boolean isUpdatingUI = false;
|
||||||
private Long selectedCustomerId = null;
|
|
||||||
private Long selectedStoreId = null;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -65,6 +63,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
setupSpinner();
|
setupSpinner();
|
||||||
observeViewModel();
|
observeViewModel();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
viewModel.loadInitialFormData();
|
||||||
|
|
||||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||||
binding.btnSavePet.setOnClickListener(v -> savePet());
|
binding.btnSavePet.setOnClickListener(v -> savePet());
|
||||||
@@ -72,23 +71,18 @@ public class PetDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void observeViewModel() {
|
private void observeViewModel() {
|
||||||
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> updateCustomerSpinnerSelection());
|
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> updateStoreSpinnerSelection());
|
|
||||||
|
|
||||||
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> {
|
||||||
if (resource == null) return;
|
PetDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
Long selectedCustomerId = state != null ? state.selectedCustomerId : null;
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
updateCustomerSpinnerSelection(selectedCustomerId);
|
||||||
viewModel.setCustomerList(resource.data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||||
if (resource == null) return;
|
PetDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
Long selectedStoreId = state != null ? state.selectedStoreId : null;
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
updateStoreSpinnerSelection(selectedStoreId);
|
||||||
viewModel.setStoreList(resource.data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,12 +113,12 @@ public class PetDetailFragment extends Fragment {
|
|||||||
String status = binding.spinnerPetStatus.getSelectedItem().toString();
|
String status = binding.spinnerPetStatus.getSelectedItem().toString();
|
||||||
|
|
||||||
Long customerId = null;
|
Long customerId = null;
|
||||||
if (binding.spinnerCustomer.getSelectedItemPosition() > 0) {
|
if (binding.spinnerCustomer.getSelectedItemPosition() > 0 && viewModel.getCustomerList().getValue() != null) {
|
||||||
customerId = viewModel.getCustomerList().getValue().get(binding.spinnerCustomer.getSelectedItemPosition() - 1).getId();
|
customerId = viewModel.getCustomerList().getValue().get(binding.spinnerCustomer.getSelectedItemPosition() - 1).getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId = null;
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0) {
|
if (binding.spinnerStore.getSelectedItemPosition() > 0 && viewModel.getStoreList().getValue() != null) {
|
||||||
storeId = viewModel.getStoreList().getValue().get(binding.spinnerStore.getSelectedItemPosition() - 1).getId();
|
storeId = viewModel.getStoreList().getValue().get(binding.spinnerStore.getSelectedItemPosition() - 1).getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,24 +187,12 @@ public class PetDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
if (getArguments() != null && getArguments().containsKey("petId")) {
|
if (getArguments() != null && getArguments().containsKey("petId")) {
|
||||||
long petId = getArguments().getLong("petId");
|
viewModel.setPetId(getArguments().getLong("petId"));
|
||||||
viewModel.setPetId(petId);
|
|
||||||
binding.tvMode.setText("Edit Pet");
|
|
||||||
binding.tvPetId.setText(DateTimeUtils.formatId(petId));
|
|
||||||
binding.tvPetId.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnDeletePet.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
UIUtils.setViewsEnabled(false, binding.etPetSpecies, binding.etPetBreed);
|
|
||||||
loadPetData();
|
loadPetData();
|
||||||
} else {
|
return;
|
||||||
viewModel.setPetId(-1);
|
|
||||||
binding.tvMode.setText("Add Pet");
|
|
||||||
binding.tvPetId.setVisibility(View.GONE);
|
|
||||||
binding.btnDeletePet.setVisibility(View.GONE);
|
|
||||||
binding.btnSavePet.setText("Add");
|
|
||||||
|
|
||||||
UIUtils.setViewsEnabled(true, binding.etPetSpecies, binding.etPetBreed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.setPetId(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadPetData() {
|
private void loadPetData() {
|
||||||
@@ -226,20 +208,13 @@ public class PetDetailFragment extends Fragment {
|
|||||||
if (p.getPetPrice() != null) {
|
if (p.getPetPrice() != null) {
|
||||||
binding.etPetPrice.setText(String.format(Locale.getDefault(), "%.2f", p.getPetPrice()));
|
binding.etPetPrice.setText(String.format(Locale.getDefault(), "%.2f", p.getPetPrice()));
|
||||||
}
|
}
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerPetStatus, p.getPetStatus());
|
|
||||||
|
|
||||||
selectedCustomerId = p.getCustomerId();
|
|
||||||
updateCustomerSpinnerSelection();
|
|
||||||
|
|
||||||
selectedStoreId = p.getStoreId();
|
|
||||||
updateStoreSpinnerSelection();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Failed to load pet: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Failed to load pet: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCustomerSpinnerSelection() {
|
private void updateCustomerSpinnerSelection(Long selectedCustomerId) {
|
||||||
SpinnerUtils.populateSpinner(
|
SpinnerUtils.populateSpinner(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
binding.spinnerCustomer,
|
binding.spinnerCustomer,
|
||||||
@@ -251,7 +226,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStoreSpinnerSelection() {
|
private void updateStoreSpinnerSelection(Long selectedStoreId) {
|
||||||
SpinnerUtils.populateSpinner(
|
SpinnerUtils.populateSpinner(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
binding.spinnerStore,
|
binding.spinnerStore,
|
||||||
@@ -264,36 +239,76 @@ public class PetDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupSpinner() {
|
private void setupSpinner() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus,
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, new String[]{});
|
||||||
new String[]{"Available", "Adopted", "Owned"});
|
|
||||||
|
|
||||||
binding.spinnerPetStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
String status = parent.getItemAtPosition(position).toString();
|
if (isUpdatingUI) return;
|
||||||
|
viewModel.onCustomerSelected(position);
|
||||||
clearSpinnerError(binding.spinnerCustomer);
|
|
||||||
clearSpinnerError(binding.spinnerStore);
|
|
||||||
|
|
||||||
if ("Available".equalsIgnoreCase(status)) {
|
|
||||||
binding.spinnerCustomer.setSelection(0);
|
|
||||||
UIUtils.setViewsEnabled(false, binding.spinnerCustomer);
|
|
||||||
} else {
|
|
||||||
UIUtils.setViewsEnabled(true, binding.spinnerCustomer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("Owned".equalsIgnoreCase(status)) {
|
|
||||||
binding.spinnerStore.setSelection(0);
|
|
||||||
UIUtils.setViewsEnabled(false, binding.spinnerStore);
|
|
||||||
} else {
|
|
||||||
UIUtils.setViewsEnabled(true, binding.spinnerStore);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNothingSelected(AdapterView<?> parent) {
|
public void onNothingSelected(AdapterView<?> parent) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (isUpdatingUI) return;
|
||||||
|
viewModel.onStoreSelected(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.spinnerPetStatus.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (isUpdatingUI) return;
|
||||||
|
String status = parent.getItemAtPosition(position).toString();
|
||||||
|
clearSpinnerError(binding.spinnerCustomer);
|
||||||
|
clearSpinnerError(binding.spinnerStore);
|
||||||
|
viewModel.onStatusSelected(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyViewState(PetDetailViewModel.ViewState state) {
|
||||||
|
isUpdatingUI = true;
|
||||||
|
|
||||||
|
binding.tvMode.setText(state.modeTitle);
|
||||||
|
binding.tvPetId.setText(DateTimeUtils.formatId(viewModel.getPetId()));
|
||||||
|
binding.tvPetId.setVisibility(state.isPetIdVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnDeletePet.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnSavePet.setText(state.saveButtonText);
|
||||||
|
|
||||||
|
UIUtils.setViewsEnabled(state.isSpeciesEnabled, binding.etPetSpecies);
|
||||||
|
UIUtils.setViewsEnabled(state.isBreedEnabled, binding.etPetBreed);
|
||||||
|
UIUtils.setViewsEnabled(state.isCustomerEnabled, binding.spinnerCustomer);
|
||||||
|
UIUtils.setViewsEnabled(state.isStoreEnabled, binding.spinnerStore);
|
||||||
|
|
||||||
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus, state.availableStatuses);
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerPetStatus, state.selectedStatus);
|
||||||
|
|
||||||
|
updateCustomerSpinnerSelection(state.selectedCustomerId);
|
||||||
|
updateStoreSpinnerSelection(state.selectedStoreId);
|
||||||
|
|
||||||
|
if (!state.isCustomerEnabled && binding.spinnerCustomer.getSelectedItemPosition() != 0) {
|
||||||
|
binding.spinnerCustomer.setSelection(0);
|
||||||
|
}
|
||||||
|
if (!state.isStoreEnabled && binding.spinnerStore.getSelectedItemPosition() != 0) {
|
||||||
|
binding.spinnerStore.setSelection(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
isUpdatingUI = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearSpinnerError(Spinner spinner) {
|
private void clearSpinnerError(Spinner spinner) {
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
viewModel.setCategoryList(resource.data.getContent());
|
viewModel.setCategoryList(resource.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -128,8 +128,8 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private void updateCategorySpinner() {
|
private void updateCategorySpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
|
||||||
CategoryDTO::getCategoryName, "-- Select Category --",
|
DropdownDTO::getLabel, "-- Select Category --",
|
||||||
preselectedCategoryId, CategoryDTO::getCategoryId);
|
preselectedCategoryId, DropdownDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -248,8 +248,8 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
String desc = binding.etProductDesc.getText().toString().trim();
|
String desc = binding.etProductDesc.getText().toString().trim();
|
||||||
BigDecimal price = new BigDecimal(binding.etProductPrice.getText().toString().trim());
|
BigDecimal price = new BigDecimal(binding.etProductPrice.getText().toString().trim());
|
||||||
|
|
||||||
CategoryDTO category = viewModel.getCategoryList().getValue().get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
|
DropdownDTO category = viewModel.getCategoryList().getValue().get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
|
||||||
ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price);
|
ProductDTO dto = new ProductDTO(name, category.getId(), desc, price);
|
||||||
|
|
||||||
viewModel.saveProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.saveProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
observeViewModel();
|
observeViewModel();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
if (!viewModel.isViewOnly()) {
|
if (viewModel.isViewOnly()) {
|
||||||
|
binding.llAddItemRow.setVisibility(View.GONE);
|
||||||
|
binding.btnSaveSale.setVisibility(View.GONE);
|
||||||
|
UIUtils.setViewsEnabled(false, binding.spinnerSaleStore, binding.spinnerSaleCustomer, binding.spinnerPaymentMethod);
|
||||||
|
} else {
|
||||||
loadData();
|
loadData();
|
||||||
setupAddItem();
|
setupAddItem();
|
||||||
}
|
}
|
||||||
@@ -84,8 +88,9 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
binding.tvSaleMode.setText("Sale #" + saleId);
|
binding.tvSaleMode.setText("Sale #" + saleId);
|
||||||
binding.tvSaleDetailId.setText("ID: " + saleId);
|
binding.tvSaleDetailId.setText("ID: " + saleId);
|
||||||
|
|
||||||
if (!a.getBoolean("isRefund", false)) {
|
boolean isRefund = a.getBoolean("isRefund", false);
|
||||||
binding.btnRefundSale.setVisibility(View.VISIBLE);
|
if (isRefund) {
|
||||||
|
binding.btnRefundSale.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewOnly) {
|
if (viewOnly) {
|
||||||
@@ -96,6 +101,16 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
binding.spinnerPaymentMethod);
|
binding.spinnerPaymentMethod);
|
||||||
binding.llAddItemRow.setVisibility(View.GONE);
|
binding.llAddItemRow.setVisibility(View.GONE);
|
||||||
binding.llExtraInfo.setVisibility(View.VISIBLE);
|
binding.llExtraInfo.setVisibility(View.VISIBLE);
|
||||||
|
binding.llCustomerInfo.setVisibility(View.VISIBLE);
|
||||||
|
binding.tvCustomerLabel.setVisibility(View.GONE);
|
||||||
|
binding.spinnerSaleCustomer.setVisibility(View.GONE);
|
||||||
|
binding.spinnerSaleStore.setVisibility(View.GONE);
|
||||||
|
binding.spinnerPaymentMethod.setVisibility(View.GONE);
|
||||||
|
binding.tvSaleStore.setVisibility(View.VISIBLE);
|
||||||
|
binding.tvSalePaymentMethod.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Show refund button only if it's not already a refund
|
||||||
|
binding.btnRefundSale.setVisibility(isRefund ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSaleDetails();
|
loadSaleDetails();
|
||||||
@@ -157,6 +172,13 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
|
|
||||||
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
||||||
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
||||||
|
binding.tvSaleStore.setText(sale.getStoreName() != null ? sale.getStoreName() : "—");
|
||||||
|
binding.tvSaleCustomer.setText(sale.getCustomerName() != null ? sale.getCustomerName() : "No Customer");
|
||||||
|
binding.tvSalePaymentMethod.setText(sale.getPaymentMethod() != null ? sale.getPaymentMethod() : "—");
|
||||||
|
|
||||||
|
if (sale.getIsRefund() != null && sale.getIsRefund()) {
|
||||||
|
binding.btnRefundSale.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());
|
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||||||
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.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
|
||||||
import com.example.petstoremobile.databinding.FragmentServiceDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentServiceDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import com.example.petstoremobile.utils.ActivityLogger;
|
||||||
@@ -21,6 +21,7 @@ import com.example.petstoremobile.utils.DateTimeUtils;
|
|||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ServiceDetailViewModel;
|
import com.example.petstoremobile.viewmodels.ServiceDetailViewModel;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
@@ -51,6 +52,7 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
observeViewModel();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||||
@@ -58,8 +60,12 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
binding.btnDeleteService.setOnClickListener(v -> deleteService());
|
binding.btnDeleteService.setOnClickListener(v -> deleteService());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||||
|
}
|
||||||
|
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,34 +132,48 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
if (getArguments() != null && getArguments().containsKey("serviceId")) {
|
if (getArguments() != null && getArguments().containsKey("serviceId")) {
|
||||||
long serviceId = getArguments().getLong("serviceId");
|
viewModel.setServiceId(getArguments().getLong("serviceId"));
|
||||||
viewModel.setServiceId(serviceId);
|
|
||||||
binding.tvMode.setText("Edit Service");
|
|
||||||
binding.tvServiceId.setText(DateTimeUtils.formatId(serviceId));
|
|
||||||
binding.btnDeleteService.setVisibility(View.VISIBLE);
|
|
||||||
loadServiceData();
|
loadServiceData();
|
||||||
} else {
|
return;
|
||||||
viewModel.setServiceId(-1);
|
|
||||||
binding.tvMode.setText("Add Service");
|
|
||||||
binding.tvServiceId.setVisibility(View.GONE);
|
|
||||||
binding.btnDeleteService.setVisibility(View.GONE);
|
|
||||||
binding.btnSaveService.setText("Add");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.setServiceId(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadServiceData() {
|
private void loadServiceData() {
|
||||||
viewModel.loadService().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadService().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.ERROR) {
|
||||||
ServiceDTO s = resource.data;
|
|
||||||
binding.etServiceName.setText(s.getServiceName());
|
|
||||||
binding.etServiceDesc.setText(s.getServiceDesc());
|
|
||||||
binding.etServiceDuration.setText(String.valueOf(s.getServiceDuration()));
|
|
||||||
binding.etServicePrice.setText(String.valueOf(s.getServicePrice()));
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Failed to load service: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Failed to load service: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyViewState(ServiceDetailViewModel.ViewState state) {
|
||||||
|
binding.tvMode.setText(state.modeTitle);
|
||||||
|
binding.tvServiceId.setText(DateTimeUtils.formatId(viewModel.getServiceId()));
|
||||||
|
binding.tvServiceId.setVisibility(state.isServiceIdVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnDeleteService.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnSaveService.setText(state.saveButtonText);
|
||||||
|
|
||||||
|
UIUtils.setViewsEnabled(state.isFieldsEnabled,
|
||||||
|
binding.etServiceName,
|
||||||
|
binding.etServiceDesc,
|
||||||
|
binding.etServiceDuration,
|
||||||
|
binding.etServicePrice);
|
||||||
|
|
||||||
|
updateIfDifferent(binding.etServiceName, state.serviceName);
|
||||||
|
updateIfDifferent(binding.etServiceDesc, state.serviceDesc);
|
||||||
|
updateIfDifferent(binding.etServiceDuration, state.serviceDuration);
|
||||||
|
updateIfDifferent(binding.etServicePrice, state.servicePrice);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIfDifferent(EditText field, String value) {
|
||||||
|
String current = field.getText() != null ? field.getText().toString() : "";
|
||||||
|
String next = value != null ? value : "";
|
||||||
|
if (!current.equals(next)) {
|
||||||
|
field.setText(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.databinding.FragmentStaffDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentStaffDetailBinding;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
@@ -16,6 +17,9 @@ import com.example.petstoremobile.utils.SpinnerUtils;
|
|||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.StaffDetailViewModel;
|
import com.example.petstoremobile.viewmodels.StaffDetailViewModel;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
@@ -25,8 +29,11 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
private StaffDetailViewModel viewModel;
|
private StaffDetailViewModel viewModel;
|
||||||
|
|
||||||
private final String[] ROLES = {"STAFF", "ADMIN"};
|
private final String[] ROLES = {"STAFF", "ADMIN"};
|
||||||
|
private final String[] STAFF_ROLES = {"STORE_MANAGER", "SALES_ASSOCIATE", "GROOMER", "VETERINARIAN"};
|
||||||
private final String[] STATUSES = {"Active", "Inactive"};
|
private final String[] STATUSES = {"Active", "Inactive"};
|
||||||
|
|
||||||
|
private long preselectedStoreId = -1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -34,6 +41,8 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
viewModel = new ViewModelProvider(this).get(StaffDetailViewModel.class);
|
viewModel = new ViewModelProvider(this).get(StaffDetailViewModel.class);
|
||||||
|
|
||||||
setupSpinners();
|
setupSpinners();
|
||||||
|
observeViewModel();
|
||||||
|
loadStores();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
binding.btnStaffBack.setOnClickListener(v -> navigateBack());
|
binding.btnStaffBack.setOnClickListener(v -> navigateBack());
|
||||||
@@ -45,11 +54,32 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
||||||
|
}
|
||||||
|
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffRole, ROLES);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffRole, ROLES);
|
||||||
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffType, STAFF_ROLES);
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffStatus, STATUSES);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffStatus, STATUSES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadStores() {
|
||||||
|
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
viewModel.setStoreList(resource.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshStoreSpinner() {
|
||||||
|
List<DropdownDTO> list = viewModel.getStoreList().getValue();
|
||||||
|
if (list == null) return;
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaffStore, list,
|
||||||
|
DropdownDTO::getLabel, "-- Select Store --",
|
||||||
|
preselectedStoreId, DropdownDTO::getId);
|
||||||
|
}
|
||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.getBoolean("isEditing", false)) {
|
if (a != null && a.getBoolean("isEditing", false)) {
|
||||||
@@ -59,16 +89,9 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
binding.tvStaffMode.setText("Edit Staff Account");
|
binding.tvStaffMode.setText("Edit Staff Account");
|
||||||
binding.tvStaffId.setText("ID: " + employeeId);
|
binding.tvStaffId.setText("ID: " + employeeId);
|
||||||
binding.tvStaffId.setVisibility(View.VISIBLE);
|
binding.tvStaffId.setVisibility(View.VISIBLE);
|
||||||
binding.etStaffUsername.setText(a.getString("username", ""));
|
|
||||||
binding.etStaffFirstName.setText(a.getString("firstName", ""));
|
|
||||||
binding.etStaffLastName.setText(a.getString("lastName", ""));
|
|
||||||
binding.etStaffEmail.setText(a.getString("email", ""));
|
|
||||||
binding.etStaffPhone.setText(a.getString("phone", ""));
|
|
||||||
binding.btnDeleteStaff.setVisibility(View.VISIBLE);
|
binding.btnDeleteStaff.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerStaffRole, a.getString("role", "STAFF"));
|
loadEmployeeData(employeeId);
|
||||||
binding.spinnerStaffStatus.setSelection(a.getBoolean("active", true) ? 0 : 1);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
viewModel.setEmployeeId(-1, false);
|
viewModel.setEmployeeId(-1, false);
|
||||||
binding.tvStaffMode.setText("Add Staff Account");
|
binding.tvStaffMode.setText("Add Staff Account");
|
||||||
@@ -77,6 +100,29 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadEmployeeData(long id) {
|
||||||
|
viewModel.loadEmployee(id).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
EmployeeDTO e = resource.data;
|
||||||
|
binding.etStaffUsername.setText(e.getUsername());
|
||||||
|
binding.etStaffFirstName.setText(e.getFirstName());
|
||||||
|
binding.etStaffLastName.setText(e.getLastName());
|
||||||
|
binding.etStaffEmail.setText(e.getEmail());
|
||||||
|
binding.etStaffPhone.setText(e.getPhone());
|
||||||
|
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerStaffRole, e.getRole());
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerStaffType, e.getStaffRole());
|
||||||
|
binding.spinnerStaffStatus.setSelection(Boolean.TRUE.equals(e.getActive()) ? 0 : 1);
|
||||||
|
|
||||||
|
preselectedStoreId = e.getPrimaryStoreId() != null ? e.getPrimaryStoreId() : -1;
|
||||||
|
refreshStoreSpinner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null && binding.progressBar != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
@@ -100,6 +146,7 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
if (!InputValidator.isNotEmpty(binding.etStaffLastName, "Last Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etStaffLastName, "Last Name")) return;
|
||||||
if (!InputValidator.isValidEmail(binding.etStaffEmail)) return;
|
if (!InputValidator.isValidEmail(binding.etStaffEmail)) return;
|
||||||
if (!InputValidator.isValidPhone(binding.etStaffPhone)) return;
|
if (!InputValidator.isValidPhone(binding.etStaffPhone)) return;
|
||||||
|
if (!InputValidator.isSpinnerSelected(binding.spinnerStaffStore, "Primary Store")) return;
|
||||||
|
|
||||||
String username = binding.etStaffUsername.getText().toString().trim();
|
String username = binding.etStaffUsername.getText().toString().trim();
|
||||||
String password = binding.etStaffPassword.getText().toString().trim();
|
String password = binding.etStaffPassword.getText().toString().trim();
|
||||||
@@ -108,8 +155,12 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
String email = binding.etStaffEmail.getText().toString().trim();
|
String email = binding.etStaffEmail.getText().toString().trim();
|
||||||
String phone = binding.etStaffPhone.getText().toString().trim();
|
String phone = binding.etStaffPhone.getText().toString().trim();
|
||||||
String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()];
|
String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()];
|
||||||
|
String staffRole = STAFF_ROLES[binding.spinnerStaffType.getSelectedItemPosition()];
|
||||||
boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0;
|
boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0;
|
||||||
|
|
||||||
|
List<DropdownDTO> stores = viewModel.getStoreList().getValue();
|
||||||
|
Long storeId = stores.get(binding.spinnerStaffStore.getSelectedItemPosition() - 1).getId();
|
||||||
|
|
||||||
EmployeeDTO dto = new EmployeeDTO(
|
EmployeeDTO dto = new EmployeeDTO(
|
||||||
username,
|
username,
|
||||||
password.isEmpty() ? null : password,
|
password.isEmpty() ? null : password,
|
||||||
@@ -118,7 +169,9 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
email,
|
email,
|
||||||
phone,
|
phone,
|
||||||
role,
|
role,
|
||||||
active
|
staffRole,
|
||||||
|
active,
|
||||||
|
storeId
|
||||||
);
|
);
|
||||||
|
|
||||||
viewModel.saveEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.saveEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||||||
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.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.example.petstoremobile.databinding.FragmentSupplierDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentSupplierDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import com.example.petstoremobile.utils.ActivityLogger;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
@@ -51,6 +53,7 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
UIUtils.formatPhoneInput(binding.etSupPhone);
|
UIUtils.formatPhoneInput(binding.etSupPhone);
|
||||||
|
observeViewModel();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||||
@@ -58,8 +61,12 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
binding.btnDeleteSupplier.setOnClickListener(v -> deleteSupplier());
|
binding.btnDeleteSupplier.setOnClickListener(v -> deleteSupplier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||||
|
}
|
||||||
|
|
||||||
private void setLoading(boolean loading) {
|
private void setLoading(boolean loading) {
|
||||||
if (binding != null && binding.progressBar != null) {
|
if (binding != null) {
|
||||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,36 +136,50 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
if (getArguments() != null && getArguments().containsKey("supId")) {
|
if (getArguments() != null && getArguments().containsKey("supId")) {
|
||||||
long supId = getArguments().getLong("supId");
|
viewModel.setSupId(getArguments().getLong("supId"));
|
||||||
viewModel.setSupId(supId);
|
|
||||||
binding.tvMode.setText("Edit Supplier");
|
|
||||||
binding.tvSupId.setText("ID: " + supId);
|
|
||||||
binding.tvSupId.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnDeleteSupplier.setVisibility(View.VISIBLE);
|
|
||||||
loadSupplierData();
|
loadSupplierData();
|
||||||
} else {
|
return;
|
||||||
viewModel.setSupId(-1);
|
|
||||||
binding.tvMode.setText("Add Supplier");
|
|
||||||
binding.tvSupId.setVisibility(View.GONE);
|
|
||||||
binding.btnDeleteSupplier.setVisibility(View.GONE);
|
|
||||||
binding.btnSaveSupplier.setText("Add");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.setSupId(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSupplierData() {
|
private void loadSupplierData() {
|
||||||
viewModel.loadSupplier().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadSupplier().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
setLoading(resource.status == Resource.Status.LOADING);
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.ERROR) {
|
||||||
SupplierDTO s = resource.data;
|
|
||||||
binding.etSupCompany.setText(s.getSupCompany());
|
|
||||||
binding.etSupContactFirstName.setText(s.getSupContactFirstName());
|
|
||||||
binding.etSupContactLastName.setText(s.getSupContactLastName());
|
|
||||||
binding.etSupEmail.setText(s.getSupEmail());
|
|
||||||
binding.etSupPhone.setText(s.getSupPhone());
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Failed to load supplier: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Failed to load supplier: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyViewState(SupplierDetailViewModel.ViewState state) {
|
||||||
|
binding.tvMode.setText(state.modeTitle);
|
||||||
|
binding.tvSupId.setText(DateTimeUtils.formatId(viewModel.getSupId()));
|
||||||
|
binding.tvSupId.setVisibility(state.isSupIdVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnDeleteSupplier.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnSaveSupplier.setText(state.saveButtonText);
|
||||||
|
|
||||||
|
UIUtils.setViewsEnabled(state.isFieldsEnabled,
|
||||||
|
binding.etSupCompany,
|
||||||
|
binding.etSupContactFirstName,
|
||||||
|
binding.etSupContactLastName,
|
||||||
|
binding.etSupEmail,
|
||||||
|
binding.etSupPhone);
|
||||||
|
|
||||||
|
updateIfDifferent(binding.etSupCompany, state.supCompany);
|
||||||
|
updateIfDifferent(binding.etSupContactFirstName, state.supFirstName);
|
||||||
|
updateIfDifferent(binding.etSupContactLastName, state.supLastName);
|
||||||
|
updateIfDifferent(binding.etSupEmail, state.supEmail);
|
||||||
|
updateIfDifferent(binding.etSupPhone, state.supPhone);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIfDifferent(EditText field, String value) {
|
||||||
|
String current = field.getText() != null ? field.getText().toString() : "";
|
||||||
|
String next = value != null ? value : "";
|
||||||
|
if (!current.equals(next)) {
|
||||||
|
field.setText(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.example.petstoremobile.dtos.CustomerDTO;
|
|||||||
import com.example.petstoremobile.dtos.MessageDTO;
|
import com.example.petstoremobile.dtos.MessageDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||||
|
import com.example.petstoremobile.dtos.UpdateConversationStatusRequest;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -73,6 +74,13 @@ public class ChatRepository extends BaseRepository {
|
|||||||
return executeCall(messageApi.downloadAttachment(messageId));
|
return executeCall(messageApi.downloadAttachment(messageId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the status of a conversation (e.g., OPEN to CLOSED).
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<ConversationDTO>> updateConversationStatus(Long conversationId, UpdateConversationStatusRequest request) {
|
||||||
|
return executeCall(chatApi.updateConversationStatus(conversationId, request));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a paginated list of customers.
|
* Fetches a paginated list of customers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.example.petstoremobile.repositories;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.api.CouponApi;
|
||||||
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class CouponRepository extends BaseRepository {
|
||||||
|
private final CouponApi couponApi;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CouponRepository(CouponApi couponApi) {
|
||||||
|
super("CouponRepository");
|
||||||
|
this.couponApi = couponApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PageResponse<CouponDTO>>> getAllCoupons(int page, int size, Boolean active, String discountType, String sort) {
|
||||||
|
return executeCall(couponApi.getAllCoupons(page, size, active, discountType, sort));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<CouponDTO>> getCouponById(Long id) {
|
||||||
|
return executeCall(couponApi.getCouponById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<CouponDTO>> createCoupon(CouponDTO coupon) {
|
||||||
|
return executeCall(couponApi.createCoupon(coupon));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<CouponDTO>> updateCoupon(Long id, CouponDTO coupon) {
|
||||||
|
return executeCall(couponApi.updateCoupon(id, coupon));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteCoupon(Long id) {
|
||||||
|
return executeCall(couponApi.deleteCoupon(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeleteCoupons(List<Long> ids) {
|
||||||
|
return executeCall(couponApi.bulkDeleteCoupons(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,20 @@ public class PetRepository extends BaseRepository {
|
|||||||
return executeCall(petApi.getAdoptionPets());
|
return executeCall(petApi.getAdoptionPets());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all pets from the dropdowns API.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getPetDropdowns() {
|
||||||
|
return executeCall(petApi.getPetDropdowns());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves available pets for a specific store.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<PageResponse<PetDTO>>> getAvailablePetsByStore(Long storeId) {
|
||||||
|
return executeCall(petApi.getAllPets(0, 200, null, "available", null, storeId, null, "petName"));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a specific pet by its ID from the API.
|
* Retrieves a specific pet by its ID from the API.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ package com.example.petstoremobile.repositories;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
|
||||||
import com.example.petstoremobile.api.ProductApi;
|
import com.example.petstoremobile.api.ProductApi;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@@ -70,4 +73,18 @@ public class ProductRepository extends BaseRepository {
|
|||||||
public LiveData<Resource<Void>> deleteProductImage(Long id) {
|
public LiveData<Resource<Void>> deleteProductImage(Long id) {
|
||||||
return executeCall(productApi.deleteProductImage(id));
|
return executeCall(productApi.deleteProductImage(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of product dropdowns from the API.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getProductDropdowns() {
|
||||||
|
return executeCall(productApi.getProductDropdowns());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of category dropdowns from the API.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getCategoryDropdowns() {
|
||||||
|
return executeCall(productApi.getCategoryDropdowns());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ public class SaleRepository extends BaseRepository {
|
|||||||
this.saleApi = saleApi;
|
this.saleApi = saleApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, String sortBy) {
|
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, Boolean isRefund, String sortBy) {
|
||||||
return executeCall(saleApi.getAllSales(page, size, query, paymentMethod, storeId, sortBy));
|
return executeCall(saleApi.getAllSales(page, size, query, paymentMethod, storeId, isRefund, sortBy));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
|
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
|
||||||
|
|||||||
@@ -63,6 +63,31 @@ public class DateTimeUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given date is strictly before today (today and future return false).
|
||||||
|
* format: date = "YYYY-MM-DD"
|
||||||
|
*/
|
||||||
|
public static boolean isDateBeforeToday(String date) {
|
||||||
|
if (date == null || date.isEmpty()) return false;
|
||||||
|
try {
|
||||||
|
String[] parts = date.split("-");
|
||||||
|
Calendar today = Calendar.getInstance();
|
||||||
|
today.set(Calendar.HOUR_OF_DAY, 0);
|
||||||
|
today.set(Calendar.MINUTE, 0);
|
||||||
|
today.set(Calendar.SECOND, 0);
|
||||||
|
today.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
Calendar selected = Calendar.getInstance();
|
||||||
|
selected.set(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]) - 1,
|
||||||
|
Integer.parseInt(parts[2]), 0, 0, 0);
|
||||||
|
selected.set(Calendar.MILLISECOND, 0);
|
||||||
|
return selected.before(today);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error parsing date: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a given date and time are in the past.
|
* Checks if a given date and time are in the past.
|
||||||
* format: date = "YYYY-MM-DD", time = "HH:MM"
|
* format: date = "YYYY-MM-DD", time = "HH:MM"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ public class ErrorUtils {
|
|||||||
try {
|
try {
|
||||||
if (response.errorBody() != null) {
|
if (response.errorBody() != null) {
|
||||||
String errorJson = response.errorBody().string();
|
String errorJson = response.errorBody().string();
|
||||||
|
Log.e(TAG, "Full Error JSON: " + errorJson);
|
||||||
ErrorResponse errorResponse = gson.fromJson(errorJson, ErrorResponse.class);
|
ErrorResponse errorResponse = gson.fromJson(errorJson, ErrorResponse.class);
|
||||||
if (errorResponse != null && errorResponse.getMessage() != null) {
|
if (errorResponse != null && errorResponse.getMessage() != null) {
|
||||||
return errorResponse.getMessage();
|
return errorResponse.getMessage();
|
||||||
|
|||||||
@@ -11,13 +11,31 @@ import java.io.InputStream;
|
|||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
public static File getFileFromUri(Context context, Uri uri) {
|
public static File getFileFromUri(Context context, Uri uri) {
|
||||||
try {
|
try {
|
||||||
|
if ("content".equals(uri.getScheme())) {
|
||||||
|
String authority = uri.getAuthority();
|
||||||
|
if (authority != null && authority.equals(context.getPackageName() + ".fileprovider")) {
|
||||||
|
String lastSegment = uri.getLastPathSegment();
|
||||||
|
if (lastSegment != null) {
|
||||||
|
String fileName = lastSegment.contains("/")
|
||||||
|
? lastSegment.substring(lastSegment.lastIndexOf('/') + 1)
|
||||||
|
: lastSegment;
|
||||||
|
File cachedFile = new File(context.getCacheDir(), fileName);
|
||||||
|
if (cachedFile.exists() && cachedFile.length() > 0) {
|
||||||
|
return cachedFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String fileName = getFileName(context, uri);
|
String fileName = getFileName(context, uri);
|
||||||
if (fileName == null) fileName = "upload_" + System.currentTimeMillis();
|
if (fileName == null) fileName = "upload_" + System.currentTimeMillis();
|
||||||
|
|
||||||
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
|
if (inputStream == null) return null;
|
||||||
|
|
||||||
File tempFile = new File(context.getCacheDir(), fileName);
|
File tempFile = new File(context.getCacheDir(), fileName);
|
||||||
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[4096];
|
||||||
int length;
|
int length;
|
||||||
while ((length = inputStream.read(buffer)) > 0) {
|
while ((length = inputStream.read(buffer)) > 0) {
|
||||||
outputStream.write(buffer, 0, length);
|
outputStream.write(buffer, 0, length);
|
||||||
|
|||||||
@@ -129,9 +129,16 @@ public class ImagePickerHelper {
|
|||||||
* Prepares a temporary file and launches the camera app.
|
* Prepares a temporary file and launches the camera app.
|
||||||
*/
|
*/
|
||||||
private void launchCamera() {
|
private void launchCamera() {
|
||||||
|
try {
|
||||||
File photoFile = new File(fragment.requireContext().getCacheDir(), tempFileName);
|
File photoFile = new File(fragment.requireContext().getCacheDir(), tempFileName);
|
||||||
photoUri = FileProvider.getUriForFile(fragment.requireContext(), fragment.requireContext().getPackageName() + ".fileprovider", photoFile);
|
if (!photoFile.exists()) photoFile.createNewFile();
|
||||||
|
photoUri = FileProvider.getUriForFile(fragment.requireContext(),
|
||||||
|
fragment.requireContext().getPackageName() + ".fileprovider", photoFile);
|
||||||
cameraLauncher.launch(photoUri);
|
cameraLauncher.launch(photoUri);
|
||||||
|
} catch (Exception e) {
|
||||||
|
android.widget.Toast.makeText(fragment.requireContext(),
|
||||||
|
"Could not prepare camera", android.widget.Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,6 +35,24 @@ public class InputValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Checks if the value is a positive decimal number greater than 0
|
||||||
|
public static boolean isGreaterThanZero(EditText field, String fieldName) {
|
||||||
|
String value = field.getText().toString().trim();
|
||||||
|
try {
|
||||||
|
double num = Double.parseDouble(value);
|
||||||
|
if (num <= 0) {
|
||||||
|
field.setError(fieldName + " must be greater than 0");
|
||||||
|
field.requestFocus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
field.setError(fieldName + " must be a number");
|
||||||
|
field.requestFocus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Checks if the value is a positive decimal number
|
// Checks if the value is a positive decimal number
|
||||||
public static boolean isPositiveDecimal(EditText field, String fieldName) {
|
public static boolean isPositiveDecimal(EditText field, String fieldName) {
|
||||||
String value = field.getText().toString().trim();
|
String value = field.getText().toString().trim();
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ public class UIUtils {
|
|||||||
},
|
},
|
||||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
||||||
c.get(Calendar.DAY_OF_MONTH));
|
c.get(Calendar.DAY_OF_MONTH));
|
||||||
|
d.setButton(DatePickerDialog.BUTTON_NEUTRAL, "Clear", (dialog, which) -> {
|
||||||
|
editText.setText("");
|
||||||
|
if (onDateSet != null) onDateSet.run();
|
||||||
|
});
|
||||||
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
||||||
d.show();
|
d.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ import com.example.petstoremobile.repositories.AdoptionRepository;
|
|||||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||||
import com.example.petstoremobile.repositories.PetRepository;
|
import com.example.petstoremobile.repositories.PetRepository;
|
||||||
import com.example.petstoremobile.repositories.StoreRepository;
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -27,12 +30,12 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
private final StoreRepository storeRepository;
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
private long adoptionId = -1;
|
private long adoptionId = -1;
|
||||||
private boolean isEditing = false;
|
|
||||||
|
|
||||||
private final MutableLiveData<List<DropdownDTO>> petList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> petList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<DropdownDTO>> employeeList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> employeeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AdoptionDetailViewModel(AdoptionRepository adoptionRepository, PetRepository petRepository,
|
public AdoptionDetailViewModel(AdoptionRepository adoptionRepository, PetRepository petRepository,
|
||||||
@@ -45,7 +48,7 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setAdoptionId(long id) {
|
public void setAdoptionId(long id) {
|
||||||
this.adoptionId = id;
|
this.adoptionId = id;
|
||||||
this.isEditing = id != -1;
|
initMode(id != -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getAdoptionId() {
|
public long getAdoptionId() {
|
||||||
@@ -53,50 +56,294 @@ public class AdoptionDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEditing() {
|
public boolean isEditing() {
|
||||||
return isEditing;
|
ViewState current = viewState.getValue();
|
||||||
|
return current != null && current.isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<ViewState> getViewState() {
|
||||||
|
return viewState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initMode(boolean isEditing) {
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.isEditing = isEditing;
|
||||||
|
state.modeTitle = isEditing ? "Edit Adoption" : "Add Adoption";
|
||||||
|
state.saveButtonText = isEditing ? "Save" : "Add";
|
||||||
|
state.isAdoptionIdVisible = isEditing;
|
||||||
|
state.isDeleteVisible = isEditing;
|
||||||
|
state.isFeeEnabled = false; // fee is always read-only
|
||||||
|
if (!isEditing) {
|
||||||
|
state.isCustomerEnabled = true;
|
||||||
|
state.isStoreEnabled = true;
|
||||||
|
state.isPetEnabled = false; // until customer selected
|
||||||
|
state.isEmployeeEnabled = false; // until store selected
|
||||||
|
state.isDateEnabled = true;
|
||||||
|
state.isStatusEnabled = true;
|
||||||
|
state.availableStatuses = new String[]{"Pending"};
|
||||||
|
state.selectedStatus = "Pending";
|
||||||
|
} else {
|
||||||
|
// edit: date-based logic applied after load
|
||||||
|
state.isCustomerEnabled = false;
|
||||||
|
state.isStoreEnabled = false;
|
||||||
|
state.isPetEnabled = false;
|
||||||
|
state.isEmployeeEnabled = false;
|
||||||
|
state.isDateEnabled = false;
|
||||||
|
state.isStatusEnabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadInitialFormData(boolean isEditing) {
|
||||||
|
// Pets are loaded dynamically based on store selection; no pre-load needed.
|
||||||
|
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
||||||
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
|
customerList.setValue(r.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
storeRepository.getStoreDropdowns().observeForever(r -> {
|
||||||
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
|
storeList.setValue(r.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCustomerSelected(int position) {
|
||||||
|
List<DropdownDTO> list = customerList.getValue();
|
||||||
|
Long customerId = (position > 0 && list != null && position <= list.size())
|
||||||
|
? list.get(position - 1).getId() : null;
|
||||||
|
updateViewState(state -> state.selectedCustomerId = customerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStoreSelected(int position) {
|
||||||
|
List<DropdownDTO> list = storeList.getValue();
|
||||||
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
|
Long storeId = list.get(position - 1).getId();
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.selectedStoreId = storeId;
|
||||||
|
if (!state.isCancelled && !state.isEditing) {
|
||||||
|
state.isEmployeeEnabled = true;
|
||||||
|
state.isPetEnabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loadEmployeesForStore(storeId);
|
||||||
|
if (!isEditing()) loadAvailablePetsByStore(storeId);
|
||||||
|
} else {
|
||||||
|
employeeList.setValue(new ArrayList<>());
|
||||||
|
petList.setValue(new ArrayList<>());
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.selectedStoreId = null;
|
||||||
|
state.selectedEmployeeId = null;
|
||||||
|
state.selectedPetId = null;
|
||||||
|
state.isEmployeeEnabled = false;
|
||||||
|
state.isPetEnabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPetSelected(int position) {
|
||||||
|
List<DropdownDTO> list = petList.getValue();
|
||||||
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
|
Long petId = list.get(position - 1).getId();
|
||||||
|
updateViewState(s -> s.selectedPetId = petId);
|
||||||
|
loadPetPrice(petId);
|
||||||
|
} else {
|
||||||
|
updateViewState(s -> {
|
||||||
|
s.selectedPetId = null;
|
||||||
|
s.adoptionFee = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAvailablePetsByStore(Long storeId) {
|
||||||
|
petRepository.getAvailablePetsByStore(storeId).observeForever(r -> {
|
||||||
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
|
List<DropdownDTO> dropdowns = new ArrayList<>();
|
||||||
|
for (com.example.petstoremobile.dtos.PetDTO pet : r.data.getContent()) {
|
||||||
|
dropdowns.add(new DropdownDTO(pet.getPetId(), pet.getPetName()));
|
||||||
|
}
|
||||||
|
petList.setValue(dropdowns);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadPetPrice(Long petId) {
|
||||||
|
petRepository.getPetById(petId).observeForever(r -> {
|
||||||
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
|
com.example.petstoremobile.dtos.PetDTO pet = r.data;
|
||||||
|
// In edit mode, add the pet to the list so the spinner can display its name
|
||||||
|
if (isEditing()) {
|
||||||
|
List<DropdownDTO> single = new ArrayList<>();
|
||||||
|
single.add(new DropdownDTO(pet.getPetId(), pet.getPetName()));
|
||||||
|
petList.setValue(single);
|
||||||
|
}
|
||||||
|
if (pet.getPetPrice() != null) {
|
||||||
|
String price = String.format(Locale.getDefault(), "%.2f", pet.getPetPrice());
|
||||||
|
updateViewState(s -> s.adoptionFee = price);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadEmployeesForStore(Long storeId) {
|
||||||
|
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
||||||
|
if (r != null && r.status == Resource.Status.SUCCESS && r.data != null) {
|
||||||
|
employeeList.setValue(r.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the date or status changes in the UI. Applies date-based field enabling.
|
||||||
|
*/
|
||||||
|
public void onDateChanged(String date, String currentStatus) {
|
||||||
|
updateViewState(s -> {
|
||||||
|
if (s.isCancelled) return;
|
||||||
|
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date);
|
||||||
|
List<String> available = Arrays.asList(s.availableStatuses);
|
||||||
|
if (!currentStatus.isEmpty() && available.contains(currentStatus)) {
|
||||||
|
s.selectedStatus = currentStatus;
|
||||||
|
} else if (!available.contains(s.selectedStatus) && s.availableStatuses.length > 0) {
|
||||||
|
s.selectedStatus = s.availableStatuses[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!s.isEditing) return; // add mode: field enabling handled separately
|
||||||
|
|
||||||
|
boolean isPast = DateTimeUtils.isDateBeforeToday(date);
|
||||||
|
if (isPast) {
|
||||||
|
setAllEditableFieldsEnabled(s, false);
|
||||||
|
s.isStatusEnabled = true;
|
||||||
|
} else if (!date.isEmpty()) {
|
||||||
|
setAllEditableFieldsEnabled(s, false);
|
||||||
|
s.isEmployeeEnabled = true;
|
||||||
|
s.isDateEnabled = true;
|
||||||
|
s.isStatusEnabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] calculateAvailableStatuses(boolean isEditing, String date) {
|
||||||
|
if (!isEditing) return new String[]{"Pending"};
|
||||||
|
if (date == null || date.isEmpty()) return new String[]{};
|
||||||
|
if (DateTimeUtils.isDateBeforeToday(date)) return new String[]{"Completed", "Missed"};
|
||||||
|
return new String[]{"Pending", "Cancelled"};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Disables all editable fields (fee is always disabled separately). */
|
||||||
|
private void setAllEditableFieldsEnabled(ViewState s, boolean enabled) {
|
||||||
|
s.isCustomerEnabled = enabled;
|
||||||
|
s.isStoreEnabled = enabled;
|
||||||
|
s.isPetEnabled = enabled;
|
||||||
|
s.isEmployeeEnabled = enabled;
|
||||||
|
s.isDateEnabled = enabled;
|
||||||
|
// fee never editable
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<AdoptionDTO>> loadAdoption() {
|
public LiveData<Resource<AdoptionDTO>> loadAdoption() {
|
||||||
return adoptionRepository.getAdoptionById(adoptionId);
|
MutableLiveData<Resource<AdoptionDTO>> result = new MutableLiveData<>();
|
||||||
}
|
adoptionRepository.getAdoptionById(adoptionId).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
AdoptionDTO a = resource.data;
|
||||||
|
String formattedStatus = DateTimeUtils.formatStatusFromBackend(
|
||||||
|
a.getAdoptionStatus() != null ? a.getAdoptionStatus() : "");
|
||||||
|
String adoptionDate = a.getAdoptionDate() != null ? a.getAdoptionDate() : "";
|
||||||
|
|
||||||
public LiveData<Resource<List<DropdownDTO>>> loadPets() {
|
updateViewState(state -> {
|
||||||
return petRepository.getAdoptionPets();
|
state.selectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
||||||
}
|
state.selectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
|
||||||
|
state.selectedStoreId = a.getSourceStoreId() != null ? a.getSourceStoreId() : -1;
|
||||||
|
state.selectedEmployeeId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
|
||||||
|
state.adoptionDate = adoptionDate;
|
||||||
|
state.adoptionFee = a.getAdoptionFee() != null ? a.getAdoptionFee().toPlainString() : "";
|
||||||
|
state.selectedStatus = formattedStatus;
|
||||||
|
state.adoptionStatus = formattedStatus;
|
||||||
|
|
||||||
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
|
if ("Cancelled".equalsIgnoreCase(formattedStatus)) {
|
||||||
return customerRepository.getCustomerDropdowns();
|
state.isCancelled = true;
|
||||||
|
state.isCustomerEnabled = false;
|
||||||
|
state.isStoreEnabled = false;
|
||||||
|
state.isPetEnabled = false;
|
||||||
|
state.isEmployeeEnabled = false;
|
||||||
|
state.isStatusEnabled = false;
|
||||||
|
state.isDateEnabled = false;
|
||||||
|
state.isFeeEnabled = false;
|
||||||
|
state.isSaveVisible = false;
|
||||||
|
state.availableStatuses = new String[]{"Cancelled"};
|
||||||
|
} else {
|
||||||
|
state.availableStatuses = calculateAvailableStatuses(true, adoptionDate);
|
||||||
|
boolean isPast = DateTimeUtils.isDateBeforeToday(adoptionDate);
|
||||||
|
if (isPast) {
|
||||||
|
setAllEditableFieldsEnabled(state, false);
|
||||||
|
state.isStatusEnabled = true;
|
||||||
|
} else if (!adoptionDate.isEmpty()) {
|
||||||
|
setAllEditableFieldsEnabled(state, false);
|
||||||
|
state.isEmployeeEnabled = true;
|
||||||
|
state.isDateEnabled = true;
|
||||||
|
state.isStatusEnabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
|
||||||
return storeRepository.getStoreDropdowns();
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
public LiveData<Resource<List<DropdownDTO>>> loadEmployees(Long storeId) {
|
if (a.getSourceStoreId() != null) loadEmployeesForStore(a.getSourceStoreId());
|
||||||
return storeRepository.getStoreEmployees(storeId);
|
if (a.getPetId() != null) loadPetPrice(a.getPetId());
|
||||||
|
}
|
||||||
|
result.setValue(resource);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<AdoptionDTO>> saveAdoption(AdoptionDTO dto) {
|
public LiveData<Resource<AdoptionDTO>> saveAdoption(AdoptionDTO dto) {
|
||||||
if (isEditing) {
|
if (isEditing()) {
|
||||||
return adoptionRepository.updateAdoption(adoptionId, dto);
|
return adoptionRepository.updateAdoption(adoptionId, dto);
|
||||||
} else {
|
|
||||||
return adoptionRepository.createAdoption(dto);
|
|
||||||
}
|
}
|
||||||
|
return adoptionRepository.createAdoption(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<Void>> deleteAdoption() {
|
public LiveData<Resource<Void>> deleteAdoption() {
|
||||||
return adoptionRepository.deleteAdoption(adoptionId);
|
return adoptionRepository.deleteAdoption(adoptionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPetList(List<DropdownDTO> list) { petList.setValue(list); }
|
|
||||||
public LiveData<List<DropdownDTO>> getPetList() { return petList; }
|
public LiveData<List<DropdownDTO>> getPetList() { return petList; }
|
||||||
|
|
||||||
public void setCustomerList(List<DropdownDTO> list) { customerList.setValue(list); }
|
|
||||||
public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
|
public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
|
||||||
|
|
||||||
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
|
||||||
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||||
|
public LiveData<List<DropdownDTO>> getEmployeeList() { return employeeList; }
|
||||||
|
|
||||||
public void setEmployeeList(List<DropdownDTO> list) { employeeList.setValue(list); }
|
public void setEmployeeList(List<DropdownDTO> list) { employeeList.setValue(list); }
|
||||||
public LiveData<List<DropdownDTO>> getEmployeeList() { return employeeList; }
|
|
||||||
|
private void updateViewState(Action<ViewState> action) {
|
||||||
|
ViewState current = viewState.getValue();
|
||||||
|
if (current != null) {
|
||||||
|
action.run(current);
|
||||||
|
viewState.setValue(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Action<T> {
|
||||||
|
void run(T target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewState {
|
||||||
|
public boolean isEditing = false;
|
||||||
|
public boolean isAdoptionIdVisible = false;
|
||||||
|
public boolean isDeleteVisible = false;
|
||||||
|
public boolean isCancelled = false;
|
||||||
|
public boolean isPetEnabled = false;
|
||||||
|
public boolean isEmployeeEnabled = false;
|
||||||
|
public boolean isCustomerEnabled = true;
|
||||||
|
public boolean isStoreEnabled = true;
|
||||||
|
public boolean isStatusEnabled = true;
|
||||||
|
public boolean isDateEnabled = true;
|
||||||
|
public boolean isFeeEnabled = false; // always read-only
|
||||||
|
public boolean isSaveVisible = true;
|
||||||
|
public String[] availableStatuses = new String[]{};
|
||||||
|
public String selectedStatus = "";
|
||||||
|
public String modeTitle = "Add Adoption";
|
||||||
|
public String saveButtonText = "Add";
|
||||||
|
public Long selectedPetId = null;
|
||||||
|
public Long selectedCustomerId = null;
|
||||||
|
public Long selectedStoreId = null;
|
||||||
|
public Long selectedEmployeeId = null;
|
||||||
|
public String adoptionDate = "";
|
||||||
|
public String adoptionFee = "";
|
||||||
|
public String adoptionStatus = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
@@ -30,6 +31,10 @@ public class AnalyticsViewModel extends ViewModel {
|
|||||||
private final MutableLiveData<AnalyticsData> analyticsData = new MutableLiveData<>();
|
private final MutableLiveData<AnalyticsData> analyticsData = new MutableLiveData<>();
|
||||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
|
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<List<String>> availablePaymentMethods = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
|
private List<SaleDTO> cachedSales = new ArrayList<>();
|
||||||
|
private FilterState currentFilter = new FilterState();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AnalyticsViewModel(SaleRepository saleRepository) {
|
public AnalyticsViewModel(SaleRepository saleRepository) {
|
||||||
@@ -39,14 +44,17 @@ public class AnalyticsViewModel extends ViewModel {
|
|||||||
public LiveData<AnalyticsData> getAnalyticsData() { return analyticsData; }
|
public LiveData<AnalyticsData> getAnalyticsData() { return analyticsData; }
|
||||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
public LiveData<String> getErrorMessage() { return errorMessage; }
|
public LiveData<String> getErrorMessage() { return errorMessage; }
|
||||||
|
public LiveData<List<String>> getAvailablePaymentMethods() { return availablePaymentMethods; }
|
||||||
|
|
||||||
public void loadAnalytics() {
|
public void loadAnalytics() {
|
||||||
isLoading.setValue(true);
|
isLoading.setValue(true);
|
||||||
errorMessage.setValue(null);
|
errorMessage.setValue(null);
|
||||||
saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc").observeForever(resource -> {
|
saleRepository.getAllSales(0, 2000, null, null, null, null, "saleDate,desc").observeForever(resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
computeAnalytics(resource.data.getContent());
|
cachedSales = resource.data.getContent();
|
||||||
|
derivePaymentMethods();
|
||||||
|
applyCurrentFilter();
|
||||||
isLoading.setValue(false);
|
isLoading.setValue(false);
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
errorMessage.setValue(resource.message);
|
errorMessage.setValue(resource.message);
|
||||||
@@ -56,11 +64,53 @@ public class AnalyticsViewModel extends ViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void computeAnalytics(List<SaleDTO> sales) {
|
public void applyFilter(FilterState filter) {
|
||||||
|
currentFilter = filter;
|
||||||
|
applyCurrentFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetFilter() {
|
||||||
|
currentFilter = new FilterState();
|
||||||
|
applyCurrentFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyCurrentFilter() {
|
||||||
|
List<SaleDTO> filtered = filterSales(cachedSales, currentFilter);
|
||||||
|
computeAnalytics(filtered, currentFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void derivePaymentMethods() {
|
||||||
|
java.util.Set<String> methods = new java.util.TreeSet<>();
|
||||||
|
for (SaleDTO s : cachedSales) {
|
||||||
|
if (s.getPaymentMethod() != null && !s.getPaymentMethod().isEmpty()) {
|
||||||
|
methods.add(s.getPaymentMethod());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
result.add("All");
|
||||||
|
result.addAll(methods);
|
||||||
|
availablePaymentMethods.setValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SaleDTO> filterSales(List<SaleDTO> sales, FilterState filter) {
|
||||||
|
List<SaleDTO> result = new ArrayList<>();
|
||||||
|
for (SaleDTO s : sales) {
|
||||||
|
String date = s.getSaleDate() != null && s.getSaleDate().length() >= 10
|
||||||
|
? s.getSaleDate().substring(0, 10) : "";
|
||||||
|
if (!filter.startDate.isEmpty() && !date.isEmpty() && date.compareTo(filter.startDate) < 0) continue;
|
||||||
|
if (!filter.endDate.isEmpty() && !date.isEmpty() && date.compareTo(filter.endDate) > 0) continue;
|
||||||
|
if (!filter.paymentMethod.equals("All") && !filter.paymentMethod.isEmpty()) {
|
||||||
|
if (!filter.paymentMethod.equalsIgnoreCase(s.getPaymentMethod())) continue;
|
||||||
|
}
|
||||||
|
result.add(s);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeAnalytics(List<SaleDTO> sales, FilterState filter) {
|
||||||
List<SaleDTO> regularSales = new ArrayList<>();
|
List<SaleDTO> regularSales = new ArrayList<>();
|
||||||
for (SaleDTO s : sales) {
|
for (SaleDTO s : sales) {
|
||||||
if (!Boolean.TRUE.equals(s.getIsRefund()))
|
if (!Boolean.TRUE.equals(s.getIsRefund())) regularSales.add(s);
|
||||||
regularSales.add(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalyticsData data = new AnalyticsData();
|
AnalyticsData data = new AnalyticsData();
|
||||||
@@ -83,72 +133,127 @@ public class AnalyticsViewModel extends ViewModel {
|
|||||||
: BigDecimal.ZERO;
|
: BigDecimal.ZERO;
|
||||||
data.totalItems = totalItems;
|
data.totalItems = totalItems;
|
||||||
|
|
||||||
// Product Maps
|
|
||||||
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
||||||
Map<String, Integer> quantityByProduct = new LinkedHashMap<>();
|
Map<String, Integer> quantityByProduct = new LinkedHashMap<>();
|
||||||
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
||||||
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
||||||
|
|
||||||
for (SaleDTO s : regularSales) {
|
for (SaleDTO s : regularSales) {
|
||||||
// Payments
|
|
||||||
String method = s.getPaymentMethod() != null ? s.getPaymentMethod() : "Unknown";
|
String method = s.getPaymentMethod() != null ? s.getPaymentMethod() : "Unknown";
|
||||||
paymentCount.merge(method, 1, Integer::sum);
|
paymentCount.merge(method, 1, Integer::sum);
|
||||||
|
|
||||||
// Employee
|
|
||||||
String emp = s.getEmployeeName() != null ? s.getEmployeeName() : "Unknown";
|
String emp = s.getEmployeeName() != null ? s.getEmployeeName() : "Unknown";
|
||||||
if (s.getTotalAmount() != null) employeeRevenue.merge(emp, s.getTotalAmount(), BigDecimal::add);
|
if (s.getTotalAmount() != null) employeeRevenue.merge(emp, s.getTotalAmount(), BigDecimal::add);
|
||||||
|
|
||||||
// Items
|
|
||||||
if (s.getItems() != null) {
|
if (s.getItems() != null) {
|
||||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||||
String name = item.getProductName() != null ? item.getProductName() : "Unknown";
|
String name = item.getProductName() != null ? item.getProductName() : "Unknown";
|
||||||
int qty = item.getQuantity() != null ? Math.abs(item.getQuantity()) : 0;
|
int qty = item.getQuantity() != null ? Math.abs(item.getQuantity()) : 0;
|
||||||
BigDecimal lineTotal = item.getUnitPrice() != null
|
BigDecimal lineTotal = item.getUnitPrice() != null
|
||||||
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty))
|
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty)) : BigDecimal.ZERO;
|
||||||
: BigDecimal.ZERO;
|
|
||||||
revenueByProduct.merge(name, lineTotal, BigDecimal::add);
|
revenueByProduct.merge(name, lineTotal, BigDecimal::add);
|
||||||
quantityByProduct.merge(name, qty, Integer::sum);
|
quantityByProduct.merge(name, qty, Integer::sum);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort Top Revenue
|
int topN = filter.topN > 0 ? filter.topN : 5;
|
||||||
|
|
||||||
data.topRevenueProducts = new ArrayList<>(revenueByProduct.entrySet());
|
data.topRevenueProducts = new ArrayList<>(revenueByProduct.entrySet());
|
||||||
data.topRevenueProducts.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
data.topRevenueProducts.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||||
if (data.topRevenueProducts.size() > 5) data.topRevenueProducts = data.topRevenueProducts.subList(0, 5);
|
if (data.topRevenueProducts.size() > topN) data.topRevenueProducts = data.topRevenueProducts.subList(0, topN);
|
||||||
|
|
||||||
// Sort Top Quantity
|
|
||||||
data.topQuantityProducts = new ArrayList<>(quantityByProduct.entrySet());
|
data.topQuantityProducts = new ArrayList<>(quantityByProduct.entrySet());
|
||||||
data.topQuantityProducts.sort((a, b) -> b.getValue() - a.getValue());
|
data.topQuantityProducts.sort((a, b) -> b.getValue() - a.getValue());
|
||||||
if (data.topQuantityProducts.size() > 5) data.topQuantityProducts = data.topQuantityProducts.subList(0, 5);
|
if (data.topQuantityProducts.size() > topN) data.topQuantityProducts = data.topQuantityProducts.subList(0, topN);
|
||||||
|
|
||||||
// Payment Stats
|
|
||||||
data.paymentMethodStats = new ArrayList<>(paymentCount.entrySet());
|
data.paymentMethodStats = new ArrayList<>(paymentCount.entrySet());
|
||||||
|
data.paymentMethodStats.sort((a, b) -> b.getValue() - a.getValue());
|
||||||
|
|
||||||
// Employee Performance
|
|
||||||
data.employeePerformance = new ArrayList<>(employeeRevenue.entrySet());
|
data.employeePerformance = new ArrayList<>(employeeRevenue.entrySet());
|
||||||
data.employeePerformance.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
data.employeePerformance.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||||
|
|
||||||
// Daily Revenue (last 7 days)
|
// Daily revenue display to filter date range, max 60 days
|
||||||
Map<String, BigDecimal> dailyMap = new TreeMap<>();
|
String rangeStart = filter.startDate;
|
||||||
for (int i = 6; i >= 0; i--) {
|
String rangeEnd = filter.endDate;
|
||||||
Calendar day = Calendar.getInstance();
|
if (rangeStart.isEmpty() && rangeEnd.isEmpty()) {
|
||||||
day.add(Calendar.DAY_OF_YEAR, -i);
|
rangeEnd = todayString(0);
|
||||||
String key = String.format("%04d-%02d-%02d",
|
rangeStart = todayString(-6);
|
||||||
day.get(Calendar.YEAR), day.get(Calendar.MONTH) + 1, day.get(Calendar.DAY_OF_MONTH));
|
} else if (rangeStart.isEmpty()) {
|
||||||
dailyMap.put(key, BigDecimal.ZERO);
|
rangeStart = shiftDate(rangeEnd, -6);
|
||||||
|
} else if (rangeEnd.isEmpty()) {
|
||||||
|
rangeEnd = todayString(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<String> dateRange = buildDateRange(rangeStart, rangeEnd, 60);
|
||||||
|
Map<String, BigDecimal> dailyMap = new TreeMap<>();
|
||||||
|
for (String d : dateRange) dailyMap.put(d, BigDecimal.ZERO);
|
||||||
for (SaleDTO s : regularSales) {
|
for (SaleDTO s : regularSales) {
|
||||||
if (s.getSaleDate() != null && s.getTotalAmount() != null) {
|
if (s.getSaleDate() != null && s.getTotalAmount() != null) {
|
||||||
String date = s.getSaleDate().length() >= 10 ? s.getSaleDate().substring(0, 10) : s.getSaleDate();
|
String d = s.getSaleDate().length() >= 10 ? s.getSaleDate().substring(0, 10) : s.getSaleDate();
|
||||||
if (dailyMap.containsKey(date)) dailyMap.merge(date, s.getTotalAmount(), BigDecimal::add);
|
if (dailyMap.containsKey(d)) dailyMap.merge(d, s.getTotalAmount(), BigDecimal::add);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.dailyRevenue = new ArrayList<>(dailyMap.entrySet());
|
data.dailyRevenue = new ArrayList<>(dailyMap.entrySet());
|
||||||
|
data.dailyRevenueTitle = buildDailyTitle(filter, rangeStart, rangeEnd);
|
||||||
|
|
||||||
analyticsData.setValue(data);
|
analyticsData.setValue(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String todayString(int offsetDays) {
|
||||||
|
Calendar c = Calendar.getInstance();
|
||||||
|
c.add(Calendar.DAY_OF_YEAR, offsetDays);
|
||||||
|
return String.format(Locale.US, "%04d-%02d-%02d",
|
||||||
|
c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String shiftDate(String date, int offsetDays) {
|
||||||
|
try {
|
||||||
|
String[] p = date.split("-");
|
||||||
|
Calendar c = Calendar.getInstance();
|
||||||
|
c.set(Integer.parseInt(p[0]), Integer.parseInt(p[1]) - 1, Integer.parseInt(p[2]), 0, 0, 0);
|
||||||
|
c.add(Calendar.DAY_OF_YEAR, offsetDays);
|
||||||
|
return String.format(Locale.US, "%04d-%02d-%02d",
|
||||||
|
c.get(Calendar.YEAR), c.get(Calendar.MONTH) + 1, c.get(Calendar.DAY_OF_MONTH));
|
||||||
|
} catch (Exception e) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> buildDateRange(String start, String end, int maxDays) {
|
||||||
|
List<String> dates = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
String[] sp = start.split("-");
|
||||||
|
String[] ep = end.split("-");
|
||||||
|
Calendar cur = Calendar.getInstance();
|
||||||
|
cur.set(Integer.parseInt(sp[0]), Integer.parseInt(sp[1]) - 1, Integer.parseInt(sp[2]), 0, 0, 0);
|
||||||
|
Calendar endCal = Calendar.getInstance();
|
||||||
|
endCal.set(Integer.parseInt(ep[0]), Integer.parseInt(ep[1]) - 1, Integer.parseInt(ep[2]), 0, 0, 0);
|
||||||
|
int count = 0;
|
||||||
|
while (!cur.after(endCal) && count < maxDays) {
|
||||||
|
dates.add(String.format(Locale.US, "%04d-%02d-%02d",
|
||||||
|
cur.get(Calendar.YEAR), cur.get(Calendar.MONTH) + 1, cur.get(Calendar.DAY_OF_MONTH)));
|
||||||
|
cur.add(Calendar.DAY_OF_YEAR, 1);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
return dates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildDailyTitle(FilterState filter, String rangeStart, String rangeEnd) {
|
||||||
|
if (filter.startDate.isEmpty() && filter.endDate.isEmpty()) return "Daily Revenue (Last 7 Days)";
|
||||||
|
String s = rangeStart.length() >= 10 ? rangeStart.substring(5) : rangeStart;
|
||||||
|
String e = rangeEnd.length() >= 10 ? rangeEnd.substring(5) : rangeEnd;
|
||||||
|
return "Daily Revenue (" + s + " – " + e + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FilterState {
|
||||||
|
public String startDate = "";
|
||||||
|
public String endDate = "";
|
||||||
|
public String paymentMethod = "All";
|
||||||
|
public int topN = 5;
|
||||||
|
}
|
||||||
|
|
||||||
public static class AnalyticsData {
|
public static class AnalyticsData {
|
||||||
public BigDecimal totalRevenue;
|
public BigDecimal totalRevenue;
|
||||||
public int totalTransactions;
|
public int totalTransactions;
|
||||||
@@ -159,5 +264,6 @@ public class AnalyticsViewModel extends ViewModel {
|
|||||||
public List<Map.Entry<String, Integer>> paymentMethodStats;
|
public List<Map.Entry<String, Integer>> paymentMethodStats;
|
||||||
public List<Map.Entry<String, BigDecimal>> employeePerformance;
|
public List<Map.Entry<String, BigDecimal>> employeePerformance;
|
||||||
public List<Map.Entry<String, BigDecimal>> dailyRevenue;
|
public List<Map.Entry<String, BigDecimal>> dailyRevenue;
|
||||||
|
public String dailyRevenueTitle = "Daily Revenue";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||||
|
|
||||||
private long appointmentId = -1;
|
private long appointmentId = -1;
|
||||||
|
private boolean isOriginallyCancel = false;
|
||||||
private Long currentCustomerId;
|
private Long currentCustomerId;
|
||||||
private Long currentStoreId;
|
private Long currentStoreId;
|
||||||
private Long currentPetId;
|
private Long currentPetId;
|
||||||
@@ -229,18 +230,21 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
repository.getAppointmentById(appointmentId).observeForever(resource -> {
|
repository.getAppointmentById(appointmentId).observeForever(resource -> {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
AppointmentDTO a = resource.data;
|
AppointmentDTO a = resource.data;
|
||||||
|
isOriginallyCancel = "CANCELLED".equalsIgnoreCase(a.getAppointmentStatus());
|
||||||
currentCustomerId = a.getCustomerId();
|
currentCustomerId = a.getCustomerId();
|
||||||
currentStoreId = a.getStoreId();
|
currentStoreId = a.getStoreId();
|
||||||
currentPetId = a.getPetId();
|
currentPetId = a.getPetId();
|
||||||
currentServiceId = a.getServiceId();
|
currentServiceId = a.getServiceId();
|
||||||
currentStaffId = a.getEmployeeId();
|
currentStaffId = a.getEmployeeId();
|
||||||
|
|
||||||
|
String formattedStatus = DateTimeUtils.formatStatusFromBackend(a.getAppointmentStatus());
|
||||||
updateViewState(s -> {
|
updateViewState(s -> {
|
||||||
s.selectedCustomerId = currentCustomerId;
|
s.selectedCustomerId = currentCustomerId;
|
||||||
s.selectedStoreId = currentStoreId;
|
s.selectedStoreId = currentStoreId;
|
||||||
s.selectedPetId = currentPetId;
|
s.selectedPetId = currentPetId;
|
||||||
s.selectedServiceId = currentServiceId;
|
s.selectedServiceId = currentServiceId;
|
||||||
s.selectedStaffId = currentStaffId;
|
s.selectedStaffId = currentStaffId;
|
||||||
|
s.selectedStatus = formattedStatus;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (currentCustomerId != null) loadPetsForCustomer(currentCustomerId);
|
if (currentCustomerId != null) loadPetsForCustomer(currentCustomerId);
|
||||||
@@ -278,10 +282,16 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
||||||
updateViewState(s -> {
|
updateViewState(s -> {
|
||||||
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
||||||
|
// Keep selectedStatus if still valid; prefer explicit currentStatus from UI if valid
|
||||||
|
java.util.List<String> available = java.util.Arrays.asList(s.availableStatuses);
|
||||||
|
if (!currentStatus.isEmpty() && available.contains(currentStatus)) {
|
||||||
|
s.selectedStatus = currentStatus;
|
||||||
|
} else if (!available.contains(s.selectedStatus) && s.availableStatuses.length > 0) {
|
||||||
|
s.selectedStatus = s.availableStatuses[0];
|
||||||
|
}
|
||||||
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
||||||
boolean isCancelled = "Cancelled".equalsIgnoreCase(currentStatus);
|
|
||||||
|
|
||||||
if (isCancelled) {
|
if (isOriginallyCancel) {
|
||||||
s.isPast = true;
|
s.isPast = true;
|
||||||
setAllFieldsEnabled(s, false);
|
setAllFieldsEnabled(s, false);
|
||||||
s.isStatusEnabled = false;
|
s.isStatusEnabled = false;
|
||||||
@@ -311,7 +321,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
private String[] calculateAvailableStatuses(boolean isEditing, String date, String currentTime, String currentStatus) {
|
private String[] calculateAvailableStatuses(boolean isEditing, String date, String currentTime, String currentStatus) {
|
||||||
if (!isEditing) return new String[]{"Booked"};
|
if (!isEditing) return new String[]{"Booked"};
|
||||||
if (date == null || date.isEmpty()) return new String[]{};
|
if (date == null || date.isEmpty()) return new String[]{};
|
||||||
if ("Cancelled".equalsIgnoreCase(currentStatus)) return new String[]{"Cancelled"};
|
if (isOriginallyCancel) return new String[]{"Cancelled"};
|
||||||
if (DateTimeUtils.isDateTimeInPast(date, currentTime)) return new String[]{"Completed", "Missed"};
|
if (DateTimeUtils.isDateTimeInPast(date, currentTime)) return new String[]{"Completed", "Missed"};
|
||||||
return new String[]{"Booked", "Cancelled"};
|
return new String[]{"Booked", "Cancelled"};
|
||||||
}
|
}
|
||||||
@@ -348,6 +358,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
s.isPetEnabled = false; // until customer selected
|
s.isPetEnabled = false; // until customer selected
|
||||||
s.isStaffEnabled = false; // until store selected
|
s.isStaffEnabled = false; // until store selected
|
||||||
s.availableStatuses = new String[]{"Booked"};
|
s.availableStatuses = new String[]{"Booked"};
|
||||||
|
s.selectedStatus = "Booked";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -391,6 +402,7 @@ public class AppointmentDetailViewModel extends ViewModel {
|
|||||||
public boolean isTimeEnabled = true;
|
public boolean isTimeEnabled = true;
|
||||||
public boolean isStatusEnabled = true;
|
public boolean isStatusEnabled = true;
|
||||||
public String[] availableStatuses = new String[]{};
|
public String[] availableStatuses = new String[]{};
|
||||||
|
public String selectedStatus = "";
|
||||||
|
|
||||||
// Selected IDs
|
// Selected IDs
|
||||||
public Long selectedCustomerId = null;
|
public Long selectedCustomerId = null;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.example.petstoremobile.dtos.CustomerDTO;
|
|||||||
import com.example.petstoremobile.dtos.MessageDTO;
|
import com.example.petstoremobile.dtos.MessageDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||||
|
import com.example.petstoremobile.dtos.UpdateConversationStatusRequest;
|
||||||
import com.example.petstoremobile.models.Chat;
|
import com.example.petstoremobile.models.Chat;
|
||||||
import com.example.petstoremobile.models.Message;
|
import com.example.petstoremobile.models.Message;
|
||||||
import com.example.petstoremobile.repositories.ChatRepository;
|
import com.example.petstoremobile.repositories.ChatRepository;
|
||||||
@@ -126,6 +127,10 @@ public class ChatListViewModel extends ViewModel {
|
|||||||
return chatRepository.downloadAttachment(messageId);
|
return chatRepository.downloadAttachment(messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<ConversationDTO>> closeConversation(Long conversationId) {
|
||||||
|
return chatRepository.updateConversationStatus(conversationId, new UpdateConversationStatusRequest("CLOSED"));
|
||||||
|
}
|
||||||
|
|
||||||
public void addMessageLocally(MessageDTO dto) {
|
public void addMessageLocally(MessageDTO dto) {
|
||||||
List<Message> current = new ArrayList<>(messageList.getValue());
|
List<Message> current = new ArrayList<>(messageList.getValue());
|
||||||
current.add(dtoToModel(dto));
|
current.add(dtoToModel(dto));
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
import com.example.petstoremobile.repositories.CouponRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class CouponDetailViewModel extends ViewModel {
|
||||||
|
private final CouponRepository repository;
|
||||||
|
private long couponId = -1;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CouponDetailViewModel(CouponRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<CouponDTO>> loadCoupon(long id) {
|
||||||
|
return repository.getCouponById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCouponId(long id, boolean isEditing) {
|
||||||
|
this.couponId = id;
|
||||||
|
this.isEditing = isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCouponId() {
|
||||||
|
return couponId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<CouponDTO>> saveCoupon(CouponDTO dto) {
|
||||||
|
if (isEditing && couponId > 0) {
|
||||||
|
return repository.updateCoupon(couponId, dto);
|
||||||
|
} else {
|
||||||
|
return repository.createCoupon(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteCoupon() {
|
||||||
|
return repository.deleteCoupon(couponId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.CouponDTO;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.repositories.CouponRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class CouponListViewModel extends ViewModel {
|
||||||
|
private final CouponRepository repository;
|
||||||
|
private final MutableLiveData<List<CouponDTO>> coupons = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public CouponListViewModel(CouponRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<CouponDTO>> getCoupons() { return coupons; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadCoupons(int page, int size, Boolean active, String discountType, String sort) {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
repository.getAllCoupons(page, size, active, discountType, sort).observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
coupons.setValue(resource.data.getContent());
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeleteCoupons(List<Long> ids) {
|
||||||
|
return repository.bulkDeleteCoupons(ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ public class InventoryDetailViewModel extends ViewModel {
|
|||||||
private boolean isEditing = false;
|
private boolean isEditing = false;
|
||||||
|
|
||||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public InventoryDetailViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository, ProductRepository productRepository) {
|
public InventoryDetailViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository, ProductRepository productRepository) {
|
||||||
@@ -55,8 +55,8 @@ public class InventoryDetailViewModel extends ViewModel {
|
|||||||
return storeRepository.getStoreDropdowns();
|
return storeRepository.getStoreDropdowns();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<ProductDTO>>> loadProducts() {
|
public LiveData<Resource<List<DropdownDTO>>> loadProducts() {
|
||||||
return productRepository.getAllProducts(null, null, 0, 500, "prodName");
|
return productRepository.getProductDropdowns();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<InventoryDTO>> saveInventory(InventoryDTO dto) {
|
public LiveData<Resource<InventoryDTO>> saveInventory(InventoryDTO dto) {
|
||||||
@@ -74,6 +74,6 @@ public class InventoryDetailViewModel extends ViewModel {
|
|||||||
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
||||||
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||||
|
|
||||||
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
|
public void setProductList(List<DropdownDTO> list) { productList.setValue(list); }
|
||||||
public LiveData<List<ProductDTO>> getProductList() { return productList; }
|
public LiveData<List<DropdownDTO>> getProductList() { return productList; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,17 +20,22 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
public class PetDetailViewModel extends ViewModel {
|
public class PetDetailViewModel extends ViewModel {
|
||||||
|
private static final String STATUS_AVAILABLE = "Available";
|
||||||
|
private static final String STATUS_ADOPTED = "Adopted";
|
||||||
|
private static final String STATUS_OWNED = "Owned";
|
||||||
|
|
||||||
private final PetRepository petRepository;
|
private final PetRepository petRepository;
|
||||||
private final CustomerRepository customerRepository;
|
private final CustomerRepository customerRepository;
|
||||||
private final StoreRepository storeRepository;
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
private final MutableLiveData<PetDTO> petState = new MutableLiveData<>();
|
|
||||||
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||||
|
|
||||||
private long petId = -1;
|
private long petId = -1;
|
||||||
private boolean isEditing = false;
|
private Long selectedCustomerId = null;
|
||||||
|
private Long selectedStoreId = null;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
|
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
|
||||||
@@ -39,9 +44,23 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
this.storeRepository = storeRepository;
|
this.storeRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadInitialFormData() {
|
||||||
|
customerRepository.getCustomerDropdowns().observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
customerList.setValue(resource.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
storeRepository.getStoreDropdowns().observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
storeList.setValue(resource.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void setPetId(long id) {
|
public void setPetId(long id) {
|
||||||
this.petId = id;
|
this.petId = id;
|
||||||
this.isEditing = id != -1;
|
initMode(id != -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getPetId() {
|
public long getPetId() {
|
||||||
@@ -49,46 +68,108 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEditing() {
|
public boolean isEditing() {
|
||||||
return isEditing;
|
ViewState current = viewState.getValue();
|
||||||
|
return current != null && current.isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<ViewState> getViewState() {
|
||||||
|
return viewState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCustomerSelected(int position) {
|
||||||
|
List<DropdownDTO> list = customerList.getValue();
|
||||||
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
|
selectedCustomerId = list.get(position - 1).getId();
|
||||||
|
} else {
|
||||||
|
selectedCustomerId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateViewState(state -> state.selectedCustomerId = selectedCustomerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStoreSelected(int position) {
|
||||||
|
List<DropdownDTO> list = storeList.getValue();
|
||||||
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
|
selectedStoreId = list.get(position - 1).getId();
|
||||||
|
} else {
|
||||||
|
selectedStoreId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateViewState(state -> state.selectedStoreId = selectedStoreId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onStatusSelected(String status) {
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.selectedStatus = normalizeStatus(status);
|
||||||
|
applyStatusRules(state, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initMode(boolean isEditing) {
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.isEditing = isEditing;
|
||||||
|
state.modeTitle = isEditing ? "Edit Pet" : "Add Pet";
|
||||||
|
state.saveButtonText = isEditing ? "Save" : "Add";
|
||||||
|
state.isPetIdVisible = isEditing;
|
||||||
|
state.isDeleteVisible = isEditing;
|
||||||
|
state.isSpeciesEnabled = !isEditing;
|
||||||
|
state.isBreedEnabled = !isEditing;
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
state.isCustomerEnabled = true;
|
||||||
|
state.isStoreEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isEditing) {
|
||||||
|
selectedCustomerId = null;
|
||||||
|
selectedStoreId = null;
|
||||||
|
state.selectedCustomerId = null;
|
||||||
|
state.selectedStoreId = null;
|
||||||
|
state.selectedStatus = STATUS_AVAILABLE;
|
||||||
|
state.isCustomerEnabled = false;
|
||||||
|
state.isStoreEnabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<PetDTO>> loadPet() {
|
public LiveData<Resource<PetDTO>> loadPet() {
|
||||||
return petRepository.getPetById(petId);
|
MutableLiveData<Resource<PetDTO>> result = new MutableLiveData<>();
|
||||||
|
petRepository.getPetById(petId).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
PetDTO pet = resource.data;
|
||||||
|
selectedCustomerId = pet.getCustomerId();
|
||||||
|
selectedStoreId = pet.getStoreId();
|
||||||
|
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.selectedCustomerId = selectedCustomerId;
|
||||||
|
state.selectedStoreId = selectedStoreId;
|
||||||
|
state.selectedStatus = normalizeStatus(pet.getPetStatus());
|
||||||
|
applyStatusRules(state, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
|
result.setValue(resource);
|
||||||
return customerRepository.getCustomerDropdowns();
|
});
|
||||||
}
|
return result;
|
||||||
|
|
||||||
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
|
||||||
return storeRepository.getStoreDropdowns();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<PetDTO>> savePet(PetDTO petDTO) {
|
public LiveData<Resource<PetDTO>> savePet(PetDTO petDTO) {
|
||||||
if (isEditing) {
|
if (isEditing()) {
|
||||||
petDTO.setPetId(petId);
|
petDTO.setPetId(petId);
|
||||||
return petRepository.updatePet(petId, petDTO);
|
return petRepository.updatePet(petId, petDTO);
|
||||||
} else {
|
|
||||||
return petRepository.createPet(petDTO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return petRepository.createPet(petDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<Void>> deletePet() {
|
public LiveData<Resource<Void>> deletePet() {
|
||||||
return petRepository.deletePet(petId);
|
return petRepository.deletePet(petId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCustomerList(List<DropdownDTO> list) {
|
|
||||||
customerList.setValue(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<List<DropdownDTO>> getCustomerList() {
|
public LiveData<List<DropdownDTO>> getCustomerList() {
|
||||||
return customerList;
|
return customerList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStoreList(List<DropdownDTO> list) {
|
|
||||||
storeList.setValue(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<List<DropdownDTO>> getStoreList() {
|
public LiveData<List<DropdownDTO>> getStoreList() {
|
||||||
return storeList;
|
return storeList;
|
||||||
}
|
}
|
||||||
@@ -100,4 +181,66 @@ public class PetDetailViewModel extends ViewModel {
|
|||||||
public void setLoading(boolean loading) {
|
public void setLoading(boolean loading) {
|
||||||
isLoading.setValue(loading);
|
isLoading.setValue(loading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyStatusRules(ViewState state, boolean clearInvalidSelections) {
|
||||||
|
if (STATUS_AVAILABLE.equalsIgnoreCase(state.selectedStatus)) {
|
||||||
|
state.isCustomerEnabled = false;
|
||||||
|
state.isStoreEnabled = true;
|
||||||
|
if (clearInvalidSelections) {
|
||||||
|
selectedCustomerId = null;
|
||||||
|
state.selectedCustomerId = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (STATUS_OWNED.equalsIgnoreCase(state.selectedStatus)) {
|
||||||
|
state.isCustomerEnabled = true;
|
||||||
|
state.isStoreEnabled = false;
|
||||||
|
if (clearInvalidSelections) {
|
||||||
|
selectedStoreId = null;
|
||||||
|
state.selectedStoreId = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.isCustomerEnabled = true;
|
||||||
|
state.isStoreEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeStatus(String status) {
|
||||||
|
if (status == null) return STATUS_AVAILABLE;
|
||||||
|
String normalized = status.trim();
|
||||||
|
if (STATUS_ADOPTED.equalsIgnoreCase(normalized)) return STATUS_ADOPTED;
|
||||||
|
if (STATUS_OWNED.equalsIgnoreCase(normalized)) return STATUS_OWNED;
|
||||||
|
return STATUS_AVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateViewState(Action<ViewState> action) {
|
||||||
|
ViewState current = viewState.getValue();
|
||||||
|
if (current != null) {
|
||||||
|
action.run(current);
|
||||||
|
viewState.setValue(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Action<T> {
|
||||||
|
void run(T target);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class ViewState {
|
||||||
|
public boolean isEditing = false;
|
||||||
|
public boolean isDeleteVisible = false;
|
||||||
|
public boolean isPetIdVisible = false;
|
||||||
|
public boolean isSpeciesEnabled = true;
|
||||||
|
public boolean isBreedEnabled = true;
|
||||||
|
public boolean isCustomerEnabled = false;
|
||||||
|
public boolean isStoreEnabled = true;
|
||||||
|
public String modeTitle = "Add Pet";
|
||||||
|
public String saveButtonText = "Add";
|
||||||
|
public String[] availableStatuses = new String[]{STATUS_AVAILABLE, STATUS_ADOPTED, STATUS_OWNED};
|
||||||
|
public String selectedStatus = STATUS_AVAILABLE;
|
||||||
|
public Long selectedCustomerId = null;
|
||||||
|
public Long selectedStoreId = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.lifecycle.MutableLiveData;
|
|||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||||
@@ -24,7 +25,7 @@ public class ProductDetailViewModel extends ViewModel {
|
|||||||
private final ProductRepository productRepository;
|
private final ProductRepository productRepository;
|
||||||
private final CategoryRepository categoryRepository;
|
private final CategoryRepository categoryRepository;
|
||||||
|
|
||||||
private final MutableLiveData<List<CategoryDTO>> categoryList = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> categoryList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private long prodId = -1;
|
private long prodId = -1;
|
||||||
private boolean isEditing = false;
|
private boolean isEditing = false;
|
||||||
|
|
||||||
@@ -47,8 +48,8 @@ public class ProductDetailViewModel extends ViewModel {
|
|||||||
return isEditing;
|
return isEditing;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<CategoryDTO>>> loadCategories() {
|
public LiveData<Resource<List<DropdownDTO>>> loadCategories() {
|
||||||
return categoryRepository.getAllCategories(0, 100);
|
return productRepository.getCategoryDropdowns();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<ProductDTO>> loadProduct() {
|
public LiveData<Resource<ProductDTO>> loadProduct() {
|
||||||
@@ -75,11 +76,12 @@ public class ProductDetailViewModel extends ViewModel {
|
|||||||
return productRepository.deleteProductImage(prodId);
|
return productRepository.deleteProductImage(prodId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCategoryList(List<CategoryDTO> list) {
|
public void setCategoryList(List<DropdownDTO> list) {
|
||||||
categoryList.setValue(list);
|
categoryList.setValue(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<CategoryDTO>> getCategoryList() {
|
public LiveData<List<DropdownDTO>> getCategoryList() {
|
||||||
return categoryList;
|
return categoryList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData;
|
|||||||
import androidx.lifecycle.MutableLiveData;
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||||
import com.example.petstoremobile.repositories.ProductRepository;
|
import com.example.petstoremobile.repositories.ProductRepository;
|
||||||
@@ -23,7 +23,7 @@ public class ProductListViewModel extends ViewModel {
|
|||||||
private final CategoryRepository categoryRepository;
|
private final CategoryRepository categoryRepository;
|
||||||
|
|
||||||
private final MutableLiveData<List<ProductDTO>> products = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<ProductDTO>> products = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<CategoryDTO>> categories = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<DropdownDTO>> categories = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -33,7 +33,7 @@ public class ProductListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<ProductDTO>> getProducts() { return products; }
|
public LiveData<List<ProductDTO>> getProducts() { return products; }
|
||||||
public LiveData<List<CategoryDTO>> getCategories() { return categories; }
|
public LiveData<List<DropdownDTO>> getCategories() { return categories; }
|
||||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
public void loadProducts(String query, Long categoryId) {
|
public void loadProducts(String query, Long categoryId) {
|
||||||
@@ -51,9 +51,9 @@ public class ProductListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void loadCategories() {
|
public void loadCategories() {
|
||||||
categoryRepository.getAllCategories(0, 100).observeForever(resource -> {
|
productRepository.getCategoryDropdowns().observeForever(resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
categories.setValue(resource.data.getContent());
|
categories.setValue(resource.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class RefundViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<SaleDTO>>> loadAllSales() {
|
public LiveData<Resource<PageResponse<SaleDTO>>> loadAllSales() {
|
||||||
return saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc");
|
return saleRepository.getAllSales(0, 1000, null, null, null, null, "saleDate,desc");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAllSales(List<SaleDTO> sales) {
|
public void setAllSales(List<SaleDTO> sales) {
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class SaleListViewModel extends ViewModel {
|
|||||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
public boolean isLastPage() { return isLastPage; }
|
public boolean isLastPage() { return isLastPage; }
|
||||||
|
|
||||||
public void loadSales(boolean reset, String query, String paymentMethod, Long storeId) {
|
public void loadSales(boolean reset, String query, String paymentMethod, Long storeId, Boolean isRefund) {
|
||||||
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||||
|
|
||||||
if (reset) {
|
if (reset) {
|
||||||
@@ -51,7 +51,7 @@ public class SaleListViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isLoading.setValue(true);
|
isLoading.setValue(true);
|
||||||
saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, "saleDate,desc").observeForever(resource -> {
|
saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, isRefund, "saleDate,desc").observeForever(resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());
|
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
@@ -14,8 +15,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
public class ServiceDetailViewModel extends ViewModel {
|
public class ServiceDetailViewModel extends ViewModel {
|
||||||
private final ServiceRepository repository;
|
private final ServiceRepository repository;
|
||||||
|
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||||
|
|
||||||
private long serviceId = -1;
|
private long serviceId = -1;
|
||||||
private boolean isEditing = false;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ServiceDetailViewModel(ServiceRepository repository) {
|
public ServiceDetailViewModel(ServiceRepository repository) {
|
||||||
@@ -24,7 +26,7 @@ public class ServiceDetailViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setServiceId(long id) {
|
public void setServiceId(long id) {
|
||||||
this.serviceId = id;
|
this.serviceId = id;
|
||||||
this.isEditing = id != -1;
|
initMode(id != -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getServiceId() {
|
public long getServiceId() {
|
||||||
@@ -32,23 +34,88 @@ public class ServiceDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEditing() {
|
public boolean isEditing() {
|
||||||
return isEditing;
|
ViewState current = viewState.getValue();
|
||||||
|
return current != null && current.isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<ViewState> getViewState() {
|
||||||
|
return viewState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initMode(boolean isEditing) {
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.isEditing = isEditing;
|
||||||
|
state.modeTitle = isEditing ? "Edit Service" : "Add Service";
|
||||||
|
state.saveButtonText = isEditing ? "Save" : "Add";
|
||||||
|
state.isServiceIdVisible = isEditing;
|
||||||
|
state.isDeleteVisible = isEditing;
|
||||||
|
state.isFieldsEnabled = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<ServiceDTO>> loadService() {
|
public LiveData<Resource<ServiceDTO>> loadService() {
|
||||||
return repository.getServiceById(serviceId);
|
MutableLiveData<Resource<ServiceDTO>> result = new MutableLiveData<>();
|
||||||
|
repository.getServiceById(serviceId).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
ServiceDTO service = resource.data;
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.serviceName = safeText(service.getServiceName());
|
||||||
|
state.serviceDesc = safeText(service.getServiceDesc());
|
||||||
|
state.serviceDuration = service.getServiceDuration() != null ? String.valueOf(service.getServiceDuration()) : "";
|
||||||
|
state.servicePrice = service.getServicePrice() != null ? String.valueOf(service.getServicePrice()) : "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result.setValue(resource);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<ServiceDTO>> saveService(ServiceDTO dto) {
|
public LiveData<Resource<ServiceDTO>> saveService(ServiceDTO dto) {
|
||||||
if (isEditing) {
|
updateViewState(state -> {
|
||||||
|
state.serviceName = safeText(dto.getServiceName());
|
||||||
|
state.serviceDesc = safeText(dto.getServiceDesc());
|
||||||
|
state.serviceDuration = dto.getServiceDuration() != null ? String.valueOf(dto.getServiceDuration()) : "";
|
||||||
|
state.servicePrice = dto.getServicePrice() != null ? String.valueOf(dto.getServicePrice()) : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isEditing()) {
|
||||||
dto.setServiceId(serviceId);
|
dto.setServiceId(serviceId);
|
||||||
return repository.updateService(serviceId, dto);
|
return repository.updateService(serviceId, dto);
|
||||||
} else {
|
|
||||||
return repository.createService(dto);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return repository.createService(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<Void>> deleteService() {
|
public LiveData<Resource<Void>> deleteService() {
|
||||||
return repository.deleteService(serviceId);
|
return repository.deleteService(serviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String safeText(String value) {
|
||||||
|
return value == null ? "" : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateViewState(Action<ViewState> action) {
|
||||||
|
ViewState current = viewState.getValue();
|
||||||
|
if (current != null) {
|
||||||
|
action.run(current);
|
||||||
|
viewState.setValue(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Action<T> {
|
||||||
|
void run(T target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewState {
|
||||||
|
public boolean isEditing = false;
|
||||||
|
public boolean isDeleteVisible = false;
|
||||||
|
public boolean isServiceIdVisible = false;
|
||||||
|
public boolean isFieldsEnabled = true;
|
||||||
|
public String modeTitle = "Add Service";
|
||||||
|
public String saveButtonText = "Add";
|
||||||
|
public String serviceName = "";
|
||||||
|
public String serviceDesc = "";
|
||||||
|
public String serviceDuration = "";
|
||||||
|
public String servicePrice = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
import com.example.petstoremobile.repositories.EmployeeRepository;
|
import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
@@ -14,12 +20,31 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
public class StaffDetailViewModel extends ViewModel {
|
public class StaffDetailViewModel extends ViewModel {
|
||||||
private final EmployeeRepository repository;
|
private final EmployeeRepository repository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
private long employeeId = -1;
|
private long employeeId = -1;
|
||||||
private boolean isEditing = false;
|
private boolean isEditing = false;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public StaffDetailViewModel(EmployeeRepository repository) {
|
public StaffDetailViewModel(EmployeeRepository repository, StoreRepository storeRepository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||||
|
return storeRepository.getStoreDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<DropdownDTO>> getStoreList() {
|
||||||
|
return storeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoreList(List<DropdownDTO> list) {
|
||||||
|
storeList.setValue(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<EmployeeDTO>> loadEmployee(long id) {
|
||||||
|
return repository.getEmployeeById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEmployeeId(long id, boolean isEditing) {
|
public void setEmployeeId(long id, boolean isEditing) {
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import androidx.lifecycle.MutableLiveData;
|
|||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.repositories.EmployeeRepository;
|
import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -18,18 +20,24 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
public class StaffListViewModel extends ViewModel {
|
public class StaffListViewModel extends ViewModel {
|
||||||
private final EmployeeRepository repository;
|
private final EmployeeRepository repository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
private final MutableLiveData<List<EmployeeDTO>> employees = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<EmployeeDTO>> employees = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<List<EmployeeDTO>> filteredEmployees = new MutableLiveData<>(new ArrayList<>());
|
private final MutableLiveData<List<EmployeeDTO>> filteredEmployees = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
private String lastQuery = "";
|
private String lastQuery = "";
|
||||||
|
private Long lastStoreId = null;
|
||||||
|
private String lastStatus = "All Statuses";
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public StaffListViewModel(EmployeeRepository repository) {
|
public StaffListViewModel(EmployeeRepository repository, StoreRepository storeRepository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<List<EmployeeDTO>> getFilteredEmployees() { return filteredEmployees; }
|
public LiveData<List<EmployeeDTO>> getFilteredEmployees() { return filteredEmployees; }
|
||||||
|
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
public void loadStaff() {
|
public void loadStaff() {
|
||||||
@@ -38,7 +46,7 @@ public class StaffListViewModel extends ViewModel {
|
|||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
employees.setValue(resource.data.getContent());
|
employees.setValue(resource.data.getContent());
|
||||||
filter(lastQuery);
|
filter(lastQuery, lastStoreId, lastStatus);
|
||||||
isLoading.setValue(false);
|
isLoading.setValue(false);
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
isLoading.setValue(false);
|
isLoading.setValue(false);
|
||||||
@@ -47,25 +55,45 @@ public class StaffListViewModel extends ViewModel {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void filter(String query) {
|
public void loadStores() {
|
||||||
|
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
stores.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filter(String query, Long storeId, String status) {
|
||||||
this.lastQuery = query;
|
this.lastQuery = query;
|
||||||
|
this.lastStoreId = storeId;
|
||||||
|
this.lastStatus = status;
|
||||||
|
|
||||||
List<EmployeeDTO> all = employees.getValue();
|
List<EmployeeDTO> all = employees.getValue();
|
||||||
if (all == null) return;
|
if (all == null) return;
|
||||||
|
|
||||||
if (query.isEmpty()) {
|
|
||||||
filteredEmployees.setValue(new ArrayList<>(all));
|
|
||||||
} else {
|
|
||||||
List<EmployeeDTO> filtered = new ArrayList<>();
|
List<EmployeeDTO> filtered = new ArrayList<>();
|
||||||
String lower = query.toLowerCase();
|
String lowerQuery = query.toLowerCase();
|
||||||
|
|
||||||
for (EmployeeDTO e : all) {
|
for (EmployeeDTO e : all) {
|
||||||
if ((e.getFullName() != null && e.getFullName().toLowerCase().contains(lower))
|
// Search Query Filter
|
||||||
|| (e.getUsername() != null && e.getUsername().toLowerCase().contains(lower))
|
boolean matchesQuery = query.isEmpty() ||
|
||||||
|| (e.getEmail() != null && e.getEmail().toLowerCase().contains(lower))
|
(e.getFullName() != null && e.getFullName().toLowerCase().contains(lowerQuery)) ||
|
||||||
|| (e.getPhone() != null && e.getPhone().toLowerCase().contains(lower))) {
|
(e.getUsername() != null && e.getUsername().toLowerCase().contains(lowerQuery)) ||
|
||||||
|
(e.getEmail() != null && e.getEmail().toLowerCase().contains(lowerQuery)) ||
|
||||||
|
(e.getPhone() != null && e.getPhone().toLowerCase().contains(lowerQuery));
|
||||||
|
|
||||||
|
// Store Filter
|
||||||
|
boolean matchesStore = storeId == null || (e.getPrimaryStoreId() != null && e.getPrimaryStoreId().equals(storeId));
|
||||||
|
|
||||||
|
// Status Filter
|
||||||
|
boolean matchesStatus = status.equals("All Statuses") ||
|
||||||
|
(status.equals("Active") && Boolean.TRUE.equals(e.getActive())) ||
|
||||||
|
(status.equals("Inactive") && Boolean.FALSE.equals(e.getActive()));
|
||||||
|
|
||||||
|
if (matchesQuery && matchesStore && matchesStatus) {
|
||||||
filtered.add(e);
|
filtered.add(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filteredEmployees.setValue(filtered);
|
filteredEmployees.setValue(filtered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
import androidx.lifecycle.ViewModel;
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
@@ -14,8 +15,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel;
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
public class SupplierDetailViewModel extends ViewModel {
|
public class SupplierDetailViewModel extends ViewModel {
|
||||||
private final SupplierRepository repository;
|
private final SupplierRepository repository;
|
||||||
|
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||||
|
|
||||||
private long supId = -1;
|
private long supId = -1;
|
||||||
private boolean isEditing = false;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SupplierDetailViewModel(SupplierRepository repository) {
|
public SupplierDetailViewModel(SupplierRepository repository) {
|
||||||
@@ -24,7 +26,7 @@ public class SupplierDetailViewModel extends ViewModel {
|
|||||||
|
|
||||||
public void setSupId(long id) {
|
public void setSupId(long id) {
|
||||||
this.supId = id;
|
this.supId = id;
|
||||||
this.isEditing = id != -1;
|
initMode(id != -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getSupId() {
|
public long getSupId() {
|
||||||
@@ -32,23 +34,82 @@ public class SupplierDetailViewModel extends ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEditing() {
|
public boolean isEditing() {
|
||||||
return isEditing;
|
ViewState current = viewState.getValue();
|
||||||
|
return current != null && current.isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<ViewState> getViewState() {
|
||||||
|
return viewState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initMode(boolean isEditing) {
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.isEditing = isEditing;
|
||||||
|
state.modeTitle = isEditing ? "Edit Supplier" : "Add Supplier";
|
||||||
|
state.saveButtonText = isEditing ? "Save" : "Add";
|
||||||
|
state.isSupIdVisible = isEditing;
|
||||||
|
state.isDeleteVisible = isEditing;
|
||||||
|
state.isFieldsEnabled = true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<SupplierDTO>> loadSupplier() {
|
public LiveData<Resource<SupplierDTO>> loadSupplier() {
|
||||||
return repository.getSupplierById(supId);
|
MutableLiveData<Resource<SupplierDTO>> result = new MutableLiveData<>();
|
||||||
|
repository.getSupplierById(supId).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
SupplierDTO s = resource.data;
|
||||||
|
updateViewState(state -> {
|
||||||
|
state.supCompany = safeText(s.getSupCompany());
|
||||||
|
state.supFirstName = safeText(s.getSupContactFirstName());
|
||||||
|
state.supLastName = safeText(s.getSupContactLastName());
|
||||||
|
state.supEmail = safeText(s.getSupEmail());
|
||||||
|
state.supPhone = safeText(s.getSupPhone());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
result.setValue(resource);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<SupplierDTO>> saveSupplier(SupplierDTO dto) {
|
public LiveData<Resource<SupplierDTO>> saveSupplier(SupplierDTO dto) {
|
||||||
if (isEditing) {
|
if (isEditing()) {
|
||||||
dto.setSupId(supId);
|
dto.setSupId(supId);
|
||||||
return repository.updateSupplier(supId, dto);
|
return repository.updateSupplier(supId, dto);
|
||||||
} else {
|
|
||||||
return repository.createSupplier(dto);
|
|
||||||
}
|
}
|
||||||
|
return repository.createSupplier(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LiveData<Resource<Void>> deleteSupplier() {
|
public LiveData<Resource<Void>> deleteSupplier() {
|
||||||
return repository.deleteSupplier(supId);
|
return repository.deleteSupplier(supId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String safeText(String value) {
|
||||||
|
return value == null ? "" : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateViewState(Action<ViewState> action) {
|
||||||
|
ViewState current = viewState.getValue();
|
||||||
|
if (current != null) {
|
||||||
|
action.run(current);
|
||||||
|
viewState.setValue(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Action<T> {
|
||||||
|
void run(T target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ViewState {
|
||||||
|
public boolean isEditing = false;
|
||||||
|
public boolean isDeleteVisible = false;
|
||||||
|
public boolean isSupIdVisible = false;
|
||||||
|
public boolean isFieldsEnabled = true;
|
||||||
|
public String modeTitle = "Add Supplier";
|
||||||
|
public String saveButtonText = "Add";
|
||||||
|
public String supCompany = "";
|
||||||
|
public String supFirstName = "";
|
||||||
|
public String supLastName = "";
|
||||||
|
public String supEmail = "";
|
||||||
|
public String supPhone = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,17 +84,17 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<!-- Pet -->
|
<!-- 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"
|
android:text="Source 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"/>
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerAdoptionPet"
|
android:id="@+id/spinnerAdoptionStore"
|
||||||
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:layout_marginBottom="16dp"/>
|
||||||
@@ -114,16 +114,17 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
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="Source Store"
|
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"/>
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerAdoptionStore"
|
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:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|||||||
@@ -45,9 +45,267 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/rowFilterHeader"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Filters"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvFilterSummary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="All time"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginEnd="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvFilterToggleIcon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="▼"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llFilterContent"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Quick Range"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:layout_marginBottom="6dp"/>
|
||||||
|
|
||||||
|
<HorizontalScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:scrollbars="none"
|
||||||
|
android:layout_marginBottom="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPresetToday"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:text="Today"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPreset7D"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:text="7D"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPreset30D"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:text="30D"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPreset3M"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:text="3M"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPreset1Y"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:text="1Y"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPresetAll"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:text="All"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:backgroundTint="@color/text_light"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</HorizontalScrollView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Date Range"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:layout_marginBottom="6dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="12dp">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etFilterStartDate"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="Start date"
|
||||||
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||||
|
android:textSize="13sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text=" – "
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etFilterEndDate"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="End date"
|
||||||
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||||
|
android:textSize="13sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Payment Method"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerFilterPayment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Top N Products"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerTopN"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnFilterApply"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:text="Apply"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnFilterReset"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Reset"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -299,6 +557,7 @@
|
|||||||
android:layout_marginBottom="16dp">
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tvDailyRevenueTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Daily Revenue (Last 7 Days)"
|
android:text="Daily Revenue (Last 7 Days)"
|
||||||
|
|||||||
@@ -34,8 +34,9 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvChatTitle"
|
android:id="@+id/tvChatTitle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:text="Customer Chat"
|
android:text="Customer Chat"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
@@ -43,6 +44,16 @@
|
|||||||
android:paddingStart="8dp"
|
android:paddingStart="8dp"
|
||||||
android:paddingEnd="8dp"/>
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnCloseChat"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Close Chat"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
|||||||
160
android/app/src/main/res/layout/fragment_coupon.xml
Normal file
160
android/app/src/main/res/layout/fragment_coupon.xml
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
<?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/headerCoupon"
|
||||||
|
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/btnHamburgerCoupon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@drawable/baseline_menu_36"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="Open menu"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Coupons"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="8dp"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnBulkDeleteCoupons"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_delete"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:contentDescription="Bulk Delete Coupons"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnToggleFilterCoupon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
app:tint="@color/white"
|
||||||
|
android:contentDescription="Toggle filter"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutFilterCoupon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:background="@color/primary_dark"
|
||||||
|
android:elevation="4dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:background="@drawable/bg_search_bar"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="18dp"
|
||||||
|
android:layout_height="18dp"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:alpha="0.6"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etSearchCoupon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:hint="Search coupons..."
|
||||||
|
android:inputType="text"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textColorHint="#99000000"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerTypeCoupon"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_spinner"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginEnd="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStatusCoupon"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_spinner"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginStart="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/swipeRefreshCoupon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/recyclerViewCoupon"
|
||||||
|
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/fabAddCoupon"
|
||||||
|
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 Coupon"
|
||||||
|
app:srcCompat="@android:drawable/ic_input_add"
|
||||||
|
app:tint="@color/white"/>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
258
android/app/src/main/res/layout/fragment_coupon_detail.xml
Normal file
258
android/app/src/main/res/layout/fragment_coupon_detail.xml
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout 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">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/background_grey">
|
||||||
|
|
||||||
|
<!-- Top Bar -->
|
||||||
|
<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/tvTitleCouponDetail"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Coupon Detail"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDeleteCouponDetail"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:text="Delete"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<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">
|
||||||
|
|
||||||
|
<!-- Coupon Code -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Coupon Code"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etCouponCodeDetail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Enter coupon code"
|
||||||
|
android:inputType="textCapCharacters"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Discount Type -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Discount Type"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerDiscountTypeDetail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Discount Value -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Discount Value"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etDiscountValueDetail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="0.00"
|
||||||
|
android:inputType="numberDecimal"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Min Order Amount -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Min Order Amount"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etMinOrderAmountDetail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="0.00"
|
||||||
|
android:inputType="numberDecimal"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Usage Limit -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Usage Limit"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etUsageLimitDetail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="No limit"
|
||||||
|
android:inputType="number"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Dates -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginEnd="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Starts At"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etStartsAtDetail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="YYYY-MM-DD"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginStart="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Ends At"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etEndsAtDetail"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="YYYY-MM-DD"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Active -->
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbActiveDetail"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Active"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- Bottom Action Bar -->
|
||||||
|
<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/btnBackCouponDetail"
|
||||||
|
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/btnSaveCouponDetail"
|
||||||
|
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>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -34,9 +34,10 @@
|
|||||||
android:layout_width="260dp"
|
android:layout_width="260dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="start"
|
android:layout_gravity="start"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:background="@color/primary_dark">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Header: Logo and Store Name (Static) -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -69,10 +70,104 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Scrollable Menu Items -->
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="24dp">
|
||||||
|
|
||||||
|
<!-- BUSINESS SECTION -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="MANAGE"
|
android:text="BUSINESS"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:letterSpacing="0.15"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="24dp"
|
||||||
|
android:paddingBottom="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/drawerAnalytics"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:background="?attr/selectableItemBackground">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Analytics"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/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="Sales"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/drawerServices"
|
||||||
|
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="Services"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- STORE SECTION -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="STORE"
|
||||||
android:textColor="@color/text_light"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="11sp"
|
android:textSize="11sp"
|
||||||
android:letterSpacing="0.15"
|
android:letterSpacing="0.15"
|
||||||
@@ -98,7 +193,7 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/drawerServices"
|
android:id="@+id/drawerAdoptions"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
@@ -109,7 +204,59 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Services"
|
android:text="Adoptions"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/drawerProducts"
|
||||||
|
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="Products"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- ADMIN SECTION -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/sectionAdmin"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="ADMIN"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:letterSpacing="0.15"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingTop="24dp"
|
||||||
|
android:paddingBottom="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/drawerInventory"
|
||||||
|
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="Inventory"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="15sp"/>
|
android:textSize="15sp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -132,7 +279,7 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/drawerAppointments"
|
android:id="@+id/drawerCoupons"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
@@ -143,110 +290,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Appointments"
|
android:text="Coupons"
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="15sp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/drawerAdoptions"
|
|
||||||
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="Adoptions"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="15sp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/drawerInventory"
|
|
||||||
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="Inventory"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="15sp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/drawerProducts"
|
|
||||||
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="Products"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="15sp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<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
|
|
||||||
android:id="@+id/drawerAnalytics"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:background="?attr/selectableItemBackground">
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Analytics"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="15sp"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/drawerStaff"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:visibility="gone">
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Staff Accounts"
|
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="15sp"/>
|
android:textSize="15sp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -263,7 +307,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="PurchaseOrder"
|
android:text="Purchase Orders"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="15sp"/>
|
android:textSize="15sp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -280,11 +324,33 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="ProductSupplier"
|
android:text="Product Suppliers"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="15sp"/>
|
android:textSize="15sp"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/drawerStaff"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:background="?attr/selectableItemBackground">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Staff Accounts"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="15sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.drawerlayout.widget.DrawerLayout>
|
</androidx.drawerlayout.widget.DrawerLayout>
|
||||||
|
|||||||
@@ -117,8 +117,25 @@
|
|||||||
android:background="@drawable/bg_spinner"
|
android:background="@drawable/bg_spinner"
|
||||||
android:paddingStart="12dp"
|
android:paddingStart="12dp"
|
||||||
android:paddingEnd="8dp"
|
android:paddingEnd="8dp"
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:layout_marginStart="4dp"/>
|
android:layout_marginStart="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerRefundStatus"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_spinner"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginEnd="4dp"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnOpenRefund"
|
android:id="@+id/btnOpenRefund"
|
||||||
|
|||||||
@@ -83,6 +83,40 @@
|
|||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tvSaleStore"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llCustomerInfo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Customer"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleCustomer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCustomerLabel"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Customer (Optional)"
|
android:text="Customer (Optional)"
|
||||||
@@ -111,6 +145,15 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSalePaymentMethod"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/llExtraInfo"
|
android:id="@+id/llExtraInfo"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -91,6 +91,34 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStoreStaff"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_spinner"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginEnd="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStatusStaff"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/bg_spinner"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:layout_marginStart="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
|||||||
@@ -173,7 +173,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Role"
|
android:text="User Role"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
@@ -184,7 +184,34 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<!-- Status -->
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Staff Role"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStaffType"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Primary Store"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStaffStore"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
92
android/app/src/main/res/layout/item_coupon.xml
Normal file
92
android/app/src/main/res/layout/item_coupon.xml
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginBottom="1dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbSelectCoupon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCouponCode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="COUPONCODE"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCouponStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingTop="3dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingBottom="3dp"
|
||||||
|
android:text="ACTIVE"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="11sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCouponDiscount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:text="10% OFF"
|
||||||
|
android:textColor="@color/accent_coral"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCouponMinOrder"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:text="Min order: $50.00"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvCouponExpiry"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:text="Expires: 2026-12-31"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -18,8 +18,8 @@
|
|||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
android:id="@+id/ivEmployeeProfile"
|
android:id="@+id/ivEmployeeProfile"
|
||||||
android:layout_width="60dp"
|
android:layout_width="80dp"
|
||||||
android:layout_height="60dp"
|
android:layout_height="80dp"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/placeholder"
|
android:src="@drawable/placeholder"
|
||||||
|
|||||||
@@ -157,4 +157,21 @@
|
|||||||
android:label="Analytics"
|
android:label="Analytics"
|
||||||
tools:layout="@layout/fragment_analytics" />
|
tools:layout="@layout/fragment_analytics" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/nav_coupon"
|
||||||
|
android:name="com.example.petstoremobile.fragments.listfragments.CouponFragment"
|
||||||
|
android:label="Coupons"
|
||||||
|
tools:layout="@layout/fragment_coupon" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/couponDetailFragment"
|
||||||
|
android:name="com.example.petstoremobile.fragments.listfragments.detailfragments.CouponDetailFragment"
|
||||||
|
android:label="Coupon Details"
|
||||||
|
tools:layout="@layout/fragment_coupon_detail">
|
||||||
|
<argument
|
||||||
|
android:name="couponId"
|
||||||
|
app:argType="long"
|
||||||
|
android:defaultValue="-1L" />
|
||||||
|
</fragment>
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.petshop.backend.controller;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.common.BulkDeleteRequest;
|
||||||
|
import com.petshop.backend.dto.common.CouponRequest;
|
||||||
|
import com.petshop.backend.dto.common.CouponResponse;
|
||||||
|
import com.petshop.backend.service.CouponService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/coupons")
|
||||||
|
@PreAuthorize("hasAnyRole('STAFF', 'ADMIN')")
|
||||||
|
public class CouponController {
|
||||||
|
|
||||||
|
private final CouponService couponService;
|
||||||
|
|
||||||
|
public CouponController(CouponService couponService) {
|
||||||
|
this.couponService = couponService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<Page<CouponResponse>> getAllCoupons(
|
||||||
|
@RequestParam(required = false) String q,
|
||||||
|
@RequestParam(required = false) Boolean active,
|
||||||
|
Pageable pageable) {
|
||||||
|
return ResponseEntity.ok(couponService.getAllCoupons(q, active, pageable));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ResponseEntity<CouponResponse> getCouponById(@PathVariable Long id) {
|
||||||
|
return ResponseEntity.ok(couponService.getCouponById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/code/{code}")
|
||||||
|
public ResponseEntity<CouponResponse> getCouponByCode(@PathVariable String code) {
|
||||||
|
return ResponseEntity.ok(couponService.getCouponByCode(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public ResponseEntity<CouponResponse> createCoupon(@Valid @RequestBody CouponRequest request) {
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(couponService.createCoupon(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ResponseEntity<CouponResponse> updateCoupon(@PathVariable Long id, @Valid @RequestBody CouponRequest request) {
|
||||||
|
return ResponseEntity.ok(couponService.updateCoupon(id, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ResponseEntity<Void> deleteCoupon(@PathVariable Long id) {
|
||||||
|
couponService.deleteCoupon(id);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping
|
||||||
|
public ResponseEntity<Void> bulkDeleteCoupons(@Valid @RequestBody BulkDeleteRequest request) {
|
||||||
|
couponService.bulkDeleteCoupons(request);
|
||||||
|
return ResponseEntity.noContent().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,8 +27,9 @@ public class SaleController {
|
|||||||
@RequestParam(required = false) String q,
|
@RequestParam(required = false) String q,
|
||||||
@RequestParam(required = false) String paymentMethod,
|
@RequestParam(required = false) String paymentMethod,
|
||||||
@RequestParam(required = false) Long storeId,
|
@RequestParam(required = false) Long storeId,
|
||||||
|
@RequestParam(required = false) Boolean isRefund,
|
||||||
Pageable pageable) {
|
Pageable pageable) {
|
||||||
return ResponseEntity.ok(saleService.getAllSales(q, paymentMethod, storeId, pageable));
|
return ResponseEntity.ok(saleService.getAllSales(q, paymentMethod, storeId, isRefund, pageable));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.petshop.backend.dto.common;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.Positive;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class CouponRequest {
|
||||||
|
@NotBlank(message = "Coupon code is required")
|
||||||
|
private String couponCode;
|
||||||
|
|
||||||
|
@NotBlank(message = "Discount type is required")
|
||||||
|
private String discountType;
|
||||||
|
|
||||||
|
@NotNull(message = "Discount value is required")
|
||||||
|
@Positive(message = "Discount value must be positive")
|
||||||
|
private BigDecimal discountValue;
|
||||||
|
|
||||||
|
private BigDecimal minOrderAmount;
|
||||||
|
|
||||||
|
private Boolean active = true;
|
||||||
|
|
||||||
|
private LocalDateTime startsAt;
|
||||||
|
|
||||||
|
private LocalDateTime endsAt;
|
||||||
|
|
||||||
|
private Integer usageLimit;
|
||||||
|
|
||||||
|
public String getCouponCode() { return couponCode; }
|
||||||
|
public void setCouponCode(String couponCode) { this.couponCode = couponCode; }
|
||||||
|
|
||||||
|
public String getDiscountType() { return discountType; }
|
||||||
|
public void setDiscountType(String discountType) { this.discountType = discountType; }
|
||||||
|
|
||||||
|
public BigDecimal getDiscountValue() { return discountValue; }
|
||||||
|
public void setDiscountValue(BigDecimal discountValue) { this.discountValue = discountValue; }
|
||||||
|
|
||||||
|
public BigDecimal getMinOrderAmount() { return minOrderAmount; }
|
||||||
|
public void setMinOrderAmount(BigDecimal minOrderAmount) { this.minOrderAmount = minOrderAmount; }
|
||||||
|
|
||||||
|
public Boolean getActive() { return active; }
|
||||||
|
public void setActive(Boolean active) { this.active = active; }
|
||||||
|
|
||||||
|
public LocalDateTime getStartsAt() { return startsAt; }
|
||||||
|
public void setStartsAt(LocalDateTime startsAt) { this.startsAt = startsAt; }
|
||||||
|
|
||||||
|
public LocalDateTime getEndsAt() { return endsAt; }
|
||||||
|
public void setEndsAt(LocalDateTime endsAt) { this.endsAt = endsAt; }
|
||||||
|
|
||||||
|
public Integer getUsageLimit() { return usageLimit; }
|
||||||
|
public void setUsageLimit(Integer usageLimit) { this.usageLimit = usageLimit; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.petshop.backend.dto.common;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public class CouponResponse {
|
||||||
|
private Long couponId;
|
||||||
|
private String couponCode;
|
||||||
|
private String discountType;
|
||||||
|
private BigDecimal discountValue;
|
||||||
|
private BigDecimal minOrderAmount;
|
||||||
|
private Boolean active;
|
||||||
|
private LocalDateTime startsAt;
|
||||||
|
private LocalDateTime endsAt;
|
||||||
|
private Integer usageLimit;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public CouponResponse(Long couponId, String couponCode, String discountType, BigDecimal discountValue, BigDecimal minOrderAmount, Boolean active, LocalDateTime startsAt, LocalDateTime endsAt, Integer usageLimit, LocalDateTime createdAt, LocalDateTime updatedAt) {
|
||||||
|
this.couponId = couponId;
|
||||||
|
this.couponCode = couponCode;
|
||||||
|
this.discountType = discountType;
|
||||||
|
this.discountValue = discountValue;
|
||||||
|
this.minOrderAmount = minOrderAmount;
|
||||||
|
this.active = active;
|
||||||
|
this.startsAt = startsAt;
|
||||||
|
this.endsAt = endsAt;
|
||||||
|
this.usageLimit = usageLimit;
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCouponId() { return couponId; }
|
||||||
|
public void setCouponId(Long couponId) { this.couponId = couponId; }
|
||||||
|
|
||||||
|
public String getCouponCode() { return couponCode; }
|
||||||
|
public void setCouponCode(String couponCode) { this.couponCode = couponCode; }
|
||||||
|
|
||||||
|
public String getDiscountType() { return discountType; }
|
||||||
|
public void setDiscountType(String discountType) { this.discountType = discountType; }
|
||||||
|
|
||||||
|
public BigDecimal getDiscountValue() { return discountValue; }
|
||||||
|
public void setDiscountValue(BigDecimal discountValue) { this.discountValue = discountValue; }
|
||||||
|
|
||||||
|
public BigDecimal getMinOrderAmount() { return minOrderAmount; }
|
||||||
|
public void setMinOrderAmount(BigDecimal minOrderAmount) { this.minOrderAmount = minOrderAmount; }
|
||||||
|
|
||||||
|
public Boolean getActive() { return active; }
|
||||||
|
public void setActive(Boolean active) { this.active = active; }
|
||||||
|
|
||||||
|
public LocalDateTime getStartsAt() { return startsAt; }
|
||||||
|
public void setStartsAt(LocalDateTime startsAt) { this.startsAt = startsAt; }
|
||||||
|
|
||||||
|
public LocalDateTime getEndsAt() { return endsAt; }
|
||||||
|
public void setEndsAt(LocalDateTime endsAt) { this.endsAt = endsAt; }
|
||||||
|
|
||||||
|
public Integer getUsageLimit() { return usageLimit; }
|
||||||
|
public void setUsageLimit(Integer usageLimit) { this.usageLimit = usageLimit; }
|
||||||
|
|
||||||
|
public LocalDateTime getCreatedAt() { return createdAt; }
|
||||||
|
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
|
||||||
|
|
||||||
|
public LocalDateTime getUpdatedAt() { return updatedAt; }
|
||||||
|
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
|
||||||
|
}
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
package com.petshop.backend.repository;
|
package com.petshop.backend.repository;
|
||||||
|
|
||||||
import com.petshop.backend.entity.Coupon;
|
import com.petshop.backend.entity.Coupon;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@@ -12,4 +16,8 @@ public interface CouponRepository extends JpaRepository<Coupon, Long> {
|
|||||||
Optional<Coupon> findByCouponCode(String couponCode);
|
Optional<Coupon> findByCouponCode(String couponCode);
|
||||||
|
|
||||||
Optional<Coupon> findByCouponCodeIgnoreCase(String couponCode);
|
Optional<Coupon> findByCouponCodeIgnoreCase(String couponCode);
|
||||||
|
@Query("SELECT c FROM Coupon c WHERE " +
|
||||||
|
"(:q IS NULL OR LOWER(c.couponCode) LIKE LOWER(CONCAT('%', :q, '%'))) AND " +
|
||||||
|
"(:active IS NULL OR c.active = :active)")
|
||||||
|
Page<Coupon> searchCoupons(@Param("q") String query, @Param("active") Boolean active, Pageable pageable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,9 @@ public interface SaleRepository extends JpaRepository<Sale, Long> {
|
|||||||
"LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))" +
|
"LOWER(s.store.storeName) LIKE LOWER(CONCAT('%', :q, '%'))" +
|
||||||
")) AND " +
|
")) AND " +
|
||||||
"(:paymentMethod IS NULL OR LOWER(s.paymentMethod) = LOWER(:paymentMethod)) AND " +
|
"(:paymentMethod IS NULL OR LOWER(s.paymentMethod) = LOWER(:paymentMethod)) AND " +
|
||||||
|
"(:isRefund IS NULL OR s.isRefund = :isRefund) AND " +
|
||||||
"(:storeId IS NULL OR s.store.storeId = :storeId)")
|
"(:storeId IS NULL OR s.store.storeId = :storeId)")
|
||||||
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, Pageable pageable);
|
Page<Sale> searchSales(@Param("q") String query, @Param("paymentMethod") String paymentMethod, @Param("storeId") Long storeId, @Param("isRefund") Boolean isRefund, Pageable pageable);
|
||||||
|
|
||||||
List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
|
List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class AdoptionService {
|
|||||||
private static final String ADOPTION_STATUS_PENDING = "Pending";
|
private static final String ADOPTION_STATUS_PENDING = "Pending";
|
||||||
private static final String ADOPTION_STATUS_COMPLETED = "Completed";
|
private static final String ADOPTION_STATUS_COMPLETED = "Completed";
|
||||||
private static final String ADOPTION_STATUS_CANCELLED = "Cancelled";
|
private static final String ADOPTION_STATUS_CANCELLED = "Cancelled";
|
||||||
|
private static final String ADOPTION_STATUS_MISSED = "Missed";
|
||||||
private static final String PET_STATUS_AVAILABLE = "Available";
|
private static final String PET_STATUS_AVAILABLE = "Available";
|
||||||
private static final String PET_STATUS_ADOPTED = "Adopted";
|
private static final String PET_STATUS_ADOPTED = "Adopted";
|
||||||
|
|
||||||
@@ -218,7 +219,10 @@ public class AdoptionService {
|
|||||||
if (ADOPTION_STATUS_CANCELLED.equalsIgnoreCase(trimmedStatus)) {
|
if (ADOPTION_STATUS_CANCELLED.equalsIgnoreCase(trimmedStatus)) {
|
||||||
return ADOPTION_STATUS_CANCELLED;
|
return ADOPTION_STATUS_CANCELLED;
|
||||||
}
|
}
|
||||||
throw new IllegalArgumentException("Adoption status must be Pending, Completed, or Cancelled");
|
if (ADOPTION_STATUS_MISSED.equalsIgnoreCase(trimmedStatus)) {
|
||||||
|
return ADOPTION_STATUS_MISSED;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Adoption status must be Pending, Completed, Cancelled, or Missed");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void validatePetAvailability(Pet pet, Long adoptionId, Long currentPetId) {
|
private void validatePetAvailability(Pet pet, Long adoptionId, Long currentPetId) {
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
package com.petshop.backend.service;
|
||||||
|
|
||||||
|
import com.petshop.backend.dto.common.BulkDeleteRequest;
|
||||||
|
import com.petshop.backend.dto.common.CouponRequest;
|
||||||
|
import com.petshop.backend.dto.common.CouponResponse;
|
||||||
|
import com.petshop.backend.entity.Coupon;
|
||||||
|
import com.petshop.backend.exception.ResourceNotFoundException;
|
||||||
|
import com.petshop.backend.repository.CouponRepository;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CouponService {
|
||||||
|
|
||||||
|
private final CouponRepository couponRepository;
|
||||||
|
|
||||||
|
public CouponService(CouponRepository couponRepository) {
|
||||||
|
this.couponRepository = couponRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page<CouponResponse> getAllCoupons(String query, Boolean active, Pageable pageable) {
|
||||||
|
return couponRepository.searchCoupons(query, active, pageable).map(this::mapToResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CouponResponse getCouponById(Long id) {
|
||||||
|
Coupon coupon = couponRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + id));
|
||||||
|
return mapToResponse(coupon);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CouponResponse getCouponByCode(String code) {
|
||||||
|
Coupon coupon = couponRepository.findByCouponCode(code)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with code: " + code));
|
||||||
|
return mapToResponse(coupon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public CouponResponse createCoupon(CouponRequest request) {
|
||||||
|
if (couponRepository.findByCouponCode(request.getCouponCode()).isPresent()) {
|
||||||
|
throw new IllegalArgumentException("Coupon code already exists: " + request.getCouponCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
Coupon coupon = new Coupon();
|
||||||
|
updateCouponFields(coupon, request);
|
||||||
|
coupon = couponRepository.save(coupon);
|
||||||
|
return mapToResponse(coupon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public CouponResponse updateCoupon(Long id, CouponRequest request) {
|
||||||
|
Coupon coupon = couponRepository.findById(id)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Coupon not found with id: " + id));
|
||||||
|
|
||||||
|
couponRepository.findByCouponCode(request.getCouponCode()).ifPresent(existing -> {
|
||||||
|
if (!existing.getCouponId().equals(id)) {
|
||||||
|
throw new IllegalArgumentException("Coupon code already exists: " + request.getCouponCode());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateCouponFields(coupon, request);
|
||||||
|
coupon = couponRepository.save(coupon);
|
||||||
|
return mapToResponse(coupon);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void deleteCoupon(Long id) {
|
||||||
|
if (!couponRepository.existsById(id)) {
|
||||||
|
throw new ResourceNotFoundException("Coupon not found with id: " + id);
|
||||||
|
}
|
||||||
|
couponRepository.deleteById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void bulkDeleteCoupons(BulkDeleteRequest request) {
|
||||||
|
couponRepository.deleteAllById(request.getIds());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCouponFields(Coupon coupon, CouponRequest request) {
|
||||||
|
coupon.setCouponCode(request.getCouponCode());
|
||||||
|
coupon.setDiscountType(request.getDiscountType());
|
||||||
|
coupon.setDiscountValue(request.getDiscountValue());
|
||||||
|
coupon.setMinOrderAmount(request.getMinOrderAmount());
|
||||||
|
coupon.setActive(request.getActive());
|
||||||
|
coupon.setStartsAt(request.getStartsAt());
|
||||||
|
coupon.setEndsAt(request.getEndsAt());
|
||||||
|
coupon.setUsageLimit(request.getUsageLimit());
|
||||||
|
}
|
||||||
|
|
||||||
|
private CouponResponse mapToResponse(Coupon coupon) {
|
||||||
|
return new CouponResponse(
|
||||||
|
coupon.getCouponId(),
|
||||||
|
coupon.getCouponCode(),
|
||||||
|
coupon.getDiscountType(),
|
||||||
|
coupon.getDiscountValue(),
|
||||||
|
coupon.getMinOrderAmount(),
|
||||||
|
coupon.getActive(),
|
||||||
|
coupon.getStartsAt(),
|
||||||
|
coupon.getEndsAt(),
|
||||||
|
coupon.getUsageLimit(),
|
||||||
|
coupon.getCreatedAt(),
|
||||||
|
coupon.getUpdatedAt()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,8 +39,8 @@ public class SaleService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public Page<SaleResponse> getAllSales(String query, String paymentMethod, Long storeId, Pageable pageable) {
|
public Page<SaleResponse> getAllSales(String query, String paymentMethod, Long storeId, Boolean isRefund, Pageable pageable) {
|
||||||
Page<Sale> sales = saleRepository.searchSales(normalizeFilter(query), normalizeFilter(paymentMethod), storeId, pageable);
|
Page<Sale> sales = saleRepository.searchSales(normalizeFilter(query), normalizeFilter(paymentMethod), storeId, isRefund, pageable);
|
||||||
return sales.map(this::mapToResponse);
|
return sales.map(this::mapToResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user