Refactor: 레이블링 페이지 레이아웃 변경
This commit is contained in:
parent
ebc548a1d9
commit
7ca4012f68
@ -1,65 +1,114 @@
|
|||||||
import { Outlet } from 'react-router-dom';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useParams, Outlet } from 'react-router-dom';
|
||||||
import Header from '../Header';
|
import Header from '../Header';
|
||||||
import { Label, Project } from '@/types';
|
import { Label, Project, DirectoryItem, FileItem } from '@/types';
|
||||||
import { ResizablePanelGroup, ResizablePanel } from '../ui/resizable';
|
import { ResizablePanelGroup, ResizablePanel } from '../ui/resizable';
|
||||||
import WorkspaceSidebar from '../WorkspaceSidebar';
|
import WorkspaceSidebar from '../WorkspaceSidebar';
|
||||||
import WorkspaceLabelBar from '../WorkspaceLabelBar';
|
import WorkspaceLabelBar from '../WorkspaceLabelBar';
|
||||||
|
import { fetchFolderApi } from '@/api/folderApi';
|
||||||
|
import { getWorkspaceApi } from '@/api/workspaceApi';
|
||||||
|
import { getAllProjectsApi } from '@/api/projectApi';
|
||||||
|
import useAuthStore from '@/stores/useAuthStore';
|
||||||
|
|
||||||
export default function WorkspaceLayout() {
|
export default function WorkspaceLayout() {
|
||||||
const workspace: { name: string; projects: Project[] } = {
|
const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();
|
||||||
name: 'Workspace-name-1',
|
const [workspace, setWorkspace] = useState<{ name: string; projects: Project[] }>({
|
||||||
projects: [
|
name: '',
|
||||||
{
|
projects: [],
|
||||||
id: 1,
|
});
|
||||||
name: 'project-111',
|
const profile = useAuthStore((state) => state.profile);
|
||||||
type: 'Segmentation',
|
const memberId = profile?.id || 0;
|
||||||
children: [
|
|
||||||
{
|
useEffect(() => {
|
||||||
id: 12,
|
const fetchWorkspaceData = async (workspaceId: number, memberId: number) => {
|
||||||
type: 'directory',
|
try {
|
||||||
name: 'directory-1',
|
const workspaceResponse = await getWorkspaceApi(workspaceId, memberId);
|
||||||
children: [
|
if (workspaceResponse.isSuccess) {
|
||||||
{
|
const workspaceTitle = workspaceResponse.data.title;
|
||||||
id: 123,
|
setWorkspace((prev) => ({
|
||||||
type: 'directory',
|
...prev,
|
||||||
name: 'directory-2',
|
name: workspaceTitle,
|
||||||
children: [
|
}));
|
||||||
{ id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' },
|
fetchProjects(workspaceId, memberId);
|
||||||
{ id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'idle' },
|
}
|
||||||
],
|
} catch (error) {
|
||||||
},
|
console.error('워크스페이스 조회 실패:', error);
|
||||||
{ id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'idle' },
|
}
|
||||||
{ id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'done' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{ id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'very-extremely-long-long-project-name-222',
|
|
||||||
type: 'Classification',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: 23,
|
|
||||||
type: 'directory',
|
|
||||||
name: 'this-is-my-very-very-long-directory-name-that-will-be-overflow',
|
|
||||||
children: [
|
|
||||||
{ id: 1, url: '', type: 'image', name: 'image-1.jpg', status: 'done' },
|
|
||||||
{ id: 1, url: '', type: 'image', name: 'image-2.jpg', status: 'done' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
url: '',
|
|
||||||
type: 'image',
|
|
||||||
name: 'wow-this-is-my-very-very-long-image-name-so-this-will-be-overflow-too.jpg',
|
|
||||||
status: 'idle',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchProjects = async (workspaceId: number, memberId: number) => {
|
||||||
|
try {
|
||||||
|
const projectResponse = await getAllProjectsApi(workspaceId, memberId);
|
||||||
|
if (projectResponse.isSuccess) {
|
||||||
|
const projects = await Promise.all(
|
||||||
|
projectResponse.data.workspaceResponses.map(async (project) => {
|
||||||
|
const children = await fetchFolderWithImages(project.id, memberId);
|
||||||
|
return {
|
||||||
|
id: project.id,
|
||||||
|
name: project.title,
|
||||||
|
type: capitalizeType(project.projectType),
|
||||||
|
children,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setWorkspace((prev) => ({
|
||||||
|
...prev,
|
||||||
|
projects: projects as Project[],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('프로젝트 목록 조회 실패:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFolderWithImages = async (projectId: number, memberId: number): Promise<DirectoryItem[]> => {
|
||||||
|
try {
|
||||||
|
const folderResponse = await fetchFolderApi(projectId, 0, memberId);
|
||||||
|
if (folderResponse.isSuccess) {
|
||||||
|
const files: FileItem[] = folderResponse.data.images.map((image) => ({
|
||||||
|
id: image.id,
|
||||||
|
name: image.imageTitle,
|
||||||
|
url: image.imageUrl,
|
||||||
|
type: 'image',
|
||||||
|
status: image.status === 'COMPLETED' ? 'done' : 'idle',
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: folderResponse.data.id,
|
||||||
|
name: folderResponse.data.title,
|
||||||
|
type: 'directory',
|
||||||
|
children: files,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('폴더 및 이미지 조회 실패:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const capitalizeType = (
|
||||||
|
type: 'classification' | 'detection' | 'segmentation'
|
||||||
|
): 'Classification' | 'Detection' | 'Segmentation' => {
|
||||||
|
switch (type) {
|
||||||
|
case 'classification':
|
||||||
|
return 'Classification';
|
||||||
|
case 'detection':
|
||||||
|
return 'Detection';
|
||||||
|
case 'segmentation':
|
||||||
|
return 'Segmentation';
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown project type: ${type}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (workspaceId && memberId) {
|
||||||
|
fetchWorkspaceData(Number(workspaceId), memberId);
|
||||||
|
}
|
||||||
|
}, [workspaceId, projectId, memberId]);
|
||||||
|
|
||||||
const labels: Label[] = [
|
const labels: Label[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { FileItem } from '@/types';
|
import { FileItem } from '@/types';
|
||||||
import { Check, Image, Minus } from 'lucide-react';
|
import { Check, Image, Minus } from 'lucide-react';
|
||||||
|
import useCanvasStore from '@/stores/useCanvasStore';
|
||||||
|
|
||||||
export default function ProjectFileItem({
|
export default function ProjectFileItem({
|
||||||
className = '',
|
className = '',
|
||||||
@ -12,6 +13,19 @@ export default function ProjectFileItem({
|
|||||||
depth?: number;
|
depth?: number;
|
||||||
}) {
|
}) {
|
||||||
const paddingLeft = depth * 12;
|
const paddingLeft = depth * 12;
|
||||||
|
const changeImage = useCanvasStore((state) => state.changeImage);
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
changeImage(item.url, [
|
||||||
|
{
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
type: 'rect',
|
||||||
|
color: '#FF0000',
|
||||||
|
coordinates: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@ -19,6 +33,7 @@ export default function ProjectFileItem({
|
|||||||
style={{
|
style={{
|
||||||
paddingLeft,
|
paddingLeft,
|
||||||
}}
|
}}
|
||||||
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Image
|
<Image
|
||||||
|
@ -1,10 +1,22 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { SquarePen } from 'lucide-react';
|
import { SquarePen } from 'lucide-react';
|
||||||
import { ResizableHandle, ResizablePanel } from '../ui/resizable';
|
import { ResizableHandle, ResizablePanel } from '../ui/resizable';
|
||||||
import ProjectStructure from './ProjectStructure';
|
import ProjectStructure from './ProjectStructure';
|
||||||
import { Button } from '../ui/button';
|
|
||||||
import { Project } from '@/types';
|
import { Project } from '@/types';
|
||||||
|
import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '../ui/select';
|
||||||
|
import ProjectCreateModal from '../ProjectCreateModal';
|
||||||
|
|
||||||
export default function WorkspaceSidebar({ workspaceName, projects }: { workspaceName: string; projects: Project[] }) {
|
export default function WorkspaceSidebar({ workspaceName, projects }: { workspaceName: string; projects: Project[] }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { workspaceId } = useParams<{ workspaceId: string }>();
|
||||||
|
const [selectedProjectId, setSelectedProjectId] = useState<string | undefined>();
|
||||||
|
|
||||||
|
const handleSelectProject = (projectId: string) => {
|
||||||
|
setSelectedProjectId(projectId);
|
||||||
|
navigate(`/workspace/${workspaceId}/project/${projectId}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
@ -21,17 +33,32 @@ export default function WorkspaceSidebar({ workspaceName, projects }: { workspac
|
|||||||
<button>
|
<button>
|
||||||
<SquarePen size={16} />
|
<SquarePen size={16} />
|
||||||
</button>
|
</button>
|
||||||
<Button
|
<ProjectCreateModal
|
||||||
variant="outline"
|
onSubmit={(data) => console.log('프로젝트 생성:', data)}
|
||||||
size="xs"
|
buttonClass="caption border-gray-800 bg-gray-100 flex items-center gap-2"
|
||||||
className="caption border-gray-800 bg-gray-100"
|
/>
|
||||||
onClick={() => console.log('New project')}
|
|
||||||
>
|
|
||||||
새 프로젝트
|
|
||||||
</Button>
|
|
||||||
</header>
|
</header>
|
||||||
<div>
|
<div className="p-2">
|
||||||
|
<Select onValueChange={handleSelectProject}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="프로젝트를 선택해주세요" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
|
<SelectItem
|
||||||
|
key={project.id}
|
||||||
|
value={project.id.toString()}
|
||||||
|
>
|
||||||
|
{project.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{projects
|
||||||
|
.filter((project) => project.id.toString() === selectedProjectId)
|
||||||
|
.map((project) => (
|
||||||
<ProjectStructure
|
<ProjectStructure
|
||||||
key={project.id}
|
key={project.id}
|
||||||
project={project}
|
project={project}
|
||||||
|
Loading…
Reference in New Issue
Block a user