Default Component - Copy this React, Tailwind Component to your project
import React, { useCallback, useContext, useEffect, useState } from "react"; import { DataContext } from "../../../hooks/UseContext"; import { GetApi } from "../../../utils/GetApi"; import { ConstantUrls } from "../../../constants/ConstantMessages"; import { Pencil, Trash } from "lucide-react"; import toast from "react-hot-toast"; import { useNavigate } from "react-router-dom"; import { Popup } from "../../PopUp/PopUp"; interface Attribute { attribute_id: string; attributes: { attribute_name: string; attribute_value: string; }[]; price: number; stock: number; discount: number; } interface Product { product_id: string; name: string; category: string; product_image_name: string; all_attributes: Attribute[]; product_availability_start_time: string; product_availability_end_time: string; } export const EditAndDeleteProducts: React.FC = () => { const [products, setProducts] = useState<Product[]>([]); const [selectedAttributes, setSelectedAttributes] = useState<{ [productId: string]: { [attributeName: string]: string }; }>({}); const [selectedAttributeVariants, setSelectedAttributeVariants] = useState< Record<string, Attribute> | undefined >({}); const [page, setPage] = useState(1); const [isDeletePopupOpen, setIsDeletePopupOpen] = useState<boolean>(false); const [hasMore, setHasMore] = useState(true); const [isLoading, setIsLoading] = useState(false); const [productIdToDelete, setProductIdToDelete] = useState<string>(""); const { getData, deleteData } = useContext(DataContext); const uid = GetApi().uid; const navigate = useNavigate(); const openPopup = () => setIsDeletePopupOpen(true); const fetchProducts = useCallback(async () => { if (isLoading || !hasMore || !uid) return; setIsLoading(true); try { const response = await getData(`get_products?page=${page}&limit=10`); const data: Product[] = response.data.products || []; if (data.length === 0) { setHasMore(false); return; } const initialSelection: Record<string, Record<string, string>> = {}; const initialVariants: Record<string, Attribute> = {}; data.forEach((product) => { const attributesMap: Record<string, string> = {}; product.all_attributes.forEach((attrGroup) => { attrGroup.attributes.forEach((attr) => { const isWeight = /kg|g|ml|ltr/i.test(attr.attribute_value); const category = isWeight ? "Weight" : attr.attribute_name; if (!attributesMap[category]) { attributesMap[category] = attr.attribute_value; } }); }); const firstVariant = product.all_attributes.find((attrGroup) => attrGroup.attributes.every((a) => { const category = /kg|g|ml|ltr/i.test(a.attribute_value) ? "Weight" : a.attribute_name; return attributesMap[category] === a.attribute_value; }) ); initialSelection[product.product_id] = attributesMap; if (firstVariant) { initialVariants[product.product_id] = firstVariant; } }); setSelectedAttributes((prev) => ({ ...prev, ...initialSelection })); setSelectedAttributeVariants((prev) => ({ ...prev, ...initialVariants })); setProducts((prev) => [...prev, ...data]); setPage((prevPage) => prevPage + 1); } catch (error) { console.error("Error fetching products:", error); } finally { setIsLoading(false); } }, [page, hasMore, uid, isLoading, getData]); useEffect(() => { fetchProducts(); }, [fetchProducts, uid]); const handleEdit = (productId: string) => { const product = products.find((p) => p.product_id === productId); if (!product) { console.error(`❌ Product with ID ${productId} not found.`); toast.error("Failed to edit product. Please try again."); return; } const selectedAttribute = selectedAttributes[productId]; if (!selectedAttribute) { console.error( `❌ No selected attributes found for product ID ${productId}.` ); toast.error("Please select attributes for the product."); return; } const selectedVariant = product.all_attributes.find((attr) => attr.attributes.every( (a) => selectedAttribute[ /kg|g|ml|ltr/i.test(a.attribute_value) ? "Weight" : a.attribute_name ] === a.attribute_value ) ); if (!selectedVariant) { console.error( `❌ No matching variant found for product ID ${productId}.` ); toast.error("Your selected option is not available."); return; } const computedSelectedProduct = { product, selectedAttributes: selectedVariant, price: selectedVariant.price || 0, }; navigate("/edit_products", { state: { from: "home", selectedProduct: computedSelectedProduct, selectedAttributes: selectedAttributeVariants, }, }); fetchProducts(); }; const handleDelete = async (productId: string) => { openPopup(); if (!productId) { console.error("❌ No product ID provided for deletion."); toast.error("Failed to delete product. Please try again."); return; } const validatedProductId = productId; const deletePayload = { product_id: validatedProductId, user_id: uid, }; try { const response = await deleteData("delete_product", deletePayload); console.log("Product Deleted Successfully", response); toast.success("Product deleted successfully"); setProducts((prevProducts) => prevProducts.filter((product) => product.product_id !== productId) ); navigate("/home", { replace: true }); } catch (error) { console.error("Error deleting product:", error); toast.error("Failed to delete product. Please try again."); } finally { setIsDeletePopupOpen(false); } }; return ( <div className="p-6 bg-gray-100 min-h-screen"> {/* Header */} <div className="flex justify-between items-center mb-8"> <h1 className="text-3xl font-bold text-gray-800">Manage Products</h1> </div> {/* Product List */} <div className="space-y-6"> {products.map((product) => ( <div key={product.product_id} className="bg-white p-6 rounded-lg shadow-md flex space-x-6" > {/* Product Image */} <div className="relative w-24 h-24"> <img src={ product.product_image_name ? `${ConstantUrls.ip}/uploads/product_images/${product.product_image_name}` : "/assets/productImages/sampleProduct.webp" } alt={product.name} className="w-full h-full object-cover rounded-md border border-gray-200 transition-transform duration-300 hover:scale-105" /> </div> {/* Product Details */} <div className="flex-1 space-y-2"> <h2 className="text-xl font-bold text-gray-800"> {product.name} </h2> <p className="text-gray-600"> Stock: {product.all_attributes.map((attr: any) => attr.stock)} </p> <p className="text-gray-600">Category: {product.category}</p> </div> {/* Actions */} <div className="flex flex-col space-y-2 justify-center"> {/* Edit Button */} <button onClick={() => handleEdit(product.product_id)} className="bg-green-500 text-white px-4 py-2 rounded-lg shadow-md hover:bg-green-600 transition duration-300 flex justify-center items-center gap-2" > <span> <Pencil size={18} /> </span> <span>Edit</span> </button> {/* Delete Button */} <button onClick={(e) => { e.stopPropagation(); setProductIdToDelete(product.product_id); setIsDeletePopupOpen(true); }} className="bg-red-500 text-white px-4 py-2 rounded-lg shadow-md hover:bg-red-600 transition duration-300 flex justify-center items-center gap-2" > <span> <Trash size={18} /> </span> <span>Delete</span> </button> </div> </div> ))} </div> <Popup isOpen={isDeletePopupOpen} onClose={() => setIsDeletePopupOpen(false)} title="Delete Product" showFooter={true} confirmText="Yes, Delete" cancelText="Cancel" onConfirm={() => handleDelete(productIdToDelete)} onCancel={() => { setIsDeletePopupOpen(false); }} > Are You sure, you want to delete your Product? </Popup> {/* Loading Indicator */} {isLoading && ( <div className="flex justify-center mt-6"> <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500"></div> </div> )} </div> ); }; I need a best rich ui for this code