Skip to content

Commit

Permalink
finish student table rework
Browse files Browse the repository at this point in the history
  • Loading branch information
Opeyem1a committed Sep 8, 2024
1 parent dc17e65 commit 8411670
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 72 deletions.
2 changes: 2 additions & 0 deletions src/_temp_types/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ export interface Course {
id: number;
name: string;
organization: Organization;
sections: CourseSection[];
lms_course_id: string;
}

export interface CourseSection {
id: number;
name: string;
description: string;
}
4 changes: 3 additions & 1 deletion src/_temp_types/student.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Relationship } from "@/_temp_types/relationship"
import { CourseSection } from "@/_temp_types/course"

export type Student = {
id: number;
lms_id: number;
name: string;
attributes?: Record<number, number[]>;
relationships?: Record<number, Relationship>;
projectPreferences?: number[];
team?: number;
sections?: string[];
sections?: CourseSection[];
};
4 changes: 3 additions & 1 deletion src/app/(app)/course/[courseId]/(hooks)/useCourse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {
useState,
} from "react"
import { useParams } from "next/navigation"
import { Course } from "@/_temp_types/course"
import {Course, CourseSection} from "@/_temp_types/course"
import { useQuery } from "@tanstack/react-query"

interface CourseContextType {
courseId: number;
courseName: string;
sections: CourseSection[];
lmsType: string;
};

Expand Down Expand Up @@ -58,6 +59,7 @@ export const CourseProvider: React.FC<PropsWithChildren> = ({ children }) => {
value={{
courseId: course.id,
courseName: course.name,
sections: course.sections,
lmsType: course.organization.lms_type,
}}
>
Expand Down
87 changes: 73 additions & 14 deletions src/app/(app)/course/[courseId]/students/(hooks)/useStudents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import { PaginationState } from "@tanstack/react-table"
import { useCourse } from "../../(hooks)/useCourse"
import { useQuery } from "@tanstack/react-query"
import { PaginatedList } from "@/_temp_types/pagination"
import { PAGE_SIZE_OPTIONS } from "@/components/ui/data-table-pagination"

const SORTABLE_FIELDS = ["firstName", "lastName", "id"] as const
export type SortableFieldKey = (typeof SORTABLE_FIELDS)[number];

interface StudentsContextType {
/**
Expand All @@ -33,16 +37,24 @@ interface Filter<T extends string | string[] | number | number[]> {
set: (value: T) => void;
}

interface FilterValues {
export interface FilterValues {
sort: `${SortableFieldKey}.${"asc" | "desc"}` | "";
searchQuery: string;
pageIndex: PaginationState["pageIndex"];
pageSize: PaginationState["pageSize"];
selectedSections: string[];
}

// todo: useEffect that runs only on first render - this one takes the initial query params and sets them
// todo: useEffect that runs on every render except the first - this will take every filter and constantly update the query params
const FILTER_QUERY_PARAMS = {
sort: "sort",
searchQuery: "search",
pageIndex: "page",
pageSize: "per_page",
selectedSections: "sections",
} as const

interface StudentFilters {
sort: Filter<FilterValues["sort"]>;
searchQuery: Filter<FilterValues["searchQuery"]>;
pageIndex: Filter<FilterValues["pageIndex"]>;
pageSize: Filter<FilterValues["pageSize"]>;
Expand All @@ -57,6 +69,10 @@ const StudentsContext = createContext<StudentsContextType>({
totalPages: 0,
isLoading: true,
filters: {
sort: {
value: "",
set: () => {},
},
searchQuery: {
value: "",
set: () => {},
Expand Down Expand Up @@ -84,7 +100,6 @@ const useEffectOnce = (effect: EffectCallback) => {

effect()
stopRef.current = true
// fixme: maybe don't have this in the dep array though
}, [effect])
}

Expand All @@ -94,16 +109,37 @@ const useStudentsFilters = (): StudentFilters & {
} => {
const searchParams = useSearchParams()
const pathname = usePathname()
const { sections } = useCourse()
const validSectionIds = sections.map((s) => s.id)

const [initialFiltersLoading, setInitialFiltersLoading] = useState(true)

const [filters, setFilters] = useState<FilterValues>({
sort: "",
searchQuery: "",
pageIndex: 0,
pageSize: INITIAL_PAGE_SIZE,
selectedSections: [],
})

const setSort = useCallback((value: FilterValues["sort"]) => {
setFilters((prev) => {
const [key, direction] = (value ?? "").split(".")
let cleanValue = value
if (!SORTABLE_FIELDS.includes(key as SortableFieldKey)) {
cleanValue = ""
}
if (!["asc", "desc"].includes(direction)) {
cleanValue = ""
}

return {
...prev,
sort: cleanValue,
}
})
}, [])

const setSearchQuery = useCallback((value: FilterValues["searchQuery"]) => {
setFilters((prev) => ({
...prev,
Expand All @@ -124,7 +160,10 @@ const useStudentsFilters = (): StudentFilters & {

const setPageSize = useCallback((value: FilterValues["pageSize"]) => {
setFilters((prev) => {
const cleanValue = value > 0 ? value : 0
const cleanValue =
value > 0 && PAGE_SIZE_OPTIONS.includes(value)
? value
: PAGE_SIZE_OPTIONS[0]
return {
...prev,
pageSize: cleanValue,
Expand All @@ -135,32 +174,45 @@ const useStudentsFilters = (): StudentFilters & {
const setSelectedSections = useCallback(
(value: FilterValues["selectedSections"]) => {
setFilters((prev) => {
const cleanValue = value.filter((v) => !isNaN(Number(v)) && v)
const cleanValue = value.filter(
(v) => !isNaN(Number(v)) && validSectionIds.includes(Number(v)) && v,
)
return {
...prev,
selectedSections: cleanValue,
pageIndex: 0,
}
})
},
[],
[validSectionIds],
)

useEffectOnce(() => {
// Set the values of all filters to the initial query param values
setSearchQuery(searchParams.get("search") ?? "")
// todo: make sure it doesnt go below 0
setPageIndex(parseInt(searchParams.get("page") ?? "1") - 1)
setSort(
searchParams.get(FILTER_QUERY_PARAMS["sort"]) as FilterValues["sort"],
)
setSearchQuery(searchParams.get(FILTER_QUERY_PARAMS["searchQuery"]) ?? "")
setPageIndex(
parseInt(searchParams.get(FILTER_QUERY_PARAMS["pageIndex"]) ?? "1") - 1,
)
setPageSize(
parseInt(searchParams.get("per_page") ?? `${INITIAL_PAGE_SIZE}`),
parseInt(
searchParams.get(FILTER_QUERY_PARAMS["pageSize"]) ??
`${INITIAL_PAGE_SIZE}`,
),
)
// fixme: type error is wack
setSelectedSections(searchParams.get("sections")?.split(",") ?? [])
const rawSections = searchParams.get(
FILTER_QUERY_PARAMS["selectedSections"],
)
setSelectedSections(rawSections ? rawSections.split(",") : [])
setInitialFiltersLoading(false)
})

const queryString = useMemo(
() =>
createQueryString({
sort: (filters.sort as string) || undefined,
search: filters.searchQuery || undefined,
page: filters.pageIndex + 1, // adding one to make it appear as 1 indexed for the URL query in browser
per_page: filters.pageSize,
Expand All @@ -170,6 +222,7 @@ const useStudentsFilters = (): StudentFilters & {
: undefined,
}),
[
filters.sort,
filters.searchQuery,
filters.pageIndex,
filters.pageSize,
Expand All @@ -185,6 +238,10 @@ const useStudentsFilters = (): StudentFilters & {
return {
queryString,
initialFiltersLoading,
sort: {
value: filters.sort,
set: setSort,
},
searchQuery: {
value: filters.searchQuery,
set: setSearchQuery,
Expand All @@ -205,7 +262,7 @@ const useStudentsFilters = (): StudentFilters & {
}

const createQueryString = (
params: Record<string, string | number | undefined>,
params: Record<typeof FILTER_QUERY_PARAMS[keyof typeof FILTER_QUERY_PARAMS], string | number | undefined>,
) => {
const searchParams = new URLSearchParams()
Object.entries(params).forEach(([key, value]) => {
Expand Down Expand Up @@ -241,6 +298,7 @@ export const StudentsProvider: React.FC<PropsWithChildren> = ({ children }) => {
const {
queryString,
initialFiltersLoading,
sort,
searchQuery,
pageIndex,
pageSize,
Expand Down Expand Up @@ -269,6 +327,7 @@ export const StudentsProvider: React.FC<PropsWithChildren> = ({ children }) => {
totalStudents,
totalPages,
filters: {
sort,
searchQuery,
pageIndex,
pageSize,
Expand Down
45 changes: 26 additions & 19 deletions src/app/(app)/course/[courseId]/students/(table)/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import { ColumnDef } from "@tanstack/react-table"
import { Text } from "@/components/ui/text"
import { Badge } from "@/components/ui/badge"
import { DataTableColumnHeader } from "@/components/ui/data-table-column-header"
import { CourseSection } from "@/_temp_types/course"

type SectionFilterValue = string[];

export const columns: ColumnDef<Student>[] = [
{
id: "firstName",
meta: { columnName: "First Name" },
header: ({ column }) => (
<DataTableColumnHeader
column={column}
Expand All @@ -27,6 +29,7 @@ export const columns: ColumnDef<Student>[] = [
},
{
id: "lastName",
meta: { columnName: "Last Name" },
header: ({ column }) => (
<DataTableColumnHeader
column={column}
Expand All @@ -43,44 +46,48 @@ export const columns: ColumnDef<Student>[] = [
),
},
{
accessorKey: "id",
id: "id",
meta: { columnName: "Student ID" },
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title="Student ID"
hasDropDownMenu={false}
/>
),
cell: ({ row }) => (
accessorFn: (row) => row.lms_id ?? "",
cell: ({ getValue }) => (
<Text element="p" as="smallText">
{row.getValue("id")}
{String(getValue())}
</Text>
),
},
{
accessorKey: "sections",
id: "sections",
enableSorting: false,
header: ({ column }) => (
<DataTableColumnHeader
column={column}
title="Sections"
hasDropDownMenu={false}
/>
),
cell: ({ row }) => (
<div className="flex flex-row gap-2">
{(row.getValue("sections") as string[])?.map((section) => (
<Badge key={section} variant="secondary" className="rounded-sm">
<Text element="p" as="mutedText">
{section}
</Text>
</Badge>
))}
</div>
),
filterFn: (row, id, filterValues: SectionFilterValue) => {
const rowSections = row.getValue(id) as string[]
return filterValues.some((filterValue) =>
rowSections.includes(filterValue),)
accessorFn: (row) => row.sections?.map((s) => s.name) ?? [],
cell: ({ row, getValue }) => {
const sections = getValue() as string[]
return (
<div className="flex flex-row gap-2">
{sections?.map((section, index) => (
<Badge
key={`${row.id}-${section}-${index}`}
variant="secondary"
className="rounded-sm"
>
<Text element="p">{section}</Text>
</Badge>
))}
</div>
)
},
},
]
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Input } from "@/components/ui/input"
import { DataTableViewOptions } from "@/components/ui/data-table-view-options"
import { useStudents } from "../(hooks)/useStudents"
import { StudentTableSectionsFilter } from "./student-table-sections-filter"
import {DataTableFacetedFilterProps} from "@/components/ui/data-table-faceted-filter"
import { useCourse } from "@/app/(app)/course/[courseId]/(hooks)/useCourse"

type DataTableToolbarProps<TData> = {
table: Table<TData>;
Expand All @@ -14,13 +14,19 @@ type DataTableToolbarProps<TData> = {
export function DataTableToolbar<TData>({
table,
}: DataTableToolbarProps<TData>) {
// todo: fix
const allSections = [] as DataTableFacetedFilterProps<any, any>['options']
const { sections: allSections } = useCourse()
const { filters } = useStudents()

const sectionsOptions = allSections.map((s) => ({
label: s.name,
value: String(s.id),
}))

return (
<div className="flex items-center justify-between mt-2">
<div className="flex flex-1 items-center space-x-2">
<Input
value={filters.searchQuery.value}
placeholder="Search students..."
onChange={(event) => filters.searchQuery.set(event.target.value)}
className="h-8 w-[150px] lg:w-[250px]"
Expand All @@ -29,7 +35,7 @@ export function DataTableToolbar<TData>({
<StudentTableSectionsFilter
column={table.getColumn("sections")}
title="Sections"
options={allSections ?? []}
options={sectionsOptions}
/>
)}
</div>
Expand Down
Loading

0 comments on commit 8411670

Please sign in to comment.