import { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Alert, Input, Modal, Select, Space, Spin, Table, Tag, Typography, message } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { HttpUtil } from '@/utils'; import { formatInboundLabel } from '@/lib/inbounds/label'; import type { DBInbound } from '@/models/dbinbound'; interface AttachExistingClientsModalProps { open: boolean; target: DBInbound | null; onClose: () => void; onAttached?: () => void; } interface BulkAttachResult { attached?: string[]; skipped?: string[]; errors?: string[]; } interface ClientRow { email: string; group: string; enable: boolean; alreadyAttached: boolean; } interface RawClient { email?: string; group?: string; enable?: boolean; inboundIds?: number[] | null; } export default function AttachExistingClientsModal({ open, target, onClose, onAttached, }: AttachExistingClientsModalProps) { const { t } = useTranslation(); const [messageApi, messageContextHolder] = message.useMessage(); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); const [clientRows, setClientRows] = useState([]); const [selectedEmails, setSelectedEmails] = useState([]); const [search, setSearch] = useState(''); const [groupFilter, setGroupFilter] = useState(undefined); useEffect(() => { if (!open || !target) return; let cancelled = false; setLoading(true); setSearch(''); setGroupFilter(undefined); HttpUtil.get('/panel/api/clients/list', undefined, { silent: true }) .then((msg) => { if (cancelled) return; const list = Array.isArray(msg?.obj) ? (msg.obj as RawClient[]) : []; const rows: ClientRow[] = list .map((c) => ({ email: (c?.email || '').trim(), group: (c?.group || '').trim(), enable: c?.enable !== false, alreadyAttached: Array.isArray(c?.inboundIds) && c.inboundIds.includes(target.id), })) .filter((r) => r.email); setClientRows(rows); setSelectedEmails(rows.filter((r) => !r.alreadyAttached).map((r) => r.email)); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [open, target]); const groupOptions = useMemo(() => { const set = new Set(); for (const r of clientRows) if (r.group) set.add(r.group); return [...set].sort((a, b) => a.localeCompare(b)).map((g) => ({ value: g, label: g })); }, [clientRows]); const attachableCount = useMemo( () => clientRows.filter((r) => !r.alreadyAttached).length, [clientRows], ); const filteredRows = useMemo(() => { const q = search.trim().toLowerCase(); return clientRows.filter((r) => { if (groupFilter && r.group !== groupFilter) return false; if (!q) return true; return r.email.toLowerCase().includes(q) || r.group.toLowerCase().includes(q); }); }, [clientRows, search, groupFilter]); const columns: ColumnsType = useMemo( () => [ { title: t('pages.inbounds.email'), dataIndex: 'email', key: 'email', ellipsis: true, }, { title: t('pages.clients.group'), dataIndex: 'group', key: 'group', width: 150, ellipsis: true, render: (group: string) => group ? {group} : , }, { title: t('enable'), key: 'status', width: 140, render: (_v, row) => { if (row.alreadyAttached) return {t('pages.inbounds.attachExistingStatusAttached')}; return row.enable ? ( {t('enable')} ) : ( {t('pages.inbounds.attachClientsStatusDisabled')} ); }, }, ], [t], ); async function submit() { if (!target || selectedEmails.length === 0) return; setSaving(true); try { const msg = await HttpUtil.post( '/panel/api/clients/bulkAttach', { emails: selectedEmails, inboundIds: [target.id] }, { headers: { 'Content-Type': 'application/json' } }, ); if (!msg?.success) { messageApi.error(msg?.msg || t('somethingWentWrong')); return; } const result = (msg.obj || {}) as BulkAttachResult; const attached = result.attached?.length ?? 0; const skipped = result.skipped?.length ?? 0; const errors = result.errors?.length ?? 0; if (errors > 0) { messageApi.warning(t('pages.inbounds.attachClientsResultMixed', { attached, skipped, errors })); } else { messageApi.success(t('pages.inbounds.attachClientsResult', { attached, skipped })); } onAttached?.(); onClose(); } finally { setSaving(false); } } const noClients = !loading && clientRows.length === 0; return ( {messageContextHolder} {t('pages.inbounds.attachExistingDesc', { count: attachableCount })} {noClients ? ( ) : ( setSearch(e.target.value)} placeholder={t('pages.inbounds.attachClientsSearchPlaceholder')} style={{ width: 260 }} /> {groupOptions.length > 0 && (