From e44d443088ef688ef183d46a1ffdc25344ba504b Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Fri, 4 Oct 2024 11:20:21 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?structure=20=EC=BD=94=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ImageCanvas/index.tsx | 2 + .../WorkspaceSidebar/AutoLabelButton.tsx | 4 +- .../WorkspaceSidebar/FileStatusIcon.tsx | 41 +++++ .../WorkspaceSidebar/ProjectFileItem.tsx | 35 +--- .../WorkspaceSidebar/ProjectStructure.tsx | 157 +++++------------- frontend/src/hooks/useTreeData.ts | 5 +- frontend/src/stores/useProjectStore.ts | 9 +- 7 files changed, 99 insertions(+), 154 deletions(-) create mode 100644 frontend/src/components/WorkspaceSidebar/FileStatusIcon.tsx diff --git a/frontend/src/components/ImageCanvas/index.tsx b/frontend/src/components/ImageCanvas/index.tsx index 485234d..9c4783a 100644 --- a/frontend/src/components/ImageCanvas/index.tsx +++ b/frontend/src/components/ImageCanvas/index.tsx @@ -100,11 +100,13 @@ export default function ImageCanvas() { queryClient.invalidateQueries({ queryKey: ['folder', project!.id.toString(), folderId] }); toast({ title: '저장 성공', + duration: 1500, }); }, onError: () => { toast({ title: '저장 실패', + duration: 1500, }); }, } diff --git a/frontend/src/components/WorkspaceSidebar/AutoLabelButton.tsx b/frontend/src/components/WorkspaceSidebar/AutoLabelButton.tsx index 8c29d98..3da5fab 100644 --- a/frontend/src/components/WorkspaceSidebar/AutoLabelButton.tsx +++ b/frontend/src/components/WorkspaceSidebar/AutoLabelButton.tsx @@ -50,10 +50,10 @@ export default function AutoLabelButton({ projectId }: { projectId: number }) { onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['folder', projectId.toString()] }); queryClient.invalidateQueries({ queryKey: ['labelJson'] }); - toast({ title: '레이블링 성공' }); + toast({ title: '레이블링 성공', duration: 1500 }); }, onError: () => { - toast({ title: '레이블링 중 오류가 발생했습니다.' }); + toast({ title: '레이블링 중 오류가 발생했습니다.', duration: 1500 }); }, } ); diff --git a/frontend/src/components/WorkspaceSidebar/FileStatusIcon.tsx b/frontend/src/components/WorkspaceSidebar/FileStatusIcon.tsx new file mode 100644 index 0000000..7185365 --- /dev/null +++ b/frontend/src/components/WorkspaceSidebar/FileStatusIcon.tsx @@ -0,0 +1,41 @@ +import { ImageStatus } from '@/types'; +import { Minus, Loader, ArrowDownToLine, Send, CircleSlash, Check } from 'lucide-react'; +import React from 'react'; + +function FileStatusIcon({ imageStatus }: { imageStatus: ImageStatus }) { + return imageStatus === 'PENDING' ? ( + + ) : imageStatus === 'IN_PROGRESS' ? ( + + ) : imageStatus === 'SAVE' ? ( + + ) : imageStatus === 'REVIEW_REQUEST' ? ( + + ) : imageStatus === 'REVIEW_REJECT' ? ( + + ) : ( + + ); +} + +const MemoFileStatusIcon = React.memo(FileStatusIcon); + +export default MemoFileStatusIcon; diff --git a/frontend/src/components/WorkspaceSidebar/ProjectFileItem.tsx b/frontend/src/components/WorkspaceSidebar/ProjectFileItem.tsx index 12d4090..b6b9a68 100644 --- a/frontend/src/components/WorkspaceSidebar/ProjectFileItem.tsx +++ b/frontend/src/components/WorkspaceSidebar/ProjectFileItem.tsx @@ -1,7 +1,8 @@ import { cn } from '@/lib/utils'; import { ImageResponse } from '@/types'; -import { ArrowDownToLine, Check, CircleSlash, Image, Loader, Minus, Send } from 'lucide-react'; +import { Image } from 'lucide-react'; import useCanvasStore from '@/stores/useCanvasStore'; +import MemoFileStatusIcon from './FileStatusIcon'; export default function ProjectFileItem({ className = '', @@ -40,37 +41,7 @@ export default function ProjectFileItem({ /> {item.imageTitle} - {item.status === 'PENDING' ? ( - - ) : item.status === 'IN_PROGRESS' ? ( - - ) : item.status === 'SAVE' ? ( - - ) : item.status === 'REVIEW_REQUEST' ? ( - - ) : item.status === 'REVIEW_REJECT' ? ( - - ) : ( - - )} + ); } diff --git a/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx b/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx index e9bcebf..d41bb30 100644 --- a/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx +++ b/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx @@ -8,13 +8,13 @@ import useMoveImageQuery from '@/queries/images/useMoveImageQuery'; import { Project, ImageResponse } from '@/types'; import WorkspaceDropdownMenu from '../WorkspaceDropdownMenu'; import AutoLabelButton from './AutoLabelButton'; -import { Folder, Image as ImageIcon, Minus, Loader, ArrowDownToLine, Send, CircleSlash, Check } from 'lucide-react'; +import { Folder, Image as ImageIcon } from 'lucide-react'; import { Spinner } from '../ui/spinner'; -import { ImageStatus } from '@/types'; import { FixedSizeList as List, ListChildComponentProps } from 'react-window'; import { DndProvider, useDrag, useDrop } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import useFolderQuery from '@/queries/folders/useFolderQuery'; +import MemoFileStatusIcon from './FileStatusIcon'; interface FlatNode extends TreeNode { depth: number; @@ -57,26 +57,19 @@ export default function ProjectStructure({ project }: { project: Project }) { const onToggle = useCallback( async (node: TreeNode, toggled: boolean) => { - if (!node.imageData) { - if (toggled && (!node.children || node.children.length === 0)) { - await fetchNodeData(node); - } - - const updateNode = (currentNode: TreeNode): TreeNode => { - if (currentNode.id === node.id) { - return { ...currentNode, toggled }; - } - if (currentNode.children) { - return { - ...currentNode, - children: currentNode.children.map(updateNode), - }; - } - return currentNode; - }; - - setTreeData((prevData) => prevData && updateNode(prevData)); + if (node.imageData) return; + if (toggled && (!node.children || node.children.length === 0)) { + await fetchNodeData(node); } + + const updateNode = (currentNode: TreeNode): TreeNode => { + if (currentNode.id === node.id) { + return { ...currentNode, toggled }; + } + return currentNode.children ? { ...currentNode, children: currentNode.children.map(updateNode) } : currentNode; + }; + + setTreeData((prevData) => prevData && updateNode(prevData)); }, [fetchNodeData, setTreeData] ); @@ -91,59 +84,6 @@ export default function ProjectStructure({ project }: { project: Project }) { [setImage, setFolderId] ); - const renderStatusIcon = (status: ImageStatus) => { - const iconProps = { size: 12, className: 'shrink-0' }; - const iconColor = { - PENDING: 'stroke-gray-400', - IN_PROGRESS: 'animate-spin stroke-yellow-400', - SAVE: 'stroke-gray-400', - REVIEW_REQUEST: 'stroke-blue-400', - REVIEW_REJECT: 'stroke-red-400', - COMPLETED: 'stroke-green-400', - }; - - const iconMapping = { - PENDING: ( - - ), - IN_PROGRESS: ( - - ), - SAVE: ( - - ), - REVIEW_REQUEST: ( - - ), - REVIEW_REJECT: ( - - ), - COMPLETED: ( - - ), - }; - - return iconMapping[status] || null; - }; - const flattenTree = useCallback((nodes: TreeNode[], depth: number = 0, parent?: FlatNode): FlatNode[] => { let flatList: FlatNode[] = []; @@ -187,47 +127,34 @@ export default function ProjectStructure({ project }: { project: Project }) { return { ...node, children: newChildren }; } - if (node.children) { - return { - ...node, - children: node.children.map(moveNodeInTree), - }; - } - - return node; + return node.children ? { ...node, children: node.children.map(moveNodeInTree) } : node; })(treeData!); setTreeData(updatedTreeData); - if (dragItem.imageData) { - moveImageMutation.mutate({ - projectId: project.id, - folderId: Number(dragItem.parent?.id), - imageId: dragItem.imageData.id, - moveRequest: { - moveFolderId: Number(hoverItem.parent?.id), - }, - }); - } + if (!dragItem.imageData) return; + moveImageMutation.mutate({ + projectId: project.id, + folderId: Number(dragItem.parent?.id), + imageId: dragItem.imageData.id, + moveRequest: { + moveFolderId: Number(hoverItem.parent?.id), + }, + }); }, [treeData, setTreeData, moveImageMutation, project.id] ); const Row = ({ index, style, data }: ListChildComponentProps) => { const node = data[index]; - const ref = useRef(null); + const ref = useRef(null); const [, drop] = useDrop({ accept: ItemTypes.NODE, drop(item: FlatNode) { - const dragItem = item; - const hoverItem = node; + if (item.id === node.id) return; - if (dragItem.id === hoverItem.id) { - return; - } - - moveNode(dragItem, hoverItem); + moveNode(item, node); }, }); @@ -242,17 +169,13 @@ export default function ProjectStructure({ project }: { project: Project }) { drag(drop(ref)); return ( -
{ if (node.imageData) { handleImageClick(node.imageData as ImageResponse, node.parent); @@ -261,7 +184,7 @@ export default function ProjectStructure({ project }: { project: Project }) { } }} > -
+
{!node.imageData ? ( )}
- {node.name} - {node.imageData &&
{renderStatusIcon(node.imageData.status)}
} -
+ + {node.name} + + {node.imageData && } + ); }; return (
@@ -308,14 +232,13 @@ export default function ProjectStructure({ project }: { project: Project }) {
) : ( {Row} diff --git a/frontend/src/hooks/useTreeData.ts b/frontend/src/hooks/useTreeData.ts index 20b97c8..0898676 100644 --- a/frontend/src/hooks/useTreeData.ts +++ b/frontend/src/hooks/useTreeData.ts @@ -4,7 +4,7 @@ import { ImageResponse, ChildFolder } from '@/types'; import { useQuery } from '@tanstack/react-query'; import { getFolder } from '@/api/folderApi'; -export function useFolder(projectId: string, folderId: number) { +function useFolder(projectId: string, folderId: number) { return useQuery({ queryKey: ['folder', projectId, folderId], queryFn: () => getFolder(projectId, folderId), @@ -12,7 +12,7 @@ export function useFolder(projectId: string, folderId: number) { }); } -export function useChildFolder(projectId: string, folderId: number, enabled: boolean) { +function useChildFolder(projectId: string, folderId: number, enabled: boolean) { return useQuery({ queryKey: ['folder', projectId, folderId], queryFn: () => getFolder(projectId, folderId), @@ -33,6 +33,7 @@ export default function useTreeData(projectId: string) { ); useEffect(() => { + console.log('root changed'); if (rootFolder) { const childFolders: TreeNode[] = rootFolder.children?.map((child: ChildFolder) => ({ diff --git a/frontend/src/stores/useProjectStore.ts b/frontend/src/stores/useProjectStore.ts index 6937735..adcb969 100644 --- a/frontend/src/stores/useProjectStore.ts +++ b/frontend/src/stores/useProjectStore.ts @@ -1,5 +1,5 @@ import { create } from 'zustand'; -import { LabelCategoryResponse, Project } from '@/types'; +import { FolderResponse, LabelCategoryResponse, Project } from '@/types'; interface ProjectState { project: Project | null; @@ -8,6 +8,7 @@ interface ProjectState { setFolderId: (folderId: number) => void; categories: LabelCategoryResponse[]; setCategories: (categories: LabelCategoryResponse[]) => void; + projectFolder: FolderResponse; } const useProjectStore = create((set) => ({ @@ -16,6 +17,12 @@ const useProjectStore = create((set) => ({ folderId: 0, setFolderId: (folderId) => set({ folderId }), categories: [], + projectFolder: { + id: 0, + title: '', + children: [], + images: [], + }, setCategories: (categories) => set({ categories }), }));