mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-31 10:14:15 +00:00
fix(groups): fetch full client list for Add/Remove/SubLinks modals
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.
This commit is contained in:
parent
3f0b7fbe97
commit
ffe661d212
2 changed files with 27 additions and 5 deletions
|
|
@ -19,6 +19,7 @@ export const keys = {
|
||||||
clients: {
|
clients: {
|
||||||
root: () => ['clients'] as const,
|
root: () => ['clients'] as const,
|
||||||
list: (params: unknown) => ['clients', 'list', params] as const,
|
list: (params: unknown) => ['clients', 'list', params] as const,
|
||||||
|
all: () => ['clients', 'all'] as const,
|
||||||
onlines: () => ['clients', 'onlines'] as const,
|
onlines: () => ['clients', 'onlines'] as const,
|
||||||
lastOnline: () => ['clients', 'lastOnline'] as const,
|
lastOnline: () => ['clients', 'lastOnline'] as const,
|
||||||
groups: () => ['clients', 'groups'] as const,
|
groups: () => ['clients', 'groups'] as const,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import {
|
||||||
UsergroupDeleteOutlined,
|
UsergroupDeleteOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { useTheme } from '@/hooks/useTheme';
|
import { useTheme } from '@/hooks/useTheme';
|
||||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||||
|
|
@ -44,9 +45,16 @@ import { setMessageInstance } from '@/utils/messageBus';
|
||||||
import AppSidebar from '@/components/AppSidebar';
|
import AppSidebar from '@/components/AppSidebar';
|
||||||
import LazyMount from '@/components/LazyMount';
|
import LazyMount from '@/components/LazyMount';
|
||||||
import { keys } from '@/api/queryKeys';
|
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';
|
import { parseMsg } from '@/utils/zodValidate';
|
||||||
|
|
||||||
|
const ClientRecordListSchema = z.array(ClientRecordSchema).nullable().transform((v) => v ?? []);
|
||||||
|
|
||||||
const SubLinksModal = lazy(() => import('../clients/SubLinksModal'));
|
const SubLinksModal = lazy(() => import('../clients/SubLinksModal'));
|
||||||
const ClientBulkAdjustModal = lazy(() => import('../clients/ClientBulkAdjustModal'));
|
const ClientBulkAdjustModal = lazy(() => import('../clients/ClientBulkAdjustModal'));
|
||||||
const GroupAddClientsModal = lazy(() => import('./GroupAddClientsModal'));
|
const GroupAddClientsModal = lazy(() => import('./GroupAddClientsModal'));
|
||||||
|
|
@ -81,7 +89,7 @@ export default function GroupsPage() {
|
||||||
useEffect(() => { setMessageInstance(messageApi); }, [messageApi]);
|
useEffect(() => { setMessageInstance(messageApi); }, [messageApi]);
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const { clients, subSettings, bulkAdjust, bulkAddToGroup, bulkRemoveFromGroup, bulkDelete } = useClients();
|
const { subSettings, bulkAdjust, bulkAddToGroup, bulkRemoveFromGroup, bulkDelete } = useClients();
|
||||||
|
|
||||||
const groupsQuery = useQuery({
|
const groupsQuery = useQuery({
|
||||||
queryKey: keys.clients.groups(),
|
queryKey: keys.clients.groups(),
|
||||||
|
|
@ -133,6 +141,19 @@ export default function GroupsPage() {
|
||||||
const [groupEmails, setGroupEmails] = useState<string[]>([]);
|
const [groupEmails, setGroupEmails] = useState<string[]>([]);
|
||||||
const [groupForAction, setGroupForAction] = useState<GroupSummary | null>(null);
|
const [groupForAction, setGroupForAction] = useState<GroupSummary | null>(null);
|
||||||
|
|
||||||
|
const allClientsQuery = useQuery<ClientRecord[]>({
|
||||||
|
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 totalGroups = groups.length;
|
||||||
const totalClients = useMemo(
|
const totalClients = useMemo(
|
||||||
() => groups.reduce((acc, g) => acc + (g.clientCount || 0), 0),
|
() => groups.reduce((acc, g) => acc + (g.clientCount || 0), 0),
|
||||||
|
|
@ -529,7 +550,7 @@ export default function GroupsPage() {
|
||||||
<SubLinksModal
|
<SubLinksModal
|
||||||
open={subLinksOpen}
|
open={subLinksOpen}
|
||||||
emails={groupEmails}
|
emails={groupEmails}
|
||||||
clients={clients}
|
clients={allClients}
|
||||||
subSettings={subSettings}
|
subSettings={subSettings}
|
||||||
onOpenChange={setSubLinksOpen}
|
onOpenChange={setSubLinksOpen}
|
||||||
/>
|
/>
|
||||||
|
|
@ -561,7 +582,7 @@ export default function GroupsPage() {
|
||||||
<GroupAddClientsModal
|
<GroupAddClientsModal
|
||||||
open={addClientsOpen}
|
open={addClientsOpen}
|
||||||
groupName={groupForAction?.name ?? null}
|
groupName={groupForAction?.name ?? null}
|
||||||
candidates={clients.filter((c) => c.group !== groupForAction?.name)}
|
candidates={allClients.filter((c) => c.group !== groupForAction?.name)}
|
||||||
onClose={() => setAddClientsOpen(false)}
|
onClose={() => setAddClientsOpen(false)}
|
||||||
onSubmit={async (emails) => {
|
onSubmit={async (emails) => {
|
||||||
const msg = await bulkAddToGroup(emails, groupForAction?.name ?? '');
|
const msg = await bulkAddToGroup(emails, groupForAction?.name ?? '');
|
||||||
|
|
@ -577,7 +598,7 @@ export default function GroupsPage() {
|
||||||
<GroupRemoveClientsModal
|
<GroupRemoveClientsModal
|
||||||
open={removeClientsOpen}
|
open={removeClientsOpen}
|
||||||
groupName={groupForAction?.name ?? null}
|
groupName={groupForAction?.name ?? null}
|
||||||
members={clients.filter((c) => c.group === groupForAction?.name)}
|
members={allClients.filter((c) => c.group === groupForAction?.name)}
|
||||||
onClose={() => setRemoveClientsOpen(false)}
|
onClose={() => setRemoveClientsOpen(false)}
|
||||||
onSubmit={async (emails) => {
|
onSubmit={async (emails) => {
|
||||||
const msg = await bulkRemoveFromGroup(emails);
|
const msg = await bulkRemoveFromGroup(emails);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue