Merge branch 'fe/refactor/improve-design' into 'fe/develop'

Refactor: 리뷰 탭 분리

See merge request s11-s-project/S11P21S002!269
This commit is contained in:
정현조 2024-10-02 20:20:06 +09:00
commit b82000562a
15 changed files with 122 additions and 71 deletions

View File

@ -6,10 +6,6 @@ export default function AdminMenuSidebar() {
const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId?: string }>(); const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId?: string }>();
const menuItems = [ const menuItems = [
{
label: '리뷰',
path: projectId ? `/admin/${workspaceId}/reviews/${projectId}` : `/admin/${workspaceId}/reviews`,
},
{ {
label: '멤버 관리', label: '멤버 관리',
path: projectId ? `/admin/${workspaceId}/members/${projectId}` : `/admin/${workspaceId}/members`, path: projectId ? `/admin/${workspaceId}/members/${projectId}` : `/admin/${workspaceId}/members`,

View File

@ -2,11 +2,8 @@ import { ResizablePanel, ResizableHandle } from '../ui/resizable';
import { Link, useLocation, useParams } from 'react-router-dom'; import { Link, useLocation, useParams } from 'react-router-dom';
import { SquarePen } from 'lucide-react'; import { SquarePen } from 'lucide-react';
import useProjectListQuery from '@/queries/projects/useProjectListQuery'; import useProjectListQuery from '@/queries/projects/useProjectListQuery';
import useCreateProjectQuery from '@/queries/projects/useCreateProjectQuery';
import useWorkspaceQuery from '@/queries/workspaces/useWorkspaceQuery'; import useWorkspaceQuery from '@/queries/workspaces/useWorkspaceQuery';
import { ProjectRequest } from '@/types';
import useAuthStore from '@/stores/useAuthStore'; import useAuthStore from '@/stores/useAuthStore';
import ProjectCreateModal from '../ProjectCreateModal';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
export default function AdminProjectSidebar(): JSX.Element { export default function AdminProjectSidebar(): JSX.Element {
@ -20,20 +17,7 @@ export default function AdminProjectSidebar(): JSX.Element {
const { data: projects } = useProjectListQuery(Number(workspaceId), memberId); 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) => { const getNewPath = (newProjectId: string) => {
if (location.pathname.includes('reviews')) {
return `/admin/${workspaceId}/reviews/${newProjectId}`;
}
if (location.pathname.includes('members')) { if (location.pathname.includes('members')) {
return `/admin/${workspaceId}/members/${newProjectId}`; return `/admin/${workspaceId}/members/${newProjectId}`;
} }
@ -64,10 +48,6 @@ export default function AdminProjectSidebar(): JSX.Element {
<button className="p-2"> <button className="p-2">
<SquarePen size={16} /> <SquarePen size={16} />
</button> </button>
<ProjectCreateModal
buttonClass="caption"
onSubmit={handleCreateProject}
/>
</header> </header>
<div className="flex flex-col gap-2 p-4"> <div className="flex flex-col gap-2 p-4">
{projects.map((project) => { {projects.map((project) => {

View File

@ -1,9 +1,15 @@
import { cn } from '@/lib/utils'; 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 useAuthStore from '@/stores/useAuthStore';
import useWorkspaceListQuery from '@/queries/workspaces/useWorkspaceListQuery'; import useWorkspaceListQuery from '@/queries/workspaces/useWorkspaceListQuery';
export default function WorkspaceNavigation() { 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 { workspaceId } = useParams<{ workspaceId: string }>();
const profile = useAuthStore((state) => state.profile); const profile = useAuthStore((state) => state.profile);
const memberId = profile?.id; const memberId = profile?.id;
@ -21,7 +27,7 @@ export default function WorkspaceNavigation() {
<nav className="hidden items-center gap-5 md:flex"> <nav className="hidden items-center gap-5 md:flex">
<Link <Link
to={activeWorkspaceId ? `/browse/${activeWorkspaceId}` : '/browse'} 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 workspace
</Link> </Link>
@ -29,13 +35,19 @@ export default function WorkspaceNavigation() {
<> <>
<Link <Link
to={`/workspace/${activeWorkspaceId}`} 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 labeling
</Link> </Link>
<Link
to={`/review/${activeWorkspaceId}`}
className={cn('', isReviewPage ? 'body-strong' : 'body')}
>
review
</Link>
<Link <Link
to={`/admin/${activeWorkspaceId}`} 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 admin
</Link> </Link>

View File

@ -2,7 +2,6 @@ import React from 'react';
import MemberAddForm, { MemberAddFormValues } from './MemberAddForm'; import MemberAddForm, { MemberAddFormValues } from './MemberAddForm';
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom'; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Plus } from 'lucide-react';
import useAddProjectMemberQuery from '@/queries/projects/useAddProjectMemberQuery'; import useAddProjectMemberQuery from '@/queries/projects/useAddProjectMemberQuery';
import { ProjectMemberRequest } from '@/types'; import { ProjectMemberRequest } from '@/types';
@ -42,12 +41,11 @@ export default function MemberAddModal({ projectId, buttonClass = '' }: MemberAd
className={`${buttonClass}`} className={`${buttonClass}`}
onClick={handleOpen} onClick={handleOpen}
> >
<Plus size={16} /> <span> </span>
<span> </span>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader title="새 멤버 초대" /> <DialogHeader title="프로젝트에 새 멤버 초대" />
<MemberAddForm onSubmit={handleMemberAdd} /> <MemberAddForm onSubmit={handleMemberAdd} />
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View 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>
</>
);
}

View File

@ -42,7 +42,7 @@ export default function ReviewItem({
return ( return (
<Link <Link
to={`/admin/${workspaceId}/reviews/${projectId}/${reviewId}`} to={`/review/${workspaceId}/${projectId}/${reviewId}`}
className="block hover:bg-gray-100" 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"> <div className="flex h-[100px] w-full items-center justify-between border-b-[0.67px] border-[#ececef] bg-[#fbfafd] p-4">

View File

@ -6,6 +6,7 @@ import WorkSpaceCreateModal from '../WorkSpaceCreateModal';
import { WorkspaceRequest, WorkspaceResponse } from '@/types'; import { WorkspaceRequest, WorkspaceResponse } from '@/types';
import useWorkspaceListQuery from '@/queries/workspaces/useWorkspaceListQuery'; import useWorkspaceListQuery from '@/queries/workspaces/useWorkspaceListQuery';
import useCreateWorkspaceQuery from '@/queries/workspaces/useCreateWorkspaceQuery'; import useCreateWorkspaceQuery from '@/queries/workspaces/useCreateWorkspaceQuery';
import { cn } from '@/lib/utils';
export default function WorkspaceBrowseLayout() { export default function WorkspaceBrowseLayout() {
const { profile } = useAuthStore(); const { profile } = useAuthStore();
@ -33,19 +34,22 @@ export default function WorkspaceBrowseLayout() {
return ( return (
<> <>
<Header className="fixed left-0 top-0 w-full" /> <Header className="fixed left-0 top-0 w-full" />
<div className="flex min-h-screen flex-col justify-between"> <div className="flex min-h-screen flex-col justify-between">
<div className="mt-16 flex flex-1"> <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 w-[280px] flex-col border-r border-gray-200 bg-gray-100 p-2">
<div className="flex items-center justify-center gap-5"> <div className="flex items-center justify-center gap-5 p-2">
<h1 className="heading mr-2.5 w-full"> </h1> <h1 className="heading mr-2.5 w-full overflow-hidden text-ellipsis whitespace-nowrap"> </h1>
<WorkSpaceCreateModal onSubmit={handleCreateWorkspace} /> <WorkSpaceCreateModal onSubmit={handleCreateWorkspace} />
</div> </div>
{workspaces.length > 0 ? ( {workspaces.length > 0 ? (
workspaces.map((workspace: WorkspaceResponse) => ( workspaces.map((workspace: WorkspaceResponse) => (
<NavLink <NavLink
to={`/browse/${workspace.id}`}
key={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} {workspace.title}
</NavLink> </NavLink>

View File

@ -2,7 +2,6 @@ import React from 'react';
import MemberAddForm, { MemberAddFormValues } from './MemberAddForm'; import MemberAddForm, { MemberAddFormValues } from './MemberAddForm';
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom'; import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '../ui/dialogCustom';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Plus } from 'lucide-react';
import useAddWorkspaceMemberQuery from '@/queries/workspaces/useAddWorkspaceMemberQuery'; import useAddWorkspaceMemberQuery from '@/queries/workspaces/useAddWorkspaceMemberQuery';
interface WorkspaceMemberAddModalProps { interface WorkspaceMemberAddModalProps {
@ -42,12 +41,11 @@ export default function WorkspaceMemberAddModal({
className={`${buttonClass}`} className={`${buttonClass}`}
onClick={handleOpen} onClick={handleOpen}
> >
<Plus size={16} /> <span> </span>
<span> </span>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader title="워크스페이스 멤버 초대" /> <DialogHeader title="워크스페이스에 새 멤버 초대" />
<MemberAddForm onSubmit={handleSubmit} /> <MemberAddForm onSubmit={handleSubmit} />
</DialogContent> </DialogContent>
</Dialog> </Dialog>

View File

@ -39,7 +39,7 @@ export default function useReviewRequest() {
}, },
{ {
onSuccess: () => { onSuccess: () => {
navigate(`/admin/${workspaceId}/reviews`); navigate(`/review/${workspaceId}`);
}, },
} }
); );

View File

@ -7,7 +7,7 @@ export default function AdminIndex() {
useEffect(() => { useEffect(() => {
if (workspaceId) { if (workspaceId) {
navigate(`/admin/${workspaceId}/reviews`); navigate(`/admin/${workspaceId}/members`);
} }
}, [navigate, workspaceId]); }, [navigate, workspaceId]);

View File

@ -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"> <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> <h1 className="text-xl font-semibold"> </h1>
<Link <Link
to={`/admin/${workspaceId}/reviews/request`} to={`/review/${workspaceId}/request`}
className="ml-auto" className="ml-auto"
> >
<Button variant="blue"> </Button> <Button variant="blue"> </Button>
</Link> </Link>
</header> </header>
<ReviewList <ReviewList
key={`${sortValue}-${activeTab}`} key={`${sortValue}-${activeTab}`}
reviews={projectReviews} reviews={projectReviews}

View File

@ -153,7 +153,7 @@ export default function ReviewDetail(): JSX.Element {
</div> </div>
<div className="mt-6 flex justify-end gap-2"> <div className="mt-6 flex justify-end gap-2">
<Link to={`/admin/${workspaceId}/reviews`}> <Link to={`/review/${workspaceId}`}>
<Button variant="black"> </Button> <Button variant="black"> </Button>
</Link> </Link>
{isAdminOrManager && reviewDetail.reviewStatus !== 'APPROVED' && reviewDetail.reviewStatus !== 'REJECTED' && ( {isAdminOrManager && reviewDetail.reviewStatus !== 'APPROVED' && reviewDetail.reviewStatus !== 'REJECTED' && (

View File

@ -28,7 +28,7 @@ export default function ReviewRequest(): JSX.Element {
reviewData, reviewData,
}, },
{ {
onSuccess: () => navigate(`/admin/${workspaceId}/reviews`), onSuccess: () => navigate(`/review/${workspaceId}`),
} }
); );
}; };

View File

@ -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"> <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> <h1 className="text-xl font-semibold"> </h1>
<Link <Link
to={`/admin/${workspaceId}/reviews/request`} to={`/review/${workspaceId}/request`}
className="ml-auto" className="ml-auto"
> >
<Button variant="blue"> </Button> <Button variant="blue"> </Button>
@ -80,9 +80,7 @@ export default function WorkspaceReviewList() {
setSortValue={setSortValue} setSortValue={setSortValue}
workspaceId={Number(workspaceId)} workspaceId={Number(workspaceId)}
/> />
{isFetchingNextPage} {isFetchingNextPage}
<div <div
ref={loadMoreRef} ref={loadMoreRef}
className="h-1" className="h-1"

View File

@ -20,11 +20,13 @@ import NotFound from '@/pages/NotFound';
import ReviewRequest from '@/pages/ReviewRequest'; import ReviewRequest from '@/pages/ReviewRequest';
import ModelIndex from '@/pages/ModelIndex'; import ModelIndex from '@/pages/ModelIndex';
import ModelDetail from '@/pages/ModelDetail'; import ModelDetail from '@/pages/ModelDetail';
import ReviewLayout from '@/components/ReviewLayout';
export const webPath = { export const webPath = {
home: () => '/', home: () => '/',
browse: () => '/browse', browse: () => '/browse',
workspace: () => '/workspace', workspace: () => '/workspace',
review: () => '/review',
admin: () => `/admin`, admin: () => `/admin`,
oauthCallback: () => '/redirect/oauth2', 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`, path: `${webPath.admin()}/:workspaceId`,
element: ( element: (
@ -92,27 +120,6 @@ const router = createBrowserRouter([
index: true, index: true,
element: <AdminIndex />, element: <AdminIndex />,
}, },
{
path: 'reviews',
children: [
{
index: true,
element: <WorkspaceReviewList />,
},
{
path: 'request',
element: <ReviewRequest />,
},
{
path: ':projectId',
element: <ProjectReviewList />,
},
{
path: ':projectId/:reviewId',
element: <ReviewDetail />,
},
],
},
{ {
path: 'members', path: 'members',
children: [ children: [