From 02602a0dd103406827f2efeb87feff0fb11ceed4 Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Mon, 23 Sep 2024 22:15:52 +0900 Subject: [PATCH 01/22] =?UTF-8?q?Feat:=20=EC=84=B8=EA=B7=B8=EB=A9=98?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EB=A0=88=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EA=B2=B0=EA=B3=BC=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/ImageCanvas/LabelPolygon.tsx | 13 ++++++++----- frontend/src/components/ImageCanvas/index.tsx | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/ImageCanvas/LabelPolygon.tsx b/frontend/src/components/ImageCanvas/LabelPolygon.tsx index 3031f62..fac1409 100644 --- a/frontend/src/components/ImageCanvas/LabelPolygon.tsx +++ b/frontend/src/components/ImageCanvas/LabelPolygon.tsx @@ -1,6 +1,5 @@ import { Label } from '@/types'; import Konva from 'konva'; -import { useState } from 'react'; import { Line } from 'react-konva'; import PolygonTransformer from './PolygonTransformer'; @@ -8,21 +7,25 @@ export default function LabelPolygon({ isSelected, onSelect, info, + setLabel, stage, dragLayer, }: { isSelected: boolean; onSelect: () => void; info: Label; + setLabel: (coordinate: [number, number][]) => void; stage: Konva.Stage; dragLayer: Konva.Layer; }) { - const [coordinates, setCoordinates] = useState>(info.coordinates); + const handleChange = (coordinates: [number, number][]) => { + setLabel(coordinates); + }; return ( <> {isSelected && ( diff --git a/frontend/src/components/ImageCanvas/index.tsx b/frontend/src/components/ImageCanvas/index.tsx index b593d20..76a4790 100644 --- a/frontend/src/components/ImageCanvas/index.tsx +++ b/frontend/src/components/ImageCanvas/index.tsx @@ -289,6 +289,7 @@ export default function ImageCanvas() { isSelected={label.id === selectedLabelId} onSelect={() => setSelectedLabelId(label.id)} info={label} + setLabel={setLabel(label.id)} stage={stageRef.current as Konva.Stage} dragLayer={dragLayerRef.current as Konva.Layer} /> From c145bc32e41f756021afb4af7eb177847c67266f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=B0=BD=EA=B8=B0?= Date: Tue, 24 Sep 2024 09:01:02 +0900 Subject: [PATCH 02/22] =?UTF-8?q?Fix:=20=EB=B0=B1=EC=97=94=EB=93=9C=20api?= =?UTF-8?q?=20=EC=9D=91=EB=8B=B5=20=ED=98=95=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/projectApi.ts | 4 ++-- frontend/src/components/AdminProjectSidebar/index.tsx | 3 +-- frontend/src/components/WorkspaceLayout/index.tsx | 2 +- frontend/src/pages/ReviewRequest.tsx | 6 +++--- frontend/src/pages/WorkspaceBrowseDetail.tsx | 4 +--- 5 files changed, 8 insertions(+), 11 deletions(-) diff --git a/frontend/src/api/projectApi.ts b/frontend/src/api/projectApi.ts index 450e692..d122996 100644 --- a/frontend/src/api/projectApi.ts +++ b/frontend/src/api/projectApi.ts @@ -1,5 +1,5 @@ import api from '@/api/axiosConfig'; -import { ProjectListResponse, ProjectResponse, ProjectMemberRequest, ProjectMemberResponse } from '@/types'; +import { ProjectResponse, ProjectMemberRequest, ProjectMemberResponse } from '@/types'; export async function getProjectList( workspaceId: number, @@ -8,7 +8,7 @@ export async function getProjectList( limit: number = 50 ) { return api - .get(`/workspaces/${workspaceId}/projects`, { + .get(`/workspaces/${workspaceId}/projects`, { params: { memberId, lastProjectId, diff --git a/frontend/src/components/AdminProjectSidebar/index.tsx b/frontend/src/components/AdminProjectSidebar/index.tsx index 5d86650..454df8a 100644 --- a/frontend/src/components/AdminProjectSidebar/index.tsx +++ b/frontend/src/components/AdminProjectSidebar/index.tsx @@ -19,8 +19,7 @@ export default function AdminProjectSidebar(): JSX.Element { const { data: workspaceData } = useWorkspaceQuery(Number(workspaceId), memberId); const workspaceTitle = workspaceData?.title || `Workspace-${workspaceId}`; - const { data: projectsResponse } = useProjectListQuery(Number(workspaceId), memberId); - const projects = projectsResponse?.workspaceResponses ?? []; + const { data: projects } = useProjectListQuery(Number(workspaceId), memberId); const createProject = useCreateProjectQuery(); diff --git a/frontend/src/components/WorkspaceLayout/index.tsx b/frontend/src/components/WorkspaceLayout/index.tsx index fd6bb48..13d6927 100644 --- a/frontend/src/components/WorkspaceLayout/index.tsx +++ b/frontend/src/components/WorkspaceLayout/index.tsx @@ -30,7 +30,7 @@ export default function WorkspaceLayout() { useEffect(() => { if (!projectListData) return; - const projects = projectListData.workspaceResponses.map( + const projects = projectListData.map( (project): Project => ({ id: project.id, name: project.title, diff --git a/frontend/src/pages/ReviewRequest.tsx b/frontend/src/pages/ReviewRequest.tsx index f5a15f5..349a54a 100644 --- a/frontend/src/pages/ReviewRequest.tsx +++ b/frontend/src/pages/ReviewRequest.tsx @@ -21,7 +21,7 @@ export default function ReviewRequest(): JSX.Element { const profile = useAuthStore((state) => state.profile); const memberId = profile?.id || 0; - const { data: projectList } = useProjectListQuery(Number(workspaceId), memberId); + const { data: projects } = useProjectListQuery(Number(workspaceId), memberId); const { register, @@ -63,8 +63,8 @@ export default function ReviewRequest(): JSX.Element { - {projectList?.workspaceResponses.length ? ( - projectList.workspaceResponses.map((project) => ( + {projects.length ? ( + projects.map((project) => ( Date: Tue, 24 Sep 2024 09:17:00 +0900 Subject: [PATCH 03/22] =?UTF-8?q?Feat:=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/WorkspaceBrowseDetail.tsx | 1 + frontend/src/types/index.ts | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/WorkspaceBrowseDetail.tsx b/frontend/src/pages/WorkspaceBrowseDetail.tsx index e968157..41fec6c 100644 --- a/frontend/src/pages/WorkspaceBrowseDetail.tsx +++ b/frontend/src/pages/WorkspaceBrowseDetail.tsx @@ -103,6 +103,7 @@ function ProjectList({ projects, workspaceId }: { projects: ProjectResponse[]; w title={project.title} to={`${webPath.workspace()}/${workspaceId}/${project.id}`} description={project.projectType} + imageUrl={project.thumbnail} /> ))} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index c0bc387..d41cf5b 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -122,18 +122,15 @@ export interface ProjectRequest { projectType: 'classification' | 'detection' | 'segmentation'; } -export interface ProjectResponse { +export type ProjectResponse = { id: number; title: string; workspaceId: number; projectType: 'classification' | 'detection' | 'segmentation'; createdAt: string; updatedAt: string; -} - -export interface ProjectListResponse { - workspaceResponses: ProjectResponse[]; -} + thumbnail?: string; // Optional +}; // 댓글 관련 DTO export interface CommentRequest { From ab60ed9fb9587c38b4737167ffe02c8caff63bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 09:17:40 +0900 Subject: [PATCH 04/22] =?UTF-8?q?Design:=20admin=20=EB=94=94=EC=9E=90?= =?UTF-8?q?=EC=9D=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ModelManage.tsx | 2 +- frontend/src/pages/ProjectReviewList.tsx | 2 +- frontend/src/pages/WorkspaceReviewList.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/ModelManage.tsx b/frontend/src/pages/ModelManage.tsx index eb814d4..ee7edb8 100644 --- a/frontend/src/pages/ModelManage.tsx +++ b/frontend/src/pages/ModelManage.tsx @@ -33,7 +33,7 @@ export default function ModelManage() { }; return ( -
+

모델 관리

diff --git a/frontend/src/pages/ProjectReviewList.tsx b/frontend/src/pages/ProjectReviewList.tsx index 599ce22..405743d 100644 --- a/frontend/src/pages/ProjectReviewList.tsx +++ b/frontend/src/pages/ProjectReviewList.tsx @@ -28,7 +28,7 @@ export default function ProjectReviewList() { to={`/admin/${workspaceId}/reviews/request`} className="ml-auto" > - +
diff --git a/frontend/src/pages/WorkspaceReviewList.tsx b/frontend/src/pages/WorkspaceReviewList.tsx index dad0182..92f0a7c 100644 --- a/frontend/src/pages/WorkspaceReviewList.tsx +++ b/frontend/src/pages/WorkspaceReviewList.tsx @@ -28,7 +28,7 @@ export default function WorkspaceReviewList() { to={`/admin/${workspaceId}/reviews/request`} className="ml-auto" > - + Date: Tue, 24 Sep 2024 09:13:17 +0900 Subject: [PATCH 05/22] =?UTF-8?q?Feat:=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EC=A0=91?= =?UTF-8?q?=EA=B8=B0,=20=ED=8E=B4=EA=B8=B0=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/WorkspaceSidebar/ProjectDirectoryItem.tsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/WorkspaceSidebar/ProjectDirectoryItem.tsx b/frontend/src/components/WorkspaceSidebar/ProjectDirectoryItem.tsx index 7689ea7..d3dd73b 100644 --- a/frontend/src/components/WorkspaceSidebar/ProjectDirectoryItem.tsx +++ b/frontend/src/components/WorkspaceSidebar/ProjectDirectoryItem.tsx @@ -36,20 +36,19 @@ export default function ProjectDirectoryItem({ {item.title}
- {isExpanded && ( -
+ { +
{folderData.children.map((item) => ( ))} {folderData.images.map((item) => ( @@ -61,7 +60,7 @@ export default function ProjectDirectoryItem({ /> ))}
- )} + } ); } From 8931bd323ac1d56852c866cdf4f63a97f6975979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 09:26:55 +0900 Subject: [PATCH 06/22] =?UTF-8?q?Test:=20=EC=8D=B8=EB=84=A4=EC=9D=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/mocks/handlers.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/mocks/handlers.ts b/frontend/src/mocks/handlers.ts index 0c730a4..b7efee8 100644 --- a/frontend/src/mocks/handlers.ts +++ b/frontend/src/mocks/handlers.ts @@ -7,7 +7,6 @@ import { MemberResponse, RefreshTokenResponse, AutoLabelingResponse, - ProjectListResponse, ErrorResponse, } from '@/types'; @@ -100,15 +99,15 @@ export const handlers = [ | 'segmentation', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + thumbnail: `thumbnail_${lastProjectId + index + 1}.jpg`, })); // 응답 생성 - const response: ProjectListResponse = { - workspaceResponses: projects, - }; + const response: ProjectResponse[] = projects; return HttpResponse.json(response); }), + http.get('/api/projects/:projectId', ({ params }) => { // 프로젝트 조회 핸들러 const { projectId } = params; From f74dbd363b53be9b1ef9da63107cee6bdd288aeb Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Tue, 24 Sep 2024 09:47:34 +0900 Subject: [PATCH 07/22] =?UTF-8?q?Feat:=20=EB=A0=88=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=EC=BB=AC=EB=9F=AC=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ImageCanvas/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/ImageCanvas/index.tsx b/frontend/src/components/ImageCanvas/index.tsx index 76a4790..11c34d0 100644 --- a/frontend/src/components/ImageCanvas/index.tsx +++ b/frontend/src/components/ImageCanvas/index.tsx @@ -119,7 +119,7 @@ export default function ImageCanvas() { setPolygonPoints([]); if (polygonPoints.length < 4) return; - const color = Math.floor(Math.random() * 65535) + const color = Math.floor(Math.random() * 0xffffff) .toString(16) .padStart(6, '0'); const id = labels.length + 1; @@ -146,7 +146,7 @@ export default function ImageCanvas() { return; } setRectPoints([]); - const color = Math.floor(Math.random() * 65535) + const color = Math.floor(Math.random() * 0xffffff) .toString(16) .padStart(6, '0'); const id = labels.length; From 6faed26f9211b68d999ab1246e2f943c98346f3b Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Tue, 24 Sep 2024 11:13:18 +0900 Subject: [PATCH 08/22] =?UTF-8?q?Feat:=20=ED=8C=8C=EC=9D=BC=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EC=8B=9C=20=ED=94=84=EB=A1=9C=EC=A0=9D?= =?UTF-8?q?=ED=8A=B8=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EA=B0=B1?= =?UTF-8?q?=EC=8B=A0=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WorkspaceDropdownMenu/index.tsx | 25 ++++++++++++++++--- .../WorkspaceSidebar/ProjectStructure.tsx | 3 ++- .../src/components/WorkspaceSidebar/index.tsx | 3 ++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/WorkspaceDropdownMenu/index.tsx b/frontend/src/components/WorkspaceDropdownMenu/index.tsx index a51be43..472a159 100644 --- a/frontend/src/components/WorkspaceDropdownMenu/index.tsx +++ b/frontend/src/components/WorkspaceDropdownMenu/index.tsx @@ -12,17 +12,34 @@ import React from 'react'; import ImageUploadFolderForm from '../ImageUploadFolderModal/ImageUploadFolderForm'; import ImageUploadZipForm from '../ImageUploadZipModal/ImageUploadZipForm'; -export default function WorkspaceDropdownMenu({ projectId, folderId }: { projectId: number; folderId: number }) { +export default function WorkspaceDropdownMenu({ + projectId, + folderId, + refetch, +}: { + projectId: number; + folderId: number; + refetch: () => void; +}) { const [isOpenUploadFile, setIsOpenUploadFile] = React.useState(false); const [isOpenUploadFolder, setIsOpenUploadFolder] = React.useState(false); const [isOpenUploadZip, setIsOpenUploadZip] = React.useState(false); const handleOpenUploadFile = () => setIsOpenUploadFile(true); - const handleCloseUploadFile = () => setIsOpenUploadFile(false); + const handleCloseUploadFile = () => { + refetch(); + setIsOpenUploadFile(false); + }; const handleOpenUploadFolder = () => setIsOpenUploadFolder(true); - const handleCloseUploadFolder = () => setIsOpenUploadFolder(false); + const handleCloseUploadFolder = () => { + refetch(); + setIsOpenUploadFolder(false); + }; const handleOpenUploadZip = () => setIsOpenUploadZip(true); - const handleCloseUploadZip = () => setIsOpenUploadZip(false); + const handleCloseUploadZip = () => { + refetch(); + setIsOpenUploadZip(false); + }; return ( <> diff --git a/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx b/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx index 3bbee52..c51c465 100644 --- a/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx +++ b/frontend/src/components/WorkspaceSidebar/ProjectStructure.tsx @@ -11,7 +11,7 @@ import WorkspaceDropdownMenu from '../WorkspaceDropdownMenu'; export default function ProjectStructure({ project }: { project: Project }) { const setProject = useCanvasStore((state) => state.setProject); const image = useCanvasStore((state) => state.image); - const { data: folderData } = useFolderQuery(project.id.toString(), 0); + const { data: folderData, refetch } = useFolderQuery(project.id.toString(), 0); useEffect(() => { setProject(project); @@ -27,6 +27,7 @@ export default function ProjectStructure({ project }: { project: Project }) { {folderData.children.length === 0 && folderData.images.length === 0 ? ( diff --git a/frontend/src/components/WorkspaceSidebar/index.tsx b/frontend/src/components/WorkspaceSidebar/index.tsx index 87e5597..c2cd817 100644 --- a/frontend/src/components/WorkspaceSidebar/index.tsx +++ b/frontend/src/components/WorkspaceSidebar/index.tsx @@ -5,6 +5,7 @@ import { Project } from '@/types'; import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '../ui/select'; import useCanvasStore from '@/stores/useCanvasStore'; import { webPath } from '@/router'; +import { Suspense } from 'react'; export default function WorkspaceSidebar({ workspaceName, projects }: { workspaceName: string; projects: Project[] }) { const { projectId: selectedProjectId } = useParams<{ projectId: string }>(); @@ -48,7 +49,7 @@ export default function WorkspaceSidebar({ workspaceName, projects }: { workspac
- {selectedProject && } +
}>{selectedProject && } From dd36acb3a06234cca5db54ee3b47eb387fdfeae6 Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Tue, 24 Sep 2024 11:22:55 +0900 Subject: [PATCH 09/22] =?UTF-8?q?Fix:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ImageFolderUploadTest.tsx | 16 ---------------- frontend/src/router/index.tsx | 6 ------ 2 files changed, 22 deletions(-) delete mode 100644 frontend/src/pages/ImageFolderUploadTest.tsx diff --git a/frontend/src/pages/ImageFolderUploadTest.tsx b/frontend/src/pages/ImageFolderUploadTest.tsx deleted file mode 100644 index 0f0cf2b..0000000 --- a/frontend/src/pages/ImageFolderUploadTest.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import WorkspaceDropdownMenu from '@/components/WorkspaceDropdownMenu'; -import { useParams } from 'react-router-dom'; - -export default function ImageFolderUploadTest() { - const params = useParams<{ workspaceId: string; projectId: string }>(); - const projectId = Number(params.projectId); - - return ( -
- -
- ); -} diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index 0ec0711..bcd9f75 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -16,7 +16,6 @@ import WorkspaceBrowseIndex from '@/pages/WorkspaceBrowseIndex'; import AdminIndex from '@/pages/AdminIndex'; import LabelCanvas from '@/pages/LabelCanvas'; import ReviewDetail from '@/pages/ReviewDetail'; -import ImageFolderUploadTest from '@/pages/ImageFolderUploadTest'; import NotFound from '@/pages/NotFound'; import ModelManage from '@/pages/ModelManage'; import ReviewRequest from '@/pages/ReviewRequest'; @@ -26,7 +25,6 @@ export const webPath = { workspace: () => '/workspace', admin: () => `/admin`, oauthCallback: () => '/redirect/oauth2', - imageFolderUploadTest: () => '/imagefolderuploadtest', }; const router = createBrowserRouter([ @@ -147,10 +145,6 @@ const router = createBrowserRouter([ ), }, - { - path: `${webPath.imageFolderUploadTest()}/:projectId`, - element: , - }, ]); export default router; From 79b4b138ec705bf9e5a3153c89f599021f787850 Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Tue, 24 Sep 2024 12:45:15 +0900 Subject: [PATCH 10/22] =?UTF-8?q?Fix:=20=ED=8F=B4=EB=A6=AC=EA=B3=A4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=8B=9C=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20-=20S11P21S002-209?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ImageCanvas/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/ImageCanvas/index.tsx b/frontend/src/components/ImageCanvas/index.tsx index 11c34d0..2a04e5d 100644 --- a/frontend/src/components/ImageCanvas/index.tsx +++ b/frontend/src/components/ImageCanvas/index.tsx @@ -122,7 +122,7 @@ export default function ImageCanvas() { const color = Math.floor(Math.random() * 0xffffff) .toString(16) .padStart(6, '0'); - const id = labels.length + 1; + const id = labels.length; addLabel({ id: id, name: 'label', From c3838c40c92ad3dcf18cd08f0a8c82d98576092a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 16:28:08 +0900 Subject: [PATCH 11/22] =?UTF-8?q?Feat:=20train=EC=9A=A9=20store=20?= =?UTF-8?q?=EC=A0=9C=EC=9E=91,=20=EC=B6=94=ED=9B=84=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/stores/useTrainStore.ts | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 frontend/src/stores/useTrainStore.ts diff --git a/frontend/src/stores/useTrainStore.ts b/frontend/src/stores/useTrainStore.ts new file mode 100644 index 0000000..6aeffd9 --- /dev/null +++ b/frontend/src/stores/useTrainStore.ts @@ -0,0 +1,40 @@ +import { create } from 'zustand'; + +interface TrainingData { + epoch: number; + total_epochs: number; + box_loss: number; + cls_loss: number; + dfl_loss: number; + fitness: number; + epoch_time: number; + left_second: number; +} + +interface StoreState { + trainingDataByProject: { [projectId: string]: TrainingData[] }; + addTrainingData: (projectId: string, data: TrainingData) => void; + resetTrainingData: (projectId: string) => void; +} + +const useTrainStore = create((set) => ({ + trainingDataByProject: {}, + + addTrainingData: (projectId: string, data: TrainingData) => + set((state) => ({ + trainingDataByProject: { + ...state.trainingDataByProject, + [projectId]: [...(state.trainingDataByProject[projectId] || []), data], + }, + })), + + resetTrainingData: (projectId: string) => + set((state) => ({ + trainingDataByProject: { + ...state.trainingDataByProject, + [projectId]: [], + }, + })), +})); + +export default useTrainStore; From f2a8482c7cd5b79fde7447d111260e3558406027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:31:53 +0900 Subject: [PATCH 12/22] =?UTF-8?q?Refactor:=20=EB=9D=BC=EC=9A=B0=ED=84=B0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/router/index.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index 0ec0711..ce5ef55 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -18,8 +18,10 @@ import LabelCanvas from '@/pages/LabelCanvas'; import ReviewDetail from '@/pages/ReviewDetail'; import ImageFolderUploadTest from '@/pages/ImageFolderUploadTest'; import NotFound from '@/pages/NotFound'; -import ModelManage from '@/pages/ModelManage'; import ReviewRequest from '@/pages/ReviewRequest'; +import ModelIndex from '@/pages/ModelIndex'; +import ModelDetail from '@/pages/ModelDetail'; + export const webPath = { home: () => '/', browse: () => '/browse', @@ -133,7 +135,11 @@ const router = createBrowserRouter([ children: [ { index: true, - element: , + element: , + }, + { + path: ':projectId', + element: , }, ], }, From 7792cb09e2387091a43a4756e5f9bd87d58db6f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:32:26 +0900 Subject: [PATCH 13/22] =?UTF-8?q?Refactor:=20=EB=A9=A4=EB=B2=84=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ProjectMemberManage.tsx | 95 ++++++++++++-------- frontend/src/pages/WorkspaceMemberManage.tsx | 57 ++++++------ 2 files changed, 88 insertions(+), 64 deletions(-) diff --git a/frontend/src/pages/ProjectMemberManage.tsx b/frontend/src/pages/ProjectMemberManage.tsx index 6776987..3983569 100644 --- a/frontend/src/pages/ProjectMemberManage.tsx +++ b/frontend/src/pages/ProjectMemberManage.tsx @@ -9,6 +9,8 @@ import useAddProjectMemberQuery from '@/queries/projects/useAddProjectMemberQuer import { useEffect, useRef, useMemo } from 'react'; import { useQueryClient } from '@tanstack/react-query'; import MemberAddModal from '@/components/MemberAddModal'; +import useIsAdminOrManager from '@/hooks/useIsAdminOrManager'; + type Role = 'ADMIN' | 'MANAGER' | 'EDITOR' | 'VIEWER' | 'NONE'; const roles: Role[] = ['ADMIN', 'MANAGER', 'EDITOR', 'VIEWER', 'NONE']; const roleToStr: { [key in Role]: string } = { @@ -25,20 +27,21 @@ export default function ProjectMemberManage() { const memberId = profile?.id || 0; const queryClient = useQueryClient(); - const previousProjectId = useRef(projectId); const { data: projectMembers = [] } = useProjectMembersQuery(Number(projectId), memberId); const { data: workspaceMembers = [] } = useWorkspaceMembersQuery(Number(workspaceId)); + const isAdminOrManager = useIsAdminOrManager(Number(projectId)); + const updatePrivilege = useUpdateProjectMemberPrivilegeQuery(); const removeMember = useRemoveProjectMemberQuery(); const addProjectMember = useAddProjectMemberQuery(); useEffect(() => { if (projectId && previousProjectId.current !== projectId) { - queryClient.invalidateQueries({ queryKey: ['projectMembers', Number(previousProjectId.current), memberId] }); // 이전 projectId의 캐시 무효화 - queryClient.invalidateQueries({ queryKey: ['workspaceMembers', Number(workspaceId)] }); // workspaceMembers 무효화 + queryClient.invalidateQueries({ queryKey: ['projectMembers', Number(previousProjectId.current), memberId] }); + queryClient.invalidateQueries({ queryKey: ['workspaceMembers', Number(workspaceId)] }); queryClient.invalidateQueries({ queryKey: ['projectMembers', Number(projectId), memberId] }); previousProjectId.current = projectId; @@ -85,43 +88,57 @@ export default function ProjectMemberManage() { }; return ( -
-
-

프로젝트 멤버 관리

- -
+
+
+
+

프로젝트 멤버 관리

+ {isAdminOrManager && } +
- {sortedMembers.map((member) => ( -
- {member.nickname} -
- -
-
- ))} +
+ {sortedMembers.length === 0 ? ( +
프로젝트에 멤버가 없습니다.
+ ) : ( +
+ {sortedMembers.map((member) => ( +
+ {member.nickname} + {member.nickname} +
+ +
+
+ ))} +
+ )} +
+
); } diff --git a/frontend/src/pages/WorkspaceMemberManage.tsx b/frontend/src/pages/WorkspaceMemberManage.tsx index d733b85..7345130 100644 --- a/frontend/src/pages/WorkspaceMemberManage.tsx +++ b/frontend/src/pages/WorkspaceMemberManage.tsx @@ -11,32 +11,39 @@ export default function WorkspaceMemberManage() { const { data: members = [] } = useWorkspaceMembersQuery(Number(workspaceId)); return ( -
-
-

워크스페이스 멤버 관리

- -
+
+
+
+

워크스페이스 멤버 관리

- {members.length === 0 ? ( -
워크스페이스에 멤버가 없습니다.
- ) : ( - members.map((member) => ( -
- {member.nickname} - {member.nickname} -
- )) - )} + +
+ +
+ {members.length === 0 ? ( +
워크스페이스에 멤버가 없습니다.
+ ) : ( +
+ {members.map((member) => ( +
+ {member.nickname} + {member.nickname} +
+ ))} +
+ )} +
+
); } From 3aca686dd02c753ca60a324047a750b9d116e407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:33:01 +0900 Subject: [PATCH 14/22] =?UTF-8?q?Refactor:=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ReviewRequest.tsx | 52 ++++++---------------------- 1 file changed, 11 insertions(+), 41 deletions(-) diff --git a/frontend/src/pages/ReviewRequest.tsx b/frontend/src/pages/ReviewRequest.tsx index 349a54a..aa029b8 100644 --- a/frontend/src/pages/ReviewRequest.tsx +++ b/frontend/src/pages/ReviewRequest.tsx @@ -1,55 +1,25 @@ -import { useState } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { useForm } from 'react-hook-form'; -import useCreateReviewQuery from '@/queries/reviews/useCreateReviewQuery'; -import type { ReviewRequest } from '@/types'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import useAuthStore from '@/stores/useAuthStore'; -import useProjectListQuery from '@/queries/projects/useProjectListQuery'; import ImageSelection from '@/components/ImageSelection'; +import useReviewRequest from '@/hooks/useReviewRequest'; +import { useNavigate } from 'react-router-dom'; export default function ReviewRequest(): JSX.Element { - const { workspaceId } = useParams<{ workspaceId: string }>(); - const navigate = useNavigate(); - const [selectedImages, setSelectedImages] = useState([]); - const [selectedProjectId, setSelectedProjectId] = useState(null); - - const profile = useAuthStore((state) => state.profile); - const memberId = profile?.id || 0; - - const { data: projects } = useProjectListQuery(Number(workspaceId), memberId); - const { register, handleSubmit, - formState: { errors }, - } = useForm(); - const createReview = useCreateReviewQuery(); - - const onSubmit = (data: ReviewRequest) => { - if (!selectedProjectId) { - return; - } - createReview.mutate( - { - projectId: Number(selectedProjectId), - memberId, - reviewData: { - ...data, - imageIds: selectedImages, - }, - }, - { - onSuccess: () => { - navigate(`/admin/${workspaceId}/reviews`); - }, - } - ); - }; + errors, + projects, + onSubmit, + selectedProjectId, + setSelectedProjectId, + selectedImages, + setSelectedImages, + } = useReviewRequest(); + const navigate = useNavigate(); return (
From 865bb12af554bf4c77d55f4459fdadd2d91a772e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:33:32 +0900 Subject: [PATCH 15/22] =?UTF-8?q?Refactr:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/ModelDetail.tsx | 12 ++ frontend/src/pages/ModelIndex.tsx | 13 ++ frontend/src/pages/ModelManage.tsx | 268 ----------------------------- 3 files changed, 25 insertions(+), 268 deletions(-) create mode 100644 frontend/src/pages/ModelDetail.tsx create mode 100644 frontend/src/pages/ModelIndex.tsx delete mode 100644 frontend/src/pages/ModelManage.tsx diff --git a/frontend/src/pages/ModelDetail.tsx b/frontend/src/pages/ModelDetail.tsx new file mode 100644 index 0000000..7399100 --- /dev/null +++ b/frontend/src/pages/ModelDetail.tsx @@ -0,0 +1,12 @@ +import { Suspense } from 'react'; +import ModelManage from '@/components/ModelManage'; + +export default function ModelDetail() { + return ( +
}> +
+ +
+ + ); +} diff --git a/frontend/src/pages/ModelIndex.tsx b/frontend/src/pages/ModelIndex.tsx new file mode 100644 index 0000000..4b54191 --- /dev/null +++ b/frontend/src/pages/ModelIndex.tsx @@ -0,0 +1,13 @@ +import { Smile } from 'lucide-react'; + +export default function ModelIndex() { + return ( +
+ +
작업할 프로젝트를 선택하세요.
+
+ ); +} diff --git a/frontend/src/pages/ModelManage.tsx b/frontend/src/pages/ModelManage.tsx deleted file mode 100644 index ee7edb8..0000000 --- a/frontend/src/pages/ModelManage.tsx +++ /dev/null @@ -1,268 +0,0 @@ -import { Rabbit, Bird, Turtle } from 'lucide-react'; -import { Button } from '@/components/ui/button'; -import { Input } from '@/components/ui/input'; -import { Label } from '@/components/ui/label'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; -import ModelLineChart from '@/components/ModelLineChart'; -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { useState } from 'react'; -import ModelBarChart from '@/components/ModelBarChart'; - -export default function ModelManage() { - interface MetricData { - epoch: string; - loss1: number; - loss2: number; - loss3: number; - fitness: number; - } - - const dummyLossData: MetricData[] = [ - { epoch: '1', loss1: 0.45, loss2: 0.43, loss3: 0.42, fitness: 0.97 }, - { epoch: '2', loss1: 0.4, loss2: 0.38, loss3: 0.37, fitness: 0.98 }, - { epoch: '3', loss1: 0.38, loss2: 0.36, loss3: 0.35, fitness: 0.99 }, - { epoch: '4', loss1: 0.36, loss2: 0.34, loss3: 0.33, fitness: 1.0 }, - ]; - - const [lossData] = useState(dummyLossData); - const [training, setTraining] = useState(false); - const [selectedModel, setSelectedModel] = useState(null); - - const handleTrainingToggle = () => { - setTraining((prev) => !prev); - }; - - return ( -
-
-
-

모델 관리

-
- -
- - - 모델 학습 - 모델 평가 - - - {/* 모델 학습 탭 */} - -
-
- - -
-
- -
-
-
- - {/* 모델 평가 탭 */} - - {/* 모델 선택 */} -
- - -
- - {/* 선택된 모델에 따른 BarChart 및 Labeling Preview */} - {selectedModel && ( -
-
- -
-
- -
-
- )} -
-
-
-
-
- ); -} - -function LabelingPreview() { - return ( -
-

레이블링 프리뷰

-
- ); -} - -function SettingsForm() { - return ( -
-
- 모델 설정 -
- - -
-
- - - - - - -
-
-
- ); -} - -interface OptionWithIconProps { - icon: JSX.Element; - title: string; - description: string; -} - -function OptionWithIcon({ icon, title, description }: OptionWithIconProps) { - return ( -
- {icon} -
-

- Neural {title} -

-

- {description} -

-
-
- ); -} - -interface InputWithLabelProps { - label: string; - id: string; - placeholder: string; -} - -function InputWithLabel({ label, id, placeholder }: InputWithLabelProps) { - return ( -
- - -
- ); -} -interface SelectWithLabelProps { - label: string; - id: string; - options: string[]; - placeholder: string; -} - -function SelectWithLabel({ label, id, options, placeholder }: SelectWithLabelProps) { - return ( -
- - -
- ); -} From 1b7e86c83a7d92c837c75057e015db8b31e3650c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:33:58 +0900 Subject: [PATCH 16/22] =?UTF-8?q?Refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=ED=9B=85=20=EC=A0=9C=EA=B1=B0,=20=EC=83=88=EB=A1=9C?= =?UTF-8?q?=EC=9A=B4=20=ED=9B=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useAuthHooks.ts | 39 ---- frontend/src/hooks/useIsAdmin.ts | 17 ++ ...thCallbackHooks.ts => useOAuthCallback.ts} | 0 frontend/src/hooks/useProjectHooks.ts | 198 ------------------ frontend/src/hooks/useReviewHooks.ts | 57 ----- frontend/src/hooks/useReviewRequest.ts | 59 ++++++ frontend/src/hooks/useTrainPolling.ts | 49 +++++ frontend/src/hooks/useWorkspaceHooks.ts | 155 -------------- 8 files changed, 125 insertions(+), 449 deletions(-) delete mode 100644 frontend/src/hooks/useAuthHooks.ts create mode 100644 frontend/src/hooks/useIsAdmin.ts rename frontend/src/hooks/{useOAuthCallbackHooks.ts => useOAuthCallback.ts} (100%) delete mode 100644 frontend/src/hooks/useProjectHooks.ts delete mode 100644 frontend/src/hooks/useReviewHooks.ts create mode 100644 frontend/src/hooks/useReviewRequest.ts create mode 100644 frontend/src/hooks/useTrainPolling.ts delete mode 100644 frontend/src/hooks/useWorkspaceHooks.ts diff --git a/frontend/src/hooks/useAuthHooks.ts b/frontend/src/hooks/useAuthHooks.ts deleted file mode 100644 index 99ec964..0000000 --- a/frontend/src/hooks/useAuthHooks.ts +++ /dev/null @@ -1,39 +0,0 @@ -// import { useMutation, useQueryClient } from '@tanstack/react-query'; -// import useAuthStore from '@/stores/useAuthStore'; -// import { reissueToken } from '@/api/authApi'; -// import { useEffect } from 'react'; - -// import useProfileQuery from '@/queries/auth/useProfileQuery'; - -// export const useReissueToken = () => { -// const queryClient = useQueryClient(); -// const { setLoggedIn } = useAuthStore(); - -// return useMutation({ -// mutationFn: reissueToken, -// onSuccess: (data) => { -// setLoggedIn(true, data.accessToken); -// queryClient.invalidateQueries({ queryKey: ['profile'] }); -// }, -// }); -// }; - -// export const useProfile = () => { -// const { setProfile } = useAuthStore(); -// const query = useProfileQuery(); - -// // TODO: query.data가 변경될 때마다 setProfile을 호출하여 profile 업데이트, useEffect 제거 -// useEffect(() => { -// setProfile(query.data); -// }, [query.data, setProfile]); - -// return query; -// }; - -// export const useFetchProfile = () => { -// const { setProfile } = useAuthStore(); -// const query = useProfileQuery(); -// if (query.data) { -// setProfile(query.data); -// } -// }; diff --git a/frontend/src/hooks/useIsAdmin.ts b/frontend/src/hooks/useIsAdmin.ts new file mode 100644 index 0000000..111f509 --- /dev/null +++ b/frontend/src/hooks/useIsAdmin.ts @@ -0,0 +1,17 @@ +import { useMemo } from 'react'; +import useAuthStore from '@/stores/useAuthStore'; +import useProjectMembersQuery from '@/queries/projects/useProjectMembersQuery'; + +export default function useIsAdmin(projectId: number) { + const profile = useAuthStore((state) => state.profile); + const memberId = profile?.id || 0; + + const { data: projectMembers = [] } = useProjectMembersQuery(projectId, memberId); + + const isAdminOrManager = useMemo(() => { + const currentMember = projectMembers.find((member) => member.memberId === memberId); + return currentMember?.privilegeType === 'ADMIN'; + }, [projectMembers, memberId]); + + return isAdminOrManager; +} diff --git a/frontend/src/hooks/useOAuthCallbackHooks.ts b/frontend/src/hooks/useOAuthCallback.ts similarity index 100% rename from frontend/src/hooks/useOAuthCallbackHooks.ts rename to frontend/src/hooks/useOAuthCallback.ts diff --git a/frontend/src/hooks/useProjectHooks.ts b/frontend/src/hooks/useProjectHooks.ts deleted file mode 100644 index e72819f..0000000 --- a/frontend/src/hooks/useProjectHooks.ts +++ /dev/null @@ -1,198 +0,0 @@ -// TODO: 훅 재설계 -// import { useQuery, UseQueryResult, useMutation, UseMutationResult, useQueryClient } from '@tanstack/react-query'; -// import { AxiosError } from 'axios'; -// import { -// getProject, -// updateProject, -// deleteProject, -// getAllProjects, -// createProject, -// addProjectMember, -// removeProjectMember, -// } from '@/api/projectApi'; -// import { BaseResponse, ProjectResponse, ProjectListResponse, CustomError } from '@/types'; - -// export const useGetProject = ( -// projectId: number, -// memberId: number -// ): UseQueryResult, AxiosError> => { -// return useQuery, AxiosError>({ -// queryKey: ['project', projectId], -// queryFn: () => getProject(projectId, memberId), -// }); -// }; - -// export const useUpdateProject = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { -// projectId: number; -// memberId: number; -// data: { title: string; projectType: 'classification' | 'detection' | 'segmentation' }; -// } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ projectId, memberId, data }) => updateProject(projectId, memberId, data), -// onSuccess: (data) => { -// queryClient.invalidateQueries({ queryKey: ['project', data.data.id] }); -// }, -// }); -// }; - -// export const useDeleteProject = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { projectId: number; memberId: number } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ projectId, memberId }) => deleteProject(projectId, memberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] }); -// }, -// }); -// }; - -// export const useGetAllProjects = ( -// workspaceId: number, -// memberId: number, -// options?: { enabled: boolean } -// ): UseQueryResult, AxiosError> => { -// return useQuery, AxiosError>({ -// queryKey: ['projects', workspaceId], -// queryFn: () => getAllProjects(workspaceId, memberId), -// enabled: options?.enabled, -// }); -// }; - -// export const useCreateProject = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { -// workspaceId: number; -// memberId: number; -// data: { title: string; projectType: 'classification' | 'detection' | 'segmentation' }; -// } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ workspaceId, memberId, data }) => createProject(workspaceId, memberId, data), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['projects', variables.workspaceId] }); -// }, -// }); -// }; - -// export const useAddProjectMember = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { projectId: number; memberId: number; newMemberId: number; privilegeType: string } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ projectId, memberId, newMemberId, privilegeType }) => -// addProjectMember(projectId, memberId, newMemberId, privilegeType), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] }); -// }, -// }); -// }; - -// export const useRemoveProjectMember = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { projectId: number; memberId: number; targetMemberId: number } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ projectId, memberId, targetMemberId }) => removeProjectMember(projectId, memberId, targetMemberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['project', variables.projectId] }); -// }, -// }); -// }; -// import { useMutation, useQueryClient } from '@tanstack/react-query'; -// import { -// createProject, -// updateProject, -// deleteProject, -// addProjectMember, -// updateProjectMemberPrivilege, -// removeProjectMember, -// } from '@/api/projectApi'; -// import { ProjectResponse, ProjectRequest, ProjectMemberRequest, ProjectMemberResponse } from '@/types'; - -// export const useCreateProject = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: ({ workspaceId, memberId, data }) => createProject(workspaceId, memberId, data), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['projects', variables.workspaceId] }); -// }, -// }); -// }; - -// export const useUpdateProject = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: ({ projectId, memberId, data }) => updateProject(projectId, memberId, data), -// onSuccess: (data) => { -// queryClient.invalidateQueries({ queryKey: ['project', data.id] }); -// }, -// }); -// }; - -// export const useDeleteProject = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: ({ projectId, memberId }) => deleteProject(projectId, memberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['projects', variables.projectId] }); -// }, -// }); -// }; - -// export const useAddProjectMember = () => { -// const queryClient = useQueryClient(); -// return useMutation< -// ProjectMemberResponse, -// Error, -// { projectId: number; memberId: number; newMember: ProjectMemberRequest } -// >({ -// mutationFn: ({ projectId, memberId, newMember }) => addProjectMember(projectId, memberId, newMember), -// onSuccess: (_, { projectId }) => { -// queryClient.invalidateQueries({ queryKey: ['projectMembers', projectId] }); -// }, -// }); -// }; - -// // 프로젝트 멤버 권한 수정 훅 -// export const useUpdateProjectMemberPrivilege = () => { -// const queryClient = useQueryClient(); -// return useMutation< -// ProjectMemberResponse, -// Error, -// { projectId: number; memberId: number; privilegeData: ProjectMemberRequest } -// >({ -// mutationFn: ({ projectId, memberId, privilegeData }) => -// updateProjectMemberPrivilege(projectId, memberId, privilegeData), -// onSuccess: (_, { projectId }) => { -// queryClient.invalidateQueries({ queryKey: ['projectMembers', projectId] }); -// }, -// }); -// }; - -// // 프로젝트 멤버 삭제 훅 -// export const useRemoveProjectMember = () => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ projectId, memberId, targetMemberId }) => removeProjectMember(projectId, memberId, targetMemberId), -// onSuccess: (_, { projectId }) => { -// queryClient.invalidateQueries({ queryKey: ['projectMembers', projectId] }); -// }, -// }); -// }; diff --git a/frontend/src/hooks/useReviewHooks.ts b/frontend/src/hooks/useReviewHooks.ts deleted file mode 100644 index 90652a3..0000000 --- a/frontend/src/hooks/useReviewHooks.ts +++ /dev/null @@ -1,57 +0,0 @@ -// import { useMutation, useQueryClient } from '@tanstack/react-query'; -// import { createReview, updateReview, deleteReview, updateReviewStatus } from '@/api/reviewApi'; -// import { ReviewRequest, ReviewResponse } from '@/types'; - -// // 리뷰 생성 훅 -// export const useCreateReview = () => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ projectId, memberId, reviewData }) => createReview(projectId, memberId, reviewData), -// onSuccess: (_, { projectId, memberId }) => { -// queryClient.invalidateQueries({ queryKey: ['reviewList', projectId, memberId] }); -// }, -// }); -// }; - -// // 리뷰 수정 훅 -// export const useUpdateReview = () => { -// const queryClient = useQueryClient(); -// return useMutation< -// ReviewResponse, -// Error, -// { projectId: number; reviewId: number; memberId: number; reviewData: ReviewRequest } -// >({ -// mutationFn: ({ projectId, reviewId, memberId, reviewData }) => -// updateReview(projectId, reviewId, memberId, reviewData), -// onSuccess: (_, { projectId, reviewId }) => { -// queryClient.invalidateQueries({ queryKey: ['reviewDetail', projectId, reviewId] }); -// }, -// }); -// }; - -// // 리뷰 삭제 훅 -// export const useDeleteReview = () => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ projectId, reviewId, memberId }) => deleteReview(projectId, reviewId, memberId), -// onSuccess: (_, { projectId, reviewId }) => { -// queryClient.invalidateQueries({ queryKey: ['reviewDetail', projectId, reviewId] }); -// }, -// }); -// }; - -// // 리뷰 상태 변경 훅 -// export const useUpdateReviewStatus = () => { -// const queryClient = useQueryClient(); -// return useMutation< -// ReviewResponse, -// Error, -// { projectId: number; reviewId: number; memberId: number; reviewStatus: string } -// >({ -// mutationFn: ({ projectId, reviewId, memberId, reviewStatus }) => -// updateReviewStatus(projectId, reviewId, memberId, reviewStatus), -// onSuccess: (_, { projectId, reviewId }) => { -// queryClient.invalidateQueries({ queryKey: ['reviewDetail', projectId, reviewId] }); -// }, -// }); -// }; diff --git a/frontend/src/hooks/useReviewRequest.ts b/frontend/src/hooks/useReviewRequest.ts new file mode 100644 index 0000000..2075a45 --- /dev/null +++ b/frontend/src/hooks/useReviewRequest.ts @@ -0,0 +1,59 @@ +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { useNavigate, useParams } from 'react-router-dom'; +import useAuthStore from '@/stores/useAuthStore'; +import useProjectListQuery from '@/queries/projects/useProjectListQuery'; +import useCreateReviewQuery from '@/queries/reviews/useCreateReviewQuery'; +import type { ReviewRequest } from '@/types'; + +export default function useReviewRequest() { + const { workspaceId } = useParams<{ workspaceId: string }>(); + const navigate = useNavigate(); + const [selectedImages, setSelectedImages] = useState([]); + const [selectedProjectId, setSelectedProjectId] = useState(null); + + const profile = useAuthStore((state) => state.profile); + const memberId = profile?.id || 0; + + const { data: projects } = useProjectListQuery(Number(workspaceId), memberId); + const createReview = useCreateReviewQuery(); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const onSubmit = (data: ReviewRequest) => { + if (!selectedProjectId) { + return; + } + createReview.mutate( + { + projectId: Number(selectedProjectId), + memberId, + reviewData: { + ...data, + imageIds: selectedImages, + }, + }, + { + onSuccess: () => { + navigate(`/admin/${workspaceId}/reviews`); + }, + } + ); + }; + + return { + register, + handleSubmit, + errors, + projects, + onSubmit, + selectedProjectId, + setSelectedProjectId, + selectedImages, + setSelectedImages, + }; +} diff --git a/frontend/src/hooks/useTrainPolling.ts b/frontend/src/hooks/useTrainPolling.ts new file mode 100644 index 0000000..4fd4a36 --- /dev/null +++ b/frontend/src/hooks/useTrainPolling.ts @@ -0,0 +1,49 @@ +// 임시 가짜 훅 +import { useEffect, useRef, useCallback } from 'react'; +import axios from 'axios'; +import useTrainStore from '@/stores/useTrainStore'; + +export default function useTrainPolling(start: boolean, projectId?: string | null) { + const { addTrainingData, resetTrainingData } = useTrainStore((state) => ({ + addTrainingData: state.addTrainingData, + resetTrainingData: state.resetTrainingData, + })); + + const intervalIdRef = useRef(null); + // 함수 api 후 교체 예정 + const fetchTrainingData = useCallback(async () => { + if (projectId) { + try { + const response = await axios.get(`/api/바보=${projectId}`); + const data = response.data; + + addTrainingData(projectId, { + epoch: data.epoch, + total_epochs: data.total_epochs, + box_loss: data.box_loss, + cls_loss: data.cls_loss, + dfl_loss: data.dfl_loss, + fitness: data.fitness, + epoch_time: data.epoch_time, + left_second: data.left_second, + }); + } catch (error) { + console.error('Fetching error:', error); + } + } + }, [projectId, addTrainingData]); + + useEffect(() => { + if (start && projectId) { + resetTrainingData(projectId); + intervalIdRef.current = window.setInterval(fetchTrainingData, 5000); + } + + return () => { + if (intervalIdRef.current) { + clearInterval(intervalIdRef.current); + intervalIdRef.current = null; + } + }; + }, [start, projectId, fetchTrainingData, resetTrainingData]); +} diff --git a/frontend/src/hooks/useWorkspaceHooks.ts b/frontend/src/hooks/useWorkspaceHooks.ts deleted file mode 100644 index aad486d..0000000 --- a/frontend/src/hooks/useWorkspaceHooks.ts +++ /dev/null @@ -1,155 +0,0 @@ -// TODO: 훅 재설계 -// import { useQuery } from '@tanstack/react-query'; -// import { getWorkspace, getWorkspaceList } from '@/api/workspaceApi'; - -// export const useGetWorkspace = (workspaceId: number, memberId: number) => { -// return useQuery({ -// queryKey: ['workspace', workspaceId], -// queryFn: () => getWorkspace(workspaceId, memberId), -// }); -// }; - -// export const useGetWorkspaceList = (memberId: number, lastWorkspaceId?: number, limit?: number) => { -// return useQuery({ -// queryKey: ['workspaces'], -// queryFn: () => getWorkspaceList(memberId, lastWorkspaceId, limit), -// }); -// }; - -// TODO: 수정된 쿼리에 맞게 훅 수정 -// export const useUpdateWorkspace = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { workspaceId: number; memberId: number; data: { title: string; content: string } } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ workspaceId, memberId, data }) => updateWorkspace(workspaceId, memberId, data), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] }); -// }, -// }); -// }; - -// export const useDeleteWorkspace = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { workspaceId: number; memberId: number } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ workspaceId, memberId }) => deleteWorkspace(workspaceId, memberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] }); -// }, -// }); -// }; - -// export const useCreateWorkspace = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { memberId: number; data: { title: string; content: string } } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ memberId, data }) => createWorkspace(memberId, data), -// onSuccess: () => { -// queryClient.invalidateQueries({ queryKey: ['workspaces'] }); -// }, -// }); -// }; - -// export const useAddWorkspaceMember = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { workspaceId: number; memberId: number; newMemberId: number } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ workspaceId, memberId, newMemberId }) => addWorkspaceMember(workspaceId, memberId, newMemberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] }); -// }, -// }); -// }; - -// export const useRemoveWorkspaceMember = (): UseMutationResult< -// BaseResponse, -// AxiosError, -// { workspaceId: number; memberId: number; targetMemberId: number } -// > => { -// const queryClient = useQueryClient(); -// return useMutation({ -// mutationFn: ({ workspaceId, memberId, targetMemberId }) => -// removeWorkspaceMember(workspaceId, memberId, targetMemberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] }); -// }, -// }); -// }; - -// import { useMutation, useQueryClient } from '@tanstack/react-query'; -// import { -// createWorkspace, -// updateWorkspace, -// deleteWorkspace, -// addWorkspaceMember, -// removeWorkspaceMember, -// } from '@/api/workspaceApi'; -// import { WorkspaceResponse, WorkspaceRequest } from '@/types'; - -// export const useCreateWorkspace = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: ({ memberId, data }) => createWorkspace(memberId, data), -// onSuccess: () => { -// queryClient.invalidateQueries({ queryKey: ['workspaceList'] }); -// }, -// }); -// }; - -// export const useUpdateWorkspace = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: ({ workspaceId, memberId, data }) => updateWorkspace(workspaceId, memberId, data), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] }); -// }, -// }); -// }; - -// export const useDeleteWorkspace = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: ({ workspaceId, memberId }) => deleteWorkspace(workspaceId, memberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] }); -// }, -// }); -// }; - -// export const useAddWorkspaceMember = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: ({ workspaceId, memberId, newMemberId }) => addWorkspaceMember(workspaceId, memberId, newMemberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] }); -// }, -// }); -// }; - -// export const useRemoveWorkspaceMember = () => { -// const queryClient = useQueryClient(); - -// return useMutation({ -// mutationFn: ({ workspaceId, memberId, targetMemberId }) => -// removeWorkspaceMember(workspaceId, memberId, targetMemberId), -// onSuccess: (_, variables) => { -// queryClient.invalidateQueries({ queryKey: ['workspace', variables.workspaceId] }); -// }, -// }); -// }; From 8db39370eed22b87ae302f560c2b3b321f750bd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:39:25 +0900 Subject: [PATCH 17/22] =?UTF-8?q?Refactor:=20Admin=20=EC=82=AC=EC=9D=B4?= =?UTF-8?q?=EB=93=9C=EB=B0=94=20=EB=A7=81=ED=81=AC=20=EC=9D=B4=EB=8F=99?= =?UTF-8?q?=EC=8B=9C=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8id=20=EC=9C=A0?= =?UTF-8?q?=EC=A7=80=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/AdminMenuSidebar/index.tsx | 9 ++++---- .../components/AdminProjectSidebar/index.tsx | 21 +++++++------------ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/AdminMenuSidebar/index.tsx b/frontend/src/components/AdminMenuSidebar/index.tsx index 2010ac4..21b5e39 100644 --- a/frontend/src/components/AdminMenuSidebar/index.tsx +++ b/frontend/src/components/AdminMenuSidebar/index.tsx @@ -1,21 +1,22 @@ import { Link, useLocation, useParams } from 'react-router-dom'; import { cn } from '@/lib/utils'; + export default function AdminMenuSidebar() { const location = useLocation(); - const { workspaceId } = useParams<{ workspaceId: string }>(); + const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId?: string }>(); const menuItems = [ { label: '리뷰', - path: `/admin/${workspaceId}/reviews`, + path: projectId ? `/admin/${workspaceId}/reviews/${projectId}` : `/admin/${workspaceId}/reviews`, }, { label: '멤버 관리', - path: `/admin/${workspaceId}/members`, + path: projectId ? `/admin/${workspaceId}/members/${projectId}` : `/admin/${workspaceId}/members`, }, { label: '모델 관리', - path: `/admin/${workspaceId}/models`, + path: projectId ? `/admin/${workspaceId}/models/${projectId}` : `/admin/${workspaceId}/models`, }, ]; diff --git a/frontend/src/components/AdminProjectSidebar/index.tsx b/frontend/src/components/AdminProjectSidebar/index.tsx index 454df8a..d6bf1b1 100644 --- a/frontend/src/components/AdminProjectSidebar/index.tsx +++ b/frontend/src/components/AdminProjectSidebar/index.tsx @@ -1,5 +1,5 @@ import { ResizablePanel, ResizableHandle } from '../ui/resizable'; -import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; +import { Link, useLocation, useParams } from 'react-router-dom'; import { SquarePen } from 'lucide-react'; import useProjectListQuery from '@/queries/projects/useProjectListQuery'; import useCreateProjectQuery from '@/queries/projects/useCreateProjectQuery'; @@ -11,7 +11,6 @@ import { cn } from '@/lib/utils'; export default function AdminProjectSidebar(): JSX.Element { const location = useLocation(); - const navigate = useNavigate(); const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId?: string }>(); const profile = useAuthStore((state) => state.profile); const memberId = profile?.id || 0; @@ -31,13 +30,6 @@ export default function AdminProjectSidebar(): JSX.Element { }); }; - const handleHeaderClick = () => { - navigate({ - pathname: location.pathname, - search: '', - }); - }; - const getNewPath = (newProjectId: string) => { if (location.pathname.includes('reviews')) { return `/admin/${workspaceId}/reviews/${newProjectId}`; @@ -51,6 +43,9 @@ export default function AdminProjectSidebar(): JSX.Element { return location.pathname; }; + const basePath = location.pathname.split('/')[3]; + const basePathWithoutProjectId = `/admin/${workspaceId}/${basePath}`; + return ( <>
-

{workspaceTitle} -

+ From ae95045c5e48c371257613a059cdab37303f6257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:39:57 +0900 Subject: [PATCH 18/22] =?UTF-8?q?Refactor:=20=EA=B6=8C=ED=95=9C=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/MemberAddModal/MemberAddForm.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/MemberAddModal/MemberAddForm.tsx b/frontend/src/components/MemberAddModal/MemberAddForm.tsx index 2ec5763..544fa57 100644 --- a/frontend/src/components/MemberAddModal/MemberAddForm.tsx +++ b/frontend/src/components/MemberAddModal/MemberAddForm.tsx @@ -9,12 +9,10 @@ import SearchInput from '../ui/search-input'; import useSearchMembersByEmailQuery from '@/queries/members/useSearchMembersByEmailQuery'; import debounce from 'lodash/debounce'; -type PrivilegeType = 'ADMIN' | 'MANAGER' | 'EDITOR' | 'VIEWER'; - -const privilegeTypes: readonly ['ADMIN', 'MANAGER', 'EDITOR', 'VIEWER'] = ['ADMIN', 'MANAGER', 'EDITOR', 'VIEWER']; +type PrivilegeType = 'MANAGER' | 'EDITOR' | 'VIEWER'; // ADMIN을 제외 +const privilegeTypes: readonly ['MANAGER', 'EDITOR', 'VIEWER'] = ['MANAGER', 'EDITOR', 'VIEWER']; // ADMIN을 제외 const privilegeTypeToStr: { [key in PrivilegeType]: string } = { - ADMIN: '관리자', MANAGER: '매니저', EDITOR: '에디터', VIEWER: '뷰어', From a91c74b42f484ab802cc23686be72d8e31ac5aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:40:19 +0900 Subject: [PATCH 19/22] =?UTF-8?q?Refactor:=20=ED=9B=85=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/OAuthCallback/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/OAuthCallback/index.tsx b/frontend/src/components/OAuthCallback/index.tsx index a72535d..9e8ab8b 100644 --- a/frontend/src/components/OAuthCallback/index.tsx +++ b/frontend/src/components/OAuthCallback/index.tsx @@ -1,4 +1,4 @@ -import useHandleOAuthCallback from '@/hooks/useOAuthCallbackHooks'; +import useHandleOAuthCallback from '@/hooks/useOAuthCallback'; import { useNavigate } from 'react-router-dom'; import { useEffect } from 'react'; import useAuthStore from '@/stores/useAuthStore'; From f0394e0977fd0a8e9ae64a519e6fa1226360769b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Tue, 24 Sep 2024 17:40:45 +0900 Subject: [PATCH 20/22] =?UTF-8?q?Refactor:=20=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ModelManage/EvaluationTab.tsx | 55 +++++ .../components/ModelManage/SettingsForm.tsx | 189 ++++++++++++++++++ .../components/ModelManage/TrainingTab.tsx | 45 +++++ frontend/src/components/ModelManage/index.tsx | 66 ++++++ 4 files changed, 355 insertions(+) create mode 100644 frontend/src/components/ModelManage/EvaluationTab.tsx create mode 100644 frontend/src/components/ModelManage/SettingsForm.tsx create mode 100644 frontend/src/components/ModelManage/TrainingTab.tsx create mode 100644 frontend/src/components/ModelManage/index.tsx diff --git a/frontend/src/components/ModelManage/EvaluationTab.tsx b/frontend/src/components/ModelManage/EvaluationTab.tsx new file mode 100644 index 0000000..5824787 --- /dev/null +++ b/frontend/src/components/ModelManage/EvaluationTab.tsx @@ -0,0 +1,55 @@ +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import ModelBarChart from '@/components/ModelBarChart'; + +interface EvaluationTabProps { + selectedModel: string | null; + setSelectedModel: (model: string | null) => void; +} + +export default function EvaluationTab({ selectedModel, setSelectedModel }: EvaluationTabProps) { + return ( +
+
+ + +
+ + {selectedModel && ( +
+
+ +
+
+ +
+
+ )} +
+ ); +} + +function LabelingPreview() { + return ( +
+

레이블링 프리뷰

+
+ ); +} diff --git a/frontend/src/components/ModelManage/SettingsForm.tsx b/frontend/src/components/ModelManage/SettingsForm.tsx new file mode 100644 index 0000000..5fdf721 --- /dev/null +++ b/frontend/src/components/ModelManage/SettingsForm.tsx @@ -0,0 +1,189 @@ +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import useProjectModelsQuery from '@/queries/models/useProjectModelsQuery'; +import { useState } from 'react'; + +interface SettingsFormProps { + projectId: string | null; // projectId를 프랍으로 받음 + onSubmit?: (data: SettingsFormData) => void; +} + +export interface SettingsFormData { + projectId: number | null; + selectedModel: string | null; + ratio: number; + epochs: number; + batchSize: number; + optimizer: string; + lr0: number; + lrf: number; +} + +export default function SettingsForm({ projectId, onSubmit }: SettingsFormProps) { + const numericProjectId = projectId ? parseInt(projectId, 10) : null; + + const { data: models } = useProjectModelsQuery(numericProjectId ?? 0); + const [selectedModel, setSelectedModel] = useState(null); + const [ratio, setRatio] = useState(0.8); + const [epochs, setEpochs] = useState(50); + const [batchSize, setBatchSize] = useState(32); + const [optimizer, setOptimizer] = useState('SGD'); + const [lr0, setLr0] = useState(0.01); + const [lrf, setLrf] = useState(0.001); + + const handleSubmit = () => { + if (onSubmit) { + onSubmit({ + projectId: numericProjectId, + selectedModel, + ratio, + epochs, + batchSize, + optimizer, + lr0, + lrf, + }); + } + }; + + return ( +
+
+ 모델 설정 + + {/* 모델 선택 */} +
+ + +
+ + {/* 훈련/검증 비율 및 학습 파라미터 */} +
+ setRatio(parseFloat(e.target.value))} + /> + setEpochs(parseInt(e.target.value, 10))} + /> + setBatchSize(parseInt(e.target.value, 10))} + /> + + setLr0(parseFloat(e.target.value))} + /> + setLrf(parseFloat(e.target.value))} + /> +
+ + +
+
+ ); +} + +interface InputWithLabelProps { + label: string; + id: string; + placeholder: string; + value: number; + onChange: (e: React.ChangeEvent) => void; +} + +function InputWithLabel({ label, id, placeholder, value, onChange }: InputWithLabelProps) { + return ( +
+ + +
+ ); +} + +interface SelectWithLabelProps { + label: string; + id: string; + options: string[]; + placeholder: string; + value: string; + onChange: (value: string) => void; +} + +function SelectWithLabel({ label, id, options, placeholder, onChange }: SelectWithLabelProps) { + return ( +
+ + +
+ ); +} diff --git a/frontend/src/components/ModelManage/TrainingTab.tsx b/frontend/src/components/ModelManage/TrainingTab.tsx new file mode 100644 index 0000000..15866df --- /dev/null +++ b/frontend/src/components/ModelManage/TrainingTab.tsx @@ -0,0 +1,45 @@ +import { Button } from '@/components/ui/button'; +import ModelLineChart from '@/components/ModelLineChart'; +import SettingsForm from './SettingsForm'; + +interface TrainingTabProps { + training: boolean; + handleTrainingToggle: () => void; + trainingDataList: { + epoch: number; + box_loss: number; + cls_loss: number; + dfl_loss: number; + fitness: number; + }[]; + projectId: string | null; // projectId를 프랍으로 받음 +} + +export default function TrainingTab({ training, handleTrainingToggle, trainingDataList, projectId }: TrainingTabProps) { + return ( +
+
+ + +
+ +
+ ({ + epoch: data.epoch.toString(), + loss1: data.box_loss, + loss2: data.cls_loss, + loss3: data.dfl_loss, + fitness: data.fitness, + }))} + /> +
+
+ ); +} diff --git a/frontend/src/components/ModelManage/index.tsx b/frontend/src/components/ModelManage/index.tsx new file mode 100644 index 0000000..e1e5938 --- /dev/null +++ b/frontend/src/components/ModelManage/index.tsx @@ -0,0 +1,66 @@ +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { useState } from 'react'; +import { useParams } from 'react-router-dom'; + +import useTrainWebSocket from '@/hooks/useTrainPolling'; +import useTrainStore from '@/stores/useTrainStore'; +import TrainingTab from './TrainingTab'; +import EvaluationTab from './EvaluationTab'; + +export default function ModelManage() { + const { projectId } = useParams<{ projectId?: string }>(); + const [training, setTraining] = useState(false); + const [selectedModel, setSelectedModel] = useState(null); + + const numericProjectId = projectId ?? null; + + useTrainWebSocket(training, numericProjectId); + + const { trainingDataList } = useTrainStore((state) => ({ + trainingDataList: numericProjectId ? state.trainingDataByProject[numericProjectId] || [] : [], + })); + + const handleTrainingToggle = () => { + setTraining((prev) => !prev); + }; + + return ( +
+
+
+

모델 관리

+
+ +
+ + + 모델 학습 + 모델 평가 + + + {/* 학습 탭 */} + + + + + {/* 평가 탭 */} + + + + +
+
+
+ ); +} From d2bafb3d4b54465dff72b617487be4adb35b6f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=B0=BD=EA=B8=B0?= Date: Tue, 24 Sep 2024 18:42:46 +0900 Subject: [PATCH 21/22] =?UTF-8?q?Refactor:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20api=EB=A5=BC=20=EC=9D=B4=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=97=AC=20=ED=8F=B4=EB=8D=94=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=ED=98=84=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=9E=84=EC=8B=9C=EB=A1=9C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/imageApi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/api/imageApi.ts b/frontend/src/api/imageApi.ts index 5fe3572..aaa0aec 100644 --- a/frontend/src/api/imageApi.ts +++ b/frontend/src/api/imageApi.ts @@ -47,11 +47,11 @@ export async function uploadImageFile(memberId: number, projectId: number, folde export async function uploadImageFolder(memberId: number, projectId: number, files: File[]) { const formData = new FormData(); files.forEach((file) => { - formData.append('folderZip', file); + formData.append('imageList', file); }); return api - .post(`/projects/${projectId}/folders/${0}/images/zip`, formData, { + .post(`/projects/${projectId}/folders/${0}/images/file`, formData, { params: { memberId }, }) .then(({ data }) => data); From 3cd358414113deffd0ac0134e98e509395970e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=ED=99=8D=EC=B0=BD=EA=B8=B0?= Date: Tue, 24 Sep 2024 18:48:18 +0900 Subject: [PATCH 22/22] =?UTF-8?q?Design:=20=ED=8F=B4=EB=8D=94=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=EC=97=90=20=EC=9E=84?= =?UTF-8?q?=EC=8B=9C=20=EB=AC=B8=EA=B5=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/WorkspaceDropdownMenu/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/WorkspaceDropdownMenu/index.tsx b/frontend/src/components/WorkspaceDropdownMenu/index.tsx index 472a159..d4a5898 100644 --- a/frontend/src/components/WorkspaceDropdownMenu/index.tsx +++ b/frontend/src/components/WorkspaceDropdownMenu/index.tsx @@ -57,7 +57,7 @@ export default function WorkspaceDropdownMenu({ 파일 업로드 - 폴더 업로드 + 폴더 업로드 (임시) 폴더 압축파일 업로드 @@ -83,7 +83,7 @@ export default function WorkspaceDropdownMenu({ > - +