diff --git a/projects/v3/src/app/components/activity/activity.component.html b/projects/v3/src/app/components/activity/activity.component.html index efa3d4ca5..5b9f6562b 100644 --- a/projects/v3/src/app/components/activity/activity.component.html +++ b/projects/v3/src/app/components/activity/activity.component.html @@ -18,27 +18,40 @@

- {activity.tasks.length, plural, =1 {Task} other {Tasks}} + + {activity.tasks.length, plural, =1 {Task} other {Tasks}} + + + + + {activity.tasks.length, plural, =1 {Task} other + {Tasks}} - + title="For team members only"> + +
+ + diff --git a/projects/v3/src/app/components/activity/activity.component.ts b/projects/v3/src/app/components/activity/activity.component.ts index 0f1dd09f7..dd929575f 100644 --- a/projects/v3/src/app/components/activity/activity.component.ts +++ b/projects/v3/src/app/components/activity/activity.component.ts @@ -1,6 +1,6 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { SharedService } from '@v3/app/services/shared.service'; -import { Activity, Task } from '@v3/services/activity.service'; +import { Activity, ActivityService, Task } from '@v3/services/activity.service'; import { Submission } from '@v3/services/assessment.service'; import { NotificationsService } from '@v3/services/notifications.service'; import { BrowserStorageService } from '@v3/services/storage.service'; @@ -11,17 +11,35 @@ import { UtilsService } from '@v3/services/utils.service'; templateUrl: './activity.component.html', styleUrls: ['./activity.component.scss'], }) -export class ActivityComponent { +export class ActivityComponent implements OnChanges { @Input() activity: Activity; @Input() currentTask: Task; @Input() submission: Submission; @Output() navigate = new EventEmitter(); + + // when user isn't in a team & all tasks are found to be team tasks, emit this event + // true: user not allowed to access + // false: at least one non-team task + @Output() cannotAccessTeamActivity = new EventEmitter(); + isForTeamOnly: boolean = false; + constructor( private utils: UtilsService, private storageService: BrowserStorageService, private notificationsService: NotificationsService, private sharedService: SharedService, - ) { } + private activityService: ActivityService + ) {} + + ngOnChanges(changes: SimpleChanges): void { + if (changes.activity?.currentValue?.tasks?.length > 0) { + this.activityService.nonTeamActivity(changes.activity.currentValue?.tasks).then((nonTeamActivity) => { + this.isForTeamOnly = !nonTeamActivity; + this.cannotAccessTeamActivity.emit(this.isForTeamOnly); + }); + } + } + /** * Task icon type diff --git a/projects/v3/src/app/components/topic/topic.component.html b/projects/v3/src/app/components/topic/topic.component.html index 3dabeeec6..1299042cc 100644 --- a/projects/v3/src/app/components/topic/topic.component.html +++ b/projects/v3/src/app/components/topic/topic.component.html @@ -36,7 +36,8 @@ diff --git a/projects/v3/src/app/components/topic/topic.component.ts b/projects/v3/src/app/components/topic/topic.component.ts index 5739f6f9d..0919e90c9 100644 --- a/projects/v3/src/app/components/topic/topic.component.ts +++ b/projects/v3/src/app/components/topic/topic.component.ts @@ -9,6 +9,8 @@ import { EmbedVideoService } from '@v3/services/ngx-embed-video.service'; import { SafeHtml } from '@angular/platform-browser'; import { FilestackService } from '@v3/app/services/filestack.service'; import { NotificationsService } from '@v3/app/services/notifications.service'; +import { BehaviorSubject } from 'rxjs'; +import { Activity, Task } from '@v3/app/services/activity.service'; @Component({ selector: 'app-topic', @@ -17,8 +19,11 @@ import { NotificationsService } from '@v3/app/services/notifications.service'; }) export class TopicComponent implements OnChanges { @Input() topic: Topic; + @Input() task: Task; continuing: boolean; @Output() continue = new EventEmitter(); + @Input() buttonDisabled$: BehaviorSubject = new BehaviorSubject(false); + thisTask iframeHtml = '' as SafeHtml; btnToggleTopicIsDone = false; @@ -42,14 +47,10 @@ export class TopicComponent implements OnChanges { } this._initVideoPlayer(); } - // mark topic as started after topic load - // this._markAsStartStop('started'); } ionViewWillLeave() { this.sharedService.stopPlayingVideos(); - // mark topic as stopped when leave topic page - // this._markAsStartStop('stopped'); } private _setVideoUrlElelemts() { diff --git a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.html b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.html index aa4410b37..1a3a4bb7c 100644 --- a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.html +++ b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.html @@ -25,34 +25,45 @@ [currentTask]="currentTask" [submission]="submission" (navigate)="goToTask($event)" + (cannotAccessTeamActivity)="allTeamTasks($event)" > - - - - + +
+ This assessment can only be accessed when you are in a team. +
+ + + + + + + + diff --git a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.scss b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.scss index 2839b5803..5edc8139b 100644 --- a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.scss +++ b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.scss @@ -23,3 +23,11 @@ ion-content { .no-review { padding: 10px 15%; } + +.centered-text { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + text-align: center; +} diff --git a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts index 1db9c6691..cfc41ab98 100644 --- a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts +++ b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts @@ -8,7 +8,7 @@ import { BrowserStorageService } from '@v3/app/services/storage.service'; import { Topic, TopicService } from '@v3/app/services/topic.service'; import { UtilsService } from '@v3/app/services/utils.service'; import { BehaviorSubject, Subscription } from 'rxjs'; -import { delay, tap } from 'rxjs/operators'; +import { delay, filter, tap } from 'rxjs/operators'; const SAVE_PROGRESS_TIMEOUT = 10000; @@ -28,6 +28,7 @@ export class ActivityDesktopPage { loading: boolean; savingText$: BehaviorSubject = new BehaviorSubject(''); btnDisabled$: BehaviorSubject = new BehaviorSubject(false); + notInATeamAndForTeamOnly: boolean = false; // grabs from URL parameter urlParams = { @@ -49,7 +50,11 @@ export class ActivityDesktopPage { ionViewWillEnter() { this.subscriptions.push( - this.activityService.activity$.subscribe(res => this.activity = res) + this.activityService.activity$ + .pipe(filter(res => res?.id === +this.route.snapshot.paramMap.get('id'))) + .subscribe(res => { + this.activity = res; + }) ); this.subscriptions.push( this.activityService.currentTask$.subscribe(res => this.currentTask = res) @@ -105,6 +110,11 @@ export class ActivityDesktopPage { })); } + ionViewWillLeave() { + this.currentTask = null; + this.topicService.clearTopic(); + } + ionViewDidLeave() { this.subscriptions.forEach(sub => { if (sub.closed !== true) { @@ -114,6 +124,7 @@ export class ActivityDesktopPage { } async goToTask(task: Task): Promise { + this.currentTask = null; const taskContentElement = this.document.getElementById('task-content'); if (taskContentElement) { taskContentElement.focus(); @@ -123,6 +134,7 @@ export class ActivityDesktopPage { } async topicComplete(task: Task) { + this.btnDisabled$.next(true); if (task.status === 'done') { // just go to the next task without any other action this.btnDisabled$.next(false); @@ -131,9 +143,11 @@ export class ActivityDesktopPage { // mark the topic as complete this.loading = true; await this.topicService.updateTopicProgress(task.id, 'completed').toPromise(); + // get the latest activity tasks and navigate to the next task return this.activityService.getActivity(this.activity.id, true, task, () => { this.loading = false; + this.btnDisabled$.next(false); }); } @@ -197,14 +211,14 @@ export class ActivityDesktopPage { } } - async readFeedback(submissionId, task: Task) { + async readFeedback(submissionId, currentTask: Task) { try { this.loading = true; const savedReview = this.assessmentService.saveFeedbackReviewed(submissionId); await savedReview.pipe( // get the latest activity tasks and navigate to the next task // wait for a while for the server to save the "read feedback" status - tap(() => this.activityService.getActivity(this.activity.id, true, task)), + tap(() => this.activityService.getActivity(this.activity.id, true, currentTask)), delay(400) ).toPromise(); await this.reviewRatingPopUp(); @@ -233,6 +247,12 @@ export class ActivityDesktopPage { } goBack() { + this.currentTask = null; + this.topicService.clearTopic(); this.router.navigate(['v3', 'home']); } + + allTeamTasks(forTeamOnlyWarning: boolean) { + this.notInATeamAndForTeamOnly = forTeamOnlyWarning; + } } diff --git a/projects/v3/src/app/pages/home/home.page.html b/projects/v3/src/app/pages/home/home.page.html index 101cf4841..d4441a043 100644 --- a/projects/v3/src/app/pages/home/home.page.html +++ b/projects/v3/src/app/pages/home/home.page.html @@ -128,7 +128,8 @@

+ diff --git a/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.ts b/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.ts index 2e9634170..4a3e111f6 100644 --- a/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.ts +++ b/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { ActivityService, Task } from '@v3/app/services/activity.service'; import { TopicService, Topic } from '@v3/app/services/topic.service'; +import { BehaviorSubject } from 'rxjs'; @Component({ selector: 'app-topic-mobile', @@ -10,10 +11,11 @@ import { TopicService, Topic } from '@v3/app/services/topic.service'; }) export class TopicMobilePage implements OnInit { topic$ = this.topicService.topic$; + btnDisabled$: BehaviorSubject = new BehaviorSubject(false); topic: Topic; activityId: number; - currentTask: Task + currentTask: Task; constructor( private route: ActivatedRoute, @@ -32,6 +34,7 @@ export class TopicMobilePage implements OnInit { } async continue() { + this.btnDisabled$.next(true); if (!this.currentTask) { this.currentTask = { id: this.topic.id, @@ -40,16 +43,20 @@ export class TopicMobilePage implements OnInit { status: '' }; } + if (this.currentTask.status === 'done') { // just go to the next task without any other action - await this.activityService.goToNextTask(this.currentTask); - // this.btnDisabled$.next(false); - conflicts from [CORE-6106] + this.activityService.goToNextTask(this.currentTask); + this.btnDisabled$.next(false); return; } + // mark the topic as completer - await this.topicService.updateTopicProgress(this.topic.id, 'completed').subscribe(); + await this.topicService.updateTopicProgress(this.topic.id, 'completed').toPromise(); // get the latest activity tasks and navigate to the next task - return this.activityService.getActivity(this.activityId, true, this.currentTask); + return this.activityService.getActivity(this.activityId, true, this.currentTask, () => { + this.btnDisabled$.next(false); + }); } goBack() { diff --git a/projects/v3/src/app/services/activity.service.ts b/projects/v3/src/app/services/activity.service.ts index 2d6abd99b..39f112378 100644 --- a/projects/v3/src/app/services/activity.service.ts +++ b/projects/v3/src/app/services/activity.service.ts @@ -191,6 +191,7 @@ export class ActivityService { }; } }); + this._activity$.next(result); this.activity = result; if (goToNextTask) { @@ -248,6 +249,12 @@ export class ActivityService { // if there is no next task if (!nextTask) { if (afterTask) { + this.assessment.getAssessment( + afterTask.id, + 'assessment', + this.activity.id, + afterTask.contextId + ); return this._activityCompleted(hasUnfinishedTask); } nextTask = tasks[0]; // go to the first task @@ -305,4 +312,27 @@ export class ActivityService { } } + + /** + * @name nonTeamActivity + * @description check if the activity is accessible by current + * user (team or individual assessment). + * When milestone contain only team assessment, only participant from a team + * can access the activities. + * + * @param {number} activityId + * + * @return {Promise} false when inaccessible, otherwise true + */ + async nonTeamActivity(tasks?: Task[]): Promise { + const teamStatus = await this.sharedService.getTeamInfo().toPromise(); + if (teamStatus?.data?.user?.teams.length > 0) { + return true; + } + + const nonTeamAsmt = (tasks || []) + .filter((task: Task) => task.isForTeam !== true); + + return nonTeamAsmt.length > 0; + } } diff --git a/projects/v3/src/app/services/assessment.service.ts b/projects/v3/src/app/services/assessment.service.ts index a98b24b07..7d1bb9f04 100644 --- a/projects/v3/src/app/services/assessment.service.ts +++ b/projects/v3/src/app/services/assessment.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, of } from 'rxjs'; +import { BehaviorSubject, of, Subscription } from 'rxjs'; import { map, shareReplay } from 'rxjs/operators'; import { RequestService } from 'request'; import { UtilsService } from '@v3/services/utils.service'; @@ -136,7 +136,7 @@ export class AssessmentService { this._assessment$.next(null); } - getAssessment(id, action, activityId, contextId, submissionId?) { + getAssessment(id, action, activityId, contextId, submissionId?): Subscription { if (!this.assessment || this.assessment.id !== id) { this.clearAssessment(); } diff --git a/projects/v3/src/app/services/home.service.ts b/projects/v3/src/app/services/home.service.ts index 5d522e2c1..5b71e3589 100644 --- a/projects/v3/src/app/services/home.service.ts +++ b/projects/v3/src/app/services/home.service.ts @@ -7,7 +7,7 @@ import { ApolloService } from './apollo.service'; import { NotificationsService } from './notifications.service'; import { AuthService } from './auth.service'; import { SharedService } from './shared.service'; -import { ActivityService, TaskBase } from './activity.service'; +import { ActivityBase, ActivityService, Task, TaskBase } from './activity.service'; export interface Experience { leadImage: string; @@ -186,28 +186,4 @@ export class HomeService { this._projectProgress$.next(data.data.project); this._experienceProgress$.next(Math.round(data.data.project.progress * 100)); } - - /** - * @name isAccessible - * @description check if the activity is accessible by current - * user (team or individual assessment). - * When milestone contain only team assessment, only participant from a team - * can access the activities. - * - * @param {number} activityId - * - * @return {Promise} false when inaccessible, otherwise true - */ - async isAccessible(activityId: number): Promise { - const teamStatus = await this.sharedServise.getTeamInfo().toPromise(); - if (teamStatus?.data?.user?.teams.length > 0) { - return true; - } - - const activitiesBase = await this.activityService.getActivityBase(activityId).toPromise(); - const nonTeamAsmt = (activitiesBase?.data?.activity?.tasks || []) - .filter((task: TaskBase) => task.isTeam !== true); - - return nonTeamAsmt.length > 0; - } }