diff --git a/docs/API.md b/docs/API.md
index d668c73..a314df7 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -30,8 +30,15 @@ curl "https://api.brocktimetable.com/api/getNameList?timetableType=UG&session=FW
#### Example Response
-```
-[ABED 4F84 D2", "ABED 4F85 D3", "ABTE 8P85 D3", "ABTE 8P90 D3", ...]
+```json
+[
+ {
+ "label": "COSC 1P02 D2",
+ "courseCode": "COSC1P02",
+ "duration": "2",
+ "courseName": "Introduction to Computer Science"
+ }
+]
```
### 2. Get Course Data
diff --git a/src/components/generator/Calendar/CalendarComponent.jsx b/src/components/generator/Calendar/CalendarComponent.jsx
index 415b4ee..a6c63ca 100644
--- a/src/components/generator/Calendar/CalendarComponent.jsx
+++ b/src/components/generator/Calendar/CalendarComponent.jsx
@@ -179,18 +179,25 @@ export default function CalendarComponent({
currentColors,
);
- const courseDetails = newEvents
- .filter((event) => event.description)
- .map((event) => {
- let titleArray = event.title.trim().split(" ");
- return {
- name: titleArray[0],
- instructor: event.description,
- section: titleArray.pop(),
- startDate: event.startRecur,
- endDate: event.endRecur,
- };
- });
+ const courseDetails = Array.from(
+ new Map(
+ newEvents
+ .filter((event) => !event.extendedProps.isBlocked)
+ .map((event) => {
+ let titleArray = event.title.trim().split(" ");
+ const detail = {
+ name: titleArray[0],
+ courseName: event.extendedProps.courseName || "",
+ instructor: event.description,
+ section: titleArray.pop(),
+ startDate: event.startRecur,
+ endDate: event.endRecur,
+ };
+
+ return [detail.name, detail];
+ }),
+ ).values(),
+ );
setCourseDetails(courseDetails);
setEvents(newEvents);
diff --git a/src/components/generator/Calendar/hooks/useCalendarEvents.js b/src/components/generator/Calendar/hooks/useCalendarEvents.js
index 05984fe..80c1ffb 100644
--- a/src/components/generator/Calendar/hooks/useCalendarEvents.js
+++ b/src/components/generator/Calendar/hooks/useCalendarEvents.js
@@ -71,18 +71,25 @@ export const useCalendarEvents = ({
currentColors,
);
- const courseDetails = newEvents
- .filter((event) => event.description)
- .map((event) => {
- let titleArray = event.title.trim().split(" ");
- return {
- name: titleArray[0],
- instructor: event.description,
- section: titleArray.pop(),
- startDate: event.startRecur,
- endDate: event.endRecur,
- };
- });
+ const courseDetails = Array.from(
+ new Map(
+ newEvents
+ .filter((event) => !event.extendedProps.isBlocked)
+ .map((event) => {
+ let titleArray = event.title.trim().split(" ");
+ const detail = {
+ name: titleArray[0],
+ courseName: event.extendedProps.courseName || "",
+ instructor: event.description,
+ section: titleArray.pop(),
+ startDate: event.startRecur,
+ endDate: event.endRecur,
+ };
+
+ return [detail.name, detail];
+ }),
+ ).values(),
+ );
setCourseDetails(courseDetails);
setEvents(newEvents);
diff --git a/src/components/generator/Calendar/utils/calendarUtils.jsx b/src/components/generator/Calendar/utils/calendarUtils.jsx
index 77225cd..2b1c8bb 100644
--- a/src/components/generator/Calendar/utils/calendarUtils.jsx
+++ b/src/components/generator/Calendar/utils/calendarUtils.jsx
@@ -56,7 +56,9 @@ export const renderEventContent = (eventInfo, isMobile = false) => {
);
} else {
const isPinned = isEventPinned(eventInfo.event);
+ const courseNameText = eventInfo.event.extendedProps.courseName?.trim();
const instructorText = eventInfo.event.extendedProps.description?.trim();
+ const shouldShowCourseName = Boolean(courseNameText);
const shouldShowInstructor =
!!instructorText && instructorText.toLowerCase() !== "no instructor";
@@ -89,27 +91,39 @@ export const renderEventContent = (eventInfo, isMobile = false) => {
)}
- {eventInfo.timeText}
-
- {eventInfo.event.title}
+ {eventInfo.timeText}
+ {eventInfo.event.title}
+ {(isMobile || eventDuration >= minDurationToShowTime) &&
+ shouldShowCourseName && (
+
+ {courseNameText}
+
+ )}
{(isMobile || eventDuration >= minDurationToShowTime) &&
shouldShowInstructor && (
- <>
-
-
- {instructorText}
-
- >
+
+ {instructorText}
+
)}
diff --git a/src/components/generator/Forms/CourseList/CourseListItemComponent.jsx b/src/components/generator/Forms/CourseList/CourseListItemComponent.jsx
index 8fd1fa1..db6a271 100644
--- a/src/components/generator/Forms/CourseList/CourseListItemComponent.jsx
+++ b/src/components/generator/Forms/CourseList/CourseListItemComponent.jsx
@@ -78,9 +78,16 @@ export default function CourseListComponent({
-
- {course}
-
+
+
+ {course}
+
+ {courseDetail?.courseName && (
+
+ {courseDetail.courseName}
+
+ )}
+
+
+ Course Name
+
+ {courseDetail?.courseName || "N/A"}
+
+
Course Instructor
diff --git a/src/components/generator/Forms/CourseSearch/CourseSearchComponent.jsx b/src/components/generator/Forms/CourseSearch/CourseSearchComponent.jsx
index da5ca8c..858f80c 100644
--- a/src/components/generator/Forms/CourseSearch/CourseSearchComponent.jsx
+++ b/src/components/generator/Forms/CourseSearch/CourseSearchComponent.jsx
@@ -30,6 +30,33 @@ export default function CourseSearchComponent({
const listRef = React.useRef(null);
const PAGE_SIZE = 200;
+ const normalizedOptions = React.useMemo(() => {
+ return courseOptions.map((option) => {
+ if (typeof option === "string") {
+ return {
+ label: option,
+ courseCode: option.replace(/\s+D\d+$/i, "").replace(/\s+/g, ""),
+ duration: option.match(/D(\d+)$/i)?.[1] || "",
+ courseName: "",
+ };
+ }
+
+ const label =
+ option.label ||
+ option.value ||
+ `${option.courseCode?.slice(0, 4) || ""} ${
+ option.courseCode?.slice(4) || ""
+ }${option.duration ? ` D${option.duration}` : ""}`.trim();
+
+ return {
+ label,
+ courseCode: option.courseCode || "",
+ duration: option.duration || "",
+ courseName: option.courseName || "",
+ };
+ });
+ }, [courseOptions]);
+
React.useEffect(() => {
setDisplayLimit(PAGE_SIZE);
}, [value]);
@@ -51,6 +78,10 @@ export default function CourseSearchComponent({
const selectedItem = listEl?.querySelector('[data-selected="true"]');
if (selectedItem) return;
event.preventDefault();
+ if (filteredOptions.length > 0) {
+ handleSelectOption(filteredOptions[0]);
+ return;
+ }
onEnterPress(value);
setOpen(false);
};
@@ -58,9 +89,17 @@ export default function CourseSearchComponent({
const filteredOptions = React.useMemo(() => {
const query = (value || "").trim().toUpperCase();
const results = [];
- for (let i = 0; i < courseOptions.length; i += 1) {
- const option = courseOptions[i];
- if (!query || option.toUpperCase().startsWith(query)) {
+ for (let i = 0; i < normalizedOptions.length; i += 1) {
+ const option = normalizedOptions[i];
+ const label = option.label.toUpperCase();
+ const courseName = option.courseName.toUpperCase();
+ const matchesQuery =
+ !query ||
+ label.startsWith(query) ||
+ label.includes(query) ||
+ courseName.includes(query);
+
+ if (matchesQuery) {
results.push(option);
if (results.length >= displayLimit) {
break;
@@ -68,13 +107,20 @@ export default function CourseSearchComponent({
}
}
return results;
- }, [courseOptions, value, displayLimit]);
+ }, [normalizedOptions, value, displayLimit]);
+
+ const handleSelectOption = (option) => {
+ setValue(option.label);
+ onCourseCodeChange(null, option.label);
+ setOpen(false);
+ onEnterPress(option.label);
+ };
const groupedOptions = React.useMemo(() => {
const groups = new Map();
for (let i = 0; i < filteredOptions.length; i += 1) {
const option = filteredOptions[i];
- const key = option.slice(0, 4).toUpperCase();
+ const key = option.label.slice(0, 4).toUpperCase();
if (!groups.has(key)) {
groups.set(key, []);
}
@@ -94,6 +140,7 @@ export default function CourseSearchComponent({
variant="outline"
role="combobox"
aria-expanded={open}
+ aria-controls="course-search-list"
className="w-full justify-between transition-none"
>
{value ? value : "Add a course"}
@@ -112,28 +159,36 @@ export default function CourseSearchComponent({
}}
onKeyDown={handleInputKeyDown}
/>
-
+
No course found.
{groupedOptions.map((group) => (
{group.items.map((option) => (
{
- setValue(currentValue);
- onCourseCodeChange(null, currentValue);
- setOpen(false);
- onEnterPress(currentValue);
+ key={option.label}
+ value={`${option.label} ${option.courseName}`.trim()}
+ onSelect={() => {
+ handleSelectOption(option);
}}
>
- {option}
+
+
{option.label}
+ {option.courseName && (
+
+ {option.courseName}
+
+ )}
+
))}
diff --git a/src/lib/generator/courseData.js b/src/lib/generator/courseData.js
index 3a8eb64..f2d173f 100644
--- a/src/lib/generator/courseData.js
+++ b/src/lib/generator/courseData.js
@@ -33,11 +33,13 @@ const initializeCourseData = (courseCode) => {
};
export const storeCourseData = (course) => {
- const { courseCode, sections, labs, tutorials, seminars } = course;
+ const { courseCode, courseName, sections, labs, tutorials, seminars } =
+ course;
initializeCourseData(courseCode);
courseData[courseCode].courseCode = courseCode;
+ courseData[courseCode].courseName = courseName || "";
courseData[courseCode].sections.push(...sections);
replaceSectionId(courseData[courseCode].sections);
courseData[courseCode].labs.push(...labs);
diff --git a/src/lib/generator/createCalendarEvents.js b/src/lib/generator/createCalendarEvents.js
index 8f1cdbc..11116fd 100644
--- a/src/lib/generator/createCalendarEvents.js
+++ b/src/lib/generator/createCalendarEvents.js
@@ -44,6 +44,7 @@ export const createCalendarEvents = (
description: component.instructor,
color: customColor,
extendedProps: {
+ courseName: course.courseName || "",
isPinned: component.pinned,
isMain: component.isMain ?? false,
},
diff --git a/src/lib/generator/timetableGeneration/utils/combinationUtils.js b/src/lib/generator/timetableGeneration/utils/combinationUtils.js
index 9cbd419..f66cf5e 100644
--- a/src/lib/generator/timetableGeneration/utils/combinationUtils.js
+++ b/src/lib/generator/timetableGeneration/utils/combinationUtils.js
@@ -167,10 +167,18 @@ export const generateSingleCourseCombinations = (course, timeSlots) => {
]);
combinations.forEach(([lab, tutorial, seminar]) => {
- singleCourseCombinations.push({
+ const courseCombination = {
courseCode: course.courseCode,
mainComponents: mainComponentGroup,
secondaryComponents: { lab, tutorial, seminar },
+ };
+
+ if (course.courseName) {
+ courseCombination.courseName = course.courseName;
+ }
+
+ singleCourseCombinations.push({
+ ...courseCombination,
});
});
});