diff --git a/client/src/components/ReactFlowDrawer.tsx b/client/src/components/ReactFlowDrawer.tsx
index 7a32bc5..85a862e 100644
--- a/client/src/components/ReactFlowDrawer.tsx
+++ b/client/src/components/ReactFlowDrawer.tsx
@@ -4,46 +4,23 @@ import {
Controls,
useNodesState,
useEdgesState,
- Handle,
- Position,
+ Node,
+ Edge,
} from "@xyflow/react";
-import IfElseNode from "./nodes/IfElseNode";
-import { useCallback, useEffect } from "react";
+import { useState } from "react";
import customEdgeStyle from "./edges/defaultEdgeStyle";
-import { message } from "antd";
-import AppropriationNode from "./nodes/AppropriationNode";
+import { Dropdown, DropdownProps } from "antd";
import { useTranslation } from "react-i18next";
-
-function CustomNode({ data }: { data: any }) {
- return (
-
- );
-}
+import { edgeTitleGenerator } from "@/utils/edge";
+import IfElseNode from "./nodes/IfElseNode";
+import AppropriationNode from "./nodes/AppropriationNode";
const nodeTypes = {
- custom: CustomNode,
ifElse: IfElseNode,
appropriation: AppropriationNode,
};
-const initialNodes = [
+const initialNodes: Node[] = [
{
id: "1",
type: "ifElse",
@@ -52,9 +29,9 @@ const initialNodes = [
},
{
id: "2",
- type: "default",
- position: { x: 400, y: 100 },
- data: { label: "Приём" },
+ type: "appropriation",
+ position: { x: 100, y: 300 },
+ data: { value: "Выбрать {{account.email}}" },
},
{
id: "3",
@@ -64,14 +41,24 @@ const initialNodes = [
},
];
-const initialEdges = [
- {
- id: "e1-2",
- source: "1",
- target: "2",
- label: "A1",
- ...customEdgeStyle,
- },
+const initialEdges: Edge[] = [
+ // {
+ // id: "e1-3",
+ // source: "1",
+ // sourceHandle: "1",
+ // target: "3",
+ // targetHandle: null,
+ // label: "A1",
+ // ...customEdgeStyle,
+ // },
+ // {
+ // id: "e1-2",
+ // source: "1",
+ // sourceHandle: "2",
+ // target: "2",
+ // label: "B1",
+ // ...customEdgeStyle,
+ // },
];
interface ReactFlowDrawerProps {
@@ -84,56 +71,117 @@ export default function ReactFlowDrawer({ showDrawer }: ReactFlowDrawerProps) {
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
- const onConnect = useCallback(
- (con: { source: any; target: any }) => {
- const exists = edges.some(
- (edge) => edge.source === con.source && edge.target === con.target
- );
+ const [menuVisible, setMenuVisible] = useState(false);
+ const [selectedHandleId, setSelectedHandleId] = useState(null);
+ const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
- if (exists) {
- message.warning("Edge already exists");
- return;
- }
+ const handleOpenChange: DropdownProps["onOpenChange"] = (nextOpen, info) => {
+ if (info.source === "trigger" || nextOpen) {
+ setMenuVisible(nextOpen);
+ }
+ };
- const newEdge = {
- ...con,
- ...customEdgeStyle,
- id: `e${con.source}-${con.target}`,
- label: "A1",
- };
+ const handleClick = (e: React.MouseEvent, node: Node) => {
+ e.stopPropagation();
- setEdges((eds) => [...eds, newEdge]);
- },
- [edges, setEdges]
- );
+ const target = e.target as HTMLElement;
+ const handleElement = target.closest(".react-flow__handle") as HTMLElement;
- useEffect(() => {
- console.log("Nodes changed:", nodes);
- }, [nodes]);
+ if (!handleElement) return;
- useEffect(() => {
- console.log("Edges changed:", edges);
- }, [edges]);
+ const handleId = handleElement.getAttribute("data-handleid");
+ if (!handleId) return;
+
+ setSelectedHandleId(`${node.id}-${handleId}`);
+
+ const flowWrapper = document.querySelector(".react-flow") as HTMLElement;
+ if (flowWrapper) {
+ const wrapperRect = flowWrapper.getBoundingClientRect();
+ const handleRect = handleElement.getBoundingClientRect();
+ setMenuPosition({
+ x: handleRect.right - wrapperRect.left,
+ y: handleRect.top - wrapperRect.top,
+ });
+ }
+
+ setMenuVisible(true);
+ };
+
+ const handleMenuItemClick = (targetNodeId: string) => {
+ if (!selectedHandleId) return;
+
+ const [sourceNodeId, sourceHandleId] = selectedHandleId.split("-");
+
+ const label = edgeTitleGenerator(edges.length + 1);
+
+ const newEdge: Edge = {
+ id: `e${sourceNodeId}-${sourceHandleId}-${targetNodeId}`,
+ source: sourceNodeId,
+ sourceHandle: sourceHandleId,
+ target: targetNodeId,
+ label,
+ ...customEdgeStyle,
+ };
+
+ setEdges((eds) => [...eds, newEdge]);
+ setMenuVisible(false);
+ setSelectedHandleId(null);
+ };
+
+ const menuItems = nodes
+ .filter((node) => node.id !== selectedHandleId?.split("-")[0]) // исключаем текущий узел
+ .map((node) => ({
+ key: node.id,
+ label: t("connectTo", { nodeId: node.id }),
+ onClick: () => handleMenuItemClick(node.id),
+ }));
return (
{
- console.log(e, "node clicked");
- showDrawer();
+ onNodeClick={(event, node) => {
+ const target = event.target as HTMLElement;
+
+ if (!target.closest(".react-flow__handle")) {
+ console.log("node clicked");
+ showDrawer();
+ } else {
+ handleClick(event, node);
+ }
}}
nodeTypes={nodeTypes}
fitView
>
-
-
+
+
+
+ {menuVisible && (
+
+ )}
);
}
diff --git a/client/src/components/drawers/DrawerTitles.tsx b/client/src/components/drawers/DrawerTitles.tsx
new file mode 100644
index 0000000..21b8ba6
--- /dev/null
+++ b/client/src/components/drawers/DrawerTitles.tsx
@@ -0,0 +1,213 @@
+import { User } from "@/types/user";
+import { Avatar, Typography } from "antd";
+import { TFunction } from "i18next";
+
+interface DrawerTitleProps {
+ closeDrawer: () => void;
+ t: TFunction;
+}
+
+interface UserEditDrawerTitleProps extends DrawerTitleProps {
+ login?: string;
+ name?: string;
+ email?: string | null;
+ user: User | null;
+}
+
+interface UserCreateDrawerTitleProps extends DrawerTitleProps {}
+
+interface NodeEditDrawerTitleProps extends DrawerTitleProps {}
+
+const UserEditDrawerTitle = ({
+ closeDrawer,
+ login,
+ name,
+ email,
+ user,
+ t,
+}: UserEditDrawerTitleProps) => {
+ return (
+
+
+

+
+
+
+
+
+
+ {name} {login === user?.login ? t("you") : ""}
+
+
+ {email}
+
+
+
+
+
+

+
+
+ );
+};
+
+const UserCreateDrawerTitle = ({
+ closeDrawer,
+ t,
+}: UserCreateDrawerTitleProps) => {
+ return (
+
+
+

+
+
+
+ {t("newAccount")}
+
+
+
+

+
+
+ );
+};
+
+const NodeEditDrawerTitle = ({ closeDrawer, t }: NodeEditDrawerTitleProps) => {
+ return (
+
+
+

+
+
+
+ {t("editNode")}
+
+
+
+

+
+
+ );
+};
+
+export { UserEditDrawerTitle, UserCreateDrawerTitle, NodeEditDrawerTitle };
diff --git a/client/src/components/nodes/AppropriationNode.tsx b/client/src/components/nodes/AppropriationNode.tsx
index b9888ee..c0b1785 100644
--- a/client/src/components/nodes/AppropriationNode.tsx
+++ b/client/src/components/nodes/AppropriationNode.tsx
@@ -1,11 +1,13 @@
import { Handle, Node, NodeProps, Position } from "@xyflow/react";
import DEFAULT_HANDLE_STYLE from "./defaultHandleStyle";
+import { useTranslation } from "react-i18next";
type AppropriationNodeData = { value: string };
export default function AppropriationNode({
data,
}: NodeProps) {
+ const { t } = useTranslation();
return (
{" "}
- ПРИСВОЕНИЕ
+ />
+ {t("appropriationNode")}
) {
+export default function IfElseNode({ id, data }: NodeProps
) {
+ const { t } = useTranslation();
return (
-
+ <>
-

{" "}
- ЕСЛИ - ТО
-
-
+
+

+ {t("ifElseNode")}
+
+
-
- Если {data.condition as string}, то
-
-
+
+ {t("conditionIf", { condition: data.condition })}
+
+
-
- Иначе
-
+
+ {t("conditionElse")}
+
-
-
-
{
- console.log("click");
- }}
- type="source"
- position={Position.Bottom}
- id="false"
- style={{ ...DEFAULT_HANDLE_STYLE }}
- />
-
+
+
+
+
+ >
);
}