From ae6e09a5c11e036fcd46adebc429bb6e5c0a3d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=ED=98=84=EC=A1=B0?= Date: Fri, 20 Sep 2024 23:39:31 +0900 Subject: [PATCH] =?UTF-8?q?Refactor:=20admin=20=ED=8E=98=EC=9D=B4=EC=A7=80?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=201=EC=B0=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/AdminMemberManage/index.tsx | 65 --------------- .../src/components/AdminMenuSidebar/index.tsx | 2 +- .../components/AdminProjectSidebar/index.tsx | 21 +++-- .../ReviewList/ProjectReviewList.tsx | 74 ----------------- .../ReviewList/WorkspaceReviewList.tsx | 73 ---------------- .../components/ReviewList/index.stories.tsx | 63 -------------- frontend/src/components/ReviewList/index.tsx | 83 +++++++++++++++---- .../ProjectMemberManage.tsx} | 41 +++++---- frontend/src/pages/ProjectReviewList.tsx | 33 ++++++++ .../index.tsx => pages/ReviewDetail.tsx} | 0 .../WorkspaceMemberManage.tsx} | 19 ++++- frontend/src/pages/WorkspaceReviewList.tsx | 33 ++++++++ frontend/src/router/index.tsx | 40 ++++++--- 13 files changed, 218 insertions(+), 329 deletions(-) delete mode 100644 frontend/src/components/AdminMemberManage/index.tsx delete mode 100644 frontend/src/components/ReviewList/ProjectReviewList.tsx delete mode 100644 frontend/src/components/ReviewList/WorkspaceReviewList.tsx delete mode 100644 frontend/src/components/ReviewList/index.stories.tsx rename frontend/src/{components/AdminMemberManage/ProjectMemberManageForm.tsx => pages/ProjectMemberManage.tsx} (80%) create mode 100644 frontend/src/pages/ProjectReviewList.tsx rename frontend/src/{components/ReviewDetail/index.tsx => pages/ReviewDetail.tsx} (100%) rename frontend/src/{components/AdminMemberManage/WorkspaceMemberManageForm.tsx => pages/WorkspaceMemberManage.tsx} (55%) create mode 100644 frontend/src/pages/WorkspaceReviewList.tsx diff --git a/frontend/src/components/AdminMemberManage/index.tsx b/frontend/src/components/AdminMemberManage/index.tsx deleted file mode 100644 index fb32b7f..0000000 --- a/frontend/src/components/AdminMemberManage/index.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useState } from 'react'; -import { useParams, useLocation } from 'react-router-dom'; -import useAuthStore from '@/stores/useAuthStore'; -import useAddWorkspaceMemberQuery from '@/queries/workspaces/useAddWorkspaceMemberQuery'; -import useAddProjectMemberQuery from '@/queries/projects/useAddProjectMemberQuery'; -import MemberAddModal from '../MemberAddModal'; -import { MemberAddFormValues } from '../MemberAddModal/MemberAddForm'; -import WorkspaceMemberManageForm from './WorkspaceMemberManageForm'; -import ProjectMemberManageForm from './ProjectMemberManageForm'; -import WorkspaceMemberAddModal from '../WorkspaceMemberAddModal'; - -export default function AdminMemberManage() { - const { workspaceId } = useParams<{ workspaceId: string }>(); - const location = useLocation(); - const searchParams = new URLSearchParams(location.search); - const projectId = searchParams.get('projectId'); - - const profile = useAuthStore((state) => state.profile); - const memberId = profile?.id || 0; - - const addWorkspaceMember = useAddWorkspaceMemberQuery(); - const addProjectMember = useAddProjectMemberQuery(); - - const [, setInviteModalOpen] = useState(false); - - const handleMemberInvite = (data: MemberAddFormValues) => { - if (workspaceId && !projectId) { - addWorkspaceMember.mutate({ - workspaceId: Number(workspaceId), - memberId: memberId, - newMemberId: data.memberId, - }); - } else if (projectId && Number(projectId) > 0) { - addProjectMember.mutate({ - projectId: Number(projectId), - memberId: memberId, - newMember: { - memberId: data.memberId, - privilegeType: data.role, - }, - }); - } - setInviteModalOpen(false); - }; - - return ( -
-
-

멤버 관리

- - {projectId ? ( - - ) : ( - - )} -
- - {workspaceId && !projectId && } - {projectId && } -
- ); -} diff --git a/frontend/src/components/AdminMenuSidebar/index.tsx b/frontend/src/components/AdminMenuSidebar/index.tsx index 196bbce..0e7e3d5 100644 --- a/frontend/src/components/AdminMenuSidebar/index.tsx +++ b/frontend/src/components/AdminMenuSidebar/index.tsx @@ -28,7 +28,7 @@ export default function AdminMenuSidebar() { return ( (); + const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId?: string }>(); const profile = useAuthStore((state) => state.profile); const memberId = profile?.id || 0; @@ -32,8 +32,6 @@ export default function AdminProjectSidebar(): JSX.Element { }); }; - const selectedProjectId = new URLSearchParams(location.search).get('projectId'); - const handleHeaderClick = () => { navigate({ pathname: location.pathname, @@ -41,6 +39,16 @@ export default function AdminProjectSidebar(): JSX.Element { }); }; + const getNewPath = (newProjectId: string) => { + if (location.pathname.includes('reviews')) { + return `/admin/${workspaceId}/reviews/${newProjectId}`; + } + if (location.pathname.includes('members')) { + return `/admin/${workspaceId}/members/${newProjectId}`; + } + return location.pathname; + }; + return ( <>
{projects.map((project) => { - const isActive = String(project.id) === selectedProjectId; + const isActive = projectId === String(project.id); return ( state.profile); - const memberId = profile?.id || 0; - - const [activeTab, setActiveTab] = useState<'REQUESTED' | 'APPROVED' | 'REJECTED' | 'all'>('REQUESTED'); - const [, setSearchQuery] = useState(''); - const [sortValue, setSortValue] = useState('latest'); - - const { data: projectReviews = [] } = useReviewByStatusQuery( - projectId, - memberId, - activeTab !== 'all' ? activeTab : undefined - ); - - return ( -
-
-
- {['REQUESTED', 'APPROVED', 'REJECTED', 'all'].map((tab) => ( - - ))} -
-
- -
- -
- -
- {projectReviews.length === 0 ? ( -
프로젝트에 리뷰가 없습니다.
- ) : ( - projectReviews.map((item) => ( - - )) - )} -
-
- ); -} diff --git a/frontend/src/components/ReviewList/WorkspaceReviewList.tsx b/frontend/src/components/ReviewList/WorkspaceReviewList.tsx deleted file mode 100644 index 99e505d..0000000 --- a/frontend/src/components/ReviewList/WorkspaceReviewList.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useState } from 'react'; -import ReviewItem from './ReviewItem'; -import ReviewSearchInput from './ReviewSearchInput'; -import useWorkspaceReviewsQuery from '@/queries/workspaces/useWorkspaceReviewsQuery'; -import useAuthStore from '@/stores/useAuthStore'; - -interface WorkspaceReviewListProps { - workspaceId: number; -} - -export default function WorkspaceReviewList({ workspaceId }: WorkspaceReviewListProps): JSX.Element { - const profile = useAuthStore((state) => state.profile); - const memberId = profile?.id || 0; - - const [activeTab, setActiveTab] = useState<'REQUESTED' | 'APPROVED' | 'REJECTED' | 'all'>('REQUESTED'); - const [, setSearchQuery] = useState(''); - const [sortValue, setSortValue] = useState('latest'); - - const { data: workspaceReviews = [] } = useWorkspaceReviewsQuery( - workspaceId, - memberId, - activeTab !== 'all' ? activeTab : undefined - ); - - return ( -
-
-
- {['REQUESTED', 'APPROVED', 'REJECTED', 'all'].map((tab) => ( - - ))} -
-
- -
- -
- -
- {workspaceReviews.length === 0 ? ( -
워크스페이스에 리뷰가 없습니다.
- ) : ( - workspaceReviews.map((item) => ( - - )) - )} -
-
- ); -} diff --git a/frontend/src/components/ReviewList/index.stories.tsx b/frontend/src/components/ReviewList/index.stories.tsx deleted file mode 100644 index 2e9d32d..0000000 --- a/frontend/src/components/ReviewList/index.stories.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import '@/index.css'; -import type { Meta, StoryObj } from '@storybook/react'; -import ReviewList from '.'; - -const meta: Meta = { - title: 'Components/ReviewList', - component: ReviewList, -}; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = { - args: { - acceptedCount: 10, - rejectedCount: 5, - pendingCount: 7, - totalCount: 22, - items: [ - { - title: '갤럭시22 생산 라인 이물질 분류', - createdTime: '2 hours ago', - creatorName: 'Kim Tae Su', - project: 'Project A', - type: 'Classification', - status: 'needs_review', - }, - { - title: '갤럭시 흠집 객체 탐지', - createdTime: '3 hours ago', - creatorName: 'Kim Tae Su', - project: 'Project B', - type: 'Detection', - status: 'completed', - }, - { - title: '갤럭시 흠집 경계 폴리곤', - createdTime: '5 hours ago', - creatorName: 'Kim Tae Su', - project: 'Project C', - type: 'Polygon', - status: 'in_progress', - }, - { - title: '갤럭시 흠집 폴리라인', - createdTime: '1 day ago', - creatorName: 'Kim Tae Su', - project: 'Project D', - type: 'Polyline', - status: 'completed', - }, - { - title: '갤럭시 흠집 디텍션 허가 요청', - createdTime: '6 hours ago', - creatorName: 'Kim Tae Su', - project: 'Project E', - type: 'Detection', - status: 'pending', - }, - ], - }, -}; diff --git a/frontend/src/components/ReviewList/index.tsx b/frontend/src/components/ReviewList/index.tsx index fcf8eec..dc6e7f7 100644 --- a/frontend/src/components/ReviewList/index.tsx +++ b/frontend/src/components/ReviewList/index.tsx @@ -1,19 +1,72 @@ -import { useLocation, useParams } from 'react-router-dom'; -import ProjectReviewList from './ProjectReviewList'; -import WorkspaceReviewList from './WorkspaceReviewList'; +import ReviewItem from './ReviewItem'; +import ReviewSearchInput from './ReviewSearchInput'; +import { ReviewResponse } from '@/types'; -export default function ReviewList(): JSX.Element { - const { workspaceId } = useParams<{ workspaceId: string }>(); - const location = useLocation(); - const searchParams = new URLSearchParams(location.search); - const projectId = searchParams.get('projectId'); +interface ReviewListProps { + reviews: ReviewResponse[]; + activeTab: 'REQUESTED' | 'APPROVED' | 'REJECTED' | 'all'; + setActiveTab: React.Dispatch>; + setSearchQuery: React.Dispatch>; + sortValue: string; + setSortValue: React.Dispatch>; + workspaceId: number; +} - return projectId && Number(projectId) > 0 ? ( - - ) : ( - +export default function ReviewList({ + reviews, + activeTab, + setActiveTab, + setSearchQuery, + sortValue, + setSortValue, + workspaceId, +}: ReviewListProps) { + return ( +
+
+
+ {['REQUESTED', 'APPROVED', 'REJECTED', 'all'].map((tab) => ( + + ))} +
+
+ +
+ +
+ +
+ {reviews.length === 0 ? ( +
리뷰가 없습니다.
+ ) : ( + reviews.map((item) => ( + + )) + )} +
+
); } diff --git a/frontend/src/components/AdminMemberManage/ProjectMemberManageForm.tsx b/frontend/src/pages/ProjectMemberManage.tsx similarity index 80% rename from frontend/src/components/AdminMemberManage/ProjectMemberManageForm.tsx rename to frontend/src/pages/ProjectMemberManage.tsx index 0333dcf..7db16dc 100644 --- a/frontend/src/components/AdminMemberManage/ProjectMemberManageForm.tsx +++ b/frontend/src/pages/ProjectMemberManage.tsx @@ -1,16 +1,16 @@ -import { useLocation, useParams } from 'react-router-dom'; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'; -import useUpdateProjectMemberPrivilegeQuery from '@/queries/projects/useUpdateProjectMemberPrivilegeQuery'; -import useRemoveProjectMemberQuery from '@/queries/projects/useRemoveProjectMemberQuery'; +import { useParams, useLocation } from 'react-router-dom'; import useProjectMembersQuery from '@/queries/projects/useProjectMembersQuery'; import useWorkspaceMembersQuery from '@/queries/workspaces/useWorkspaceMembersQuery'; import useAuthStore from '@/stores/useAuthStore'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import useUpdateProjectMemberPrivilegeQuery from '@/queries/projects/useUpdateProjectMemberPrivilegeQuery'; +import useRemoveProjectMemberQuery from '@/queries/projects/useRemoveProjectMemberQuery'; import useAddProjectMemberQuery from '@/queries/projects/useAddProjectMemberQuery'; +import { useEffect } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; type Role = 'ADMIN' | 'MANAGER' | 'EDITOR' | 'VIEWER' | 'NONE'; - const roles: Role[] = ['ADMIN', 'MANAGER', 'EDITOR', 'VIEWER', 'NONE']; - const roleToStr: { [key in Role]: string } = { ADMIN: '관리자', MANAGER: '매니저', @@ -19,17 +19,22 @@ const roleToStr: { [key in Role]: string } = { NONE: '역할 없음', }; -export default function ProjectMemberManageForm() { - const location = useLocation(); - const searchParams = new URLSearchParams(location.search); - const projectId = searchParams.get('projectId'); - const { workspaceId } = useParams<{ workspaceId: string }>(); - +export default function ProjectMemberManage() { + const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>(); const profile = useAuthStore((state) => state.profile); const memberId = profile?.id || 0; - const { data: workspaceMembers = [] } = useWorkspaceMembersQuery(Number(workspaceId)); + const queryClient = useQueryClient(); + const location = useLocation(); + + useEffect(() => { + if (projectId) { + queryClient.invalidateQueries({ queryKey: ['projectMembers', projectId, memberId] }); + } + }, [location.pathname, projectId, memberId, queryClient]); + const { data: projectMembers = [] } = useProjectMembersQuery(Number(projectId), memberId); + const { data: workspaceMembers = [] } = useWorkspaceMembersQuery(Number(workspaceId)); const { mutate: updatePrivilege } = useUpdateProjectMemberPrivilegeQuery(); const { mutate: removeMember } = useRemoveProjectMemberQuery(); const { mutate: addProjectMember } = useAddProjectMemberQuery(); @@ -79,7 +84,11 @@ export default function ProjectMemberManageForm() { }; return ( -
+
+
+

프로젝트 멤버 관리

+
+ {sortedMembers.map((member) => (