"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { ChevronLeft, ChevronRight, Package, Pencil, Plus, Search, Tag, Trash2 } from "lucide-react"; import { BrandFormDialog, type BrandRow } from "@/components/product-profile/brand-form-dialog"; import { ProductItemFormDialog, type ProductItemRow, } from "@/components/product-profile/product-item-form-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { ConfirmDialog } from "@/components/ui/confirm-dialog"; import { EmptyState } from "@/components/layout/empty-state"; import { PageHeader } from "@/components/layout/page-header"; import { InlineAlert } from "@/components/ui/inline-alert"; import { Input } from "@/components/ui/input"; import { summarizeProductContext } from "@/lib/types/product-context"; import { useActionFeedback } from "@/lib/use-action-feedback"; import { parseFetchJson } from "@/lib/utils"; const BRANDS_PER_PAGE = 8; const PRODUCTS_PREVIEW = 6; interface BrandWithProducts extends BrandRow { products: ProductItemRow[]; productCount?: number; } interface BrandsResponse { brands?: BrandWithProducts[]; total?: number; page?: number; totalPages?: number; error?: string; } export default function ProductsPage() { const [brands, setBrands] = useState([]); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [total, setTotal] = useState(0); const [totalPages, setTotalPages] = useState(1); const [searchInput, setSearchInput] = useState(""); const [searchQuery, setSearchQuery] = useState(""); const [expandedBrands, setExpandedBrands] = useState>(new Set()); const [fullProducts, setFullProducts] = useState>({}); const [loadingProducts, setLoadingProducts] = useState(null); const [brandDialogOpen, setBrandDialogOpen] = useState(false); const [editingBrand, setEditingBrand] = useState(null); const [productDialogOpen, setProductDialogOpen] = useState(false); const [productBrand, setProductBrand] = useState(null); const [editingProduct, setEditingProduct] = useState(null); const [deletingBrandId, setDeletingBrandId] = useState(null); const [deletingProductId, setDeletingProductId] = useState(null); const [confirmDialog, setConfirmDialog] = useState<{ title: string; description?: string; onConfirm: () => void | Promise; } | null>(null); const { feedback, clearFeedback, showError, showSuccess } = useActionFeedback(); const load = useCallback(async () => { setLoading(true); const params = new URLSearchParams({ page: String(page), limit: String(BRANDS_PER_PAGE), productLimit: "12", }); if (searchQuery) params.set("q", searchQuery); const res = await fetch(`/api/brands?${params}`); try { const data = await parseFetchJson(res); if (!res.ok) { showError(data.error ?? "無法載入品牌列表", "讀取失敗"); setBrands([]); setTotal(0); setTotalPages(1); } else { setBrands(data.brands ?? []); setTotal(data.total ?? 0); setTotalPages(data.totalPages ?? 1); } } catch (err) { showError(err instanceof Error ? err.message : "伺服器回應異常", "讀取失敗"); setBrands([]); setTotal(0); setTotalPages(1); } setLoading(false); }, [page, searchQuery, showError]); useEffect(() => { load(); }, [load]); useEffect(() => { const timer = window.setTimeout(() => { setSearchQuery(searchInput.trim()); setPage(1); }, 300); return () => window.clearTimeout(timer); }, [searchInput]); const summaryText = useMemo(() => { if (total === 0) return searchQuery ? "沒有符合的品牌或產品" : "尚無品牌"; if (searchQuery) return `找到 ${total} 個品牌`; return `共 ${total} 個品牌`; }, [total, searchQuery]); function openCreateBrand() { setEditingBrand(null); setBrandDialogOpen(true); } function openEditBrand(brand: BrandRow) { setEditingBrand(brand); setBrandDialogOpen(true); } function openCreateProduct(brand: BrandWithProducts) { setProductBrand(brand); setEditingProduct(null); setProductDialogOpen(true); } function openEditProduct(brand: BrandWithProducts, product: ProductItemRow) { setProductBrand(brand); setEditingProduct(product); setProductDialogOpen(true); } async function loadAllProducts(brandId: string) { setLoadingProducts(brandId); const res = await fetch(`/api/brands?brandId=${brandId}&productLimit=200`); try { const data = await parseFetchJson<{ brand?: BrandWithProducts; error?: string }>(res); if (!res.ok || !data.brand) { showError(data.error ?? "無法載入產品列表", "讀取失敗"); return; } setFullProducts((prev) => ({ ...prev, [brandId]: data.brand!.products })); setExpandedBrands((prev) => new Set(prev).add(brandId)); } catch (err) { showError(err instanceof Error ? err.message : "伺服器回應異常", "讀取失敗"); } finally { setLoadingProducts(null); } } function getVisibleProducts(brand: BrandWithProducts) { const all = fullProducts[brand.id] ?? brand.products; const expanded = expandedBrands.has(brand.id); if (expanded || all.length <= PRODUCTS_PREVIEW) return all; return all.slice(0, PRODUCTS_PREVIEW); } function hasMoreProducts(brand: BrandWithProducts) { const count = brand.productCount ?? brand.products.length; const loaded = fullProducts[brand.id] ?? brand.products; if (expandedBrands.has(brand.id)) return false; return count > loaded.length || loaded.length > PRODUCTS_PREVIEW; } async function handleDeleteBrand(id: string) { setConfirmDialog({ title: "刪除品牌", description: "確定刪除此品牌?若有主題正在使用將無法刪除。", onConfirm: async () => { setDeletingBrandId(id); const res = await fetch(`/api/brands?id=${id}`, { method: "DELETE" }); const data = await res.json(); setDeletingBrandId(null); if (!res.ok) { showError(data.error ?? "無法刪除品牌", "刪除失敗"); return; } showSuccess("品牌已刪除"); if (brands.length === 1 && page > 1) { setPage((p) => p - 1); } else { load(); } }, }); } async function handleDeleteProduct(id: string) { setConfirmDialog({ title: "刪除產品", description: "確定刪除此產品?若有主題指定此產品將無法刪除。", onConfirm: async () => { setDeletingProductId(id); const res = await fetch(`/api/brands/products?id=${id}`, { method: "DELETE" }); const data = await res.json(); setDeletingProductId(null); if (!res.ok) { showError(data.error ?? "無法刪除產品", "刪除失敗"); return; } setFullProducts({}); setExpandedBrands(new Set()); load(); showSuccess("產品已刪除"); }, }); } return (
新增品牌 } /> {feedback && ( )}
setSearchInput(e.target.value)} placeholder="搜尋品牌、產品或標籤…" className="pl-9" />

{summaryText}

{loading ? (
) : brands.length === 0 ? ( setSearchInput("")}> 清除搜尋 ) : ( ) } /> ) : ( <>
{brands.map((brand) => { const visibleProducts = getVisibleProducts(brand); const productTotal = brand.productCount ?? brand.products.length; return (
{brand.name} {brand.notes && ( {brand.notes} )}

{productTotal} 個產品

{visibleProducts.length === 0 ? (

尚無產品,請新增至少一項。

) : (
    {visibleProducts.map((product) => { const summary = summarizeProductContext(product.context); return (
  • {product.label}

    {summary && (

    {summary}

    )} {product.matchTags.length > 0 && (

    {product.matchTags.join("、")}

    )}
  • ); })}
)} {hasMoreProducts(brand) && ( )}
); })}
{totalPages > 1 && (

第 {page} / {totalPages} 頁

)} )} { setFullProducts({}); setExpandedBrands(new Set()); load(); }} /> {productBrand && ( { setFullProducts({}); setExpandedBrands(new Set()); load(); }} /> )} !open && setConfirmDialog(null)} title={confirmDialog?.title ?? ""} description={confirmDialog?.description} confirmText="確認刪除" danger onConfirm={confirmDialog?.onConfirm ?? (() => {})} />
); }