()
| 4 | import { useSelector } from 'react-redux'; |
| 5 | |
| 6 | const ProjectSidebar = () => { |
| 7 | |
| 8 | const location = useLocation(); |
| 9 | |
| 10 | const [expandedProjects, setExpandedProjects] = useState(new Set()); |
| 11 | const [searchParams] = useSearchParams(); |
| 12 | |
| 13 | const projects = useSelector( |
| 14 | (state) => state?.workspace?.currentWorkspace?.projects || [] |
| 15 | ); |
| 16 | |
| 17 | const getProjectSubItems = (projectId) => [ |
| 18 | { title: 'Tasks', icon: KanbanIcon, url: `/projectsDetail?id=${projectId}&tab=tasks` }, |
| 19 | { title: 'Analytics', icon: ChartColumnIcon, url: `/projectsDetail?id=${projectId}&tab=analytics` }, |
| 20 | { title: 'Calendar', icon: CalendarIcon, url: `/projectsDetail?id=${projectId}&tab=calendar` }, |
| 21 | { title: 'Settings', icon: SettingsIcon, url: `/projectsDetail?id=${projectId}&tab=settings` } |
| 22 | ]; |
| 23 | |
| 24 | const toggleProject = (id) => { |
| 25 | const newSet = new Set(expandedProjects); |
| 26 | newSet.has(id) ? newSet.delete(id) : newSet.add(id); |
| 27 | setExpandedProjects(newSet); |
| 28 | }; |
| 29 | |
| 30 | return ( |
| 31 | <div className="mt-6 px-3"> |
| 32 | <div className="flex items-center justify-between px-3 py-2"> |
| 33 | <h3 className="text-xs font-medium text-gray-500 dark:text-zinc-400 uppercase tracking-wider"> |
| 34 | Projects |
| 35 | </h3> |
| 36 | <Link to="/projects"> |
| 37 | <button className="size-5 text-gray-500 dark:text-zinc-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-zinc-800 rounded flex items-center justify-center transition-colors duration-200"> |
| 38 | <ArrowRightIcon className="size-3" /> |
| 39 | </button> |
| 40 | </Link> |
| 41 | </div> |
| 42 | |
| 43 | <div className="space-y-1 px-3"> |
| 44 | {projects.map((project) => ( |
| 45 | <div key={project.id}> |
| 46 | <button onClick={() => toggleProject(project.id)} className="w-full flex items-center gap-2 px-3 py-2 rounded-lg transition-colors duration-200 text-gray-700 dark:text-zinc-300 hover:bg-gray-100 dark:hover:bg-zinc-800 hover:text-gray-900 dark:hover:text-white" > |
| 47 | <ChevronRightIcon className={`size-3 text-gray-500 dark:text-zinc-400 transition-transform duration-200 ${expandedProjects.has(project.id) && 'rotate-90'}`} /> |
| 48 | <div className="size-2 rounded-full bg-blue-500" /> |
| 49 | <span className="truncate max-w-40 text-sm">{project.name}</span> |
| 50 | </button> |
| 51 | |
| 52 | {expandedProjects.has(project.id) && ( |
| 53 | <div className="ml-5 mt-1 space-y-1"> |
| 54 | {getProjectSubItems(project.id).map((subItem) => { |
| 55 | // checking if the current path matches the sub-item's URL |
| 56 | const isActive = |
| 57 | location.pathname === `/projectsDetail` && |
| 58 | searchParams.get('id') === project.id && |
| 59 | searchParams.get('tab') === subItem.title.toLowerCase(); |
| 60 | |
| 61 | return ( |
| 62 | <Link key={subItem.title} to={subItem.url} className={`flex items-center gap-3 px-3 py-1.5 rounded-lg transition-colors duration-200 text-xs ${isActive ? 'bg-blue-100 text-blue-600 hover:bg-blue-200 dark:bg-blue-500/10 dark:text-blue-400 dark:hover:bg-blue-500/20' : 'text-gray-600 dark:text-zinc-400 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100 dark:hover:bg-zinc-800'}`} > |
| 63 | <subItem.icon className="size-3" /> |
nothing calls this directly
no test coverage detected