}
+ >
{imageUrl ? (
=> {
+ return await apiClient
+ .put(`structures/picks/${pickId}`, {
+ json: structure,
+ })
+ .json();
+};
+
+export const getPicksByParentId = async (
+ parentId: string
+): Promise => {
+ try {
+ return await apiClient.get(`picks?parentId=${parentId}`).json();
+ } catch (error) {
+ console.error('Error fetching picks by parent ID:', error);
+ throw error;
+ }
+};
+
+export const deletePick = async ({
+ pickId,
+ structure,
+}: {
+ pickId: string;
+ structure: object;
+}): Promise => {
+ return await apiClient
+ .delete(`structures/picks/${pickId}`, {
+ json: structure,
+ })
+ .json();
+};
+
+export const getPickDetail = async (pickId: string): Promise => {
+ try {
+ return await apiClient.get(`/api/picks/${pickId}`).json();
+ } catch (error) {
+ console.error('Failed to fetch pick details:', error);
+ throw error;
+ }
+};
diff --git a/frontend/techpick/src/features/nodeManagement/api/pick/useDeletePick.ts b/frontend/techpick/src/features/nodeManagement/api/pick/useDeletePick.ts
new file mode 100644
index 00000000..b8dfa3f1
--- /dev/null
+++ b/frontend/techpick/src/features/nodeManagement/api/pick/useDeletePick.ts
@@ -0,0 +1,8 @@
+import { useMutation } from '@tanstack/react-query';
+import { deletePick } from '@/features/nodeManagement/api/pick/pickQueryFunctions';
+
+export const useDeletePick = () => {
+ return useMutation({
+ mutationFn: deletePick,
+ });
+};
diff --git a/frontend/techpick/src/features/nodeManagement/api/pick/useGetPicksByParentId.ts b/frontend/techpick/src/features/nodeManagement/api/pick/useGetPicksByParentId.ts
new file mode 100644
index 00000000..5cb9891b
--- /dev/null
+++ b/frontend/techpick/src/features/nodeManagement/api/pick/useGetPicksByParentId.ts
@@ -0,0 +1,11 @@
+import { getPicksByParentId } from '@/features/nodeManagement/api/pick/pickQueryFunctions';
+import { useQuery } from '@tanstack/react-query';
+import { ApiPickData } from '@/shared/types/ApiTypes';
+
+export const useGetPicksByParentId = (parentId: string) => {
+ return useQuery({
+ queryKey: ['picksByParentId', parentId],
+ queryFn: async () => await getPicksByParentId(parentId),
+ enabled: !!parentId,
+ });
+};
diff --git a/frontend/techpick/src/features/nodeManagement/api/pick/useMovePick.ts b/frontend/techpick/src/features/nodeManagement/api/pick/useMovePick.ts
new file mode 100644
index 00000000..5dc0a285
--- /dev/null
+++ b/frontend/techpick/src/features/nodeManagement/api/pick/useMovePick.ts
@@ -0,0 +1,8 @@
+import { useMutation } from '@tanstack/react-query';
+import { putPickMove } from '@/features/nodeManagement/api/pick/pickQueryFunctions';
+
+export const useMovePick = () => {
+ return useMutation({
+ mutationFn: putPickMove,
+ });
+};
diff --git a/frontend/techpick/src/features/nodeManagement/hooks/useDragHook.ts b/frontend/techpick/src/features/nodeManagement/hooks/useDragHook.ts
index 5b24c6b9..84cc0ca2 100644
--- a/frontend/techpick/src/features/nodeManagement/hooks/useDragHook.ts
+++ b/frontend/techpick/src/features/nodeManagement/hooks/useDragHook.ts
@@ -7,9 +7,12 @@ import { safeRun } from 'react-arborist/dist/main/utils';
import { ROOT_ID } from 'react-arborist/dist/main/data/create-root';
import { useEffect } from 'react';
import { getEmptyImage } from 'react-dnd-html5-backend';
+import { useTreeStore } from '@/shared/stores/treeStore';
export function useDragHook(node: NodeApi): ConnectDragSource {
- const tree = node.tree;
+ const { treeRef } = useTreeStore();
+ const tree = treeRef.rootRef.current!;
+
const [_, ref, preview] = useDrag(
() => ({
canDrag: () => node.isDraggable,
diff --git a/frontend/techpick/src/features/nodeManagement/hooks/useRestoreNode.ts b/frontend/techpick/src/features/nodeManagement/hooks/useRestoreNode.ts
index 60c78f15..3d22d73b 100644
--- a/frontend/techpick/src/features/nodeManagement/hooks/useRestoreNode.ts
+++ b/frontend/techpick/src/features/nodeManagement/hooks/useRestoreNode.ts
@@ -4,9 +4,12 @@ import { useMoveFolder } from '@/features/nodeManagement/api/folder/useMoveFolde
import { useQueryClient } from '@tanstack/react-query';
import { ApiStructureData } from '@/shared/types/ApiTypes';
import { useGetDefaultFolderData } from '@/features/nodeManagement/api/folder/useGetDefaultFolderData';
+import { useMovePick } from '@/features/nodeManagement/api/pick/useMovePick';
export const useRestoreNode = () => {
const { mutateAsync: moveFolder } = useMoveFolder();
+ const { mutateAsync: movePick } = useMovePick();
+
const { data: defaultFolderIdData } = useGetDefaultFolderData();
const queryClient = useQueryClient();
const structureData: ApiStructureData | undefined = queryClient.getQueryData([
@@ -20,10 +23,8 @@ export const useRestoreNode = () => {
ids: string[];
nodes: NodeApi[];
}) => {
- const realNodeId =
- nodes[0].data.type === 'folder'
- ? nodes[0].data.folderId
- : nodes[0].data.pickId;
+ const isFolder = nodes[0].data.type === 'folder';
+ const realNodeId = isFolder ? nodes[0].data.folderId : nodes[0].data.pickId;
const updatedRoot = structuredClone(structureData!.root);
updatedRoot.splice(0, 0, nodes[0].data);
@@ -41,10 +42,17 @@ export const useRestoreNode = () => {
},
};
- await moveFolder({
- folderId: realNodeId.toString(),
- structure: serverData,
- });
+ if (isFolder) {
+ await moveFolder({
+ folderId: realNodeId.toString(),
+ structure: serverData,
+ });
+ } else {
+ await movePick({
+ pickId: realNodeId.toString(),
+ structure: serverData,
+ });
+ }
await queryClient.invalidateQueries({
queryKey: ['rootAndRecycleBinData'],
diff --git a/frontend/techpick/src/features/nodeManagement/hooks/useTreeHandlers.ts b/frontend/techpick/src/features/nodeManagement/hooks/useTreeHandlers.ts
index a0902b32..f196990f 100644
--- a/frontend/techpick/src/features/nodeManagement/hooks/useTreeHandlers.ts
+++ b/frontend/techpick/src/features/nodeManagement/hooks/useTreeHandlers.ts
@@ -14,6 +14,8 @@ import { ApiStructureData } from '@/shared/types/ApiTypes';
import toast from 'react-hot-toast';
import { getCurrentTreeTypeByNode } from '@/features/nodeManagement/utils/getCurrentTreeTypeByNode';
import { deleteFolder } from '@/features/nodeManagement/api/folder/folderQueryFunctions';
+import { useMovePick } from '@/features/nodeManagement/api/pick/useMovePick';
+import { deletePick } from '@/features/nodeManagement/api/pick/pickQueryFunctions';
export const useTreeHandlers = () => {
const { data: structureData, refetch: refetchStructure } =
@@ -30,6 +32,8 @@ export const useTreeHandlers = () => {
const { data: defaultFolderIdData } = useGetDefaultFolderData();
const { mutateAsync: moveFolder } = useMoveFolder();
const { mutateAsync: renameFolder } = useRenameFolder();
+
+ const { mutateAsync: movePick } = useMovePick();
const queryClient = useQueryClient();
const recycleBinId = defaultFolderIdData?.RECYCLE_BIN;
@@ -72,12 +76,8 @@ export const useTreeHandlers = () => {
parentNode,
index,
}) => {
- console.log('dragIds', dragIds);
- console.log('dragNodes', dragNodes);
- console.log('parentId', parentId);
- console.log('parentNode', parentNode);
- console.log('index', index);
const isRoot = getCurrentTreeTypeByNode(dragNodes[0], treeRef) === 'root';
+ const isPick = dragNodes[0].data.type === 'pick';
const currentStructureData = isRoot
? structuredClone(structureData!.root)
: structuredClone(structureData!.recycleBin);
@@ -98,6 +98,7 @@ export const useTreeHandlers = () => {
parentNode,
index
);
+
// 서버에 업데이트된 트리 전송
const serverData = {
parentFolderId: parentNode ? parentNode.data.folderId : currentRootId,
@@ -107,10 +108,17 @@ export const useTreeHandlers = () => {
},
};
- await moveFolder({
- folderId: dragNodes[0].data.folderId!.toString(),
- structure: serverData,
- });
+ if (isPick) {
+ await movePick({
+ pickId: dragNodes[0].data.pickId!.toString(),
+ structure: serverData,
+ });
+ } else {
+ await moveFolder({
+ folderId: dragNodes[0].data.folderId!.toString(),
+ structure: serverData,
+ });
+ }
await refetchStructure();
};
@@ -129,7 +137,7 @@ export const useTreeHandlers = () => {
await refetchStructure();
} catch (error) {
console.error('Folder rename failed:', error);
- toast.error('동일한 이름을 가진 폴더가 존재합니다.');
+ toast.error('이름이 중복됩니다.\n 다른 이름을 입력해주세요.');
treeRef.rootRef.current?.root?.reset();
await refetchStructure();
}
@@ -142,10 +150,8 @@ export const useTreeHandlers = () => {
ids: string[];
nodes: NodeApi[];
}) => {
- const realNodeId =
- nodes[0].data.type === 'folder'
- ? nodes[0].data.folderId
- : nodes[0].data.pickId;
+ const isFolder = nodes[0].data.type === 'folder';
+ const realNodeId = isFolder ? nodes[0].data.folderId : nodes[0].data.pickId;
const updatedRecycleBin = structuredClone(structureData!.recycleBin);
updatedRecycleBin.splice(0, 0, nodes[0].data);
@@ -163,10 +169,18 @@ export const useTreeHandlers = () => {
recycleBin: updatedRecycleBin,
},
};
- await moveFolder({
- folderId: realNodeId.toString(),
- structure: serverData,
- });
+ if (isFolder) {
+ await moveFolder({
+ folderId: realNodeId.toString(),
+ structure: serverData,
+ });
+ } else {
+ await movePick({
+ pickId: realNodeId.toString(),
+ structure: serverData,
+ });
+ }
+
await refetchStructure();
setFocusedNode(null);
};
@@ -178,10 +192,8 @@ export const useTreeHandlers = () => {
ids: string[];
nodes: NodeApi[];
}) => {
- const realNodeId =
- nodes[0].data.type === 'folder'
- ? nodes[0].data.folderId
- : nodes[0].data.pickId;
+ const isFolder = nodes[0].data.type === 'folder';
+ const realNodeId = isFolder ? nodes[0].data.folderId : nodes[0].data.pickId;
const updatedRecycleBin = removeByIdFromStructure(
structuredClone(structureData!.recycleBin),
@@ -195,10 +207,18 @@ export const useTreeHandlers = () => {
recycleBin: updatedRecycleBin,
},
};
- await deleteFolder({
- folderId: realNodeId.toString(),
- structure: serverData,
- });
+ if (isFolder) {
+ await deleteFolder({
+ folderId: realNodeId.toString(),
+ structure: serverData,
+ });
+ } else {
+ await deletePick({
+ pickId: realNodeId.toString(),
+ structure: serverData,
+ });
+ }
+
await refetchStructure();
setFocusedNode(null);
diff --git a/frontend/techpick/src/features/nodeManagement/ui/Folder.tsx b/frontend/techpick/src/features/nodeManagement/ui/Folder.tsx
index 486787a0..90966fe6 100644
--- a/frontend/techpick/src/features/nodeManagement/ui/Folder.tsx
+++ b/frontend/techpick/src/features/nodeManagement/ui/Folder.tsx
@@ -7,7 +7,6 @@ import {
import { NodeApi } from 'react-arborist';
import { useDragHook } from '@/features/nodeManagement/hooks/useDragHook';
import { useTreeStore } from '@/shared/stores/treeStore';
-import { EditorContextMenu } from '@/widgets/ContextMenu/EditorContextMenu';
export function Folder({ node }: { node: NodeApi }) {
const ref = useDragHook(node);
@@ -19,28 +18,28 @@ export function Folder({ node }: { node: NodeApi }) {
const isFocused = focusedNodeInEditorSection?.id === node.id;
return (
-
- }
- className={isFocused ? folderWrapperFocused : folderWrapper}
- onClick={() => {
- setFocusedNodeInEditorSection(node);
- }}
- onDoubleClick={() => {
- setFocusedNode(node);
- }}
- onContextMenu={() => {
- setFocusedNodeInEditorSection(node);
- }}
- >
-
- {node.data.name}
-
-
+ //
+ }
+ className={isFocused ? folderWrapperFocused : folderWrapper}
+ onClick={() => {
+ setFocusedNodeInEditorSection(node);
+ }}
+ onDoubleClick={() => {
+ setFocusedNode(node);
+ }}
+ onContextMenu={() => {
+ setFocusedNodeInEditorSection(node);
+ }}
+ >
+
+ {node.data.name}
+
+ //
);
}
diff --git a/frontend/techpick/src/features/nodeManagement/utils/convertPickDataToNodeData.ts b/frontend/techpick/src/features/nodeManagement/utils/convertPickDataToNodeData.ts
new file mode 100644
index 00000000..cdab6a3c
--- /dev/null
+++ b/frontend/techpick/src/features/nodeManagement/utils/convertPickDataToNodeData.ts
@@ -0,0 +1,26 @@
+import { ApiPickData, ApiStructureData } from '@/shared/types/ApiTypes';
+import { getNewIdFromStructure } from '@/features/nodeManagement/utils/getNewIdFromStructure';
+import { NodeData } from '@/shared/types';
+
+export function convertPickDataToNodeData(
+ unClassifiedPickDataList: ApiPickData[],
+ structure: ApiStructureData
+): NodeData {
+ let newId = Number(getNewIdFromStructure(structure));
+ const children: NodeData[] = unClassifiedPickDataList.map((pickData) => {
+ return {
+ id: String(newId++),
+ name: pickData.title,
+ type: 'pick',
+ pickId: pickData.id,
+ url: pickData.linkUrlResponse.url,
+ };
+ });
+ return {
+ id: '-2',
+ name: '미분류',
+ type: 'folder',
+ folderId: -2,
+ children,
+ };
+}
diff --git a/frontend/techpick/src/features/nodeManagement/utils/getCurrentTreeTypeByNode.ts b/frontend/techpick/src/features/nodeManagement/utils/getCurrentTreeTypeByNode.ts
index 897f502c..dbdba200 100644
--- a/frontend/techpick/src/features/nodeManagement/utils/getCurrentTreeTypeByNode.ts
+++ b/frontend/techpick/src/features/nodeManagement/utils/getCurrentTreeTypeByNode.ts
@@ -8,5 +8,7 @@ export const getCurrentTreeTypeByNode = (
recycleBinRef: React.RefObject | undefined>;
}
) => {
- return treeRef.rootRef.current?.get(currentNode.id) ? 'root' : 'recycleBin';
+ return treeRef.recycleBinRef.current?.get(currentNode.id)
+ ? 'recycleBin'
+ : 'root';
};
diff --git a/frontend/techpick/src/features/nodeManagement/utils/getNewIdFromStructure.ts b/frontend/techpick/src/features/nodeManagement/utils/getNewIdFromStructure.ts
index 344f54bd..a81e8741 100644
--- a/frontend/techpick/src/features/nodeManagement/utils/getNewIdFromStructure.ts
+++ b/frontend/techpick/src/features/nodeManagement/utils/getNewIdFromStructure.ts
@@ -22,7 +22,12 @@ function getMaxIdFromNodes(nodes: NodeData[]): number {
export function getNewIdFromStructure(structure: ApiStructureData): string {
const maxIdInRoot = getMaxIdFromNodes(structure.root);
const maxIdInRecycleBin = getMaxIdFromNodes(structure.recycleBin);
- const maxId = Math.max(maxIdInRoot, maxIdInRecycleBin);
+ if (structure.unclassified) {
+ const maxIdInUnclassified = getMaxIdFromNodes(structure.unclassified);
+ return String(
+ Math.max(maxIdInRoot, maxIdInRecycleBin, maxIdInUnclassified) + 1
+ );
+ }
- return String(maxId + 1);
+ return String(Math.max(maxIdInRoot, maxIdInRecycleBin) + 1);
}
diff --git a/frontend/techpick/src/shared/stores/treeStore.ts b/frontend/techpick/src/shared/stores/treeStore.ts
index 524dc58b..fec3beb0 100644
--- a/frontend/techpick/src/shared/stores/treeStore.ts
+++ b/frontend/techpick/src/shared/stores/treeStore.ts
@@ -2,7 +2,7 @@ import { create } from 'zustand';
import { NodeData } from '@/shared/types';
import { NodeApi, TreeApi } from 'react-arborist';
import React, { createRef } from 'react';
-import { ApiPickData } from '@/shared/types/ApiTypes';
+import { ApiDefaultFolderIdData, ApiPickData } from '@/shared/types/ApiTypes';
interface TreeState {
treeData: NodeData[];
@@ -14,9 +14,13 @@ interface TreeState {
focusedNodeInEditorSection: NodeApi | null;
focusedFolderNodeList: NodeApi[];
focusedLinkNodeList: NodeApi[];
- unClassifiedPicks: ApiPickData[];
+ unClassifiedPickDataList: ApiPickData[];
+ unClassifiedNodeRoot: NodeApi | null;
+ defaultFolderIdData: ApiDefaultFolderIdData | null;
- setUnClassifiedPicks: (data: ApiPickData[]) => void;
+ setDeFaultFolderIdData: (data: ApiDefaultFolderIdData) => void;
+ setUnclassifiedNodeRoot: (data: NodeApi | null) => void;
+ setUnClassifiedPickDataList: (data: ApiPickData[]) => void;
setTreeData: (data: NodeData[]) => void;
setTreeRef: (
rootRef: React.RefObject | undefined>,
@@ -38,9 +42,14 @@ export const useTreeStore = create((set) => ({
focusedNodeInEditorSection: null,
focusedFolderNodeList: [],
focusedLinkNodeList: [],
- unClassifiedPicks: [],
+ unClassifiedPickDataList: [],
+ unClassifiedNodeRoot: null,
+ defaultFolderIdData: null,
- setUnClassifiedPicks: (data) => set({ unClassifiedPicks: data }),
+ setDeFaultFolderIdData: (data) => set({ defaultFolderIdData: data }),
+ setUnclassifiedNodeRoot: (data) => set({ unClassifiedNodeRoot: data }),
+ setUnClassifiedPickDataList: (data) =>
+ set({ unClassifiedPickDataList: data }),
setTreeData: (data) => set({ treeData: data }),
setTreeRef: (rootRef, recycleBinRef) =>
set({ treeRef: { rootRef, recycleBinRef } }),
diff --git a/frontend/techpick/src/shared/types/ApiTypes.ts b/frontend/techpick/src/shared/types/ApiTypes.ts
index e2b018bd..b6304d3c 100644
--- a/frontend/techpick/src/shared/types/ApiTypes.ts
+++ b/frontend/techpick/src/shared/types/ApiTypes.ts
@@ -10,6 +10,7 @@ export interface ApiDefaultFolderIdData {
export interface ApiStructureData {
root: NodeData[];
recycleBin: NodeData[];
+ unclassified?: NodeData[];
}
export interface ApiFolderData {
@@ -35,7 +36,7 @@ export interface ApiLinkUrlData {
export interface ApiPickData {
id: number;
- name: string;
+ title: string;
memo: string;
folderId: number;
userId: number;
diff --git a/frontend/techpick/src/shared/types/NodeData.ts b/frontend/techpick/src/shared/types/NodeData.ts
index 513d4351..abef8593 100644
--- a/frontend/techpick/src/shared/types/NodeData.ts
+++ b/frontend/techpick/src/shared/types/NodeData.ts
@@ -8,6 +8,7 @@ export interface NodeData {
name: string;
folderId?: number; // folder에만 적용
pickId?: number; // pick에만 적용
+ url?: string; // pick에만 적용
}
export interface ArboristCreateProps {
diff --git a/frontend/techpick/src/widgets/ContextMenu/EditorContextMenu.tsx b/frontend/techpick/src/widgets/ContextMenu/EditorContextMenu.tsx
index ee333529..fd06d109 100644
--- a/frontend/techpick/src/widgets/ContextMenu/EditorContextMenu.tsx
+++ b/frontend/techpick/src/widgets/ContextMenu/EditorContextMenu.tsx
@@ -63,14 +63,14 @@ export function EditorContextMenu({ children }: ContextMenuWrapperProps) {
Folder
- {
- treeRef.rootRef.current!.createLeaf();
- }}
- >
- Pick
-
+ {/* {*/}
+ {/* treeRef.rootRef.current!.createLeaf();*/}
+ {/* }}*/}
+ {/*>*/}
+ {/* Pick*/}
+ {/**/}
diff --git a/frontend/techpick/src/widgets/ContextMenu/TreeContextMenu.tsx b/frontend/techpick/src/widgets/ContextMenu/TreeContextMenu.tsx
index 49419f79..1e9ed4ee 100644
--- a/frontend/techpick/src/widgets/ContextMenu/TreeContextMenu.tsx
+++ b/frontend/techpick/src/widgets/ContextMenu/TreeContextMenu.tsx
@@ -1,14 +1,11 @@
import React from 'react';
import * as ContextMenu from '@radix-ui/react-context-menu';
-import { ChevronRightIcon } from '@radix-ui/react-icons';
import {
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
RightSlot,
- ContextMenuSubContent,
- ContextMenuSubTrigger,
} from './ContextMenu.css';
import { useTreeStore } from '@/shared/stores/treeStore';
import { getCurrentTreeTypeByNode } from '@/features/nodeManagement/utils/getCurrentTreeTypeByNode';
@@ -35,39 +32,47 @@ export function TreeContextMenu({ children }: ContextMenuWrapperProps) {
{focusedNode?.data.type === 'folder' && currentTree === 'root' && (
-
-
- 새로 만들기
-
-
-
-
-
-
- {
- treeRef.rootRef.current!.createInternal();
- }}
- >
- Folder
-
-
- {
- treeRef.rootRef.current!.createLeaf();
- }}
- >
- Pick
-
-
-
-
+ {
+ treeRef.rootRef.current!.createInternal();
+ }}
+ >
+ 새 폴더
+
+ //
+ //
+ // 새로 만들기
+ //
+ //
+ //
+ //
+ //
+ //
+ // {
+ // treeRef.rootRef.current!.createInternal();
+ // }}
+ // >
+ // Folder
+ //
+ //
+ // {
+ // treeRef.rootRef.current!.createLeaf();
+ // }}
+ // >
+ // Pick
+ //
+ //
+ //
+ //
)}
{currentTree === 'root' && (
- 이름 변경
+ 이름 바꾸기
)}
{currentTree === 'recycleBin' && (
diff --git a/frontend/techpick/src/widgets/DirectoryNode/DirectoryNode.css.ts b/frontend/techpick/src/widgets/DirectoryNode/DirectoryNode.css.ts
index 45e643c6..afd4d86a 100644
--- a/frontend/techpick/src/widgets/DirectoryNode/DirectoryNode.css.ts
+++ b/frontend/techpick/src/widgets/DirectoryNode/DirectoryNode.css.ts
@@ -33,6 +33,12 @@ export const dirIcFolder = style({
marginRight: '8px',
});
+export const dirName = style({
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ whiteSpace: 'nowrap',
+});
+
export const nodeNameInput = style({
fontSize: '14px',
fontWeight: 300,
diff --git a/frontend/techpick/src/widgets/DirectoryNode/DirectoryNode.tsx b/frontend/techpick/src/widgets/DirectoryNode/DirectoryNode.tsx
index 501ecfb3..6c849350 100644
--- a/frontend/techpick/src/widgets/DirectoryNode/DirectoryNode.tsx
+++ b/frontend/techpick/src/widgets/DirectoryNode/DirectoryNode.tsx
@@ -1,6 +1,7 @@
import { DirectoryNodeProps } from '@/shared/types/NodeData';
import {
dirIcFolder,
+ dirName,
dirNode,
dirNodeWrapper,
dirNodeWrapperFocused,
@@ -56,7 +57,7 @@ export const DirectoryNode = ({
if (event.key === 'Enter') {
if (event.currentTarget.value === '') {
- toast.error('폴더 이름을 입력해주세요.');
+ toast.error('이름을 입력해주세요.');
return;
}
@@ -99,7 +100,7 @@ export const DirectoryNode = ({
exact: true,
});
- toast.error('동일한 이름을 가진 폴더가 존재합니다.');
+ toast.error('이름이 중복됩니다.\n 다른 이름을 입력해주세요. ');
node.reset();
}
} else node.submit(event.currentTarget.value);
@@ -115,6 +116,13 @@ export const DirectoryNode = ({
setFocusedNode(node);
if (currentTree === 'root') {
treeRef.recycleBinRef.current!.deselectAll();
+ if (node.isLeaf) {
+ console.log('node', node);
+ // const pickData = getPickDetail(node.data.pickId!.toString());
+ // pickData.then((data) => {
+ // alert(data.linkUrlResponse.url);
+ // });
+ }
} else {
treeRef.rootRef.current!.deselectAll();
}
@@ -130,44 +138,46 @@ export const DirectoryNode = ({
}
}}
>
-
-
- {node.isOpen ? (
-
- ) : node.isLeaf ? (
-
- ) : (
-
- )}
- {node.data.type === 'folder' ? (
-
- ) : (
-
- )}
- {node.isEditing ? (
-
- ) : (
- node.data.name
- )}
-
-
+ {node.id !== '-2' && (
+
+
+ {node.isOpen ? (
+
+ ) : node.isLeaf ? (
+
+ ) : (
+
+ )}
+ {node.data.type === 'folder' ? (
+
+ ) : (
+
+ )}
+ {node.isEditing ? (
+
+ ) : (
+
{node.data.name}
+ )}
+
+
+ )}
);
};
diff --git a/frontend/techpick/src/widgets/DirectoryTreeSection/DirectoryTreeSection.tsx b/frontend/techpick/src/widgets/DirectoryTreeSection/DirectoryTreeSection.tsx
index 041a5c31..3327562e 100644
--- a/frontend/techpick/src/widgets/DirectoryTreeSection/DirectoryTreeSection.tsx
+++ b/frontend/techpick/src/widgets/DirectoryTreeSection/DirectoryTreeSection.tsx
@@ -6,20 +6,20 @@ import {
directoryTreeContainer,
directoryTreeSectionFooter,
directoryTreeWrapper,
- recycleBinTreeWrapperClosed,
+ directoryTreeWrapperFullSize,
leftSidebarSection,
logo,
+ logout,
+ plusButton,
profileContainer,
profileSection,
+ recycleBinContainerClosed,
+ recycleBinContainerOpen,
recycleBinLabelContainer,
recycleBinTreeWrapper,
- recycleBinContainerOpen,
- recycleBinContainerClosed,
- logout,
- directoryTreeWrapperFullSize,
- plusButton,
- unClassifiedLabelContainer,
+ recycleBinTreeWrapperClosed,
rightIcon,
+ unClassifiedLabelContainer,
} from './DirectoryTreeSection.css';
import Image from 'next/image';
import { NodeData } from '@/shared/types/NodeData';
@@ -41,14 +41,33 @@ import { DirectoryNode } from '@/widgets/DirectoryNode/DirectoryNode';
import { useLogout } from '@/features/userManagement/api/useLogout';
import { useRouter } from 'next/navigation';
import toast from 'react-hot-toast';
-import { useGetUnclassifiedPicks } from '@/features/nodeManagement/api/pick/useGetUnclassifiedPicks';
+import { convertPickDataToNodeData } from '@/features/nodeManagement/utils/convertPickDataToNodeData';
+import { addNodeToStructure } from '@/features/nodeManagement/utils/addNodeToStructure';
+import { useQueryClient } from '@tanstack/react-query';
+import {
+ ApiDefaultFolderIdData,
+ ApiStructureData,
+} from '@/shared/types/ApiTypes';
+import { debounce } from 'lodash';
+import { useGetPicksByParentId } from '@/features/nodeManagement/api/pick/useGetPicksByParentId';
-export function DirectoryTreeSection() {
+export function DirectoryTreeSection({
+ defaultFolderIdData,
+}: {
+ defaultFolderIdData: ApiDefaultFolderIdData;
+}) {
+ const queryClient = useQueryClient();
const { ref, width, height } = useResizeObserver
();
const rootTreeRef = useRef | undefined>(undefined);
const recycleBinTreeRef = useRef | undefined>(undefined);
const dragDropManager = useDragDropManager();
- const { setTreeRef, setFocusedNode, setUnClassifiedPicks } = useTreeStore();
+ const {
+ treeRef,
+ setTreeRef,
+ setFocusedNode,
+ setUnClassifiedPickDataList,
+ setUnclassifiedNodeRoot,
+ } = useTreeStore();
const {
handleCreate,
handleDrag,
@@ -96,9 +115,48 @@ export function DirectoryTreeSection() {
data: rootAndRecycleBinData,
error: structureError,
isLoading: isStructureLoading,
+ refetch: refetchStructure,
} = useGetRootAndRecycleBinData();
- const { data: unClassifiedPicks } = useGetUnclassifiedPicks();
+ const {
+ data: unClassifiedPickDataList,
+ isLoading: isUnClassifiedPickDataLoading,
+ refetch: refetchUnclassifiedPickDataList,
+ } = useGetPicksByParentId(defaultFolderIdData.UNCLASSIFIED.toString());
+
+ function convertUnClassifiedPickDataToNodeApi() {
+ if (unClassifiedPickDataList && rootAndRecycleBinData) {
+ // NodeData 타입으로 변환해야함
+ const unClassifiedNodeData = convertPickDataToNodeData(
+ unClassifiedPickDataList,
+ rootAndRecycleBinData
+ );
+ // Tree에 추가해야 함
+ const newRootData = addNodeToStructure(
+ rootAndRecycleBinData.root,
+ null,
+ 0,
+ unClassifiedNodeData
+ );
+ queryClient.setQueryData(
+ ['rootAndRecycleBinData'],
+ (oldData: ApiStructureData) => ({
+ root: newRootData,
+ recycleBin: oldData.recycleBin,
+ })
+ );
+ // NodeApi 타입으로 변환된 데이터를 Store 에 저장, focusedNode 설정
+ setTimeout(() => {
+ const unClassifiedNodeRoot = rootTreeRef.current?.get('-2');
+ if (unClassifiedNodeRoot) {
+ setUnclassifiedNodeRoot(unClassifiedNodeRoot);
+ setFocusedNode(unClassifiedNodeRoot);
+ }
+ }, 0);
+ // Tree에서 삭제
+ refetchStructure();
+ }
+ }
return (
@@ -117,16 +175,17 @@ export function DirectoryTreeSection() {
{
- if (!unClassifiedPicks) {
+ onClick={debounce(async () => {
+ await refetchUnclassifiedPickDataList();
+ if (!unClassifiedPickDataList) {
return;
}
- setFocusedNode(null);
- console.log('clicked');
- console.log('unClassifiedPicks :', unClassifiedPicks);
- setUnClassifiedPicks(unClassifiedPicks);
- }}
- onDoubleClick={() => {}}
+ treeRef.rootRef.current?.deselectAll();
+ treeRef.recycleBinRef.current?.deselectAll();
+ setUnClassifiedPickDataList(unClassifiedPickDataList);
+
+ convertUnClassifiedPickDataToNodeApi();
+ }, 300)}
>
Unclassified
@@ -159,32 +218,37 @@ export function DirectoryTreeSection() {
}
ref={ref}
>
- {isStructureLoading &&
Loading...
}
- {structureError &&
Error: {structureError.message}
}
- {!isStructureLoading && !structureError && (
-
- ref={handleTreeRef('root')}
- className={directoryTree}
- data={rootAndRecycleBinData?.root}
- disableMultiSelection={true}
- onFocus={(node: NodeApi) => {
- setFocusedNode(node);
- }}
- onMove={handleDrag}
- onCreate={handleCreate}
- onRename={handleRename}
- onDelete={handleMoveToTrash}
- openByDefault={false}
- width={width}
- height={height}
- rowHeight={32}
- indent={24}
- overscanCount={1}
- dndManager={dragDropManager}
- >
- {DirectoryNode}
-
+ {isStructureLoading && isUnClassifiedPickDataLoading && (
+
Loading...
)}
+
+ {structureError &&
Error: {structureError.message}
}
+ {!isStructureLoading &&
+ !isUnClassifiedPickDataLoading &&
+ !structureError && (
+
+ ref={handleTreeRef('root')}
+ className={directoryTree}
+ data={rootAndRecycleBinData?.root}
+ disableMultiSelection={true}
+ onFocus={(node: NodeApi) => {
+ setFocusedNode(node);
+ }}
+ onMove={handleDrag}
+ onCreate={handleCreate}
+ onRename={handleRename}
+ onDelete={handleMoveToTrash}
+ openByDefault={false}
+ width={width}
+ height={height}
+ rowHeight={32}
+ indent={24}
+ overscanCount={1}
+ dndManager={dragDropManager}
+ >
+ {DirectoryNode}
+
+ )}
{
- const {
- treeRef,
- focusedNode,
- focusedFolderNodeList,
- focusedLinkNodeList,
- unClassifiedPicks,
- } = useTreeStore();
+ const { treeRef, focusedNode, focusedFolderNodeList, focusedLinkNodeList } =
+ useTreeStore();
const el = useRef
(null);
const dropRef = useDropHook(el, focusedNode || treeRef.rootRef.current!.root);
@@ -29,6 +20,7 @@ export const LinkEditor = () => {
},
[dropRef]
);
+
return (
{!!focusedFolderNodeList?.length && (
@@ -38,20 +30,20 @@ export const LinkEditor = () => {
))}
)}
+ {/*{!!focusedLinkNodeList?.length && (*/}
+ {/* */}
+ {/* {focusedLinkNodeList?.map((node, index) => (*/}
+ {/*
*/}
+ {/* ))}*/}
+ {/*
*/}
+ {/*)}*/}
{!!focusedLinkNodeList?.length && (
-
- {/*todo: pick id 넘져줘야 함*/}
- {focusedLinkNodeList?.map((node, index) => (
-
- ))}
-
- )}
- {!!unClassifiedPicks?.length && !focusedNode && (
- {unClassifiedPicks.map((node, index) => {
+ {focusedLinkNodeList.map((node, index) => {
+ console.log(node.data);
return (
-
-
+
+
);
})}
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 3d523719..cd2e5afd 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -4766,7 +4766,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/lodash@npm:^4.14.167":
+"@types/lodash@npm:^4, @types/lodash@npm:^4.14.167":
version: 4.17.10
resolution: "@types/lodash@npm:4.17.10"
checksum: 10c0/149b2b9fcc277204393423ed14df28894980c2322ec522fc23f2c6f7edef6ee8d876ee09ed4520f45d128adc0a7a6e618bb0017668349716cd99c6ef54a21621
@@ -14535,6 +14535,7 @@ __metadata:
"@testing-library/jest-dom": "npm:^6.5.0"
"@testing-library/react": "npm:^16.0.1"
"@types/dompurify": "npm:^3"
+ "@types/lodash": "npm:^4"
"@types/node": "npm:^22.5.4"
"@types/randomcolor": "npm:^0.5.9"
"@types/react": "npm:^18"
@@ -14557,6 +14558,7 @@ __metadata:
jest: "npm:^29.7.0"
jest-environment-jsdom: "npm:^29.7.0"
ky: "npm:^1.7.2"
+ lodash: "npm:^4.17.21"
lucide-react: "npm:^0.447.0"
mini-css-extract-plugin: "npm:^2.9.1"
next: "npm:14.2.9"