Refactor: 리뷰 탭 분리
This commit is contained in:
parent
f11e8d91d6
commit
474674ba93
@ -6,10 +6,6 @@ export default function AdminMenuSidebar() {
|
||||
const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId?: string }>();
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: '리뷰',
|
||||
path: projectId ? `/admin/${workspaceId}/reviews/${projectId}` : `/admin/${workspaceId}/reviews`,
|
||||
},
|
||||
{
|
||||
label: '멤버 관리',
|
||||
path: projectId ? `/admin/${workspaceId}/members/${projectId}` : `/admin/${workspaceId}/members`,
|
||||
|
@ -2,11 +2,8 @@ import { ResizablePanel, ResizableHandle } from '../ui/resizable';
|
||||
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';
|
||||
import useWorkspaceQuery from '@/queries/workspaces/useWorkspaceQuery';
|
||||
import { ProjectRequest } from '@/types';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import ProjectCreateModal from '../ProjectCreateModal';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export default function AdminProjectSidebar(): JSX.Element {
|
||||
@ -20,20 +17,7 @@ export default function AdminProjectSidebar(): JSX.Element {
|
||||
|
||||
const { data: projects } = useProjectListQuery(Number(workspaceId), memberId);
|
||||
|
||||
const createProject = useCreateProjectQuery();
|
||||
|
||||
const handleCreateProject = (data: ProjectRequest) => {
|
||||
createProject.mutate({
|
||||
workspaceId: Number(workspaceId),
|
||||
memberId,
|
||||
data,
|
||||
});
|
||||
};
|
||||
|
||||
const getNewPath = (newProjectId: string) => {
|
||||
if (location.pathname.includes('reviews')) {
|
||||
return `/admin/${workspaceId}/reviews/${newProjectId}`;
|
||||
}
|
||||
if (location.pathname.includes('members')) {
|
||||
return `/admin/${workspaceId}/members/${newProjectId}`;
|
||||
}
|
||||
@ -64,10 +48,6 @@ export default function AdminProjectSidebar(): JSX.Element {
|
||||
<button className="p-2">
|
||||
<SquarePen size={16} />
|
||||
</button>
|
||||
<ProjectCreateModal
|
||||
buttonClass="caption"
|
||||
onSubmit={handleCreateProject}
|
||||
/>
|
||||
</header>
|
||||
<div className="flex flex-col gap-2 p-4">
|
||||
{projects.map((project) => {
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import useWorkspaceListQuery from '@/queries/workspaces/useWorkspaceListQuery';
|
||||
|
||||
export default function WorkspaceNavigation() {
|
||||
const location = useLocation();
|
||||
const isBrowsePage = location.pathname.startsWith('/browse');
|
||||
const isWorkspacePage = location.pathname.startsWith('/workspace');
|
||||
const isReviewPage = location.pathname.startsWith('/review');
|
||||
const isAdminPage = location.pathname.startsWith('/admin');
|
||||
|
||||
const { workspaceId } = useParams<{ workspaceId: string }>();
|
||||
const profile = useAuthStore((state) => state.profile);
|
||||
const memberId = profile?.id;
|
||||
@ -21,7 +27,7 @@ export default function WorkspaceNavigation() {
|
||||
<nav className="hidden items-center gap-5 md:flex">
|
||||
<Link
|
||||
to={activeWorkspaceId ? `/browse/${activeWorkspaceId}` : '/browse'}
|
||||
className={cn('text-color-text-default-default', 'font-body-strong', 'text-sm sm:text-base md:text-lg')}
|
||||
className={cn('', isBrowsePage ? 'body-strong' : 'body')}
|
||||
>
|
||||
workspace
|
||||
</Link>
|
||||
@ -29,13 +35,19 @@ export default function WorkspaceNavigation() {
|
||||
<>
|
||||
<Link
|
||||
to={`/workspace/${activeWorkspaceId}`}
|
||||
className={cn('text-color-text-default-default', 'font-body', 'text-sm sm:text-base md:text-lg')}
|
||||
className={cn('', isWorkspacePage ? 'body-strong' : 'body')}
|
||||
>
|
||||
labeling
|
||||
</Link>
|
||||
<Link
|
||||
to={`/review/${activeWorkspaceId}`}
|
||||
className={cn('', isReviewPage ? 'body-strong' : 'body')}
|
||||
>
|
||||
review
|
||||
</Link>
|
||||
<Link
|
||||
to={`/admin/${activeWorkspaceId}`}
|
||||
className={cn('text-color-text-default-default', 'font-body', 'text-sm sm:text-base md:text-lg')}
|
||||
className={cn('', isAdminPage ? 'body-strong' : 'body')}
|
||||
>
|
||||
admin
|
||||
</Link>
|
||||
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||
import MemberAddForm, { MemberAddFormValues } from './MemberAddForm';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus } from 'lucide-react';
|
||||
import useAddProjectMemberQuery from '@/queries/projects/useAddProjectMemberQuery';
|
||||
import { ProjectMemberRequest } from '@/types';
|
||||
|
||||
@ -42,12 +41,11 @@ export default function MemberAddModal({ projectId, buttonClass = '' }: MemberAd
|
||||
className={`${buttonClass}`}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<Plus size={16} />
|
||||
<span>프로젝트 멤버 초대하기</span>
|
||||
<span>프로젝트에 새 멤버 초대</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader title="새 멤버 초대" />
|
||||
<DialogHeader title="프로젝트에 새 멤버 초대" />
|
||||
<MemberAddForm onSubmit={handleMemberAdd} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
57
frontend/src/components/ReviewLayout/index.tsx
Normal file
57
frontend/src/components/ReviewLayout/index.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import { Link, Outlet, useParams } from 'react-router-dom';
|
||||
import Header from '../Header';
|
||||
import useAuthStore from '@/stores/useAuthStore';
|
||||
import useWorkspaceQuery from '@/queries/workspaces/useWorkspaceQuery';
|
||||
import useProjectListQuery from '@/queries/projects/useProjectListQuery';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ProjectResponse } from '@/types';
|
||||
|
||||
export default function ReviewLayout() {
|
||||
const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId?: string }>();
|
||||
const profile = useAuthStore((state) => state.profile);
|
||||
const memberId = profile?.id || 0;
|
||||
|
||||
const { data: workspaceData } = useWorkspaceQuery(Number(workspaceId), memberId);
|
||||
const workspaceTitle = workspaceData?.title || `Workspace-${workspaceId}`;
|
||||
|
||||
const { data: projects } = useProjectListQuery(Number(workspaceId), memberId);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header className="fixed left-0 top-0 w-full" />
|
||||
|
||||
<div className="flex min-h-screen flex-col justify-between">
|
||||
<div className="mt-16 flex flex-1">
|
||||
<div className="flex w-[280px] flex-col border-r border-gray-200 bg-gray-100 p-2">
|
||||
<div className="flex items-center justify-center gap-5 p-2">
|
||||
<Link
|
||||
to={`/review/${workspaceId}`}
|
||||
className="heading w-full overflow-hidden text-ellipsis whitespace-nowrap"
|
||||
>
|
||||
{workspaceTitle}
|
||||
</Link>
|
||||
</div>
|
||||
{projects.map((project: ProjectResponse) => (
|
||||
<Link
|
||||
key={project.id}
|
||||
to={`/review/${workspaceId}/${project.id}`}
|
||||
className={cn(
|
||||
'cursor-pointer rounded-lg p-3 hover:bg-gray-200',
|
||||
projectId === String(project.id) ? 'body-strong bg-gray-300' : 'body'
|
||||
)}
|
||||
>
|
||||
{project.title}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex w-[calc(100%-280px)] flex-col gap-24">
|
||||
<main className="h-full grow overflow-y-auto">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -42,7 +42,7 @@ export default function ReviewItem({
|
||||
|
||||
return (
|
||||
<Link
|
||||
to={`/admin/${workspaceId}/reviews/${projectId}/${reviewId}`}
|
||||
to={`/review/${workspaceId}/${projectId}/${reviewId}`}
|
||||
className="block hover:bg-gray-100"
|
||||
>
|
||||
<div className="flex h-[100px] w-full items-center justify-between border-b-[0.67px] border-[#ececef] bg-[#fbfafd] p-4">
|
||||
|
@ -6,6 +6,7 @@ import WorkSpaceCreateModal from '../WorkSpaceCreateModal';
|
||||
import { WorkspaceRequest, WorkspaceResponse } from '@/types';
|
||||
import useWorkspaceListQuery from '@/queries/workspaces/useWorkspaceListQuery';
|
||||
import useCreateWorkspaceQuery from '@/queries/workspaces/useCreateWorkspaceQuery';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
export default function WorkspaceBrowseLayout() {
|
||||
const { profile } = useAuthStore();
|
||||
@ -33,19 +34,22 @@ export default function WorkspaceBrowseLayout() {
|
||||
return (
|
||||
<>
|
||||
<Header className="fixed left-0 top-0 w-full" />
|
||||
|
||||
<div className="flex min-h-screen flex-col justify-between">
|
||||
<div className="mt-16 flex flex-1">
|
||||
<div className="flex w-[280px] flex-col gap-4 border-r border-gray-200 bg-gray-100 px-6 py-4">
|
||||
<div className="flex items-center justify-center gap-5">
|
||||
<h1 className="heading mr-2.5 w-full">내 워크스페이스</h1>
|
||||
<div className="flex w-[280px] flex-col border-r border-gray-200 bg-gray-100 p-2">
|
||||
<div className="flex items-center justify-center gap-5 p-2">
|
||||
<h1 className="heading mr-2.5 w-full overflow-hidden text-ellipsis whitespace-nowrap">내 워크스페이스</h1>
|
||||
<WorkSpaceCreateModal onSubmit={handleCreateWorkspace} />
|
||||
</div>
|
||||
{workspaces.length > 0 ? (
|
||||
workspaces.map((workspace: WorkspaceResponse) => (
|
||||
<NavLink
|
||||
to={`/browse/${workspace.id}`}
|
||||
key={workspace.id}
|
||||
className={({ isActive }) => (isActive ? 'body-strong' : 'body') + ' cursor-pointer'}
|
||||
to={`/browse/${workspace.id}`}
|
||||
className={({ isActive }) =>
|
||||
cn('cursor-pointer rounded-lg p-3 hover:bg-gray-200', isActive ? 'body-strong bg-gray-300' : 'body')
|
||||
}
|
||||
>
|
||||
{workspace.title}
|
||||
</NavLink>
|
||||
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||
import MemberAddForm, { MemberAddFormValues } from './MemberAddForm';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Plus } from 'lucide-react';
|
||||
import useAddWorkspaceMemberQuery from '@/queries/workspaces/useAddWorkspaceMemberQuery';
|
||||
|
||||
interface WorkspaceMemberAddModalProps {
|
||||
@ -42,12 +41,11 @@ export default function WorkspaceMemberAddModal({
|
||||
className={`${buttonClass}`}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<Plus size={16} />
|
||||
<span>워크스페이스 멤버 초대하기</span>
|
||||
<span>워크스페이스에 새 멤버 초대</span>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader title="새 워크스페이스 멤버 초대" />
|
||||
<DialogHeader title="워크스페이스에 새 멤버 초대" />
|
||||
<MemberAddForm onSubmit={handleSubmit} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
@ -39,7 +39,7 @@ export default function useReviewRequest() {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
navigate(`/admin/${workspaceId}/reviews`);
|
||||
navigate(`/review/${workspaceId}`);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ export default function AdminIndex() {
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceId) {
|
||||
navigate(`/admin/${workspaceId}/reviews`);
|
||||
navigate(`/admin/${workspaceId}/members`);
|
||||
}
|
||||
}, [navigate, workspaceId]);
|
||||
|
||||
|
@ -63,12 +63,13 @@ export default function ProjectReviewList() {
|
||||
<header className="sticky top-0 z-10 flex h-[57px] items-center gap-1 border-b bg-white px-4">
|
||||
<h1 className="text-xl font-semibold">프로젝트 리뷰</h1>
|
||||
<Link
|
||||
to={`/admin/${workspaceId}/reviews/request`}
|
||||
to={`/review/${workspaceId}/request`}
|
||||
className="ml-auto"
|
||||
>
|
||||
<Button variant="blue">리뷰 요청</Button>
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<ReviewList
|
||||
key={`${sortValue}-${activeTab}`}
|
||||
reviews={projectReviews}
|
||||
|
@ -153,7 +153,7 @@ export default function ReviewDetail(): JSX.Element {
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<Link to={`/admin/${workspaceId}/reviews`}>
|
||||
<Link to={`/review/${workspaceId}`}>
|
||||
<Button variant="black">목록으로 돌아가기</Button>
|
||||
</Link>
|
||||
{isAdminOrManager && reviewDetail.reviewStatus !== 'APPROVED' && reviewDetail.reviewStatus !== 'REJECTED' && (
|
||||
|
@ -28,7 +28,7 @@ export default function ReviewRequest(): JSX.Element {
|
||||
reviewData,
|
||||
},
|
||||
{
|
||||
onSuccess: () => navigate(`/admin/${workspaceId}/reviews`),
|
||||
onSuccess: () => navigate(`/review/${workspaceId}`),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -63,7 +63,7 @@ export default function WorkspaceReviewList() {
|
||||
<header className="sticky top-0 z-10 flex h-[57px] items-center gap-1 border-b bg-white px-4">
|
||||
<h1 className="text-xl font-semibold">워크스페이스 리뷰</h1>
|
||||
<Link
|
||||
to={`/admin/${workspaceId}/reviews/request`}
|
||||
to={`/review/${workspaceId}/request`}
|
||||
className="ml-auto"
|
||||
>
|
||||
<Button variant="blue">리뷰 요청</Button>
|
||||
@ -80,9 +80,7 @@ export default function WorkspaceReviewList() {
|
||||
setSortValue={setSortValue}
|
||||
workspaceId={Number(workspaceId)}
|
||||
/>
|
||||
|
||||
{isFetchingNextPage}
|
||||
|
||||
<div
|
||||
ref={loadMoreRef}
|
||||
className="h-1"
|
||||
|
@ -20,11 +20,13 @@ import NotFound from '@/pages/NotFound';
|
||||
import ReviewRequest from '@/pages/ReviewRequest';
|
||||
import ModelIndex from '@/pages/ModelIndex';
|
||||
import ModelDetail from '@/pages/ModelDetail';
|
||||
import ReviewLayout from '@/components/ReviewLayout';
|
||||
|
||||
export const webPath = {
|
||||
home: () => '/',
|
||||
browse: () => '/browse',
|
||||
workspace: () => '/workspace',
|
||||
review: () => '/review',
|
||||
admin: () => `/admin`,
|
||||
oauthCallback: () => '/redirect/oauth2',
|
||||
};
|
||||
@ -80,6 +82,32 @@ const router = createBrowserRouter([
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: `${webPath.review()}/:workspaceId`,
|
||||
element: (
|
||||
<Suspense fallback={<div></div>}>
|
||||
<ReviewLayout />
|
||||
</Suspense>
|
||||
),
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <WorkspaceReviewList />,
|
||||
},
|
||||
{
|
||||
path: 'request',
|
||||
element: <ReviewRequest />,
|
||||
},
|
||||
{
|
||||
path: ':projectId',
|
||||
element: <ProjectReviewList />,
|
||||
},
|
||||
{
|
||||
path: ':projectId/:reviewId',
|
||||
element: <ReviewDetail />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: `${webPath.admin()}/:workspaceId`,
|
||||
element: (
|
||||
@ -92,27 +120,6 @@ const router = createBrowserRouter([
|
||||
index: true,
|
||||
element: <AdminIndex />,
|
||||
},
|
||||
{
|
||||
path: 'reviews',
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <WorkspaceReviewList />,
|
||||
},
|
||||
{
|
||||
path: 'request',
|
||||
element: <ReviewRequest />,
|
||||
},
|
||||
{
|
||||
path: ':projectId',
|
||||
element: <ProjectReviewList />,
|
||||
},
|
||||
{
|
||||
path: ':projectId/:reviewId',
|
||||
element: <ReviewDetail />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'members',
|
||||
children: [
|
||||
|
Loading…
Reference in New Issue
Block a user