Skip to content

Commit

Permalink
Merge pull request #884 from sandboxnu/ts-az-edit-course-info-page-fr…
Browse files Browse the repository at this point in the history
…ontend

Admin Edit Course Info Page Frontend
  • Loading branch information
dfarooq610 authored Jul 26, 2023
2 parents bf7381a + 911b8ac commit cfe4901
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 9 deletions.
9 changes: 9 additions & 0 deletions packages/app/components/Settings/CourseAdminPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
BellOutlined,
EditOutlined,
QuestionCircleOutlined,
BookOutlined,
} from "@ant-design/icons";
import { Col, Menu, Row, Space, Tooltip } from "antd";
import { useRouter } from "next/router";
Expand All @@ -11,9 +12,11 @@ import { useProfile } from "../../hooks/useProfile";
import CourseRosterPage from "./CourseRosterPage";
import { SettingsPanelAvatar } from "./SettingsSharedComponents";
import TACheckInCheckOutTimes from "./TACheckInCheckOutTimes";
import CourseInformation from "./CourseInformation";

export enum CourseAdminOptions {
CHECK_IN = "CHECK_IN",
INFO = "INFO",
ROSTER = "ROSTER",
}

Expand Down Expand Up @@ -80,11 +83,17 @@ export default function CourseAdminPanel({
<Menu.Item key={CourseAdminOptions.ROSTER} icon={<BellOutlined />}>
Course Roster
</Menu.Item>
<Menu.Item key={CourseAdminOptions.INFO} icon={<BookOutlined />}>
Course Information
</Menu.Item>
</Menu>
</Col>
<VerticalDivider />
<Space direction="vertical" size={40} style={{ flexGrow: 1 }}>
<Col span={20}>
{currentSettings === CourseAdminOptions.INFO && (
<CourseInformation courseId={courseId} />
)}
{currentSettings === CourseAdminOptions.CHECK_IN && (
<TACheckInCheckOutTimes courseId={courseId} />
)}
Expand Down
173 changes: 173 additions & 0 deletions packages/app/components/Settings/CourseInformation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { DeleteTwoTone, PlusCircleOutlined } from "@ant-design/icons";
import { API } from "@koh/api-client";
import { Form, Input, InputNumber, Tag, Button, Space, message } from "antd";
import React, { ReactElement, useState } from "react";
import styled from "styled-components";
import { useCourse } from "../../hooks/useCourse";

type CourseOverrideSettingsProps = { courseId: number };

const CRNTag = styled(Tag)`
font-size: 15px;
padding: 5px 10px;
`;

const AddCRNTag = styled(CRNTag)`
cursor: pointer;
`;

export default function CourseInfo({
courseId,
}: CourseOverrideSettingsProps): ReactElement {
const [form] = Form.useForm();
const [showCRNInput, setShowCRNInput] = useState(false);
const { course } = useCourse(courseId);
const [crns, setCrns] = useState(
course.crns.map((c) => c.toString().padStart(5, "0"))
);
const [inputCRN, setInputCRN] = useState<string | null>(null);

const showInput = () => {
setShowCRNInput(true);
};

const handleDiscardChanges = async () => {
form.setFieldsValue({ ...course });
setCrns(course.crns.map((c) => c.toString().padStart(5, "0")));
};

const handleSaveChanges = async () => {
const value = await form.validateFields();
value.crns = Array.from(new Set(crns));

try {
await API.course.editCourseInfo(course.id, value);
message.success("Successfully updated course information.");
} catch (e) {
message.error(e.response?.data?.message);
}
};

const handleCRNAdd = () => {
if (inputCRN) {
if (crns.includes(inputCRN)) {
message.error(`The CRN ${inputCRN} already exists.`);
} else {
setCrns([...crns, inputCRN]);
}
}
setShowCRNInput(false);
setInputCRN(null);
};

const handleCRNDelete = (crn) => {
setCrns(crns.filter((c) => c !== crn));
};

return (
<div>
<Form form={form} layout="vertical" initialValues={course}>
<Space style={{ marginTop: "25px" }}>
<Form.Item
name="name"
label="Course Display Name"
tooltip="This is the course name that will be displayed within the app"
rules={[
{ required: true, message: "Please input a display name." },
]}
>
<Input placeholder="ex: CS 2500" maxLength={20} />
</Form.Item>
</Space>

<Form.Item
name="coordinator_email"
label="Coordinator Email"
rules={[
{
required: true,
type: "email",
message: "Please input your email.",
},
]}
>
<Input placeholder="[email protected]" />
</Form.Item>

<Form.Item
label="Office Hours Calendar URL"
tooltip={
<div>
See{" "}
<a
target="_blank"
rel="noopener noreferrer"
href="https://info.khouryofficehours.com/coordinators-manual"
>
here
</a>{" "}
to create your office hours calendar
</div>
}
name="icalURL"
rules={[
{
required: true,
pattern: new RegExp("https://.*.ics"),
message: "Please input your office hours calendar URL.",
},
]}
>
<Input placeholder="https://calendar.google.com/calendar/ical/.../basic.ics" />
</Form.Item>

<Form.Item label="Registered CRNs">
{crns.map((crn) => (
<CRNTag
closeIcon={
<DeleteTwoTone
twoToneColor="#F76C6C"
style={{ fontSize: "18px" }}
/>
}
key={crn}
closable={true}
onClose={() => handleCRNDelete(crn)}
>
{crn}
</CRNTag>
))}
{showCRNInput ? (
<InputNumber<string>
className="tag-input"
value={inputCRN}
maxLength={5}
min={"00000"}
onChange={(evt) => setInputCRN(evt.padStart(5, "0"))}
onBlur={handleCRNAdd}
onPressEnter={handleCRNAdd}
stringMode
/>
) : (
<AddCRNTag
icon={<PlusCircleOutlined style={{ fontSize: "15px" }} />}
color="#408FEA"
className="add-crn"
onClick={showInput}
>
Add CRN
</AddCRNTag>
)}
</Form.Item>
</Form>

<Space style={{ marginTop: "5px" }}>
<Button onClick={handleDiscardChanges}>Discard Changes</Button>

<Button onClick={handleSaveChanges} type="primary">
Save Changes
</Button>
</Space>
</div>
);
}
6 changes: 5 additions & 1 deletion packages/app/hooks/useQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,11 @@ export function useQueue(qid: number, onUpdate?: OnUpdate): UseQueueReturn {
)
);

const { data: queue, error: queueError, mutate: mutateQueue } = useSWR(
const {
data: queue,
error: queueError,
mutate: mutateQueue,
} = useSWR(
key,
useCallback(async () => API.queues.get(Number(qid)), [qid]),
{
Expand Down
3 changes: 0 additions & 3 deletions packages/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -692,9 +692,6 @@ export class RegisterCourseParams {
}

export class EditCourseInfoParams {
@IsNumber()
courseId!: number;

@IsString()
@IsOptional()
name?: string;
Expand Down
5 changes: 4 additions & 1 deletion packages/server/src/course/course.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ export class CourseController {

const course_response = { ...course, crns: null };
try {
course_response.crns = await CourseSectionMappingModel.find({ course });
const mappings = await CourseSectionMappingModel.find({
courseId: course.id,
});
course_response.crns = mappings.map((mapping) => mapping.crn);
} catch (err) {
console.error(
ERROR_MESSAGES.courseController.courseOfficeHourError +
Expand Down
20 changes: 19 additions & 1 deletion packages/server/src/course/course.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,26 @@ export class CourseService {
);
}

let courseCrnMaps = await CourseSectionMappingModel.find({ courseId });
for (const courseCrnMap of courseCrnMaps) {
if (!coursePatch.crns.includes(courseCrnMap.crn)) {
try {
await CourseSectionMappingModel.delete({
crn: courseCrnMap.crn,
courseId: course.id,
});
} catch (err) {
console.error(err);
throw new HttpException(
ERROR_MESSAGES.courseController.createCourseMappings,
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
}

for (const crn of new Set(coursePatch.crns)) {
const courseCrnMaps = await CourseSectionMappingModel.find({
courseCrnMaps = await CourseSectionMappingModel.find({
crn: crn,
});

Expand Down
8 changes: 5 additions & 3 deletions packages/server/test/course.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -857,11 +857,10 @@ describe('Course Integration', () => {
});

const editCourseTomato = {
courseId: course.id,
name: 'Tomato',
icalURL: 'https://calendar.google.com/calendar/ical/tomato/basic.ics',
coordinator_email: '[email protected]',
crns: [30303, 67890],
crns: [67890],
};

// update crns, coordinator email, name, icalURL
Expand All @@ -882,6 +881,10 @@ describe('Course Integration', () => {
where: { crn: 67890, courseId: course.id },
});
expect(crnCourseMap).toBeDefined();
const crnCourseMapDeleted = await CourseSectionMappingModel.findOne({
where: { crn: 30303, courseId: course.id },
});
expect(crnCourseMapDeleted).toBeUndefined();
});

it('test crn mapped to another course for a different semester', async () => {
Expand Down Expand Up @@ -916,7 +919,6 @@ describe('Course Integration', () => {
});

const editCourseCrn = {
courseId: potato.id,
crns: [CRN],
};

Expand Down

0 comments on commit cfe4901

Please sign in to comment.