"use client"; import { useMemo, useState } from "react"; import { PageHeader, DataTable, EmptyState, Input, Pagination, Skeleton } from "@/components/ds"; import { Search } from "lucide-react"; import { HospitalityBreadcrumbs } from "@/modules/hospitality/components/HospitalityBreadcrumbs"; export type HospitalityTableColumn = { key: string; header: string; className?: string; render?: (row: Record) => React.ReactNode; searchable?: boolean; sortable?: boolean; defaultVisible?: boolean; }; export function HospitalityTablePage({ title, description, breadcrumb, columns, rows, empty, actions, searchPlaceholder = "جستجو…", filterFn, toolbar, page, pageSize, onPageChange, selectedIds, onSelectionChange, bulkActions, loading, }: { title: string; description?: string; breadcrumb?: string; columns: HospitalityTableColumn[]; rows: Record[]; empty?: React.ReactNode; actions?: React.ReactNode; searchPlaceholder?: string; filterFn?: (row: Record, query: string) => boolean; toolbar?: React.ReactNode; page?: number; pageSize?: number; onPageChange?: (page: number) => void; selectedIds?: Set; onSelectionChange?: (ids: Set) => void; bulkActions?: React.ReactNode; loading?: boolean; }) { const [query, setQuery] = useState(""); const [sortKey, setSortKey] = useState(null); const [sortDir, setSortDir] = useState<"asc" | "desc">("asc"); const [hiddenCols, setHiddenCols] = useState>(() => { const hidden = new Set(); columns.forEach((c) => { if (c.defaultVisible === false) hidden.add(c.key); }); return hidden; }); const visibleColumns = useMemo( () => columns.filter((c) => !hiddenCols.has(c.key)), [columns, hiddenCols] ); const filtered = useMemo(() => { let data = rows; const q = query.trim().toLowerCase(); if (q) { data = filterFn ? data.filter((r) => filterFn(r, q)) : data.filter((r) => visibleColumns.some((c) => { if (c.searchable === false) return false; const val = r[c.key]; return val != null && String(val).toLowerCase().includes(q); }) ); } if (sortKey) { data = [...data].sort((a, b) => { const av = a[sortKey]; const bv = b[sortKey]; const cmp = String(av ?? "").localeCompare(String(bv ?? ""), "fa"); return sortDir === "asc" ? cmp : -cmp; }); } return data; }, [rows, query, visibleColumns, filterFn, sortKey, sortDir]); const paged = useMemo(() => { if (!pageSize || !page) return filtered; const start = (page - 1) * pageSize; return filtered.slice(start, start + pageSize); }, [filtered, page, pageSize]); const displayRows = pageSize && page ? paged : filtered; const toggleSort = (key: string) => { if (sortKey === key) setSortDir((d) => (d === "asc" ? "desc" : "asc")); else { setSortKey(key); setSortDir("asc"); } }; if (loading) { return (
); } return (
{breadcrumb ? : null}
setQuery(e.target.value)} placeholder={searchPlaceholder} className="pr-10" aria-label="جستجو" data-hospitality-search />
{toolbar}
ستون‌ها
{columns .filter((c) => c.key !== "actions") .map((c) => ( ))}
{selectedIds && selectedIds.size > 0 && bulkActions ? (
{selectedIds.size} مورد انتخاب شده {bulkActions}
) : null} ( { const id = String(row.id); const next = new Set(selectedIds); if (e.target.checked) next.add(id); else next.delete(id); onSelectionChange(next); }} aria-label="انتخاب" onClick={(e) => e.stopPropagation()} /> ), }, ...visibleColumns, ] : visibleColumns } rows={displayRows} empty={empty ?? } /> {pageSize && page && onPageChange ? (
) : null}
); } export function HospitalityStatGrid({ stats, }: { stats: { label: string; value: string | number; hint?: string }[]; }) { return (
{stats.map((s) => (

{s.label}

{s.value}

{s.hint ?

{s.hint}

: null}
))}
); }