148 lines
5.7 KiB
TypeScript

import { forwardRef, memo, useEffect, useImperativeHandle, useState } from "react";
import { useTranslation } from "react-i18next";
import { Alert, Form, type FormInstance, Input, Radio } from "antd";
import { createSchemaFieldRule } from "antd-zod";
import dayjs from "dayjs";
import { z } from "zod";
import Show from "@/components/Show";
import { WORKFLOW_TRIGGERS, type WorkflowNodeConfigForStart, type WorkflowTriggerType } from "@/domain/workflow";
import { useAntdForm } from "@/hooks";
import { getNextCronExecutions, validCronExpression } from "@/utils/cron";
type StartNodeConfigFormFieldValues = Partial<WorkflowNodeConfigForStart>;
export type StartNodeConfigFormProps = {
className?: string;
style?: React.CSSProperties;
disabled?: boolean;
initialValues?: StartNodeConfigFormFieldValues;
onValuesChange?: (values: StartNodeConfigFormFieldValues) => void;
};
export type StartNodeConfigFormInstance = {
getFieldsValue: () => ReturnType<FormInstance<StartNodeConfigFormFieldValues>["getFieldsValue"]>;
resetFields: FormInstance<StartNodeConfigFormFieldValues>["resetFields"];
validateFields: FormInstance<StartNodeConfigFormFieldValues>["validateFields"];
};
const initFormModel = (): StartNodeConfigFormFieldValues => {
return {
trigger: WORKFLOW_TRIGGERS.AUTO,
triggerCron: "0 0 * * *",
};
};
const StartNodeConfigForm = forwardRef<StartNodeConfigFormInstance, StartNodeConfigFormProps>(
({ className, style, disabled, initialValues, onValuesChange }, ref) => {
const { t } = useTranslation();
const formSchema = z
.object({
trigger: z.string({ message: t("workflow_node.start.form.trigger.placeholder") }).min(1, t("workflow_node.start.form.trigger.placeholder")),
triggerCron: z.string().nullish(),
})
.superRefine((data, ctx) => {
if (data.trigger !== WORKFLOW_TRIGGERS.AUTO) {
return;
}
if (!validCronExpression(data.triggerCron!)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("workflow_node.start.form.trigger_cron.errmsg.invalid"),
path: ["triggerCron"],
});
}
});
const formRule = createSchemaFieldRule(formSchema);
const { form: formInst, formProps } = useAntdForm({
name: "workflowNodeStartConfigForm",
initialValues: initialValues ?? initFormModel(),
});
const fieldTrigger = Form.useWatch<WorkflowTriggerType>("trigger", formInst);
const fieldTriggerCron = Form.useWatch<string>("triggerCron", formInst);
const [fieldTriggerCronExpectedExecutions, setFieldTriggerCronExpectedExecutions] = useState<Date[]>([]);
useEffect(() => {
setFieldTriggerCronExpectedExecutions(getNextCronExecutions(fieldTriggerCron, 5));
}, [fieldTriggerCron]);
const handleTriggerChange = (value: string) => {
if (value === WORKFLOW_TRIGGERS.AUTO) {
formInst.setFieldValue("triggerCron", formProps.initialValues?.triggerCron || initFormModel().triggerCron);
} else {
formInst.setFieldValue("triggerCron", undefined);
}
onValuesChange?.(formInst.getFieldsValue(true));
};
const handleFormChange = (_: unknown, values: z.infer<typeof formSchema>) => {
onValuesChange?.(values as StartNodeConfigFormFieldValues);
};
useImperativeHandle(ref, () => {
return {
getFieldsValue: () => {
return formInst.getFieldsValue(true);
},
resetFields: (fields) => {
return formInst.resetFields(fields);
},
validateFields: (nameList, config) => {
return formInst.validateFields(nameList, config);
},
} as StartNodeConfigFormInstance;
});
return (
<Form className={className} style={style} {...formProps} disabled={disabled} layout="vertical" scrollToFirstError onValuesChange={handleFormChange}>
<Form.Item
name="trigger"
label={t("workflow_node.start.form.trigger.label")}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.start.form.trigger.tooltip") }}></span>}
>
<Radio.Group onChange={(e) => handleTriggerChange(e.target.value)}>
<Radio value={WORKFLOW_TRIGGERS.AUTO}>{t("workflow_node.start.form.trigger.option.auto.label")}</Radio>
<Radio value={WORKFLOW_TRIGGERS.MANUAL}>{t("workflow_node.start.form.trigger.option.manual.label")}</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name="triggerCron"
label={t("workflow_node.start.form.trigger_cron.label")}
hidden={fieldTrigger !== WORKFLOW_TRIGGERS.AUTO}
rules={[formRule]}
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.start.form.trigger_cron.tooltip") }}></span>}
extra={
<Show when={fieldTriggerCronExpectedExecutions.length > 0}>
<div>
{t("workflow_node.start.form.trigger_cron.extra")}
<br />
{fieldTriggerCronExpectedExecutions.map((date, index) => (
<span key={index}>
{dayjs(date).format("YYYY-MM-DD HH:mm:ss")}
<br />
</span>
))}
</div>
</Show>
}
>
<Input placeholder={t("workflow_node.start.form.trigger_cron.placeholder")} />
</Form.Item>
<Show when={fieldTrigger === WORKFLOW_TRIGGERS.AUTO}>
<Form.Item>
<Alert type="info" message={<span dangerouslySetInnerHTML={{ __html: t("workflow_node.start.form.trigger_cron.guide") }}></span>} />
</Form.Item>
</Show>
</Form>
);
}
);
export default memo(StartNodeConfigForm);