diff --git a/backend/api/academics/course.py b/backend/api/academics/course.py
index 73048cac8..164638b6f 100644
--- a/backend/api/academics/course.py
+++ b/backend/api/academics/course.py
@@ -20,13 +20,13 @@
}
-@api.get("", response_model=list[CourseDetails], tags=["Academics"])
-def get_courses(course_service: CourseService = Depends()) -> list[CourseDetails]:
+@api.get("", tags=["Academics"])
+def get_courses(course_service: CourseService = Depends()) -> list[Course]:
"""
Get all courses
Returns:
- list[CourseDetails]: All `Course`s in the `Course` database table
+ list[Course]: All `Course`s in the `Course` database table
"""
return course_service.all()
diff --git a/backend/services/academics/course.py b/backend/services/academics/course.py
index 738908a2b..7a455ea5d 100644
--- a/backend/services/academics/course.py
+++ b/backend/services/academics/course.py
@@ -33,7 +33,7 @@ def __init__(
self._session = session
self._permission_svc = permission_svc
- def all(self) -> list[CourseDetails]:
+ def all(self) -> list[Course]:
"""Retrieves all courses from the table
Returns:
@@ -45,7 +45,7 @@ def all(self) -> list[CourseDetails]:
entities = self._session.scalars(query).all()
# Convert entries to a model and return
- return [entity.to_details_model() for entity in entities]
+ return [entity.to_model() for entity in entities]
def get_by_id(self, id: str) -> CourseDetails:
"""Gets the course from the table for an id.
diff --git a/backend/services/academics/section.py b/backend/services/academics/section.py
index 9c954d8fe..be6b61e9e 100644
--- a/backend/services/academics/section.py
+++ b/backend/services/academics/section.py
@@ -7,7 +7,7 @@
from fastapi import Depends
from sqlalchemy import select
-from sqlalchemy.orm import Session
+from sqlalchemy.orm import Session, joinedload
from pydantic import BaseModel
from ...database import db_session
@@ -62,8 +62,15 @@ def get_by_term(self, term_id: str) -> list[CatalogSection]:
select(SectionEntity)
.where(SectionEntity.term_id == term_id)
.order_by(SectionEntity.course_id, SectionEntity.number)
+ .options(
+ joinedload(SectionEntity.members).joinedload(SectionMemberEntity.user),
+ joinedload(SectionEntity.lecture_rooms).joinedload(
+ SectionRoomEntity.room
+ ),
+ joinedload(SectionEntity.course),
+ )
)
- entities = self._session.scalars(query).all()
+ entities = self._session.scalars(query).unique().all()
# Return the model
return [entity.to_catalog_model() for entity in entities]
@@ -276,7 +283,7 @@ def update_enrollment_totals(self, subject: User):
# Currently active terms.
# This is hard-coded based on the availability and representation
# of course enrollment data from UNC's course database.
- AVAILABLE_TERMS = {"2024+Summer+II": "SuII24", "2024+Fall": "F24"}
+ AVAILABLE_TERMS = {"2024+Summer+II": "24SSII", "2024+Fall": "24F"}
# Store updates to make.
updates: dict[tuple[str, str], SectionEnrollmentData] = {}
diff --git a/backend/test/services/academics/course_test.py b/backend/test/services/academics/course_test.py
index 53b778a88..4ee35df51 100644
--- a/backend/test/services/academics/course_test.py
+++ b/backend/test/services/academics/course_test.py
@@ -8,7 +8,7 @@
)
from backend.services.permission import PermissionService
from ....services.academics import CourseService
-from ....models.academics import CourseDetails
+from ....models.academics import Course, CourseDetails
# Imported fixtures provide dependencies injected for the tests as parameters.
from .fixtures import permission_svc, course_svc
@@ -29,7 +29,7 @@ def test_all(course_svc: CourseService):
courses = course_svc.all()
assert len(courses) == len(course_data.courses)
- assert isinstance(courses[0], CourseDetails)
+ assert isinstance(courses[0], Course)
def test_get_by_id(course_svc: CourseService):
diff --git a/frontend/src/app/academics/academics-admin/section/admin-section.component.css b/frontend/src/app/academics/academics-admin/section/admin-section.component.css
index 0ac5784ce..f457bc3b1 100644
--- a/frontend/src/app/academics/academics-admin/section/admin-section.component.css
+++ b/frontend/src/app/academics/academics-admin/section/admin-section.component.css
@@ -11,7 +11,8 @@
.header {
display: flex;
align-items: center;
- justify-content: space-between;
+ padding-top: 16px;
+ padding-bottom: 16px;
}
.row {
@@ -29,3 +30,17 @@
#edit-button {
margin-right: 8px;
}
+
+.right-header-container {
+ display: flex;
+ flex-direction: row;
+ margin-left: auto;
+ gap: 12px;
+ align-items: center;
+}
+
+.term-selector {
+ width: 260px;
+ margin-bottom: -1.25em;
+ }
+
\ No newline at end of file
diff --git a/frontend/src/app/academics/academics-admin/section/admin-section.component.html b/frontend/src/app/academics/academics-admin/section/admin-section.component.html
index ab59c7dba..bb323ee77 100644
--- a/frontend/src/app/academics/academics-admin/section/admin-section.component.html
+++ b/frontend/src/app/academics/academics-admin/section/admin-section.component.html
@@ -1,20 +1,23 @@
-
+
|
diff --git a/frontend/src/app/academics/academics-admin/section/admin-section.component.ts b/frontend/src/app/academics/academics-admin/section/admin-section.component.ts
index cefa344df..875f00550 100644
--- a/frontend/src/app/academics/academics-admin/section/admin-section.component.ts
+++ b/frontend/src/app/academics/academics-admin/section/admin-section.component.ts
@@ -44,11 +44,13 @@ export class AdminSectionComponent {
public sections: WritableSignal = signal([]);
/** Store list of Terms */
- public terms: RxTermList = new RxTermList();
- public terms$: Observable = this.terms.value$;
+ public terms: Term[];
/** Store the currently selected term from the form */
- public displayTerm: FormControl = new FormControl();
+ // NOTE: Separating these fields into an ID and a selected term was required
+ // for Angular to correctly show the correct term in the initial drop down.
+ public displayTermId: string | null;
+ public displayTerm: WritableSignal;
public displayedColumns: string[] = ['name'];
@@ -64,17 +66,13 @@ export class AdminSectionComponent {
currentTerm: Term | undefined;
};
- this.terms.set(data.terms);
+ this.terms = data.terms;
- if (data.currentTerm) {
- this.displayTerm.setValue(data.currentTerm);
-
- this.academicsService
- .getSectionsByTerm(data.currentTerm!)
- .subscribe((sections) => {
- this.sections.set(sections);
- });
- }
+ // Set initial display term
+ this.displayTermId = data.currentTerm?.id ?? null;
+ this.displayTerm = signal(this.selectedTerm());
+ // Initialize the sections list
+ this.resetSections();
}
/** Event handler to open the Section Editor to create a new term */
@@ -104,15 +102,29 @@ export class AdminSectionComponent {
);
confirmDelete.onAction().subscribe(() => {
this.academicsService.deleteSection(section).subscribe(() => {
- let termToUpdate = this.displayTerm.value;
this.sections.update((sections) =>
sections.filter((s) => s.id !== section.id)
);
- this.terms.updateTerm(termToUpdate);
this.snackBar.open('This Section has been deleted.', '', {
duration: 2000
});
});
});
}
+
+ selectedTerm() {
+ return this.terms.find((term) => term.id == this.displayTermId);
+ }
+
+ /** Resets the section data based on the selected term. */
+ resetSections() {
+ this.displayTerm.set(this.selectedTerm());
+ if (this.displayTerm()) {
+ this.academicsService
+ .getSectionsByTerm(this.displayTerm()!)
+ .subscribe((sections) => {
+ this.sections.set(sections);
+ });
+ }
+ }
}
diff --git a/frontend/src/app/academics/academics.module.ts b/frontend/src/app/academics/academics.module.ts
index 612e8b580..8c95cbda4 100644
--- a/frontend/src/app/academics/academics.module.ts
+++ b/frontend/src/app/academics/academics.module.ts
@@ -11,7 +11,7 @@ import { AcademicsRoutingModule } from './academics-routing.module';
import { MatIconModule } from '@angular/material/icon';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatSelectModule } from '@angular/material/select';
-import { ReactiveFormsModule } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AcademicsHomeComponent } from './academics-home/academics-home.component';
import { AcademicsAdminComponent } from './academics-admin/academics-admin.component';
import { MatTabsModule } from '@angular/material/tabs';
@@ -52,6 +52,7 @@ import { MatSlideToggleModule } from '@angular/material/slide-toggle';
MatTableModule,
MatIconModule,
MatFormFieldModule,
+ FormsModule,
MatSelectModule,
ReactiveFormsModule,
MatTabsModule,
|