diff --git a/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/ProjectSetSelect.tsx b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/ProjectSetSelect.tsx
new file mode 100644
index 0000000..dc243a2
--- /dev/null
+++ b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/ProjectSetSelect.tsx
@@ -0,0 +1,36 @@
+'use client'
+
+import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "@/components/ui/select"
+import * as React from "react"
+import {useParams, useRouter} from "next/navigation"
+import {type ApiTeamSetTemplate} from "@/_temp_types/api/teams"
+
+export type ProjectSetSelectProps = {
+ allProjectSets: ApiTeamSetTemplate[]
+}
+
+export function ProjectSetSelect({allProjectSets}: ProjectSetSelectProps) {
+ const {courseId, projectSetId} = useParams<{ courseId: string, projectSetId: string }>()
+ const router = useRouter()
+ const handleProjectSetChanged = (newProjectSetId: string) => {
+ router.push(`/course/${courseId}/project-sets/${newProjectSetId}`)
+ }
+
+ return (
+
+ )
+}
diff --git a/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/ProjectSetSidebar.tsx b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/ProjectSetSidebar.tsx
new file mode 100644
index 0000000..0950c6c
--- /dev/null
+++ b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/ProjectSetSidebar.tsx
@@ -0,0 +1,27 @@
+import {
+ ProjectSetSelect,
+ type ProjectSetSelectProps, SidebarProjectList,
+} from "."
+import {Text} from "@/components/ui/text"
+
+type ProjectSetSidebarProps = ProjectSetSelectProps
+
+export const ProjectSetSidebar = ({allProjectSets}: ProjectSetSidebarProps) => {
+ return (
+
)
+}
diff --git a/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/SidebarProjectList.tsx b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/SidebarProjectList.tsx
new file mode 100644
index 0000000..b505bc3
--- /dev/null
+++ b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/SidebarProjectList.tsx
@@ -0,0 +1,36 @@
+'use client'
+
+import {
+ useProjectsContext,
+ useProjectSearchContext,
+} from "../(hooks)"
+import {SearchBar} from "@/components/SearchBar"
+import {Button} from "@/components/ui/button"
+
+export const SidebarProjectList = () => {
+ const {displayProjects, currentProject, setCurrentProject} = useProjectsContext()
+ const {searchText, setSearchText} = useProjectSearchContext()
+
+ return (
+ <>
+ setSearchText(e.target.value)}
+ />
+
+ {currentProject && displayProjects.map((project) => (
+
+ ))}
+
+ >
+ )
+}
diff --git a/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/index.tsx b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/index.tsx
new file mode 100644
index 0000000..876473e
--- /dev/null
+++ b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(components)/index.tsx
@@ -0,0 +1,3 @@
+export {ProjectSetSelect, type ProjectSetSelectProps} from './ProjectSetSelect'
+export {SidebarProjectList} from './SidebarProjectList'
+export {ProjectSetSidebar} from './ProjectSetSidebar'
diff --git a/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/index.tsx b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/index.tsx
new file mode 100644
index 0000000..5d5a665
--- /dev/null
+++ b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/index.tsx
@@ -0,0 +1,2 @@
+export {useProjectSearchContext, ProjectSearchProvider} from './useProjectSearch'
+export {useProjectsContext, ProjectsProvider} from './useProjects'
diff --git a/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/useProjectSearch.tsx b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/useProjectSearch.tsx
new file mode 100644
index 0000000..b6eb44a
--- /dev/null
+++ b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/useProjectSearch.tsx
@@ -0,0 +1,48 @@
+'use client'
+
+import {createContext, useContext, useEffect, useState, type PropsWithChildren, type FC} from "react"
+import {useProjectsContext} from "@/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/useProjects"
+
+type ProjectSearchContextType = {
+ searchText: string
+ setSearchText: (searchText: string) => void
+}
+
+const ProjectSearchContext = createContext({
+ searchText: '',
+ setSearchText: () => {},
+})
+
+const useProjectSearch = () => {
+ const {projects: allProjects, setDisplayProjects, currentProject, setCurrentProject} = useProjectsContext()
+ const [searchText, setSearchText] = useState('')
+
+ useEffect(() => {
+ const filteredProjects = allProjects.filter(project => project.name.toLowerCase().includes(searchText.toLowerCase()))
+ setDisplayProjects(filteredProjects)
+ setSearchText(searchText)
+
+ // If the current project is not in the filtered projects, set the current project to the first filtered project
+ const isCurrentProjectInFilteredProjects = !!currentProject && filteredProjects.some(project => project.id === currentProject.id)
+ if (!isCurrentProjectInFilteredProjects && filteredProjects.length > 0) {
+ setCurrentProject(filteredProjects[0])
+ }
+ }, [allProjects, currentProject, searchText, setCurrentProject, setDisplayProjects])
+
+ return {
+ searchText,
+ setSearchText,
+ }
+}
+
+export const ProjectSearchProvider: FC = ({children}) => {
+ const projectSearch = useProjectSearch()
+
+ return (
+
+ {children}
+
+ )
+}
+
+export const useProjectSearchContext = () => useContext(ProjectSearchContext)
diff --git a/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/useProjects.tsx b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/useProjects.tsx
new file mode 100644
index 0000000..dd3a02a
--- /dev/null
+++ b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/(hooks)/useProjects.tsx
@@ -0,0 +1,68 @@
+'use client'
+
+import {createContext, useContext, useEffect, useState, type PropsWithChildren, type FC} from "react"
+import {useParams} from "next/navigation"
+import {type Project} from "@/_temp_types/projects"
+import {toast} from "@/hooks/use-toast"
+
+type ProjectSetContextType = {
+ projectSetId: string
+ projects: Project[]
+ displayProjects: Project[]
+ setDisplayProjects: (projects: Project[]) => void
+ currentProject: Project | undefined
+ setCurrentProject: (project: Project) => void
+}
+
+const ProjectSetContext = createContext({
+ projectSetId: "",
+ projects: [],
+ displayProjects: [],
+ setDisplayProjects: () => {},
+ currentProject: undefined,
+ setCurrentProject: () => {},
+})
+
+const useProjects = () => {
+ const {projectSetId} = useParams<{ projectSetId: string }>()
+ const [projects, setProjects] = useState([])
+ const [displayProjects, setDisplayProjects] = useState([])
+ const [currentProject, setCurrentProject] = useState()
+
+ useEffect(() => {
+ const fetchProjects = async () => {
+ const projectsURL = new URL(`/api/v1/teamset-templates/${projectSetId}/team-templates`, process.env.NEXT_PUBLIC_BACKEND_URL)
+ const projectsResponse = await fetch(projectsURL)
+ const projects = await projectsResponse.json()
+ setProjects(projects)
+ }
+ fetchProjects().catch((e) => {
+ toast({
+ title: "There was an error fetching the projects.",
+ variant: "destructive",
+ })
+ console.error(e)
+ })
+ }, [projectSetId])
+
+ return {
+ projectSetId,
+ projects,
+ displayProjects,
+ setDisplayProjects,
+ currentProject,
+ setCurrentProject,
+ }
+}
+
+export const ProjectsProvider: FC = ({children}) => {
+ const projectSet = useProjects()
+
+ return (
+
+ {children}
+
+ )
+}
+
+export const useProjectsContext = () => useContext(ProjectSetContext)
diff --git a/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/page.tsx b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/page.tsx
new file mode 100644
index 0000000..c6e177d
--- /dev/null
+++ b/src/app/(app)/course/[courseId]/project-sets/[projectSetId]/page.tsx
@@ -0,0 +1,47 @@
+import {toast} from "@/hooks/use-toast"
+import {type ApiTeamSetTemplate} from "@/_temp_types/api/teams"
+import PageView from "@/components/views/Page"
+import {
+ ProjectSetSidebar,
+} from "./(components)"
+import {ProjectsProvider, ProjectSearchProvider} from "./(hooks)"
+
+const getOutlinedProjectSetsData = async (): Promise => {
+ const projectSetsURL = new URL('/api/v1/teamset-templates', process.env.NEXT_PUBLIC_BACKEND_URL)
+ const response = await fetch(projectSetsURL)
+ if (!response.ok) {
+ const errMsg = "Unable to fetch project sets from API."
+ toast({
+ title: errMsg,
+ variant: "destructive",
+ })
+ throw new Error(errMsg)
+ }
+ return await response.json()
+}
+
+type ProjectPageType = {
+ params: {
+ courseId: string,
+ projectSetId: string,
+ },
+}
+
+const ProjectSetPage = async ({params: {courseId, projectSetId}}: ProjectPageType) => {
+ return
+
+
+
+
+
+
+}
+
+export default ProjectSetPage
diff --git a/src/app/(app)/course/[courseId]/project-sets/page.tsx b/src/app/(app)/course/[courseId]/project-sets/page.tsx
index 6361364..0e45b24 100644
--- a/src/app/(app)/course/[courseId]/project-sets/page.tsx
+++ b/src/app/(app)/course/[courseId]/project-sets/page.tsx
@@ -7,7 +7,8 @@ import {redirect} from "next/navigation"
import {columns} from "./columns"
const getProjectSetsData = async (): Promise => {
- const response = await fetch(process.env.NEXT_PUBLIC_BACKEND_URL + "/api/v1/teamset-templates")
+ const projectSetsURL = new URL('/api/v1/teamset-templates', process.env.NEXT_PUBLIC_BACKEND_URL) + "?detailed=true"
+ const response = await fetch(projectSetsURL)
if (!response.ok) {
throw new Error("Unable to fetch project sets from API.")
}
@@ -20,18 +21,24 @@ const getProjectSetsData = async (): Promise => {
}) as ProjectSet,)
}
-async function ProjectSetsPage() {
+type ProjectPageType = {
+ params: {
+ courseId: string,
+ },
+}
+
+async function ProjectSetsPage({params: {courseId}}: ProjectPageType) {
const handleRowClick = async (row: ProjectSet) => {
"use server"
- redirect(`project-sets/${row.id}`)
+ redirect(`/course/${courseId}/project-sets/${row.id}`)
}
return (
diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx
index 7f0b8e6..251e967 100644
--- a/src/components/SearchBar/index.tsx
+++ b/src/components/SearchBar/index.tsx
@@ -9,8 +9,8 @@ interface SearchProps extends InputProps {}
const SearchBar = React.forwardRef(({ className, type, ...props }, ref) => {
return (
-