Refactor: 사이드바 이미지 상태 쿼리로 관리하게 해봄, 실패
This commit is contained in:
parent
1bbf0bc77e
commit
b61390be0e
@ -3,13 +3,15 @@ import { TreeNode } from 'react-treebeard';
|
|||||||
import useProjectStore from '@/stores/useProjectStore';
|
import useProjectStore from '@/stores/useProjectStore';
|
||||||
import useCanvasStore from '@/stores/useCanvasStore';
|
import useCanvasStore from '@/stores/useCanvasStore';
|
||||||
import useTreeData from '@/hooks/useTreeData';
|
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 WorkspaceDropdownMenu from '../WorkspaceDropdownMenu';
|
||||||
import AutoLabelButton from './AutoLabelButton';
|
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 { Folder, Image as ImageIcon, Minus, Loader, ArrowDownToLine, Send, CircleSlash, Check } from 'lucide-react';
|
||||||
import { Spinner } from '../ui/spinner';
|
import { Spinner } from '../ui/spinner';
|
||||||
|
import { ImageStatus } from '@/types';
|
||||||
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
|
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
|
||||||
|
|
||||||
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
||||||
@ -27,19 +29,52 @@ const ItemTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function ProjectStructure({ project }: { project: Project }) {
|
export default function ProjectStructure({ project }: { project: Project }) {
|
||||||
const { setProject } = useProjectStore();
|
const { setProject, setCategories } = useProjectStore();
|
||||||
const { setImage } = useCanvasStore();
|
const { image: selectedImage, setImage } = useCanvasStore();
|
||||||
const { treeData, fetchNodeData, initializeTree, setTreeData, isLoading } = useTreeData(project.id.toString(), 0);
|
const { treeData, fetchNodeData, setTreeData } = useTreeData(project.id.toString());
|
||||||
const [cursor, setCursor] = useState<TreeNode | null>(null);
|
const { data: categories } = useProjectCategoriesQuery(project.id);
|
||||||
const moveImageMutation = useMoveImageQuery();
|
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 containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [containerHeight, setContainerHeight] = useState<number>(400);
|
const [containerHeight, setContainerHeight] = useState<number>(400);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setCategories(categories);
|
||||||
|
}, [categories, setCategories]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setProject(project);
|
setProject(project);
|
||||||
initializeTree();
|
}, [project, setProject]);
|
||||||
}, [project, setProject, initializeTree]);
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
if (containerRef.current) {
|
if (containerRef.current) {
|
||||||
@ -47,17 +82,28 @@ export default function ProjectStructure({ project }: { project: Project }) {
|
|||||||
}
|
}
|
||||||
}, [containerRef, treeData, isLoading]);
|
}, [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(
|
const onToggle = useCallback(
|
||||||
async (node: TreeNode, toggled: boolean) => {
|
async (node: TreeNode, toggled: boolean) => {
|
||||||
if (cursor) {
|
if (!node.imageData) {
|
||||||
cursor.active = false;
|
|
||||||
}
|
|
||||||
node.active = true;
|
|
||||||
setCursor(node);
|
|
||||||
|
|
||||||
if (node.imageData) {
|
|
||||||
setImage(node.imageData as ImageResponse);
|
|
||||||
} else {
|
|
||||||
if (toggled && (!node.children || node.children.length === 0)) {
|
if (toggled && (!node.children || node.children.length === 0)) {
|
||||||
await fetchNodeData(node);
|
await fetchNodeData(node);
|
||||||
}
|
}
|
||||||
@ -78,56 +124,68 @@ export default function ProjectStructure({ project }: { project: Project }) {
|
|||||||
setTreeData((prevData) => prevData && updateNode(prevData));
|
setTreeData((prevData) => prevData && updateNode(prevData));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[cursor, fetchNodeData, setImage, setTreeData]
|
[fetchNodeData, setTreeData]
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderStatusIcon = useCallback((status: ImageStatus) => {
|
const handleImageClick = useCallback(
|
||||||
switch (status) {
|
(image: ImageResponse) => {
|
||||||
case 'PENDING':
|
setImage(image);
|
||||||
return (
|
},
|
||||||
<Minus
|
[setImage]
|
||||||
size={12}
|
);
|
||||||
className="shrink-0 stroke-gray-400"
|
|
||||||
/>
|
const renderStatusIcon = (status: ImageStatus) => {
|
||||||
);
|
const iconProps = { size: 12, className: 'shrink-0' };
|
||||||
case 'IN_PROGRESS':
|
const iconColor = {
|
||||||
return (
|
PENDING: 'stroke-gray-400',
|
||||||
<Loader
|
IN_PROGRESS: 'animate-spin stroke-yellow-400',
|
||||||
size={12}
|
SAVE: 'stroke-gray-400',
|
||||||
className="shrink-0 animate-spin stroke-yellow-400"
|
REVIEW_REQUEST: 'stroke-blue-400',
|
||||||
/>
|
REVIEW_REJECT: 'stroke-red-400',
|
||||||
);
|
COMPLETED: 'stroke-green-400',
|
||||||
case 'SAVE':
|
};
|
||||||
return (
|
|
||||||
<ArrowDownToLine
|
const iconMapping = {
|
||||||
size={12}
|
PENDING: (
|
||||||
className="shrink-0 stroke-gray-400"
|
<Minus
|
||||||
/>
|
{...iconProps}
|
||||||
);
|
className={`${iconProps.className} ${iconColor.PENDING}`}
|
||||||
case 'REVIEW_REQUEST':
|
/>
|
||||||
return (
|
),
|
||||||
<Send
|
IN_PROGRESS: (
|
||||||
size={12}
|
<Loader
|
||||||
className="shrink-0 stroke-blue-400"
|
{...iconProps}
|
||||||
/>
|
className={`${iconProps.className} ${iconColor.IN_PROGRESS}`}
|
||||||
);
|
/>
|
||||||
case 'REVIEW_REJECT':
|
),
|
||||||
return (
|
SAVE: (
|
||||||
<CircleSlash
|
<ArrowDownToLine
|
||||||
size={12}
|
{...iconProps}
|
||||||
className="shrink-0 stroke-red-400"
|
className={`${iconProps.className} ${iconColor.SAVE}`}
|
||||||
/>
|
/>
|
||||||
);
|
),
|
||||||
case 'COMPLETED':
|
REVIEW_REQUEST: (
|
||||||
default:
|
<Send
|
||||||
return (
|
{...iconProps}
|
||||||
<Check
|
className={`${iconProps.className} ${iconColor.REVIEW_REQUEST}`}
|
||||||
size={12}
|
/>
|
||||||
className="shrink-0 stroke-green-400"
|
),
|
||||||
/>
|
REVIEW_REJECT: (
|
||||||
);
|
<CircleSlash
|
||||||
}
|
{...iconProps}
|
||||||
}, []);
|
className={`${iconProps.className} ${iconColor.REVIEW_REJECT}`}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
COMPLETED: (
|
||||||
|
<Check
|
||||||
|
{...iconProps}
|
||||||
|
className={`${iconProps.className} ${iconColor.COMPLETED}`}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return iconMapping[status] || null;
|
||||||
|
};
|
||||||
|
|
||||||
const flattenTree = useCallback((nodes: TreeNode[], depth: number = 0, parent?: FlatNode): FlatNode[] => {
|
const flattenTree = useCallback((nodes: TreeNode[], depth: number = 0, parent?: FlatNode): FlatNode[] => {
|
||||||
let flatList: FlatNode[] = [];
|
let flatList: FlatNode[] = [];
|
||||||
@ -183,23 +241,8 @@ export default function ProjectStructure({ project }: { project: Project }) {
|
|||||||
})(treeData!);
|
})(treeData!);
|
||||||
|
|
||||||
setTreeData(updatedTreeData);
|
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[]>) => {
|
const Row = ({ index, style, data }: ListChildComponentProps<FlatNode[]>) => {
|
||||||
@ -240,8 +283,15 @@ export default function ProjectStructure({ project }: { project: Project }) {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingLeft: `${node.depth * 20}px`,
|
paddingLeft: `${node.depth * 20}px`,
|
||||||
cursor: 'pointer',
|
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' }}>
|
<div style={{ marginRight: '5px' }}>
|
||||||
{!node.imageData ? (
|
{!node.imageData ? (
|
||||||
@ -277,7 +327,7 @@ export default function ProjectStructure({ project }: { project: Project }) {
|
|||||||
<WorkspaceDropdownMenu
|
<WorkspaceDropdownMenu
|
||||||
projectId={project.id}
|
projectId={project.id}
|
||||||
folderId={0}
|
folderId={0}
|
||||||
onRefetch={() => {}}
|
onRefetch={refetch}
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
|
@ -3,9 +3,8 @@ import { TreeNode } from 'react-treebeard';
|
|||||||
import { getFolder } from '@/api/folderApi';
|
import { getFolder } from '@/api/folderApi';
|
||||||
import { ImageResponse, ChildFolder } from '@/types';
|
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 [treeData, setTreeData] = useState<TreeNode | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const fetchNodeData = useCallback(
|
const fetchNodeData = useCallback(
|
||||||
async (node: TreeNode) => {
|
async (node: TreeNode) => {
|
||||||
@ -52,51 +51,14 @@ export default function useTreeData(projectId: string, initialFolderId: number)
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
node.loading = false;
|
node.loading = false;
|
||||||
console.error(`Error fetching data for node ${node.id}:`, error);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[projectId]
|
[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 {
|
return {
|
||||||
treeData,
|
treeData,
|
||||||
fetchNodeData,
|
fetchNodeData,
|
||||||
initializeTree,
|
|
||||||
setTreeData,
|
setTreeData,
|
||||||
isLoading,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user