diff --git a/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx b/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx
index d41bb30..18d3f8d 100644
--- a/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx
+++ b/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx
@@ -15,7 +15,7 @@ import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import useFolderQuery from '@/queries/folders/useFolderQuery';
import MemoFileStatusIcon from './FileStatusIcon';
-
+import moveNodeInTree from '@/utils/moveNodeInTree';
interface FlatNode extends TreeNode {
depth: number;
isLeaf: boolean;
@@ -114,25 +114,10 @@ export default function ProjectStructure({ project }: { project: Project }) {
const moveNode = useCallback(
(dragItem: FlatNode, hoverItem: FlatNode) => {
- const updatedTreeData = (function moveNodeInTree(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);
- 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);
+ setTreeData(moveNodeInTree(treeData!, dragItem, hoverItem));
if (!dragItem.imageData) return;
+
moveImageMutation.mutate({
projectId: project.id,
folderId: Number(dragItem.parent?.id),
@@ -222,13 +207,12 @@ export default function ProjectStructure({ project }: { project: Project }) {
onRefetch={refetch}
/>
- {isLoading ? (
+ {isLoading || !treeData ? (
-
-
- ) : !treeData ? (
-
- Loading...
+
) : (
getFolder(projectId, folderId),
- enabled: folderId === 0,
- });
-}
-
-function useChildFolder(projectId: string, folderId: number, enabled: boolean) {
- return useQuery({
- queryKey: ['folder', projectId, folderId],
- queryFn: () => getFolder(projectId, folderId),
- enabled: enabled,
+ enabled,
});
}
@@ -25,87 +18,62 @@ export default function useTreeData(projectId: string) {
const [currentFolderId, setCurrentFolderId] = useState(null);
const { data: rootFolder, isLoading: isRootLoading } = useFolder(projectId, 0);
-
- const { data: childFolder, isFetching: isChildLoading } = useChildFolder(
+ const { data: childFolder, isFetching: isChildLoading } = useFolder(
projectId,
currentFolderId || 0,
currentFolderId !== null
);
- useEffect(() => {
- console.log('root changed');
- if (rootFolder) {
- const childFolders: TreeNode[] =
- rootFolder.children?.map((child: ChildFolder) => ({
- id: child.id.toString(),
- name: child.title,
- children: [],
- })) || [];
+ const updateTreeData = useCallback((folder: FolderResponse, isRoot: boolean = false) => {
+ if (!folder) return;
- const images: TreeNode[] =
- rootFolder.images?.map((image: ImageResponse) => ({
- id: image.id.toString(),
- name: image.imageTitle,
- imageData: image,
- children: [],
- })) || [];
+ const childNodes = buildTreeNodes(folder);
- const rootNode: TreeNode = {
- id: rootFolder.id.toString(),
- name: rootFolder.title,
- children: [...childFolders, ...images],
- toggled: true,
+ setTreeData((prevData) => {
+ if (isRoot || !prevData) {
+ return {
+ id: folder.id.toString(),
+ 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);
- }
- }, [rootFolder]);
+ return updateNode(prevData);
+ });
+ }, []);
useEffect(() => {
- if (childFolder && currentFolderId !== null) {
- const childFolders: TreeNode[] =
- childFolder.children?.map((child: ChildFolder) => ({
- id: child.id.toString(),
- name: child.title,
- children: [],
- })) || [];
+ if (!rootFolder) return;
+ updateTreeData(rootFolder, true);
+ }, [rootFolder, updateTreeData]);
- const images: TreeNode[] =
- childFolder.images?.map((image: ImageResponse) => ({
- id: image.id.toString(),
- name: image.imageTitle,
- imageData: image,
- children: [],
- })) || [];
+ useEffect(() => {
+ if (!childFolder || currentFolderId === null) return;
+ updateTreeData(childFolder);
+ }, [childFolder, currentFolderId, updateTreeData]);
- setTreeData((prevData) => {
- if (!prevData) return null;
-
- const updateNode = (currentNode: TreeNode): TreeNode => {
- if (currentNode.id === currentFolderId.toString()) {
- return {
- ...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));
- }, []);
+ const fetchNodeData = useCallback(
+ (node: TreeNode) => {
+ if (currentFolderId === Number(node.id)) return;
+ setCurrentFolderId(Number(node.id));
+ },
+ [currentFolderId]
+ );
return {
treeData,
diff --git a/frontend/src/utils/buildTreeNodes.ts b/frontend/src/utils/buildTreeNodes.ts
new file mode 100644
index 0000000..d5f5a7b
--- /dev/null
+++ b/frontend/src/utils/buildTreeNodes.ts
@@ -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];
+}
diff --git a/frontend/src/utils/moveNodeInTree.ts b/frontend/src/utils/moveNodeInTree.ts
new file mode 100644
index 0000000..3ea6ec0
--- /dev/null
+++ b/frontend/src/utils/moveNodeInTree.ts
@@ -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);
+}