Files
alert/src/pages/rules/components/CopyRule/Multi‌CopyPopup.tsx
Johnton Chen 3c636bcaa2 refactor(copy-rule): extract context and improve batch clone modal
- Extract MultiCopyPopupContext to standalone context.ts file
- Add Task type and improve type safety in BatchCloneModal
- Use useRef/useCallback to stabilize batch clone handlers
- Add cleanup effect for selectRules in RuleCheckCard on unmount

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 23:05:56 +08:00

303 lines
9.8 KiB
TypeScript

import {
type ProFormInstance,
StepsForm,
useBreakpoint,
} from '@ant-design/pro-components';
import { Button, Divider, Flex, Input, Modal, Space, Typography } from 'antd';
import { useMemo, useRef, useState } from 'react';
import useRichFormat from '@/hooks/useRichI18n';
import { $t } from '@/utils/i18n';
import '../style.less';
import { flushSync } from 'react-dom';
import { type SchemaType, schemaMap } from '../../ruleFormSchema';
import { handleEmptyValuesFunc } from '../../utils';
import BatchCloneModal from './comp/BatchCloneModal';
import Step1 from './comp/Step1';
import Step2 from './comp/Step2';
import { MultiCopyPopupContext } from './context';
const { Text, Title } = Typography;
type MultiCopyPopupProps = {
visible: boolean;
ruleMeta: Record<string, any>;
dataSourceList: API.DataSourceListItem[];
currentRuleType: number;
onDone: (refresh?: boolean) => void;
};
const MultiCopyPopup = ({
visible,
ruleMeta,
onDone,
currentRuleType,
dataSourceList,
}: MultiCopyPopupProps) => {
const { richFormat } = useRichFormat();
const screens = useBreakpoint();
const currentSchemaMap =
schemaMap[currentRuleType as unknown as SchemaType]?.({ richFormat }) || {};
const formMapRef = useRef<
React.RefObject<ProFormInstance<any> | undefined>[]
>([]);
const [targetDataSourceId, setTargetDataSourceId] = useState<
string | number | null
>(null);
const [selectRules, setSelectRules] = useState<API.RuleListItem[]>([]);
const [checkedIdsList, setCheckedIdsList] = useState<Array<string | number>>(
[],
);
const [inputValue, setInputValue] = useState('');
const [batchCloneModalVisible, setBatchCloneModalVisible] = useState(false);
const [selectedRows, setSelectedRows] = useState<Partial<API.RuleListItem>[]>(
[],
);
const submitRuleList = useMemo(() => {
return selectRules.filter((item) => checkedIdsList.includes(item.id));
}, [selectRules, checkedIdsList]);
const targetDataSource = useMemo(() => {
return dataSourceList.find((item) => item.id === targetDataSourceId);
}, [targetDataSourceId, dataSourceList]);
const handleCancel = () => {
setBatchCloneModalVisible(false);
setSelectedRows([]);
setSelectRules([]);
setCheckedIdsList([]);
setInputValue('');
setTargetDataSourceId(null);
};
return (
<Modal
width={['xl', 'xxl'].includes(screens || '') ? '50%' : '95%'}
open={visible}
centered
destroyOnHidden
onCancel={() => {
handleCancel();
onDone();
}}
title={$t('pages.clones.title')}
footer={null}
maskClosable={false}
styles={{
body: {
maxHeight: '90vh',
overflowY: 'auto',
overflowX: 'hidden',
marginRight: -10,
paddingRight: 8,
},
}}
>
<MultiCopyPopupContext.Provider
value={{
ruleMeta,
dataSourceList,
currentRuleType,
targetDataSourceId,
selectRules,
setSelectRules,
currentSchemaMap,
}}
>
<StepsForm
formMapRef={formMapRef}
onFinish={async (values) => {
const editNameRules = values?.step2 || {};
const submitValues = submitRuleList.map((item) => {
const tmpValues = handleEmptyValuesFunc(item);
const tmpValues2 =
currentSchemaMap?.transformSubmitValue?.(tmpValues) ||
tmpValues;
const submitValues = {
...tmpValues2,
dataSourceId: targetDataSourceId,
name:
editNameRules[item.id] ||
`${item.name || ruleMeta?.title || ''} - ${targetDataSource?.sourceName || ''}`,
id: void 0,
groupFilter: '',
};
if (currentSchemaMap?.symbolFormShow) {
submitValues.param2 = '';
}
if (currentSchemaMap?.assetFormShow) {
submitValues.param1 = '';
}
const tmp: Record<string, any> = {
companyId: submitValues.companyId,
dataSourceId: submitValues.dataSourceId,
groupFilter: submitValues.groupFilter,
name: submitValues.name,
type: submitValues.type,
};
for (const key in submitValues) {
if (key.indexOf('param') !== -1) {
tmp[key] = submitValues[key];
}
}
return tmp;
});
setSelectedRows(submitValues);
setBatchCloneModalVisible(true);
}}
submitter={{
render: (props) => {
if (props.step === 0) {
return [
<Button key="cancel" onClick={() => onDone()}>
{$t('pages.model.cancel')}
</Button>,
<Button
disabled={selectRules.length === 0}
key="goToTree"
type="primary"
onClick={() => props.onSubmit?.()}
>
{$t('pages.clones.step1.confirm')}
</Button>,
];
}
if (props.step === 1) {
return [
<Button key="pre" onClick={() => props.onPre?.()}>
{$t('pages.clones.back')}
</Button>,
<Button
disabled={checkedIdsList.length === 0}
type="primary"
key="goToTree"
onClick={() => props.onSubmit?.()}
>
{$t('pages.clones.step2.confirm')}
</Button>,
];
}
return [
<Button
key="gotoTwo"
onClick={() => {
setInputValue('');
props.onPre?.();
}}
>
{$t('pages.clones.backPreview')}
</Button>,
<Button
disabled={
submitRuleList.length === 0 ||
inputValue !== targetDataSource?.sourceName
}
variant="solid"
color="danger"
key="goToTree"
onClick={() => props.onSubmit?.()}
>
{$t('pages.clones.step3.confirm')}
</Button>,
];
},
}}
>
<StepsForm.StepForm
name="step1"
title={$t('pages.clones.step1.title')}
onValuesChange={({ sourceDataSourceId }) => {
if (sourceDataSourceId) {
const curFormRef = formMapRef?.current?.[0]?.current;
const curTargetId = curFormRef?.getFieldValue('targetDataSourceId');
const sourceItem = dataSourceList.find(
(item) => item.id === sourceDataSourceId,
);
const targetItem = dataSourceList.find(
(item) => item.id === curTargetId,
);
if (targetItem && sourceItem && targetItem.sourceType !== sourceItem.sourceType) {
const fallback =
dataSourceList.find(
(item) =>
item.id !== sourceDataSourceId &&
item.sourceType === sourceItem.sourceType,
)?.id || sourceDataSourceId;
curFormRef?.setFieldsValue({ targetDataSourceId: fallback });
setTargetDataSourceId(fallback);
}
}
}}
>
<Step1
formMapRef={formMapRef}
setTargetDataSourceId={setTargetDataSourceId}
/>
</StepsForm.StepForm>
<StepsForm.StepForm
name="step2"
title={$t('pages.clones.step2.title')}
>
<Step2 setCheckedIdsList={setCheckedIdsList} />
</StepsForm.StepForm>
<StepsForm.StepForm
name="time"
title={$t('pages.clones.step3.title')}
>
<div className="confirm-container">
<Flex className="confirm-space" align="center" justify="center">
<Space direction="vertical" align="center">
<Title level={5}>
{$t('pages.clones.step3.confirmTitle', {
cloneCount: checkedIdsList.length,
target: targetDataSource?.sourceName || '',
})}
</Title>
<Text type="secondary">
{$t('pages.clones.step3.confirmTips')}
</Text>
<Input
placeholder={targetDataSource?.sourceName || ''}
className="text-center"
size="large"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<Text type="secondary">
{$t('pages.clones.step3.confirmTips2')}
</Text>
</Space>
</Flex>
</div>
<Divider style={{ margin: '16px 0' }} />
</StepsForm.StepForm>
</StepsForm>
</MultiCopyPopupContext.Provider>
<BatchCloneModal
visible={batchCloneModalVisible}
selectedRows={selectedRows}
onClose={(cb) => {
setBatchCloneModalVisible(false);
cb?.();
}}
onFinish={(cb) => {
flushSync(() => {
setBatchCloneModalVisible(false);
});
cb?.();
handleCancel();
onDone?.(true);
}}
/>
</Modal>
);
};
export default MultiCopyPopup;