diff options
Diffstat (limited to 'components/project/ProjectSidebar.tsx')
| -rw-r--r-- | components/project/ProjectSidebar.tsx | 318 |
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> + ); +} + |
