diff --git a/apps/site/src/app/admin/applicants/hackers/HackerApplicants.tsx b/apps/site/src/app/admin/applicants/hackers/HackerApplicants.tsx new file mode 100644 index 00000000..154ac248 --- /dev/null +++ b/apps/site/src/app/admin/applicants/hackers/HackerApplicants.tsx @@ -0,0 +1,134 @@ +"use client"; + +import { useRouter } from "next/navigation"; + +import { useContext, useState } from "react"; + +import Box from "@cloudscape-design/components/box"; +import Cards from "@cloudscape-design/components/cards"; +import Header from "@cloudscape-design/components/header"; +import Link from "@cloudscape-design/components/link"; + +import { useFollowWithNextLink } from "@/app/admin/layout/common"; +import useHackerApplicants, { + HackerApplicantSummary, +} from "@/lib/admin/useHackerApplicants"; + +import ApplicantFilters, { Options } from "../components/ApplicantFilters"; +import ApplicantStatus from "../components/ApplicantStatus"; + +import UserContext from "@/lib/admin/UserContext"; +import { isApplicationManager } from "@/lib/admin/authorization"; + +function HackerApplicants() { + const router = useRouter(); + + const { roles } = useContext(UserContext); + + if (!isApplicationManager(roles)) { + router.push("/admin/dashboard"); + } + + const [selectedStatuses, setSelectedStatuses] = useState([]); + const [selectedDecisions, setSelectedDecisions] = useState([]); + const { applicantList, loading } = useHackerApplicants(); + + const selectedStatusValues = selectedStatuses.map(({ value }) => value); + const selectedDecisionValues = selectedDecisions.map(({ value }) => value); + + const filteredApplicants = applicantList.filter( + (applicant) => + (selectedStatuses.length === 0 || + selectedStatusValues.includes(applicant.status)) && + (selectedDecisions.length === 0 || + selectedDecisionValues.includes(applicant.decision || "-")), + ); + + const items = filteredApplicants; + + const counter = + selectedStatuses.length > 0 || selectedDecisions.length > 0 + ? `(${items.length}/${applicantList.length})` + : `(${applicantList.length})`; + + const emptyContent = ( + + No applicants + + ); + + return ( + _id, + }, + { + id: "school", + header: "School", + content: ({ application_data }) => application_data.school, + }, + { + id: "status", + header: "Status", + content: ApplicantStatus, + }, + { + id: "submission_time", + header: "Applied", + content: ({ application_data }) => + new Date(application_data.submission_time).toLocaleDateString(), + }, + { + id: "avg_score", + header: "Averaged Score", + content: ({ avg_score }) => (avg_score === -1 ? "-" : avg_score), + }, + { + id: "decision", + header: "Decision", + content: DecisionStatus, + }, + ], + }} + // visibleSections={preferences.visibleContent} + loading={loading} + loadingText="Loading applicants" + items={items} + trackBy="_id" + variant="full-page" + filter={ + + } + empty={emptyContent} + header={
Applicants
} + /> + ); +} + +const CardHeader = ({ _id, first_name, last_name }: HackerApplicantSummary) => { + const followWithNextLink = useFollowWithNextLink(); + return ( + + {first_name} {last_name} + + ); +}; + +const DecisionStatus = ({ decision }: HackerApplicantSummary) => + decision ? : "-"; + +export default HackerApplicants; diff --git a/apps/site/src/app/admin/applicants/hackers/page.tsx b/apps/site/src/app/admin/applicants/hackers/page.tsx new file mode 100644 index 00000000..3480dad7 --- /dev/null +++ b/apps/site/src/app/admin/applicants/hackers/page.tsx @@ -0,0 +1 @@ +export { default as default } from "./HackerApplicants"; diff --git a/apps/site/src/lib/admin/useHackerApplicants.ts b/apps/site/src/lib/admin/useHackerApplicants.ts new file mode 100644 index 00000000..f4e1b457 --- /dev/null +++ b/apps/site/src/lib/admin/useHackerApplicants.ts @@ -0,0 +1,34 @@ +import axios from "axios"; +import useSWR from "swr"; + +import { Decision, Status } from "./useApplicant"; + +export interface HackerApplicantSummary { + _id: string; + first_name: string; + last_name: string; + status: Status; + decision: Decision | null; + num_reviewers: number; + avg_score: number; + application_data: { + school: string; + submission_time: string; + }; +} + +const fetcher = async (url: string) => { + const res = await axios.get(url); + return res.data; +}; + +function useHackerApplicants() { + const { data, error, isLoading } = useSWR( + "/api/admin/hackerApplicants", + fetcher, + ); + + return { applicantList: data || [], loading: isLoading, error }; +} + +export default useHackerApplicants;