Skip to content
Snippets Groups Projects
Commit 3323d430 authored by Scott Berg's avatar Scott Berg
Browse files

Search improvements

parent fae7be68
No related branches found
No related tags found
No related merge requests found
<mat-sidenav-container hasBackdrop="false" *ngIf="(degreePlan$ | async) as degreePlan"> <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"> <mat-toolbar color="primary" class="dialog-toolbar">
<span class="dialog-toolbar-title">Course Search</span> <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-toolbar>
<mat-sidenav #addMenu position="end" mode="over" [opened]="isCourseSearchOpen$ | async"> <form [formGroup]='courseSearchForm' (ngSubmit)="search()" class="add-course-form" fxLayout="column" fxLayoutAlign="space-around none" style="padding: 12px 22px;">
<mat-toolbar color="primary" class="dialog-toolbar">
<span class="dialog-toolbar-title">Course Search</span> <mat-form-field>
<button mat-button class="close-btn" (click)="closeCourseSearch();"><i class="material-icons">keyboard_arrow_right</i></button> <mat-select placeholder="Term" aria-label="Term" formControlName="term">
</mat-toolbar> <mat-option value="0000">All</mat-option>
<mat-option *ngFor="let yearCode of activeTerms$ | async" [value]="yearCode">{{yearCode | getTermDescription}}</mat-option>
<form [formGroup]='courseSearchForm' (ngSubmit)="search()" class="add-course-form" fxLayout="column" fxLayoutAlign="space-around none" style="padding: 12px 22px;"> </mat-select>
<mat-form-field> </mat-form-field>
<mat-select placeholder="Term" formControlName="term">
<mat-option value="0000" selected>All</mat-option> <mat-form-field class="example-full-width">
<mat-option *ngFor="let term of activeTerms$ | async" [value]="term">{{term | getTermDescription}}</mat-option> <input type="text" placeholder="Subject" aria-label="Subject" matInput formControlName="subject" [matAutocomplete]="subject">
</mat-select> <mat-autocomplete autoActiveFirstOption #subject="matAutocomplete">
</mat-form-field> <mat-option *ngFor="let subject of filteredSubjects | keyvalue" [value]="subject.value">{{subject.value}}</mat-option>
</mat-autocomplete>
<mat-form-field> </mat-form-field>
<mat-select placeholder="Subject" formControlName="subject" required>
<mat-option value="all">All</mat-option> <mat-form-field>
<mat-option *ngFor="let subject of (subjects$ | async) | keyvalue" [value]="subject.key"> <div class="search-input-wrapper">
{{subject.value}} <input id="keyword-field" matInput placeholder="Keyword, number" formControlName="search" value="">
</mat-option> <button id="search-button" mat-icon-button aria-label="Search" matSuffix style="margin-top: -10px;"><i class="material-icons">search</i></button>
</mat-select> </div>
</mat-form-field> </mat-form-field>
<mat-form-field> </form>
<div class="search-input-wrapper"> <div *ngIf="hasResults || isLoading">
<input id="keyword-field" matInput placeholder="Keyword, number" formControlName="search" value=""> <div class="search-results-toolbar mat-typography" fxLayout="row" fxLayoutAlign="space-between center" style="padding: 12px 22px; background-color: #EDF1F3; min-height: 60px;">
<button id="search-button" mat-icon-button aria-label="Search" matSuffix style="margin-top: -10px;"><i class="material-icons">search</i></button> <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> </div>
</mat-form-field> </div>
</form> </div>
</div>
</div>
</mat-sidenav>
<div *ngIf="hasResults || isLoading"> <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;"> <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 @@ ...@@ -49,7 +77,7 @@
cdkDropList cdkDropList
id="queried-courses-list" id="queried-courses-list"
[cdkDropListData]="queriedCourses" [cdkDropListData]="queriedCourses"
[cdkDropListConnectedTo]="dropZones$ | async"> [cdkDropListConnectedTo]="getTermDropZone()">
<div <div
class="course-wrapper" class="course-wrapper"
cdkDrag cdkDrag
...@@ -62,13 +90,12 @@ ...@@ -62,13 +90,12 @@
</div> </div>
</div> </div>
</div> </div>
</mat-sidenav>
<div <div
cdkDropList cdkDropList
id="queried-courses-list" id="queried-courses-list"
[cdkDropListData]="queriedCourses" [cdkDropListData]="queriedCourses"
[cdkDropListConnectedTo]="dropZones$ | async"> [cdkDropListConnectedTo]="dropZoneIds$ | async">
<div <div
class="course-wrapper" class="course-wrapper"
cdkDrag cdkDrag
...@@ -79,7 +106,6 @@ ...@@ -79,7 +106,6 @@
</div> </div>
</div> </div>
</div> </div>
</mat-sidenav>
<mat-sidenav-content> <mat-sidenav-content>
<mat-sidenav-container id="plans-container"> <mat-sidenav-container id="plans-container">
......
...@@ -4,7 +4,7 @@ import { ...@@ -4,7 +4,7 @@ import {
withLatestFrom, withLatestFrom,
distinctUntilChanged, distinctUntilChanged,
} from 'rxjs/operators'; } from 'rxjs/operators';
import { OnInit } from '@angular/core'; import { OnInit, ViewChild } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { select } from '@ngrx/store'; import { select } from '@ngrx/store';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
...@@ -16,11 +16,11 @@ import { MatSnackBar } from '@angular/material'; ...@@ -16,11 +16,11 @@ import { MatSnackBar } from '@angular/material';
import { GlobalState } from '@app/core/state'; import { GlobalState } from '@app/core/state';
import { DegreePlan } from '@app/core/models/degree-plan'; import { DegreePlan } from '@app/core/models/degree-plan';
import { Year } from '@app/core/models/year'; import { Year } from '@app/core/models/year';
import { Course } from '@app/core/models/course';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { DegreePlannerApiService } from '@app/degree-planner/services/api.service'; import { DegreePlannerApiService } from '@app/degree-planner/services/api.service';
import * as selectors from '@app/degree-planner/store/selectors'; import * as selectors from '@app/degree-planner/store/selectors';
import * as utils from '@app/degree-planner/shared/utils'; import * as utils from '@app/degree-planner/shared/utils';
import { Course, SubjectMapping } from '@app/core/models/course';
// Actions // Actions
import { import {
...@@ -59,6 +59,7 @@ export class DegreePlannerComponent implements OnInit { ...@@ -59,6 +59,7 @@ export class DegreePlannerComponent implements OnInit {
public subjects$: Observable<object>; public subjects$: Observable<object>;
public activeTerms$: Observable<string[]>; public activeTerms$: Observable<string[]>;
public yearCodes$: Observable<string[]>; public yearCodes$: Observable<string[]>;
public dropZoneIds$: Observable<string[]>;
// Search variables // Search variables
public queriedCourses: Course[]; public queriedCourses: Course[];
...@@ -67,6 +68,9 @@ export class DegreePlannerComponent implements OnInit { ...@@ -67,6 +68,9 @@ export class DegreePlannerComponent implements OnInit {
public courseSearchForm: FormGroup; public courseSearchForm: FormGroup;
public isCourseSearchVisible$: Observable<boolean>; public isCourseSearchVisible$: Observable<boolean>;
public subjects: SubjectMapping;
public filteredSubjects: SubjectMapping;
constructor( constructor(
private store: Store<GlobalState>, private store: Store<GlobalState>,
public mediaMatcher: MediaMatcher, public mediaMatcher: MediaMatcher,
...@@ -107,13 +111,57 @@ export class DegreePlannerComponent implements OnInit { ...@@ -107,13 +111,57 @@ export class DegreePlannerComponent implements OnInit {
distinctUntilChanged(utils.compareStringArrays), 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 // Deafults for the search form
this.courseSearchForm = this.fb.group({ this.courseSearchForm = this.fb.group({
term: '0000', term: '0000',
subject: 'all', subject: 'All',
search: '', 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 { public handleDegreePlanChange(event: MatSelectChange): void {
...@@ -235,6 +283,24 @@ export class DegreePlannerComponent implements OnInit { ...@@ -235,6 +283,24 @@ export class DegreePlannerComponent implements OnInit {
public search() { public search() {
// Get the form field values // Get the form field values
const { search, term, subject } = this.courseSearchForm.value; 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 // Set the internal UI state
this.isLoading = true; this.isLoading = true;
...@@ -244,7 +310,7 @@ export class DegreePlannerComponent implements OnInit { ...@@ -244,7 +310,7 @@ export class DegreePlannerComponent implements OnInit {
// Hit the search API // Hit the search API
this.api this.api
.searchCourses({ .searchCourses({
subjectCode: subject, subjectCode,
searchText: search, searchText: search,
termCode: term === '' ? '0000' : term, termCode: term === '' ? '0000' : term,
}) })
...@@ -273,8 +339,9 @@ export class DegreePlannerComponent implements OnInit { ...@@ -273,8 +339,9 @@ export class DegreePlannerComponent implements OnInit {
this.queriedCourses = []; this.queriedCourses = [];
this.hasResults = false; this.hasResults = false;
this.courseSearchForm.setValue({ this.courseSearchForm.setValue({
subject: 'all', subject: 'All',
search: '', search: '',
term: '0000',
}); });
} }
} }
......
...@@ -57,6 +57,10 @@ export class DegreePlannerApiService { ...@@ -57,6 +57,10 @@ export class DegreePlannerApiService {
); );
} }
public getAllSubjectDescriptions(): Observable<{}> {
return this.http.get(this.searchEndpoint('subjects'));
}
public getActiveTerms(): Observable<Term[]> { public getActiveTerms(): Observable<Term[]> {
return this.http.get<Term[]>(this.searchEndpoint('terms')); return this.http.get<Term[]>(this.searchEndpoint('terms'));
} }
...@@ -111,27 +115,6 @@ export class DegreePlannerApiService { ...@@ -111,27 +115,6 @@ export class DegreePlannerApiService {
searchText?: string; searchText?: string;
termCode?: string; termCode?: string;
}): Observable<any> { }): 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 { subjectCode, termCode, searchText } = config;
const payload: any = { const payload: any = {
...@@ -148,7 +131,7 @@ export class DegreePlannerApiService { ...@@ -148,7 +131,7 @@ export class DegreePlannerApiService {
}; };
// If we have a specific subject code, add a fitler for it // 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 } }); payload.filters.push({ term: { 'subject.subjectCode': subjectCode } });
} }
......
...@@ -28,9 +28,10 @@ ...@@ -28,9 +28,10 @@
<button mat-menu-item [matMenuTriggerFor]="academicYearsGroup">Move</button> <button mat-menu-item [matMenuTriggerFor]="academicYearsGroup">Move</button>
<mat-menu #academicYearsGroup="matMenu" class="course-item-submenu"> <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 (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> </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> </mat-menu>
</div> </div>
<div *ngIf="disabled" fxLayout="row" fxLayoutAlign="end center"> <div *ngIf="disabled" fxLayout="row" fxLayoutAlign="end center">
......
...@@ -8,13 +8,15 @@ import { ...@@ -8,13 +8,15 @@ import {
AddCourse, AddCourse,
AddSaveForLater, AddSaveForLater,
MoveCourseBetweenTerms, MoveCourseBetweenTerms,
RemoveSaveForLater,
RemoveCourse,
} from './../../store/actions/course.actions'; } from './../../store/actions/course.actions';
import { GlobalState } from '@app/core/state'; import { GlobalState } from '@app/core/state';
import { Course } from '@app/core/models/course'; import { Course } from '@app/core/models/course';
import * as selectors from '@app/degree-planner/store/selectors'; import * as selectors from '@app/degree-planner/store/selectors';
import { DegreePlannerApiService } from '@app/degree-planner/services/api.service'; 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'; import { CourseDetailsDialogComponent } from '@app/degree-planner/dialogs/course-details-dialog/course-details-dialog.component';
// tslint:disable-next-line:max-line-length // tslint:disable-next-line:max-line-length
import { RemoveCourseConfirmDialogComponent } from '@app/degree-planner/dialogs/remove-course-confirm-dialog/remove-course-confirm-dialog.component'; 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 { ...@@ -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) { addToTerm(course, term) {
const { subjectCode, courseId } = course; const { subjectCode, courseId } = course;
const termCode = term; const termCode = term;
this.store.dispatch(new AddCourse({ subjectCode, courseId, termCode })); 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) { openCourseDetailsDialog(course) {
this.api this.api
.getCourseDetails(course.subjectCode, course.courseId) .getCourseDetails(course.subjectCode, course.courseId)
...@@ -100,10 +214,4 @@ export class CourseItemComponent implements OnInit { ...@@ -100,10 +214,4 @@ export class CourseItemComponent implements OnInit {
}); });
}); });
} }
openRemoveConfirmationDialog() {
const dialogRef = this.dialog.open(RemoveCourseConfirmDialogComponent, {
data: { course: this.course, type: this.type },
});
}
} }
...@@ -73,31 +73,41 @@ export class DegreePlanEffects { ...@@ -73,31 +73,41 @@ export class DegreePlanEffects {
return forkJoinWithKeys({ return forkJoinWithKeys({
allDegreePlans: this.api.getAllDegreePlans(), allDegreePlans: this.api.getAllDegreePlans(),
subjects: this.api.getAllSubjects(), subjects: this.api.getAllSubjects(),
subjectDescriptions: this.api.getAllSubjectDescriptions(),
activeTermCodes, activeTermCodes,
}); });
}), }),
// Load data specific to the primary degree plan. // Load data specific to the primary degree plan.
flatMap(({ allDegreePlans, subjects, activeTermCodes }) => { flatMap(
const savedForLaterCourses = this.loadSavedForLaterCourses(subjects); ({ allDegreePlans, subjects, subjectDescriptions, activeTermCodes }) => {
const visibleDegreePlan = pickPrimaryDegreePlan(allDegreePlans); const savedForLaterCourses = this.loadSavedForLaterCourses(subjects);
const visibleYears = loadPlanYears( const visibleDegreePlan = pickPrimaryDegreePlan(allDegreePlans);
this.api, const visibleYears = loadPlanYears(
visibleDegreePlan.roadmapId, this.api,
subjects, visibleDegreePlan.roadmapId,
activeTermCodes, subjects,
); activeTermCodes,
);
return forkJoinWithKeys({ const descriptions = {};
visibleDegreePlan: of(visibleDegreePlan),
visibleYears, subjectDescriptions['0000'].map(subject => {
savedForLaterCourses, descriptions[subject.subjectCode] = subject.formalDescription;
activeTermCodes: of(activeTermCodes), });
allDegreePlans: of(allDegreePlans),
subjects: of(subjects), return forkJoinWithKeys({
expandedYears: of([] as string[]), visibleDegreePlan: of(visibleDegreePlan),
isCourseSearchVisible: of(false), visibleYears,
}); savedForLaterCourses,
}), activeTermCodes: of(activeTermCodes),
allDegreePlans: of(allDegreePlans),
subjects: of(subjects),
expandedYears: of([] as string[]),
isCourseSearchVisible: of(false),
subjectDescriptions: of(descriptions),
});
},
),
// map(payload => { // map(payload => {
// const allTerms = payload.visibleYears.map(term => term.termCode); // const allTerms = payload.visibleYears.map(term => term.termCode);
// const currentIndex = allTerms.indexOf(payload.activeTermCodes[0]); // const currentIndex = allTerms.indexOf(payload.activeTermCodes[0]);
......
...@@ -79,6 +79,20 @@ export const selectVisibleTerm = createSelector( ...@@ -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) => export const isCurrentTerm = (termCode: string) =>
createSelector( createSelector(
getDegreePlannerState, getDegreePlannerState,
......
...@@ -11,6 +11,7 @@ export interface DegreePlannerState { ...@@ -11,6 +11,7 @@ export interface DegreePlannerState {
activeTermCodes: string[]; activeTermCodes: string[];
allDegreePlans: DegreePlan[]; allDegreePlans: DegreePlan[];
subjects: SubjectMapping; subjects: SubjectMapping;
subjectDescriptions: SubjectMapping;
expandedYears: string[]; expandedYears: string[];
isCourseSearchVisible: boolean; isCourseSearchVisible: boolean;
} }
...@@ -22,6 +23,7 @@ export const INITIAL_DEGREE_PLANNER_STATE: DegreePlannerState = { ...@@ -22,6 +23,7 @@ export const INITIAL_DEGREE_PLANNER_STATE: DegreePlannerState = {
activeTermCodes: [], activeTermCodes: [],
allDegreePlans: [], allDegreePlans: [],
subjects: {}, subjects: {},
subjectDescriptions: {},
expandedYears: [], expandedYears: [],
isCourseSearchVisible: false, isCourseSearchVisible: false,
}; };
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment