Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remove department from professor #131

Open
wants to merge 1 commit into
base: beta
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 1 addition & 20 deletions packages/backend/src/routers/admin.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { t, protectedProcedure } from "@backend/trpc";
import { z } from "zod";
import { addRating } from "@backend/types/schemaHelpers";
import { bulkKeys, DEPARTMENT_LIST } from "@backend/utils/const";

const changeDepartmentParser = z.object({
professorId: z.string().uuid(),
department: z.enum(DEPARTMENT_LIST),
});
import { bulkKeys } from "@backend/utils/const";

const changeNameParser = z.object({
professorId: z.string().uuid(),
Expand Down Expand Up @@ -63,20 +58,6 @@ export const adminRouter = t.router({
await ctx.env.kvDao.putProfessor(destProfessor);
await ctx.env.kvDao.removeProfessor(sourceProfessor.id);
}),
changeProfessorDepartment: protectedProcedure
.input(changeDepartmentParser)
.mutation(async ({ ctx, input: { professorId, department } }) => {
const professor = await ctx.env.kvDao.getProfessor(professorId);
professor.department = department;
await ctx.env.kvDao.putProfessor(professor);
}),
changePendingProfessorDepartment: protectedProcedure
.input(changeDepartmentParser)
.mutation(async ({ ctx, input: { professorId, department } }) => {
const professor = await ctx.env.kvDao.getPendingProfessor(professorId);
professor.department = department;
await ctx.env.kvDao.putPendingProfessor(professor);
}),
changeProfessorName: protectedProcedure
.input(changeNameParser)
.mutation(async ({ ctx, input: { professorId, firstName, lastName } }) => {
Expand Down
2 changes: 0 additions & 2 deletions packages/backend/src/routers/professor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export const professorRouter = t.router({
add: t.procedure
.input(
z.object({
department: z.enum(DEPARTMENT_LIST),
firstName: z.string(),
lastName: z.string(),
rating: ratingBaseParser.merge(
Expand All @@ -35,7 +34,6 @@ export const professorRouter = t.router({
id: professorId,
firstName: input.firstName,
lastName: input.lastName,
department: input.department,
courses: [input.rating.department],
numEvals: 1,
overallRating: input.rating.overallRating,
Expand Down
1 change: 0 additions & 1 deletion packages/backend/src/types/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ export type PendingRating = z.infer<typeof pendingRatingParser>;

export const truncatedProfessorParser = z.object({
id: z.string().uuid(),
department: z.enum(DEPARTMENT_LIST),
firstName: z.string(),
lastName: z.string(),
numEvals: z.number(),
Expand Down
2 changes: 0 additions & 2 deletions packages/backend/src/types/schemaHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ export function professorToTruncatedProfessor({
id,
firstName,
lastName,
department,
courses,
numEvals,
overallRating,
Expand All @@ -113,7 +112,6 @@ export function professorToTruncatedProfessor({
id,
firstName,
lastName,
department,
courses,
numEvals,
overallRating,
Expand Down
1 change: 0 additions & 1 deletion packages/frontend/src/components/NewProfessorForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ function useNewProfessorForm() {
await addNewProfessorMutation({
firstName: data.professorFirstName,
lastName: data.professorLastName,
department: data.professorDepartment,
rating: {
overallRating: data.overallRating,
presentsMaterialClearly: data.presentsMaterialClearly,
Expand Down
1 change: 0 additions & 1 deletion packages/frontend/src/components/ProfessorCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export function ProfessorCard({ professor }: ProfessorCardProps) {
{professor?.lastName}, {professor?.firstName}
</h3>
<div className="text-right text-xl pr-3 font-medium flex-shrink-0">
<div>{professor?.department}</div>
<div className="flex items-center justify-end">
<img className="pr-1 h-4" src={star} alt="" />
<div>{professor?.overallRating.toFixed(2)}</div>
Expand Down
49 changes: 24 additions & 25 deletions packages/frontend/src/hooks/useSortedCourses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,40 @@ export function useSortedCourses(professorId: string | undefined) {
id: professorId ?? "",
});

// Put classes for professors primary department first. This is to cut down on rating spamming
// of other departments. It is possible for a professor to teach outside of the department but
// it is ok if those ratings come after the primary department
// Sort by the most common department and then by course number

// Sort Into Departments
// Group ratings by department and count occurrences in one pass
const professorByDepartments = Object.entries(professorData?.reviews || {}).reduce(
(acc, [course, ratings]) => {
const obj: CourseRatings = { courseName: course, ratings };
const [department] = course.split(" ");
if (acc[department]) {
acc[department].push(obj);
} else {
acc[department] = [obj];

if (!acc[department]) {
acc[department] = { ratings: [], count: 0 };
}

acc[department].ratings.push(obj);
acc[department].count += obj.ratings.length;

return acc;
},
{} as { [department: string]: CourseRatings[] },
{} as { [department: string]: { ratings: CourseRatings[]; count: number } },
);

// Sort departments by class number
Object.values(professorByDepartments).forEach((department) =>
department.sort((a, b) => {
const [, aNumber] = a.courseName.split(" ");
const [, bNumber] = b.courseName.split(" ");
return parseInt(aNumber, 10) - parseInt(bNumber, 10);
}),
);

const primaryClasses = professorByDepartments[professorData?.department ?? ""] ?? [];
const otherClasses = Object.entries(professorByDepartments)
.filter(([department]) => department !== professorData?.department)
.flatMap(([, courseRatings]) => courseRatings);

const sortedCourseRatings = [...primaryClasses, ...otherClasses].map((courseRating) => {
// Be carful the array is sorted in place. This is fine here but if moved could cause issues.
// Sort departments by frequency of ratings and within departments by course number
const sortedProfessorCourses = Object.entries(professorByDepartments)
.sort(([, a], [, b]) => b.count - a.count) // Sort departments by count (most common first)
.map(([, { ratings }]) =>
ratings.sort((a, b) => {
const [, aNumber] = a.courseName.split(" ");
const [, bNumber] = b.courseName.split(" ");
return parseInt(aNumber, 10) - parseInt(bNumber, 10);
}),
);

// Flatten sorted departments into a single array of course ratings
const sortedCourseRatings = sortedProfessorCourses.flat().map((courseRating) => {
// Sort ratings by post date within each course
courseRating.ratings.sort((a, b) => Date.parse(b.postDate) - Date.parse(a.postDate));
return courseRating;
});
Expand Down
56 changes: 2 additions & 54 deletions packages/frontend/src/pages/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { Fragment, lazy, Suspense, useState } from "react";
import { Professor, RatingReport, TruncatedProfessor } from "@backend/types/schema";
import { useQueryClient } from "@tanstack/react-query";
import { ExpanderComponentProps, TableProps } from "react-data-table-component";
import { Department, DEPARTMENT_LIST } from "@backend/utils/const";
import { Department } from "@backend/utils/const";
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/solid";
import { useAuth } from "@/hooks";
import { trpc } from "@/trpc";
import { bulkInvalidationKey, useDbValues } from "@/hooks/useDbValues";
import { Button } from "@/components/forms/Button";
import { AutoComplete, Select, TextInput } from "@/components";
import { AutoComplete, TextInput } from "@/components";
import { professorSearch } from "@/utils/ProfessorSearch";

const DataTableLazy = lazy(() => import("react-data-table-component"));
Expand Down Expand Up @@ -64,16 +64,6 @@ function ReportedRatings() {
return `${professor?.lastName}, ${professor?.firstName}`;
},
},
{
name: "Department",
grow: 0.5,
selector: (row: RatingReport) => {
const professor = professors?.find(
(professor) => professor?.id === row.professorId,
);
return professor?.department ?? "";
},
},
{
name: "Reason",
wrap: true,
Expand Down Expand Up @@ -181,10 +171,6 @@ function PendingProfessors() {
</a>
),
},
{
name: "Department",
selector: (row: Professor) => row.department,
},
{
name: "Rating Course",
selector: (row: Professor) => Object.keys(row.reviews ?? {})[0],
Expand Down Expand Up @@ -238,7 +224,6 @@ function PendingProfessorActions({ data: professor }: ExpanderComponentProps<Pro
<div className="flex flex-col gap-2 pl-2 pt-4">
<SubmitUnderAction professor={professor} />
<ChangeNameAction professor={professor} />
<ChangeNameDepartment professor={professor} />
</div>
);
}
Expand Down Expand Up @@ -373,43 +358,6 @@ function ChangeNameAction({ professor }: PendingProfessorAction) {
);
}

function ChangeNameDepartment({ professor }: PendingProfessorAction) {
const [department, setDepartment] = useState(professor.department);

const queryClient = useQueryClient();
const {
mutate: setProfessorDepartment,
isLoading,
error,
} = trpc.admin.changePendingProfessorDepartment.useMutation({
onSuccess: () => queryClient.invalidateQueries(bulkInvalidationKey("professor-queue")),
});

return (
<div className="flex gap-2 items-center text-sm">
<span>Change Department</span>
<Select
label=""
options={DEPARTMENT_LIST.map((dep) => ({ label: dep, value: dep }))}
value={department}
onChange={(e) => setDepartment(e.target.value as Department)}
/>
<Button
disabled={isLoading}
onClick={() =>
setProfessorDepartment({
professorId: professor.id,
department,
})
}
>
Submit
</Button>
{error?.message && <span className="text-red text-sm">{error.message}</span>}
</div>
);
}

function ProcessedRatings() {
const { data: processedRatings } = useDbValues("rating-log");
type PendingRating = NonNullable<typeof processedRatings>[0];
Expand Down
2 changes: 0 additions & 2 deletions packages/frontend/src/pages/ProfessorPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@ export function ProfessorPage() {

<div className="lg:max-w-5xl w-full mx-auto flex justify-center md:justify-between pt-4 md:pt-10 pb-3 px-2">
<div className="flex flex-col">
<h2 className="text-lg font-semibold">{professorData?.department} Professor</h2>

<h1 className="text-5xl font-bold">
{professorData?.lastName}, {professorData?.firstName}
</h1>
Expand Down