diff --git a/src/components/flow/nodes/RowFilterNode.tsx b/src/components/flow/nodes/RowFilterNode.tsx index 846b17a..0e49d32 100644 --- a/src/components/flow/nodes/RowFilterNode.tsx +++ b/src/components/flow/nodes/RowFilterNode.tsx @@ -1,6 +1,6 @@ import { - BadgeConfig, - EnhancedBaseNode, + BadgeConfig, + EnhancedBaseNode, } from "@/components/flow/nodes/EnhancedBaseNode"; import { Button } from "@/components/ui/button"; import { useNodeColumns } from "@/hooks/useNodeColumns"; @@ -12,277 +12,320 @@ import { FlowNodeProps, RowFilterNodeDataContext } from "@/types/nodes"; import { PlusIcon, TrashIcon } from "@radix-ui/react-icons"; import { - Flex, - Grid, - IconButton, - ScrollArea, - Select, - Text, - TextField, + Flex, + Grid, + IconButton, + ScrollArea, + Select, + Text, + TextField, } from "@radix-ui/themes"; import { useMemo, useState } from "react"; import { useNodeId } from "reactflow"; import { useTranslation } from "react-i18next"; const getOperators = (t: any) => [ - { value: "==", label: t("flow.operators.equals") }, - { value: "!=", label: t("flow.operators.notEquals") }, - { value: ">", label: t("flow.operators.greaterThan") }, - { value: ">=", label: t("flow.operators.greaterThanOrEqual") }, - { value: "<", label: t("flow.operators.lessThan") }, - { value: "<=", label: t("flow.operators.lessThanOrEqual") }, - { value: "contains", label: t("flow.operators.contains") }, - { value: "not_contains", label: t("flow.operators.notContains") }, + { value: "==", label: t("flow.operators.equals") }, + { value: "!=", label: t("flow.operators.notEquals") }, + { value: ">", label: t("flow.operators.greaterThan") }, + { value: ">=", label: t("flow.operators.greaterThanOrEqual") }, + { value: "<", label: t("flow.operators.lessThan") }, + { value: "<=", label: t("flow.operators.lessThanOrEqual") }, + { value: "contains", label: t("flow.operators.contains") }, + { value: "not_contains", label: t("flow.operators.notContains") }, ]; export const RowFilterNode: React.FC = ({ data }) => { - const { t } = useTranslation(); - const toast = useToast(); - const nodeId = useNodeId()!; - const nodeData = data as RowFilterNodeDataContext; - const OPERATORS = getOperators(t); - const currentWorkspace = useWorkspaceStore((state) => state.currentWorkspace); - const previewNodeMutation = usePreviewNodeMutation(); - const [condValue, setCondValue] = useState(""); - const { - columns: availableColumns, - isLoading: isLoadingColumns, - error: columnsError, - } = useNodeColumns(); - - const updateRowFilterNodeDataInStore = useWorkspaceStore( - (state) => state.updateNodeData, - ); + const { t } = useTranslation(); + const toast = useToast(); + const nodeId = useNodeId()!; + const nodeData = data as RowFilterNodeDataContext; + const OPERATORS = getOperators(t); + const currentWorkspace = useWorkspaceStore( + (state) => state.currentWorkspace + ); + const previewNodeMutation = usePreviewNodeMutation(); + // const [condValue, setCondValue] = useState(""); + const [condValues, setCondValues] = useState([{ index: 0, value: "" }]); + const { + columns: availableColumns, + isLoading: isLoadingColumns, + error: columnsError, + } = useNodeColumns(); - const updateCondition = (index: number, field: string, value: any) => { - const updatedConditions = [...(nodeData.conditions || [])]; - updatedConditions[index] = { ...updatedConditions[index], [field]: value }; - updateRowFilterNodeDataInStore( - nodeId, - { - conditions: updatedConditions as any, - error: undefined, - testResult: undefined, - }, - true, + const updateRowFilterNodeDataInStore = useWorkspaceStore( + (state) => state.updateNodeData ); - }; - const addCondition = () => { - const newCondition = { - column: "", - operator: "==", - value: "", - logic: "AND" as "AND" | "OR", + const updateCondition = (index: number, field: string, value: any) => { + const updatedConditions = [...(nodeData.conditions || [])]; + updatedConditions[index] = { + ...updatedConditions[index], + [field]: value, + }; + updateRowFilterNodeDataInStore( + nodeId, + { + conditions: updatedConditions as any, + error: undefined, + testResult: undefined, + }, + true + ); }; - updateRowFilterNodeDataInStore( - nodeId, - { - conditions: [...(nodeData.conditions || []), newCondition], - error: undefined, - testResult: undefined, - }, - true, - ); - }; - const removeCondition = (index: number) => { - const updatedConditions = [...(nodeData.conditions || [])]; - updatedConditions.splice(index, 1); - updateRowFilterNodeDataInStore( - nodeId, - { - conditions: updatedConditions, - error: undefined, - testResult: undefined, - }, - true, - ); - }; + const addCondition = () => { + const newCondition = { + column: "", + operator: "==", + value: "", + logic: "AND" as "AND" | "OR", + }; + updateRowFilterNodeDataInStore( + nodeId, + { + conditions: [...(nodeData.conditions || []), newCondition], + error: undefined, + testResult: undefined, + }, + true + ); + }; - const handleCondValueChange = (index: number, field: string, value: any) => { - setCondValue(value); - updateCondition(index, field, value); - }; + const removeCondition = (index: number) => { + const updatedConditions = [...(nodeData.conditions || [])]; + updatedConditions.splice(index, 1); + updateRowFilterNodeDataInStore( + nodeId, + { + conditions: updatedConditions, + error: undefined, + testResult: undefined, + }, + true + ); + }; - const previewNode = async () => { - if (!currentWorkspace) { - toast.error(t("workspace.no_workspace_loaded")); - return; - } + const handleCondValueChange = ( + index: number, + field: string, + value: any + ) => { + setCondValues([ + ...condValues.filter((cv) => cv.index !== index), + { index, value }, + ]); + updateCondition(index, field, value); + }; - if (!nodeData.conditions || nodeData.conditions.length === 0) { - updateRowFilterNodeDataInStore(nodeId, { - error: t("flow.validation.filter.noConditions"), - testResult: undefined, - }); - return; - } + const previewNode = async () => { + if (!currentWorkspace) { + toast.error(t("workspace.no_workspace_loaded")); + return; + } - // clear existing test result - updateRowFilterNodeDataInStore(nodeId, { - testResult: undefined, - error: undefined, - }); - previewNodeMutation.mutate( - { - nodeId: nodeData.id, - testModeMaxRows: 100, - workspaceConfig: currentWorkspace || undefined, - }, - { - onSuccess: (result) => { - if (result.success) { - const sheets = convertPreviewToSheets(result); + if (!nodeData.conditions || nodeData.conditions.length === 0) { updateRowFilterNodeDataInStore(nodeId, { - testResult: sheets, - error: undefined, + error: t("flow.validation.filter.noConditions"), + testResult: undefined, }); - } else { - updateRowFilterNodeDataInStore(nodeId, { - error: result.error || t("flow.previewFailed"), - testResult: undefined, - }); - } - }, - onError: (error: Error) => { - updateRowFilterNodeDataInStore(nodeId, { - error: t("flow.previewFailedWithError", { error: error.message }), + return; + } + + // clear existing test result + updateRowFilterNodeDataInStore(nodeId, { testResult: undefined, - }); - }, - }, - ); - }; + error: undefined, + }); + previewNodeMutation.mutate( + { + nodeId: nodeData.id, + testModeMaxRows: 100, + workspaceConfig: currentWorkspace || undefined, + }, + { + onSuccess: (result) => { + if (result.success) { + const sheets = convertPreviewToSheets(result); + updateRowFilterNodeDataInStore(nodeId, { + testResult: sheets, + error: undefined, + }); + } else { + updateRowFilterNodeDataInStore(nodeId, { + error: result.error || t("flow.previewFailed"), + testResult: undefined, + }); + } + }, + onError: (error: Error) => { + updateRowFilterNodeDataInStore(nodeId, { + error: t("flow.previewFailedWithError", { + error: error.message, + }), + testResult: undefined, + }); + }, + } + ); + }; + + const badges: BadgeConfig[] = useMemo(() => { + return ( + nodeData.conditions?.map((condition) => ({ + label: `${condition.column} ${condition.operator} ${condition.value}`, + color: `${condition.logic}` === "AND" ? "blue" : "green", + variant: "soft", + })) ?? [] + ); + }, [nodeData.conditions]); - const badges: BadgeConfig[] = useMemo(() => { return ( - nodeData.conditions?.map((condition) => ({ - label: `${condition.column} ${condition.operator} ${condition.value}`, - color: `${condition.logic}` === "AND" ? "blue" : "green", - variant: "soft", - })) ?? [] - ); - }, [nodeData.conditions]); + + + + + + {t("flow.filterConditions")} + + + - return ( - - - - - - {t("flow.filterConditions")} - - - + {/* 列加载状态 */} + {isLoadingColumns && ( + + {t("flow.loadingColumns")} + + )} - {/* 列加载状态 */} - {isLoadingColumns && ( - - {t("flow.loadingColumns")} - - )} + {columnsError && ( + + {t("flow.columnsError", { + message: columnsError.message, + })} + + )} - {columnsError && ( - - {t("flow.columnsError", { message: columnsError.message })} - - )} + {(!nodeData.conditions || + nodeData.conditions.length === 0) && ( + + {t("flow.addConditionsPrompt")} + + )} - {(!nodeData.conditions || nodeData.conditions.length === 0) && ( - - {t("flow.addConditionsPrompt")} - - )} + {(nodeData.conditions || []).map((condition, index) => ( + + {index > 0 && ( + + updateCondition(index, "logic", value) + } + > + + + + {t("flow.logicAnd")} + + + {t("flow.logicOr")} + + + + )} - {(nodeData.conditions || []).map((condition, index) => ( - - {index > 0 && ( - - updateCondition(index, "logic", value) - } - > - - - {t("flow.logicAnd")} - {t("flow.logicOr")} - - - )} + + {/* 列选择 */} + + updateCondition(index, "column", value) + } + > + + + {availableColumns.map( + (column: string) => ( + + {column} + + ) + )} + + - - {/* 列选择 */} - - updateCondition(index, "column", value) - } - > - - - {availableColumns.map((column: string) => ( - - {column} - - ))} - - + {/* 操作符选择 */} + + updateCondition( + index, + "operator", + value + ) + } + > + + + {OPERATORS.map((op) => ( + + {op.label} + + ))} + + - {/* 操作符选择 */} - - updateCondition(index, "operator", value) - } - > - - - {OPERATORS.map((op) => ( - - {op.label} - - ))} - - - - {/* 值输入 */} - - handleCondValueChange(index, "value", e.target.value) - } - placeholder={t("flow.valuePlaceholder")} - /> + {/* 值输入 */} + + condValue.index === index + )?.value || condition.value + } + onChange={(e) => + handleCondValueChange( + index, + "value", + e.target.value + ) + } + placeholder={t("flow.valuePlaceholder")} + /> - {/* 删除按钮 */} - removeCondition(index)} - > - - - - - ))} - - - - ); + {/* 删除按钮 */} + removeCondition(index)} + > + + + + + ))} + + + + ); };