"use client"; import React, { useState, useTransition, useCallback } from "react"; import Image from "next/image"; import { deleteProduct } from "@/actions/products"; import Link from "next/link"; import { useRouter } from "next/navigation"; type Product = { id: string; name: string; description: string; price: number; type: string; active: boolean; deleted_at?: string | null; image_url?: string | null; brands: { name: string } | { name: string }[]; }; type Props = { products: Product[]; }; export default function ProductTableClient({ products }: Props) { const router = useRouter(); const [, startTransition] = useTransition(); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState<"all" | "active" | "inactive">("all"); const [deleteError, setDeleteError] = useState(null); const filtered = products.filter((p) => { const matchesSearch = !search || p.name.toLowerCase().includes(search.toLowerCase()) || p.description.toLowerCase().includes(search.toLowerCase()); const matchesStatus = statusFilter === "all" || (statusFilter === "active" && p.active) || (statusFilter === "inactive" && !p.active); return matchesSearch && matchesStatus; }); const handleDeleted = useCallback(() => { setDeleteError(null); startTransition(() => { router.refresh(); }); }, [router]); return ( <> {/* Filter bar */}
setSearch(e.target.value)} className="flex-1 min-w-48 rounded-lg border border-stone-200 bg-stone-50 px-3 py-2 text-sm text-stone-900 outline-none focus:border-emerald-500 placeholder:text-stone-400 transition-colors" />
{(["all", "active", "inactive"] as const).map((f) => ( ))}
{filtered.length} products
{/* Delete error */} {deleteError && (
{deleteError}{" "}
)} {/* Table */} {filtered.length === 0 ? ( ) : ( filtered.map((product) => ( )) )}
Product Brand Type Price Status
{search || statusFilter !== "all" ? "No products match your search." : "No products found."}
); } function ProductRowBase({ product, onDeleted, onDeleteError, }: { product: Product; onDeleted: () => void; onDeleteError: (msg: string) => void; }) { const [openMenu, setOpenMenu] = useState(null); const [deletingId, setDeletingId] = useState(null); const [confirmDelete, setConfirmDelete] = useState(null); async function handleDelete(productId: string) { setDeletingId(productId); const result = await deleteProduct(productId, null); setDeletingId(null); setConfirmDelete(null); setOpenMenu(null); if (result.success) { onDeleted(); } else { onDeleteError(result.error ?? "Delete failed"); } } return (
{product.image_url ? (
{product.name} { (e.target as HTMLImageElement).style.display = "none"; }} />
) : (
)}
{product.name}
{product.description || ( No description )}
{Array.isArray(product.brands) ? product.brands[0]?.name : product.brands?.name} {product.type} ${Number(product.price).toFixed(2)} {product.active ? "Active" : "Inactive"}
Edit {openMenu === product.id && ( <>
)} {confirmDelete === product.id && ( <> )} ); } const ProductRow = React.memo(ProductRowBase);