From e114c50b0b38c3f8cadec16f2377ef3b4ee226a9 Mon Sep 17 00:00:00 2001
From: Scott Berg <saberg3@wisc.edu>
Date: Fri, 1 Feb 2019 15:28:33 -0600
Subject: [PATCH] Add actions / effects / reducers for adding and removing
 saved for later courses

---
 src/app/core/models/saved-for-later-course.ts |  2 +-
 .../degree-planner/actions/plan.actions.ts    | 39 ++++++++
 .../degree-planner/effects/plan.effects.ts    | 97 +++++++++++++++++--
 .../favorites-container.component.ts          | 18 +++-
 src/app/degree-planner/reducer.ts             | 41 +++++++-
 .../degree-planner/services/api.service.ts    | 10 +-
 .../term-container.component.ts               |  6 +-
 7 files changed, 196 insertions(+), 17 deletions(-)

diff --git a/src/app/core/models/saved-for-later-course.ts b/src/app/core/models/saved-for-later-course.ts
index 2f95a9c..14d0113 100644
--- a/src/app/core/models/saved-for-later-course.ts
+++ b/src/app/core/models/saved-for-later-course.ts
@@ -1,5 +1,5 @@
 export interface SavedForLaterCourse {
-	id: number;
+	id: number | null;
 	courseId: string;
 	termCode: string;
 	topicId: number;
diff --git a/src/app/degree-planner/actions/plan.actions.ts b/src/app/degree-planner/actions/plan.actions.ts
index 70182eb..c716a6a 100644
--- a/src/app/degree-planner/actions/plan.actions.ts
+++ b/src/app/degree-planner/actions/plan.actions.ts
@@ -5,14 +5,25 @@ import { Course} from '../../core/models/course';
 
 export enum PlanActionTypes {
 	InitialPlanLoadResponse = '[Plan] Initial Load Response',
+
 	ChangeVisiblePlanRequest = '[Plan] Change Visible Request',
 	ChangeVisiblePlanResponse = '[Plan] Change Visible Response',
+
 	AddCourseRequest = '[Plan] Add Course Request',
 	AddCourseResponse = '[Plan] Add Course Response',
+
 	RemoveCourseRequest = '[Plan] Remove Course Request',
 	RemoveCourseResponse = '[Plan] Remove Course Response',
+
 	ChangeCourseTermRequest = '[Plan] Change Course Term Request',
 	ChangeCourseTermResponse = '[Plan] Change Course Term Response',
+
+	AddSavedForLaterReqeust = '[Plan] Add Saved For Later Request',
+	AddSavedForLaterResponse = '[Plan] Add Saved For Later Response',
+
+	RemoveSavedForLaterReqeust = '[Plan] Remove Saved For Later Request',
+	RemoveSavedForLaterResponse = '[Plan] Remove Saved For Later Response',
+
 	MoveFromSavedToTermRequest = '[Plan] Move Course From Saved to Term Request',
 	MoveFromSavedToTermResponse= '[Plan] Move Course From Saved to Term Response'
 }
@@ -75,3 +86,31 @@ export class RemoveCourseResponse implements Action {
 		public payload: {id: number}
 	) {}
 }
+
+export class AddSavedForLaterRequest implements Action {
+	public readonly type = PlanActionTypes.AddSavedForLaterReqeust;
+	constructor(
+		public payload: {subjectCode: string, courseId: string}
+	) {}
+}
+
+export class AddSavedForLaterResponse implements Action {
+	public readonly type = PlanActionTypes.AddSavedForLaterResponse;
+	constructor(
+		public payload: {subjectCode: string, courseId: string}
+	) {}
+}
+
+export class RemoveSavedForLaterRequest implements Action {
+	public readonly type = PlanActionTypes.RemoveSavedForLaterReqeust;
+	constructor(
+		public payload: {subjectCode: string, courseId: string}
+	) {}
+}
+
+export class RemoveSavedForLaterResponse implements Action {
+	public readonly type = PlanActionTypes.RemoveSavedForLaterResponse;
+	constructor(
+		public payload: {subjectCode: string, courseId: string}
+	) {}
+}
diff --git a/src/app/degree-planner/effects/plan.effects.ts b/src/app/degree-planner/effects/plan.effects.ts
index b85d9a8..6775d84 100644
--- a/src/app/degree-planner/effects/plan.effects.ts
+++ b/src/app/degree-planner/effects/plan.effects.ts
@@ -18,7 +18,9 @@ import {
 	PlanActionTypes,
 	ChangeCourseTermResponse,
 	AddCourseResponse,
-	RemoveCourseResponse
+	RemoveCourseResponse,
+	RemoveSavedForLaterResponse,
+	AddSavedForLaterResponse
 } from '@app/degree-planner/actions/plan.actions';
 
 // Models
@@ -144,10 +146,6 @@ export class DegreePlanEffects {
 		// Wrap data in an Action for dispatch
 		map(({ response, action }) => {
 			// TODO add error handleing
-			console.log('------');
-			console.log(action);
-			console.log(response);
-			console.log('------');
 			return new AddCourseResponse({ course: response});
 		})
 	);
@@ -160,10 +158,10 @@ export class DegreePlanEffects {
 		filter(([_, state]) => typeof state.visibleRoadmapId === 'number'),
 
 		// Get the roadmap ID from the action.
-		tap(([action, state]) => {
-			console.log(action);
-			console.log(state);
-		}),
+		// tap(([action, state]) => {
+		// 	console.log(action);
+		// 	console.log(state);
+		// }),
 
 		// Get term data for the degree plan specified by the roadmap ID.
 		flatMap(([action, state]) => {
@@ -188,6 +186,87 @@ export class DegreePlanEffects {
 		})
 	);
 
+	@Effect()
+	RemoveSavedForLater$ = this.actions$.pipe(
+		ofType<any>(PlanActionTypes.RemoveSavedForLaterReqeust),
+
+		withLatestFrom(this.store$.select(getDegreePlannerState)),
+		filter(([_, state]) => typeof state.visibleRoadmapId === 'number'),
+
+		// tap(([action, state]) => {
+		// 	console.log('REMOVE SAVED FOR LATER ----------');
+
+		// 	console.log(action);
+		// 	console.log(state);
+
+		// 	console.log('---------------------------------');
+		// }),
+
+		// Get term data for the degree plan specified by the roadmap ID.
+		flatMap(([action, state]) => {
+			// TODO error handle the API calls
+			return this.api.removeSavedForLater(action.payload.subjectCode, action.payload.courseId).pipe(
+				map(response => {
+					return {
+						response,
+						action
+					};
+				})
+			);
+		}),
+
+		// // Wrap data in an Action for dispatch
+		map(({ response, action }) => {
+			if (response === null) {
+				const { courseId, subjectCode } = action.payload;
+				return new RemoveSavedForLaterResponse({courseId, subjectCode});
+				// TODO Update UI and remove saved response
+			}
+			return;
+		})
+	);
+
+	@Effect()
+	SaveForLater$ = this.actions$.pipe(
+		ofType<any>(PlanActionTypes.AddSavedForLaterReqeust),
+
+		withLatestFrom(this.store$.select(getDegreePlannerState)),
+		filter(([_, state]) => typeof state.visibleRoadmapId === 'number'),
+
+		// Get the roadmap ID from the action.
+		// tap(([action, state]) => {
+		// 	console.log('ADD SAVED FOR LATER ----------');
+
+		// 	console.log(action);
+		// 	console.log(state);
+
+		// 	console.log('---------------------------------');
+		// }),
+
+		// Get term data for the degree plan specified by the roadmap ID.
+		flatMap(([action, state]) => {
+			// TODO error handle the API calls
+			return this.api.saveForLater(action.payload.subjectCode, action.payload.courseId).pipe(
+				map(response => {
+					return {
+						response,
+						action
+					};
+				})
+			);
+		}),
+
+		// // // Wrap data in an Action for dispatch
+		map(({ response, action }) => {
+			if (response === null) {
+				const { subjectCode, courseId } = action.payload;
+				return new AddSavedForLaterResponse({ subjectCode, courseId });
+			}
+			// return;
+			return;
+		})
+	);
+
 	private loadTermsForPlan<T extends { visibleRoadmapId: number }>(stdin: T) {
 		return forkJoin(
 			this.api.getAllNotes(stdin.visibleRoadmapId),
diff --git a/src/app/degree-planner/favorites-container/favorites-container.component.ts b/src/app/degree-planner/favorites-container/favorites-container.component.ts
index 2da8c38..3e82e55 100644
--- a/src/app/degree-planner/favorites-container/favorites-container.component.ts
+++ b/src/app/degree-planner/favorites-container/favorites-container.component.ts
@@ -7,6 +7,9 @@ import { getSavedForLaterCourses } from '@app/degree-planner/selectors';
 
 // rsjx / ngrx
 import { DegreePlannerState } from '@app/degree-planner/state';
+import {
+	AddSavedForLaterRequest, RemoveCourseRequest
+} from '@app/degree-planner/actions/plan.actions';
 
 // Selectors
 import {
@@ -30,6 +33,19 @@ export class SavedForLaterContainerComponent implements OnInit {
 	}
 
 	drop(event) {
-		console.log(event);
+		const newContainer = event.container.id;
+		const previousContainer = event.previousContainer.id;
+
+		if (newContainer === previousContainer) {
+			// If the user dropped a course into the same container do nothing
+			return;
+
+		} else if (previousContainer.indexOf('term-') === 0) {
+			// If moving from term to to saved for later
+
+			const {subjectCode, courseId, id} = event.item.data;
+			this.store.dispatch(new AddSavedForLaterRequest({subjectCode, courseId}));
+			this.store.dispatch(new RemoveCourseRequest({id}));
+		}
 	}
 }
diff --git a/src/app/degree-planner/reducer.ts b/src/app/degree-planner/reducer.ts
index aaacf1d..efd8643 100644
--- a/src/app/degree-planner/reducer.ts
+++ b/src/app/degree-planner/reducer.ts
@@ -8,7 +8,9 @@ import {
 	ChangeVisiblePlanResponse,
 	RemoveCourseResponse,
 	ChangeCourseTermResponse,
-	AddCourseResponse
+	AddCourseResponse,
+	RemoveSavedForLaterResponse,
+	AddSavedForLaterResponse
 } from '@app/degree-planner/actions/plan.actions';
 import {
 	NoteActionTypes,
@@ -23,7 +25,9 @@ type SupportedActions =
 	| DeleteNoteResponse
 	| ChangeCourseTermResponse
 	| RemoveCourseResponse
-	| AddCourseResponse;
+	| AddCourseResponse
+	| RemoveSavedForLaterResponse
+	| AddSavedForLaterResponse;
 
 export function degreePlannerReducer(
 	state = INITIAL_DEGREE_PLANNER_STATE,
@@ -179,6 +183,39 @@ export function degreePlannerReducer(
 			return {...state, visibleTerms: newVisibleTerms};
 		}
 
+		case PlanActionTypes.RemoveSavedForLaterResponse: {
+			const { courseId, subjectCode } = action.payload;
+
+			// // Create new saved for later array
+			const newSavedForLater = state.savedForLaterCourses.filter(
+				course => course.subjectCode !== subjectCode && course.courseId !== courseId
+			);
+
+			return {...state, savedForLaterCourses: newSavedForLater};
+		}
+
+		case PlanActionTypes.AddSavedForLaterResponse: {
+			const { courseId, subjectCode } = action.payload;
+
+			// // Create new saved for later array
+			const newSavedForLater = [
+				...state.savedForLaterCourses,
+				// TODO Update this when the API is fixed, the API should be sending a fav course as a response
+				{
+					id: null,
+					courseId,
+					subjectCode,
+					topicId: 0,
+					courseOrder: 0,
+					catalogNumber: '',
+					title: '',
+					termCode: '0000'
+				}
+			];
+
+			return {...state, savedForLaterCourses: newSavedForLater};
+		}
+
 		/**
 		 * It's okay if the action didn't match any of the cases above. If that's
 		 * the case, just return the existing state object.
diff --git a/src/app/degree-planner/services/api.service.ts b/src/app/degree-planner/services/api.service.ts
index 734bdb8..d71d7d6 100644
--- a/src/app/degree-planner/services/api.service.ts
+++ b/src/app/degree-planner/services/api.service.ts
@@ -58,7 +58,7 @@ export class DegreePlannerApiService {
 		roadmapId + '/courses/' + recordId + '?termCode=' + termCode, HTTP_OPTIONS);
 	}
 
-	public addCourse(planId: number, subjectCode: string, courseId: string, termCode: string) {
+	public addCourse(planId: number, subjectCode: string, courseId: string, termCode: string): Observable<Course> {
 		return this.http.post<Course>(
 			this.config.apiPlannerUrl + '/degreePlan/' + planId + '/courses', {subjectCode, courseId, termCode }, HTTP_OPTIONS);
 	}
@@ -67,6 +67,14 @@ export class DegreePlannerApiService {
 		return this.http.delete(this.config.apiPlannerUrl + '/degreePlan/' + planId + '/courses/' + recordId, HTTP_OPTIONS);
 	}
 
+	public saveForLater(subjectCode: string, courseId: string): Observable<SavedForLaterCourse> {
+		return this.http.post<SavedForLaterCourse>(this.config.apiPlannerUrl + '/favorites/' + subjectCode + '/' + courseId, HTTP_OPTIONS);
+	}
+
+	public removeSavedForLater(subjectCode: string, courseId: string): Observable<SavedForLaterCourse> {
+		return this.http.delete<SavedForLaterCourse>(this.config.apiPlannerUrl + '/favorites/' + subjectCode + '/' + courseId, HTTP_OPTIONS);
+	}
+
 	public createNote(
 		planId: number,
 		termCode: string,
diff --git a/src/app/degree-planner/term-container/term-container.component.ts b/src/app/degree-planner/term-container/term-container.component.ts
index 05f2897..42936df 100644
--- a/src/app/degree-planner/term-container/term-container.component.ts
+++ b/src/app/degree-planner/term-container/term-container.component.ts
@@ -9,7 +9,8 @@ import { Store, select } from '@ngrx/store';
 import { DegreePlannerState } from '@app/degree-planner/state';
 import {
 	ChangeCourseTermRequest,
-	AddCourseRequest
+	AddCourseRequest,
+	RemoveSavedForLaterRequest
 } from '@app/degree-planner/actions/plan.actions';
 
 // Selectors
@@ -91,8 +92,6 @@ export class TermContainerComponent implements OnInit {
 
 		} else if (previousContainer === 'saved-courses') {
 			// If moving from saved courses to term
-			console.log('add course and remove from saved');
-			console.log(event);
 
 			// Get the term code from the new term dropzone's ID
 			const termCode = newContainer.substr(5);
@@ -102,6 +101,7 @@ export class TermContainerComponent implements OnInit {
 
 			// Dispatch the add event
 			this.store.dispatch(new AddCourseRequest({ subjectCode, courseId, termCode }));
+			this.store.dispatch(new RemoveSavedForLaterRequest({ subjectCode, courseId }));
 		}
 	}
 }
-- 
GitLab