fix: add pagination to campaigns list

The campaigns page was fetching only the first 50 items with no way
to load more. Switched from useQuery to useInfiniteQuery with a
"Load more" button so all campaigns are accessible.
This commit is contained in:
Fringg
2026-03-07 07:04:56 +03:00
parent f7afa002f0
commit 46f640a7e0
3 changed files with 34 additions and 4 deletions

View File

@@ -2031,6 +2031,7 @@
"subtitle": "Manage advertising links",
"createButton": "Create",
"noData": "No ad campaigns",
"loadMore": "Load more",
"bonusType": {
"balance": "Balance",
"subscription": "Subscription",

View File

@@ -2545,6 +2545,7 @@
"subtitle": "Управление рекламными ссылками",
"createButton": "Создать",
"noData": "Нет рекламных кампаний",
"loadMore": "Загрузить ещё",
"bonusType": {
"balance": "Баланс",
"subscription": "Подписка",

View File

@@ -1,12 +1,14 @@
import { useState } from 'react';
import { useNavigate } from 'react-router';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import i18n from '../i18n';
import { campaignsApi, CampaignListItem, CampaignBonusType } from '../api/campaigns';
import { PlusIcon, EditIcon, TrashIcon, CheckIcon, XIcon, ChartIcon } from '../components/icons';
import { usePlatform } from '../platform/hooks/usePlatform';
const PAGE_SIZE = 50;
// Bonus type labels and colors
const bonusTypeConfig: Record<
CampaignBonusType,
@@ -69,9 +71,20 @@ export default function AdminCampaigns() {
const [deleteConfirm, setDeleteConfirm] = useState<number | null>(null);
// Queries
const { data: campaignsData, isLoading } = useQuery({
const {
data: campaignsData,
isLoading,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: ['admin-campaigns'],
queryFn: () => campaignsApi.getCampaigns(true),
queryFn: ({ pageParam = 0 }) => campaignsApi.getCampaigns(true, pageParam, PAGE_SIZE),
initialPageParam: 0,
getNextPageParam: (lastPage, allPages) => {
const loaded = allPages.reduce((sum, p) => sum + p.campaigns.length, 0);
return loaded < lastPage.total ? loaded : undefined;
},
});
const { data: overview } = useQuery({
@@ -96,7 +109,7 @@ export default function AdminCampaigns() {
},
});
const campaigns = campaignsData?.campaigns || [];
const campaigns = campaignsData?.pages.flatMap((p) => p.campaigns) ?? [];
return (
<div className="animate-fade-in">
@@ -261,6 +274,21 @@ export default function AdminCampaigns() {
</div>
</div>
))}
{/* Load more */}
{hasNextPage && (
<button
onClick={() => fetchNextPage()}
disabled={isFetchingNextPage}
className="flex w-full items-center justify-center gap-2 rounded-xl border border-dark-700 bg-dark-800 py-3 text-sm font-medium text-dark-300 transition-colors hover:border-dark-600 hover:text-dark-100 disabled:opacity-50"
>
{isFetchingNextPage ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-dark-500 border-t-accent-500" />
) : (
t('admin.campaigns.loadMore', 'Load more')
)}
</button>
)}
</div>
)}