MOBILE-3934 competency: Add swipe navigation
parent
c549e733fb
commit
d2b716da8d
|
@ -0,0 +1,97 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||||
|
import { CoreUserProfile } from '@features/user/services/user';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import {
|
||||||
|
AddonCompetency,
|
||||||
|
AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||||
|
AddonCompetencyDataForCourseCompetenciesPageWSResponse,
|
||||||
|
} from '../services/competency';
|
||||||
|
import { AddonCompetencyHelper } from '../services/competency-helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a collection of course competencies.
|
||||||
|
*/
|
||||||
|
export class AddonCompetencyCourseCompetenciesSource
|
||||||
|
extends CoreRoutedItemsManagerSource<AddonCompetencyDataForCourseCompetenciesPageCompetency> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
static getSourceId(courseId: number, userId?: number): string {
|
||||||
|
return `${courseId}-${userId || 'current-user'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly COURSE_ID: number;
|
||||||
|
readonly USER_ID?: number;
|
||||||
|
|
||||||
|
courseCompetencies?: AddonCompetencyDataForCourseCompetenciesPageWSResponse;
|
||||||
|
user?: CoreUserProfile;
|
||||||
|
|
||||||
|
constructor(courseId: number, userId?: number) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.COURSE_ID = courseId;
|
||||||
|
this.USER_ID = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getItemPath(competency: AddonCompetencyDataForCourseCompetenciesPageCompetency): string {
|
||||||
|
return String(competency.competency.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async load(): Promise<void> {
|
||||||
|
if (this.dirty || !this.courseCompetencies) {
|
||||||
|
await this.loadCourseCompetencies();
|
||||||
|
}
|
||||||
|
|
||||||
|
await super.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate course cache.
|
||||||
|
*/
|
||||||
|
async invalidateCache(): Promise<void> {
|
||||||
|
await CoreUtils.ignoreErrors(AddonCompetency.invalidateCourseCompetencies(this.COURSE_ID, this.USER_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected async loadPageItems(): Promise<{ items: AddonCompetencyDataForCourseCompetenciesPageCompetency[] }> {
|
||||||
|
if (!this.courseCompetencies) {
|
||||||
|
throw new Error('Can\'t load competencies without course data');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { items: this.courseCompetencies.competencies };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load competencies.
|
||||||
|
*/
|
||||||
|
private async loadCourseCompetencies(): Promise<void> {
|
||||||
|
[this.courseCompetencies, this.user] = await Promise.all([
|
||||||
|
AddonCompetency.getCourseCompetencies(this.COURSE_ID, this.USER_ID),
|
||||||
|
AddonCompetencyHelper.getProfile(this.USER_ID),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||||
|
import { CoreUserProfile } from '@features/user/services/user';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import {
|
||||||
|
AddonCompetency,
|
||||||
|
AddonCompetencyDataForPlanPageCompetency,
|
||||||
|
AddonCompetencyDataForPlanPageWSResponse,
|
||||||
|
} from '../services/competency';
|
||||||
|
import { AddonCompetencyHelper } from '../services/competency-helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a collection of plan competencies.
|
||||||
|
*/
|
||||||
|
export class AddonCompetencyPlanCompetenciesSource extends CoreRoutedItemsManagerSource<AddonCompetencyDataForPlanPageCompetency> {
|
||||||
|
|
||||||
|
readonly PLAN_ID: number;
|
||||||
|
|
||||||
|
plan?: AddonCompetencyDataForPlanPageWSResponse;
|
||||||
|
user?: CoreUserProfile;
|
||||||
|
|
||||||
|
constructor(planId: number) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.PLAN_ID = planId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getItemPath(competency: AddonCompetencyDataForPlanPageCompetency): string {
|
||||||
|
return String(competency.competency.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async load(): Promise<void> {
|
||||||
|
if (this.dirty || !this.plan) {
|
||||||
|
await this.loadLearningPlan();
|
||||||
|
}
|
||||||
|
|
||||||
|
await super.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate plan cache.
|
||||||
|
*/
|
||||||
|
async invalidateCache(): Promise<void> {
|
||||||
|
await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlan(this.PLAN_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected async loadPageItems(): Promise<{ items: AddonCompetencyDataForPlanPageCompetency[] }> {
|
||||||
|
if (!this.plan) {
|
||||||
|
throw new Error('Can\'t load competencies without plan!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return { items: this.plan.competencies };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load learning plan.
|
||||||
|
*/
|
||||||
|
private async loadLearningPlan(): Promise<void> {
|
||||||
|
this.plan = await AddonCompetency.getLearningPlan(this.PLAN_ID);
|
||||||
|
this.plan.plan.statusname = AddonCompetencyHelper.getPlanStatusName(this.plan.plan.status);
|
||||||
|
|
||||||
|
// Get the user profile image.
|
||||||
|
this.user = await AddonCompetencyHelper.getProfile(this.plan.plan.userid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||||
|
import { ADDON_COMPETENCY_COMPETENCIES_PAGE } from '../competency.module';
|
||||||
|
import { AddonCompetency, AddonCompetencyPlan, AddonCompetencyProvider } from '../services/competency';
|
||||||
|
import { AddonCompetencyHelper } from '../services/competency-helper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a collection of learning plans.
|
||||||
|
*/
|
||||||
|
export class AddonCompetencyPlansSource extends CoreRoutedItemsManagerSource<AddonCompetencyPlanFormatted> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
static getSourceId(userId?: number): string {
|
||||||
|
return userId ? String(userId) : 'current-user';
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly USER_ID?: number;
|
||||||
|
|
||||||
|
constructor(userId?: number) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.USER_ID = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getItemPath(plan: AddonCompetencyPlanFormatted): string {
|
||||||
|
return `${plan.id}/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getItemQueryParams(): Params {
|
||||||
|
if (this.USER_ID) {
|
||||||
|
return { userId: this.USER_ID };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate learning plans cache.
|
||||||
|
*/
|
||||||
|
async invalidateCache(): Promise<void> {
|
||||||
|
await AddonCompetency.invalidateLearningPlans(this.USER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected async loadPageItems(): Promise<{ items: AddonCompetencyPlanFormatted[] }> {
|
||||||
|
const plans = await AddonCompetency.getLearningPlans(this.USER_ID);
|
||||||
|
|
||||||
|
plans.forEach((plan: AddonCompetencyPlanFormatted) => {
|
||||||
|
plan.statusname = AddonCompetencyHelper.getPlanStatusName(plan.status);
|
||||||
|
switch (plan.status) {
|
||||||
|
case AddonCompetencyProvider.STATUS_ACTIVE:
|
||||||
|
plan.statuscolor = 'success';
|
||||||
|
break;
|
||||||
|
case AddonCompetencyProvider.STATUS_COMPLETE:
|
||||||
|
plan.statuscolor = 'danger';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
plan.statuscolor = 'warning';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return { items: plans };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Competency plan with some calculated data.
|
||||||
|
*/
|
||||||
|
export type AddonCompetencyPlanFormatted = AddonCompetencyPlan & {
|
||||||
|
statuscolor?: string; // Calculated in the app. Color of the plan's status.
|
||||||
|
};
|
|
@ -10,7 +10,7 @@
|
||||||
</ion-title>
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content [core-swipe-navigation]="competencies">
|
||||||
<ion-refresher slot="fixed" [disabled]="!competencyLoaded" (ionRefresh)="refreshCompetency($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!competencyLoaded" (ionRefresh)="refreshCompetency($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
@ -36,9 +36,7 @@
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
|
<p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
|
||||||
<p>
|
<p>
|
||||||
<a *ngIf="competency.competency.comppath.showlinks" [href]="competency.competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' +
|
<a *ngIf="competency.competency.comppath.showlinks" [href]="competencyFrameworkUrl" core-link>
|
||||||
competency.competency.comppath.framework.id + '&pagecontextid=' +
|
|
||||||
competency.competency.comppath.pagecontextid" core-link>
|
|
||||||
{{ competency.competency.comppath.framework.name }}
|
{{ competency.competency.comppath.framework.name }}
|
||||||
</a>
|
</a>
|
||||||
<ng-container *ngIf="!competency.competency.comppath.showlinks">
|
<ng-container *ngIf="!competency.competency.comppath.showlinks">
|
||||||
|
@ -79,7 +77,8 @@
|
||||||
</p>
|
</p>
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let activity of coursemodules" [href]="activity.url"
|
<ion-item class="ion-text-wrap" *ngFor="let activity of coursemodules" [href]="activity.url"
|
||||||
[attr.aria-label]="activity.name" core-link capture="true">
|
[attr.aria-label]="activity.name" core-link capture="true">
|
||||||
<core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl"></core-mod-icon>
|
<core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl">
|
||||||
|
</core-mod-icon>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id"
|
<core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id"
|
||||||
[courseId]="courseId">
|
[courseId]="courseId">
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { AddonCompetencyHelper } from '@addons/competency/services/competency-helper';
|
import { AddonCompetencyHelper } from '@addons/competency/services/competency-helper';
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreCourseModuleSummary } from '@features/course/services/course';
|
import { CoreCourseModuleSummary } from '@features/course/services/course';
|
||||||
import { CoreUserSummary } from '@features/user/services/user';
|
import { CoreUserSummary } from '@features/user/services/user';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
|
@ -25,14 +25,19 @@ import {
|
||||||
AddonCompetencyUserCompetency,
|
AddonCompetencyUserCompetency,
|
||||||
AddonCompetencyUserCompetencyCourse,
|
AddonCompetencyUserCompetencyCourse,
|
||||||
AddonCompetency,
|
AddonCompetency,
|
||||||
AddonCompetencyDataForUserCompetencySummaryInPlanWSResponse,
|
AddonCompetencyDataForPlanPageCompetency,
|
||||||
AddonCompetencyDataForUserCompetencySummaryInCourseWSResponse,
|
AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||||
} from '@addons/competency/services/competency';
|
} from '@addons/competency/services/competency';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { ContextLevel } from '@/core/constants';
|
import { ContextLevel } from '@/core/constants';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
|
import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
|
||||||
|
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||||
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
|
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
|
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the competency information.
|
* Page that displays the competency information.
|
||||||
|
@ -41,13 +46,10 @@ import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.mod
|
||||||
selector: 'page-addon-competency-competency',
|
selector: 'page-addon-competency-competency',
|
||||||
templateUrl: 'competency.html',
|
templateUrl: 'competency.html',
|
||||||
})
|
})
|
||||||
export class AddonCompetencyCompetencyPage implements OnInit {
|
export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
competencyLoaded = false;
|
competencyLoaded = false;
|
||||||
competencyId!: number;
|
competencies!: AddonCompetencyCompetenciesSwipeManager;
|
||||||
planId?: number;
|
|
||||||
courseId?: number;
|
|
||||||
userId?: number;
|
|
||||||
planStatus?: number;
|
planStatus?: number;
|
||||||
coursemodules?: CoreCourseModuleSummary[];
|
coursemodules?: CoreCourseModuleSummary[];
|
||||||
user?: CoreUserSummary;
|
user?: CoreUserSummary;
|
||||||
|
@ -56,17 +58,26 @@ export class AddonCompetencyCompetencyPage implements OnInit {
|
||||||
contextLevel?: string;
|
contextLevel?: string;
|
||||||
contextInstanceId?: number;
|
contextInstanceId?: number;
|
||||||
|
|
||||||
/**
|
constructor() {
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
async ngOnInit(): Promise<void> {
|
|
||||||
try {
|
try {
|
||||||
this.competencyId = CoreNavigator.getRequiredRouteNumberParam('competencyId');
|
const planId = CoreNavigator.getRouteNumberParam('planId');
|
||||||
this.planId = CoreNavigator.getRouteNumberParam('planId');
|
|
||||||
if (!this.planId) {
|
if (!planId) {
|
||||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||||
this.userId = CoreNavigator.getRouteNumberParam('userId');
|
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||||
|
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||||
|
AddonCompetencyCourseCompetenciesSource,
|
||||||
|
[courseId, userId],
|
||||||
|
);
|
||||||
|
|
||||||
|
this.competencies = new AddonCompetencyCompetenciesSwipeManager(source);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlanCompetenciesSource, [planId]);
|
||||||
|
|
||||||
|
this.competencies = new AddonCompetencyCompetenciesSwipeManager(source);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
|
||||||
|
@ -74,24 +85,63 @@ export class AddonCompetencyCompetencyPage implements OnInit {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get competencyFrameworkUrl(): string | undefined {
|
||||||
|
if (!this.competency) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pluginbaseurl, framework, pagecontextid } = this.competency.competency.comppath;
|
||||||
|
|
||||||
|
return `${pluginbaseurl}/competencies.php?competencyframeworkid=${framework.id}&pagecontextid=${pagecontextid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get courseId(): number | undefined {
|
||||||
|
const source = this.competencies.getSource();
|
||||||
|
|
||||||
|
if (!(source instanceof AddonCompetencyCourseCompetenciesSource)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return source.COURSE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
const source = this.competencies.getSource();
|
||||||
|
|
||||||
|
await source.reload();
|
||||||
|
await this.competencies.start();
|
||||||
await this.fetchCompetency();
|
await this.fetchCompetency();
|
||||||
|
|
||||||
const name = this.competency && this.competency.competency && this.competency.competency.competency &&
|
if (!this.competency) {
|
||||||
this.competency.competency.competency.shortname;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.planId) {
|
const name = this.competency.competency.competency.shortname;
|
||||||
CoreUtils.ignoreErrors(AddonCompetency.logCompetencyInPlanView(
|
|
||||||
this.planId,
|
if (source instanceof AddonCompetencyPlanCompetenciesSource) {
|
||||||
this.competencyId,
|
this.planStatus && await CoreUtils.ignoreErrors(
|
||||||
this.planStatus!,
|
AddonCompetency.logCompetencyInPlanView(
|
||||||
name,
|
source.PLAN_ID,
|
||||||
this.userId,
|
this.requireCompetencyId(),
|
||||||
));
|
this.planStatus,
|
||||||
|
name,
|
||||||
|
source.user?.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
CoreUtils.ignoreErrors(
|
await CoreUtils.ignoreErrors(
|
||||||
AddonCompetency.logCompetencyInCourseView(this.courseId!, this.competencyId, name, this.userId),
|
AddonCompetency.logCompetencyInCourseView(
|
||||||
|
source.COURSE_ID,
|
||||||
|
this.requireCompetencyId(),
|
||||||
|
name,
|
||||||
|
source.USER_ID,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -99,47 +149,25 @@ export class AddonCompetencyCompetencyPage implements OnInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.competencies.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the competency and updates the view.
|
* Fetches the competency and updates the view.
|
||||||
*
|
*
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async fetchCompetency(): Promise<void> {
|
protected async fetchCompetency(): Promise<void> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let competency: AddonCompetencyDataForUserCompetencySummaryInPlanWSResponse |
|
const source = this.competencies.getSource();
|
||||||
AddonCompetencyDataForUserCompetencySummaryInCourseWSResponse;
|
|
||||||
|
|
||||||
if (this.planId) {
|
this.competency = source instanceof AddonCompetencyPlanCompetenciesSource
|
||||||
this.planStatus = undefined;
|
? await this.fetchCompetencySummaryFromPlan(source)
|
||||||
|
: await this.fetchCompetencySummaryFromCourse(source);
|
||||||
competency = await AddonCompetency.getCompetencyInPlan(this.planId, this.competencyId);
|
|
||||||
} else if (this.courseId) {
|
|
||||||
competency = await AddonCompetency.getCompetencyInCourse(this.courseId, this.competencyId, this.userId);
|
|
||||||
} else {
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate the context.
|
|
||||||
if (this.courseId) {
|
|
||||||
this.contextLevel = ContextLevel.COURSE;
|
|
||||||
this.contextInstanceId = this.courseId;
|
|
||||||
} else {
|
|
||||||
this.contextLevel = ContextLevel.USER;
|
|
||||||
this.contextInstanceId = this.userId || competency.usercompetencysummary.user.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.competency = competency.usercompetencysummary;
|
|
||||||
this.userCompetency = this.competency.usercompetencyplan || this.competency.usercompetency;
|
|
||||||
|
|
||||||
if ('plan' in competency) {
|
|
||||||
this.planStatus = competency.plan.status;
|
|
||||||
this.competency.usercompetency!.statusname =
|
|
||||||
AddonCompetencyHelper.getCompetencyStatusName(this.competency.usercompetency!.status);
|
|
||||||
} else {
|
|
||||||
this.userCompetency = this.competency.usercompetencycourse;
|
|
||||||
this.coursemodules = competency.coursemodules;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.competency.user.id != CoreSites.getCurrentSiteUserId()) {
|
if (this.competency.user.id != CoreSites.getCurrentSiteUserId()) {
|
||||||
// Get the user profile from the returned object.
|
// Get the user profile from the returned object.
|
||||||
|
@ -163,18 +191,17 @@ export class AddonCompetencyCompetencyPage implements OnInit {
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
*/
|
*/
|
||||||
async refreshCompetency(refresher: IonRefresher): Promise<void> {
|
async refreshCompetency(refresher: IonRefresher): Promise<void> {
|
||||||
try {
|
const source = this.competencies.getSource();
|
||||||
if (this.planId) {
|
|
||||||
await AddonCompetency.invalidateCompetencyInPlan(this.planId, this.competencyId);
|
|
||||||
} else {
|
|
||||||
await AddonCompetency.invalidateCompetencyInCourse(this.courseId!, this.competencyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
} finally {
|
await CoreUtils.ignoreErrors(
|
||||||
this.fetchCompetency().finally(() => {
|
source instanceof AddonCompetencyPlanCompetenciesSource
|
||||||
refresher?.complete();
|
? AddonCompetency.invalidateCompetencyInPlan(source.PLAN_ID, this.requireCompetencyId())
|
||||||
});
|
: AddonCompetency.invalidateCompetencyInCourse(source.COURSE_ID, this.requireCompetencyId(), source.USER_ID),
|
||||||
}
|
);
|
||||||
|
|
||||||
|
this.fetchCompetency().finally(() => {
|
||||||
|
refresher?.complete();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -191,4 +218,91 @@ export class AddonCompetencyCompetencyPage implements OnInit {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get competency id or fail.
|
||||||
|
*
|
||||||
|
* @returns Competency id.
|
||||||
|
*/
|
||||||
|
private requireCompetencyId(): number {
|
||||||
|
const selectedItem = this.competencies.getSelectedItem();
|
||||||
|
|
||||||
|
if (!selectedItem) {
|
||||||
|
throw new Error('Failed to get competency id from selected item');
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedItem.competency.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch competency summary from a plan source.
|
||||||
|
*
|
||||||
|
* @param source Plan competencies source.
|
||||||
|
* @returns Competency summary.
|
||||||
|
*/
|
||||||
|
private async fetchCompetencySummaryFromPlan(
|
||||||
|
source: AddonCompetencyPlanCompetenciesSource,
|
||||||
|
): Promise<AddonCompetencyDataForUserCompetencySummaryWSResponse> {
|
||||||
|
const competency = await AddonCompetency.getCompetencyInPlan(
|
||||||
|
source.PLAN_ID,
|
||||||
|
this.requireCompetencyId(),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.planStatus = competency.plan.status;
|
||||||
|
|
||||||
|
if (competency.usercompetencysummary.usercompetency) {
|
||||||
|
competency.usercompetencysummary.usercompetency.statusname =
|
||||||
|
AddonCompetencyHelper.getCompetencyStatusName(competency.usercompetencysummary.usercompetency.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.contextLevel = ContextLevel.USER;
|
||||||
|
this.contextInstanceId = source.user?.id || competency.usercompetencysummary.user.id;
|
||||||
|
this.userCompetency = competency.usercompetencysummary.usercompetencyplan
|
||||||
|
|| competency.usercompetencysummary.usercompetency;
|
||||||
|
|
||||||
|
return competency.usercompetencysummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch competency summary from a course source.
|
||||||
|
*
|
||||||
|
* @param source Course competencies source.
|
||||||
|
* @returns Competency summary.
|
||||||
|
*/
|
||||||
|
private async fetchCompetencySummaryFromCourse(
|
||||||
|
source: AddonCompetencyCourseCompetenciesSource,
|
||||||
|
): Promise<AddonCompetencyDataForUserCompetencySummaryWSResponse> {
|
||||||
|
const competency = await AddonCompetency.getCompetencyInCourse(
|
||||||
|
source.COURSE_ID,
|
||||||
|
this.requireCompetencyId(),
|
||||||
|
source.USER_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.coursemodules = competency.coursemodules;
|
||||||
|
|
||||||
|
this.contextLevel = ContextLevel.COURSE;
|
||||||
|
this.contextInstanceId = source.COURSE_ID;
|
||||||
|
this.userCompetency = competency.usercompetencysummary.usercompetencycourse
|
||||||
|
|| competency.usercompetencysummary.usercompetency;
|
||||||
|
|
||||||
|
return competency.usercompetencysummary;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to manage swiping within a collection of competencies.
|
||||||
|
*/
|
||||||
|
class AddonCompetencyCompetenciesSwipeManager
|
||||||
|
extends CoreSwipeNavigationItemsManager<
|
||||||
|
AddonCompetencyDataForPlanPageCompetency | AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||||
|
AddonCompetencyPlanCompetenciesSource | AddonCompetencyCourseCompetenciesSource
|
||||||
|
> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||||
|
return route.params.competencyId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,35 +9,35 @@
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-refresher slot="fixed" [disabled]="!competenciesLoaded" (ionRefresh)="refreshCourseCompetencies($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!competencies.loaded" (ionRefresh)="refreshCourseCompetencies($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="competenciesLoaded">
|
<core-loading [hideUntil]="competencies.loaded">
|
||||||
<ion-card *ngIf="!user && competencies && competencies.statistics.competencycount > 0">
|
<ion-card *ngIf="!user && courseCompetencies && courseCompetencies.statistics.competencycount > 0">
|
||||||
<ng-container *ngIf="competencies.cangradecompetencies">
|
<ng-container *ngIf="courseCompetencies.cangradecompetencies">
|
||||||
<ion-item class="ion-text-wrap" *ngIf="competencies.settings.pushratingstouserplans">
|
<ion-item class="ion-text-wrap" *ngIf="courseCompetencies.settings.pushratingstouserplans">
|
||||||
<ion-label>{{ 'addon.competency.coursecompetencyratingsarepushedtouserplans' | translate }}</ion-label>
|
<ion-label>{{ 'addon.competency.coursecompetencyratingsarepushedtouserplans' | translate }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap" *ngIf="!competencies.settings.pushratingstouserplans" color="danger">
|
<ion-item class="ion-text-wrap" *ngIf="!courseCompetencies.settings.pushratingstouserplans" color="danger">
|
||||||
<ion-label>{{ 'addon.competency.coursecompetencyratingsarenotpushedtouserplans' | translate }}</ion-label>
|
<ion-label>{{ 'addon.competency.coursecompetencyratingsarenotpushedtouserplans' | translate }}</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ion-item class="ion-text-wrap" *ngIf="competencies.statistics.canbegradedincourse">
|
<ion-item class="ion-text-wrap" *ngIf="courseCompetencies.statistics.canbegradedincourse">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<span id="addon-competency-course-{{courseId}}-progress">
|
<span id="addon-competency-course-{{courseId}}-progress">
|
||||||
{{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate: {$a:
|
{{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate: {$a:
|
||||||
{x: competencies.statistics.proficientcompetencycount, y: competencies.statistics.competencycount} } }}
|
{x: courseCompetencies.statistics.proficientcompetencycount, y: courseCompetencies.statistics.competencycount} } }}
|
||||||
</span>
|
</span>
|
||||||
<core-progress-bar [progress]="competencies.statistics.proficientcompetencypercentage"
|
<core-progress-bar [progress]="courseCompetencies.statistics.proficientcompetencypercentage"
|
||||||
ariaDescribedBy="addon-competency-course-{{courseId}}-progress">
|
ariaDescribedBy="addon-competency-course-{{courseId}}-progress">
|
||||||
</core-progress-bar>
|
</core-progress-bar>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap"
|
<ion-item class="ion-text-wrap"
|
||||||
*ngIf="competencies.statistics.canmanagecoursecompetencies && competencies.statistics.leastproficientcount > 0">
|
*ngIf="courseCompetencies.statistics.canmanagecoursecompetencies && courseCompetencies.statistics.leastproficientcount > 0">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading">{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}</p>
|
<p class="item-heading">{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}</p>
|
||||||
<p *ngFor="let comp of competencies.statistics.leastproficient">
|
<p *ngFor="let comp of courseCompetencies.statistics.leastproficient">
|
||||||
<button class="as-link" (click)="openCompetencySummary(comp.id)">
|
<button class="as-link" (click)="openCompetencySummary(comp.id)">
|
||||||
{{ comp.shortname }} - {{ comp.idnumber }}
|
{{ comp.shortname }} - {{ comp.idnumber }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
|
||||||
<h2 class="ion-margin-horizontal" *ngIf="competencies && competencies.statistics.competencycount > 0">
|
<h2 class="ion-margin-horizontal" *ngIf="courseCompetencies && courseCompetencies.statistics.competencycount > 0">
|
||||||
{{ 'addon.competency.coursecompetencies' | translate }}
|
{{ 'addon.competency.coursecompetencies' | translate }}
|
||||||
</h2>
|
</h2>
|
||||||
<ion-card *ngIf="user">
|
<ion-card *ngIf="user">
|
||||||
|
@ -57,13 +57,13 @@
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
<core-empty-box *ngIf="competencies && competencies.statistics.competencycount == 0" icon="fas-award"
|
<core-empty-box *ngIf="courseCompetencies && courseCompetencies.statistics.competencycount == 0" icon="fas-award"
|
||||||
message="{{ 'addon.competency.nocompetenciesincourse' | translate }}">
|
message="{{ 'addon.competency.nocompetenciesincourse' | translate }}">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
|
|
||||||
<div *ngIf="competencies">
|
<div *ngIf="competencies.loaded">
|
||||||
<ion-card *ngFor="let competency of competencies.competencies">
|
<ion-card *ngFor="let competency of competencies.items">
|
||||||
<ion-item class="ion-text-wrap" (click)="openCompetency(competency.competency.id)"
|
<ion-item class="ion-text-wrap" (click)="competencies.select(competency)"
|
||||||
[attr.aria-label]="competency.competency.shortname" detail="true" button>
|
[attr.aria-label]="competency.competency.shortname" detail="true" button>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading">
|
<p class="item-heading">
|
||||||
|
@ -85,8 +85,7 @@
|
||||||
<div>
|
<div>
|
||||||
<p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
|
<p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
|
||||||
<p>
|
<p>
|
||||||
<a *ngIf="competency.comppath.showlinks" [href]="competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' +
|
<a *ngIf="competency.comppath.showlinks" [href]="getCompetencyFrameworkUrl(competency)" core-link
|
||||||
competency.comppath.framework.id + '&pagecontextid=' + competency.comppath.pagecontextid" core-link
|
|
||||||
[title]="competency.comppath.framework.name">
|
[title]="competency.comppath.framework.name">
|
||||||
{{ competency.comppath.framework.name }}
|
{{ competency.comppath.framework.name }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -104,7 +103,7 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="competencies.statistics.canmanagecoursecompetencies">
|
<div *ngIf="courseCompetencies?.statistics.canmanagecoursecompetencies">
|
||||||
<p class="item-heading">{{ 'addon.competency.uponcoursecompletion' | translate }}</p>
|
<p class="item-heading">{{ 'addon.competency.uponcoursecompletion' | translate }}</p>
|
||||||
<ng-container *ngFor="let ruleoutcome of competency.ruleoutcomeoptions">
|
<ng-container *ngFor="let ruleoutcome of competency.ruleoutcomeoptions">
|
||||||
<span *ngIf="ruleoutcome.selected">{{ ruleoutcome.text }}</span>
|
<span *ngIf="ruleoutcome.selected">{{ ruleoutcome.text }}</span>
|
||||||
|
|
|
@ -12,15 +12,20 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { AddonCompetencyDataForCourseCompetenciesPageWSResponse, AddonCompetency } from '@addons/competency/services/competency';
|
import {
|
||||||
import { AddonCompetencyHelper } from '@addons/competency/services/competency-helper';
|
AddonCompetencyDataForCourseCompetenciesPageWSResponse,
|
||||||
|
AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||||
|
} from '@addons/competency/services/competency';
|
||||||
import { CoreUserProfile } from '@features/user/services/user';
|
import { CoreUserProfile } from '@features/user/services/user';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { ContextLevel } from '@/core/constants';
|
import { ContextLevel } from '@/core/constants';
|
||||||
import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
|
import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
|
||||||
|
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||||
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
|
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of competencies of a course.
|
* Page that displays the list of competencies of a course.
|
||||||
|
@ -29,22 +34,23 @@ import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.mod
|
||||||
selector: 'page-addon-competency-coursecompetencies',
|
selector: 'page-addon-competency-coursecompetencies',
|
||||||
templateUrl: 'coursecompetencies.html',
|
templateUrl: 'coursecompetencies.html',
|
||||||
})
|
})
|
||||||
export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
competenciesLoaded = false;
|
competencies!: CoreListItemsManager<
|
||||||
competencies?: AddonCompetencyDataForCourseCompetenciesPageWSResponse;
|
AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||||
user?: CoreUserProfile;
|
AddonCompetencyCourseCompetenciesSource
|
||||||
courseId!: number;
|
>;
|
||||||
|
|
||||||
protected userId?: number;
|
constructor() {
|
||||||
|
|
||||||
/**
|
|
||||||
* View loaded.
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
try {
|
try {
|
||||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||||
this.userId = CoreNavigator.getRouteNumberParam('userId');
|
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||||
|
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||||
|
AddonCompetencyCourseCompetenciesSource,
|
||||||
|
[courseId, userId],
|
||||||
|
);
|
||||||
|
|
||||||
|
this.competencies = new CoreListItemsManager(source, AddonCompetencyCourseCompetenciesPage);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
|
||||||
|
@ -52,10 +58,50 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.fetchCourseCompetencies().finally(() => {
|
get courseCompetencies(): AddonCompetencyDataForCourseCompetenciesPageWSResponse | undefined {
|
||||||
this.competenciesLoaded = true;
|
return this.competencies.getSource().courseCompetencies;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
get courseId(): number {
|
||||||
|
return this.competencies.getSource().COURSE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
get user(): CoreUserProfile | undefined {
|
||||||
|
return this.competencies.getSource().user;
|
||||||
|
}
|
||||||
|
|
||||||
|
get showLeastProficientCompetencies(): boolean {
|
||||||
|
return !!this.courseCompetencies?.statistics.canmanagecoursecompetencies
|
||||||
|
&& this.courseCompetencies?.statistics.leastproficientcount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
await this.fetchCourseCompetencies();
|
||||||
|
await this.competencies.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.competencies.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get competency framework url.
|
||||||
|
*
|
||||||
|
* @param competency Competency.
|
||||||
|
* @returns Competency framework url.
|
||||||
|
*/
|
||||||
|
getCompetencyFrameworkUrl(competency: AddonCompetencyDataForCourseCompetenciesPageCompetency): string {
|
||||||
|
const { pluginbaseurl, framework, pagecontextid } = competency.comppath;
|
||||||
|
|
||||||
|
return `${pluginbaseurl}/competencies.php?competencyframeworkid=${framework.id}&pagecontextid=${pagecontextid}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,24 +111,12 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
||||||
*/
|
*/
|
||||||
protected async fetchCourseCompetencies(): Promise<void> {
|
protected async fetchCourseCompetencies(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.competencies = await AddonCompetency.getCourseCompetencies(this.courseId, this.userId);
|
await this.competencies.getSource().reload();
|
||||||
|
|
||||||
// Get the user profile image.
|
|
||||||
this.user = await AddonCompetencyHelper.getProfile(this.userId);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting course competencies data.');
|
CoreDomUtils.showErrorModalDefault(error, 'Error getting course competencies data.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens a competency.
|
|
||||||
*
|
|
||||||
* @param competencyId
|
|
||||||
*/
|
|
||||||
openCompetency(competencyId: number): void {
|
|
||||||
CoreNavigator.navigate('./' + competencyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the summary of a competency.
|
* Opens the summary of a competency.
|
||||||
*
|
*
|
||||||
|
@ -105,11 +139,11 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
||||||
*
|
*
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
*/
|
*/
|
||||||
refreshCourseCompetencies(refresher?: IonRefresher): void {
|
async refreshCourseCompetencies(refresher?: IonRefresher): Promise<void> {
|
||||||
AddonCompetency.invalidateCourseCompetencies(this.courseId, this.userId).finally(() => {
|
await this.competencies.getSource().invalidateCache();
|
||||||
this.fetchCourseCompetencies().finally(() => {
|
|
||||||
refresher?.complete();
|
this.fetchCourseCompetencies().finally(() => {
|
||||||
});
|
refresher?.complete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
</ion-title>
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content [core-swipe-navigation]="plans">
|
||||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshLearningPlan($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!competencies.loaded" (ionRefresh)="refreshLearningPlan($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="loaded">
|
<core-loading [hideUntil]="competencies.loaded">
|
||||||
<ion-card *ngIf="user">
|
<ion-card *ngIf="user">
|
||||||
<ion-item class="ion-text-wrap">
|
<ion-item class="ion-text-wrap">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
|
@ -74,9 +74,8 @@
|
||||||
<p>{{ 'addon.competency.nocompetencies' | translate }}</p>
|
<p>{{ 'addon.competency.nocompetencies' | translate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let competency of plan.competencies"
|
<ion-item class="ion-text-wrap" *ngFor="let competency of competencies.items" (click)="competencies.select(competency)"
|
||||||
(click)="openCompetency(competency.competency.id)" [attr.aria-label]="competency.competency.shortname" detail="true"
|
[attr.aria-label]="competency.competency.shortname" detail="true" button>
|
||||||
button>
|
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading">{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></p>
|
<p class="item-heading">{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
|
|
|
@ -12,13 +12,17 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { AddonCompetencyDataForPlanPageWSResponse, AddonCompetency } from '../../services/competency';
|
import { AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForPlanPageWSResponse } from '../../services/competency';
|
||||||
import { AddonCompetencyHelper } from '../../services/competency-helper';
|
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreUserProfile } from '@features/user/services/user';
|
import { CoreUserProfile } from '@features/user/services/user';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||||
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
|
import { AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source';
|
||||||
|
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||||
|
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a learning plan.
|
* Page that displays a learning plan.
|
||||||
|
@ -27,19 +31,26 @@ import { IonRefresher } from '@ionic/angular';
|
||||||
selector: 'page-addon-competency-plan',
|
selector: 'page-addon-competency-plan',
|
||||||
templateUrl: 'plan.html',
|
templateUrl: 'plan.html',
|
||||||
})
|
})
|
||||||
export class AddonCompetencyPlanPage implements OnInit {
|
export class AddonCompetencyPlanPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
protected planId!: number;
|
plans!: CoreSwipeNavigationItemsManager;
|
||||||
loaded = false;
|
competencies!: CoreListItemsManager<AddonCompetencyDataForPlanPageCompetency, AddonCompetencyPlanCompetenciesSource>;
|
||||||
plan?: AddonCompetencyDataForPlanPageWSResponse;
|
|
||||||
user?: CoreUserProfile;
|
|
||||||
|
|
||||||
/**
|
constructor() {
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
try {
|
try {
|
||||||
this.planId = CoreNavigator.getRequiredRouteNumberParam('planId');
|
const planId = CoreNavigator.getRequiredRouteNumberParam('planId');
|
||||||
|
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||||
|
const plansSource = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||||
|
AddonCompetencyPlansSource,
|
||||||
|
[userId],
|
||||||
|
);
|
||||||
|
const competenciesSource = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||||
|
AddonCompetencyPlanCompetenciesSource,
|
||||||
|
[planId],
|
||||||
|
);
|
||||||
|
|
||||||
|
this.competencies = new CoreListItemsManager(competenciesSource, AddonCompetencyPlanPage);
|
||||||
|
this.plans = new CoreSwipeNavigationItemsManager(plansSource);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
|
||||||
|
@ -47,10 +58,31 @@ export class AddonCompetencyPlanPage implements OnInit {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.fetchLearningPlan().finally(() => {
|
get plan(): AddonCompetencyDataForPlanPageWSResponse | undefined {
|
||||||
this.loaded = true;
|
return this.competencies.getSource().plan;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
get user(): CoreUserProfile | undefined {
|
||||||
|
return this.competencies.getSource().user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
await this.fetchLearningPlan();
|
||||||
|
await this.plans.start();
|
||||||
|
await this.competencies.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.plans.destroy();
|
||||||
|
this.competencies.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,39 +92,22 @@ export class AddonCompetencyPlanPage implements OnInit {
|
||||||
*/
|
*/
|
||||||
protected async fetchLearningPlan(): Promise<void> {
|
protected async fetchLearningPlan(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const plan = await AddonCompetency.getLearningPlan(this.planId);
|
await this.competencies.getSource().reload();
|
||||||
plan.plan.statusname = AddonCompetencyHelper.getPlanStatusName(plan.plan.status);
|
|
||||||
|
|
||||||
// Get the user profile image.
|
|
||||||
this.user = await AddonCompetencyHelper.getProfile(plan.plan.userid);
|
|
||||||
|
|
||||||
this.plan = plan;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plan data.');
|
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plan data.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to a particular competency.
|
|
||||||
*
|
|
||||||
* @param competencyId
|
|
||||||
*/
|
|
||||||
openCompetency(competencyId: number): void {
|
|
||||||
CoreNavigator.navigate('./' + competencyId, {
|
|
||||||
params: { userId: this.user?.id },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the learning plan.
|
* Refreshes the learning plan.
|
||||||
*
|
*
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
*/
|
*/
|
||||||
refreshLearningPlan(refresher: IonRefresher): void {
|
async refreshLearningPlan(refresher: IonRefresher): Promise<void> {
|
||||||
AddonCompetency.invalidateLearningPlan(this.planId).finally(() => {
|
await this.competencies.getSource().invalidateCache();
|
||||||
this.fetchLearningPlan().finally(() => {
|
|
||||||
refresher?.complete();
|
this.fetchLearningPlan().finally(() => {
|
||||||
});
|
refresher?.complete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,10 @@ import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { AddonCompetencyProvider, AddonCompetencyPlan, AddonCompetency } from '../../services/competency';
|
|
||||||
import { AddonCompetencyHelper } from '../../services/competency-helper';
|
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
import { AddonCompetencyPlanFormatted, AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source';
|
||||||
import { ADDON_COMPETENCY_COMPETENCIES_PAGE } from '@addons/competency/competency.module';
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
import { Params } from '@angular/router';
|
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of learning plans.
|
* Page that displays the list of learning plans.
|
||||||
|
@ -34,13 +32,13 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
|
||||||
protected userId?: number;
|
plans: CoreListItemsManager<AddonCompetencyPlanFormatted, AddonCompetencyPlansSource>;
|
||||||
plans: AddonCompetencyPlanListManager;
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.userId = CoreNavigator.getRouteNumberParam('userId');
|
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||||
|
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlansSource, [userId]);
|
||||||
|
|
||||||
this.plans = new AddonCompetencyPlanListManager(AddonCompetencyPlanListPage, this.userId);
|
this.plans = new CoreListItemsManager(source, AddonCompetencyPlanListPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,23 +57,7 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
protected async fetchLearningPlans(): Promise<void> {
|
protected async fetchLearningPlans(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const plans = await AddonCompetency.getLearningPlans(this.userId);
|
await this.plans.load();
|
||||||
plans.forEach((plan: AddonCompetencyPlanFormatted) => {
|
|
||||||
plan.statusname = AddonCompetencyHelper.getPlanStatusName(plan.status);
|
|
||||||
switch (plan.status) {
|
|
||||||
case AddonCompetencyProvider.STATUS_ACTIVE:
|
|
||||||
plan.statuscolor = 'success';
|
|
||||||
break;
|
|
||||||
case AddonCompetencyProvider.STATUS_COMPLETE:
|
|
||||||
plan.statuscolor = 'danger';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
plan.statuscolor = 'warning';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.plans.setItems(plans);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plans data.');
|
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plans data.');
|
||||||
}
|
}
|
||||||
|
@ -86,11 +68,11 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy {
|
||||||
*
|
*
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
*/
|
*/
|
||||||
refreshLearningPlans(refresher: IonRefresher): void {
|
async refreshLearningPlans(refresher: IonRefresher): Promise<void> {
|
||||||
AddonCompetency.invalidateLearningPlans(this.userId).finally(() => {
|
await this.plans.getSource().invalidateCache();
|
||||||
this.fetchLearningPlans().finally(() => {
|
|
||||||
refresher?.complete();
|
this.fetchLearningPlans().finally(() => {
|
||||||
});
|
refresher?.complete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,43 +84,3 @@ export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Competency plan with some calculated data.
|
|
||||||
*/
|
|
||||||
type AddonCompetencyPlanFormatted = AddonCompetencyPlan & {
|
|
||||||
statuscolor?: string; // Calculated in the app. Color of the plan's status.
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to manage plan list.
|
|
||||||
*/
|
|
||||||
class AddonCompetencyPlanListManager extends CorePageItemsListManager<AddonCompetencyPlanFormatted> {
|
|
||||||
|
|
||||||
private userId?: number;
|
|
||||||
|
|
||||||
constructor(pageComponent: unknown, userId?: number) {
|
|
||||||
super(pageComponent);
|
|
||||||
|
|
||||||
this.userId = userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getItemPath(plan: AddonCompetencyPlanFormatted): string {
|
|
||||||
return `${plan.id}/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getItemQueryParams(): Params {
|
|
||||||
if (this.userId) {
|
|
||||||
return { userId: this.userId };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -64,8 +64,10 @@ export class CoreListItemsManager<
|
||||||
*
|
*
|
||||||
* @param splitView Split view component.
|
* @param splitView Split view component.
|
||||||
*/
|
*/
|
||||||
async start(splitView: CoreSplitViewComponent): Promise<void> {
|
async start(splitView?: CoreSplitViewComponent): Promise<void> {
|
||||||
this.watchSplitViewOutlet(splitView);
|
if (splitView) {
|
||||||
|
this.watchSplitViewOutlet(splitView);
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate current selected item.
|
// Calculate current selected item.
|
||||||
this.updateSelectedItem();
|
this.updateSelectedItem();
|
||||||
|
@ -172,7 +174,7 @@ export class CoreListItemsManager<
|
||||||
protected updateSelectedItem(route: ActivatedRouteSnapshot | null = null): void {
|
protected updateSelectedItem(route: ActivatedRouteSnapshot | null = null): void {
|
||||||
super.updateSelectedItem(route);
|
super.updateSelectedItem(route);
|
||||||
|
|
||||||
if (CoreScreen.isMobile || this.selectedItem !== null || this.splitView?.isNested) {
|
if (CoreScreen.isMobile || this.selectedItem !== null || !this.splitView || this.splitView.isNested) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue