summaryrefslogtreecommitdiff
path: root/components/project/ProjectSidebar.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/project/ProjectSidebar.tsx')
-rw-r--r--components/project/ProjectSidebar.tsx318
1 files changed, 318 insertions, 0 deletions
diff --git a/components/project/ProjectSidebar.tsx b/components/project/ProjectSidebar.tsx
new file mode 100644
index 00000000..ce2007b1
--- /dev/null
+++ b/components/project/ProjectSidebar.tsx
@@ -0,0 +1,318 @@
+// components/project/ProjectSidebar.tsx
+'use client';
+
+import { useState, useEffect } from 'react';
+import { useRouter, usePathname } from 'next/navigation';
+import {
+ Home,
+ FolderOpen,
+ Users,
+ Settings,
+ Plus,
+ ChevronLeft,
+ ChevronRight,
+ Search,
+ Crown,
+ Shield,
+ Eye,
+ Clock,
+ Star,
+ LogOut
+} from 'lucide-react';
+import { Button } from '@/components/ui/button';
+import { ScrollArea } from '@/components/ui/scroll-area';
+import { Input } from '@/components/ui/input';
+import { Separator } from '@/components/ui/separator';
+import { Badge } from '@/components/ui/badge';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+import { cn } from '@/lib/utils';
+import { useSession, signOut } from 'next-auth/react';
+
+interface RecentProject {
+ id: string;
+ name: string;
+ role: string;
+ lastAccessed: string;
+}
+
+export function ProjectSidebar() {
+ const [collapsed, setCollapsed] = useState(false);
+ const [recentProjects, setRecentProjects] = useState<RecentProject[]>([]);
+ const [favoriteProjects, setFavoriteProjects] = useState<string[]>([]);
+
+ const router = useRouter();
+ const pathname = usePathname();
+ const { data: session } = useSession();
+
+ const isInternalUser = session?.user?.domain !== 'partners';
+
+ useEffect(() => {
+ // 최근 프로젝트 로드
+ const stored = localStorage.getItem('recentProjects');
+ if (stored) {
+ setRecentProjects(JSON.parse(stored));
+ }
+
+ // 즐겨찾기 프로젝트 로드
+ const favorites = localStorage.getItem('favoriteProjects');
+ if (favorites) {
+ setFavoriteProjects(JSON.parse(favorites));
+ }
+ }, [pathname]);
+
+ const menuItems = [
+ {
+ label: '홈',
+ icon: Home,
+ href: '/projects',
+ active: pathname === '/projects',
+ },
+ {
+ label: '모든 프로젝트',
+ icon: FolderOpen,
+ href: '/projects',
+ active: pathname === '/projects',
+ },
+ ...(isInternalUser ? [{
+ label: '팀 관리',
+ icon: Users,
+ href: '/projects/team',
+ active: pathname === '/projects/team',
+ }] : []),
+ {
+ label: '설정',
+ icon: Settings,
+ href: '/projects/settings',
+ active: pathname === '/projects/settings',
+ },
+ ];
+
+ const roleIcons = {
+ owner: { icon: Crown, color: 'text-yellow-500' },
+ admin: { icon: Shield, color: 'text-blue-500' },
+ viewer: { icon: Eye, color: 'text-gray-500' },
+ };
+
+ return (
+ <TooltipProvider>
+ <div className={cn(
+ "flex flex-col bg-white border-r transition-all duration-300",
+ collapsed ? "w-16" : "w-64"
+ )}>
+ {/* 헤더 */}
+ <div className="flex items-center justify-between p-4 border-b">
+ {!collapsed && (
+ <div>
+ <h2 className="text-lg font-semibold">파일 매니저</h2>
+ <p className="text-xs text-muted-foreground">
+ {session?.user?.name}
+ </p>
+ </div>
+ )}
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={() => setCollapsed(!collapsed)}
+ className={cn(collapsed && "mx-auto")}
+ >
+ {collapsed ? <ChevronRight className="h-4 w-4" /> : <ChevronLeft className="h-4 w-4" />}
+ </Button>
+ </div>
+
+ {/* 검색 */}
+ {!collapsed && (
+ <div className="p-3 border-b">
+ <div className="relative">
+ <Search className="absolute left-2 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
+ <Input
+ placeholder="프로젝트 검색..."
+ className="pl-8 h-8"
+ />
+ </div>
+ </div>
+ )}
+
+ {/* 메인 메뉴 */}
+ <ScrollArea className="flex-1">
+ <div className="p-2">
+ <div className={cn(!collapsed && "mb-3")}>
+ {!collapsed && (
+ <p className="text-xs text-muted-foreground px-2 mb-2">메뉴</p>
+ )}
+ {menuItems.map((item) => (
+ <Tooltip key={item.label} delayDuration={0}>
+ <TooltipTrigger asChild>
+ <Button
+ variant={item.active ? "secondary" : "ghost"}
+ className={cn(
+ "w-full justify-start mb-1",
+ collapsed && "justify-center"
+ )}
+ onClick={() => router.push(item.href)}
+ >
+ <item.icon className={cn("h-4 w-4", !collapsed && "mr-2")} />
+ {!collapsed && item.label}
+ </Button>
+ </TooltipTrigger>
+ {collapsed && (
+ <TooltipContent side="right">
+ {item.label}
+ </TooltipContent>
+ )}
+ </Tooltip>
+ ))}
+ </div>
+
+ <Separator className="my-3" />
+
+ {/* 빠른 액세스 */}
+ {!collapsed && (
+ <div className="mb-3">
+ <div className="flex items-center justify-between px-2 mb-2">
+ <p className="text-xs text-muted-foreground">빠른 액세스</p>
+ <Button
+ variant="ghost"
+ size="sm"
+ className="h-6 w-6 p-0"
+ onClick={() => router.push('/projects/new')}
+ >
+ <Plus className="h-3 w-3" />
+ </Button>
+ </div>
+
+ {/* 즐겨찾기 프로젝트 */}
+ {favoriteProjects.length > 0 && (
+ <div className="space-y-1 mb-3">
+ {favoriteProjects.slice(0, 3).map((projectId) => (
+ <Button
+ key={projectId}
+ variant="ghost"
+ className="w-full justify-start h-8 px-2"
+ onClick={() => router.push(`/projects/${projectId}/files`)}
+ >
+ <Star className="h-3 w-3 mr-2 text-yellow-500" />
+ <span className="text-sm truncate">프로젝트 이름</span>
+ </Button>
+ ))}
+ </div>
+ )}
+
+ {/* 최근 프로젝트 */}
+ <div className="space-y-1">
+ <p className="text-xs text-muted-foreground px-2 mb-1">최근 프로젝트</p>
+ {recentProjects.slice(0, 5).map((project) => {
+ const RoleIcon = roleIcons[project.role as keyof typeof roleIcons];
+ return (
+ <Button
+ key={project.id}
+ variant="ghost"
+ className="w-full justify-start h-8 px-2 group"
+ onClick={() => router.push(`/projects/${project.id}/files`)}
+ >
+ {RoleIcon && (
+ <RoleIcon.icon className={cn("h-3 w-3 mr-2", RoleIcon.color)} />
+ )}
+ <span className="text-sm truncate flex-1 text-left">
+ {project.name}
+ </span>
+ <Clock className="h-3 w-3 text-muted-foreground opacity-0 group-hover:opacity-100" />
+ </Button>
+ );
+ })}
+ </div>
+ </div>
+ )}
+
+ {collapsed && (
+ <div className="space-y-1">
+ <Tooltip delayDuration={0}>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ className="w-full justify-center"
+ onClick={() => router.push('/projects/new')}
+ >
+ <Plus className="h-4 w-4" />
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent side="right">
+ 새 프로젝트
+ </TooltipContent>
+ </Tooltip>
+
+ {recentProjects.slice(0, 3).map((project) => {
+ const RoleIcon = roleIcons[project.role as keyof typeof roleIcons];
+ return (
+ <Tooltip key={project.id} delayDuration={0}>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ className="w-full justify-center"
+ onClick={() => router.push(`/projects/${project.id}/files`)}
+ >
+ {RoleIcon && (
+ <RoleIcon.icon className={cn("h-4 w-4", RoleIcon.color)} />
+ )}
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent side="right">
+ {project.name}
+ </TooltipContent>
+ </Tooltip>
+ );
+ })}
+ </div>
+ )}
+ </div>
+ </ScrollArea>
+
+ {/* 하단 사용자 정보 */}
+ <div className="border-t p-3">
+ {!collapsed ? (
+ <div className="flex items-center gap-2">
+ <div className="h-8 w-8 bg-gray-200 rounded-full flex items-center justify-center">
+ <span className="text-xs font-medium">
+ {session?.user?.name?.charAt(0).toUpperCase()}
+ </span>
+ </div>
+ <div className="flex-1">
+ <p className="text-sm font-medium truncate">{session?.user?.name}</p>
+ <Badge variant="outline" className="text-xs">
+ {isInternalUser ? '내부' : '외부'}
+ </Badge>
+ </div>
+ <Button
+ variant="ghost"
+ size="sm"
+ onClick={() => signOut()}
+ >
+ <LogOut className="h-4 w-4" />
+ </Button>
+ </div>
+ ) : (
+ <Tooltip delayDuration={0}>
+ <TooltipTrigger asChild>
+ <Button
+ variant="ghost"
+ className="w-full justify-center"
+ onClick={() => signOut()}
+ >
+ <LogOut className="h-4 w-4" />
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent side="right">
+ 로그아웃
+ </TooltipContent>
+ </Tooltip>
+ )}
+ </div>
+ </div>
+ </TooltipProvider>
+ );
+}
+