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

ROENROLL-1364

parent da8f790c
No related branches found
No related tags found
No related merge requests found
Pipeline #32932 passed
...@@ -3,44 +3,43 @@ ...@@ -3,44 +3,43 @@
<mat-sidenav #addMenu position="end" mode="over" [opened]="isCourseSearchOpen$ | 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>
<div [formGroup]='courseSearchForm' class="add-course-form" fxLayout="column" fxLayoutAlign="space-around none" style="padding: 12px 22px;"> <form [formGroup]='courseSearchForm' (ngSubmit)="search()" class="add-course-form" fxLayout="column" fxLayoutAlign="space-around none" style="padding: 12px 22px;">
<mat-form-field> <mat-form-field>
<input type="text" placeholder="Term" aria-label="Term" matInput [formControl]="" [matAutocomplete]="term"> <mat-select placeholder="Term" formControlName="term">
<mat-autocomplete #term="matAutocomplete"> <mat-option value="0000" selected>All</mat-option>
<mat-option *ngFor="let term of (coursesData$ | async)" [value]="term[0].termCode | getTermDescription"> <mat-option *ngFor="let term of activeTerms$ | async" [value]="term">{{term | getTermDescription}}</mat-option>
{{ term[0].termCode | getTermDescription }} </mat-select>
</mat-option>
</mat-autocomplete>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<input matInput placeholder="Subject" formControlName='coursesInput' required> <mat-select placeholder="Subject" formControlName="subject" required>
<!-- <mat-error *ngIf="coursesInput.invalid">Please select an existing Subject or 'All'.</mat-error> --> <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>
<mat-form-field class="example-full-width"> <mat-form-field>
<input matInput placeholder="Keyword, number" value=""> <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> </mat-form-field>
</form>
<button mat-button (click)="search()">Search <i class="material-icons">search</i></button> <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"> <h3 *ngIf="isLoading" style="margin: 0px;">Searching for courses...</h3>
<h3 style="margin: 0px;">15 results</h3> <h3 *ngIf="hasResults" style="margin: 0px;">{{queriedCourses.length}} result(s)</h3>
<span class="mat-button">Reset Search</span> <button *ngIf="hasResults" mat-button (click)="resetSearch()">Reset Search</button>
</div> </div>
<mat-progress-bar mode="indeterminate" *ngIf="isLoading"></mat-progress-bar>
<div id="course-search-results" fxLayout="column" fxLayoutAlign="space-around none" style="padding: 12px 22px;"> <div id="course-search-results" fxLayout="column" fxLayoutAlign="space-around none" style="padding: 12px 22px;">
<mat-form-field>
<mat-select placeholder="Order by">
<mat-option>Relevance</mat-option>
<mat-option>Subject</mat-option>
<mat-option>Catalog Number</mat-option>
</mat-select>
</mat-form-field>
<div <div
cdkDropList cdkDropList
id="queried-courses-list" id="queried-courses-list"
......
...@@ -6,10 +6,6 @@ mat-sidenav { ...@@ -6,10 +6,6 @@ mat-sidenav {
width: 340px; width: 340px;
} }
#degree-plan-wrapper {
// margin-right: 0 !important;
}
#menu-toggle-btn { #menu-toggle-btn {
position: absolute; position: absolute;
right: 0px; right: 0px;
...@@ -52,3 +48,10 @@ mat-sidenav { ...@@ -52,3 +48,10 @@ mat-sidenav {
padding: 12px; padding: 12px;
} }
} }
// Course search
.search-input-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
}
...@@ -41,6 +41,8 @@ import { ...@@ -41,6 +41,8 @@ import {
hasLoadedDegreePlan, hasLoadedDegreePlan,
getDropZones, getDropZones,
isCourseSearchOpen, isCourseSearchOpen,
getSubjects,
getActiveTerms,
} from '@app/degree-planner/store/selectors'; } from '@app/degree-planner/store/selectors';
// Actions // Actions
...@@ -77,9 +79,15 @@ export class DegreePlannerComponent implements OnInit { ...@@ -77,9 +79,15 @@ export class DegreePlannerComponent implements OnInit {
public allDegreePlans$: Observable<DegreePlan[]>; public allDegreePlans$: Observable<DegreePlan[]>;
public firstActiveTermCode$: Observable<string | undefined>; public firstActiveTermCode$: Observable<string | undefined>;
public termsByYear$: Observable<Year[]>; public termsByYear$: Observable<Year[]>;
public isCourseSearchOpen$: Observable<boolean>;
public subjects$: Observable<object>;
public activeTerms$: Observable<string[]>;
// Search variables
public queriedCourses: Course[]; public queriedCourses: Course[];
public hasResults: boolean;
public isLoading: boolean;
public courseSearchForm: FormGroup; public courseSearchForm: FormGroup;
public isCourseSearchOpen$: Observable<boolean>;
constructor( constructor(
private store: Store<GlobalState>, private store: Store<GlobalState>,
...@@ -103,8 +111,20 @@ export class DegreePlannerComponent implements OnInit { ...@@ -103,8 +111,20 @@ export class DegreePlannerComponent implements OnInit {
this.termsByYear$ = this.store.pipe(select(getAllVisibleTermsByYear)); this.termsByYear$ = this.store.pipe(select(getAllVisibleTermsByYear));
this.dropZones$ = this.store.pipe(select(getDropZones)); this.dropZones$ = this.store.pipe(select(getDropZones));
// State attributes needed to create the search form
this.subjects$ = this.store.pipe(select(getSubjects));
this.activeTerms$ = this.store.pipe(select(getActiveTerms));
// Internal values used to manage loading state
this.queriedCourses = [];
this.hasResults = false;
this.isLoading = false;
// Deafults for the search form
this.courseSearchForm = this.fb.group({ this.courseSearchForm = this.fb.group({
coursesInput: null, term: '0000',
subject: 'all',
search: '',
}); });
this.isCourseSearchOpen$ = this.store.pipe(select(isCourseSearchOpen)); this.isCourseSearchOpen$ = this.store.pipe(select(isCourseSearchOpen));
...@@ -225,13 +245,30 @@ export class DegreePlannerComponent implements OnInit { ...@@ -225,13 +245,30 @@ export class DegreePlannerComponent implements OnInit {
} }
public search() { public search() {
// console.log('here'); // Get the form field values
// console.log(this.coursesForm.value.subjectCode); const { search, term, subject } = this.courseSearchForm.value;
// Set the internal UI state
this.isLoading = true;
this.hasResults = false;
this.queriedCourses = [];
// Hit the search API
this.api this.api
.searchCourses('test', 'test') .searchCourses({
subjectCode: subject,
searchText: search,
termCode: term === '' ? '0000' : term,
})
.toPromise() .toPromise()
.then(res => { .then(res => {
console.log(res.hits); // TODO add error handeling
// Update the internal state
this.hasResults = true;
this.isLoading = false;
// Map out the results and update the course object to match the needed structure
this.queriedCourses = res.hits.map(course => { this.queriedCourses = res.hits.map(course => {
return { return {
...course, ...course,
...@@ -242,6 +279,16 @@ export class DegreePlannerComponent implements OnInit { ...@@ -242,6 +279,16 @@ export class DegreePlannerComponent implements OnInit {
}) })
.catch(console.log); .catch(console.log);
} }
public resetSearch() {
// Reset the internal state and form values
this.queriedCourses = [];
this.hasResults = false;
this.courseSearchForm.setValue({
subject: 'all',
search: '',
});
}
} }
const isntUndefined = <T>(anything: T | undefined): anything is T => { const isntUndefined = <T>(anything: T | undefined): anything is T => {
......
...@@ -4,7 +4,7 @@ import { Injectable } from '@angular/core'; ...@@ -4,7 +4,7 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http'; import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { map, filter } from 'rxjs/operators';
// Services // Services
import { ConfigService } from '@app/core/config.service'; import { ConfigService } from '@app/core/config.service';
...@@ -106,7 +106,11 @@ export class DegreePlannerApiService { ...@@ -106,7 +106,11 @@ export class DegreePlannerApiService {
return this.http.post('/api/search/v1/autocomplete', data, HTTP_OPTIONS); return this.http.post('/api/search/v1/autocomplete', data, HTTP_OPTIONS);
} }
public searchCourses(search: string, subjectCode: string): Observable<any> { public searchCourses(config: {
subjectCode: string;
searchText?: string;
termCode?: string;
}): Observable<any> {
// const data = { // const data = {
// filters: [ // filters: [
// { term: { 'subject.subjectCode': '266' } }, // { term: { 'subject.subjectCode': '266' } },
...@@ -128,43 +132,44 @@ export class DegreePlannerApiService { ...@@ -128,43 +132,44 @@ export class DegreePlannerApiService {
// sortOrder: 'SCORE', // sortOrder: 'SCORE',
// }; // };
// Used to search all terms const { subjectCode, termCode, searchText } = config;
const data = {
filters: [{ term: { 'subject.subjectCode': '266' } }],
page: 1,
pageSize: 10,
queryString: 'Programming',
selectedTerm: '0000',
sortOrder: 'SCORE',
};
// Used to search a specific term const payload: any = {
const test = { selectedTerm: termCode,
selectedTerm: '1194', queryString: searchText === '' ? '*' : searchText,
queryString: 'programing',
// Filters are use to build the elastic search query // Filters are use to build the elastic search query
filters: [ filters: [],
{ term: { 'subject.subjectCode': '266' } },
{
has_child: {
type: 'enrollmentPackage',
query: {
// We want to make sure we search for ALL classes regardless of status
match: {
'packageEnrollmentStatus.status': 'OPEN WAITLISTED CLOSED',
},
},
},
},
],
// These options control how much data we get back // These options control how much data we get back
page: 1, page: 1,
pageSize: 20, pageSize: 25,
sortOrder: 'SCORE',
}; };
return this.http.post('/api/search/v1', data, HTTP_OPTIONS); // If we have a specific subject code, add a fitler for it
if (subjectCode !== 'all') {
payload.filters.push({ term: { 'subject.subjectCode': subjectCode } });
}
// 0000 is used to search all courses
// Any other term code we can assuem is an active term
if (termCode !== '0000') {
// Used to search a specific term
payload.filters.push({
has_child: {
type: 'enrollmentPackage',
query: {
// We want to make sure we search for ALL classes regardless of status
match: {
'packageEnrollmentStatus.status': 'OPEN WAITLISTED CLOSED',
},
},
},
});
}
return this.http.post('/api/search/v1', payload, HTTP_OPTIONS);
} }
public addCourse( public addCourse(
......
...@@ -69,6 +69,8 @@ export const getDropableTerms = createSelector( ...@@ -69,6 +69,8 @@ export const getDropableTerms = createSelector(
export const getAllVisibleTermsByYear = createSelector( export const getAllVisibleTermsByYear = createSelector(
getDegreePlannerState, getDegreePlannerState,
state => { state => {
console.log('getting visible terms');
const unqiueYears = state.visibleTerms const unqiueYears = state.visibleTerms
.map(term => term.termCode.slice(0, 3)) .map(term => term.termCode.slice(0, 3))
.filter(year => year.match(/^\d{3}/)) .filter(year => year.match(/^\d{3}/))
...@@ -117,6 +119,20 @@ export const isCourseSearchOpen = createSelector( ...@@ -117,6 +119,20 @@ export const isCourseSearchOpen = createSelector(
}, },
); );
export const getSubjects = createSelector(
getDegreePlannerState,
(state: DegreePlannerState) => {
return state.subjects;
},
);
export const getActiveTerms = createSelector(
getDegreePlannerState,
(state: DegreePlannerState) => {
return state.activeTermCodes;
},
);
export const isPastTerm = (termCode: string) => export const isPastTerm = (termCode: string) =>
createSelector( createSelector(
getDegreePlannerState, getDegreePlannerState,
......
...@@ -16,6 +16,7 @@ import { MatDialogModule } from '@angular/material/dialog'; ...@@ -16,6 +16,7 @@ import { MatDialogModule } from '@angular/material/dialog';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { GetTermDescriptionPipe } from './pipes/get-term-description.pipe'; import { GetTermDescriptionPipe } from './pipes/get-term-description.pipe';
import { AcademicYearStatePipe } from './pipes/academic-year-state.pipe'; import { AcademicYearStatePipe } from './pipes/academic-year-state.pipe';
...@@ -45,6 +46,7 @@ const modules = [ ...@@ -45,6 +46,7 @@ const modules = [
MatAutocompleteModule, MatAutocompleteModule,
MatFormFieldModule, MatFormFieldModule,
MatSnackBarModule, MatSnackBarModule,
MatProgressBarModule,
]; ];
const pipes = [ const pipes = [
GetTermDescriptionPipe, GetTermDescriptionPipe,
......
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