From ffe661d2123370d9092619950e9574a2ffa4aa4c Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Thu, 28 May 2026 20:25:37 +0200 Subject: [PATCH] fix(groups): fetch full client list for Add/Remove/SubLinks modals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GroupsPage was sourcing modal candidates from useClients(), which is server-paginated at 25 rows — so "Add clients to group" only ever offered the first page, "Remove" missed members past page 1, and SubLinks silently skipped emails whose record wasn't in the cached page. Pull the unpaginated list via /panel/api/clients/list when any of the three modals open. --- frontend/src/api/queryKeys.ts | 1 + frontend/src/pages/groups/GroupsPage.tsx | 31 ++++++++++++++++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/frontend/src/api/queryKeys.ts b/frontend/src/api/queryKeys.ts index ab74ec40..4f7f5037 100644 --- a/frontend/src/api/queryKeys.ts +++ b/frontend/src/api/queryKeys.ts @@ -19,6 +19,7 @@ export const keys = { clients: { root: () => ['clients'] as const, list: (params: unknown) => ['clients', 'list', params] as const, + all: () => ['clients', 'all'] as const, onlines: () => ['clients', 'onlines'] as const, lastOnline: () => ['clients', 'lastOnline'] as const, groups: () => ['clients', 'groups'] as const, diff --git a/frontend/src/pages/groups/GroupsPage.tsx b/frontend/src/pages/groups/GroupsPage.tsx index 6a0f1750..12690c0f 100644 --- a/frontend/src/pages/groups/GroupsPage.tsx +++ b/frontend/src/pages/groups/GroupsPage.tsx @@ -34,6 +34,7 @@ import { UsergroupDeleteOutlined, } from '@ant-design/icons'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { z } from 'zod'; import { useTheme } from '@/hooks/useTheme'; import { useMediaQuery } from '@/hooks/useMediaQuery'; @@ -44,9 +45,16 @@ import { setMessageInstance } from '@/utils/messageBus'; import AppSidebar from '@/components/AppSidebar'; import LazyMount from '@/components/LazyMount'; import { keys } from '@/api/queryKeys'; -import { GroupSummaryListSchema, type GroupSummary } from '@/schemas/client'; +import { + ClientRecordSchema, + GroupSummaryListSchema, + type ClientRecord, + type GroupSummary, +} from '@/schemas/client'; import { parseMsg } from '@/utils/zodValidate'; +const ClientRecordListSchema = z.array(ClientRecordSchema).nullable().transform((v) => v ?? []); + const SubLinksModal = lazy(() => import('../clients/SubLinksModal')); const ClientBulkAdjustModal = lazy(() => import('../clients/ClientBulkAdjustModal')); const GroupAddClientsModal = lazy(() => import('./GroupAddClientsModal')); @@ -81,7 +89,7 @@ export default function GroupsPage() { useEffect(() => { setMessageInstance(messageApi); }, [messageApi]); const queryClient = useQueryClient(); - const { clients, subSettings, bulkAdjust, bulkAddToGroup, bulkRemoveFromGroup, bulkDelete } = useClients(); + const { subSettings, bulkAdjust, bulkAddToGroup, bulkRemoveFromGroup, bulkDelete } = useClients(); const groupsQuery = useQuery({ queryKey: keys.clients.groups(), @@ -133,6 +141,19 @@ export default function GroupsPage() { const [groupEmails, setGroupEmails] = useState([]); const [groupForAction, setGroupForAction] = useState(null); + const allClientsQuery = useQuery({ + queryKey: keys.clients.all(), + queryFn: async () => { + const msg = await HttpUtil.get('/panel/api/clients/list', undefined, { silent: true }); + if (!msg?.success) throw new Error(msg?.msg || 'Failed to load clients'); + const validated = parseMsg(msg, ClientRecordListSchema, 'clients/list'); + return validated.obj ?? []; + }, + enabled: addClientsOpen || removeClientsOpen || subLinksOpen, + staleTime: 30_000, + }); + const allClients = allClientsQuery.data ?? []; + const totalGroups = groups.length; const totalClients = useMemo( () => groups.reduce((acc, g) => acc + (g.clientCount || 0), 0), @@ -529,7 +550,7 @@ export default function GroupsPage() { @@ -561,7 +582,7 @@ export default function GroupsPage() { c.group !== groupForAction?.name)} + candidates={allClients.filter((c) => c.group !== groupForAction?.name)} onClose={() => setAddClientsOpen(false)} onSubmit={async (emails) => { const msg = await bulkAddToGroup(emails, groupForAction?.name ?? ''); @@ -577,7 +598,7 @@ export default function GroupsPage() { c.group === groupForAction?.name)} + members={allClients.filter((c) => c.group === groupForAction?.name)} onClose={() => setRemoveClientsOpen(false)} onSubmit={async (emails) => { const msg = await bulkRemoveFromGroup(emails);