MOBILE-3628 competency: Add competencies and learning plans functionality
parent
717583dc9b
commit
e8830eedaf
|
@ -30,6 +30,7 @@ import { AddonQtypeModule } from './qtype/qtype.module';
|
|||
import { AddonBlogModule } from './blog/blog.module';
|
||||
import { AddonRemoteThemesModule } from './remotethemes/remotethemes.module';
|
||||
import { AddonNotesModule } from './notes/notes.module';
|
||||
import { AddonCompetencyModule } from './competency/competency.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -37,6 +38,7 @@ import { AddonNotesModule } from './notes/notes.module';
|
|||
AddonBadgesModule,
|
||||
AddonBlogModule,
|
||||
AddonCalendarModule,
|
||||
AddonCompetencyModule,
|
||||
AddonCourseCompletionModule,
|
||||
AddonMessagesModule,
|
||||
AddonPrivateFilesModule,
|
||||
|
|
|
@ -17,6 +17,7 @@ import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
|
|||
import { CoreBlockOnlyTitleComponent } from '@features/block/components/only-title-block/only-title-block';
|
||||
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetencyMainMenuHandlerService } from '@addons/competency/services/handlers/mainmenu';
|
||||
|
||||
/**
|
||||
* Block handler.
|
||||
|
@ -39,7 +40,7 @@ export class AddonBlockLearningPlansHandlerService extends CoreBlockBaseHandler
|
|||
title: 'addon.block_learningplans.pluginname',
|
||||
class: 'addon-block-learning-plans',
|
||||
component: CoreBlockOnlyTitleComponent,
|
||||
link: 'AddonCompetencyPlanListPage',
|
||||
link: AddonCompetencyMainMenuHandlerService.PAGE_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module';
|
||||
import { AddonCompetencyCourseCompetenciesPage } from './pages/coursecompetencies/coursecompetencies.page';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AddonCompetencyCourseCompetenciesPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
AddonCompetencyCourseCompetenciesPageModule,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyCourseLazyModule {}
|
|
@ -0,0 +1,108 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { AddonCompetencyPlanPage } from './pages/plan/plan.page';
|
||||
import { AddonCompetencyPlanListPage } from './pages/planlist/planlist.page';
|
||||
import { AddonCompetencyCompetenciesPage } from './pages/competencies/competencies.page';
|
||||
import { AddonCompetencyCompetencyPage } from './pages/competency/competency.page';
|
||||
import { AddonCompetencyCompetencySummaryPage } from './pages/competencysummary/competencysummary.page';
|
||||
import { AddonCompetencyCourseCompetenciesPage } from './pages/coursecompetencies/coursecompetencies.page';
|
||||
import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module';
|
||||
|
||||
const mobileRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
component: AddonCompetencyPlanListPage,
|
||||
},
|
||||
{
|
||||
path: 'competencies/',
|
||||
component: AddonCompetencyCompetenciesPage,
|
||||
},
|
||||
{
|
||||
path: 'competencies/:competencyId',
|
||||
component: AddonCompetencyCompetencyPage,
|
||||
},
|
||||
{
|
||||
path: 'course/:courseId',
|
||||
component: AddonCompetencyCourseCompetenciesPage,
|
||||
},
|
||||
{
|
||||
path: 'summary/:competencyId',
|
||||
component: AddonCompetencyCompetencySummaryPage,
|
||||
},
|
||||
{
|
||||
path: ':planId',
|
||||
component: AddonCompetencyPlanPage,
|
||||
},
|
||||
];
|
||||
|
||||
const tabletRoutes: Routes = [
|
||||
{
|
||||
path: 'summary/:competencyId',
|
||||
component: AddonCompetencyCompetencySummaryPage,
|
||||
},
|
||||
{
|
||||
path: 'competencies',
|
||||
component: AddonCompetencyCompetenciesPage,
|
||||
children: [
|
||||
{
|
||||
path: ':competencyId',
|
||||
component: AddonCompetencyCompetencyPage,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'course/:courseId',
|
||||
component: AddonCompetencyCourseCompetenciesPage,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: AddonCompetencyPlanListPage,
|
||||
children: [
|
||||
{
|
||||
path: ':planId',
|
||||
component: AddonCompetencyPlanPage,
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile),
|
||||
...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CoreSharedModule,
|
||||
AddonCompetencyCourseCompetenciesPageModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonCompetencyPlanPage,
|
||||
AddonCompetencyPlanListPage,
|
||||
AddonCompetencyCompetenciesPage,
|
||||
AddonCompetencyCompetencyPage,
|
||||
AddonCompetencyCompetencySummaryPage,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyLazyModule {}
|
|
@ -0,0 +1,81 @@
|
|||
// (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 { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||
import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-delegate';
|
||||
import { CorePushNotificationsDelegate } from '@features/pushnotifications/services/push-delegate';
|
||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||
import { AddonCompetencyProvider } from './services/competency';
|
||||
import { AddonCompetencyHelperProvider } from './services/competency-helper';
|
||||
import { AddonCompetencyCompetencyLinkHandler } from './services/handlers/competency-link';
|
||||
import { AddonCompetencyCourseOptionHandler } from './services/handlers/course-option';
|
||||
import { AddonCompetencyMainMenuHandler, AddonCompetencyMainMenuHandlerService } from './services/handlers/mainmenu';
|
||||
import { AddonCompetencyPlanLinkHandler } from './services/handlers/plan-link';
|
||||
import { AddonCompetencyPlansLinkHandler } from './services/handlers/plans-link';
|
||||
import { AddonCompetencyPushClickHandler } from './services/handlers/push-click';
|
||||
import { AddonCompetencyUserCompetencyLinkHandler } from './services/handlers/user-competency-link';
|
||||
import { AddonCompetencyUserHandler } from './services/handlers/user';
|
||||
import { Routes } from '@angular/router';
|
||||
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
|
||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
|
||||
|
||||
// List of providers (without handlers).
|
||||
export const ADDON_COMPETENCY_SERVICES: Type<unknown>[] = [
|
||||
AddonCompetencyProvider,
|
||||
AddonCompetencyHelperProvider,
|
||||
];
|
||||
|
||||
const mainMenuChildrenRoutes: Routes = [
|
||||
{
|
||||
path: AddonCompetencyMainMenuHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('./competency-lazy.module').then(m => m.AddonCompetencyLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
const courseIndexRoutes: Routes = [
|
||||
{
|
||||
path: AddonCompetencyMainMenuHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('@addons/competency/competency-course-lazy.module').then(m => m.AddonCompetencyCourseLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreMainMenuTabRoutingModule.forChild(mainMenuChildrenRoutes),
|
||||
CoreMainMenuRoutingModule.forChild({ children: mainMenuChildrenRoutes }),
|
||||
CoreCourseIndexRoutingModule.forChild({ children: courseIndexRoutes }),
|
||||
],
|
||||
exports: [CoreMainMenuRoutingModule],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => async () => {
|
||||
CoreContentLinksDelegate.registerHandler(AddonCompetencyCompetencyLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonCompetencyPlanLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonCompetencyPlansLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonCompetencyUserCompetencyLinkHandler.instance);
|
||||
CoreMainMenuDelegate.registerHandler(AddonCompetencyMainMenuHandler.instance);
|
||||
CoreUserDelegate.registerHandler(AddonCompetencyUserHandler.instance);
|
||||
CoreCourseOptionsDelegate.registerHandler(AddonCompetencyCourseOptionHandler.instance);
|
||||
CorePushNotificationsDelegate.registerClickHandler(AddonCompetencyPushClickHandler.instance);
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyModule {}
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"activities": "Activities",
|
||||
"competencies": "Competencies",
|
||||
"competenciesmostoftennotproficientincourse": "Competencies most often not proficient in this course",
|
||||
"coursecompetencies": "Course competencies",
|
||||
"coursecompetencyratingsarenotpushedtouserplans": "Competency ratings in this course do not affect learning plans.",
|
||||
"coursecompetencyratingsarepushedtouserplans": "Competency ratings in this course are updated immediately in learning plans.",
|
||||
"crossreferencedcompetencies": "Cross-referenced competencies",
|
||||
"duedate": "Due date",
|
||||
"errornocompetenciesfound": "No competencies found",
|
||||
"evidence": "Evidence",
|
||||
"evidence_competencyrule": "The rule of the competency was met.",
|
||||
"evidence_coursecompleted": "The course '{{$a}}' was completed.",
|
||||
"evidence_coursemodulecompleted": "The activity '{{$a}}' was completed.",
|
||||
"evidence_courserestored": "The rating was restored along with the course '{{$a}}'.",
|
||||
"evidence_evidenceofpriorlearninglinked": "The evidence of prior learning '{{$a}}' was linked.",
|
||||
"evidence_evidenceofpriorlearningunlinked": "The evidence of prior learning '{{$a}}' was unlinked.",
|
||||
"evidence_manualoverride": "The competency rating was manually set.",
|
||||
"evidence_manualoverrideincourse": "The competency rating was manually set in the course '{{$a}}'.",
|
||||
"evidence_manualoverrideinplan": "The competency rating was manually set in the learning plan '{{$a}}'.",
|
||||
"learningplancompetencies": "Learning plan competencies",
|
||||
"learningplans": "Learning plans",
|
||||
"myplans": "My learning plans",
|
||||
"noactivities": "No activities",
|
||||
"nocompetencies": "No competencies",
|
||||
"nocompetenciesincourse": "No competencies have been linked to this course.",
|
||||
"nocrossreferencedcompetencies": "No other competencies have been cross-referenced to this competency.",
|
||||
"noevidence": "No evidence",
|
||||
"noplanswerecreated": "No learning plans were created.",
|
||||
"nouserplanswithcompetency": "No learning plans contain this competency.",
|
||||
"path": "Path:",
|
||||
"planstatusactive": "Active",
|
||||
"planstatuscomplete": "Complete",
|
||||
"planstatusdraft": "Draft",
|
||||
"planstatusinreview": "In review",
|
||||
"planstatuswaitingforreview": "Waiting for review",
|
||||
"proficient": "Proficient",
|
||||
"progress": "Progress",
|
||||
"rating": "Rating",
|
||||
"reviewstatus": "Review status",
|
||||
"status": "Status",
|
||||
"template": "Learning plan template",
|
||||
"uponcoursecompletion": "Upon course completion:",
|
||||
"usercompetencystatus_idle": "Idle",
|
||||
"usercompetencystatus_inreview": "In review",
|
||||
"usercompetencystatus_waitingforreview": "Waiting for review",
|
||||
"userplans": "Learning plans",
|
||||
"xcompetenciesproficientoutofy": "{{$a.x}} out of {{$a.y}} competencies are proficient",
|
||||
"xcompetenciesproficientoutofyincourse": "You are proficient in {{$a.x}} out of {{$a.y}} competencies in this course."
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ title }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-split-view>
|
||||
<ion-refresher slot="fixed" [disabled]="!competencies.loaded" (ionRefresh)="refreshCompetencies($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="competencies.loaded">
|
||||
<ion-list>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let competency of competencies.items"
|
||||
[title]="competency.competency.shortname" (click)="competencies.select(competency)"
|
||||
[class.core-selected-item]="competencies.isSelected(competency)">
|
||||
<ion-label>
|
||||
<h2>{{ competency.competency.shortname }} <em>{{competency.competency.idnumber}}</em></h2>
|
||||
</ion-label>
|
||||
<ion-badge slot="end" *ngIf="competency.usercompetency"
|
||||
[color]="competency.usercompetency.proficiency ? 'success' : 'danger'">
|
||||
{{ competency.usercompetency.gradename }}
|
||||
</ion-badge>
|
||||
<ion-badge slot="end" *ngIf="competency.usercompetencycourse"
|
||||
[color]="competency.usercompetencycourse.proficiency ? 'success' : 'danger'">
|
||||
{{ competency.usercompetencycourse.gradename }}
|
||||
</ion-badge>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</core-split-view>
|
||||
</ion-content>
|
|
@ -0,0 +1,172 @@
|
|||
// (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 { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import {
|
||||
AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency, AddonCompetency,
|
||||
} from '../../services/competency';
|
||||
import { Params, ActivatedRouteSnapshot, ActivatedRoute } from '@angular/router';
|
||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Page that displays the list of competencies of a learning plan.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-competency-competencies',
|
||||
templateUrl: 'competencies.html',
|
||||
})
|
||||
export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy {
|
||||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
protected planId?: number;
|
||||
protected courseId?: number;
|
||||
protected userId?: number;
|
||||
|
||||
competenciesLoaded = false;
|
||||
competencies: AddonCompetencyListManager;
|
||||
title = '';
|
||||
|
||||
constructor(protected route: ActivatedRoute) {
|
||||
this.planId = CoreNavigator.getRouteNumberParam('planId', { route });
|
||||
if (!this.planId) {
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId', { route });
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId', { route });
|
||||
}
|
||||
|
||||
this.competencies =
|
||||
new AddonCompetencyListManager(AddonCompetencyCompetenciesPage, this.planId, this.courseId, this.userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
await this.fetchCompetencies();
|
||||
|
||||
this.competencies.start(this.splitView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the competencies and updates the view.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchCompetencies(): Promise<void> {
|
||||
try {
|
||||
if (this.planId) {
|
||||
|
||||
const response = await AddonCompetency.getLearningPlan(this.planId);
|
||||
|
||||
if (response.competencycount <= 0) {
|
||||
throw new CoreError(Translate.instant('addon.competency.errornocompetenciesfound'));
|
||||
}
|
||||
|
||||
this.title = response.plan.name;
|
||||
this.userId = response.plan.userid;
|
||||
|
||||
this.competencies.setItems(response.competencies);
|
||||
} else if (this.courseId) {
|
||||
const response = await AddonCompetency.getCourseCompetencies(this.courseId, this.userId);
|
||||
this.title = Translate.instant('addon.competency.coursecompetencies');
|
||||
|
||||
this.competencies.setItems(response.competencies);
|
||||
} else {
|
||||
throw null;
|
||||
}
|
||||
} catch (message) {
|
||||
CoreDomUtils.showErrorModalDefault(message, 'Error getting competencies data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the competencies.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
async refreshCompetencies(refresher?: IonRefresher): Promise<void> {
|
||||
try {
|
||||
if (this.planId) {
|
||||
await AddonCompetency.invalidateLearningPlan(this.planId);
|
||||
} else {
|
||||
await AddonCompetency.invalidateCourseCompetencies(this.courseId!, this.userId);
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.fetchCompetencies().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.competencies.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
type AddonCompetencyDataForPlanPageCompetencyFormatted =
|
||||
AddonCompetencyDataForPlanPageCompetency | AddonCompetencyDataForCourseCompetenciesPageCompetency;
|
||||
|
||||
/**
|
||||
* Helper class to manage competencies list.
|
||||
*/
|
||||
class AddonCompetencyListManager extends CorePageItemsListManager<AddonCompetencyDataForPlanPageCompetencyFormatted> {
|
||||
|
||||
planId?: number;
|
||||
courseId?: number;
|
||||
userId?: number;
|
||||
|
||||
constructor(pageComponent: unknown, planId?: number, courseId?: number, userId?: number) {
|
||||
super(pageComponent);
|
||||
this.planId = planId;
|
||||
this.courseId = courseId;
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemPath(competency: AddonCompetencyDataForPlanPageCompetencyFormatted): string {
|
||||
return String(competency.competency.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemQueryParams(): Params {
|
||||
if (this.planId) {
|
||||
return { planId: this.planId };
|
||||
} else {
|
||||
return { courseId: this.courseId, userId: this.userId };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.competencyId ?? null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title *ngIf="competency">
|
||||
{{ competency!.competency.competency.shortname }} <small>{{ competency.competency.competency.idnumber }}</small>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!competencyLoaded" (ionRefresh)="refreshCompetency($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="competencyLoaded">
|
||||
<ion-card *ngIf="user">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
|
||||
<ion-label><h2>{{ user!.fullname }}</h2></ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<ion-card *ngIf="competency">
|
||||
<ion-item class="ion-text-wrap" *ngIf="competency!.competency.competency.description">
|
||||
<ion-label>
|
||||
<core-format-text [text]="competency!.competency.competency.description" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<strong>{{ 'addon.competency.path' | translate }}</strong>
|
||||
<a *ngIf="competency!.competency.comppath.showlinks"
|
||||
[href]="competency!.competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' +
|
||||
competency!.competency.comppath.framework.id + '&pagecontextid=' +
|
||||
competency!.competency.comppath.pagecontextid"
|
||||
core-link [title]="competency!.competency.comppath.framework.name">
|
||||
{{ competency!.competency.comppath.framework.name }}
|
||||
</a>
|
||||
<ng-container *ngIf="!competency!.competency.comppath.showlinks">
|
||||
{{ competency!.competency.comppath.framework.name }}
|
||||
</ng-container>
|
||||
/
|
||||
<span *ngFor="let ancestor of competency!.competency.comppath.ancestors">
|
||||
<a *ngIf="competency!.competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)"
|
||||
class="core-clickable">
|
||||
{{ ancestor.name }}
|
||||
</a>
|
||||
<ng-container *ngIf="!competency!.competency.comppath.showlinks">{{ ancestor.name }}</ng-container>
|
||||
<ng-container *ngIf="!ancestor.last"> / </ng-container>
|
||||
</span>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<strong>{{ 'addon.competency.crossreferencedcompetencies' | translate }}</strong>:
|
||||
<div *ngIf="!competency!.competency.hasrelatedcompetencies">
|
||||
{{ 'addon.competency.nocrossreferencedcompetencies' | translate }}
|
||||
</div>
|
||||
<div *ngIf="competency!.competency.hasrelatedcompetencies">
|
||||
<p *ngFor="let relatedcomp of competency!.competency.relatedcompetencies">
|
||||
<a (click)="openCompetencySummary(relatedcomp.id)" class="core-clickable">
|
||||
{{ relatedcomp.shortname }} - {{ relatedcomp.idnumber }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="coursemodules">
|
||||
<ion-label>
|
||||
<strong>{{ 'addon.competency.activities' | translate }}</strong>
|
||||
<p *ngIf="coursemodules!.length == 0">
|
||||
{{ 'addon.competency.noactivities' | translate }}
|
||||
</p>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let activity of coursemodules" [href]="activity.url" [title]="activity.name"
|
||||
core-link capture="true">
|
||||
<img slot="start" core-external-content [src]="activity.iconurl" alt="" role="presentation" *ngIf="activity.iconurl"
|
||||
class="core-module-icon">
|
||||
<ion-label>
|
||||
<core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ng-container *ngIf="userCompetency">
|
||||
<ion-item class="ion-text-wrap" *ngIf="competency!.usercompetency && competency!.usercompetency!.status">
|
||||
<ion-label>
|
||||
<strong>{{ 'addon.competency.reviewstatus' | translate }}</strong>
|
||||
{{ competency!.usercompetency!.statusname }}
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<strong>{{ 'addon.competency.proficient' | translate }}</strong>
|
||||
</ion-label>
|
||||
<ion-badge slot="end" color="success" *ngIf="userCompetency!.proficiency">
|
||||
{{ 'core.yes' | translate }}
|
||||
</ion-badge>
|
||||
<ion-badge slot="end" color="danger" *ngIf="!userCompetency!.proficiency">
|
||||
{{ 'core.no' | translate }}
|
||||
</ion-badge>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<strong>{{ 'addon.competency.rating' | translate }}</strong>
|
||||
</ion-label>
|
||||
<ion-badge color="dark" slot="end">{{ userCompetency!.gradename }}</ion-badge>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ion-card>
|
||||
|
||||
<div *ngIf="competency">
|
||||
<h3 class="ion-margin-horizontal">{{ 'addon.competency.evidence' | translate }}</h3>
|
||||
<p class="ion-margin-horizontal" *ngIf="competency!.evidence.length == 0">
|
||||
{{ 'addon.competency.noevidence' | translate }}
|
||||
</p>
|
||||
<ion-card *ngFor="let evidence of competency!.evidence">
|
||||
<ion-item class="ion-text-wrap" *ngIf="evidence.actionuser" core-user-link [userId]="evidence.actionuser!.id"
|
||||
[courseId]="courseId">
|
||||
<core-user-avatar [user]="evidence.actionuser" slot="start"></core-user-avatar>
|
||||
<ion-label>
|
||||
<h2>{{ evidence.actionuser!.fullname }}</h2>
|
||||
<p>{{ evidence.timemodified * 1000 | coreFormatDate }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p><ion-badge color="dark">{{ evidence.gradename }}</ion-badge></p>
|
||||
<p class="ion-margin-top" *ngIf="evidence.description">{{ evidence.description }}</p>
|
||||
<blockquote *ngIf="evidence.note">{{ evidence.note }}</blockquote>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</div>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,186 @@
|
|||
// (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 { AddonCompetencyHelper } from '@addons/competency/services/competency-helper';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CoreCourseModuleSummary } from '@features/course/services/course';
|
||||
import { CoreUserSummary } from '@features/user/services/user';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { Translate } from '@singletons';
|
||||
import {
|
||||
AddonCompetencyDataForUserCompetencySummaryWSResponse,
|
||||
AddonCompetencyUserCompetencyPlan,
|
||||
AddonCompetencyUserCompetency,
|
||||
AddonCompetencyUserCompetencyCourse,
|
||||
AddonCompetency,
|
||||
AddonCompetencyDataForUserCompetencySummaryInPlanWSResponse,
|
||||
AddonCompetencyDataForUserCompetencySummaryInCourseWSResponse,
|
||||
} from '@addons/competency/services/competency';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { ContextLevel } from '@/core/constants';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { AddonCompetencyMainMenuHandlerService } from '@addons/competency/services/handlers/mainmenu';
|
||||
|
||||
/**
|
||||
* Page that displays a learning plan.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-competency-competency',
|
||||
templateUrl: 'competency.html',
|
||||
})
|
||||
export class AddonCompetencyCompetencyPage implements OnInit {
|
||||
|
||||
competencyLoaded = false;
|
||||
competencyId!: number;
|
||||
planId?: number;
|
||||
courseId?: number;
|
||||
userId?: number;
|
||||
planStatus?: number;
|
||||
coursemodules?: CoreCourseModuleSummary[];
|
||||
user?: CoreUserSummary;
|
||||
competency?: AddonCompetencyDataForUserCompetencySummaryWSResponse;
|
||||
userCompetency?: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse;
|
||||
contextLevel?: string;
|
||||
contextInstanceId?: number;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.competencyId = CoreNavigator.getRouteNumberParam('competencyId')!;
|
||||
this.planId = CoreNavigator.getRouteNumberParam('planId');
|
||||
if (!this.planId) {
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
}
|
||||
|
||||
try {
|
||||
await this.fetchCompetency();
|
||||
|
||||
const name = this.competency && this.competency.competency && this.competency.competency.competency &&
|
||||
this.competency.competency.competency.shortname;
|
||||
|
||||
if (this.planId) {
|
||||
CoreUtils.ignoreErrors(AddonCompetency.logCompetencyInPlanView(
|
||||
this.planId,
|
||||
this.competencyId,
|
||||
this.planStatus!,
|
||||
name,
|
||||
this.userId,
|
||||
));
|
||||
} else {
|
||||
CoreUtils.ignoreErrors(
|
||||
AddonCompetency.logCompetencyInCourseView(this.courseId!, this.competencyId, name, this.userId),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
this.competencyLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the competency and updates the view.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchCompetency(): Promise<void> {
|
||||
|
||||
try {
|
||||
let competency: AddonCompetencyDataForUserCompetencySummaryInPlanWSResponse |
|
||||
AddonCompetencyDataForUserCompetencySummaryInCourseWSResponse;
|
||||
|
||||
if (this.planId) {
|
||||
this.planStatus = undefined;
|
||||
|
||||
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()) {
|
||||
// Get the user profile from the returned object.
|
||||
this.user = this.competency.user;
|
||||
}
|
||||
|
||||
this.competency.evidence.forEach((evidence) => {
|
||||
if (evidence.descidentifier) {
|
||||
const key = 'addon.competency.' + evidence.descidentifier;
|
||||
evidence.description = Translate.instant(key, { $a: evidence.desca });
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting competency data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the competency.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
async refreshCompetency(refresher: IonRefresher): Promise<void> {
|
||||
try {
|
||||
if (this.planId) {
|
||||
await AddonCompetency.invalidateCompetencyInPlan(this.planId, this.competencyId);
|
||||
} else {
|
||||
await AddonCompetency.invalidateCompetencyInCourse(this.courseId!, this.competencyId);
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.fetchCompetency().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the summary of a competency.
|
||||
*
|
||||
* @param competencyId
|
||||
*/
|
||||
openCompetencySummary(competencyId: number): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/summary/' + competencyId,
|
||||
{
|
||||
params: { contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title *ngIf="competency">
|
||||
{{ competency!.competency.shortname }} <small>{{ competency!.competency.idnumber }}</small>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!competencyLoaded" (ionRefresh)="refreshCompetency($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="competencyLoaded">
|
||||
<ion-card *ngIf="competency">
|
||||
<ion-item class="ion-text-wrap" *ngIf="competency!.competency.description">
|
||||
<ion-label>
|
||||
<core-format-text [text]="competency!.competency.description" [contextLevel]="contextLevel"
|
||||
[contextInstanceId]="contextInstanceId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<strong>{{ 'addon.competency.path' | translate }}</strong>
|
||||
{{ competency!.comppath.framework.name }}
|
||||
<span *ngFor="let ancestor of competency!.comppath.ancestors">
|
||||
/ <a (click)="openCompetencySummary(ancestor.id)">{{ ancestor.name }}</a>
|
||||
</span>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,104 @@
|
|||
// (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 { Component, OnInit } from '@angular/core';
|
||||
import { ContextLevel } from '@/core/constants';
|
||||
import { AddonCompetencySummary, AddonCompetency } from '@addons/competency/services/competency';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { AddonCompetencyMainMenuHandlerService } from '@addons/competency/services/handlers/mainmenu';
|
||||
|
||||
/**
|
||||
* Page that displays a learning plan.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-competency-competency-summary',
|
||||
templateUrl: 'competencysummary.html',
|
||||
})
|
||||
export class AddonCompetencyCompetencySummaryPage implements OnInit {
|
||||
|
||||
competencyLoaded = false;
|
||||
competencyId!: number;
|
||||
competency?: AddonCompetencySummary;
|
||||
contextLevel?: ContextLevel;
|
||||
contextInstanceId?: number;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
this.competencyId = CoreNavigator.getRouteNumberParam('competencyId')!;
|
||||
this.contextLevel = CoreNavigator.getRouteParam<ContextLevel>('contextLevel');
|
||||
this.contextInstanceId = CoreNavigator.getRouteNumberParam('contextInstanceId');
|
||||
|
||||
try {
|
||||
await this.fetchCompetency();
|
||||
const name = this.competency!.competency && this.competency!.competency.shortname;
|
||||
|
||||
CoreUtils.ignoreErrors(AddonCompetency.logCompetencyView(this.competencyId, name));
|
||||
} finally {
|
||||
this.competencyLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the competency summary and updates the view.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchCompetency(): Promise<void> {
|
||||
try {
|
||||
const result = await AddonCompetency.getCompetencySummary(this.competencyId);
|
||||
if (!this.contextLevel || typeof this.contextInstanceId == 'undefined') {
|
||||
// Context not specified, use user context.
|
||||
this.contextLevel = ContextLevel.USER;
|
||||
this.contextInstanceId = result.usercompetency!.userid;
|
||||
}
|
||||
|
||||
this.competency = result.competency;
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting competency summary data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the competency summary.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshCompetency(refresher: IonRefresher): void {
|
||||
AddonCompetency.invalidateCompetencySummary(this.competencyId).finally(() => {
|
||||
this.fetchCompetency().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the summary of a competency.
|
||||
*
|
||||
* @param competencyId
|
||||
*/
|
||||
openCompetencySummary(competencyId: number): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/summary/' + competencyId,
|
||||
{
|
||||
params: { contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'addon.competency.coursecompetencies' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!competenciesLoaded" (ionRefresh)="refreshCourseCompetencies($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="competenciesLoaded">
|
||||
<ion-card *ngIf="!user && competencies && competencies!.statistics.competencycount > 0">
|
||||
<ng-container *ngIf="competencies!.cangradecompetencies">
|
||||
<ion-item class="ion-text-wrap" *ngIf="competencies!.settings.pushratingstouserplans">
|
||||
<ion-label>{{ 'addon.competency.coursecompetencyratingsarepushedtouserplans' | translate }}</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="!competencies!.settings.pushratingstouserplans" color="danger">
|
||||
<ion-label>{{ 'addon.competency.coursecompetencyratingsarenotpushedtouserplans' | translate }}</ion-label>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
<ion-item class="ion-text-wrap" *ngIf="competencies!.statistics.canbegradedincourse">
|
||||
<ion-label>
|
||||
{{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate: {$a:
|
||||
{x: competencies!.statistics.proficientcompetencycount, y: competencies!.statistics.competencycount} } }}
|
||||
<core-progress-bar [progress]="competencies!.statistics.proficientcompetencypercentage"></core-progress-bar>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap"
|
||||
*ngIf="competencies!.statistics.canmanagecoursecompetencies && competencies!.statistics.leastproficientcount > 0">
|
||||
<ion-label>
|
||||
<strong>{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}</strong>:
|
||||
<p *ngFor="let comp of competencies!.statistics.leastproficient">
|
||||
<a (click)="openCompetencySummary(comp.id)">
|
||||
{{ comp.shortname }} - {{ comp.idnumber }}
|
||||
</a>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<h3 class="ion-margin-horizontal" *ngIf="competencies && competencies!.statistics.competencycount > 0">
|
||||
{{ 'addon.competency.coursecompetencies' | translate }}
|
||||
</h3>
|
||||
<ion-card *ngIf="user">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
|
||||
<ion-label><h2>{{ user!.fullname }}</h2></ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
<core-empty-box *ngIf="competencies && competencies!.statistics.competencycount == 0"
|
||||
icon="fas-award" message="{{ 'addon.competency.nocompetenciesincourse' | translate }}">
|
||||
</core-empty-box>
|
||||
|
||||
<div *ngIf="competencies">
|
||||
<ion-card *ngFor="let competency of competencies!.competencies">
|
||||
<ion-item class="ion-text-wrap" (click)="openCompetency(competency.competency.id)"
|
||||
[title]="competency.competency.shortname" detail="true">
|
||||
<ion-label>
|
||||
<h2><strong>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></strong></h2>
|
||||
</ion-label>
|
||||
<ion-badge slot="end" *ngIf="competency.usercompetencycourse && competency.usercompetencycourse!.gradename"
|
||||
[color]="competency.usercompetencycourse!.proficiency ? 'success' : 'danger'">
|
||||
{{ competency.usercompetencycourse!.gradename }}
|
||||
</ion-badge>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<p *ngIf="competency.competency.description">
|
||||
<core-format-text [text]="competency.competency.description" contextLevel="course"
|
||||
[contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
<div>
|
||||
<strong>{{ 'addon.competency.path' | translate }}</strong>
|
||||
<a *ngIf="competency.comppath.showlinks"
|
||||
[href]="competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' +
|
||||
competency.comppath.framework.id + '&pagecontextid=' + competency.comppath.pagecontextid"
|
||||
core-link [title]="competency.comppath.framework.name">
|
||||
{{ competency.comppath.framework.name }}
|
||||
</a>
|
||||
<ng-container *ngIf="!competency.comppath.showlinks">
|
||||
{{ competency.comppath.framework.name }}
|
||||
</ng-container>
|
||||
/
|
||||
<span *ngFor="let ancestor of competency.comppath.ancestors">
|
||||
<a *ngIf="competency.comppath.showlinks" (click)="openCompetencySummary(ancestor.id)">
|
||||
{{ ancestor.name }}
|
||||
</a>
|
||||
<ng-container *ngIf="!competency.comppath.showlinks">{{ ancestor.name }}</ng-container>
|
||||
<ng-container *ngIf="!ancestor.last"> / </ng-container>
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="competencies!.statistics.canmanagecoursecompetencies">
|
||||
<strong>{{ 'addon.competency.uponcoursecompletion' | translate }}</strong>
|
||||
<ng-container *ngFor="let ruleoutcome of competency.ruleoutcomeoptions">
|
||||
<span *ngIf="ruleoutcome.selected">{{ ruleoutcome.text }}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ 'addon.competency.activities' | translate }}</strong>
|
||||
<p *ngIf="competency.coursemodules.length == 0">
|
||||
{{ 'addon.competency.noactivities' | translate }}
|
||||
</p>
|
||||
<ion-item class="ion-text-wrap core-course-module-handler item-media" [title]="activity.name" core-link
|
||||
*ngFor="let activity of competency.coursemodules" [href]="activity.url" capture="true">
|
||||
<img slot="start" [src]="activity.iconurl" core-external-content alt="" role="presentation"
|
||||
*ngIf="activity.iconurl" class="core-module-icon">
|
||||
<ion-label>
|
||||
<core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id"
|
||||
[courseId]="courseId">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
<div *ngIf="competency.plans">
|
||||
<strong>{{ 'addon.competency.userplans' | translate }}</strong>
|
||||
<p *ngIf="competency.plans.length == 0">
|
||||
{{ 'addon.competency.nouserplanswithcompetency' | translate }}
|
||||
</p>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let plan of competency.plans" [href]="plan.url"
|
||||
[title]="plan.name" core-link capture="true">
|
||||
<ion-label>
|
||||
<core-format-text [text]="plan.name" contextLevel="user" [contextInstanceId]="plan.userid">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</div>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
</div>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,28 @@
|
|||
// (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 { NgModule } from '@angular/core';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { AddonCompetencyCourseCompetenciesPage } from './coursecompetencies.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonCompetencyCourseCompetenciesPage,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyCourseCompetenciesPageModule {}
|
|
@ -0,0 +1,109 @@
|
|||
// (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 { Component, OnInit } from '@angular/core';
|
||||
import { AddonCompetencyDataForCourseCompetenciesPageWSResponse, AddonCompetency } from '@addons/competency/services/competency';
|
||||
import { AddonCompetencyHelper } from '@addons/competency/services/competency-helper';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { AddonCompetencyMainMenuHandlerService } from '@addons/competency/services/handlers/mainmenu';
|
||||
import { ContextLevel } from '@/core/constants';
|
||||
|
||||
/**
|
||||
* Page that displays the list of competencies of a course.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-competency-coursecompetencies',
|
||||
templateUrl: 'coursecompetencies.html',
|
||||
})
|
||||
export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
||||
|
||||
competenciesLoaded = false;
|
||||
competencies?: AddonCompetencyDataForCourseCompetenciesPageWSResponse;
|
||||
user?: CoreUserProfile;
|
||||
courseId!: number;
|
||||
|
||||
protected userId!: number;
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId')!;
|
||||
|
||||
this.fetchCourseCompetencies().finally(() => {
|
||||
this.competenciesLoaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the competencies and updates the view.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchCourseCompetencies(): Promise<void> {
|
||||
try {
|
||||
this.competencies = await AddonCompetency.getCourseCompetencies(this.courseId, this.userId);
|
||||
|
||||
// Get the user profile image.
|
||||
this.user = await AddonCompetencyHelper.getProfile(this.userId);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting course competencies data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a competency.
|
||||
*
|
||||
* @param competencyId
|
||||
*/
|
||||
openCompetency(competencyId: number): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/competencies/' + competencyId,
|
||||
{
|
||||
params: { courseId: this.courseId, userId: this.userId },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the summary of a competency.
|
||||
*
|
||||
* @param competencyId
|
||||
*/
|
||||
openCompetencySummary(competencyId: number): void {
|
||||
CoreNavigator.navigateToSitePath('/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/summary/' + competencyId, {
|
||||
params: {
|
||||
contextLevel: ContextLevel.COURSE,
|
||||
contextInstanceId: this.courseId,
|
||||
} });
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the competencies.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshCourseCompetencies(refresher?: IonRefresher): void {
|
||||
AddonCompetency.invalidateCourseCompetencies(this.courseId, this.userId).finally(() => {
|
||||
this.fetchCourseCompetencies().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title *ngIf="plan">{{plan!.plan.name}}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshLearningPlan($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<ion-card *ngIf="user">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
<core-user-avatar [user]="user" slot="start"></core-user-avatar>
|
||||
<h2>{{ user!.fullname }}</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
<ion-card *ngIf="plan">
|
||||
<ion-list>
|
||||
<ion-item class="ion-text-wrap" *ngIf="plan!.plan.description" lines="none">
|
||||
<ion-label>
|
||||
<core-format-text [text]="plan!.plan.description" contextLevel="user"
|
||||
[contextInstanceId]="plan!.plan.userid">
|
||||
</core-format-text>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" lines="none">
|
||||
<ion-label>
|
||||
<p>
|
||||
<strong>{{ 'addon.competency.status' | translate }}</strong>: {{ plan!.plan.statusname }}
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="plan!.plan.duedate > 0" lines="none">
|
||||
<ion-label>
|
||||
<p>
|
||||
<strong>{{ 'addon.competency.duedate' | translate }}</strong>:
|
||||
{{ plan.plan.duedate * 1000 | coreFormatDate }}
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngIf="plan!.plan.template" lines="none">
|
||||
<ion-label>
|
||||
<p>
|
||||
<strong>{{ 'addon.competency.template' | translate }}</strong>: {{ plan!.plan.template!.shortname }}
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" lines="none">
|
||||
<ion-label>
|
||||
<p>
|
||||
<strong>{{ 'addon.competency.progress' | translate }}</strong>:
|
||||
{{ 'addon.competency.xcompetenciesproficientoutofy' | translate:
|
||||
{$a: {x: plan.proficientcompetencycount, y: plan.competencycount} } }}
|
||||
</p>
|
||||
<core-progress-bar [progress]="plan.proficientcompetencypercentage"
|
||||
[text]="plan.proficientcompetencypercentageformatted"></core-progress-bar>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-card>
|
||||
<ion-card *ngIf="plan">
|
||||
<ion-card-header class="ion-text-wrap">
|
||||
<h2>{{ 'addon.competency.learningplancompetencies' | translate }}</h2>
|
||||
</ion-card-header>
|
||||
<ion-list>
|
||||
<ion-item class="ion-text-wrap" *ngIf="plan.competencycount == 0">
|
||||
<ion-label>
|
||||
<p>{{ 'addon.competency.nocompetencies' | translate }}</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let competency of plan.competencies"
|
||||
(click)="openCompetency(competency.competency.id)"
|
||||
[title]="competency.competency.shortname" detail="true">
|
||||
<ion-label><h2>{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></h2></ion-label>
|
||||
<ion-badge *ngIf="competency.usercompetencyplan" slot="end"
|
||||
[color]="competency.usercompetencyplan.proficiency ? 'success' : 'danger'">
|
||||
{{ competency.usercompetencyplan.gradename }}
|
||||
</ion-badge>
|
||||
<ion-badge *ngIf="!competency.usercompetencyplan" slot="end"
|
||||
[color]="competency.usercompetency.proficiency ? 'success' : 'danger'">
|
||||
{{ competency.usercompetency.gradename }}
|
||||
</ion-badge>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</ion-card>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -0,0 +1,93 @@
|
|||
// (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 { Component, OnInit } from '@angular/core';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { AddonCompetencyDataForPlanPageWSResponse, AddonCompetency } from '../../services/competency';
|
||||
import { AddonCompetencyHelper } from '../../services/competency-helper';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { AddonCompetencyMainMenuHandlerService } from '@addons/competency/services/handlers/mainmenu';
|
||||
|
||||
/**
|
||||
* Page that displays a learning plan.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-competency-plan',
|
||||
templateUrl: 'plan.html',
|
||||
})
|
||||
export class AddonCompetencyPlanPage implements OnInit {
|
||||
|
||||
protected planId!: number;
|
||||
loaded = false;
|
||||
plan?: AddonCompetencyDataForPlanPageWSResponse;
|
||||
user?: CoreUserProfile;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.planId = CoreNavigator.getRouteNumberParam('planId')!;
|
||||
|
||||
this.fetchLearningPlan().finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the learning plan and updates the view.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchLearningPlan(): Promise<void> {
|
||||
try {
|
||||
const plan = await AddonCompetency.getLearningPlan(this.planId);
|
||||
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) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plan data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to a particular competency.
|
||||
*
|
||||
* @param competencyId
|
||||
*/
|
||||
openCompetency(competencyId: number): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/competencies/' + competencyId,
|
||||
{ params: { planId: this.planId } },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the learning plan.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshLearningPlan(refresher: IonRefresher): void {
|
||||
AddonCompetency.invalidateLearningPlan(this.planId).finally(() => {
|
||||
this.fetchLearningPlan().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>{{ 'addon.competency.userplans' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-split-view>
|
||||
<ion-refresher slot="fixed" [disabled]="!plans.loaded" (ionRefresh)="refreshLearningPlans($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="plans.loaded">
|
||||
<core-empty-box *ngIf="plans.empty" icon="fas-route" [message]="'addon.competency.noplanswerecreated' | translate">
|
||||
|
||||
</core-empty-box>
|
||||
<ion-list *ngIf="!plans.empty" class="ion-no-margin">
|
||||
<ion-item class="ion-text-wrap" *ngFor="let plan of plans.items" [title]="plan.name" (click)="plans.select(plan)"
|
||||
[class.core-selected-item]="plans.isSelected(plan)">
|
||||
<ion-label>
|
||||
<h2>{{ plan.name }}</h2>
|
||||
<p *ngIf="plan.duedate > 0">
|
||||
{{ 'addon.competency.duedate' | translate }}:
|
||||
{{ plan.duedate * 1000 | coreFormatDate :'strftimedatetimeshort' }}
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-badge slot="end" class="ion-text-wrap" [color]="plan.statuscolor">{{ plan.statusname }}</ion-badge>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</core-split-view>
|
||||
</ion-content>
|
|
@ -0,0 +1,140 @@
|
|||
// (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 { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
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 { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Page that displays the list of learning plans.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-competency-planlist',
|
||||
templateUrl: 'planlist.html',
|
||||
})
|
||||
export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
protected userId?: number;
|
||||
plans: AddonCompetencyPlanListManager;
|
||||
|
||||
constructor() {
|
||||
this.plans = new AddonCompetencyPlanListManager(AddonCompetencyPlanListPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
await this.fetchLearningPlans();
|
||||
|
||||
this.plans.start(this.splitView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the learning plans and updates the view.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchLearningPlans(): Promise<void> {
|
||||
try {
|
||||
const plans = await AddonCompetency.getLearningPlans(this.userId);
|
||||
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) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plans data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the learning plans.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshLearningPlans(refresher: IonRefresher): void {
|
||||
AddonCompetency.invalidateLearningPlans(this.userId).finally(() => {
|
||||
this.fetchLearningPlans().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.plans.destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
constructor(pageComponent: unknown) {
|
||||
super(pageComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemPath(plan: AddonCompetencyPlanFormatted): string {
|
||||
return String(plan.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.planId ?? null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { AddonCompetencyProvider } from './competency';
|
||||
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding learning plans.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyHelperProvider {
|
||||
|
||||
/**
|
||||
* Convenient helper to get the user profile image.
|
||||
*
|
||||
* @param userId User Id
|
||||
* @return User profile Image URL or true if default icon.
|
||||
*/
|
||||
async getProfile(userId?: number): Promise<CoreUserProfile | undefined> {
|
||||
if (!userId || userId == CoreSites.getCurrentSiteUserId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user profile to retrieve the user image.
|
||||
return CoreUser.getProfile(userId, undefined, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the review status name translated.
|
||||
*
|
||||
* @param status
|
||||
*/
|
||||
getCompetencyStatusName(status: number): string {
|
||||
let statusTranslateName: string;
|
||||
switch (status) {
|
||||
case AddonCompetencyProvider.REVIEW_STATUS_IDLE:
|
||||
statusTranslateName = 'idle';
|
||||
break;
|
||||
case AddonCompetencyProvider.REVIEW_STATUS_IN_REVIEW:
|
||||
statusTranslateName = 'inreview';
|
||||
break;
|
||||
case AddonCompetencyProvider.REVIEW_STATUS_WAITING_FOR_REVIEW:
|
||||
statusTranslateName = 'waitingforreview';
|
||||
break;
|
||||
default:
|
||||
// We can use the current status name.
|
||||
return String(status);
|
||||
}
|
||||
|
||||
return Translate.instant('addon.competency.usercompetencystatus_' + statusTranslateName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status name translated.
|
||||
*
|
||||
* @param status
|
||||
*/
|
||||
getPlanStatusName(status: number): string {
|
||||
let statusTranslateName: string;
|
||||
switch (status) {
|
||||
case AddonCompetencyProvider.STATUS_DRAFT:
|
||||
statusTranslateName = 'draft';
|
||||
break;
|
||||
case AddonCompetencyProvider.STATUS_ACTIVE:
|
||||
statusTranslateName = 'active';
|
||||
break;
|
||||
case AddonCompetencyProvider.STATUS_COMPLETE:
|
||||
statusTranslateName = 'complete';
|
||||
break;
|
||||
case AddonCompetencyProvider.STATUS_WAITING_FOR_REVIEW:
|
||||
statusTranslateName = 'waitingforreview';
|
||||
break;
|
||||
case AddonCompetencyProvider.STATUS_IN_REVIEW:
|
||||
statusTranslateName = 'inreview';
|
||||
break;
|
||||
default:
|
||||
// We can use the current status name.
|
||||
return String(status);
|
||||
}
|
||||
|
||||
return Translate.instant('addon.competency.planstatus' + statusTranslateName);
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyHelper = makeSingleton(AddonCompetencyHelperProvider);
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,66 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
import { AddonCompetencyMainMenuHandlerService } from './mainmenu';
|
||||
|
||||
/**
|
||||
* Handler to treat links to a competency in a plan or in a course.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyCompetencyLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||
|
||||
name = 'AddonCompetencyCompetencyLinkHandler';
|
||||
pattern = /\/admin\/tool\/lp\/(user_competency_in_course|user_competency_in_plan)\.php/;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: Record<string, string>, courseId?: number): CoreContentLinksAction[] {
|
||||
courseId = courseId || parseInt(params.courseid || params.cid, 10);
|
||||
|
||||
return [{
|
||||
action: (siteId: string): void => {
|
||||
const pageParams = {
|
||||
planId: params.planid,
|
||||
courseId: courseId,
|
||||
userId: params.userid,
|
||||
};
|
||||
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/competencies/' + params.competencyid,
|
||||
{ params: pageParams, siteId },
|
||||
);
|
||||
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(siteId: string): Promise<boolean> {
|
||||
// Handler is disabled if all competency features are disabled.
|
||||
const disabled = await AddonCompetency.allCompetenciesDisabled(siteId);
|
||||
|
||||
return !disabled;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyCompetencyLinkHandler = makeSingleton(AddonCompetencyCompetencyLinkHandlerService);
|
|
@ -0,0 +1,139 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreCourseProvider } from '@features/course/services/course';
|
||||
import {
|
||||
CoreCourseAccess,
|
||||
CoreCourseOptionsHandler,
|
||||
CoreCourseOptionsHandlerData,
|
||||
} from '@features/course/services/course-options-delegate';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
|
||||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||
import { ContextLevel } from '@/core/constants';
|
||||
import { AddonCompetencyMainMenuHandlerService } from './mainmenu';
|
||||
|
||||
/**
|
||||
* Course nav handler.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyCourseOptionHandlerService implements CoreCourseOptionsHandler {
|
||||
|
||||
name = 'AddonCompetency';
|
||||
priority = 300;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabledForCourse(
|
||||
courseId: number,
|
||||
accessData: CoreCourseAccess,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<boolean> {
|
||||
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
|
||||
return false; // Not enabled for guests.
|
||||
}
|
||||
|
||||
if (navOptions && typeof navOptions.competencies != 'undefined') {
|
||||
return navOptions.competencies;
|
||||
}
|
||||
|
||||
try {
|
||||
const competencies = await AddonCompetency.getCourseCompetencies(courseId);
|
||||
|
||||
return competencies ? !competencies.canmanagecoursecompetencies : false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(): CoreCourseOptionsHandlerData {
|
||||
return {
|
||||
title: 'addon.competency.competencies',
|
||||
class: 'addon-competency-course-handler',
|
||||
page: AddonCompetencyMainMenuHandlerService.PAGE_NAME,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||
if (navOptions && typeof navOptions.competencies != 'undefined') {
|
||||
// No need to invalidate anything.
|
||||
return;
|
||||
}
|
||||
|
||||
return AddonCompetency.invalidateCourseCompetencies(courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async prefetch(course: CoreEnrolledCourseDataWithExtraInfoAndOptions): Promise<void> {
|
||||
// Get the competencies in the course.
|
||||
const competencies = await AddonCompetency.getCourseCompetencies(course.id, undefined, undefined, true);
|
||||
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
// Prefetch all the competencies.
|
||||
if (competencies && competencies.competencies) {
|
||||
competencies.competencies.forEach((competency) => {
|
||||
promises.push(AddonCompetency.getCompetencyInCourse(
|
||||
course.id,
|
||||
competency.competency.id,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
));
|
||||
|
||||
promises.push(AddonCompetency.getCompetencySummary(
|
||||
competency.competency.id,
|
||||
undefined,
|
||||
undefined,
|
||||
true,
|
||||
));
|
||||
|
||||
if (competency.coursemodules) {
|
||||
competency.coursemodules.forEach((module) => {
|
||||
promises.push(CoreFilterHelper.getFilters(ContextLevel.MODULE, module.id, { courseId: course.id }));
|
||||
});
|
||||
}
|
||||
|
||||
if (competency.plans) {
|
||||
competency.plans.forEach((plan) => {
|
||||
promises.push(CoreFilterHelper.getFilters(ContextLevel.USER, plan.userid));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyCourseOptionHandler = makeSingleton(AddonCompetencyCourseOptionHandlerService);
|
|
@ -0,0 +1,54 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@features/mainmenu/services/mainmenu-delegate';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
|
||||
/**
|
||||
* Handler to inject an option into main menu.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyMainMenuHandlerService implements CoreMainMenuHandler {
|
||||
|
||||
static readonly PAGE_NAME = 'competency';
|
||||
|
||||
name = 'AddonCompetency';
|
||||
priority = 500;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
// Check the user has at least one learn plan available.
|
||||
const plans = await AddonCompetency.getLearningPlans();
|
||||
|
||||
return plans.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(): CoreMainMenuHandlerData {
|
||||
return {
|
||||
icon: 'fas-route',
|
||||
title: 'addon.competency.myplans',
|
||||
page: AddonCompetencyMainMenuHandlerService.PAGE_NAME,
|
||||
class: 'addon-competency-handler',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyMainMenuHandler = makeSingleton(AddonCompetencyMainMenuHandlerService);
|
|
@ -0,0 +1,57 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
import { AddonCompetencyMainMenuHandlerService } from './mainmenu';
|
||||
|
||||
/**
|
||||
* Handler to treat links to a plan.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyPlanLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||
|
||||
name = 'AddonCompetencyPlanLinkHandler';
|
||||
pattern = /\/admin\/tool\/lp\/plan\.php.*([?&]id=\d+)/;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
|
||||
return [{
|
||||
action: (siteId: string): void => {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/' + params.id,
|
||||
{ siteId },
|
||||
);
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(siteId: string): Promise<boolean> {
|
||||
// Handler is disabled if all competency features are disabled.
|
||||
const disabled = await AddonCompetency.allCompetenciesDisabled(siteId);
|
||||
|
||||
return !disabled;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyPlanLinkHandler = makeSingleton(AddonCompetencyPlanLinkHandlerService);
|
|
@ -0,0 +1,58 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
import { AddonCompetencyMainMenuHandlerService } from './mainmenu';
|
||||
|
||||
/**
|
||||
* Handler to treat links to user plans.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyPlansLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||
|
||||
name = 'AddonCompetencyPlansLinkHandler';
|
||||
pattern = /\/admin\/tool\/lp\/plans\.php/;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
|
||||
return [{
|
||||
action: (siteId: string): void => {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME,
|
||||
{ params: { userId: params.userid }, siteId },
|
||||
);
|
||||
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(siteId: string): Promise<boolean> {
|
||||
// Handler is disabled if all competency features are disabled.
|
||||
const disabled = await AddonCompetency.allCompetenciesDisabled(siteId);
|
||||
|
||||
return !disabled;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyPlansLinkHandler = makeSingleton(AddonCompetencyPlansLinkHandlerService);
|
|
@ -0,0 +1,102 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate';
|
||||
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
import { AddonCompetencyMainMenuHandlerService } from './mainmenu';
|
||||
|
||||
/**
|
||||
* Handler for competencies push notifications clicks.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyPushClickHandlerService implements CorePushNotificationsClickHandler {
|
||||
|
||||
name = 'AddonCompetencyPushClickHandler';
|
||||
priority = 200;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async handles(notification: AddonCompetencyPushNotificationData): Promise<boolean> {
|
||||
if (CoreUtils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'moodle' &&
|
||||
(notification.name == 'competencyplancomment' || notification.name == 'competencyusercompcomment')) {
|
||||
// If all competency features are disabled, don't handle the click.
|
||||
return AddonCompetency.allCompetenciesDisabled(notification.site).then((disabled) => !disabled);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async handleClick(notification: AddonCompetencyPushNotificationData): Promise<void> {
|
||||
const contextUrlParams = CoreUrlUtils.extractUrlParams(notification.contexturl);
|
||||
|
||||
if (notification.name == 'competencyplancomment') {
|
||||
// Open the learning plan.
|
||||
const planId = Number(contextUrlParams.id);
|
||||
|
||||
await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlan(planId, notification.site));
|
||||
|
||||
await CoreNavigator.navigateToSitePath('/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/' + planId, {
|
||||
siteId: notification.site,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (notification.contexturl && notification.contexturl.indexOf('user_competency_in_plan.php') != -1) {
|
||||
// Open the competency.
|
||||
const courseId = Number(notification.course);
|
||||
const competencyId = Number(contextUrlParams.competencyid);
|
||||
const planId = Number(contextUrlParams.planid);
|
||||
const userId = Number(contextUrlParams.userid);
|
||||
|
||||
await CoreUtils.ignoreErrors(AddonCompetency.invalidateCompetencyInPlan(planId, competencyId, notification.site));
|
||||
await CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/competencies/' + competencyId,
|
||||
{
|
||||
params: { planId, courseId, userId },
|
||||
siteId: notification.site,
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the list of plans.
|
||||
const userId = Number(contextUrlParams.userid);
|
||||
|
||||
await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlans(userId, notification.site));
|
||||
|
||||
await CoreNavigator.navigateToSitePath('/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME, {
|
||||
params: { userId },
|
||||
siteId: notification.site,
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyPushClickHandler = makeSingleton(AddonCompetencyPushClickHandlerService);
|
||||
|
||||
type AddonCompetencyPushNotificationData = CorePushNotificationsNotificationBasicData & {
|
||||
contexturl: string;
|
||||
course: number;
|
||||
};
|
|
@ -0,0 +1,59 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
import { AddonCompetencyMainMenuHandlerService } from './mainmenu';
|
||||
|
||||
/**
|
||||
* Handler to treat links to a usr competency.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyUserCompetencyLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||
|
||||
name = 'AddonCompetencyUserCompetencyLinkHandler';
|
||||
pattern = /\/admin\/tool\/lp\/user_competency\.php.*([?&]id=\d+)/;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
|
||||
|
||||
return [{
|
||||
action: (siteId: string): void => {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/summary/' + params.id,
|
||||
{ siteId },
|
||||
);
|
||||
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(siteId: string): Promise<boolean> {
|
||||
// Handler is disabled if all competency features are disabled.
|
||||
const disabled = await AddonCompetency.allCompetenciesDisabled(siteId);
|
||||
|
||||
return !disabled;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyUserCompetencyLinkHandler = makeSingleton(AddonCompetencyUserCompetencyLinkHandlerService);
|
|
@ -0,0 +1,99 @@
|
|||
// (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 { Injectable } from '@angular/core';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
import { CoreUserProfileHandler, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
import { AddonCompetencyMainMenuHandlerService } from './mainmenu';
|
||||
|
||||
/**
|
||||
* Profile competencies handler.
|
||||
*/
|
||||
@Injectable( { providedIn: 'root' })
|
||||
export class AddonCompetencyUserHandlerService implements CoreUserProfileHandler {
|
||||
|
||||
name = 'AddonCompetency:learningPlan';
|
||||
priority = 900;
|
||||
type = CoreUserDelegateService.TYPE_NEW_PAGE;
|
||||
cacheEnabled = true;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
|
||||
try {
|
||||
if (courseId) {
|
||||
const response = await AddonCompetency.getCourseCompetencies(courseId, user.id);
|
||||
|
||||
return response.competencies.length > 0;
|
||||
} else {
|
||||
const plans = await AddonCompetency.getLearningPlans(user.id);
|
||||
|
||||
// Check the user has at least one learn plan available.
|
||||
return plans.length > 0;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(user: CoreUserProfile, courseId: number): CoreUserProfileHandlerData {
|
||||
if (courseId) {
|
||||
return {
|
||||
icon: 'fas-award',
|
||||
title: 'addon.competency.competencies',
|
||||
class: 'addon-competency-handler',
|
||||
action: (event, user, courseId): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
CoreNavigator.navigateToSitePath(
|
||||
'/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME + '/course/' + courseId,
|
||||
{
|
||||
params: { userId: user.id },
|
||||
},
|
||||
);
|
||||
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
icon: 'fas-route',
|
||||
title: 'addon.competency.learningplans',
|
||||
class: 'addon-competency-handler',
|
||||
action: (event, user): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
CoreNavigator.navigateToSitePath('/' + AddonCompetencyMainMenuHandlerService.PAGE_NAME, {
|
||||
params: { userId: user.id },
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonCompetencyUserHandler = makeSingleton(AddonCompetencyUserHandlerService);
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Component, OnDestroy, AfterViewInit, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
|
||||
import { ActivatedRouteSnapshot, Params } from '@angular/router';
|
||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
|
@ -73,9 +73,7 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro
|
|||
canviewsubmissions: false,
|
||||
};
|
||||
|
||||
constructor(
|
||||
protected route: ActivatedRoute,
|
||||
) {
|
||||
constructor() {
|
||||
this.submissions = new AddonModAssignSubmissionListManager(AddonModAssignSubmissionListPage);
|
||||
|
||||
// Update data if some grade changes.
|
||||
|
|
|
@ -118,7 +118,7 @@ import { CoreSitePluginsAssignSubmissionComponent } from '@features/siteplugins/
|
|||
import { ADDON_BADGES_SERVICES } from '@addons/badges/badges.module';
|
||||
import { ADDON_CALENDAR_SERVICES } from '@addons/calendar/calendar.module';
|
||||
import { ADDON_COURSECOMPLETION_SERVICES } from '@addons/coursecompletion/coursecompletion.module';
|
||||
// @todo import { ADDON_COMPETENCY_SERVICES } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_SERVICES } from '@addons/competency/competency.module';
|
||||
import { ADDON_MESSAGEOUTPUT_SERVICES } from '@addons/messageoutput/messageoutput.module';
|
||||
import { ADDON_MESSAGES_SERVICES } from '@addons/messages/messages.module';
|
||||
import { ADDON_MOD_ASSIGN_SERVICES } from '@addons/mod/assign/assign.module';
|
||||
|
@ -283,7 +283,7 @@ export class CoreCompileProvider {
|
|||
...ADDON_BADGES_SERVICES,
|
||||
...ADDON_CALENDAR_SERVICES,
|
||||
...ADDON_COURSECOMPLETION_SERVICES,
|
||||
// @todo ...ADDON_COMPETENCY_SERVICES,
|
||||
...ADDON_COMPETENCY_SERVICES,
|
||||
...ADDON_MESSAGEOUTPUT_SERVICES,
|
||||
...ADDON_MESSAGES_SERVICES,
|
||||
...ADDON_MOD_ASSIGN_SERVICES,
|
||||
|
|
Loading…
Reference in New Issue