"use client"; import { useEffect, useMemo, useRef, useState } from "react"; import CatalogCardControls, { type CatalogControlsMode, } from "@/components/catalog/CatalogCardControls"; import CatalogEmptyState from "@/components/catalog/CatalogEmptyState"; import CatalogPagination from "@/components/catalog/CatalogPagination"; import { PRODUCT_PAGE_SIZE, productImage, productSubtitle, } from "@/components/catalog/utils"; import ConfirmDialog from "@/components/ConfirmDialog"; import styles from "@/components/BusinessDetailsPage.module.css"; import { en } from "@/lib/i18n/en"; import { collectCategorySubtreeIds, deleteMeshkeeProduct, listMeshkeeBusinessCategories, listMeshkeeProducts, type MeshkeeBusinessCategory, type MeshkeeProduct, } from "@/services/meshkee-content"; export default function ProductsPanel({ meshkeeId, emptyLabel = en.products.empty, addHref, controlsMode = "edit-remove", onManage, }: { meshkeeId: string; emptyLabel?: string; addHref?: string; controlsMode?: CatalogControlsMode; onManage?: () => void; }) { const sectionRef = useRef(null); const [categories, setCategories] = useState([]); const [parentCategoryId, setParentCategoryId] = useState(null); const [childCategoryId, setChildCategoryId] = useState(null); const [products, setProducts] = useState([]); const [productTotal, setProductTotal] = useState(0); const [productPage, setProductPage] = useState(1); const [productBusy, setProductBusy] = useState(false); const [productCatalog, setProductCatalog] = useState( null, ); const [catalogBusy, setCatalogBusy] = useState(false); const [removing, setRemoving] = useState(null); const [removeBusy, setRemoveBusy] = useState(false); const [error, setError] = useState(""); const rootCategories = useMemo( () => categories.filter((item) => item.parentId == null), [categories], ); const childCategories = useMemo(() => { if (!parentCategoryId) return []; return categories.filter((item) => item.parentId === parentCategoryId); }, [categories, parentCategoryId]); const activeCategoryId = childCategoryId ?? parentCategoryId; const filteredCatalog = useMemo(() => { if (!productCatalog || !activeCategoryId) return productCatalog; const ids = collectCategorySubtreeIds(categories, activeCategoryId); return productCatalog.filter( (item) => item.categoryId && ids.has(item.categoryId), ); }, [productCatalog, activeCategoryId, categories]); const pagedFiltered = useMemo(() => { if (!filteredCatalog) return null; const start = (productPage - 1) * PRODUCT_PAGE_SIZE; return filteredCatalog.slice(start, start + PRODUCT_PAGE_SIZE); }, [filteredCatalog, productPage]); const displayed = activeCategoryId ? (pagedFiltered ?? products) : products; const displayedTotal = activeCategoryId ? (filteredCatalog?.length ?? productTotal) : productTotal; useEffect(() => { let cancelled = false; void (async () => { setProductBusy(true); setError(""); try { const [productsRes, categoriesRes] = await Promise.all([ listMeshkeeProducts(meshkeeId, { page: 1, pageSize: PRODUCT_PAGE_SIZE, }), listMeshkeeBusinessCategories(meshkeeId).catch( () => [] as MeshkeeBusinessCategory[], ), ]); if (cancelled) return; setProducts(productsRes.items); setProductTotal(productsRes.total); setProductPage(1); setCategories(categoriesRes); setProductCatalog(null); setParentCategoryId(null); setChildCategoryId(null); } catch (err) { if (!cancelled) { setError( err instanceof Error ? err.message : "Could not load products.", ); } } finally { if (!cancelled) setProductBusy(false); } })(); return () => { cancelled = true; }; }, [meshkeeId]); function scrollToSection() { const node = sectionRef.current; if (!node) return; requestAnimationFrame(() => { requestAnimationFrame(() => { node.scrollIntoView({ behavior: "smooth", block: "start" }); }); }); } async function ensureCatalog() { if (productCatalog) return productCatalog; setCatalogBusy(true); try { const pageSize = 100; let page = 1; let total = Infinity; const all: MeshkeeProduct[] = []; while (all.length < total) { const res = await listMeshkeeProducts(meshkeeId, { page, pageSize }); total = res.total; all.push(...res.items); if (!res.items.length) break; page += 1; } setProductCatalog(all); return all; } finally { setCatalogBusy(false); } } async function loadPage(page: number) { if (activeCategoryId) { setProductPage(page); scrollToSection(); return; } setProductBusy(true); try { const res = await listMeshkeeProducts(meshkeeId, { page, pageSize: PRODUCT_PAGE_SIZE, }); setProducts(res.items); setProductTotal(res.total); setProductPage(res.page); scrollToSection(); } finally { setProductBusy(false); } } async function selectParent(id: string | null) { setParentCategoryId(id); setChildCategoryId(null); setProductPage(1); if (id) await ensureCatalog(); scrollToSection(); } async function selectChild(id: string | null) { setChildCategoryId(id); setProductPage(1); if (id || parentCategoryId) await ensureCatalog(); scrollToSection(); } async function confirmRemove() { if (!removing) return; setRemoveBusy(true); try { await deleteMeshkeeProduct(meshkeeId, removing.id); setProducts((prev) => prev.filter((item) => item.id !== removing.id)); setProductCatalog((prev) => prev ? prev.filter((item) => item.id !== removing.id) : prev, ); setProductTotal((total) => Math.max(0, total - 1)); setRemoving(null); } catch (err) { window.alert( err instanceof Error ? err.message : "Could not remove product.", ); } finally { setRemoveBusy(false); } } if (error) { return

{error}

; } return (
{productTotal > 0 ? (
{rootCategories.map((item) => ( ))}
) : null} {productTotal > 0 && childCategories.length ? (
{childCategories.map((item) => ( ))}
) : null} {catalogBusy || productBusy ? (

Loading products…

) : null}
{displayed.map((product) => { const image = productImage(product); const subtitle = productSubtitle(product); return (
{image ? ( ) : (
)}

{product.title}

{subtitle ?

{subtitle}

: null}
window.alert(`Edit product coming soon: ${product.title}`) } onRemove={() => setRemoving(product)} />
); })}
{!displayed.length && !catalogBusy && !productBusy ? ( ) : null} void loadPage(page)} /> { if (!removeBusy) setRemoving(null); }} onConfirm={() => { void confirmRemove(); }} />
); }