import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { ArrowPathIcon } from '@heroicons/react/24/outline'; import { api } from '../lib/api'; import { formatRial, formatNumber } from '../lib/utils'; import type { ClaimPatientRow } from '../types'; import type { ApiResponse, PaginatedResponse } from '../lib/api'; import PageHeader from '../components/ui/PageHeader'; import DataTable, { type Column } from '../components/ui/DataTable'; import StatCard from '../components/ui/StatCard'; import StatusBadge from '../components/ui/StatusBadge'; import SearchableSelect from '../components/ui/SearchableSelect'; import PersianDatePicker from '../components/ui/PersianDatePicker'; import FeatureGate from '../components/ui/FeatureGate'; import Pagination from '../components/ui/Pagination'; const LIMIT = 20; const STATUS_OPTIONS = [ { value: '', label: 'همه وضعیت‌ها' }, { value: 'pending', label: 'در انتظار ارسال' }, { value: 'submitted', label: 'ارسال شده' }, { value: 'approved', label: 'تأیید شده' }, { value: 'rejected', label: 'رد شده' }, { value: 'paid', label: 'پرداخت شده' }, ]; const PAYMENT_OPTIONS = [ { value: '', label: 'همه' }, { value: 'paid', label: 'وصول‌شده' }, { value: 'unpaid', 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); const toUnix = (iso: string): number | null => { if (!iso) return null; const t = new Date(iso + 'T00:00:00').getTime(); return Number.isNaN(t) ? null : Math.floor(t / 1000); }; const endOfDayUnix = (iso: string): number | null => { if (!iso) return null; const t = new Date(iso + 'T23:59:59').getTime(); return Number.isNaN(t) ? null : Math.floor(t / 1000); }; interface DebtRow { insurance_id: number; insurance_name: string | null; claimed: number; approved: number; paid: number; debt: number; } /** * داشبورد پرونده‌های بیمه — سطح اول: یک ردیف به‌ازای هر بیمار. * فیلترها در query string زندگی می‌کنند تا رفرش و اشتراک لینک، نما را حفظ کند. */ export default function ClaimsPage() { const navigate = useNavigate(); const [params, setParams] = useSearchParams(); const search = params.get('search') ?? ''; const status = params.get('status') ?? ''; const insuranceId = params.get('insurance_id') ?? ''; const paymentStatus = params.get('payment_status') ?? ''; const from = params.get('from') ?? ''; const to = params.get('to') ?? ''; const page = Math.max(1, Number(params.get('page') ?? 1)); const sort = params.get('sort') ?? 'last_activity_at'; const dir = (params.get('dir') ?? 'desc') as 'asc' | 'desc'; const hasFilters = !!(search || status || insuranceId || paymentStatus || 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 queryString = useMemo(() => { const qs = new URLSearchParams(); qs.set('page', String(page)); qs.set('limit', String(LIMIT)); qs.set('sort', sort); qs.set('dir', dir); if (search) qs.set('search', search); if (status) qs.set('status', status); if (insuranceId) qs.set('insurance_id', insuranceId); if (paymentStatus) qs.set('payment_status', paymentStatus); const fromUnix = toUnix(from); const toUnixVal = endOfDayUnix(to); if (fromUnix) qs.set('from', String(fromUnix)); if (toUnixVal) qs.set('to', String(toUnixVal)); return qs.toString(); }, [page, sort, dir, search, status, insuranceId, paymentStatus, from, to]); const listQuery = useQuery>({ queryKey: ['claims-by-patient', queryString], queryFn: () => api.get(`/api/v1/billing/claims/by-patient?${queryString}`), }); // یک fetch برای هر دو مصرف: کارت‌های آمار و گزینه‌های فیلتر بیمه. const debtQuery = useQuery>({ queryKey: ['insurance-debt'], queryFn: () => api.get('/api/v1/billing/reports/insurance-debt'), }); const rows = listQuery.data?.data ?? []; const total = listQuery.data?.meta?.totalRecords ?? 0; const debt: DebtRow[] = debtQuery.data?.data?.data ?? []; const totals = useMemo( () => debt.reduce( (acc, d) => ({ claimed: acc.claimed + d.claimed, paid: acc.paid + d.paid, debt: acc.debt + d.debt, }), { claimed: 0, paid: 0, debt: 0 }, ), [debt], ); const insuranceOptions = useMemo( () => [ { value: '', label: 'همه بیمه‌ها' }, ...debt.map((d) => ({ value: String(d.insurance_id), label: d.insurance_name ?? `بیمه #${d.insurance_id}` })), ], [debt], ); const columns: Column[] = [ { key: 'full_name', header: 'بیمار', sortable: true, render: (r) => r.full_name ?? '—' }, { key: 'mobile', header: 'موبایل', render: (r) => r.mobile ?? '—' }, { key: 'national_code', header: 'کد ملی', render: (r) => r.national_code ?? '—' }, { key: 'claims_count', header: 'تعداد درخواست', sortable: true, render: (r) => formatNumber(r.claims_count) }, { key: 'total_services_rials', header: 'مجموع خدمات', sortable: true, render: (r) => formatRial(r.total_services_rials) }, { key: 'total_insurance_rials', header: 'سهم بیمه', sortable: true, render: (r) => formatRial(r.total_insurance_rials) }, { key: 'total_patient_rials', header: 'سهم بیمار', render: (r) => formatRial(r.total_patient_rials) }, { key: 'overall_status', header: 'وضعیت کلی', render: (r) => }, ]; return (
setParam({ insurance_id: v ? String(v) : '' })} placeholder="همه بیمه‌ها" height={38} />
setParam({ status: v ? String(v) : '' })} placeholder="همه وضعیت‌ها" height={38} />
setParam({ payment_status: v ? String(v) : '' })} placeholder="همه" height={38} />
setParam({ from: v })} height={38} />
setParam({ to: v })} height={38} />
{hasFilters && ( )}
setParam({ search: v })} searchPlaceholder="نام، موبایل یا کد ملی بیمار" emptyMessage="پرونده بیمه‌ای ثبت نشده است." sortKey={sort} sortDir={dir} onSort={(key) => setParam({ sort: key, dir: sort === key && dir === 'desc' ? 'asc' : 'desc' })} actions={(row) => ( )} /> {total > LIMIT && ( setParam({ page: String(p) })} /> )}
); }