"use client"; import { useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Download, RefreshCw, Search } from "lucide-react"; import { Button, Input, Select } from "@/components/ds"; import { useTenantId } from "@/hooks/useTenantId"; import { PaymentStatusChip } from "@/modules/payment/components/PaymentStatusChip"; import { PaymentEmptyState, PaymentPageError, PaymentPageLoader, exportToCsv, } from "@/modules/payment/pages/shared"; import type { PaymentRecord } from "@/modules/payment/types"; type Column = { key: string; header: string; type?: "status" | "datetime" | "text"; render?: (row: PaymentRecord) => React.ReactNode; }; type Props = { title: string; description?: string; resourceKey: string; columns: Column[]; fetcher: (tenantId: string) => Promise; filterKey?: string; detailHref?: (row: PaymentRecord) => string | null; rowActions?: React.ReactNode | ((row: PaymentRecord, refresh: () => void) => React.ReactNode); headerActions?: React.ReactNode; }; const PAGE_SIZE = 10; export function PaymentListPage({ title, description, resourceKey, columns, fetcher, filterKey = "status", detailHref, rowActions, headerActions, }: Props) { const { tenantId } = useTenantId(); const [search, setSearch] = useState(""); const [filter, setFilter] = useState(""); const [sortKey, setSortKey] = useState(columns[0]?.key || "created_at"); const [sortDir, setSortDir] = useState<"asc" | "desc">("desc"); const [page, setPage] = useState(1); const q = useQuery({ queryKey: ["payment", tenantId, resourceKey], queryFn: () => fetcher(tenantId!), enabled: !!tenantId, }); const rows = useMemo(() => { let data = [...(q.data ?? [])]; if (filter) data = data.filter((r) => String(r[filterKey] ?? "") === filter); if (search.trim()) { const s = search.trim().toLowerCase(); data = data.filter((r) => JSON.stringify(r).toLowerCase().includes(s)); } data.sort((a, b) => { const av = String(a[sortKey] ?? ""); const bv = String(b[sortKey] ?? ""); return sortDir === "asc" ? av.localeCompare(bv) : bv.localeCompare(av); }); return data; }, [q.data, filter, filterKey, search, sortKey, sortDir]); const filterOptions = useMemo(() => { const set = new Set((q.data ?? []).map((r) => String(r[filterKey] ?? "")).filter(Boolean)); return Array.from(set); }, [q.data, filterKey]); const totalPages = Math.max(1, Math.ceil(rows.length / PAGE_SIZE)); const pageRows = rows.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE); if (!tenantId || q.isLoading) return ; if (q.isError) return q.refetch()} />; return (

{title}

{description ?

{description}

: null}
{headerActions}
{ setSearch(e.target.value); setPage(1); }} aria-label="جستجو" />
{rows.length === 0 ? ( ) : (
{columns.map((c) => ( ))} {(detailHref || rowActions) && ( )} {pageRows.map((row, idx) => ( {columns.map((c) => ( ))} {(detailHref || rowActions) && ( )} ))}
{c.header} عملیات
{c.render ? c.render(row) : c.type === "status" ? : c.type === "datetime" && row[c.key] ? new Date(String(row[c.key])).toLocaleString("fa-IR") : String(row[c.key] ?? "—")}
{detailHref?.(row) ? ( جزئیات ) : null} {typeof rowActions === "function" ? rowActions(row, () => q.refetch()) : rowActions}
)}
{rows.length} مورد — صفحه {page} از {totalPages}
); }