import { useEffect, useState } from "react"; import { ChevronLeft, ChevronRight, Search, Loader2 } from "lucide-react"; import { cn } from "~/lib/utils"; /* ------------------------------ helpers ------------------------------ */ export const faNum = (n?: number | string | null) => n === null || n === undefined || n === "" ? "—" : Number(n).toLocaleString("fa-IR"); export const faToman = (n?: number | null) => n === null || n === undefined ? "—" : `${Math.round(n).toLocaleString("fa-IR")} تومان`; export const faDate = (iso?: string | null) => { if (!iso) return "—"; try { return new Date(iso).toLocaleDateString("fa-IR", { year: "numeric", month: "2-digit", day: "2-digit", }); } catch { return "—"; } }; /** Debounce a rapidly-changing value (e.g. a search box). */ export function useDebouncedValue(value: T, delay = 350): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t); }, [value, delay]); return debounced; } /* ---------------------------- status badge --------------------------- */ type Tone = "green" | "red" | "yellow" | "blue" | "gray"; const TONE: Record = { green: "bg-GREEN/10 text-GREEN", red: "bg-RED/10 text-RED", yellow: "bg-yellow-500/10 text-yellow-600", blue: "bg-VITROWN_BLUE/10 text-VITROWN_BLUE", gray: "bg-WHITE3 text-BLACK2", }; export function StatusBadge({ label, tone }: { label: string; tone: Tone }) { return ( {label} ); } /* ------------------------------- table ------------------------------- */ export interface Column { header: string; render: (row: T) => React.ReactNode; className?: string; } export function AdminTable({ columns, rows, isLoading, getRowKey, emptyText = "موردی یافت نشد", }: { columns: Column[]; rows: T[]; isLoading?: boolean; getRowKey: (row: T) => string; emptyText?: string; }) { return (
{columns.map((c, i) => ( ))} {isLoading ? ( ) : rows.length === 0 ? ( ) : ( rows.map((row) => ( {columns.map((c, i) => ( ))} )) )}
{c.header}
{emptyText}
{c.render(row)}
); } /* ---------------------------- pagination ----------------------------- */ export function AdminPagination({ page, pageSize, count, onPageChange, }: { page: number; pageSize: number; count: number; onPageChange: (page: number) => void; }) { const totalPages = Math.max(1, Math.ceil(count / pageSize)); const from = count === 0 ? 0 : (page - 1) * pageSize + 1; const to = Math.min(count, page * pageSize); const btn = "h-8 w-8 grid place-items-center rounded-lg border border-WHITE3 text-BLACK2 disabled:opacity-40 hover:bg-WHITE2 transition-colors cursor-pointer disabled:cursor-default"; return (
{faNum(from)}–{faNum(to)} از {faNum(count)}
{faNum(page)} / {faNum(totalPages)}
); } /* --------------------------- toolbar bits ---------------------------- */ export function AdminSearch({ value, onChange, placeholder = "جستجو…", }: { value: string; onChange: (v: string) => void; placeholder?: string; }) { return (
onChange(e.target.value)} placeholder={placeholder} className="w-full rounded-xl border border-WHITE3 bg-WHITE pr-9 pl-3 py-2 text-[13px] outline-none focus:border-VITROWN_BLUE" />
); } export function AdminSelect({ value, onChange, options, }: { value: string; onChange: (v: string) => void; options: { value: string; label: string }[]; }) { return ( ); } export function AdminToolbar({ children }: { children: React.ReactNode }) { return
{children}
; } export function AdminPageTitle({ children }: { children: React.ReactNode }) { return (

{children}

); }