From fe8a0cb21ff673ad17db7cf644232a5198d28ac7 Mon Sep 17 00:00:00 2001 From: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> Date: Mon, 31 Aug 2026 11:58:11 +0800 Subject: [PATCH] fix(promotion): clamp emptied queue pages Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com> --- web/src/pages/dashboard/promotions.test.tsx | 30 ++++++++++++++++++++- web/src/pages/dashboard/promotions.tsx | 16 ++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/web/src/pages/dashboard/promotions.test.tsx b/web/src/pages/dashboard/promotions.test.tsx index a1666c47..7f924af9 100644 --- a/web/src/pages/dashboard/promotions.test.tsx +++ b/web/src/pages/dashboard/promotions.test.tsx @@ -1,5 +1,5 @@ /** @vitest-environment jsdom */ -import { cleanup, fireEvent, render, screen, within } from '@testing-library/react' +import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { PromotionStatus, PromotionTask } from '@/api/types' @@ -246,6 +246,34 @@ describe('PromotionsPage', () => { expect(screen.getByRole('button', { name: 'pagination:0/2' })).toBeTruthy() }) + it('returns to the last valid page when a mutation empties the current page', async () => { + const pending = createPromotion() + mocks.usePromotionList.mockImplementation((params: { status?: PromotionStatus; page?: number } = {}) => { + const page = params.page ?? 0 + return { + data: { + items: params.status === 'PENDING' && page === 0 ? [pending] : [], + total: params.status === 'PENDING' ? 20 : 0, + page, + size: 20, + }, + isLoading: false, + } + }) + render() + + mocks.paginationProps[0]?.onPageChange(1) + + await waitFor(() => { + expect(mocks.usePromotionList).toHaveBeenLastCalledWith({ + status: 'PENDING', + page: 0, + size: 20, + }) + }) + expect(screen.getByText('Knowledge Helper')).toBeTruthy() + }) + it('renders approved history as a sortable table', () => { render() diff --git a/web/src/pages/dashboard/promotions.tsx b/web/src/pages/dashboard/promotions.tsx index ba1ad811..5ac266eb 100644 --- a/web/src/pages/dashboard/promotions.tsx +++ b/web/src/pages/dashboard/promotions.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useApprovePromotion, usePromotionList, useRejectPromotion } from '@/features/promotion/use-promotion-list' import { DashboardPageHeader } from '@/shared/components/dashboard-page-header' @@ -79,6 +79,18 @@ function PromotionPagination({ data, onPageChange }: { data: PromotionPage; onPa return } +function useClampPromotionPage(data: PromotionPage | undefined, page: number, onPageChange: (page: number) => void) { + useEffect(() => { + if (!data) { + return + } + const totalPages = data.size > 0 ? Math.ceil(data.total / data.size) : 0 + if (page > 0 && page >= totalPages) { + onPageChange(Math.max(0, totalPages - 1)) + } + }, [data, onPageChange, page]) +} + function PendingPromotionCard({ item, comment, @@ -141,6 +153,7 @@ function PendingPromotionList({ page, onPageChange }: { page: number; onPageChan const approveMutation = useApprovePromotion() const rejectMutation = useRejectPromotion() const [commentById, setCommentById] = useState>({}) + useClampPromotionPage(data, page, onPageChange) if (isLoading) { return
@@ -192,6 +205,7 @@ function PromotionHistoryTable({ }) const nextDirection = sortDirection === 'DESC' ? 'ASC' : 'DESC' const sortLabel = nextDirection === 'ASC' ? t('promotions.sortReviewedTimeAsc') : t('promotions.sortReviewedTimeDesc') + useClampPromotionPage(data, page, onPageChange) if (isLoading) { return (