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

General: Add course archive for old courses from previous semesters #9343

Merged
merged 90 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
e2bd263
Implement new course card design
edkaya Jul 22, 2024
75efc71
Add management button to course cards
edkaya Jul 24, 2024
1fafd3f
improve recently accessed course cards
edkaya Jul 25, 2024
de4836b
Clean up code
edkaya Jul 25, 2024
b708c01
Merge branch 'develop' into feature/general/redesign-course-cards
edkaya Jul 28, 2024
486d027
Change background
edkaya Jul 29, 2024
4be0641
Fix pie-chart and next exercise icon alignments
edkaya Aug 2, 2024
d5d7999
Merge branch 'develop' into feature/general/redesign-course-cards
edkaya Aug 6, 2024
daf974f
improve course card paddings
edkaya Aug 8, 2024
cda7f60
Add new design of course cards for bigger score icon
edkaya Aug 15, 2024
12052e1
Merge branch 'develop' into feature/general/redesign-course-cards
edkaya Aug 15, 2024
ac61daf
Remove unused code
edkaya Aug 15, 2024
42359ab
Fix paddings in responsive layout
edkaya Aug 19, 2024
9f05883
improve responsiveness
edkaya Aug 19, 2024
3bdfe2f
Merge branch 'develop' into feature/general/redesign-course-cards
edkaya Aug 20, 2024
f0187f9
Fix guided tour test
edkaya Aug 20, 2024
e4ff8af
Remove animation
edkaya Aug 20, 2024
9cca49e
Implement archive feature
edkaya Aug 21, 2024
a824b28
Integrate feedback
edkaya Aug 21, 2024
8d93712
Remove comment
edkaya Aug 21, 2024
669f103
improve ui, add search/sort functionalities to archive
edkaya Aug 21, 2024
a3cdde1
adjust styling and alignment of course cards
rabeatwork Aug 22, 2024
f7fd16c
Do further enchancements and polishing
edkaya Aug 23, 2024
b8714dd
Integrate feedback
edkaya Aug 23, 2024
5ea6e84
Improve responsiveness
edkaya Aug 23, 2024
6ac0eaa
fix border issue, make exercise title look clickable
edkaya Aug 26, 2024
5f29b8d
improve header
edkaya Aug 26, 2024
0dbb9c3
Resolve merge conflict
edkaya Aug 26, 2024
0924074
Add breakpoints for card sizes, incorporate feedback
edkaya Aug 28, 2024
2e1e626
Incorporate feedback, display more cards instead of shrinking the cards
edkaya Aug 31, 2024
4a37677
Adjust 3 card row layout better
edkaya Aug 31, 2024
937d675
Merge branch 'develop' into feature/general/redesign-course-cards
edkaya Sep 1, 2024
ed00b0a
Merge branch 'develop' into feature/courses-from-previous-semesters
edkaya Sep 1, 2024
c9e2bbb
Merge course cards
edkaya Sep 1, 2024
06074e5
Enhance client ui, create course card header component
edkaya Sep 2, 2024
8c9c2ac
Fix inner border radius
edkaya Sep 2, 2024
203b96e
Display more cards for very large screens
edkaya Sep 2, 2024
42cd8da
Remove empty line
edkaya Sep 2, 2024
9be9933
Use grid layout to omit hardcoded breakpoints
edkaya Sep 3, 2024
59df1ac
Make grid gaps more consistent
edkaya Sep 4, 2024
2cf6930
Reduce min width
edkaya Sep 4, 2024
0bfdf5a
improve layout for smaller screens
edkaya Sep 4, 2024
33fbbf5
Implement feedback
edkaya Sep 8, 2024
25f1274
Merge branch 'develop' into feature/general/redesign-course-cards
edkaya Sep 15, 2024
fef208d
Merge branch 'develop' into feature/courses-from-previous-semesters
edkaya Sep 15, 2024
c3239f9
Enhance code, resolve merge conflicts
edkaya Sep 16, 2024
e2aa6ec
Add empty course message to improve ux
edkaya Sep 16, 2024
9ed164d
Add Old Course Cards
edkaya Sep 19, 2024
915119e
Clean up
edkaya Sep 19, 2024
3aecc59
Add translations for semesters
edkaya Sep 19, 2024
a0824af
Improve spacing
edkaya Sep 19, 2024
f3b352f
use lower case for page title
edkaya Sep 20, 2024
a64a145
Add javadoc comment
edkaya Sep 20, 2024
ad738bb
remove unused code
edkaya Sep 20, 2024
b8bc564
remove console.logs
edkaya Sep 20, 2024
20b50c8
Fix client test
edkaya Sep 20, 2024
8615d03
Bring old state
edkaya Sep 20, 2024
5ed403f
Make new components standalone
edkaya Sep 20, 2024
10ea7df
Add client tests for course archive
edkaya Sep 20, 2024
e45e7b1
Remove comment
edkaya Sep 20, 2024
b255621
Change signature
edkaya Sep 20, 2024
06f2561
Use toList
edkaya Sep 20, 2024
d6f4d30
Merge branch 'develop' into feature/general/see-previous-courses
edkaya Sep 21, 2024
16bf752
integrate rabbit feedback
edkaya Sep 21, 2024
2d54c39
Fix one archive test
edkaya Sep 21, 2024
1884775
Integrate feedback
edkaya Sep 23, 2024
d8de68a
Integrate feedback, add server tests
edkaya Sep 24, 2024
33ff96b
Integrate collapse state, make the semesters unclickable when there i…
edkaya Sep 25, 2024
7c8f749
clean up
edkaya Sep 25, 2024
0267c51
Fix recently accessed courses
edkaya Sep 26, 2024
de095bf
integrate rabbit feedback + update comment
edkaya Sep 26, 2024
bc2161e
Adjust method name
edkaya Sep 26, 2024
794f9d4
fix client test
edkaya Sep 26, 2024
bc81dc1
Merge branch 'develop' into feature/general/see-previous-courses
edkaya Sep 29, 2024
486d8ad
use inject, remove unused imports
edkaya Sep 29, 2024
8d52295
Resolve merge conflicts
edkaya Oct 4, 2024
933df06
Add course archive dto
edkaya Oct 4, 2024
3a7ec30
Remove unused function
edkaya Oct 4, 2024
8c9fadc
adapt client tests to dto
edkaya Oct 4, 2024
a4fe093
Adapt server tests to dto
edkaya Oct 4, 2024
817c873
add javadoc for dto
edkaya Oct 4, 2024
61da73d
fix one client test
edkaya Oct 4, 2024
56bec82
fix
edkaya Oct 4, 2024
59ba4c6
Fix client tests
edkaya Oct 5, 2024
a4b9249
integrate feedback
edkaya Oct 9, 2024
f9e24ea
Resolve merge conflict, integrate feedback
edkaya Oct 11, 2024
4017d0e
change variable name in client tests
edkaya Oct 11, 2024
4251db5
Resolve merge conflict
edkaya Oct 12, 2024
bc137e7
Increase test coverage
edkaya Oct 14, 2024
6d9f82c
increase test coverage
edkaya Oct 14, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package de.tum.cit.aet.artemis.core.dto;

import com.fasterxml.jackson.annotation.JsonInclude;

/**
* DTO for representing archived courses from previous semesters.
*
* @param id The id of the course
* @param title The title of the course
* @param semester The semester in which the course was offered
* @param color The background color of the course
* @param icon The icon of the course
*/
@JsonInclude(JsonInclude.Include.NON_EMPTY)
edkaya marked this conversation as resolved.
Show resolved Hide resolved
public record CourseForArchiveDTO(long id, String title, String semester, String color, String icon) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -534,4 +534,30 @@ SELECT COUNT(c) > 0
""")
boolean hasLearningPathsEnabled(@Param("courseId") long courseId);

/**
* Retrieves all courses that the user has access to based on their role
* or if they are an admin. Filters out any courses that do not belong to
* a specific semester (i.e., have a null semester).
*
* @param userId The id of the user whose courses are being retrieved
* @param isAdmin A boolean flag indicating whether the user is an admin
* @param now The current time to check if the course is still active
* @return A set of courses that the user has access to and belong to a specific semester
*/
@Query("""
SELECT DISTINCT c
FROM Course c
LEFT JOIN UserGroup ug ON ug.group IN (
c.studentGroupName,
c.teachingAssistantGroupName,
c.editorGroupName,
c.instructorGroupName
)
WHERE (:isAdmin = TRUE OR ug.userId = :userId)
AND c.semester IS NOT NULL
AND c.endDate IS NOT NULL
AND c.endDate < :now
""")
edkaya marked this conversation as resolved.
Show resolved Hide resolved
Set<Course> findInactiveCoursesForUserRolesWithNonNullSemester(@Param("userId") long userId, @Param("isAdmin") boolean isAdmin, @Param("now") ZonedDateTime now);

}
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,18 @@ public List<Course> getAllCoursesForManagementOverview(boolean onlyActive) {
return courseRepository.findAllCoursesByManagementGroupNames(userGroups);
}

/**
* Retrieves all inactive courses from non-null semesters that the current user is enrolled in
* for the course archive.
*
* @return A list of courses for the course archive.
*/
public Set<Course> getAllCoursesForCourseArchive() {
var user = userRepository.getUserWithGroupsAndAuthorities();
boolean isAdmin = authCheckService.isAdmin(user);
return courseRepository.findInactiveCoursesForUserRolesWithNonNullSemester(user.getId(), isAdmin, ZonedDateTime.now());
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

/**
* Get the active students for these particular exercise ids
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import de.tum.cit.aet.artemis.core.config.Constants;
import de.tum.cit.aet.artemis.core.domain.Course;
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.core.dto.CourseForArchiveDTO;
import de.tum.cit.aet.artemis.core.dto.CourseForDashboardDTO;
import de.tum.cit.aet.artemis.core.dto.CourseForImportDTO;
import de.tum.cit.aet.artemis.core.dto.CourseManagementDetailViewDTO;
Expand Down Expand Up @@ -555,6 +556,29 @@ public ResponseEntity<List<Course>> getCoursesForManagementOverview(@RequestPara
return ResponseEntity.ok(courseService.getAllCoursesForManagementOverview(onlyActive));
}

/**
* GET /courses/for-archive : get all courses for course archive
*
* @return the ResponseEntity with status 200 (OK) and with body containing
* a set of DTOs, which contain the courses with id, title, semester, color, icon
*/
@GetMapping("courses/for-archive")
@EnforceAtLeastStudent
public ResponseEntity<Set<CourseForArchiveDTO>> getCoursesForArchive() {
long start = System.nanoTime();
krusche marked this conversation as resolved.
Show resolved Hide resolved
User user = userRepository.getUserWithGroupsAndAuthorities();
log.debug("REST request to get all inactive courses from previous semesters user {} has access to", user.getLogin());
Set<Course> courses = courseService.getAllCoursesForCourseArchive();
log.debug("courseService.getAllCoursesForCourseArchive done");

final Set<CourseForArchiveDTO> dto = courses.stream()
.map(course -> new CourseForArchiveDTO(course.getId(), course.getTitle(), course.getSemester(), course.getColor(), course.getCourseIcon()))
.collect(Collectors.toSet());

log.debug("GET /courses/for-archive took {} for {} courses for user {}", TimeLogUtil.formatDurationFrom(start), courses.size(), user.getLogin());
return ResponseEntity.ok(dto);
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

/**
* GET /courses/{courseId}/for-enrollment : get a course by id if the course allows enrollment and is currently active.
*
Expand Down
7 changes: 7 additions & 0 deletions src/main/webapp/app/course/manage/course-for-archive-dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class CourseForArchiveDTO {
id: number;
title: string;
semester: string;
color: string;
icon: string;
edkaya marked this conversation as resolved.
Show resolved Hide resolved
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved
17 changes: 17 additions & 0 deletions src/main/webapp/app/course/manage/course-management.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { ScoresStorageService } from 'app/course/course-scores/scores-storage.se
import { CourseStorageService } from 'app/course/manage/course-storage.service';
import { ExerciseType, ScoresPerExerciseType } from 'app/entities/exercise.model';
import { OnlineCourseDtoModel } from 'app/lti/online-course-dto.model';
import { CourseForArchiveDTO } from './course-for-archive-dto';

export type EntityResponseType = HttpResponse<Course>;
export type EntityArrayResponseType = HttpResponse<Course[]>;
Expand Down Expand Up @@ -343,6 +344,13 @@ export class CourseManagementService {
);
}

/**
* Find all courses for the archive using a GET request
*/
getCoursesForArchive(): Observable<HttpResponse<CourseForArchiveDTO[]>> {
return this.http.get<CourseForArchiveDTO[]>(`${this.resourceUrl}/for-archive`, { observe: 'response' });
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

/**
* returns the exercise details of the courses for the courses' management dashboard
* @param onlyActive - if true, only active courses will be considered in the result
Expand Down Expand Up @@ -703,4 +711,13 @@ export class CourseManagementService {
disableCourseOverviewBackground() {
this.courseOverviewSubject.next(false);
}

getSemesterCollapseStateFromStorage(storageId: string): boolean {
const storedCollapseState: string | null = localStorage.getItem('semester.collapseState.' + storageId);
return storedCollapseState ? JSON.parse(storedCollapseState) : false;
edkaya marked this conversation as resolved.
Show resolved Hide resolved
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

setSemesterCollapseState(storageId: string, isCollapsed: boolean) {
localStorage.setItem('semester.collapseState.' + storageId, JSON.stringify(isCollapsed));
edkaya marked this conversation as resolved.
Show resolved Hide resolved
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
@if (courses) {
<div class="module-bg p-3 rounded-3 mb-3">
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex justify-content-start gap-1">
<h3 class="fw-medium mb-0" jhiTranslate="artemisApp.course.archive.title"></h3>
<fa-icon
[icon]="faQuestionCircle"
[size]="iconSize"
class="text-secondary align-self-center"
ngbTooltip="{{ 'artemisApp.course.archive.tip' | artemisTranslate }}"
edkaya marked this conversation as resolved.
Show resolved Hide resolved
/>
</div>
@if (courses.length) {
<div class="d-flex justify-content-between gap-3 align-items-center ms-1">
<div class="text-primary d-inline-flex">
<a id="sort-test" (click)="onSort()" class="d-inline-flex align-items-center">
<fa-icon id="icon-test-down" [icon]="isSortAscending ? faArrowDown19 : faArrowUp19" />
<span class="ms-1" jhiTranslate="artemisApp.course.archive.sort"></span>
</a>
</div>
<jhi-search-filter (newSearchEvent)="setSearchValue($event)" class="my-0" />
</div>
}
</div>
</div>
<div class="module-bg py-3 rounded-3">
@if (courses.length) {
<div class="mb-0">
@for (semester of semesters; track semester; let last = $last; let i = $index) {
<div
class="d-flex justify-content-between align-items-center px-3"
(click)="isCourseFoundInSemester(semester) && toggleCollapseState(semester)"
tabindex="0"
role="button"
edkaya marked this conversation as resolved.
Show resolved Hide resolved
id="semester-group-{{ i }}"
[ngClass]="{ 'text-secondary': !(coursesBySemester[semester] | searchFilter: ['title'] : searchCourseText).length }"
>
<span class="fw-bold" jhiTranslate="{{ fullFormOfSemesterStrings[semester] }}" [translateValues]="{ param: semester.slice(2) }"></span>
edkaya marked this conversation as resolved.
Show resolved Hide resolved
<fa-icon [icon]="semesterCollapsed[semester] ? faAngleDown : faAngleUp" />
</div>
@if (!semesterCollapsed[semester]) {
<div class="container-fluid mt-2">
<div class="course-grid justify-content-center align-items-center">
@for (course of coursesBySemester[semester] | searchFilter: ['title'] : searchCourseText; track course) {
<div class="course-card-wrapper p-0">
<jhi-course-card-header
class="col-2"
[courseId]="course.id"
[courseTitle]="course.title"
[courseIcon]="course.icon"
[courseColor]="course.color"
[archiveMode]="true"
/>
</div>
}
</div>
</div>
}
@if (!last) {
<hr class="mx-3" />
}
}
</div>
} @else {
<div class="d-flex justify-content-center">
<h4 class="text-secondary text-center mb-0" jhiTranslate="artemisApp.course.archive.noCoursesPreviousSemester"></h4>
</div>
}
</div>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.course-grid {
display: grid;
// cards can shrink to 325px
grid-template-columns: repeat(auto-fill, minmax(325px, 1fr));
grid-gap: 1rem;
justify-items: center;
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

.course-card-wrapper {
width: 100%;
max-width: 400px;
}

.container-fluid {
// ensure that horizontal spacing in container is consistent
--bs-gutter-x: 2rem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Component, OnDestroy, OnInit, inject } from '@angular/core';
import { Course } from 'app/entities/course.model';
import { CourseManagementService } from '../../course/manage/course-management.service';
import { HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { AlertService } from 'app/core/util/alert.service';
import { onError } from 'app/shared/util/global.utils';
import { Subscription } from 'rxjs';
import { faAngleDown, faAngleUp, faArrowDown19, faArrowUp19, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
import { SizeProp } from '@fortawesome/fontawesome-svg-core';
import { ArtemisSharedModule } from 'app/shared/shared.module';
import { CourseCardHeaderComponent } from '../course-card-header/course-card-header.component';
import { CourseForArchiveDTO } from 'app/course/manage/course-for-archive-dto';
import { SearchFilterComponent } from 'app/shared/search-filter/search-filter.component';

edkaya marked this conversation as resolved.
Show resolved Hide resolved
@Component({
selector: 'jhi-course-archive',
templateUrl: './course-archive.component.html',
styleUrls: ['./course-archive.component.scss'],
standalone: true,
imports: [ArtemisSharedModule, CourseCardHeaderComponent, SearchFilterComponent],
})
export class CourseArchiveComponent implements OnInit, OnDestroy {
private archiveCourseSubscription: Subscription;
private courseService = inject(CourseManagementService);
private alertService = inject(AlertService);
edkaya marked this conversation as resolved.
Show resolved Hide resolved

courses: CourseForArchiveDTO[] = [];
semesters: string[];
edkaya marked this conversation as resolved.
Show resolved Hide resolved
fullFormOfSemesterStrings: { [key: string]: string } = {};
semesterCollapsed: { [key: string]: boolean } = {};
coursesBySemester: { [key: string]: Course[] } = {};
edkaya marked this conversation as resolved.
Show resolved Hide resolved
searchCourseText = '';
isSortAscending = true;
iconSize: SizeProp = 'lg';

//Icons
readonly faAngleDown = faAngleDown;
readonly faAngleUp = faAngleUp;
readonly faArrowDown19 = faArrowDown19;
readonly faArrowUp19 = faArrowUp19;
readonly faQuestionCircle = faQuestionCircle;

ngOnInit(): void {
this.loadArchivedCourses();
this.courseService.enableCourseOverviewBackground();
}

/**
* Loads all courses that the student has been enrolled in from previous semesters
*/
loadArchivedCourses(): void {
this.archiveCourseSubscription = this.courseService.getCoursesForArchive().subscribe({
next: (res: HttpResponse<CourseForArchiveDTO[]>) => {
if (res.body) {
this.courses = res.body || [];
this.courses = this.sortCoursesByTitle(this.courses);
this.semesters = this.getUniqueSemesterNamesSorted(this.courses);
this.mapCoursesIntoSemesters();
}
},
error: (error: HttpErrorResponse) => onError(this.alertService, error),
});
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

/**
* maps existing courses to each semester
*/
edkaya marked this conversation as resolved.
Show resolved Hide resolved
edkaya marked this conversation as resolved.
Show resolved Hide resolved
mapCoursesIntoSemesters(): void {
this.semesters.forEach((semester) => {
this.semesterCollapsed[semester] = false;
this.courseService.setSemesterCollapseState(semester, false);
this.coursesBySemester[semester] = this.courses.filter((course) => course.semester === semester);
this.fullFormOfSemesterStrings[semester] = semester.startsWith('WS') ? 'artemisApp.course.archive.winterSemester' : 'artemisApp.course.archive.summerSemester';
});
}

ngOnDestroy(): void {
this.archiveCourseSubscription.unsubscribe();
edkaya marked this conversation as resolved.
Show resolved Hide resolved
this.courseService.disableCourseOverviewBackground();
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

setSearchValue(searchValue: string): void {
this.searchCourseText = searchValue;
if (searchValue !== '') {
this.expandOrCollapseBasedOnSearchValue();
} else {
this.getCollapseStateForSemesters();
}
}

onSort(): void {
if (this.semesters) {
this.semesters.reverse();
this.isSortAscending = !this.isSortAscending;
}
}
/**
* if the searched text is matched with a course title, expand the accordion, otherwise collapse
*/
edkaya marked this conversation as resolved.
Show resolved Hide resolved
edkaya marked this conversation as resolved.
Show resolved Hide resolved
expandOrCollapseBasedOnSearchValue(): void {
for (const semester of this.semesters) {
const hasMatchingCourse = this.coursesBySemester[semester].some((course) => course.title?.toLowerCase().includes(this.searchCourseText.toLowerCase()));
edkaya marked this conversation as resolved.
Show resolved Hide resolved
this.semesterCollapsed[semester] = !hasMatchingCourse;
}
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

getCollapseStateForSemesters(): void {
for (const semester of this.semesters) {
this.semesterCollapsed[semester] = this.courseService.getSemesterCollapseStateFromStorage(semester);
}
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

toggleCollapseState(semester: string): void {
this.semesterCollapsed[semester] = !this.semesterCollapsed[semester];
this.courseService.setSemesterCollapseState(semester, this.semesterCollapsed[semester]);
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

isCourseFoundInSemester(semester: string): boolean {
return this.coursesBySemester[semester].some((course) => course.title?.toLowerCase().includes(this.searchCourseText.toLowerCase()));
}
edkaya marked this conversation as resolved.
Show resolved Hide resolved

sortCoursesByTitle(courses: CourseForArchiveDTO[]): CourseForArchiveDTO[] {
return courses.sort((courseA, courseB) => (courseA.title ?? '').localeCompare(courseB.title ?? ''));
}

getUniqueSemesterNamesSorted(courses: CourseForArchiveDTO[]): string[] {
return (
courses
.map((course) => course.semester ?? '')
// filter down to unique values
.filter((course, index, courses) => courses.indexOf(course) === index)
edkaya marked this conversation as resolved.
Show resolved Hide resolved
.sort((semesterA, semesterB) => {
// Parse years in base 10 by extracting the two digits after the WS or SS prefix
const yearsCompared = parseInt(semesterB.slice(2, 4), 10) - parseInt(semesterA.slice(2, 4), 10);
edkaya marked this conversation as resolved.
Show resolved Hide resolved
if (yearsCompared !== 0) {
return yearsCompared;
}

// If years are the same, sort WS over SS
const prefixA = semesterA.slice(0, 2);
const prefixB = semesterB.slice(0, 2);

if (prefixA === prefixB) {
return 0; // Both semesters are the same (either both WS or both SS)
}

return prefixA === 'WS' ? -1 : 1; // WS should be placed above SS
})
);
}
}
Loading
Loading