From 943f4ded2de4c0457eba0e61b209d8d619e7c4bb Mon Sep 17 00:00:00 2001 From: vsyroc Date: Wed, 9 Jul 2025 16:17:49 +0500 Subject: [PATCH] feat(client): add appropriation node and node edit drawer --- client/public/icons/node/calculate.svg | 10 + client/src/components/ContentDrawer.tsx | 175 ------------- client/src/components/Header.tsx | 6 +- client/src/components/ReactFlowDrawer.tsx | 139 ++++++++++ .../src/components/drawers/ContentDrawer.tsx | 245 ++++++++++++++++++ .../{ => drawers/users}/UserCreate.tsx | 0 .../{ => drawers/users}/UserEdit.tsx | 0 .../components/nodes/AppropriationNode.tsx | 56 ++++ client/src/components/nodes/IfElseNode.tsx | 3 + 9 files changed, 456 insertions(+), 178 deletions(-) create mode 100644 client/public/icons/node/calculate.svg delete mode 100644 client/src/components/ContentDrawer.tsx create mode 100644 client/src/components/ReactFlowDrawer.tsx create mode 100644 client/src/components/drawers/ContentDrawer.tsx rename client/src/components/{ => drawers/users}/UserCreate.tsx (100%) rename client/src/components/{ => drawers/users}/UserEdit.tsx (100%) create mode 100644 client/src/components/nodes/AppropriationNode.tsx diff --git a/client/public/icons/node/calculate.svg b/client/public/icons/node/calculate.svg new file mode 100644 index 0000000..b6404da --- /dev/null +++ b/client/public/icons/node/calculate.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/client/src/components/ContentDrawer.tsx b/client/src/components/ContentDrawer.tsx deleted file mode 100644 index 4cf0627..0000000 --- a/client/src/components/ContentDrawer.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { Drawer } from 'antd'; -import { useEffect, useState } from 'react'; -import { Avatar, Typography } from 'antd'; -import { useTranslation } from 'react-i18next'; -import { useUserSelector } from '@/store/userStore'; - -interface ContentDrawerProps { - open: boolean; - closeDrawer: () => void; - children: React.ReactNode; - type: 'create' | 'edit'; - login?: string; - name?: string; - email?: string | null; -} - -export default function ContentDrawer({ - open, - closeDrawer, - children, - type, - login, - name, - email, -}: ContentDrawerProps) { - const user = useUserSelector(); - const { t } = useTranslation(); - const [width, setWidth] = useState('30%'); - - const calculateWidths = () => { - const windowWidth = window.innerWidth; - const expanded = Math.max(windowWidth * 0.3, 300); - setWidth(expanded); - }; - - useEffect(() => { - calculateWidths(); - window.addEventListener('resize', calculateWidths); - return () => window.removeEventListener('resize', calculateWidths); - }, []); - console.log(login, user?.login, login === user?.login); - - const editDrawerTitle = ( -
-
- close_drawer -
- -
- -
- - {name} {login === user?.login ? t('you') : ''} - - - {email} - -
-
- -
- delete -
-
- ); - - const createDrawerTitle = ( -
-
- close_drawer -
- -
- {t('newAccount')} -
- -
- delete -
-
- ); - - return ( - - {children} - - ); -} diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 3a65726..70c5d3d 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -2,8 +2,8 @@ import { useUserSelector } from '@/store/userStore'; import { Avatar } from 'antd'; import Title from 'antd/es/typography/Title'; import { useState } from 'react'; -import ContentDrawer from './ContentDrawer'; -import UserEdit from './UserEdit'; +import ContentDrawer from './drawers/ContentDrawer'; +import UserEdit from './drawers/users/UserEdit'; interface HeaderProps { title: string; @@ -68,7 +68,7 @@ export default function Header({ title, additionalContent }: HeaderProps) { email={user?.email} open={openEdit} closeDrawer={closeEditDrawer} - type="edit" + type="userEdit" > {user?.id && } diff --git a/client/src/components/ReactFlowDrawer.tsx b/client/src/components/ReactFlowDrawer.tsx new file mode 100644 index 0000000..7a32bc5 --- /dev/null +++ b/client/src/components/ReactFlowDrawer.tsx @@ -0,0 +1,139 @@ +import { + ReactFlow, + Background, + Controls, + useNodesState, + useEdgesState, + Handle, + Position, +} from "@xyflow/react"; +import IfElseNode from "./nodes/IfElseNode"; +import { useCallback, useEffect } from "react"; +import customEdgeStyle from "./edges/defaultEdgeStyle"; +import { message } from "antd"; +import AppropriationNode from "./nodes/AppropriationNode"; +import { useTranslation } from "react-i18next"; + +function CustomNode({ data }: { data: any }) { + return ( +
+
+
{data.label}
+
+
+ +
+
+ +
+
+ ); +} + +const nodeTypes = { + custom: CustomNode, + ifElse: IfElseNode, + appropriation: AppropriationNode, +}; + +const initialNodes = [ + { + id: "1", + type: "ifElse", + position: { x: 100, y: 100 }, + data: { condition: "B=2" }, + }, + { + id: "2", + type: "default", + position: { x: 400, y: 100 }, + data: { label: "Приём" }, + }, + { + id: "3", + type: "appropriation", + position: { x: 400, y: 300 }, + data: { value: "Выбрать {{account.role}}" }, + }, +]; + +const initialEdges = [ + { + id: "e1-2", + source: "1", + target: "2", + label: "A1", + ...customEdgeStyle, + }, +]; + +interface ReactFlowDrawerProps { + showDrawer: () => void; +} + +export default function ReactFlowDrawer({ showDrawer }: ReactFlowDrawerProps) { + const { t } = useTranslation(); + + 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 + ); + + if (exists) { + message.warning("Edge already exists"); + return; + } + + const newEdge = { + ...con, + ...customEdgeStyle, + id: `e${con.source}-${con.target}`, + label: "A1", + }; + + setEdges((eds) => [...eds, newEdge]); + }, + [edges, setEdges] + ); + + useEffect(() => { + console.log("Nodes changed:", nodes); + }, [nodes]); + + useEffect(() => { + console.log("Edges changed:", edges); + }, [edges]); + + return ( + { + console.log(e, "node clicked"); + showDrawer(); + }} + nodeTypes={nodeTypes} + fitView + > + + + + ); +} diff --git a/client/src/components/drawers/ContentDrawer.tsx b/client/src/components/drawers/ContentDrawer.tsx new file mode 100644 index 0000000..7535783 --- /dev/null +++ b/client/src/components/drawers/ContentDrawer.tsx @@ -0,0 +1,245 @@ +import { Drawer } from "antd"; +import { useEffect, useState } from "react"; +import { Avatar, Typography } from "antd"; +import { useTranslation } from "react-i18next"; +import { useUserSelector } from "@/store/userStore"; + +interface ContentDrawerProps { + open: boolean; + closeDrawer: () => void; + children: React.ReactNode; + type: "userCreate" | "userEdit" | "nodeEdit"; + login?: string; + name?: string; + email?: string | null; +} + +export default function ContentDrawer({ + open, + closeDrawer, + children, + type, + login, + name, + email, +}: ContentDrawerProps) { + const user = useUserSelector(); + const { t } = useTranslation(); + const [width, setWidth] = useState("30%"); + + const calculateWidths = () => { + const windowWidth = window.innerWidth; + const expanded = Math.max(windowWidth * 0.3, 300); + setWidth(expanded); + }; + + useEffect(() => { + calculateWidths(); + window.addEventListener("resize", calculateWidths); + return () => window.removeEventListener("resize", calculateWidths); + }, []); + console.log(login, user?.login, login === user?.login); + + const userEditDrawerTitle = ( +
+
+ close_drawer +
+ +
+ +
+ + {name} {login === user?.login ? t("you") : ""} + + + {email} + +
+
+ +
+ delete +
+
+ ); + + const userCreateDrawerTitle = ( +
+
+ close_drawer +
+ +
+ {t("newAccount")} +
+ +
+ delete +
+
+ ); + + const nodeEditDrawerTitle = ( +
+
+ close_drawer +
+ +
+ Редактирование блока +
+ +
+ delete +
+
+ ); + + return ( + { + switch (type) { + case "userCreate": + return userCreateDrawerTitle; + case "userEdit": + return userEditDrawerTitle; + case "nodeEdit": + return nodeEditDrawerTitle; + default: + return null; + } + })()} + placement="right" + open={open} + width={width} + destroyOnHidden={true} + closable={false} + > + {children} + + ); +} diff --git a/client/src/components/UserCreate.tsx b/client/src/components/drawers/users/UserCreate.tsx similarity index 100% rename from client/src/components/UserCreate.tsx rename to client/src/components/drawers/users/UserCreate.tsx diff --git a/client/src/components/UserEdit.tsx b/client/src/components/drawers/users/UserEdit.tsx similarity index 100% rename from client/src/components/UserEdit.tsx rename to client/src/components/drawers/users/UserEdit.tsx diff --git a/client/src/components/nodes/AppropriationNode.tsx b/client/src/components/nodes/AppropriationNode.tsx new file mode 100644 index 0000000..b9888ee --- /dev/null +++ b/client/src/components/nodes/AppropriationNode.tsx @@ -0,0 +1,56 @@ +import { Handle, Node, NodeProps, Position } from "@xyflow/react"; +import DEFAULT_HANDLE_STYLE from "./defaultHandleStyle"; + +type AppropriationNodeData = { value: string }; + +export default function AppropriationNode({ + data, +}: NodeProps) { + return ( +
+
+ appropriation logo{" "} + ПРИСВОЕНИЕ +
+
+
+ {data.value as string} +
+ +
+ ); +} diff --git a/client/src/components/nodes/IfElseNode.tsx b/client/src/components/nodes/IfElseNode.tsx index c907168..95c8ae5 100644 --- a/client/src/components/nodes/IfElseNode.tsx +++ b/client/src/components/nodes/IfElseNode.tsx @@ -69,6 +69,9 @@ export default function IfElseNode({ data }: NodeProps) { style={{ ...DEFAULT_HANDLE_STYLE }} /> { + console.log("click"); + }} type="source" position={Position.Bottom} id="false"