-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #195 from HackAtUCI/feature/admin-site
Import Admin site from last year
- Loading branch information
Showing
26 changed files
with
1,052 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
{ | ||
"useTabs": true, | ||
"tabWidth": 4 | ||
"useTabs": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
"use client"; | ||
|
||
import { 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 useApplicants, { ApplicantSummary } from "@/lib/admin/useApplicants"; | ||
|
||
import ApplicantFilters, { Options } from "./components/ApplicantFilters"; | ||
import ApplicantStatus from "./components/ApplicantStatus"; | ||
|
||
function Applicants() { | ||
const [selectedStatuses, setSelectedStatuses] = useState<Options>([]); | ||
const [selectedDecisions, setSelectedDecisions] = useState<Options>([]); | ||
const { applicantList, loading } = useApplicants(); | ||
|
||
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 = ( | ||
<Box textAlign="center" color="inherit"> | ||
No applicants | ||
</Box> | ||
); | ||
|
||
return ( | ||
<Cards | ||
cardDefinition={{ | ||
header: CardHeader, | ||
sections: [ | ||
{ | ||
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: "decision", | ||
header: "Decision", | ||
content: DecisionStatus, | ||
}, | ||
], | ||
}} | ||
// visibleSections={preferences.visibleContent} | ||
loading={loading} | ||
loadingText="Loading applicants" | ||
items={items} | ||
trackBy="_id" | ||
variant="full-page" | ||
filter={ | ||
<ApplicantFilters | ||
selectedStatuses={selectedStatuses} | ||
setSelectedStatuses={setSelectedStatuses} | ||
selectedDecisions={selectedDecisions} | ||
setSelectedDecisions={setSelectedDecisions} | ||
/> | ||
} | ||
empty={emptyContent} | ||
header={<Header counter={counter}>Applicants</Header>} | ||
/> | ||
); | ||
} | ||
|
||
const CardHeader = ({ _id, application_data }: ApplicantSummary) => { | ||
const followWithNextLink = useFollowWithNextLink(); | ||
return ( | ||
<Link | ||
href={`/admin/applicants/${_id}`} | ||
fontSize="inherit" | ||
onFollow={followWithNextLink} | ||
> | ||
{application_data.first_name} {application_data.last_name} | ||
</Link> | ||
); | ||
}; | ||
|
||
const DecisionStatus = ({ decision }: ApplicantSummary) => | ||
decision ? <ApplicantStatus status={decision} /> : "-"; | ||
|
||
export default Applicants; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
"use client"; | ||
|
||
import ContentLayout from "@cloudscape-design/components/content-layout"; | ||
import Header from "@cloudscape-design/components/header"; | ||
import SpaceBetween from "@cloudscape-design/components/space-between"; | ||
import Spinner from "@cloudscape-design/components/spinner"; | ||
|
||
import useApplicant from "@/lib/admin/useApplicant"; | ||
|
||
import ApplicantActions from "./components/ApplicantActions"; | ||
import ApplicantOverview from "./components/ApplicantOverview"; | ||
import Application from "./components/Application"; | ||
|
||
interface ApplicantProps { | ||
params: { uid: string }; | ||
} | ||
|
||
function Applicant({ params }: ApplicantProps) { | ||
const { uid } = params; | ||
|
||
const { applicant, loading, submitReview } = useApplicant(uid); | ||
|
||
if (loading || !applicant) { | ||
return ( | ||
<ContentLayout header={<Header />}> | ||
<Spinner variant="inverted" /> | ||
</ContentLayout> | ||
); | ||
} | ||
|
||
const { application_data } = applicant; | ||
const { first_name, last_name } = application_data; | ||
|
||
return ( | ||
<ContentLayout | ||
header={ | ||
<Header | ||
variant="h1" | ||
description="Applicant" | ||
actions={ | ||
<ApplicantActions | ||
applicant={applicant._id} | ||
submitReview={submitReview} | ||
/> | ||
} | ||
> | ||
{first_name} {last_name} | ||
</Header> | ||
} | ||
> | ||
<SpaceBetween direction="vertical" size="l"> | ||
<ApplicantOverview applicant={applicant} /> | ||
<Application applicant={applicant} /> | ||
</SpaceBetween> | ||
</ContentLayout> | ||
); | ||
} | ||
|
||
export default Applicant; |
63 changes: 63 additions & 0 deletions
63
apps/site/src/app/admin/applicants/[uid]/components/ApplicantActions.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { useContext } from "react"; | ||
|
||
import ButtonDropdown, { | ||
ButtonDropdownProps, | ||
} from "@cloudscape-design/components/button-dropdown"; | ||
|
||
import { Decision, submitReview, Uid } from "@/lib/admin/useApplicant"; | ||
import UserContext from "@/lib/admin/UserContext"; | ||
|
||
interface ApplicantActionsProps { | ||
applicant: Uid; | ||
submitReview: submitReview; | ||
} | ||
|
||
interface ReviewButtonItem extends ButtonDropdownProps.Item { | ||
id: Decision; | ||
} | ||
|
||
type ReviewButtonItems = ReviewButtonItem[]; | ||
|
||
function ApplicantActions({ applicant, submitReview }: ApplicantActionsProps) { | ||
const { role } = useContext(UserContext); | ||
|
||
if (role !== "reviewer") { | ||
return null; | ||
} | ||
|
||
const handleClick = ( | ||
event: CustomEvent<ButtonDropdownProps.ItemClickDetails>, | ||
) => { | ||
const review = event.detail.id; | ||
submitReview(applicant, review as Decision); | ||
}; | ||
|
||
const dropdownItems: ReviewButtonItems = [ | ||
{ | ||
text: "Accept", | ||
id: Decision.accepted, | ||
iconName: "status-positive", | ||
description: "Accept the applicant", | ||
}, | ||
{ | ||
text: "Waitlist", | ||
id: Decision.waitlisted, | ||
iconName: "status-pending", | ||
description: "Waitlist the applicant", | ||
}, | ||
{ | ||
text: "Reject", | ||
id: Decision.rejected, | ||
iconName: "status-negative", | ||
description: "Reject the applicant", | ||
}, | ||
]; | ||
|
||
return ( | ||
<ButtonDropdown items={dropdownItems} onItemClick={handleClick}> | ||
Review | ||
</ButtonDropdown> | ||
); | ||
} | ||
|
||
export default ApplicantActions; |
41 changes: 41 additions & 0 deletions
41
apps/site/src/app/admin/applicants/[uid]/components/ApplicantOverview.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import Box from "@cloudscape-design/components/box"; | ||
import ColumnLayout from "@cloudscape-design/components/column-layout"; | ||
import Container from "@cloudscape-design/components/container"; | ||
import Header from "@cloudscape-design/components/header"; | ||
|
||
import ApplicantStatus from "@/app/admin/applicants/components/ApplicantStatus"; | ||
import { Applicant } from "@/lib/admin/useApplicant"; | ||
|
||
import ApplicationReviews from "./ApplicationReviews"; | ||
|
||
interface ApplicantOverviewProps { | ||
applicant: Applicant; | ||
} | ||
|
||
function ApplicantOverview({ applicant }: ApplicantOverviewProps) { | ||
const { application_data, status } = applicant; | ||
const { submission_time, reviews } = application_data; | ||
|
||
const submittedDate = new Date(submission_time).toDateString(); | ||
|
||
return ( | ||
<Container header={<Header variant="h2">Overview</Header>}> | ||
<ColumnLayout columns={3} variant="text-grid"> | ||
<div> | ||
<Box variant="awsui-key-label">Submitted</Box> | ||
{submittedDate} | ||
</div> | ||
<div> | ||
<Box variant="awsui-key-label">Status</Box> | ||
<ApplicantStatus status={status} /> | ||
</div> | ||
<div> | ||
<Box variant="awsui-key-label">Reviews</Box> | ||
<ApplicationReviews reviews={reviews} /> | ||
</div> | ||
</ColumnLayout> | ||
</Container> | ||
); | ||
} | ||
|
||
export default ApplicantOverview; |
43 changes: 43 additions & 0 deletions
43
apps/site/src/app/admin/applicants/[uid]/components/Application.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import Container from "@cloudscape-design/components/container"; | ||
import Header from "@cloudscape-design/components/header"; | ||
import SpaceBetween from "@cloudscape-design/components/space-between"; | ||
|
||
import { Applicant, ApplicationQuestion } from "@/lib/admin/useApplicant"; | ||
|
||
import ApplicationSection from "./ApplicationSection"; | ||
|
||
interface ApplicationSections { | ||
[key: string]: ApplicationQuestion[]; | ||
} | ||
|
||
const APPLICATION_SECTIONS: ApplicationSections = { | ||
"Personal Information": ["pronouns", "ethnicity", "is_18_older"], | ||
Education: ["school", "education_level", "major", "is_first_hackathon"], | ||
Experience: ["portfolio", "linkedin", "resume_url"], | ||
"Free Response Questions": ["frq_collaboration", "frq_dream_job"], | ||
}; | ||
|
||
interface ApplicationProps { | ||
applicant: Applicant; | ||
} | ||
|
||
function Application({ applicant }: ApplicationProps) { | ||
const { application_data } = applicant; | ||
|
||
return ( | ||
<Container header={<Header variant="h2">Application</Header>}> | ||
<SpaceBetween direction="vertical" size="m"> | ||
{Object.entries(APPLICATION_SECTIONS).map(([section, questions]) => ( | ||
<ApplicationSection | ||
key={section} | ||
title={section} | ||
data={application_data} | ||
propsToShow={questions} | ||
/> | ||
))} | ||
</SpaceBetween> | ||
</Container> | ||
); | ||
} | ||
|
||
export default Application; |
43 changes: 43 additions & 0 deletions
43
apps/site/src/app/admin/applicants/[uid]/components/ApplicationReviews.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { useContext } from "react"; | ||
|
||
import ApplicantStatus from "@/app/admin/applicants/components/ApplicantStatus"; | ||
import { Review, Uid } from "@/lib/admin/useApplicant"; | ||
import UserContext from "@/lib/admin/UserContext"; | ||
|
||
interface ApplicationReviewsProps { | ||
reviews: Review[]; | ||
} | ||
|
||
function ApplicationReviews({ reviews }: ApplicationReviewsProps) { | ||
const { uid } = useContext(UserContext); | ||
|
||
if (reviews.length === 0) { | ||
return <p>-</p>; | ||
} | ||
|
||
const formatUid = (uid: Uid) => uid.split(".").at(-1); | ||
const formatDate = (timestamp: string) => | ||
new Date(timestamp).toLocaleDateString(); | ||
|
||
return ( | ||
<ul> | ||
{reviews.map(([date, reviewer, decision]) => | ||
reviewer === uid ? ( | ||
<li key={date}> | ||
<> | ||
You reviewed as <ApplicantStatus status={decision} /> on{" "} | ||
{formatDate(date)} | ||
</> | ||
</li> | ||
) : ( | ||
<li key={date}> | ||
{formatUid(reviewer)} reviewed this application on{" "} | ||
{formatDate(date)} | ||
</li> | ||
), | ||
)} | ||
</ul> | ||
); | ||
} | ||
|
||
export default ApplicationReviews; |
Oops, something went wrong.