Skip to content

Commit

Permalink
Home Page Filtering, Team role changes (#94)
Browse files Browse the repository at this point in the history
* feat: role management

* feat: hide notifications from team

* feat: filtering + designer feedback

* fix: remove hover
  • Loading branch information
parth4apple authored Apr 23, 2024
1 parent 49aee5f commit c5794b5
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 262 deletions.
4 changes: 3 additions & 1 deletion frontend/src/components/Navigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Link from "next/link";
import { useRouter } from "next/router";
import React, { useEffect } from "react";

import { navigation } from "../constants/navigation";
import { useNavigation } from "../constants/navigation";
import { useWindowSize } from "../hooks/useWindowSize";
import { cn } from "../lib/utils";

Expand Down Expand Up @@ -51,6 +51,7 @@ const Logo = ({ setShelf, isMobile, black }: LinkProps & { black?: boolean }) =>
// the mapping of elements within navigation
const Links = ({ setShelf, isMobile }: LinkProps) => {
const router = useRouter();
const navigation = useNavigation();

return navigation.map((item, i) => {
return (
Expand Down Expand Up @@ -81,6 +82,7 @@ function Navigation({ children }: { children: React.ReactNode }) {
const [offset, setOffset] = React.useState(0);
const [shelf, setShelf] = React.useState(false); // on mobile whether the navbar is open
const { isMobile } = useWindowSize();
const navigation = useNavigation();

useEffect(() => {
const ordering = navigation.map((item) => item.href);
Expand Down
65 changes: 39 additions & 26 deletions frontend/src/components/StudentFormButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { ProgramsContext } from "./StudentsTable/StudentsTable";
import { StudentMap } from "./StudentsTable/types";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "./ui/dialog";

import { UserContext } from "@/contexts/user";

type BaseProps = {
classname?: string;
setAllStudents: Dispatch<SetStateAction<StudentMap>>;
Expand Down Expand Up @@ -50,6 +52,7 @@ export default function StudentFormButton({
const [openForm, setOpenForm] = useState(false);
const programsMap = useContext(ProgramsContext);
const allPrograms = useMemo(() => Object.values(programsMap), [programsMap]);
const { isAdmin } = useContext(UserContext);

const onFormSubmit: SubmitHandler<StudentFormData> = (formData: StudentFormData) => {
const programAbbreviationToId = {} as Record<string, string>; // abbreviation -> programId
Expand Down Expand Up @@ -178,19 +181,19 @@ export default function StudentFormButton({
classname,
)}
>
<fieldset>
<fieldset disabled={!isAdmin}>
<legend className="mb-5 w-full text-left font-bold">Contact Information</legend>
<ContactInfo register={register} data={data ?? null} type={type} />
</fieldset>
<fieldset>
<fieldset disabled={!isAdmin}>
<legend className="mb-5 w-full text-left font-bold">Student Background</legend>
<StudentBackground
register={register}
data={data ?? null}
setCalendarValue={setCalendarValue}
/>
</fieldset>
<fieldset>
<fieldset disabled={!isAdmin}>
<legend className="mb-5 w-full text-left font-bold">Student Information</legend>
<StudentInfo
register={register}
Expand All @@ -200,30 +203,40 @@ export default function StudentFormButton({
</fieldset>
<div className="ml-auto mt-5 flex gap-5">
{/* Modal Confirmation Dialog */}
<Dialog>
<DialogTrigger asChild>
<Button label="Cancel" kind="secondary" />
</DialogTrigger>
<Button label="Save Changes" type="submit" />
<DialogContent className="max-h-[30%] max-w-[80%] rounded-[8px] md:max-w-[50%] lg:max-w-[30%]">
<div className="p-3 min-[450px]:p-10">
<p className="my-10 text-center">Leave without saving changes?</p>
<div className="grid justify-center gap-5 min-[450px]:flex min-[450px]:justify-between">
<DialogClose asChild>
<Button label="Back" kind="secondary" />
</DialogClose>
<DialogClose asChild>
<Button
label="Continue"
onClick={() => {
setOpenForm(false);
}}
/>
</DialogClose>
{isAdmin ? (
<Dialog>
<DialogTrigger asChild>
<Button label="Cancel" kind="secondary" />
</DialogTrigger>
<Button label="Save Changes" type="submit" />
<DialogContent className="max-h-[30%] max-w-[80%] rounded-[8px] md:max-w-[50%] lg:max-w-[30%]">
<div className="p-3 min-[450px]:p-10">
<p className="my-10 text-center">Leave without saving changes?</p>
<div className="grid justify-center gap-5 min-[450px]:flex min-[450px]:justify-between">
<DialogClose asChild>
<Button label="Back" kind="secondary" />
</DialogClose>
<DialogClose asChild>
<Button
label="Continue"
onClick={() => {
setOpenForm(false);
}}
/>
</DialogClose>
</div>
</div>
</div>
</DialogContent>
</Dialog>
</DialogContent>
</Dialog>
) : (
<Button
label="Exit"
kind="secondary"
onClick={() => {
setOpenForm(false);
}}
/>
)}
</div>
</form>
</DialogContent>
Expand Down
119 changes: 119 additions & 0 deletions frontend/src/components/StudentsTable/FilterFns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { RankingInfo, rankItem } from "@tanstack/match-sorter-utils";
import { Column, FilterFn } from "@tanstack/react-table";
import { useContext, useMemo } from "react";

import { Dropdown } from "../Dropdown";
import { ProgramLink } from "../StudentForm/types";

import { ProgramsContext } from "./StudentsTable";
import { StudentTableRow } from "./types";

import { useWindowSize } from "@/hooks/useWindowSize";

// Extend the FilterFns and FilterMeta interfaces
/* eslint-disable */
declare module "@tanstack/table-core" {
interface FilterFns {
fuzzy: FilterFn<unknown>;
programFilter: FilterFn<unknown>;
statusFilter: FilterFn<unknown>;
}
interface FilterMeta {
itemRank: RankingInfo;
}
}
/* eslint-enable */

export const programFilterFn: FilterFn<unknown> = (rows, id, filterValue) => {
if (filterValue === "") return true; // no filter case
let containsProgram = false;
const programLinks: ProgramLink[] = rows.getValue(id);
programLinks.forEach((prog) => {
if (prog.programId === filterValue && prog.status === "Joined") {
containsProgram = true;
}
});
return containsProgram;
};

export function ProgramFilter({ column }: { column: Column<StudentTableRow> }) {
const { isTablet } = useWindowSize();

const programsMap = useContext(ProgramsContext);
// Get unique programs to display in the program filter dropdown
const sortedUniqueValues = useMemo(() => {
const values = new Set(Object.values(programsMap).map((program) => program.name));

return Array.from(values)
.sort()
.filter((v) => v !== "");
}, [programsMap]);

// Create a map from program name to program id for easy lookup (dropdown uses names but filter needs ids)
const programNameToId = useMemo(() => {
const map: Record<string, string> = {};
for (const [id, program] of Object.entries(programsMap)) {
map[program.name] = id;
}
return map;
}, [programsMap]);

return (
<Dropdown
label="Program"
name="program"
placeholder="Program"
className={`rounded-md ${isTablet ? "w-[200px]" : "w-[244px]"}`}
defaultValue="All Programs"
options={sortedUniqueValues}
onChange={(value): void => {
column.setFilterValue(programNameToId[value] || "");
}}
/>
);
}

export const statusFilterFn: FilterFn<unknown> = (rows, id, filterValue) => {
if (filterValue === "") return true; // no filter case
let containsStatus = false;
const programLinks: ProgramLink[] = rows.getValue(id);
programLinks.forEach((prog) => {
if (prog.status === filterValue) {
containsStatus = true;
}
});
return containsStatus;
};

export function StatusFilter({ column }: { column: Column<StudentTableRow> }) {
const { isTablet } = useWindowSize();
const statusOptions = ["Joined", "Waitlisted", "Archived", "Not a fit"];

return (
<Dropdown
label="Status"
name="status"
placeholder="Status"
className={`rounded-md ${isTablet ? "w-[200px]" : "w-[244px]"}`}
defaultValue="All Statuses"
options={statusOptions}
onChange={(value): void => {
column.setFilterValue(value);
}}
/>
);
}

// Filter function from tanstack docs for global filter (search in students )
export const fuzzyFilter: FilterFn<StudentTableRow> = (row, columnId, value, addMeta) => {
// Rank the item
const itemRank = rankItem(row.getValue(columnId), value as string);

// Store the itemRank info
addMeta({
itemRank,
});

// Return if the item should be filtered in/out
return itemRank.passed;
};
33 changes: 0 additions & 33 deletions frontend/src/components/StudentsTable/ProgramFilter.tsx

This file was deleted.

25 changes: 11 additions & 14 deletions frontend/src/components/StudentsTable/StudentsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import {
ColumnFiltersState,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getSortedRowModel,
useReactTable,
} from "@tanstack/react-table";
import React, { useEffect, useMemo, useState } from "react";
import React, { useContext, useEffect, useMemo, useState } from "react";

import { getAllStudents } from "../../api/students";
import { fuzzyFilter } from "../../lib/fuzzyFilter";
import StudentFormButton from "../StudentFormButton";
import { Table } from "../ui/table";

import { fuzzyFilter, programFilterFn, statusFilterFn } from "./FilterFns";
import TBody from "./TBody";
import THead from "./THead";
import { ProgramMap, StudentMap, StudentTableRow } from "./types";
import { useColumnSchema } from "./useColumnSchema";

import { Program, getAllPrograms } from "@/api/programs";
import { UserContext } from "@/contexts/user";
import { useWindowSize } from "@/hooks/useWindowSize";
import { cn } from "@/lib/utils";

Expand All @@ -30,10 +30,11 @@ export default function StudentsTable() {
const [allPrograms, setAllPrograms] = useState<ProgramMap>({}); // map from program id to program
const [isLoading, setIsLoading] = useState(true);
const [studentTable, setStudentTable] = useState<StudentTableRow[]>([]);
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [globalFilter, setGlobalFilter] = useState("");
const { isTablet } = useWindowSize();

const { isAdmin } = useContext(UserContext);

useEffect(() => {
getAllPrograms().then(
(result) => {
Expand Down Expand Up @@ -92,6 +93,7 @@ export default function StudentsTable() {

const columns = useColumnSchema({ allStudents, allPrograms, setAllStudents });
const data = useMemo(() => studentTable, [studentTable]);
console.log(data);
// const data = useMemo(() => [], [allStudents]); // uncomment this line and comment the line above to see empty table state

const table = useReactTable({
Expand All @@ -100,12 +102,12 @@ export default function StudentsTable() {
getCoreRowModel: getCoreRowModel(),
filterFns: {
fuzzy: fuzzyFilter,
programFilter: programFilterFn,
statusFilter: statusFilterFn,
},
state: {
columnFilters,
globalFilter,
},
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
globalFilterFn: fuzzyFilter,
getFilteredRowModel: getFilteredRowModel(),
Expand All @@ -131,20 +133,15 @@ export default function StudentsTable() {
>
Students
</h1>
{isTablet && <StudentFormButton type="add" setAllStudents={setAllStudents} />}
{isAdmin && <StudentFormButton type="add" setAllStudents={setAllStudents} />}
</div>
<Table
className={cn(
"h-fit w-[100%] min-w-[640px] max-w-[1120px] border-collapse rounded-lg bg-pia_primary_white",
"h-fit w-[100%] min-w-[640px] max-w-[1480px] border-collapse rounded-lg bg-pia_primary_white font-['Poppins']",
isTablet && "text-[12px]",
)}
>
<THead
table={table}
globalFilter={globalFilter}
setGlobalFilter={setGlobalFilter}
setAllStudents={setAllStudents}
/>
<THead table={table} globalFilter={globalFilter} setGlobalFilter={setGlobalFilter} />
<TBody table={table} />
</Table>
</div>
Expand Down
Loading

0 comments on commit c5794b5

Please sign in to comment.