fix(admin): paginate claims & settlement lists for backend-audit 50-cap
GET /billing/claims and GET /settlement now return only the first 50 rows by default (data.data unchanged, data.meta added). ClaimsPage and RepresentationSettlementPage read the full array with no pager, so rows beyond 50 were unreachable. Add page state + ?page/limit + the existing <Pagination> (reading data.meta.totalRecords). No change needed for: 422 on claim approve/pay (api.ts already surfaces the backend message via toast; the admin UI sends no amount so it's unreachable), the owner-only appointment-settings endpoints (admin user bypasses), and refresh rotation (authStore.refresh already persists the rotated refresh_token). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { CheckIcon, XMarkIcon, PaperAirplaneIcon, BanknotesIcon, MagnifyingGlassIcon, ArrowPathIcon } from '@heroicons/react/24/outline';
|
||||
import { toast } from 'sonner';
|
||||
@@ -9,6 +9,7 @@ import Modal from '../components/ui/Modal';
|
||||
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 isoNDaysAgo = (days: number): string => {
|
||||
const d = new Date();
|
||||
@@ -86,6 +87,11 @@ export default function ClaimsPage() {
|
||||
const [searchInput, setSearchInput] = useState('');
|
||||
const [rejectTarget, setRejectTarget] = useState<Claim | null>(null);
|
||||
const [rejectReason, setRejectReason] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const limit = 20;
|
||||
|
||||
// back to page 1 whenever a filter changes
|
||||
useEffect(() => { setPage(1); }, [statusFilter, insuranceFilter, fromDate, toDate, search]);
|
||||
|
||||
const queryString = useMemo(() => {
|
||||
const p = new URLSearchParams();
|
||||
@@ -94,9 +100,11 @@ export default function ClaimsPage() {
|
||||
const from = toUnix(fromDate); if (from) p.set('from', String(from));
|
||||
const to = endOfDayUnix(toDate); if (to) p.set('to', String(to));
|
||||
if (search.trim()) p.set('q', search.trim());
|
||||
p.set('page', String(page));
|
||||
p.set('limit', String(limit));
|
||||
const s = p.toString();
|
||||
return s ? `?${s}` : '';
|
||||
}, [statusFilter, insuranceFilter, fromDate, toDate, search]);
|
||||
}, [statusFilter, insuranceFilter, fromDate, toDate, search, page]);
|
||||
|
||||
const claimsQuery = useQuery<{ data: { data: Claim[] } }>({
|
||||
queryKey: ['claims', queryString],
|
||||
@@ -115,6 +123,7 @@ export default function ClaimsPage() {
|
||||
});
|
||||
|
||||
const claims = (claimsQuery.data as any)?.data?.data ?? [];
|
||||
const claimsTotal = (claimsQuery.data as any)?.data?.meta?.totalRecords ?? 0;
|
||||
const debt = (debtQuery.data as any)?.data?.data ?? [];
|
||||
|
||||
const transitionMut = useMutation({
|
||||
@@ -332,6 +341,7 @@ export default function ClaimsPage() {
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
<Pagination page={page} total={claimsTotal} limit={limit} onPageChange={setPage} />
|
||||
</div>
|
||||
|
||||
<Modal open={!!rejectTarget} onClose={() => setRejectTarget(null)} title="رد مطالبه"
|
||||
|
||||
@@ -4,6 +4,7 @@ import { toast } from 'sonner';
|
||||
import { api } from '../lib/api';
|
||||
import type { ApiResponse } from '../lib/api';
|
||||
import { formatRial, formatDate } from '../lib/utils';
|
||||
import Pagination from '../components/ui/Pagination';
|
||||
|
||||
interface WalletBalance { balance_rials: number }
|
||||
interface RepSummary { income: { settlable_rials: number; settled_rials: number; pending_rials: number } }
|
||||
@@ -29,6 +30,8 @@ export default function RepresentationSettlementPage() {
|
||||
const qc = useQueryClient();
|
||||
const [amount, setAmount] = useState('');
|
||||
const [ibanId, setIbanId] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
const limit = 20;
|
||||
|
||||
const balanceQ = useQuery({
|
||||
queryKey: ['wallet-balance'],
|
||||
@@ -55,12 +58,14 @@ export default function RepresentationSettlementPage() {
|
||||
const income = useMemo<any>(() => ((summaryQ.data?.data as any)?.data ?? summaryQ.data?.data)?.income, [summaryQ.data]);
|
||||
|
||||
const listQ = useQuery({
|
||||
queryKey: ['settlements-mine'],
|
||||
queryFn: () => api.get<ApiResponse<SettlementRow[]>>('/api/v1/settlement'),
|
||||
queryKey: ['settlements-mine', page],
|
||||
queryFn: () => api.get<ApiResponse<SettlementRow[]>>(`/api/v1/settlement?page=${page}&limit=${limit}`),
|
||||
staleTime: 30_000,
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const settlements: SettlementRow[] = (listQ.data?.data as any)?.data ?? listQ.data?.data ?? [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const settlementsTotal: number = (listQ.data?.data as any)?.meta?.totalRecords ?? settlements.length;
|
||||
|
||||
const createMut = useMutation({
|
||||
mutationFn: (payload: { amount_rials: number; iban_id: string }) =>
|
||||
@@ -163,6 +168,7 @@ export default function RepresentationSettlementPage() {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<Pagination page={page} total={settlementsTotal} limit={limit} onPageChange={setPage} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user