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