Inventory
Inventory- details of product loads with id and described with filter, and categories selection
This commit is contained in:
@@ -1,75 +1,144 @@
|
|||||||
package com.example.petstoremobile.adapters;
|
package com.example.petstoremobile.adapters;
|
||||||
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.models.Inventory;
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> {
|
public class InventoryAdapter extends RecyclerView.Adapter<InventoryAdapter.InventoryViewHolder> {
|
||||||
|
|
||||||
private List<Inventory> inventoryList;
|
private final List<InventoryDTO> inventoryList;
|
||||||
private OnInventoryClickListener inventoryClickListener;
|
private final OnInventoryClickListener clickListener;
|
||||||
|
private final List<Long> selectedIds = new ArrayList<>();
|
||||||
|
private boolean selectionMode = false;
|
||||||
|
|
||||||
// Interface for inventory click on recycler view
|
|
||||||
public interface OnInventoryClickListener {
|
public interface OnInventoryClickListener {
|
||||||
void onInventoryClick(int position);
|
void onInventoryClick(int position);
|
||||||
|
|
||||||
|
void onSelectionChanged(int selectedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructor
|
public InventoryAdapter(List<InventoryDTO> inventoryList, OnInventoryClickListener clickListener) {
|
||||||
public InventoryAdapter(List<Inventory> inventoryList, OnInventoryClickListener inventoryClickListener) {
|
|
||||||
this.inventoryList = inventoryList;
|
this.inventoryList = inventoryList;
|
||||||
this.inventoryClickListener = inventoryClickListener;
|
this.clickListener = clickListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the controls of each row in recycler view
|
|
||||||
public static class InventoryViewHolder extends RecyclerView.ViewHolder {
|
public static class InventoryViewHolder extends RecyclerView.ViewHolder {
|
||||||
TextView tvItemName, tvCategory, tvQuantity, tvUnitPrice, tvSupplier;
|
// Matches desktop table columns: Inventory ID, Product ID, Product Name,
|
||||||
|
// Quantity
|
||||||
|
TextView tvInventoryId, tvProdId, tvProductName, tvQuantity;
|
||||||
|
CheckBox checkBox;
|
||||||
|
|
||||||
public InventoryViewHolder(@NonNull View v) {
|
public InventoryViewHolder(@NonNull View v) {
|
||||||
super(v);
|
super(v);
|
||||||
tvItemName = v.findViewById(R.id.tvItemName);
|
tvInventoryId = v.findViewById(R.id.tvInventoryId);
|
||||||
tvCategory = v.findViewById(R.id.tvCategory);
|
tvProdId = v.findViewById(R.id.tvProdId);
|
||||||
|
tvProductName = v.findViewById(R.id.tvProductName);
|
||||||
tvQuantity = v.findViewById(R.id.tvQuantity);
|
tvQuantity = v.findViewById(R.id.tvQuantity);
|
||||||
tvUnitPrice = v.findViewById(R.id.tvUnitPrice);
|
checkBox = v.findViewById(R.id.cbSelectInventory);
|
||||||
tvSupplier = v.findViewById(R.id.tvInvSupplier);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new row view
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
public InventoryViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_inventory, parent, false);
|
View v = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.item_inventory, parent, false);
|
||||||
return new InventoryViewHolder(v);
|
return new InventoryViewHolder(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate the row with inventory data
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull InventoryViewHolder holder, int position) {
|
||||||
Inventory inventory = inventoryList.get(position);
|
InventoryDTO inv = inventoryList.get(position);
|
||||||
|
|
||||||
holder.tvItemName.setText(inventory.getItemName());
|
// Column: Inventory ID
|
||||||
holder.tvCategory.setText(inventory.getCategory());
|
holder.tvInventoryId.setText(String.valueOf(inv.getInventoryId() != null ? inv.getInventoryId() : "—"));
|
||||||
holder.tvQuantity.setText("Qty: " + inventory.getQuantity());
|
|
||||||
holder.tvUnitPrice.setText("$" + String.format("%.2f", inventory.getUnitPrice()));
|
|
||||||
holder.tvSupplier.setText("Supplier: " + inventory.getSupplier());
|
|
||||||
|
|
||||||
// Highlight low stock items in red
|
// Column: Product ID
|
||||||
if (inventory.getQuantity() <= 5) {
|
holder.tvProdId.setText(String.valueOf(inv.getProdId() != null ? inv.getProdId() : "—"));
|
||||||
|
|
||||||
|
// Column: Product Name
|
||||||
|
holder.tvProductName.setText(inv.getProductName() != null ? inv.getProductName() : "—");
|
||||||
|
|
||||||
|
// Column: Quantity
|
||||||
|
int qty = inv.getQuantity() != null ? inv.getQuantity() : 0;
|
||||||
|
holder.tvQuantity.setText(String.valueOf(qty));
|
||||||
|
|
||||||
|
// Low stock = red, normal = green (like desktop reorder concept)
|
||||||
|
if (qty <= 5) {
|
||||||
holder.tvQuantity.setTextColor(Color.parseColor("#F44336"));
|
holder.tvQuantity.setTextColor(Color.parseColor("#F44336"));
|
||||||
} else {
|
} else {
|
||||||
holder.tvQuantity.setTextColor(Color.parseColor("#4CAF50"));
|
holder.tvQuantity.setTextColor(Color.parseColor("#4CAF50"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a row is clicked, open the detail view
|
// Bulk delete selection mode
|
||||||
holder.itemView.setOnClickListener(v -> inventoryClickListener.onInventoryClick(position));
|
if (selectionMode) {
|
||||||
|
holder.checkBox.setVisibility(View.VISIBLE);
|
||||||
|
holder.checkBox.setChecked(inv.getInventoryId() != null
|
||||||
|
&& selectedIds.contains(inv.getInventoryId()));
|
||||||
|
} else {
|
||||||
|
holder.checkBox.setVisibility(View.GONE);
|
||||||
|
holder.checkBox.setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.itemView.setOnClickListener(v -> {
|
||||||
|
if (selectionMode) {
|
||||||
|
toggleSelection(inv.getInventoryId(), holder.checkBox);
|
||||||
|
} else {
|
||||||
|
clickListener.onInventoryClick(holder.getAdapterPosition());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
holder.itemView.setOnLongClickListener(v -> {
|
||||||
|
if (!selectionMode) {
|
||||||
|
selectionMode = true;
|
||||||
|
toggleSelection(inv.getInventoryId(), holder.checkBox);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleSelection(Long id, CheckBox checkBox) {
|
||||||
|
if (id == null)
|
||||||
|
return;
|
||||||
|
if (selectedIds.contains(id)) {
|
||||||
|
selectedIds.remove(id);
|
||||||
|
checkBox.setChecked(false);
|
||||||
|
} else {
|
||||||
|
selectedIds.add(id);
|
||||||
|
checkBox.setChecked(true);
|
||||||
|
}
|
||||||
|
clickListener.onSelectionChanged(selectedIds.size());
|
||||||
|
if (selectedIds.isEmpty()) {
|
||||||
|
selectionMode = false;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getSelectedIds() {
|
||||||
|
return new ArrayList<>(selectedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearSelection() {
|
||||||
|
selectedIds.clear();
|
||||||
|
selectionMode = false;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInSelectionMode() {
|
||||||
|
return selectionMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.InventoryRequest;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.http.Body;
|
||||||
|
import retrofit2.http.DELETE;
|
||||||
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.PUT;
|
||||||
|
import retrofit2.http.Path;
|
||||||
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
|
public interface InventoryApi {
|
||||||
|
|
||||||
|
// GET /api/v1/inventory?q=...&page=...&size=...
|
||||||
|
@GET("api/v1/inventory")
|
||||||
|
Call<PageResponse<InventoryDTO>> getAllInventory(
|
||||||
|
@Query("q") String query,
|
||||||
|
@Query("page") int page,
|
||||||
|
@Query("size") int size,
|
||||||
|
@Query("sort") String sort);
|
||||||
|
|
||||||
|
// GET /api/v1/inventory/{id}
|
||||||
|
@GET("api/v1/inventory/{id}")
|
||||||
|
Call<InventoryDTO> getInventoryById(@Path("id") Long id);
|
||||||
|
|
||||||
|
// POST /api/v1/inventory
|
||||||
|
@POST("api/v1/inventory")
|
||||||
|
Call<InventoryDTO> createInventory(@Body InventoryRequest request);
|
||||||
|
|
||||||
|
// PUT /api/v1/inventory/{id}
|
||||||
|
@PUT("api/v1/inventory/{id}")
|
||||||
|
Call<InventoryDTO> updateInventory(@Path("id") Long id, @Body InventoryRequest request);
|
||||||
|
|
||||||
|
// DELETE /api/v1/inventory/{id}
|
||||||
|
@DELETE("api/v1/inventory/{id}")
|
||||||
|
Call<Void> deleteInventory(@Path("id") Long id);
|
||||||
|
|
||||||
|
// DELETE /api/v1/inventory (bulk delete)
|
||||||
|
@DELETE("api/v1/inventory")
|
||||||
|
Call<Void> bulkDeleteInventory(@Body BulkDeleteRequest request);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class BulkDeleteRequest {
|
||||||
|
private List<Long> ids;
|
||||||
|
|
||||||
|
public BulkDeleteRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BulkDeleteRequest(List<Long> ids) {
|
||||||
|
this.ids = ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Long> getIds() {
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIds(List<Long> ids) {
|
||||||
|
this.ids = ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
public class InventoryDTO {
|
||||||
|
// Response fields (from backend InventoryResponse)
|
||||||
|
private Long inventoryId;
|
||||||
|
private Long prodId;
|
||||||
|
private String productName;
|
||||||
|
private String categoryName;
|
||||||
|
private Integer quantity;
|
||||||
|
private String createdAt;
|
||||||
|
private String updatedAt;
|
||||||
|
|
||||||
|
public InventoryDTO() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor for create/update requests (matches InventoryRequest)
|
||||||
|
public InventoryDTO(Long prodId, Integer quantity) {
|
||||||
|
this.prodId = prodId;
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getInventoryId() {
|
||||||
|
return inventoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInventoryId(Long inventoryId) {
|
||||||
|
this.inventoryId = inventoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProdId() {
|
||||||
|
return prodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProdId(Long prodId) {
|
||||||
|
this.prodId = prodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProductName() {
|
||||||
|
return productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductName(String productName) {
|
||||||
|
this.productName = productName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategoryName() {
|
||||||
|
return categoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryName(String categoryName) {
|
||||||
|
this.categoryName = categoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(Integer quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreatedAt() {
|
||||||
|
return createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreatedAt(String createdAt) {
|
||||||
|
this.createdAt = createdAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdatedAt() {
|
||||||
|
return updatedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUpdatedAt(String updatedAt) {
|
||||||
|
this.updatedAt = updatedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
public class InventoryRequest {
|
||||||
|
private Long prodId;
|
||||||
|
private Integer quantity;
|
||||||
|
|
||||||
|
public InventoryRequest() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public InventoryRequest(Long prodId, Integer quantity) {
|
||||||
|
this.prodId = prodId;
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getProdId() {
|
||||||
|
return prodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProdId(Long prodId) {
|
||||||
|
this.prodId = prodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getQuantity() {
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQuantity(Integer quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,162 +1,378 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
// Added search/filter bar to filter inventory by item name or category.
|
|
||||||
// Added pull-to-refresh using SwipeRefreshLayout.
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.Spinner;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.InventoryAdapter;
|
import com.example.petstoremobile.adapters.InventoryAdapter;
|
||||||
|
import com.example.petstoremobile.api.CategoryApi;
|
||||||
|
import com.example.petstoremobile.api.InventoryApi;
|
||||||
|
import com.example.petstoremobile.api.RetrofitClient;
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.fragments.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.InventoryDetailFragment;
|
import com.example.petstoremobile.fragments.listfragments.detailfragments.InventoryDetailFragment;
|
||||||
import com.example.petstoremobile.models.Inventory;
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
||||||
|
|
||||||
private List<Inventory> inventoryList = new ArrayList<>();
|
private static final String TAG = "InventoryFragment";
|
||||||
private List<Inventory> filteredList = new ArrayList<>();
|
private static final int PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
private final List<InventoryDTO> inventoryList = new ArrayList<>();
|
||||||
|
private final List<CategoryDTO> categoryList = new ArrayList<>();
|
||||||
private InventoryAdapter adapter;
|
private InventoryAdapter adapter;
|
||||||
|
private InventoryApi inventoryApi;
|
||||||
|
private CategoryApi categoryApi;
|
||||||
|
|
||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
|
private Spinner spinnerCategory;
|
||||||
private ImageButton hamburger;
|
private ImageButton hamburger;
|
||||||
|
private Button btnBulkDelete;
|
||||||
|
private TextView tvSelectionCount;
|
||||||
|
|
||||||
|
// Debounce search
|
||||||
|
private final Handler searchHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private Runnable searchRunnable;
|
||||||
|
private String currentQuery = "";
|
||||||
|
|
||||||
|
// Selected category filter — null means "All"
|
||||||
|
private String selectedCategory = null;
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
private int currentPage = 0;
|
||||||
|
private boolean isLastPage = false;
|
||||||
|
private boolean isLoading = false;
|
||||||
|
|
||||||
|
// Prevent spinner from firing on initial load
|
||||||
|
private boolean spinnerReady = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_inventory, container, false);
|
View view = inflater.inflate(R.layout.fragment_inventory, container, false);
|
||||||
|
|
||||||
hamburger = view.findViewById(R.id.btnHamburger);
|
inventoryApi = RetrofitClient.getInventoryApi(requireContext());
|
||||||
|
categoryApi = RetrofitClient.getCategoryApi(requireContext());
|
||||||
|
|
||||||
|
hamburger = view.findViewById(R.id.btnHamburger);
|
||||||
|
btnBulkDelete = view.findViewById(R.id.btnBulkDelete);
|
||||||
|
tvSelectionCount = view.findViewById(R.id.tvSelectionCount);
|
||||||
|
spinnerCategory = view.findViewById(R.id.spinnerCategory);
|
||||||
|
|
||||||
loadInventoryData(); // TODO: Replace with actual API call when backend is ready
|
|
||||||
setupRecyclerView(view);
|
setupRecyclerView(view);
|
||||||
setupSearch(view);
|
setupSearch(view);
|
||||||
setupSwipeRefresh(view);
|
setupSwipeRefresh(view);
|
||||||
|
loadCategories(); // loads categories then triggers loadInventory
|
||||||
|
loadInventory(true);
|
||||||
|
|
||||||
FloatingActionButton fabAddInventory = view.findViewById(R.id.fabAddInventory);
|
view.findViewById(R.id.fabAddInventory)
|
||||||
fabAddInventory.setOnClickListener(v -> openInventoryDetails(-1));
|
.setOnClickListener(v -> openDetail(null));
|
||||||
|
|
||||||
//Make the hamburger button open the drawer from listFragment
|
|
||||||
hamburger.setOnClickListener(v -> {
|
hamburger.setOnClickListener(v -> {
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
//if list fragment is found then use its helper function to open the drawer
|
if (lf != null)
|
||||||
if (listFragment != null) {
|
lf.openDrawer();
|
||||||
listFragment.openDrawer();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
btnBulkDelete.setOnClickListener(v -> confirmBulkDelete());
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filters inventory list by item name or category
|
// Categories
|
||||||
private void setupSearch(View view) {
|
private void loadCategories() {
|
||||||
etSearch = view.findViewById(R.id.etSearchInventory);
|
categoryApi.getAllCategories(0, 100).enqueue(new Callback<PageResponse<CategoryDTO>>() {
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
@Override
|
||||||
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
public void onResponse(Call<PageResponse<CategoryDTO>> call,
|
||||||
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
|
Response<PageResponse<CategoryDTO>> response) {
|
||||||
filterInventory(s.toString());
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
categoryList.clear();
|
||||||
|
categoryList.addAll(response.body().getContent());
|
||||||
|
setupCategorySpinner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<PageResponse<CategoryDTO>> call, Throwable t) {
|
||||||
|
Log.e(TAG, "Failed to load categories", t);
|
||||||
|
// Still setup spinner with just "All"
|
||||||
|
setupCategorySpinner();
|
||||||
}
|
}
|
||||||
@Override public void afterTextChanged(Editable s) {}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filterInventory(String query) {
|
private void setupCategorySpinner() {
|
||||||
filteredList.clear();
|
// First item is always "All Categories"
|
||||||
if (query.isEmpty()) {
|
List<String> categoryNames = new ArrayList<>();
|
||||||
filteredList.addAll(inventoryList);
|
categoryNames.add("All Categories");
|
||||||
|
for (CategoryDTO c : categoryList) {
|
||||||
|
categoryNames.add(c.getCategoryName());
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(
|
||||||
|
requireContext(),
|
||||||
|
android.R.layout.simple_spinner_item,
|
||||||
|
categoryNames);
|
||||||
|
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
spinnerCategory.setAdapter(spinnerAdapter);
|
||||||
|
|
||||||
|
spinnerCategory.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (!spinnerReady) {
|
||||||
|
// Skip the first automatic trigger on setup
|
||||||
|
spinnerReady = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (position == 0) {
|
||||||
|
selectedCategory = null; // "All Categories"
|
||||||
} else {
|
} else {
|
||||||
String lower = query.toLowerCase();
|
selectedCategory = categoryList.get(position - 1).getCategoryName();
|
||||||
for (Inventory i : inventoryList) {
|
}
|
||||||
if (i.getItemName().toLowerCase().contains(lower)
|
loadInventory(true);
|
||||||
|| i.getCategory().toLowerCase().contains(lower)
|
}
|
||||||
|| i.getSupplier().toLowerCase().contains(lower)) {
|
|
||||||
filteredList.add(i);
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search
|
||||||
|
|
||||||
|
private void setupSearch(View view) {
|
||||||
|
etSearch = view.findViewById(R.id.etSearchInventory);
|
||||||
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int i, int i1, int i2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
if (searchRunnable != null)
|
||||||
|
searchHandler.removeCallbacks(searchRunnable);
|
||||||
|
searchRunnable = () -> {
|
||||||
|
currentQuery = s.toString().trim();
|
||||||
|
loadInventory(true);
|
||||||
|
};
|
||||||
|
searchHandler.postDelayed(searchRunnable, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// RecyclerView + infinite scroll
|
||||||
|
private void setupRecyclerView(View view) {
|
||||||
|
RecyclerView rv = view.findViewById(R.id.recyclerViewInventory);
|
||||||
|
adapter = new InventoryAdapter(inventoryList, this);
|
||||||
|
rv.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
rv.setAdapter(adapter);
|
||||||
|
|
||||||
|
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
|
@Override
|
||||||
|
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||||
|
if (dy <= 0)
|
||||||
|
return;
|
||||||
|
LinearLayoutManager lm = (LinearLayoutManager) rv.getLayoutManager();
|
||||||
|
if (lm == null)
|
||||||
|
return;
|
||||||
|
int visible = lm.getChildCount();
|
||||||
|
int total = lm.getItemCount();
|
||||||
|
int firstVis = lm.findFirstVisibleItemPosition();
|
||||||
|
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
||||||
|
loadInventory(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshInventory);
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshInventory);
|
||||||
swipeRefreshLayout.setOnRefreshListener(() -> {
|
swipeRefreshLayout.setOnRefreshListener(() -> loadInventory(true));
|
||||||
loadInventoryData(); // TODO: Replace with actual API call
|
}
|
||||||
filterInventory(etSearch.getText().toString());
|
|
||||||
|
// Load inventory
|
||||||
|
private void loadInventory(boolean reset) {
|
||||||
|
if (isLoading)
|
||||||
|
return;
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
currentPage = 0;
|
||||||
|
isLastPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build query: combine search text + selected category
|
||||||
|
String q = buildQuery();
|
||||||
|
|
||||||
|
inventoryApi.getAllInventory(q, currentPage, PAGE_SIZE, "inventoryId,asc")
|
||||||
|
.enqueue(new Callback<PageResponse<InventoryDTO>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<PageResponse<InventoryDTO>> call,
|
||||||
|
Response<PageResponse<InventoryDTO>> response) {
|
||||||
|
isLoading = false;
|
||||||
|
if (swipeRefreshLayout != null)
|
||||||
swipeRefreshLayout.setRefreshing(false);
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openInventoryDetails(int position) {
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
InventoryDetailFragment detailFragment = new InventoryDetailFragment();
|
PageResponse<InventoryDTO> page = response.body();
|
||||||
Bundle args = new Bundle();
|
if (reset)
|
||||||
args.putInt("position", position);
|
inventoryList.clear();
|
||||||
|
inventoryList.addAll(page.getContent());
|
||||||
if (position != -1) {
|
adapter.notifyDataSetChanged();
|
||||||
Inventory inventory = filteredList.get(position);
|
isLastPage = page.isLast();
|
||||||
int realPosition = inventoryList.indexOf(inventory);
|
if (!isLastPage)
|
||||||
args.putInt("position", realPosition);
|
currentPage++;
|
||||||
args.putInt("inventoryId", inventory.getInventoryId());
|
|
||||||
args.putString("itemName", inventory.getItemName());
|
|
||||||
args.putString("category", inventory.getCategory());
|
|
||||||
args.putInt("quantity", inventory.getQuantity());
|
|
||||||
args.putDouble("unitPrice", inventory.getUnitPrice());
|
|
||||||
args.putString("supplier", inventory.getSupplier());
|
|
||||||
}
|
|
||||||
|
|
||||||
detailFragment.setArguments(args);
|
|
||||||
detailFragment.setInventoryFragment(this);
|
|
||||||
|
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
|
||||||
if (listFragment != null) listFragment.loadFragment(detailFragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onInventorySaved(int position, Inventory inventory) {
|
|
||||||
if (position == -1) {
|
|
||||||
inventoryList.add(inventory);
|
|
||||||
} else {
|
} else {
|
||||||
inventoryList.set(position, inventory);
|
Log.e(TAG, "Error " + response.code());
|
||||||
|
Toast.makeText(getContext(), "Failed to load inventory", Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
filterInventory(etSearch.getText().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onInventoryDeleted(int position) {
|
|
||||||
inventoryList.remove(position);
|
|
||||||
filterInventory(etSearch.getText().toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<PageResponse<InventoryDTO>> call, Throwable t) {
|
||||||
|
isLoading = false;
|
||||||
|
if (swipeRefreshLayout != null)
|
||||||
|
swipeRefreshLayout.setRefreshing(false);
|
||||||
|
Log.e(TAG, "Network error", t);
|
||||||
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combines search text and category into one query string for ?q=
|
||||||
|
private String buildQuery() {
|
||||||
|
String q = null;
|
||||||
|
if (!currentQuery.isEmpty() && selectedCategory != null) {
|
||||||
|
// Both active — prioritize search text, category acts as context
|
||||||
|
q = currentQuery;
|
||||||
|
} else if (!currentQuery.isEmpty()) {
|
||||||
|
q = currentQuery;
|
||||||
|
} else if (selectedCategory != null) {
|
||||||
|
q = selectedCategory;
|
||||||
|
}
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk delete
|
||||||
|
private void confirmBulkDelete() {
|
||||||
|
List<Long> ids = adapter.getSelectedIds();
|
||||||
|
if (ids.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Delete " + ids.size() + " item(s)?")
|
||||||
|
.setMessage("This cannot be undone.")
|
||||||
|
.setPositiveButton("Delete", (d, w) -> bulkDelete(ids))
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bulkDelete(List<Long> ids) {
|
||||||
|
inventoryApi.bulkDeleteInventory(new BulkDeleteRequest(ids))
|
||||||
|
.enqueue(new Callback<Void>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Void> call, Response<Void> response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
adapter.clearSelection();
|
||||||
|
hideBulkDeleteBar();
|
||||||
|
loadInventory(true);
|
||||||
|
Toast.makeText(getContext(), ids.size() + " item(s) deleted", Toast.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), "Delete failed", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Void> call, Throwable t) {
|
||||||
|
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideBulkDeleteBar() {
|
||||||
|
if (btnBulkDelete != null)
|
||||||
|
btnBulkDelete.setVisibility(View.GONE);
|
||||||
|
if (tvSelectionCount != null)
|
||||||
|
tvSelectionCount.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
private void openDetail(InventoryDTO inv) {
|
||||||
|
InventoryDetailFragment detail = new InventoryDetailFragment();
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
|
if (inv != null) {
|
||||||
|
args.putLong("inventoryId", inv.getInventoryId());
|
||||||
|
args.putLong("prodId", inv.getProdId() != null ? inv.getProdId() : -1);
|
||||||
|
args.putString("productName", inv.getProductName());
|
||||||
|
args.putString("categoryName", inv.getCategoryName());
|
||||||
|
args.putInt("quantity", inv.getQuantity() != null ? inv.getQuantity() : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
detail.setArguments(args);
|
||||||
|
detail.setInventoryFragment(this);
|
||||||
|
|
||||||
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
|
if (lf != null)
|
||||||
|
lf.loadFragment(detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onInventoryChanged() {
|
||||||
|
loadInventory(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapter callbacks
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInventoryClick(int position) {
|
public void onInventoryClick(int position) {
|
||||||
openInventoryDetails(position);
|
if (position >= 0 && position < inventoryList.size()) {
|
||||||
|
openDetail(inventoryList.get(position));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadInventoryData() {
|
@Override
|
||||||
inventoryList.clear();
|
public void onSelectionChanged(int selectedCount) {
|
||||||
inventoryList.add(new Inventory(1, "Dog Food - Large", "Food", 50, 25.99, "PetSupplies Co."));
|
if (selectedCount > 0) {
|
||||||
inventoryList.add(new Inventory(2, "Cat Litter", "Hygiene", 30, 12.99, "CleanPaws Ltd."));
|
btnBulkDelete.setVisibility(View.VISIBLE);
|
||||||
inventoryList.add(new Inventory(3, "Dog Leash", "Accessories", 4, 15.99, "PetGear Inc."));
|
tvSelectionCount.setVisibility(View.VISIBLE);
|
||||||
inventoryList.add(new Inventory(4, "Bird Cage - Medium", "Housing", 8, 79.99, "BirdWorld"));
|
tvSelectionCount.setText(selectedCount + " selected");
|
||||||
inventoryList.add(new Inventory(5, "Flea Treatment", "Medicine", 2, 34.99, "VetCare Supply"));
|
} else {
|
||||||
filteredList.clear();
|
hideBulkDeleteBar();
|
||||||
filteredList.addAll(inventoryList);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void setupRecyclerView(View view) {
|
|
||||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewInventory);
|
|
||||||
adapter = new InventoryAdapter(filteredList, this);
|
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
|
||||||
recyclerView.setAdapter(adapter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,34 +1,65 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
// Uses InputValidator for detailed field validation and ActivityLogger to log all changes.
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.fragment.app.Fragment;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.AutoCompleteTextView;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
|
import com.example.petstoremobile.api.InventoryApi;
|
||||||
|
import com.example.petstoremobile.api.ProductApi;
|
||||||
|
import com.example.petstoremobile.api.RetrofitClient;
|
||||||
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.InventoryRequest;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.fragments.ListFragment;
|
||||||
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
||||||
import com.example.petstoremobile.models.Inventory;
|
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import java.util.ArrayList;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import java.util.List;
|
||||||
|
|
||||||
|
import retrofit2.Call;
|
||||||
|
import retrofit2.Callback;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
public class InventoryDetailFragment extends Fragment {
|
public class InventoryDetailFragment extends Fragment {
|
||||||
|
|
||||||
private TextView tvMode, tvInventoryId;
|
private TextView tvMode, tvInventoryId, tvProductInfo;
|
||||||
private EditText etItemName, etCategory, etQuantity, etUnitPrice, etSupplier;
|
private AutoCompleteTextView etProductSearch;
|
||||||
private Button btnSaveInventory, btnDeleteInventory, btnBack;
|
private android.widget.EditText etQuantity;
|
||||||
private int inventoryId;
|
private Button btnSave, btnDelete, btnBack;
|
||||||
private int position;
|
|
||||||
private boolean isEditing = false;
|
private InventoryApi inventoryApi;
|
||||||
|
private ProductApi productApi;
|
||||||
private InventoryFragment inventoryFragment;
|
private InventoryFragment inventoryFragment;
|
||||||
|
|
||||||
// Set the inventory fragment as parent so we refer back when save or delete is done
|
private boolean isEditing = false;
|
||||||
|
private long inventoryId = -1;
|
||||||
|
|
||||||
|
// The product selected from the dropdown
|
||||||
|
private ProductDTO selectedProduct = null;
|
||||||
|
|
||||||
|
// For debouncing product search
|
||||||
|
private final Handler searchHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private Runnable searchRunnable;
|
||||||
|
|
||||||
|
// Dropdown list
|
||||||
|
private final List<ProductDTO> productSuggestions = new ArrayList<>();
|
||||||
|
private ArrayAdapter<String> dropdownAdapter;
|
||||||
|
|
||||||
public void setInventoryFragment(InventoryFragment fragment) {
|
public void setInventoryFragment(InventoryFragment fragment) {
|
||||||
this.inventoryFragment = fragment;
|
this.inventoryFragment = fragment;
|
||||||
}
|
}
|
||||||
@@ -38,102 +69,271 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.fragment_inventory_detail, container, false);
|
View view = inflater.inflate(R.layout.fragment_inventory_detail, container, false);
|
||||||
|
|
||||||
|
inventoryApi = RetrofitClient.getInventoryApi(requireContext());
|
||||||
|
productApi = RetrofitClient.getProductApi(requireContext());
|
||||||
|
|
||||||
initViews(view);
|
initViews(view);
|
||||||
|
setupProductSearch();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
btnBack.setOnClickListener(v -> {
|
btnBack.setOnClickListener(v -> navigateBack());
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
btnSave.setOnClickListener(v -> saveInventory());
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||||
});
|
|
||||||
btnSaveInventory.setOnClickListener(v -> saveInventory());
|
|
||||||
btnDeleteInventory.setOnClickListener(v -> deleteInventory());
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validates all fields using InputValidator, then saves the inventory item
|
|
||||||
private void saveInventory() {
|
|
||||||
if (!InputValidator.isNotEmpty(etItemName, "Item Name")) return;
|
|
||||||
if (!InputValidator.isNotEmpty(etCategory, "Category")) return;
|
|
||||||
if (!InputValidator.isPositiveInteger(etQuantity, "Quantity")) return;
|
|
||||||
if (!InputValidator.isPositiveDecimal(etUnitPrice, "Unit Price")) return;
|
|
||||||
if (!InputValidator.isNotEmpty(etSupplier, "Supplier")) return;
|
|
||||||
|
|
||||||
String itemName = etItemName.getText().toString().trim();
|
|
||||||
String category = etCategory.getText().toString().trim();
|
|
||||||
int quantity = Integer.parseInt(etQuantity.getText().toString().trim());
|
|
||||||
double unitPrice = Double.parseDouble(etUnitPrice.getText().toString().trim());
|
|
||||||
String supplier = etSupplier.getText().toString().trim();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (isEditing) {
|
|
||||||
// TODO: Replace with actual API PUT call when backend is ready
|
|
||||||
Inventory updated = new Inventory(inventoryId, itemName, category, quantity, unitPrice, supplier);
|
|
||||||
if (inventoryFragment != null) inventoryFragment.onInventorySaved(position, updated);
|
|
||||||
ActivityLogger.logChange(requireContext(), "Inventory", "UPDATED", inventoryId);
|
|
||||||
Toast.makeText(getContext(), "Inventory item updated.", Toast.LENGTH_SHORT).show();
|
|
||||||
} else {
|
|
||||||
// TODO: Replace with actual API POST call when backend is ready
|
|
||||||
Inventory newItem = new Inventory(0, itemName, category, quantity, unitPrice, supplier);
|
|
||||||
if (inventoryFragment != null) inventoryFragment.onInventorySaved(-1, newItem);
|
|
||||||
ActivityLogger.log(requireContext(), "Added new Inventory item: " + itemName);
|
|
||||||
Toast.makeText(getContext(), "Inventory item added.", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
|
||||||
} catch (Exception e) {
|
|
||||||
ActivityLogger.logException(requireContext(), "InventoryDetailFragment.saveInventory", e);
|
|
||||||
Toast.makeText(getContext(), "Error saving inventory item.", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deletes the inventory item and logs the action
|
|
||||||
private void deleteInventory() {
|
|
||||||
try {
|
|
||||||
// TODO: Replace with actual API DELETE call when backend is ready
|
|
||||||
if (inventoryFragment != null) inventoryFragment.onInventoryDeleted(position);
|
|
||||||
ActivityLogger.logChange(requireContext(), "Inventory", "DELETED", inventoryId);
|
|
||||||
Toast.makeText(getContext(), "Inventory item deleted.", Toast.LENGTH_SHORT).show();
|
|
||||||
ListFragment listFragment = (ListFragment) getParentFragment();
|
|
||||||
if (listFragment != null) listFragment.getChildFragmentManager().popBackStack();
|
|
||||||
} catch (Exception e) {
|
|
||||||
ActivityLogger.logException(requireContext(), "InventoryDetailFragment.deleteInventory", e);
|
|
||||||
Toast.makeText(getContext(), "Error deleting inventory item.", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleArguments() {
|
|
||||||
if (getArguments() != null && getArguments().containsKey("inventoryId")) {
|
|
||||||
isEditing = true;
|
|
||||||
inventoryId = getArguments().getInt("inventoryId");
|
|
||||||
position = getArguments().getInt("position");
|
|
||||||
tvMode.setText("Edit Inventory Item");
|
|
||||||
tvInventoryId.setText("ID: " + inventoryId);
|
|
||||||
etItemName.setText(getArguments().getString("itemName"));
|
|
||||||
etCategory.setText(getArguments().getString("category"));
|
|
||||||
etQuantity.setText(String.valueOf(getArguments().getInt("quantity")));
|
|
||||||
etUnitPrice.setText(String.valueOf(getArguments().getDouble("unitPrice")));
|
|
||||||
etSupplier.setText(getArguments().getString("supplier"));
|
|
||||||
btnDeleteInventory.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
isEditing = false;
|
|
||||||
tvMode.setText("Add Inventory Item");
|
|
||||||
tvInventoryId.setVisibility(View.GONE);
|
|
||||||
btnDeleteInventory.setVisibility(View.GONE);
|
|
||||||
btnSaveInventory.setText("Add");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initViews(View view) {
|
private void initViews(View view) {
|
||||||
tvMode = view.findViewById(R.id.tvInventoryMode);
|
tvMode = view.findViewById(R.id.tvInventoryMode);
|
||||||
tvInventoryId = view.findViewById(R.id.tvInventoryId);
|
tvInventoryId = view.findViewById(R.id.tvInventoryId);
|
||||||
etItemName = view.findViewById(R.id.etItemName);
|
tvProductInfo = view.findViewById(R.id.tvProductInfo);
|
||||||
etCategory = view.findViewById(R.id.etInventoryCategory);
|
etProductSearch = view.findViewById(R.id.etProductSearch);
|
||||||
etQuantity = view.findViewById(R.id.etQuantity);
|
etQuantity = view.findViewById(R.id.etQuantity);
|
||||||
etUnitPrice = view.findViewById(R.id.etUnitPrice);
|
btnSave = view.findViewById(R.id.btnSaveInventory);
|
||||||
etSupplier = view.findViewById(R.id.etInventorySupplier);
|
btnDelete = view.findViewById(R.id.btnDeleteInventory);
|
||||||
btnSaveInventory = view.findViewById(R.id.btnSaveInventory);
|
|
||||||
btnDeleteInventory = view.findViewById(R.id.btnDeleteInventory);
|
|
||||||
btnBack = view.findViewById(R.id.btnInventoryBack);
|
btnBack = view.findViewById(R.id.btnInventoryBack);
|
||||||
|
|
||||||
|
// Setup dropdown adapter
|
||||||
|
dropdownAdapter = new ArrayAdapter<>(requireContext(),
|
||||||
|
android.R.layout.simple_dropdown_item_1line, new ArrayList<>());
|
||||||
|
etProductSearch.setAdapter(dropdownAdapter);
|
||||||
|
etProductSearch.setThreshold(1); // start showing after 1 character
|
||||||
|
}
|
||||||
|
|
||||||
|
// Product search dropdown
|
||||||
|
private void setupProductSearch() {
|
||||||
|
etProductSearch.addTextChangedListener(new TextWatcher() {
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(CharSequence s, int i, int i1, int i2) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(Editable s) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||||
|
// Clear selected product when user is typing again
|
||||||
|
selectedProduct = null;
|
||||||
|
tvProductInfo.setVisibility(View.GONE);
|
||||||
|
|
||||||
|
if (searchRunnable != null)
|
||||||
|
searchHandler.removeCallbacks(searchRunnable);
|
||||||
|
String query = s.toString().trim();
|
||||||
|
if (query.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
searchRunnable = () -> searchProducts(query);
|
||||||
|
searchHandler.postDelayed(searchRunnable, 400);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When user picks an item from the dropdown
|
||||||
|
etProductSearch.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
|
if (position < productSuggestions.size()) {
|
||||||
|
selectedProduct = productSuggestions.get(position);
|
||||||
|
// Show product details below the search box
|
||||||
|
tvProductInfo.setText(
|
||||||
|
"ID: " + selectedProduct.getProdId()
|
||||||
|
+ " • " + selectedProduct.getCategoryName());
|
||||||
|
tvProductInfo.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void searchProducts(String query) {
|
||||||
|
productApi.getAllProducts(query, 0, 20).enqueue(new Callback<PageResponse<ProductDTO>>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<PageResponse<ProductDTO>> call,
|
||||||
|
Response<PageResponse<ProductDTO>> response) {
|
||||||
|
if (response.isSuccessful() && response.body() != null) {
|
||||||
|
productSuggestions.clear();
|
||||||
|
productSuggestions.addAll(response.body().getContent());
|
||||||
|
|
||||||
|
// Build display strings: "Product Name (ID: X)"
|
||||||
|
List<String> names = new ArrayList<>();
|
||||||
|
for (ProductDTO p : productSuggestions) {
|
||||||
|
names.add(p.getProdName() + " (ID: " + p.getProdId() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdownAdapter.clear();
|
||||||
|
dropdownAdapter.addAll(names);
|
||||||
|
dropdownAdapter.notifyDataSetChanged();
|
||||||
|
etProductSearch.showDropDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<PageResponse<ProductDTO>> call, Throwable t) {
|
||||||
|
Toast.makeText(getContext(), "Failed to load products", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arguments (edit mode)
|
||||||
|
|
||||||
|
private void handleArguments() {
|
||||||
|
Bundle args = getArguments();
|
||||||
|
if (args != null && args.containsKey("inventoryId")) {
|
||||||
|
isEditing = true;
|
||||||
|
inventoryId = args.getLong("inventoryId");
|
||||||
|
|
||||||
|
tvMode.setText("Edit Inventory");
|
||||||
|
tvInventoryId.setText("Inventory ID: " + inventoryId);
|
||||||
|
tvInventoryId.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Pre-fill search box with existing product name
|
||||||
|
String productName = args.getString("productName", "");
|
||||||
|
long prodId = args.getLong("prodId", -1);
|
||||||
|
etProductSearch.setText(productName);
|
||||||
|
|
||||||
|
// Show existing product info
|
||||||
|
if (prodId != -1) {
|
||||||
|
tvProductInfo.setText(
|
||||||
|
"ID: " + prodId
|
||||||
|
+ " • " + args.getString("categoryName", ""));
|
||||||
|
tvProductInfo.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// Build a minimal ProductDTO so selectedProduct is not null on save
|
||||||
|
selectedProduct = new ProductDTO(productName, null, null, null);
|
||||||
|
selectedProduct.setProdId(prodId);
|
||||||
|
}
|
||||||
|
|
||||||
|
etQuantity.setText(String.valueOf(args.getInt("quantity", 0)));
|
||||||
|
btnDelete.setVisibility(View.VISIBLE);
|
||||||
|
btnSave.setText("Save");
|
||||||
|
} else {
|
||||||
|
isEditing = false;
|
||||||
|
tvMode.setText("Add Inventory");
|
||||||
|
tvInventoryId.setVisibility(View.GONE);
|
||||||
|
tvProductInfo.setVisibility(View.GONE);
|
||||||
|
btnDelete.setVisibility(View.GONE);
|
||||||
|
btnSave.setText("Add");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
private void saveInventory() {
|
||||||
|
if (selectedProduct == null) {
|
||||||
|
etProductSearch.setError("Please select a product from the list");
|
||||||
|
etProductSearch.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String quantityStr = etQuantity.getText().toString().trim();
|
||||||
|
if (quantityStr.isEmpty()) {
|
||||||
|
etQuantity.setError("Quantity is required");
|
||||||
|
etQuantity.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int quantity;
|
||||||
|
try {
|
||||||
|
quantity = Integer.parseInt(quantityStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
etQuantity.setError("Invalid quantity");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quantity < 0) {
|
||||||
|
etQuantity.setError("Quantity must be 0 or more");
|
||||||
|
etQuantity.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InventoryRequest request = new InventoryRequest(selectedProduct.getProdId(), quantity);
|
||||||
|
setButtonsEnabled(false);
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
inventoryApi.updateInventory(inventoryId, request).enqueue(new Callback<InventoryDTO>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<InventoryDTO> call, Response<InventoryDTO> response) {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
Toast.makeText(getContext(), "Inventory updated", Toast.LENGTH_SHORT).show();
|
||||||
|
notifyParentAndGoBack();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), "Update failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<InventoryDTO> call, Throwable t) {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
inventoryApi.createInventory(request).enqueue(new Callback<InventoryDTO>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<InventoryDTO> call, Response<InventoryDTO> response) {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
Toast.makeText(getContext(), "Inventory created", Toast.LENGTH_SHORT).show();
|
||||||
|
notifyParentAndGoBack();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), "Create failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<InventoryDTO> call, Throwable t) {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
private void confirmDelete() {
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle("Delete inventory item?")
|
||||||
|
.setMessage("This cannot be undone.")
|
||||||
|
.setPositiveButton("Delete", (d, w) -> deleteInventory())
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteInventory() {
|
||||||
|
setButtonsEnabled(false);
|
||||||
|
inventoryApi.deleteInventory(inventoryId).enqueue(new Callback<Void>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Call<Void> call, Response<Void> response) {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
|
||||||
|
notifyParentAndGoBack();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), "Delete failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Call<Void> call, Throwable t) {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
Toast.makeText(getContext(), "Network error", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
private void notifyParentAndGoBack() {
|
||||||
|
if (inventoryFragment != null)
|
||||||
|
inventoryFragment.onInventoryChanged();
|
||||||
|
navigateBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void navigateBack() {
|
||||||
|
ListFragment lf = (ListFragment) getParentFragment();
|
||||||
|
if (lf != null)
|
||||||
|
lf.getChildFragmentManager().popBackStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setButtonsEnabled(boolean enabled) {
|
||||||
|
btnSave.setEnabled(enabled);
|
||||||
|
btnDelete.setEnabled(enabled);
|
||||||
|
btnBack.setEnabled(enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/header"
|
android:id="@+id/header"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -38,12 +39,15 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Search bar -->
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etSearchInventory"
|
android:id="@+id/etSearchInventory"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:hint="Search by item name or category..."
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:hint="Search by product or category…"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:drawableStart="@android:drawable/ic_menu_search"
|
android:drawableStart="@android:drawable/ic_menu_search"
|
||||||
android:drawablePadding="8dp"
|
android:drawablePadding="8dp"
|
||||||
@@ -51,6 +55,51 @@
|
|||||||
android:padding="12dp"
|
android:padding="12dp"
|
||||||
android:textColor="@color/text_dark"/>
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
<!-- Category filter dropdown -->
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerCategory"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:background="@android:color/white"
|
||||||
|
android:padding="4dp"/>
|
||||||
|
|
||||||
|
<!-- Bulk-delete action bar (hidden until long-press) -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@color/primary_medium"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="8dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="4dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSelectionCount"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="0 selected"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnBulkDelete"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Delete Selected"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Inventory list -->
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:id="@+id/swipeRefreshInventory"
|
android:id="@+id/swipeRefreshInventory"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -60,7 +109,8 @@
|
|||||||
android:id="@+id/recyclerViewInventory"
|
android:id="@+id/recyclerViewInventory"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="8dp"/>
|
android:padding="8dp"
|
||||||
|
android:clipToPadding="false"/>
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
@@ -73,7 +123,7 @@
|
|||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
android:backgroundTint="@color/accent_coral"
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:contentDescription="Add Inventory Item"
|
android:contentDescription="Add Inventory"
|
||||||
app:srcCompat="@android:drawable/ic_input_add"
|
app:srcCompat="@android:drawable/ic_input_add"
|
||||||
app:tint="@color/white"/>
|
app:tint="@color/white"/>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@color/background_grey">
|
android:background="@color/background_grey">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="56dp"
|
||||||
@@ -19,7 +20,7 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="Add Inventory Item"
|
android:text="Add Inventory"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"/>
|
||||||
@@ -28,10 +29,10 @@
|
|||||||
android:id="@+id/btnDeleteInventory"
|
android:id="@+id/btnDeleteInventory"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:text="Delete"
|
android:text="Delete"
|
||||||
android:textColor="@color/white" />
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -51,52 +52,51 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:background="@drawable/rounded_card"
|
||||||
android:padding="16dp"
|
android:padding="16dp">
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
|
<!-- Inventory ID — edit mode only -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvInventoryId"
|
android:id="@+id/tvInventoryId"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="ID: #0"
|
android:text="Inventory ID: —"
|
||||||
android:textColor="@color/text_light"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="11sp"
|
android:textSize="11sp"
|
||||||
android:textStyle="italic"
|
android:textStyle="italic"
|
||||||
android:layout_gravity="end"
|
android:layout_gravity="end"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:layout_marginBottom="12dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<!-- Product search label -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Item Name"
|
android:text="Product"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<!-- AutoComplete search box -->
|
||||||
android:id="@+id/etItemName"
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/etProductSearch"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter item name"
|
android:hint="Search product name…"
|
||||||
android:inputType="text"
|
android:inputType="text"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:completionThreshold="1"
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Category"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<!-- Selected product info (ID + category) shown after picking -->
|
||||||
android:id="@+id/etInventoryCategory"
|
<TextView
|
||||||
|
android:id="@+id/tvProductInfo"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="e.g. Food, Toys, Medicine"
|
android:textColor="#888888"
|
||||||
android:inputType="text"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<!-- Quantity label -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -105,44 +105,13 @@
|
|||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<!-- Quantity input -->
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/etQuantity"
|
android:id="@+id/etQuantity"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter quantity"
|
android:hint="Enter quantity"
|
||||||
android:inputType="number"
|
android:inputType="number"/>
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Unit Price"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/etUnitPrice"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="Enter unit price"
|
|
||||||
android:inputType="numberDecimal"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Supplier"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/etInventorySupplier"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="Enter supplier name"
|
|
||||||
android:inputType="text"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -150,6 +119,7 @@
|
|||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- Bottom buttons -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@@ -2,80 +2,84 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="horizontal"
|
||||||
|
android:background="@color/white"
|
||||||
android:paddingStart="16dp"
|
android:paddingStart="16dp"
|
||||||
android:paddingEnd="16dp"
|
android:paddingEnd="16dp"
|
||||||
android:paddingTop="16dp"
|
android:paddingTop="12dp"
|
||||||
android:paddingBottom="16dp"
|
android:paddingBottom="12dp">
|
||||||
android:background="@color/white">
|
|
||||||
|
<!-- Checkbox (visible only in bulk-delete selection mode) -->
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/cbSelectInventory"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical">
|
||||||
android:gravity="center_vertical">
|
|
||||||
|
|
||||||
|
<!-- Row 1: Product Name (most prominent — like desktop table header) -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvItemName"
|
android:id="@+id/tvProductName"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:text="Item Name"
|
android:text="Product Name"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="18sp"
|
android:textSize="17sp"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
<TextView
|
<!-- Row 2: Inventory ID | Product ID -->
|
||||||
android:id="@+id/tvUnitPrice"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="$0.00"
|
|
||||||
android:textColor="@color/accent_coral"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvCategory"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:text="Category"
|
|
||||||
android:textColor="#888888"
|
|
||||||
android:textSize="13sp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical"
|
android:layout_marginTop="4dp">
|
||||||
android:layout_marginTop="8dp">
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvQuantity"
|
android:id="@+id/tvInventoryId"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="Qty: 0"
|
android:text="Inv ID: —"
|
||||||
android:textSize="13sp" />
|
android:textColor="#888888"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvInvSupplier"
|
android:id="@+id/tvProdId"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Supplier: "
|
android:layout_weight="1"
|
||||||
|
android:text="Prod ID: —"
|
||||||
android:textColor="#888888"
|
android:textColor="#888888"
|
||||||
android:textSize="13sp" />
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Row 3: Quantity -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvQuantity"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="0"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="#F0F0F0"
|
android:background="#F0F0F0"
|
||||||
android:layout_marginTop="12dp"/>
|
android:layout_marginTop="10dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
Reference in New Issue
Block a user