From 2809b37f4c96df84cb8e2407948bb63b7b400a7c Mon Sep 17 00:00:00 2001 From: MXerFix Date: Tue, 24 Sep 2024 16:22:26 +0300 Subject: [PATCH] chore: slots components refactor & add condition icons --- frontend/src/UI/Input/DefCombobox.tsx | 40 ++--- frontend/src/UI/Input/DefSelect.tsx | 5 +- frontend/src/components/nodes/SlotsNode.tsx | 5 +- .../nodes/slots/SlotsGroupsTable.tsx | 133 +++++++++++++++ frontend/src/components/ui/button.tsx | 56 ------- frontend/src/components/ui/command.tsx | 153 ------------------ frontend/src/components/ui/dialog.tsx | 120 -------------- frontend/src/components/ui/drawer.tsx | 116 ------------- frontend/src/components/ui/popover.tsx | 29 ---- frontend/src/contexts/flowContext.tsx | 1 - frontend/src/icons/DocumentationIcon.tsx | 23 +++ .../modals/ConditionModal/ConditionModal.tsx | 19 ++- .../modals/SlotsModals/SlotsGroupModal.tsx | 114 +++++++++---- .../src/modals/SlotsModals/SlotsNodeModal.tsx | 88 ++-------- frontend/src/utils.ts | 6 +- 15 files changed, 285 insertions(+), 623 deletions(-) create mode 100644 frontend/src/components/nodes/slots/SlotsGroupsTable.tsx delete mode 100644 frontend/src/components/ui/button.tsx delete mode 100644 frontend/src/components/ui/command.tsx delete mode 100644 frontend/src/components/ui/dialog.tsx delete mode 100644 frontend/src/components/ui/drawer.tsx delete mode 100644 frontend/src/components/ui/popover.tsx create mode 100644 frontend/src/icons/DocumentationIcon.tsx diff --git a/frontend/src/UI/Input/DefCombobox.tsx b/frontend/src/UI/Input/DefCombobox.tsx index ea9b412b..c44ad25b 100644 --- a/frontend/src/UI/Input/DefCombobox.tsx +++ b/frontend/src/UI/Input/DefCombobox.tsx @@ -1,7 +1,7 @@ import * as Popover from "@radix-ui/react-popover" import classNames from "classnames" import { CheckIcon } from "lucide-react" -import React, { ReactNode, useEffect, useRef, useState } from "react" +import React, { ReactNode, useCallback, useEffect, useRef, useState } from "react" interface ComboboxProps { items: string[] @@ -35,11 +35,14 @@ const DefCombobox: React.FC = ({ setIsOpen(true) } - const handleSelectItem = (item: string) => { - setInputValue(item) - setSelected(item) - setIsOpen(false) - } + const handleSelectItem = useCallback( + (item: string) => { + setInputValue(item) + setSelected(item) + setIsOpen(false) + }, + [setSelected] + ) useEffect(() => { if (isOpen && inputRef.current) { @@ -50,17 +53,13 @@ const DefCombobox: React.FC = ({ useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (isOpen) { - console.log(e.key, highlightedIndex) if (e.key === "ArrowDown") { - console.log("arrow down") setHighlightedIndex((prev) => Math.min(prev + 1, filteredItems.length - 1)) e.preventDefault() // Предотвращаем прокрутку страницы } else if (e.key === "ArrowUp") { - console.log("arrow up") setHighlightedIndex((prev) => Math.max(prev - 1, 0)) e.preventDefault() // Предотвращаем прокрутку страницы } else if (e.key === "Enter" && highlightedIndex >= 0) { - console.log("enter") handleSelectItem(filteredItems[highlightedIndex]) e.preventDefault() // Предотвращаем отправку формы, если она есть } @@ -71,34 +70,21 @@ const DefCombobox: React.FC = ({ return () => { window.removeEventListener("keydown", handleKeyDown) } - }, [isOpen, highlightedIndex, filteredItems]) - - console.log(isOpen) + }, [isOpen, highlightedIndex, filteredItems, handleSelectItem]) return (
- {/* Input field */} - {/* setIsOpen(true)} // Открываем Popover при фокусе на поле ввода - placeholder={placeholder} - className='w-full bg-background p-2 rounded-lg border border-input-border' - /> */}
+ className='w-full flex items-center justify-between bg-background p-2 rounded-lg border border-input-border hover:bg-bg-secondary transition-colors'> {startContent && {startContent}} setIsOpen(true)} placeholder={placeholder} - className='w-full bg-transparent outline-none' + className='w-full bg-transparent outline-none placeholder:text-input-border text-sm' /> {endContent && {endContent}}
@@ -118,7 +104,7 @@ const DefCombobox: React.FC = ({ style={{ width: containerRef.current?.offsetWidth ?? "320px", }} - className={`mt-2 bg-background border border-input-border rounded-lg py-1 z-[9999] overflow-x-hidden`}> + className={`mt-2 bg-background border border-input-border rounded-lg py-1 z-[9999] overflow-x-hidden *:text-sm`}> {filteredItems.length ? ( filteredItems.map((item, index) => (
- + {items.map((item) => ( {item.value} diff --git a/frontend/src/components/nodes/SlotsNode.tsx b/frontend/src/components/nodes/SlotsNode.tsx index bcb30c75..8b27bc0c 100644 --- a/frontend/src/components/nodes/SlotsNode.tsx +++ b/frontend/src/components/nodes/SlotsNode.tsx @@ -1,6 +1,6 @@ import { Button } from "@nextui-org/react" import { PlusIcon } from "lucide-react" -import { memo, useContext, useEffect, useState } from "react" +import { memo, useContext, useState } from "react" import { PopUpContext } from "../../contexts/popUpContext" import SlotsConditionIcon from "../../icons/nodes/conditions/SlotsConditionIcon" import EditNodeIcon from "../../icons/nodes/EditNodeIcon" @@ -13,9 +13,6 @@ const SlotsNode = memo(({ data }: { data: SlotsNodeDataType }) => { const { openPopUp } = useContext(PopUpContext) const [nodeData, setNodeData] = useState(data) - useEffect(() => { - console.log(nodeData) - }, [nodeData]) const onNodeModalOpen = () => { openPopUp( diff --git a/frontend/src/components/nodes/slots/SlotsGroupsTable.tsx b/frontend/src/components/nodes/slots/SlotsGroupsTable.tsx new file mode 100644 index 00000000..cc902929 --- /dev/null +++ b/frontend/src/components/nodes/slots/SlotsGroupsTable.tsx @@ -0,0 +1,133 @@ +import { PopUpContext } from "@/contexts/popUpContext" +import DocumentationIcon from "@/icons/DocumentationIcon" +import NewWindowIcon from "@/icons/NewWindowIcon" +import TrashIcon from "@/icons/TrashIcon" +import AlertModal from "@/modals/AlertModal" +import SlotsGroupModal from "@/modals/SlotsModals/SlotsGroupModal" +import { SlotsGroupType } from "@/types/FlowTypes" +import { SlotsNodeDataType } from "@/types/NodeTypes" +import DefTable from "@/UI/Table/DefTable" +import { Button, Divider, TableCell, TableRow } from "@nextui-org/react" +import React, { Fragment, useContext } from "react" + +type Props = { + groups: SlotsGroupType[] + setGroups: React.Dispatch> + nodeData: SlotsNodeDataType + setNodeData: React.Dispatch> + onDeleteGroupHandler?: (group: SlotsGroupType, updatedGroups: SlotsGroupType[]) => void +} + +const SlotsGroupsTable = ({ + groups, + setGroups, + nodeData, + setNodeData, + onDeleteGroupHandler = () => {}, +}: Props) => { + const { openPopUp } = useContext(PopUpContext) + + const handleDeleteGroup = (group: SlotsGroupType) => { + // Удаление группы + openPopUp( + { + const updatedGroups = nodeData.groups.filter((g) => g.id !== group.id) + setGroups(updatedGroups) + onDeleteGroupHandler(group, updatedGroups) + }} + actionText='Delete' + />, + `delete-group-modal-${group.id}` + ) + } + + const handleGroupModalOpen = (group: SlotsGroupType) => { + openPopUp( + , + `slots-group-modal-edit-${group.id}` + ) + } + + return ( + + {groups.map((group) => ( + + + {group.name} + +
+
    + {group.slots.slice(0, 3).map((slot, idx) => ( + + {idx === 2 && group.slots.length > 3 ? ( +
  • + {group.slots.length - 2} more
  • + ) : ( + idx === 2 &&
  • {slot.name}
  • + )} + {idx !== 2 &&
  • {slot.name}
  • } +
    + ))} +
+ {group.subgroups && group.subgroups.length > 0 && ( + <> + +
    + {group.subgroups.map((s) => { + const subgroup = nodeData.groups.find((g) => g.id === s) + if (subgroup) { + return ( +
  • + + {subgroup.name} +
  • + ) + } else { + return <> + } + })} +
+ + )} +
+
+ + + + +
+ ))} +
+ ) +} + +export default SlotsGroupsTable diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx deleted file mode 100644 index a2aba9fc..00000000 --- a/frontend/src/components/ui/button.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import * as React from "react" -import { Slot } from "@radix-ui/react-slot" -import { cva, type VariantProps } from "class-variance-authority" - -import { cn } from "@/lib/utils" - -const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-neutral-950 dark:focus-visible:ring-neutral-300", - { - variants: { - variant: { - default: "bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50/90", - destructive: - "bg-red-500 text-neutral-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-neutral-50 dark:hover:bg-red-900/90", - outline: - "border border-neutral-200 bg-white hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", - secondary: - "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", - ghost: "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", - link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - } -) - -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean -} - -const Button = React.forwardRef( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button" - return ( - - ) - } -) -Button.displayName = "Button" - -export { Button, buttonVariants } diff --git a/frontend/src/components/ui/command.tsx b/frontend/src/components/ui/command.tsx deleted file mode 100644 index efe1ecc1..00000000 --- a/frontend/src/components/ui/command.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import * as React from "react" -import { type DialogProps } from "@radix-ui/react-dialog" -import { Command as CommandPrimitive } from "cmdk" -import { Search } from "lucide-react" - -import { cn } from "@/lib/utils" -import { Dialog, DialogContent } from "@/components/ui/dialog" - -const Command = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -Command.displayName = CommandPrimitive.displayName - -interface CommandDialogProps extends DialogProps {} - -const CommandDialog = ({ children, ...props }: CommandDialogProps) => { - return ( - - - - {children} - - - - ) -} - -const CommandInput = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- - -
-)) - -CommandInput.displayName = CommandPrimitive.Input.displayName - -const CommandList = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandList.displayName = CommandPrimitive.List.displayName - -const CommandEmpty = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->((props, ref) => ( - -)) - -CommandEmpty.displayName = CommandPrimitive.Empty.displayName - -const CommandGroup = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandGroup.displayName = CommandPrimitive.Group.displayName - -const CommandSeparator = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -CommandSeparator.displayName = CommandPrimitive.Separator.displayName - -const CommandItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) - -CommandItem.displayName = CommandPrimitive.Item.displayName - -const CommandShortcut = ({ - className, - ...props -}: React.HTMLAttributes) => { - return ( - - ) -} -CommandShortcut.displayName = "CommandShortcut" - -export { - Command, - CommandDialog, - CommandInput, - CommandList, - CommandEmpty, - CommandGroup, - CommandItem, - CommandShortcut, - CommandSeparator, -} diff --git a/frontend/src/components/ui/dialog.tsx b/frontend/src/components/ui/dialog.tsx deleted file mode 100644 index fddbfa73..00000000 --- a/frontend/src/components/ui/dialog.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import * as React from "react" -import * as DialogPrimitive from "@radix-ui/react-dialog" -import { X } from "lucide-react" - -import { cn } from "@/lib/utils" - -const Dialog = DialogPrimitive.Root - -const DialogTrigger = DialogPrimitive.Trigger - -const DialogPortal = DialogPrimitive.Portal - -const DialogClose = DialogPrimitive.Close - -const DialogOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogOverlay.displayName = DialogPrimitive.Overlay.displayName - -const DialogContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - - {children} - - - Close - - - -)) -DialogContent.displayName = DialogPrimitive.Content.displayName - -const DialogHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -DialogHeader.displayName = "DialogHeader" - -const DialogFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -DialogFooter.displayName = "DialogFooter" - -const DialogTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogTitle.displayName = DialogPrimitive.Title.displayName - -const DialogDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DialogDescription.displayName = DialogPrimitive.Description.displayName - -export { - Dialog, - DialogPortal, - DialogOverlay, - DialogClose, - DialogTrigger, - DialogContent, - DialogHeader, - DialogFooter, - DialogTitle, - DialogDescription, -} diff --git a/frontend/src/components/ui/drawer.tsx b/frontend/src/components/ui/drawer.tsx deleted file mode 100644 index 5d7e2a4f..00000000 --- a/frontend/src/components/ui/drawer.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import * as React from "react" -import { Drawer as DrawerPrimitive } from "vaul" - -import { cn } from "@/lib/utils" - -const Drawer = ({ - shouldScaleBackground = true, - ...props -}: React.ComponentProps) => ( - -) -Drawer.displayName = "Drawer" - -const DrawerTrigger = DrawerPrimitive.Trigger - -const DrawerPortal = DrawerPrimitive.Portal - -const DrawerClose = DrawerPrimitive.Close - -const DrawerOverlay = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName - -const DrawerContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - - -
- {children} - - -)) -DrawerContent.displayName = "DrawerContent" - -const DrawerHeader = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -DrawerHeader.displayName = "DrawerHeader" - -const DrawerFooter = ({ - className, - ...props -}: React.HTMLAttributes) => ( -
-) -DrawerFooter.displayName = "DrawerFooter" - -const DrawerTitle = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DrawerTitle.displayName = DrawerPrimitive.Title.displayName - -const DrawerDescription = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)) -DrawerDescription.displayName = DrawerPrimitive.Description.displayName - -export { - Drawer, - DrawerPortal, - DrawerOverlay, - DrawerTrigger, - DrawerClose, - DrawerContent, - DrawerHeader, - DrawerFooter, - DrawerTitle, - DrawerDescription, -} diff --git a/frontend/src/components/ui/popover.tsx b/frontend/src/components/ui/popover.tsx deleted file mode 100644 index b118b92f..00000000 --- a/frontend/src/components/ui/popover.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from "react" -import * as PopoverPrimitive from "@radix-ui/react-popover" - -import { cn } from "@/lib/utils" - -const Popover = PopoverPrimitive.Root - -const PopoverTrigger = PopoverPrimitive.Trigger - -const PopoverContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( - - - -)) -PopoverContent.displayName = PopoverPrimitive.Content.displayName - -export { Popover, PopoverTrigger, PopoverContent } diff --git a/frontend/src/contexts/flowContext.tsx b/frontend/src/contexts/flowContext.tsx index 27755027..87a14330 100644 --- a/frontend/src/contexts/flowContext.tsx +++ b/frontend/src/contexts/flowContext.tsx @@ -180,7 +180,6 @@ export const FlowProvider = ({ children }: { children: React.ReactNode }) => { setSlots(slots) setGroups(groups) const parsed_groups = await parseGroups(groups) - console.log(JSON.stringify(parsed_groups, null, 2)) try { await save_flows(flows, parsed_groups) setFlows(flows) diff --git a/frontend/src/icons/DocumentationIcon.tsx b/frontend/src/icons/DocumentationIcon.tsx new file mode 100644 index 00000000..2d14ffe1 --- /dev/null +++ b/frontend/src/icons/DocumentationIcon.tsx @@ -0,0 +1,23 @@ +import React from "react" + +const DocumentationIcon = ({ className }: React.SVGAttributes) => { + return ( + + + + ) +} + +export default DocumentationIcon diff --git a/frontend/src/modals/ConditionModal/ConditionModal.tsx b/frontend/src/modals/ConditionModal/ConditionModal.tsx index d36e42b2..4e851a4e 100644 --- a/frontend/src/modals/ConditionModal/ConditionModal.tsx +++ b/frontend/src/modals/ConditionModal/ConditionModal.tsx @@ -1,3 +1,8 @@ +import ButtonConditionIcon from "@/icons/nodes/conditions/ButtonConditionIcon" +import CodeConditionIcon from "@/icons/nodes/conditions/CodeConditionIcon" +import CustomConditionIcon from "@/icons/nodes/conditions/CustomConditionIcon" +import LLMConditionIcon from "@/icons/nodes/conditions/LLMConditionIcon" +import SlotsConditionIcon from "@/icons/nodes/conditions/SlotsConditionIcon" import { Button, Tab, Tabs } from "@nextui-org/react" import { Edge, useReactFlow } from "@xyflow/react" import classNames from "classnames" @@ -157,27 +162,33 @@ const ConditionModal = ({ const tabItems: { title: ConditionModalTab value: conditionTypeType + icon: JSX.Element }[] = useMemo( () => [ { title: "Python code", value: "python", + icon: , }, { title: "Using LLM", value: "llm", + icon: , }, { title: "Slot filling", value: "slot", + icon: , }, { title: "Button", value: "button", + icon: , }, { title: "Custom", value: "custom", + icon: , }, ], [] @@ -362,7 +373,7 @@ const ConditionModal = ({ onSelectionChange={setSelectedHandler} items={tabItems} classNames={{ - tabList: "w-full", + tabList: "w-full bg-table-background", tab: "", cursor: "border border-contrast-border", }} @@ -370,7 +381,11 @@ const ConditionModal = ({ {(item) => ( + {item.icon} {item.title} +
+ } onClick={() => setCurrentCondition({ ...currentCondition, type: item.value }) }> diff --git a/frontend/src/modals/SlotsModals/SlotsGroupModal.tsx b/frontend/src/modals/SlotsModals/SlotsGroupModal.tsx index 89a4cbff..66897941 100644 --- a/frontend/src/modals/SlotsModals/SlotsGroupModal.tsx +++ b/frontend/src/modals/SlotsModals/SlotsGroupModal.tsx @@ -1,19 +1,20 @@ -import { flowContext } from "@/contexts/flowContext"; +import SlotsGroupsTable from "@/components/nodes/slots/SlotsGroupsTable" +import { flowContext } from "@/contexts/flowContext" import { Button, Switch } from "@nextui-org/react"; // Можно заменить на свой UI-компонент -import { useReactFlow } from "@xyflow/react"; -import { Plus } from "lucide-react"; -import { useContext, useEffect, useState } from "react"; -import { v4 } from "uuid"; -import { NotificationsContext } from "../../contexts/notificationsContext"; -import { PopUpContext } from "../../contexts/popUpContext"; -import SlotsConditionIcon from "../../icons/nodes/conditions/SlotsConditionIcon"; -import { SlotsGroupType, SlotType } from "../../types/FlowTypes"; -import { SlotsNodeDataType } from "../../types/NodeTypes"; -import DefInput from "../../UI/Input/DefInput"; -import DefSelect from "../../UI/Input/DefSelect"; -import { generateNewSlot } from "../../utils"; -import { CustomModalProps, Modal, ModalBody, ModalFooter, ModalHeader } from "../ModalComponents"; -import SlotItem from "./components/SlotItem"; +import { useReactFlow } from "@xyflow/react" +import { Plus } from "lucide-react" +import { useContext, useEffect, useState } from "react" +import { v4 } from "uuid" +import { NotificationsContext } from "../../contexts/notificationsContext" +import { PopUpContext } from "../../contexts/popUpContext" +import SlotsConditionIcon from "../../icons/nodes/conditions/SlotsConditionIcon" +import { SlotsGroupType, SlotType } from "../../types/FlowTypes" +import { SlotsNodeDataType } from "../../types/NodeTypes" +import DefInput from "../../UI/Input/DefInput" +import DefSelect from "../../UI/Input/DefSelect" +import { generateNewSlot } from "../../utils" +import { CustomModalProps, Modal, ModalBody, ModalFooter, ModalHeader } from "../ModalComponents" +import SlotItem from "./components/SlotItem" type SlotsGroupModalType = CustomModalProps & { data: SlotsNodeDataType @@ -30,10 +31,18 @@ const SlotsGroupModal = ({ group, }: SlotsGroupModalType) => { const { updateNodeData } = useReactFlow() - const { closePopUp } = useContext(PopUpContext) + const { closePopUp, openPopUp } = useContext(PopUpContext) const { notification: n } = useContext(NotificationsContext) const { quietSaveFlows } = useContext(flowContext) - const [isSubGroup, setIsSubGroup] = useState(false) + const [nodeData, setNodeData] = useState(data) + const [groups, setGroups] = useState(data.groups ?? []) + const [subgroups, setSubgroups] = useState( + !is_create && group && group.subgroups + ? group.subgroups.map((id) => groups.find((g) => g.id === id)!) + : [] + ) + const [isSubGroup, setIsSubGroup] = useState(!!group?.subgroup_to ?? false) + const [parentGroup, setParentGroup] = useState(null) const [currentGroup, setCurrentGroup] = useState(() => { const id = "group_" + v4() return ( @@ -62,6 +71,11 @@ const SlotsGroupModal = ({ })) } + useEffect(() => { + setGroups(nodeData.groups ?? []) + setSubgroups(nodeData.groups.filter((g) => g.subgroup_to === group?.id) ?? []) + }, [nodeData]) + const onSave = () => { if ( !currentGroup.name || @@ -74,16 +88,21 @@ const SlotsGroupModal = ({ }) } else { const newData = { - ...data, + ...nodeData, groups: is_create - ? [...data.groups, currentGroup] - : data.groups.map((g: SlotsGroupType) => (g.id === currentGroup.id ? currentGroup : g)), + ? [ + ...groups.map((g: SlotsGroupType) => (g.id === parentGroup?.id ? parentGroup : g)), + currentGroup, + ] + : groups.map((g: SlotsGroupType) => + g.id === currentGroup.id ? currentGroup : g.id === parentGroup?.id ? parentGroup : g + ), } updateNodeData(data.id, newData) setData(() => newData) } } - + const onSaveHandler = () => { onSave() quietSaveFlows() @@ -94,6 +113,21 @@ const SlotsGroupModal = ({ closePopUp(id) } + const onDeleteTableGroupHandler = (group: SlotsGroupType, updatedGroups: SlotsGroupType[]) => { + setCurrentGroup((prev) => ({ + ...prev, + subgroups: updatedGroups.map((g) => g.id), + })) + setGroups((prev) => prev.map((g) => (g.id === group.id ? { ...g, subgroup_to: "" } : g))) + } + + useEffect(() => { + if (!isSubGroup) { + setParentGroup(null) + setCurrentGroup({ ...currentGroup, subgroup_to: "" }) + } + }, [isSubGroup]) + return ( setCurrentGroup({ ...currentGroup, name })} /> - {data.groups.length >= (is_create ? 1 : 2) && ( -
+ {groups.length >= (is_create ? 1 : 2) && ( +

Standalone

{isSubGroup && ( - setCurrentGroup({ ...currentGroup, subgroup_to: value }) - } + mini + defaultValue={parentGroup?.name ?? ""} + onValueChange={(value) => { + const parent_group = nodeData.groups.find((g) => g.name === value) + if (parent_group) { + setParentGroup({ + ...parent_group, + subgroups: [...(parent_group.subgroups ?? []), currentGroup.id], + }) + setCurrentGroup({ ...currentGroup, subgroup_to: parent_group.id }) + } + }} placeholder='Select parent group' - className='w-1/3' - items={data.groups + className='w-1/3 h-8 min-h-8' + items={nodeData.groups .filter((g) => g.id !== currentGroup.id) .map((g) => ({ key: g.name, value: g.name }))} /> @@ -141,7 +183,19 @@ const SlotsGroupModal = ({
)}
-
+ {!is_create && subgroups.length > 0 && ( +
+

Subgroups

+ +
+ )} +
{currentGroup.slots.map((slot) => ( > } - const SlotsNodeModal = ({ id = "slots-node-modal", data, setData }: SlotsNodeModalType) => { const [nodeData, setNodeData] = useState(data) const { updateNodeData } = useReactFlow() @@ -44,24 +39,6 @@ const SlotsNodeModal = ({ id = "slots-node-modal", data, setData }: SlotsNodeMod updateNodeData(data.id, newData) } - const handleDeleteGroup = (group: SlotsGroupType) => { - // Удаление группы - openPopUp( - { - const updatedGroups = nodeData.groups.filter((g) => g.id !== group.id) - setGroups(updatedGroups) - }} - actionText='Delete' - />, - "delete-group-modal" - ) - } - const handleNewGroupModalOpen = () => { openPopUp( { - openPopUp( - , - "slots-group-modal-edit" - ) - } - const onSaveHandler = () => { onSave() quietSaveFlows() @@ -119,46 +83,12 @@ const SlotsNodeModal = ({ id = "slots-node-modal", data, setData }: SlotsNodeMod
- - {groups.map((group) => ( - - - {group.name} - -
    - {group.slots.slice(0, 3).map((slot, idx) => ( - - {idx === 2 && group.slots.length > 3 ? ( -
  • + {group.slots.length - 2} more
  • - ) : ( - idx === 2 &&
  • {slot.name}
  • - )} - {idx !== 2 &&
  • {slot.name}
  • } -
    - ))} -
-
- - - - -
- ))} -
+
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts index a43d5393..dbac4c46 100644 --- a/frontend/src/utils.ts +++ b/frontend/src/utils.ts @@ -196,10 +196,10 @@ export async function parseGroups( // Если у группы есть подгруппы, обрабатываем их рекурсивно if (group.subgroups) { - group.subgroups.forEach((subgroupName) => { - const subgroup = groups.find((g) => g.name === subgroupName); + group.subgroups.forEach((subgroupId) => { + const subgroup = groups.find((g) => g.id === subgroupId); if (subgroup) { - groupData[subgroupName] = processGroup(subgroup); + groupData[subgroup.name] = processGroup(subgroup); } }); }