From 3323d43073663fbaccab25735860ee6a1fa8a61e Mon Sep 17 00:00:00 2001
From: Scott Berg <scott.berg@wisc.edu>
Date: Mon, 18 Feb 2019 22:18:39 +0000
Subject: [PATCH] Search improvements

---
 .../degree-planner.component.html             |  96 ++++++++-----
 .../degree-planner.component.ts               |  79 ++++++++++-
 .../degree-planner/services/api.service.ts    |  27 +---
 .../course-item/course-item.component.html    |   5 +-
 .../course-item/course-item.component.ts      | 134 ++++++++++++++++--
 .../store/effects/plan.effects.ts             |  50 ++++---
 src/app/degree-planner/store/selectors.ts     |  14 ++
 src/app/degree-planner/store/state.ts         |   2 +
 8 files changed, 309 insertions(+), 98 deletions(-)

diff --git a/src/app/degree-planner/degree-planner.component.html b/src/app/degree-planner/degree-planner.component.html
index 6072075..4ecd026 100644
--- a/src/app/degree-planner/degree-planner.component.html
+++ b/src/app/degree-planner/degree-planner.component.html
@@ -1,40 +1,68 @@
 <mat-sidenav-container hasBackdrop="false" *ngIf="(degreePlan$ | async) as degreePlan">
-  <mat-sidenav position="end" mode="over" [opened]="isCourseSearchVisible$ | async">
+  <mat-sidenav #addMenu position="end" mode="over" [opened]="isCourseSearchOpen$ | async">
     <mat-toolbar color="primary" class="dialog-toolbar">
       <span class="dialog-toolbar-title">Course Search</span>
-      <button mat-button class="close-btn" (click)="closeCourseSearch();"><i class="material-icons">clear</i></button>
+      <button mat-button class="close-btn" (click)="closeCourseSearch();"><i class="material-icons">keyboard_arrow_right</i></button>
     </mat-toolbar>
 
-    <mat-sidenav #addMenu position="end" mode="over" [opened]="isCourseSearchOpen$ | async">
-      <mat-toolbar color="primary" class="dialog-toolbar">
-        <span class="dialog-toolbar-title">Course Search</span>
-        <button mat-button class="close-btn" (click)="closeCourseSearch();"><i class="material-icons">keyboard_arrow_right</i></button>
-      </mat-toolbar>
-
-      <form [formGroup]='courseSearchForm' (ngSubmit)="search()" class="add-course-form" fxLayout="column" fxLayoutAlign="space-around none" style="padding: 12px 22px;">
-        <mat-form-field>
-          <mat-select placeholder="Term" formControlName="term">
-            <mat-option value="0000" selected>All</mat-option>
-            <mat-option *ngFor="let term of activeTerms$ | async" [value]="term">{{term | getTermDescription}}</mat-option>
-          </mat-select>
-        </mat-form-field>
-
-        <mat-form-field>
-          <mat-select placeholder="Subject" formControlName="subject" required>
-            <mat-option value="all">All</mat-option>
-            <mat-option *ngFor="let subject of (subjects$ | async) | keyvalue" [value]="subject.key">
-              {{subject.value}}
-            </mat-option>
-          </mat-select>
-        </mat-form-field>
-
-        <mat-form-field>
-          <div class="search-input-wrapper">
-            <input id="keyword-field" matInput placeholder="Keyword, number" formControlName="search" value="">
-            <button id="search-button" mat-icon-button aria-label="Search" matSuffix style="margin-top: -10px;"><i class="material-icons">search</i></button>
+    <form [formGroup]='courseSearchForm' (ngSubmit)="search()" class="add-course-form" fxLayout="column" fxLayoutAlign="space-around none" style="padding: 12px 22px;">
+
+      <mat-form-field>
+        <mat-select placeholder="Term" aria-label="Term" formControlName="term">
+          <mat-option value="0000">All</mat-option>
+          <mat-option *ngFor="let yearCode of activeTerms$ | async" [value]="yearCode">{{yearCode | getTermDescription}}</mat-option>
+        </mat-select>
+      </mat-form-field>
+
+      <mat-form-field class="example-full-width">
+        <input type="text" placeholder="Subject" aria-label="Subject" matInput formControlName="subject" [matAutocomplete]="subject">
+        <mat-autocomplete autoActiveFirstOption #subject="matAutocomplete">
+          <mat-option *ngFor="let subject of filteredSubjects | keyvalue" [value]="subject.value">{{subject.value}}</mat-option>
+        </mat-autocomplete>
+      </mat-form-field>
+
+      <mat-form-field>
+        <div class="search-input-wrapper">
+          <input id="keyword-field" matInput placeholder="Keyword, number" formControlName="search" value="">
+          <button id="search-button" mat-icon-button aria-label="Search" matSuffix style="margin-top: -10px;"><i class="material-icons">search</i></button>
+        </div>
+      </mat-form-field>
+
+    </form>
+  <div *ngIf="hasResults || isLoading">
+    <div class="search-results-toolbar mat-typography" fxLayout="row" fxLayoutAlign="space-between center" style="padding: 12px 22px; background-color: #EDF1F3; min-height: 60px;">
+      <h3 *ngIf="isLoading" style="margin: 0px;">Searching for courses...</h3>
+      <h3 *ngIf="hasResults" style="margin: 0px;">{{queriedCourses.length}} result(s)</h3>
+      <button *ngIf="hasResults" mat-button (click)="resetSearch()">Reset Search</button>
+    </div>
+    <mat-progress-bar mode="indeterminate" *ngIf="isLoading"></mat-progress-bar>
+
+    <div id="course-search-results" fxLayout="column" fxLayoutAlign="space-around none" style="margin-top: 20px; padding: 12px 22px;">
+      <div *ngIf="hasResults && queriedCourses.length === 0" class="mat-typography" style="text-align: center;">
+        <img  style="width: 50%;" src="../../assets/img/bucky-sad.svg" alt="No results found">
+        <h3>No search results found.</h3>
+      </div>
+
+      <div
+        cdkDropList
+        id="queried-courses-list"
+        [cdkDropListData]="queriedCourses"
+        [cdkDropListConnectedTo]="dropZoneIds$ | async"
+      >
+        <div
+          class="course-wrapper"
+          cdkDrag
+          [cdkDragData]="course"
+          *ngFor="let course of queriedCourses"
+        >
+          <div class="course-wrapper-inner">
+            <cse-course-item [course]="course" type="search"></cse-course-item>
           </div>
-        </mat-form-field>
-      </form>
+        </div>
+      </div>
+    </div>
+    </div>
+  </mat-sidenav>
 
       <div *ngIf="hasResults || isLoading">
         <div class="search-results-toolbar mat-typography" fxLayout="row" fxLayoutAlign="space-between center" style="padding: 12px 22px; background-color: #EDF1F3; min-height: 60px;">
@@ -49,7 +77,7 @@
             cdkDropList
             id="queried-courses-list"
             [cdkDropListData]="queriedCourses"
-            [cdkDropListConnectedTo]="dropZones$ | async">
+            [cdkDropListConnectedTo]="getTermDropZone()">
             <div
               class="course-wrapper"
               cdkDrag
@@ -62,13 +90,12 @@
           </div>
         </div>
       </div>
-    </mat-sidenav>
 
     <div
       cdkDropList
       id="queried-courses-list"
       [cdkDropListData]="queriedCourses"
-      [cdkDropListConnectedTo]="dropZones$ | async">
+      [cdkDropListConnectedTo]="dropZoneIds$ | async">
       <div
         class="course-wrapper"
         cdkDrag
@@ -79,7 +106,6 @@
         </div>
       </div>
     </div>
-  </mat-sidenav>
 
   <mat-sidenav-content>
     <mat-sidenav-container id="plans-container">
diff --git a/src/app/degree-planner/degree-planner.component.ts b/src/app/degree-planner/degree-planner.component.ts
index 7844c3b..5f4f04e 100644
--- a/src/app/degree-planner/degree-planner.component.ts
+++ b/src/app/degree-planner/degree-planner.component.ts
@@ -4,7 +4,7 @@ import {
   withLatestFrom,
   distinctUntilChanged,
 } from 'rxjs/operators';
-import { OnInit } from '@angular/core';
+import { OnInit, ViewChild } from '@angular/core';
 import { Observable } from 'rxjs';
 import { select } from '@ngrx/store';
 import { Component } from '@angular/core';
@@ -16,11 +16,11 @@ import { MatSnackBar } from '@angular/material';
 import { GlobalState } from '@app/core/state';
 import { DegreePlan } from '@app/core/models/degree-plan';
 import { Year } from '@app/core/models/year';
-import { Course } from '@app/core/models/course';
 import { FormBuilder, FormGroup } from '@angular/forms';
 import { DegreePlannerApiService } from '@app/degree-planner/services/api.service';
 import * as selectors from '@app/degree-planner/store/selectors';
 import * as utils from '@app/degree-planner/shared/utils';
+import { Course, SubjectMapping } from '@app/core/models/course';
 
 // Actions
 import {
@@ -59,6 +59,7 @@ export class DegreePlannerComponent implements OnInit {
   public subjects$: Observable<object>;
   public activeTerms$: Observable<string[]>;
   public yearCodes$: Observable<string[]>;
+  public dropZoneIds$: Observable<string[]>;
 
   // Search variables
   public queriedCourses: Course[];
@@ -67,6 +68,9 @@ export class DegreePlannerComponent implements OnInit {
   public courseSearchForm: FormGroup;
   public isCourseSearchVisible$: Observable<boolean>;
 
+  public subjects: SubjectMapping;
+  public filteredSubjects: SubjectMapping;
+
   constructor(
     private store: Store<GlobalState>,
     public mediaMatcher: MediaMatcher,
@@ -107,13 +111,57 @@ export class DegreePlannerComponent implements OnInit {
       distinctUntilChanged(utils.compareStringArrays),
     );
 
+    this.store
+      .pipe(select(selectors.getSubjectDescriptions))
+      .subscribe(subjects => {
+        this.subjects = { [-1]: 'All', ...subjects };
+        this.filteredSubjects = { [-1]: 'All', ...subjects };
+      });
+
+    this.dropZoneIds$ = this.store.pipe(
+      select(selectors.selectAllVisibleYears),
+      utils.yearsToDropZoneIds(),
+      distinctUntilChanged(utils.compareStringArrays),
+    );
+
     // Deafults for the search form
     this.courseSearchForm = this.fb.group({
       term: '0000',
-      subject: 'all',
+      subject: 'All',
       search: '',
-      coursesInput: null,
     });
+
+    this.courseSearchForm.valueChanges.subscribe(values => {
+      if (values.subject === '') {
+        this.filteredSubjects = this.subjects;
+        return;
+      }
+
+      const filtered = {};
+
+      // Filter the terms based on users search
+      Object.entries(this.subjects).map(subject => {
+        const [key, name] = subject;
+
+        const search = name.replace(/\s/g, '');
+
+        if (search.toLowerCase().indexOf(values.subject.toLowerCase()) === 0) {
+          filtered[key] = name;
+        }
+      });
+
+      this.filteredSubjects = filtered;
+    });
+
+    this.isCourseSearchOpen$ = this.store.pipe(
+      select(selectors.isCourseSearchOpen),
+    );
+  }
+
+  public handleAcademicYearToggle(year: Year): void {
+    // this.store.dispatch(
+    // new ToggleAcademicYear({ year: year.twoDigitYearCode.toString() }),
+    // );
   }
 
   public handleDegreePlanChange(event: MatSelectChange): void {
@@ -235,6 +283,24 @@ export class DegreePlannerComponent implements OnInit {
   public search() {
     // Get the form field values
     const { search, term, subject } = this.courseSearchForm.value;
+    let subjectCode;
+
+    // Check if subject is valid
+    Object.entries(this.subjects).forEach(option => {
+      const [key, value] = option;
+      if (value === subject) {
+        subjectCode = key;
+      }
+    });
+
+    if (!subjectCode) {
+      this.snackBar.open('Please select a valid subject', undefined, {
+        duration: 6000,
+      });
+      return;
+    }
+
+    console.log(search, term, subject, subjectCode);
 
     // Set the internal UI state
     this.isLoading = true;
@@ -244,7 +310,7 @@ export class DegreePlannerComponent implements OnInit {
     // Hit the search API
     this.api
       .searchCourses({
-        subjectCode: subject,
+        subjectCode,
         searchText: search,
         termCode: term === '' ? '0000' : term,
       })
@@ -273,8 +339,9 @@ export class DegreePlannerComponent implements OnInit {
     this.queriedCourses = [];
     this.hasResults = false;
     this.courseSearchForm.setValue({
-      subject: 'all',
+      subject: 'All',
       search: '',
+      term: '0000',
     });
   }
 }
diff --git a/src/app/degree-planner/services/api.service.ts b/src/app/degree-planner/services/api.service.ts
index 63a681a..2dbf41b 100644
--- a/src/app/degree-planner/services/api.service.ts
+++ b/src/app/degree-planner/services/api.service.ts
@@ -57,6 +57,10 @@ export class DegreePlannerApiService {
     );
   }
 
+  public getAllSubjectDescriptions(): Observable<{}> {
+    return this.http.get(this.searchEndpoint('subjects'));
+  }
+
   public getActiveTerms(): Observable<Term[]> {
     return this.http.get<Term[]>(this.searchEndpoint('terms'));
   }
@@ -111,27 +115,6 @@ export class DegreePlannerApiService {
     searchText?: string;
     termCode?: string;
   }): Observable<any> {
-    // const data = {
-    //   filters: [
-    //     { term: { 'subject.subjectCode': '266' } },
-    //     {
-    //       has_child: {
-    //         type: 'enrollmentPackage',
-    //         query: {
-    //           match: {
-    //             'packageEnrollmentStatus.status': 'OPEN WAITLISTED CLOSED',
-    //           },
-    //         },
-    //       },
-    //     },
-    //   ],
-    //   page: 1,
-    //   pageSize: 500,
-    //   queryString: 'programing',
-    //   selectedTerm: '1194',
-    //   sortOrder: 'SCORE',
-    // };
-
     const { subjectCode, termCode, searchText } = config;
 
     const payload: any = {
@@ -148,7 +131,7 @@ export class DegreePlannerApiService {
     };
 
     // If we have a specific subject code, add a fitler for it
-    if (subjectCode !== 'all') {
+    if (subjectCode !== '-1') {
       payload.filters.push({ term: { 'subject.subjectCode': subjectCode } });
     }
 
diff --git a/src/app/degree-planner/shared/course-item/course-item.component.html b/src/app/degree-planner/shared/course-item/course-item.component.html
index d854691..ba27d1b 100644
--- a/src/app/degree-planner/shared/course-item/course-item.component.html
+++ b/src/app/degree-planner/shared/course-item/course-item.component.html
@@ -28,9 +28,10 @@
           <button mat-menu-item [matMenuTriggerFor]="academicYearsGroup">Move</button>
           <mat-menu #academicYearsGroup="matMenu" class="course-item-submenu">
             <button mat-menu-item (click)="moveToSavedForLater(course)" *ngIf="type != 'saved'" class="saved-for-later-list">Saved for later</button>
-            <button mat-menu-item *ngFor="let term of (droppableTermCodes$ | async)" (click)="(type == 'saved') ? addToTerm(course, term) : switchTerm(course, term)">{{ term | getTermDescription }}</button>
+            <button mat-menu-item *ngFor="let term of (droppableTermCodes$ | async)" (click)="onMove(term)">{{ term | getTermDescription }}</button>
           </mat-menu>
-          <button mat-menu-item (click)="openRemoveConfirmationDialog()">Remove</button>
+          <button mat-menu-item *ngIf="type !== 'saved'" (click)="onSaveForLater()">Save for later</button>
+          <button mat-menu-item *ngIf="type !== 'search'" (click)="onRemove()">Remove</button>
         </mat-menu>
       </div>
       <div *ngIf="disabled" fxLayout="row" fxLayoutAlign="end center">
diff --git a/src/app/degree-planner/shared/course-item/course-item.component.ts b/src/app/degree-planner/shared/course-item/course-item.component.ts
index e586ae4..cdad1ab 100644
--- a/src/app/degree-planner/shared/course-item/course-item.component.ts
+++ b/src/app/degree-planner/shared/course-item/course-item.component.ts
@@ -8,13 +8,15 @@ import {
   AddCourse,
   AddSaveForLater,
   MoveCourseBetweenTerms,
+  RemoveSaveForLater,
+  RemoveCourse,
 } from './../../store/actions/course.actions';
 import { GlobalState } from '@app/core/state';
 import { Course } from '@app/core/models/course';
 import * as selectors from '@app/degree-planner/store/selectors';
 
 import { DegreePlannerApiService } from '@app/degree-planner/services/api.service';
-// tslint:disable-next-line:max-line-length
+import { ConfirmDialogComponent } from '@app/shared/dialogs/confirm-dialog/confirm-dialog.component';
 import { CourseDetailsDialogComponent } from '@app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component';
 // tslint:disable-next-line:max-line-length
 import { RemoveCourseConfirmDialogComponent } from '@app/degree-planner/dialogs/remove-course-confirm-dialog/remove-course-confirm-dialog.component';
@@ -79,18 +81,130 @@ export class CourseItemComponent implements OnInit {
     );
   }
 
+  /**
+   *
+   *  Handle moving a course to different terms based on course type
+   *
+   */
+  onMove(termCode: string) {
+    switch (this.type) {
+      case 'course':
+        const { id, termCode: from } = this.course as {
+          id: number;
+          termCode: string;
+        };
+        this.store.dispatch(
+          new MoveCourseBetweenTerms({ to: termCode, from, id }),
+        );
+        break;
+
+      case 'saved':
+        const { subjectCode, courseId } = this.course;
+        this.addToTerm(this.course, termCode);
+        this.store.dispatch(new RemoveSaveForLater({ subjectCode, courseId }));
+        break;
+
+      case 'search':
+        this.addToTerm(this.course, termCode);
+        break;
+    }
+  }
+
+  /**
+   *
+   *  Handle saving a course for later (This is not possible if a course is already saved)
+   *
+   */
+  onSaveForLater() {
+    const {
+      courseId,
+      subjectCode,
+      title,
+      catalogNumber,
+      termCode,
+    } = this.course;
+
+    // Dispatch a save for later event
+    this.store.dispatch(
+      new AddSaveForLater({
+        courseId: courseId,
+        subjectCode: subjectCode,
+        title: title,
+        catalogNumber: catalogNumber,
+      }),
+    );
+
+    // If course is in a term, we need to remove it
+    if (this.type === 'course') {
+      this.store.dispatch(
+        new RemoveCourse({
+          fromTermCode: termCode,
+          recordId: this.course.id as number,
+        }),
+      );
+    }
+  }
+
+  /**
+   *
+   *  Handle removing a course (This is not possible for type 'search')
+   *
+   */
+  onRemove() {
+    const dialogOptions = {
+      title: 'Remove Course?',
+      text: '',
+      confirmText: 'Remove Course',
+      confirmColor: 'accent',
+    };
+
+    switch (this.type) {
+      case 'saved':
+        dialogOptions.text = `This will remove "${
+          this.course.title
+        }" from your saved courses.`;
+        break;
+
+      default:
+        dialogOptions.text = `This will remove "${
+          this.course.title
+        }" from your degree plan and your cart.`;
+    }
+
+    this.dialog
+      .open(ConfirmDialogComponent, { data: dialogOptions })
+      .afterClosed()
+      .subscribe((result: { confirmed: boolean }) => {
+        // If the user confirmed the removal, remove course
+        if (result.confirmed) {
+          switch (this.type) {
+            case 'course':
+              this.store.dispatch(
+                new RemoveCourse({
+                  fromTermCode: this.course.termCode,
+                  recordId: this.course.id as number,
+                }),
+              );
+              break;
+
+            case 'saved':
+              const { subjectCode, courseId } = this.course;
+              this.store.dispatch(
+                new RemoveSaveForLater({ subjectCode, courseId }),
+              );
+              break;
+          }
+        }
+      });
+  }
+
   addToTerm(course, term) {
     const { subjectCode, courseId } = course;
+
     const termCode = term;
     this.store.dispatch(new AddCourse({ subjectCode, courseId, termCode }));
   }
 
-  switchTerm(course, term) {
-    const { id, termCode: from } = course;
-    const to = term;
-    this.store.dispatch(new MoveCourseBetweenTerms({ to, from, id }));
-  }
-
   openCourseDetailsDialog(course) {
     this.api
       .getCourseDetails(course.subjectCode, course.courseId)
@@ -100,10 +214,4 @@ export class CourseItemComponent implements OnInit {
         });
       });
   }
-
-  openRemoveConfirmationDialog() {
-    const dialogRef = this.dialog.open(RemoveCourseConfirmDialogComponent, {
-      data: { course: this.course, type: this.type },
-    });
-  }
 }
diff --git a/src/app/degree-planner/store/effects/plan.effects.ts b/src/app/degree-planner/store/effects/plan.effects.ts
index 1f25d40..de75461 100644
--- a/src/app/degree-planner/store/effects/plan.effects.ts
+++ b/src/app/degree-planner/store/effects/plan.effects.ts
@@ -73,31 +73,41 @@ export class DegreePlanEffects {
       return forkJoinWithKeys({
         allDegreePlans: this.api.getAllDegreePlans(),
         subjects: this.api.getAllSubjects(),
+        subjectDescriptions: this.api.getAllSubjectDescriptions(),
         activeTermCodes,
       });
     }),
     // Load data specific to the primary degree plan.
-    flatMap(({ allDegreePlans, subjects, activeTermCodes }) => {
-      const savedForLaterCourses = this.loadSavedForLaterCourses(subjects);
-      const visibleDegreePlan = pickPrimaryDegreePlan(allDegreePlans);
-      const visibleYears = loadPlanYears(
-        this.api,
-        visibleDegreePlan.roadmapId,
-        subjects,
-        activeTermCodes,
-      );
+    flatMap(
+      ({ allDegreePlans, subjects, subjectDescriptions, activeTermCodes }) => {
+        const savedForLaterCourses = this.loadSavedForLaterCourses(subjects);
+        const visibleDegreePlan = pickPrimaryDegreePlan(allDegreePlans);
+        const visibleYears = loadPlanYears(
+          this.api,
+          visibleDegreePlan.roadmapId,
+          subjects,
+          activeTermCodes,
+        );
 
-      return forkJoinWithKeys({
-        visibleDegreePlan: of(visibleDegreePlan),
-        visibleYears,
-        savedForLaterCourses,
-        activeTermCodes: of(activeTermCodes),
-        allDegreePlans: of(allDegreePlans),
-        subjects: of(subjects),
-        expandedYears: of([] as string[]),
-        isCourseSearchVisible: of(false),
-      });
-    }),
+        const descriptions = {};
+
+        subjectDescriptions['0000'].map(subject => {
+          descriptions[subject.subjectCode] = subject.formalDescription;
+        });
+
+        return forkJoinWithKeys({
+          visibleDegreePlan: of(visibleDegreePlan),
+          visibleYears,
+          savedForLaterCourses,
+          activeTermCodes: of(activeTermCodes),
+          allDegreePlans: of(allDegreePlans),
+          subjects: of(subjects),
+          expandedYears: of([] as string[]),
+          isCourseSearchVisible: of(false),
+          subjectDescriptions: of(descriptions),
+        });
+      },
+    ),
     // map(payload => {
     //   const allTerms = payload.visibleYears.map(term => term.termCode);
     //   const currentIndex = allTerms.indexOf(payload.activeTermCodes[0]);
diff --git a/src/app/degree-planner/store/selectors.ts b/src/app/degree-planner/store/selectors.ts
index c5878ec..3a2d5d7 100644
--- a/src/app/degree-planner/store/selectors.ts
+++ b/src/app/degree-planner/store/selectors.ts
@@ -79,6 +79,20 @@ export const selectVisibleTerm = createSelector(
   },
 );
 
+export const getSubjectDescriptions = createSelector(
+  getDegreePlannerState,
+  (state: DegreePlannerState) => {
+    return state.subjectDescriptions;
+  },
+);
+
+export const isCourseSearchOpen = createSelector(
+  getDegreePlannerState,
+  (state: DegreePlannerState) => {
+    return state.isCourseSearchVisible;
+  },
+);
+
 export const isCurrentTerm = (termCode: string) =>
   createSelector(
     getDegreePlannerState,
diff --git a/src/app/degree-planner/store/state.ts b/src/app/degree-planner/store/state.ts
index 3b8b2d0..45ccb17 100644
--- a/src/app/degree-planner/store/state.ts
+++ b/src/app/degree-planner/store/state.ts
@@ -11,6 +11,7 @@ export interface DegreePlannerState {
   activeTermCodes: string[];
   allDegreePlans: DegreePlan[];
   subjects: SubjectMapping;
+  subjectDescriptions: SubjectMapping;
   expandedYears: string[];
   isCourseSearchVisible: boolean;
 }
@@ -22,6 +23,7 @@ export const INITIAL_DEGREE_PLANNER_STATE: DegreePlannerState = {
   activeTermCodes: [],
   allDegreePlans: [],
   subjects: {},
+  subjectDescriptions: {},
   expandedYears: [],
   isCourseSearchVisible: false,
 };
-- 
GitLab