Refactor: 사이드바 유틸 분리 및 의존성 줄임
This commit is contained in:
parent
25bd5fc42c
commit
63d2b806e8
@ -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
|
||||||
|
@ -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,88 +18,63 @@ 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 = {
|
|
||||||
id: rootFolder.id.toString(),
|
|
||||||
name: rootFolder.title,
|
|
||||||
children: [...childFolders, ...images],
|
|
||||||
toggled: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
setTreeData(rootNode);
|
|
||||||
}
|
|
||||||
}, [rootFolder]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (childFolder && currentFolderId !== null) {
|
|
||||||
const childFolders: TreeNode[] =
|
|
||||||
childFolder.children?.map((child: ChildFolder) => ({
|
|
||||||
id: child.id.toString(),
|
|
||||||
name: child.title,
|
|
||||||
children: [],
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
const images: TreeNode[] =
|
|
||||||
childFolder.images?.map((image: ImageResponse) => ({
|
|
||||||
id: image.id.toString(),
|
|
||||||
name: image.imageTitle,
|
|
||||||
imageData: image,
|
|
||||||
children: [],
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
setTreeData((prevData) => {
|
setTreeData((prevData) => {
|
||||||
if (!prevData) return null;
|
if (isRoot || !prevData) {
|
||||||
|
|
||||||
const updateNode = (currentNode: TreeNode): TreeNode => {
|
|
||||||
if (currentNode.id === currentFolderId.toString()) {
|
|
||||||
return {
|
return {
|
||||||
...currentNode,
|
id: folder.id.toString(),
|
||||||
children: [...childFolders, ...images],
|
name: folder.title,
|
||||||
|
children: childNodes,
|
||||||
toggled: true,
|
toggled: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (currentNode.children) {
|
|
||||||
|
const updateNode = (currentNode: TreeNode): TreeNode => {
|
||||||
|
if (currentNode.id !== folder.id.toString()) {
|
||||||
|
return currentNode.children
|
||||||
|
? { ...currentNode, children: currentNode.children.map(updateNode) }
|
||||||
|
: currentNode;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...currentNode,
|
...currentNode,
|
||||||
children: currentNode.children.map(updateNode),
|
children: childNodes,
|
||||||
|
toggled: true,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
return currentNode;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return updateNode(prevData);
|
return updateNode(prevData);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}, [childFolder, currentFolderId]);
|
|
||||||
|
|
||||||
const fetchNodeData = useCallback((node: TreeNode) => {
|
|
||||||
setCurrentFolderId(Number(node.id));
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!rootFolder) return;
|
||||||
|
updateTreeData(rootFolder, true);
|
||||||
|
}, [rootFolder, updateTreeData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!childFolder || currentFolderId === null) return;
|
||||||
|
updateTreeData(childFolder);
|
||||||
|
}, [childFolder, currentFolderId, updateTreeData]);
|
||||||
|
|
||||||
|
const fetchNodeData = useCallback(
|
||||||
|
(node: TreeNode) => {
|
||||||
|
if (currentFolderId === Number(node.id)) return;
|
||||||
|
setCurrentFolderId(Number(node.id));
|
||||||
|
},
|
||||||
|
[currentFolderId]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
treeData,
|
treeData,
|
||||||
fetchNodeData,
|
fetchNodeData,
|
||||||
|
19
frontend/src/utils/buildTreeNodes.ts
Normal file
19
frontend/src/utils/buildTreeNodes.ts
Normal 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];
|
||||||
|
}
|
42
frontend/src/utils/moveNodeInTree.ts
Normal file
42
frontend/src/utils/moveNodeInTree.ts
Normal 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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user