mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-11 23:26:07 +00:00
987a6dd1e5
Add an IP Log popup (view list + refresh + clear) to the client edit form and the Client Information modal, with IPs stacked vertically. Identify inbounds by their xray tag (not remark/protocol:port) across every picker and chip: attach/detach modals, the attached-inbounds column and field, the filter drawer, and bulk-add. Add the tag field to the InboundOption schema (the backend already returned it). Clarify modal titles/labels: Client Information (was More Information) and Inbound Information (was Inbound's Data); Client Information / QR Code titles now include the client email. i18n: rename keys moreInformation->clientInfo and inboundData->inboundInfo with proper translations in all languages; addTitle->addClient, editTitle->editClient, addToGroupPlaceholder->groupName.
82 lines
2.2 KiB
TypeScript
82 lines
2.2 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { AutoComplete, Form, Modal, message } from 'antd';
|
|
|
|
interface BulkAddToGroupModalProps {
|
|
open: boolean;
|
|
count: number;
|
|
groups: string[];
|
|
onOpenChange: (open: boolean) => void;
|
|
onSubmit: (group: string) => Promise<{ affected?: number } | null>;
|
|
}
|
|
|
|
export default function BulkAddToGroupModal({
|
|
open,
|
|
count,
|
|
groups,
|
|
onOpenChange,
|
|
onSubmit,
|
|
}: BulkAddToGroupModalProps) {
|
|
const { t } = useTranslation();
|
|
const [messageApi, messageContextHolder] = message.useMessage();
|
|
const [value, setValue] = useState('');
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (open) setValue('');
|
|
}, [open]);
|
|
|
|
async function submit() {
|
|
const next = value.trim();
|
|
if (!next) return;
|
|
setSubmitting(true);
|
|
try {
|
|
const result = await onSubmit(next);
|
|
if (result) {
|
|
const affected = result.affected ?? 0;
|
|
messageApi.success(t('pages.clients.addToGroupSuccessToast', { count: affected, group: next }));
|
|
onOpenChange(false);
|
|
}
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{messageContextHolder}
|
|
<Modal
|
|
open={open}
|
|
title={t('pages.clients.addToGroupTitle', { count })}
|
|
okText={t('add')}
|
|
cancelText={t('cancel')}
|
|
confirmLoading={submitting}
|
|
okButtonProps={{ disabled: !value.trim() }}
|
|
onCancel={() => onOpenChange(false)}
|
|
onOk={submit}
|
|
destroyOnHidden
|
|
>
|
|
<Form layout="vertical">
|
|
<Form.Item
|
|
label={t('pages.clients.group')}
|
|
tooltip={t('pages.clients.addToGroupTooltip')}
|
|
>
|
|
<AutoComplete
|
|
value={value}
|
|
placeholder={t('pages.clients.groupName')}
|
|
options={groups.map((g) => ({ value: g }))}
|
|
onChange={(v) => setValue(v ?? '')}
|
|
filterOption={(input, option) =>
|
|
String(option?.value ?? '').toLowerCase().includes((input || '').toLowerCase())
|
|
}
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
autoFocus
|
|
/>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</>
|
|
);
|
|
}
|