Refactor: 사이드바 유틸 분리 및 의존성 줄임

This commit is contained in:
정현조 2024-10-04 13:21:37 +09:00
parent 25bd5fc42c
commit 63d2b806e8
4 changed files with 116 additions and 103 deletions

View File

@ -15,7 +15,7 @@ import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend'; import { HTML5Backend } from 'react-dnd-html5-backend';
import useFolderQuery from '@/queries/folders/useFolderQuery'; import useFolderQuery from '@/queries/folders/useFolderQuery';
import MemoFileStatusIcon from './FileStatusIcon'; import MemoFileStatusIcon from './FileStatusIcon';
import moveNodeInTree from '@/utils/moveNodeInTree';
interface FlatNode extends TreeNode { interface FlatNode extends TreeNode {
depth: number; depth: number;
isLeaf: boolean; isLeaf: boolean;
@ -114,25 +114,10 @@ export default function ProjectStructure({ project }: { project: Project }) {
const moveNode = useCallback( const moveNode = useCallback(
(dragItem: FlatNode, hoverItem: FlatNode) => { (dragItem: FlatNode, hoverItem: FlatNode) => {
const updatedTreeData = (function moveNodeInTree(node: TreeNode): TreeNode { setTreeData(moveNodeInTree(treeData!, dragItem, hoverItem));
if (node.id === dragItem.parent?.id) {
const newChildren = node.children?.filter((child) => child.id !== dragItem.id) || [];
return { ...node, children: newChildren };
}
if (node.id === hoverItem.parent?.id) {
const newChildren = [...(node.children || [])];
const hoverIndex = newChildren.findIndex((child) => child.id === hoverItem.id);
newChildren.splice(hoverIndex, 0, { ...dragItem, parent: hoverItem.parent } as FlatNode);
return { ...node, children: newChildren };
}
return node.children ? { ...node, children: node.children.map(moveNodeInTree) } : node;
})(treeData!);
setTreeData(updatedTreeData);
if (!dragItem.imageData) return; if (!dragItem.imageData) return;
moveImageMutation.mutate({ moveImageMutation.mutate({
projectId: project.id, projectId: project.id,
folderId: Number(dragItem.parent?.id), folderId: Number(dragItem.parent?.id),
@ -222,13 +207,12 @@ export default function ProjectStructure({ project }: { project: Project }) {
onRefetch={refetch} onRefetch={refetch}
/> />
</header> </header>
{isLoading ? ( {isLoading || !treeData ? (
<div className="flex h-full items-center justify-center"> <div className="flex h-full items-center justify-center">
<Spinner /> <Spinner
</div> show={true}
) : !treeData ? ( size={'large'}
<div className="body-small flex h-full select-none items-center justify-center text-gray-400"> />
Loading...
</div> </div>
) : ( ) : (
<List <List

View File

@ -1,22 +1,15 @@
import { useState, useCallback, useEffect } from 'react'; import { useState, useCallback, useEffect } from 'react';
import { TreeNode } from 'react-treebeard'; import { TreeNode } from 'react-treebeard';
import { ImageResponse, ChildFolder } from '@/types'; import { FolderResponse } from '@/types';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { getFolder } from '@/api/folderApi'; import { getFolder } from '@/api/folderApi';
import buildTreeNodes from '@/utils/buildTreeNodes';
function useFolder(projectId: string, folderId: number) { function useFolder(projectId: string, folderId: number, enabled: boolean = folderId === 0) {
return useQuery({ return useQuery({
queryKey: ['folder', projectId, folderId], queryKey: ['folder', projectId, folderId],
queryFn: () => getFolder(projectId, folderId), queryFn: () => getFolder(projectId, folderId),
enabled: folderId === 0, enabled,
});
}
function useChildFolder(projectId: string, folderId: number, enabled: boolean) {
return useQuery({
queryKey: ['folder', projectId, folderId],
queryFn: () => getFolder(projectId, folderId),
enabled: enabled,
}); });
} }
@ -25,87 +18,62 @@ export default function useTreeData(projectId: string) {
const [currentFolderId, setCurrentFolderId] = useState<number | null>(null); const [currentFolderId, setCurrentFolderId] = useState<number | null>(null);
const { data: rootFolder, isLoading: isRootLoading } = useFolder(projectId, 0); const { data: rootFolder, isLoading: isRootLoading } = useFolder(projectId, 0);
const { data: childFolder, isFetching: isChildLoading } = useFolder(
const { data: childFolder, isFetching: isChildLoading } = useChildFolder(
projectId, projectId,
currentFolderId || 0, currentFolderId || 0,
currentFolderId !== null currentFolderId !== null
); );
useEffect(() => { const updateTreeData = useCallback((folder: FolderResponse, isRoot: boolean = false) => {
console.log('root changed'); if (!folder) return;
if (rootFolder) {
const childFolders: TreeNode[] =
rootFolder.children?.map((child: ChildFolder) => ({
id: child.id.toString(),
name: child.title,
children: [],
})) || [];
const images: TreeNode[] = const childNodes = buildTreeNodes(folder);
rootFolder.images?.map((image: ImageResponse) => ({
id: image.id.toString(),
name: image.imageTitle,
imageData: image,
children: [],
})) || [];
const rootNode: TreeNode = { setTreeData((prevData) => {
id: rootFolder.id.toString(), if (isRoot || !prevData) {
name: rootFolder.title, return {
children: [...childFolders, ...images], id: folder.id.toString(),
toggled: true, name: folder.title,
children: childNodes,
toggled: true,
};
}
const updateNode = (currentNode: TreeNode): TreeNode => {
if (currentNode.id !== folder.id.toString()) {
return currentNode.children
? { ...currentNode, children: currentNode.children.map(updateNode) }
: currentNode;
}
return {
...currentNode,
children: childNodes,
toggled: true,
};
}; };
setTreeData(rootNode); return updateNode(prevData);
} });
}, [rootFolder]); }, []);
useEffect(() => { useEffect(() => {
if (childFolder && currentFolderId !== null) { if (!rootFolder) return;
const childFolders: TreeNode[] = updateTreeData(rootFolder, true);
childFolder.children?.map((child: ChildFolder) => ({ }, [rootFolder, updateTreeData]);
id: child.id.toString(),
name: child.title,
children: [],
})) || [];
const images: TreeNode[] = useEffect(() => {
childFolder.images?.map((image: ImageResponse) => ({ if (!childFolder || currentFolderId === null) return;
id: image.id.toString(), updateTreeData(childFolder);
name: image.imageTitle, }, [childFolder, currentFolderId, updateTreeData]);
imageData: image,
children: [],
})) || [];
setTreeData((prevData) => { const fetchNodeData = useCallback(
if (!prevData) return null; (node: TreeNode) => {
if (currentFolderId === Number(node.id)) return;
const updateNode = (currentNode: TreeNode): TreeNode => { setCurrentFolderId(Number(node.id));
if (currentNode.id === currentFolderId.toString()) { },
return { [currentFolderId]
...currentNode, );
children: [...childFolders, ...images],
toggled: true,
};
}
if (currentNode.children) {
return {
...currentNode,
children: currentNode.children.map(updateNode),
};
}
return currentNode;
};
return updateNode(prevData);
});
}
}, [childFolder, currentFolderId]);
const fetchNodeData = useCallback((node: TreeNode) => {
setCurrentFolderId(Number(node.id));
}, []);
return { return {
treeData, treeData,

View File

@ -0,0 +1,19 @@
import { FolderResponse, ChildFolder, ImageResponse } from '@/types';
import { TreeNode } from 'react-treebeard';
export default function buildTreeNodes(folder: FolderResponse): TreeNode[] {
const childFolders: TreeNode[] = folder.children.map((child: ChildFolder) => ({
id: child.id.toString(),
name: child.title,
children: [],
}));
const images: TreeNode[] = folder.images.map((image: ImageResponse) => ({
id: image.id.toString(),
name: image.imageTitle,
imageData: image,
children: [],
}));
return [...childFolders, ...images];
}

View File

@ -0,0 +1,42 @@
import { TreeNode } from 'react-treebeard';
interface FlatNode extends TreeNode {
depth: number;
isLeaf: boolean;
parent?: FlatNode;
index?: number;
}
export default function moveNodeInTree(treeData: TreeNode, dragItem: FlatNode, hoverItem: FlatNode): TreeNode {
if (!treeData) return treeData;
// 드래그된 항목과 호버된 항목이 동일한 경우 이동 처리하지 않음
if (dragItem.id === hoverItem.id) return treeData;
// 부모 노드가 동일한 경우 중복 이동 처리하지 않음
if (dragItem.parent?.id === hoverItem.parent?.id) return treeData;
const updateTreeData = (node: TreeNode): TreeNode => {
// 드래그된 노드의 부모에서 노드를 제거
if (node.id === dragItem.parent?.id) {
const newChildren = node.children?.filter((child) => child.id !== dragItem.id) || [];
return { ...node, children: newChildren };
}
// 호버된 노드의 부모에 드래그된 노드 추가
if (node.id === hoverItem.parent?.id) {
const newChildren = [...(node.children || [])];
const hoverIndex = newChildren.findIndex((child) => child.id === hoverItem.id);
if (hoverIndex !== -1) {
newChildren.splice(hoverIndex, 0, { ...dragItem, parent: hoverItem.parent } as FlatNode);
}
return { ...node, children: newChildren };
}
// 자식 노드가 존재하는 경우 자식 노드들을 순환하면서 업데이트
return node.children ? { ...node, children: node.children.map(updateTreeData) } : node;
};
return updateTreeData(treeData);
}