Skip to content

Commit 8db8a03

Browse files
committed
address comments
1 parent 7f54bc5 commit 8db8a03

File tree

7 files changed

+89
-61
lines changed

7 files changed

+89
-61
lines changed

backend/src/services/ApplicationService.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,39 @@ class ApplicationService {
5454
return ApplicationModel.findById(id);
5555
}
5656

57+
/* 1 - First year, 2 - Second year... */
58+
public determineApplicantGradeLevel(startQuarter: number, gradQuarter: number): number {
59+
const totalQuartersAtUCSD = this.calculateQuarterDiff(startQuarter, gradQuarter);
60+
61+
const now = new Date();
62+
63+
// If it's currently summer, year level is rounded up to next fall
64+
// Shouldn't be relevant since we only receive applications in the fall
65+
const yearsSinceStart = Math.ceil(
66+
this.calculateQuarterDiff(
67+
startQuarter,
68+
now.getFullYear() * 4 + Math.floor(now.getMonth() / 3),
69+
) / 3,
70+
);
71+
72+
return totalQuartersAtUCSD < 9 ? yearsSinceStart + 2 : yearsSinceStart;
73+
}
74+
75+
/* Helper function to calculate academic quarters between two encoded quarter values */
76+
private calculateQuarterDiff(startQuarter: number, endQuarter: number): number {
77+
if (endQuarter < startQuarter) return 0;
78+
const yearsBetween = Math.floor(endQuarter / 4) - Math.floor(startQuarter / 4);
79+
return endQuarter - startQuarter - yearsBetween + 1;
80+
}
81+
5782
serialize(application: ApplicationDocument) {
58-
return application.toJSON();
83+
return {
84+
...application.toJSON(),
85+
applicantYear: this.determineApplicantGradeLevel(
86+
application.startQuarter,
87+
application.gradQuarter,
88+
),
89+
};
5990
}
6091
}
6192

backend/src/services/ReviewService.ts

Lines changed: 9 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,10 @@ class ReviewService {
222222
reviewers = excludingPreviousReviewers;
223223
}
224224

225-
const gradeLevel = this.determineApplicantGradeLevel(applicationDoc);
225+
const gradeLevel = ApplicationService.determineApplicantGradeLevel(
226+
applicationDoc.startQuarter,
227+
applicationDoc.gradQuarter,
228+
);
226229

227230
// Define filters for each stage
228231
const stageFilters: Partial<Record<StageIdentifier, (reviewer: UserDocument) => boolean>> = {
@@ -320,44 +323,19 @@ class ReviewService {
320323
return ReviewStatus.Completed;
321324
}
322325

323-
/* 1 - First year, 2 - Second year... */
324-
private determineApplicantGradeLevel(application: ApplicationDocument): number {
325-
const totalQuartersAtUCSD = this.calculateQuarterDiff(
326-
application.startQuarter,
327-
application.gradQuarter,
328-
);
329-
330-
const now = new Date();
331-
332-
// If it's currently summer, year level is rounded up to next fall
333-
// Shouldn't be relevant since we only receive applications in the fall
334-
const yearsSinceStart = Math.ceil(
335-
this.calculateQuarterDiff(
336-
application.startQuarter,
337-
now.getFullYear() * 4 + Math.floor(now.getMonth() / 3),
338-
) / 3,
339-
);
340-
341-
return totalQuartersAtUCSD < 9 ? yearsSinceStart + 2 : yearsSinceStart;
342-
}
343-
344-
/* Helper function to calculate academic quarters between two encoded quarter values */
345-
private calculateQuarterDiff(startQuarter: number, endQuarter: number): number {
346-
if (endQuarter < startQuarter) return 0;
347-
const yearsBetween = Math.floor(endQuarter / 4) - Math.floor(startQuarter / 4);
348-
return endQuarter - startQuarter - yearsBetween + 1;
349-
}
350-
351326
serialize(review: ReviewDocument) {
327+
const application = review.application as unknown as ApplicationDocument;
328+
352329
return {
353330
_id: review._id.toHexString(),
354331
stageId: review.stageId,
355332
stage: StageService.getById(review.stageId),
356333
application: review.application.toJSON(),
357334
reviewerEmail: review.reviewerEmail,
358335
fields: review.fields,
359-
applicantYear: this.determineApplicantGradeLevel(
360-
review.application as unknown as ApplicationDocument,
336+
applicantYear: ApplicationService.determineApplicantGradeLevel(
337+
application.startQuarter,
338+
application.gradQuarter,
361339
),
362340
};
363341
}

frontend/src/api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ export interface Application {
8484
email: string;
8585
phone: string;
8686

87+
applicantYear: number;
88+
8789
// Calendar year, e.g. 2022.
8890
yearApplied: number;
8991

frontend/src/components/ApplicationHeader.tsx

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
44
import { twMerge } from "tailwind-merge";
55

66
import api, { Application } from "../api";
7-
import { formatPhoneNumber } from "../helpers/application";
7+
import { formatApplicantYear, formatPhoneNumber } from "../helpers/application";
88
import { formatFieldNameHumanReadable } from "../helpers/review";
99
import { useAlerts } from "../hooks/alerts";
1010
import { formatQuarter } from "../util";
@@ -13,7 +13,7 @@ function PromptDropdown({ title, content }: { title: string; content: string | u
1313
const [isOpen, setIsOpen] = useState(true);
1414

1515
return (
16-
<div className="tw:rounded-lg tw:overflow-hidden">
16+
<div className="tw:rounded-lg tw:overflow-hidden tw:border tw:border-teal-primary">
1717
<button
1818
type="button"
1919
onClick={() => setIsOpen(!isOpen)}
@@ -25,7 +25,7 @@ function PromptDropdown({ title, content }: { title: string; content: string | u
2525
/>
2626
</button>
2727
{isOpen && (
28-
<div className="tw:p-4 tw:border tw:border-teal-primary tw:border-t-0 tw:rounded-b-lg">
28+
<div className="tw:p-4 tw:border-t tw:border-teal-primary tw:rounded-b-lg">
2929
<div className="tw:whitespace-pre-line">{content || "No response provided"}</div>
3030
</div>
3131
)}
@@ -38,6 +38,12 @@ interface ApplicationHeaderProps {
3838
reassignReview?: () => void;
3939
}
4040

41+
const field = (name: string, value: string) => (
42+
<p className="tw:!m-0">
43+
<b>{name}</b>: {value}
44+
</p>
45+
);
46+
4147
/* Displays information about the applicant, including a Reassign button if being used in the Review page */
4248
export default function ApplicationHeader({
4349
applicationId,
@@ -60,7 +66,11 @@ export default function ApplicationHeader({
6066
"About Me": application?.aboutPrompt,
6167
"Why TSE": application?.interestPrompt,
6268
"TEST Barriers": application?.testBarriersPrompt,
63-
...application?.rolePrompts,
69+
// Format role prompts as "Why [role]"
70+
...Object.entries(application?.rolePrompts || {}).reduce((acc, [key, value]) => {
71+
acc[`Why ${formatFieldNameHumanReadable(key)}`] = value;
72+
return acc;
73+
}, {} as Record<string, string | undefined>),
6474
};
6575

6676
return (
@@ -77,19 +87,20 @@ export default function ApplicationHeader({
7787
</Button>
7888
)}
7989
</div>
80-
<div className="tw:grid tw:grid-rows-4 tw:grid-flow-col tw:w-fit tw:gap-x-30 tw:gap-y-2 tw:!text-lg">
81-
<p className="tw:!m-0">ID: {application?._id}</p>
82-
<p className="tw:!m-0">
83-
Major: {application?.major} ({application?.majorDept})
84-
</p>
85-
<p className="tw:!m-0">Start Date: {formatQuarter(application?.startQuarter || 0)}</p>
86-
<p className="tw:!m-0">Grad Date: {formatQuarter(application?.gradQuarter || 0)}</p>
87-
<p className="tw:!m-0">Phone: {formatPhoneNumber(application?.phone || "")}</p>
88-
<p className="tw:!m-0">Email: {application?.email}</p>
89-
<p className="tw:!m-0">
90-
Previously in TEST:{" "}
91-
{application?.prevTest ? formatFieldNameHumanReadable(application?.prevTest) : "No"}
92-
</p>
90+
<div className="tw:grid tw:grid-rows-5 tw:grid-flow-col tw:w-fit tw:gap-x-30 tw:gap-y-2 tw:!text-lg">
91+
{field("ID", application?._id || "")}
92+
{field("Major", `${application?.major} (${application?.majorDept})`)}
93+
{field("Year", formatApplicantYear(application?.applicantYear || 0))}
94+
{field("Start Date", formatQuarter(application?.startQuarter || 0))}
95+
{field("Grad Date", formatQuarter(application?.gradQuarter || 0))}
96+
{field("Phone", formatPhoneNumber(application?.phone || ""))}
97+
{field("Email", application?.email || "")}
98+
{field(
99+
"Previously in TEST",
100+
application?.prevTest && application?.prevTest !== "none"
101+
? formatFieldNameHumanReadable(application?.prevTest)
102+
: "No"
103+
)}
93104
<a href={application?.resumeUrl} target="_blank" rel="noreferrer noopener">
94105
Resume
95106
</a>
@@ -105,13 +116,7 @@ export default function ApplicationHeader({
105116
return null;
106117
}
107118

108-
return (
109-
<PromptDropdown
110-
key={key}
111-
title={formatFieldNameHumanReadable(key)}
112-
content={response}
113-
/>
114-
);
119+
return <PromptDropdown key={key} title={key} content={response} />;
115120
})}
116121
</div>
117122
{alerts}

frontend/src/components/ScoreCard.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ import { Link } from "react-router-dom";
22

33
import { PopulatedReview } from "../api";
44
import { formatFieldNameHumanReadable } from "../helpers/review";
5+
import { useUsers } from "../hooks/users";
56

67
interface ScoreCardProps {
78
review: PopulatedReview;
89
}
910

1011
export default function ScoreCard({ review }: ScoreCardProps) {
12+
const { emailsToUsers } = useUsers();
13+
1114
// Each field in this array is [fieldName, fieldValue, maxValue, rubricLink]
1215
const scoreAndRatingFields = Object.entries(review.stage.fields || {})
1316
.filter(([key, _]) => key.includes("score") || key.includes("rating"))
@@ -19,14 +22,18 @@ export default function ScoreCard({ review }: ScoreCardProps) {
1922
]);
2023

2124
return (
22-
<div className="tw:flex tw:flex-col tw:rounded-lg tw:overflow-hidden tw:min-w-90">
25+
<div className="tw:flex tw:flex-col tw:rounded-lg tw:overflow-hidden tw:min-w-90 tw:border tw:border-teal-primary">
2326
<div className="tw:bg-accent tw:p-2.5 tw:text-white tw:flex tw:justify-between tw:items-center">
24-
<span>{review.reviewerEmail ?? "(Unassigned)"}</span>
27+
<span>
28+
{review.reviewerEmail
29+
? emailsToUsers[review.reviewerEmail]?.name ?? "(unknown user)"
30+
: "(unassigned)"}
31+
</span>
2532
<Link to={`/review/${review._id}/edit`} className="tw:!text-white tw:underline">
2633
View
2734
</Link>
2835
</div>
29-
<div className="tw:rounded-b-lg tw:border tw:border-top-0 tw:border-teal-primary tw:p-5">
36+
<div className="tw:rounded-b-lg tw:border-t tw:border-teal-primary tw:p-5">
3037
<div
3138
className={`tw:grid tw:gap-5 tw:content-center tw:items-center ${
3239
scoreAndRatingFields.length === 1 ? "tw:grid-cols-1" : "tw:grid-cols-2"

frontend/src/components/StageNotes.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { PopulatedReview } from "../api";
22
import { formatFieldNameHumanReadable } from "../helpers/review";
3+
import { useUsers } from "../hooks/users";
34

45
interface StageNotesProps {
56
reviewsInStage: PopulatedReview[];
67
}
78

89
/* Lists notes from all reviewers */
910
export default function StageNotes({ reviewsInStage: reviews }: StageNotesProps) {
11+
const { emailsToUsers } = useUsers();
12+
1013
const notesFields = reviews.flatMap((r) =>
1114
Object.entries(r.fields)
1215
.filter(([key, _]) => key.includes("notes"))
@@ -24,9 +27,10 @@ export default function StageNotes({ reviewsInStage: reviews }: StageNotesProps)
2427

2528
return (
2629
<div key={`${key}-${email}`} className="tw:flex tw:flex-col">
27-
({email})
30+
({emailsToUsers[email]?.name ?? "(unknown user)"})
2831
<div>
29-
<span className="tw:font-medium">{label === "" ? "General" : label}</span>: {value}
32+
<span className="tw:font-medium">{label === "" ? "General" : label}: </span>
33+
<span className="tw:whitespace-pre-line">{value}</span>
3034
</div>
3135
</div>
3236
);

frontend/src/pages/ViewApplication.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function ViewApplication() {
2525
.slice()
2626
.sort(
2727
makeComparator((r) => [
28+
-r.stage.pipelineIndex,
2829
r.stage.pipelineIdentifier,
2930
r.stage.pipelineIndex,
3031
r.reviewerEmail || "",

0 commit comments

Comments
 (0)