Refactor: 사이드바 이미지 상태 쿼리로 관리하게 해봄, 실패

This commit is contained in:
정현조 2024-10-04 05:02:05 +09:00
parent 1bbf0bc77e
commit b61390be0e
2 changed files with 136 additions and 124 deletions

View File

@ -3,13 +3,15 @@ import { TreeNode } from 'react-treebeard';
import useProjectStore from '@/stores/useProjectStore';
import useCanvasStore from '@/stores/useCanvasStore';
import useTreeData from '@/hooks/useTreeData';
import { Project, ImageResponse, ImageStatus } from '@/types';
import useFolderQuery from '@/queries/folders/useFolderQuery';
import useProjectCategoriesQuery from '@/queries/category/useProjectCategoriesQuery';
import useImage from '@/hooks/useImage';
import { Project, ImageResponse } from '@/types';
import WorkspaceDropdownMenu from '../WorkspaceDropdownMenu';
import AutoLabelButton from './AutoLabelButton';
import useMoveImageQuery from '@/queries/images/useMoveImageQuery';
import { Folder, Image as ImageIcon, Minus, Loader, ArrowDownToLine, Send, CircleSlash, Check } 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';
@ -27,19 +29,52 @@ const ItemTypes = {
};
export default function ProjectStructure({ project }: { project: Project }) {
const { setProject } = useProjectStore();
const { setImage } = useCanvasStore();
const { treeData, fetchNodeData, initializeTree, setTreeData, isLoading } = useTreeData(project.id.toString(), 0);
const [cursor, setCursor] = useState<TreeNode | null>(null);
const moveImageMutation = useMoveImageQuery();
const { setProject, setCategories } = useProjectStore();
const { image: selectedImage, setImage } = useCanvasStore();
const { treeData, fetchNodeData, setTreeData } = useTreeData(project.id.toString());
const { data: categories } = useProjectCategoriesQuery(project.id);
const { data: rootFolder, isLoading, refetch } = useFolderQuery(project.id.toString(), 0);
const { data: updatedImage } = useImage(project.id, rootFolder?.id || 0, selectedImage?.id || 0);
const containerRef = useRef<HTMLDivElement>(null);
const [containerHeight, setContainerHeight] = useState<number>(400);
useEffect(() => {
setCategories(categories);
}, [categories, setCategories]);
useEffect(() => {
setProject(project);
initializeTree();
}, [project, setProject, initializeTree]);
}, [project, setProject]);
useEffect(() => {
if (rootFolder) {
const childFolders: TreeNode[] =
rootFolder.children?.map((child) => ({
id: child.id.toString(),
name: child.title,
children: [],
})) || [];
const images: TreeNode[] =
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, setTreeData]);
useEffect(() => {
if (containerRef.current) {
@ -47,17 +82,28 @@ export default function ProjectStructure({ project }: { project: Project }) {
}
}, [containerRef, treeData, isLoading]);
useEffect(() => {
if (updatedImage?.data) {
const updateNodeStatus = (currentNode: TreeNode): TreeNode => {
if (currentNode.imageData?.id === updatedImage.data.id) {
return { ...currentNode, imageData: { ...currentNode.imageData, status: updatedImage.data.status } };
}
if (currentNode.children) {
return {
...currentNode,
children: currentNode.children.map(updateNodeStatus),
};
}
return currentNode;
};
setTreeData((prevData) => prevData && updateNodeStatus(prevData));
}
}, [updatedImage, setTreeData]);
const onToggle = useCallback(
async (node: TreeNode, toggled: boolean) => {
if (cursor) {
cursor.active = false;
}
node.active = true;
setCursor(node);
if (node.imageData) {
setImage(node.imageData as ImageResponse);
} else {
if (!node.imageData) {
if (toggled && (!node.children || node.children.length === 0)) {
await fetchNodeData(node);
}
@ -78,56 +124,68 @@ export default function ProjectStructure({ project }: { project: Project }) {
setTreeData((prevData) => prevData && updateNode(prevData));
}
},
[cursor, fetchNodeData, setImage, setTreeData]
[fetchNodeData, setTreeData]
);
const renderStatusIcon = useCallback((status: ImageStatus) => {
switch (status) {
case 'PENDING':
return (
const handleImageClick = useCallback(
(image: ImageResponse) => {
setImage(image);
},
[setImage]
);
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: (
<Minus
size={12}
className="shrink-0 stroke-gray-400"
{...iconProps}
className={`${iconProps.className} ${iconColor.PENDING}`}
/>
);
case 'IN_PROGRESS':
return (
),
IN_PROGRESS: (
<Loader
size={12}
className="shrink-0 animate-spin stroke-yellow-400"
{...iconProps}
className={`${iconProps.className} ${iconColor.IN_PROGRESS}`}
/>
);
case 'SAVE':
return (
),
SAVE: (
<ArrowDownToLine
size={12}
className="shrink-0 stroke-gray-400"
{...iconProps}
className={`${iconProps.className} ${iconColor.SAVE}`}
/>
);
case 'REVIEW_REQUEST':
return (
),
REVIEW_REQUEST: (
<Send
size={12}
className="shrink-0 stroke-blue-400"
{...iconProps}
className={`${iconProps.className} ${iconColor.REVIEW_REQUEST}`}
/>
);
case 'REVIEW_REJECT':
return (
),
REVIEW_REJECT: (
<CircleSlash
size={12}
className="shrink-0 stroke-red-400"
{...iconProps}
className={`${iconProps.className} ${iconColor.REVIEW_REJECT}`}
/>
);
case 'COMPLETED':
default:
return (
),
COMPLETED: (
<Check
size={12}
className="shrink-0 stroke-green-400"
{...iconProps}
className={`${iconProps.className} ${iconColor.COMPLETED}`}
/>
);
}
}, []);
),
};
return iconMapping[status] || null;
};
const flattenTree = useCallback((nodes: TreeNode[], depth: number = 0, parent?: FlatNode): FlatNode[] => {
let flatList: FlatNode[] = [];
@ -183,23 +241,8 @@ export default function ProjectStructure({ project }: { project: Project }) {
})(treeData!);
setTreeData(updatedTreeData);
if (dragItem.imageData) {
const moveFolderId = Number(hoverItem.parent?.id) || 0;
const folderId = Number(dragItem.parent?.id) || 0;
const projectId = Number(project.id);
moveImageMutation.mutate({
projectId,
folderId,
imageId: dragItem.imageData.id,
moveRequest: {
moveFolderId,
},
});
}
},
[treeData, setTreeData, moveImageMutation, project.id]
[treeData, setTreeData]
);
const Row = ({ index, style, data }: ListChildComponentProps<FlatNode[]>) => {
@ -240,8 +283,15 @@ export default function ProjectStructure({ project }: { project: Project }) {
alignItems: 'center',
paddingLeft: `${node.depth * 20}px`,
cursor: 'pointer',
backgroundColor: node.imageData && selectedImage?.id === node.imageData.id ? '#e5e7eb' : 'transparent',
}}
onClick={() => {
if (node.imageData) {
handleImageClick(node.imageData as ImageResponse);
} else {
onToggle(node, !node.toggled);
}
}}
onClick={() => onToggle(node, !node.toggled)}
>
<div style={{ marginRight: '5px' }}>
{!node.imageData ? (
@ -277,7 +327,7 @@ export default function ProjectStructure({ project }: { project: Project }) {
<WorkspaceDropdownMenu
projectId={project.id}
folderId={0}
onRefetch={() => {}}
onRefetch={refetch}
/>
</header>
{isLoading ? (

View File

@ -3,9 +3,8 @@ import { TreeNode } from 'react-treebeard';
import { getFolder } from '@/api/folderApi';
import { ImageResponse, ChildFolder } from '@/types';
export default function useTreeData(projectId: string, initialFolderId: number) {
export default function useTreeData(projectId: string) {
const [treeData, setTreeData] = useState<TreeNode | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const fetchNodeData = useCallback(
async (node: TreeNode) => {
@ -52,51 +51,14 @@ export default function useTreeData(projectId: string, initialFolderId: number)
});
} catch (error) {
node.loading = false;
console.error(`Error fetching data for node ${node.id}:`, error);
}
},
[projectId]
);
const initializeTree = useCallback(async () => {
setIsLoading(true);
try {
const rootFolder = await getFolder(projectId, initialFolderId);
const childFolders: TreeNode[] =
rootFolder.children?.map((child: ChildFolder) => ({
id: child.id.toString(),
name: child.title,
children: [],
})) || [];
const images: TreeNode[] =
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);
} catch (error) {
console.error('Error initializing tree data:', error);
} finally {
setIsLoading(false);
}
}, [projectId, initialFolderId]);
return {
treeData,
fetchNodeData,
initializeTree,
setTreeData,
isLoading,
};
}