Feat: 워크스페이스 레이아웃 기반으로 Admin 사이트 레이아웃 구현
This commit is contained in:
parent
a0dc26719b
commit
250cd864c5
44
frontend/src/components/AdminLayout/index.stories.tsx
Normal file
44
frontend/src/components/AdminLayout/index.stories.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import '@/index.css';
|
||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import AdminLayout from './index';
|
||||||
|
import { BrowserRouter as Router } from 'react-router-dom';
|
||||||
|
import { Workspace } from '@/types';
|
||||||
|
|
||||||
|
const meta: Meta<typeof AdminLayout> = {
|
||||||
|
title: 'Layout/AdminLayout',
|
||||||
|
component: AdminLayout,
|
||||||
|
parameters: {
|
||||||
|
layout: 'fullscreen',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof AdminLayout>;
|
||||||
|
|
||||||
|
const workspace: Workspace = {
|
||||||
|
id: 1,
|
||||||
|
name: 'Workspace Alpha',
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Project Alpha',
|
||||||
|
type: 'Segmentation',
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Project Beta',
|
||||||
|
type: 'Classification',
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Router>
|
||||||
|
<AdminLayout workspace={workspace} />
|
||||||
|
</Router>
|
||||||
|
),
|
||||||
|
};
|
32
frontend/src/components/AdminLayout/index.tsx
Normal file
32
frontend/src/components/AdminLayout/index.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Outlet } from 'react-router-dom';
|
||||||
|
import Header from '../Header';
|
||||||
|
import { ResizablePanelGroup, ResizablePanel } from '../ui/resizable';
|
||||||
|
import AdminProjectSidebar from '../AdminProjectSidebar';
|
||||||
|
import AdminMenuSidebar from '../AdminMenuSidebar';
|
||||||
|
import { Workspace } from '@/types';
|
||||||
|
|
||||||
|
interface AdminLayoutProps {
|
||||||
|
workspace: Workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AdminLayout({ workspace }: AdminLayoutProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header className="fixed left-0 top-0" />
|
||||||
|
<div className="mt-16 h-[calc(100vh-64px)] w-screen">
|
||||||
|
<ResizablePanelGroup direction="horizontal">
|
||||||
|
<AdminProjectSidebar
|
||||||
|
workspaceName={workspace.name}
|
||||||
|
projects={workspace.projects}
|
||||||
|
/>
|
||||||
|
<ResizablePanel className="flex w-full items-center">
|
||||||
|
<main className="h-full grow">
|
||||||
|
<Outlet />
|
||||||
|
</main>
|
||||||
|
</ResizablePanel>
|
||||||
|
<AdminMenuSidebar />
|
||||||
|
</ResizablePanelGroup>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
32
frontend/src/components/AdminMenuSidebar/index.tsx
Normal file
32
frontend/src/components/AdminMenuSidebar/index.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
export default function AdminMenuSidebar() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{ label: '작업', path: '/admin/tasks' },
|
||||||
|
{ label: '리뷰', path: '/admin/reviews' },
|
||||||
|
{ label: '멤버 관리', path: '/admin/members' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-[280px] flex-col border-l border-gray-300 bg-gray-100 p-4">
|
||||||
|
<h2 className="mb-4 text-lg font-semibold text-gray-800">메뉴</h2>
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
{menuItems.map((item) => (
|
||||||
|
<button
|
||||||
|
key={item.label}
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer rounded-md px-3 py-2 text-left text-gray-700 hover:bg-gray-200',
|
||||||
|
'transition-colors focus:bg-gray-300 focus:outline-none'
|
||||||
|
)}
|
||||||
|
onClick={() => navigate(item.path)}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
54
frontend/src/components/AdminProjectSidebar/index.tsx
Normal file
54
frontend/src/components/AdminProjectSidebar/index.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { ResizablePanel, ResizableHandle } from '../ui/resizable';
|
||||||
|
import { Project } from '@/types';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { SquarePen } from 'lucide-react';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
|
||||||
|
interface AdminProjectSidebarProps {
|
||||||
|
workspaceName: string;
|
||||||
|
projects: Project[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AdminProjectSidebar({ workspaceName, projects }: AdminProjectSidebarProps): JSX.Element {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ResizablePanel
|
||||||
|
minSize={15}
|
||||||
|
maxSize={35}
|
||||||
|
defaultSize={20}
|
||||||
|
className="flex h-full flex-col bg-gray-100"
|
||||||
|
>
|
||||||
|
<header className="body flex w-full items-center gap-2 p-2">
|
||||||
|
<h1 className="w-full overflow-hidden text-ellipsis whitespace-nowrap text-xl font-bold text-gray-900">
|
||||||
|
{workspaceName}
|
||||||
|
</h1>
|
||||||
|
<button>
|
||||||
|
<SquarePen size={16} />
|
||||||
|
</button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="xs"
|
||||||
|
className="caption border-gray-800 bg-gray-100"
|
||||||
|
onClick={() => console.log('New project')}
|
||||||
|
>
|
||||||
|
새 프로젝트
|
||||||
|
</Button>
|
||||||
|
</header>
|
||||||
|
<div className="flex flex-col gap-2 p-4">
|
||||||
|
{projects.map((project) => (
|
||||||
|
<button
|
||||||
|
key={project.id}
|
||||||
|
className="rounded-md px-3 py-2 text-left hover:bg-gray-200"
|
||||||
|
onClick={() => navigate(`/project/${project.id}`)}
|
||||||
|
>
|
||||||
|
{project.name}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ResizablePanel>
|
||||||
|
<ResizableHandle className="bg-gray-300" />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user