import { useNavigate, useSearchParams } from 'react-router-dom'; import { ArrowPathIcon } from '@heroicons/react/24/outline'; import PageHeader from '../components/ui/PageHeader'; import Pagination from '../components/ui/Pagination'; import PersianDatePicker from '../components/ui/PersianDatePicker'; import SearchableSelect from '../components/ui/SearchableSelect'; import StatCard from '../components/ui/StatCard'; import StatusBadge from '../components/ui/StatusBadge'; import DataTable, { type Column } from '../components/ui/DataTable'; import { formatRial, formatDate, formatNumber, toDate } from '../lib/utils'; import { usePayments, usePaymentsSummary, MY_PAYMENTS_LIMIT, type PaymentRow, } from '../hooks/useMyPayments'; const STATUS_OPTIONS = [ { value: '', label: 'همه وضعیت‌ها' }, { value: 'paid', label: 'پرداخت شده' }, { value: 'partial', label: 'پرداخت ناقص' }, { value: 'unsettled', label: 'تسویه نشده' }, ]; const isoNDaysAgo = (days: number): string => { const d = new Date(); d.setDate(d.getDate() - days); return d.toISOString().slice(0, 10); }; const todayIso = (): string => new Date().toISOString().slice(0, 10); /** HH:MM (Persian digits) from a unix timestamp. */ function formatTime(unix: number): string { return new Intl.DateTimeFormat('fa-IR', { hour: '2-digit', minute: '2-digit' }).format(new Date(unix * 1000)); } /** unix start-of-day for `from`, end-of-day for `to`, from a gregorian Y-m-d. */ function dayBound(value: string, end: boolean): number | undefined { const d = toDate(value); if (!d) return undefined; const secs = Math.floor(d.setHours(0, 0, 0, 0) / 1000); return end ? secs + 86399 : secs; } function Avatar({ name }: { name: string | null }) { return (
{(name ?? '؟').charAt(0)}
); } /** * لیست پرداخت‌ها — فهرست تخت صورتحساب‌های ثبت‌شده‌ی tenant. * فیلترها در query string زندگی می‌کنند تا رفرش و اشتراک لینک، نما را حفظ کند. */ export default function MyPaymentsPage() { const navigate = useNavigate(); const [params, setParams] = useSearchParams(); const search = params.get('search') ?? ''; const status = params.get('status') ?? ''; const from = params.get('from') ?? ''; const to = params.get('to') ?? ''; const page = Math.max(1, Number(params.get('page') ?? 1)); const hasFilters = !!(search || status || from || to); /** تغییر فیلتر همیشه به صفحه‌ی اول برمی‌گردد؛ ماندن روی صفحه ۵ با نتیجه‌ی جدید بی‌معناست. */ const setParam = (patch: Record) => { const next = new URLSearchParams(params); Object.entries(patch).forEach(([k, v]) => (v ? next.set(k, v) : next.delete(k))); if (!('page' in patch)) next.delete('page'); setParams(next, { replace: true }); }; const filters = { national_code: search || undefined, status: status || undefined, from: from ? dayBound(from, false) : undefined, to: to ? dayBound(to, true) : undefined, }; const listQuery = usePayments({ page, ...filters }); const summaryQuery = usePaymentsSummary(filters); const rows = listQuery.data?.data ?? []; const total = listQuery.data?.meta?.totalRecords ?? 0; const summary = summaryQuery.data?.data; const columns: Column[] = [ { key: 'patient_name', header: 'بیمار', render: (r) => (
{r.patient_name ?? '—'}
), }, { key: 'national_code', header: 'کد ملی', render: (r) => {r.national_code ?? '—'} }, { key: 'issued_at', header: 'تاریخ', render: (r) => {formatDate(r.issued_at)} - {formatTime(r.issued_at)}, }, { key: 'amount_rials', header: 'مبلغ', render: (r) => (
{formatRial(r.amount_rials)}
{r.status === 'partial' && (
پرداخت‌شده: {formatRial(r.paid_rials)}
)}
), }, { key: 'status', header: 'وضعیت', render: (r) => }, ]; return ( <>
setParam({ status: v ? String(v) : '' })} placeholder="همه وضعیت‌ها" height={38} />
setParam({ from: v })} height={38} />
setParam({ to: v })} height={38} />
{hasFilters && ( )}
setParam({ search: v.replace(/\D/g, '') })} searchPlaceholder="کد ملی بیمار" emptyMessage="پرداختی ثبت نشده است." actions={(row) => ( )} /> {total > MY_PAYMENTS_LIMIT && ( setParam({ page: String(p) })} /> )}
); }