Merge pull request #3046 from NoelDeMartin/MOBILE-3934
MOBILE-3934: Refactor pages with nested split viewsmain
commit
1b43cb4076
|
@ -17,7 +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 { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module';
|
||||
|
||||
/**
|
||||
* Block handler.
|
||||
|
@ -38,7 +38,7 @@ export class AddonBlockLearningPlansHandlerService extends CoreBlockBaseHandler
|
|||
title: 'addon.block_learningplans.pluginname',
|
||||
class: 'addon-block-learning-plans',
|
||||
component: CoreBlockOnlyTitleComponent,
|
||||
link: ADDON_COMPETENCY_MAIN_PAGE_NAME,
|
||||
link: ADDON_COMPETENCY_LEARNING_PLANS_PAGE,
|
||||
navOptions: {
|
||||
preferCurrentTab: false,
|
||||
},
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import {
|
||||
AddonCompetency,
|
||||
AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||
AddonCompetencyDataForCourseCompetenciesPageWSResponse,
|
||||
} from '../services/competency';
|
||||
import { AddonCompetencyHelper } from '../services/competency-helper';
|
||||
|
||||
/**
|
||||
* Provides a collection of course competencies.
|
||||
*/
|
||||
export class AddonCompetencyCourseCompetenciesSource
|
||||
extends CoreRoutedItemsManagerSource<AddonCompetencyDataForCourseCompetenciesPageCompetency> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
static getSourceId(courseId: number, userId?: number): string {
|
||||
return `${courseId}-${userId || 'current-user'}`;
|
||||
}
|
||||
|
||||
readonly COURSE_ID: number;
|
||||
readonly USER_ID?: number;
|
||||
|
||||
courseCompetencies?: AddonCompetencyDataForCourseCompetenciesPageWSResponse;
|
||||
user?: CoreUserProfile;
|
||||
|
||||
constructor(courseId: number, userId?: number) {
|
||||
super();
|
||||
|
||||
this.COURSE_ID = courseId;
|
||||
this.USER_ID = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemPath(competency: AddonCompetencyDataForCourseCompetenciesPageCompetency): string {
|
||||
return String(competency.competency.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async load(): Promise<void> {
|
||||
if (this.dirty || !this.courseCompetencies) {
|
||||
await this.loadCourseCompetencies();
|
||||
}
|
||||
|
||||
await super.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate course cache.
|
||||
*/
|
||||
async invalidateCache(): Promise<void> {
|
||||
await CoreUtils.ignoreErrors(AddonCompetency.invalidateCourseCompetencies(this.COURSE_ID, this.USER_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async loadPageItems(): Promise<{ items: AddonCompetencyDataForCourseCompetenciesPageCompetency[] }> {
|
||||
if (!this.courseCompetencies) {
|
||||
throw new Error('Can\'t load competencies without course data');
|
||||
}
|
||||
|
||||
return { items: this.courseCompetencies.competencies };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load competencies.
|
||||
*/
|
||||
private async loadCourseCompetencies(): Promise<void> {
|
||||
[this.courseCompetencies, this.user] = await Promise.all([
|
||||
AddonCompetency.getCourseCompetencies(this.COURSE_ID, this.USER_ID),
|
||||
AddonCompetencyHelper.getProfile(this.USER_ID),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import {
|
||||
AddonCompetency,
|
||||
AddonCompetencyDataForPlanPageCompetency,
|
||||
AddonCompetencyDataForPlanPageWSResponse,
|
||||
} from '../services/competency';
|
||||
import { AddonCompetencyHelper } from '../services/competency-helper';
|
||||
|
||||
/**
|
||||
* Provides a collection of plan competencies.
|
||||
*/
|
||||
export class AddonCompetencyPlanCompetenciesSource extends CoreRoutedItemsManagerSource<AddonCompetencyDataForPlanPageCompetency> {
|
||||
|
||||
readonly PLAN_ID: number;
|
||||
|
||||
plan?: AddonCompetencyDataForPlanPageWSResponse;
|
||||
user?: CoreUserProfile;
|
||||
|
||||
constructor(planId: number) {
|
||||
super();
|
||||
|
||||
this.PLAN_ID = planId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemPath(competency: AddonCompetencyDataForPlanPageCompetency): string {
|
||||
return String(competency.competency.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async load(): Promise<void> {
|
||||
if (this.dirty || !this.plan) {
|
||||
await this.loadLearningPlan();
|
||||
}
|
||||
|
||||
await super.load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate plan cache.
|
||||
*/
|
||||
async invalidateCache(): Promise<void> {
|
||||
await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlan(this.PLAN_ID));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async loadPageItems(): Promise<{ items: AddonCompetencyDataForPlanPageCompetency[] }> {
|
||||
if (!this.plan) {
|
||||
throw new Error('Can\'t load competencies without plan!');
|
||||
}
|
||||
|
||||
return { items: this.plan.competencies };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load learning plan.
|
||||
*/
|
||||
private async loadLearningPlan(): Promise<void> {
|
||||
this.plan = await AddonCompetency.getLearningPlan(this.PLAN_ID);
|
||||
this.plan.plan.statusname = AddonCompetencyHelper.getPlanStatusName(this.plan.plan.status);
|
||||
|
||||
// Get the user profile image.
|
||||
this.user = await AddonCompetencyHelper.getProfile(this.plan.plan.userid);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { ADDON_COMPETENCY_COMPETENCIES_PAGE } from '../competency.module';
|
||||
import { AddonCompetency, AddonCompetencyPlan, AddonCompetencyProvider } from '../services/competency';
|
||||
import { AddonCompetencyHelper } from '../services/competency-helper';
|
||||
|
||||
/**
|
||||
* Provides a collection of learning plans.
|
||||
*/
|
||||
export class AddonCompetencyPlansSource extends CoreRoutedItemsManagerSource<AddonCompetencyPlanFormatted> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
static getSourceId(userId?: number): string {
|
||||
return userId ? String(userId) : 'current-user';
|
||||
}
|
||||
|
||||
readonly USER_ID?: number;
|
||||
|
||||
constructor(userId?: number) {
|
||||
super();
|
||||
|
||||
this.USER_ID = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemPath(plan: AddonCompetencyPlanFormatted): string {
|
||||
return `${plan.id}/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemQueryParams(): Params {
|
||||
if (this.USER_ID) {
|
||||
return { userId: this.USER_ID };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate learning plans cache.
|
||||
*/
|
||||
async invalidateCache(): Promise<void> {
|
||||
await AddonCompetency.invalidateLearningPlans(this.USER_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async loadPageItems(): Promise<{ items: AddonCompetencyPlanFormatted[] }> {
|
||||
const plans = await AddonCompetency.getLearningPlans(this.USER_ID);
|
||||
|
||||
plans.forEach((plan: AddonCompetencyPlanFormatted) => {
|
||||
plan.statusname = AddonCompetencyHelper.getPlanStatusName(plan.status);
|
||||
switch (plan.status) {
|
||||
case AddonCompetencyProvider.STATUS_ACTIVE:
|
||||
plan.statuscolor = 'success';
|
||||
break;
|
||||
case AddonCompetencyProvider.STATUS_COMPLETE:
|
||||
plan.statuscolor = 'danger';
|
||||
break;
|
||||
default:
|
||||
plan.statuscolor = 'warning';
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return { items: plans };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Competency plan with some calculated data.
|
||||
*/
|
||||
export type AddonCompetencyPlanFormatted = AddonCompetencyPlan & {
|
||||
statuscolor?: string; // Calculated in the app. Color of the plan's status.
|
||||
};
|
|
@ -15,8 +15,8 @@
|
|||
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';
|
||||
import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -31,4 +31,4 @@ const routes: Routes = [
|
|||
AddonCompetencyCourseCompetenciesPageModule,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyCourseLazyModule {}
|
||||
export class AddonCompetencyCourseContentsLazyModule {}
|
|
@ -0,0 +1,72 @@
|
|||
// (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 { AddonCompetencyCompetencyPage } from './pages/competency/competency.page';
|
||||
import { AddonCompetencyCompetencySummaryPage } from './pages/competencysummary/competencysummary.page';
|
||||
import { ADDON_COMPETENCY_SUMMARY_PAGE } from './competency.module';
|
||||
import { AddonCompetencyCompetencyPageModule } from './pages/competency/competency.module';
|
||||
import { AddonCompetencyCompetencySummaryPageModule } from './pages/competencysummary/competencysummary.module';
|
||||
import { AddonCompetencyCourseCompetenciesPage } from './pages/coursecompetencies/coursecompetencies.page';
|
||||
import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module';
|
||||
import { AddonCompetencyCompetenciesPage } from './pages/competencies/competencies.page';
|
||||
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { AddonCompetencyCompetenciesPageModule } from './pages/competencies/competencies.module';
|
||||
|
||||
const mobileRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AddonCompetencyCourseCompetenciesPage,
|
||||
},
|
||||
{
|
||||
path: ':competencyId',
|
||||
component: AddonCompetencyCompetencyPage,
|
||||
},
|
||||
];
|
||||
|
||||
const tabletRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AddonCompetencyCompetenciesPage,
|
||||
children: [
|
||||
{
|
||||
path: ':competencyId',
|
||||
component: AddonCompetencyCompetencyPage,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile),
|
||||
...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
|
||||
{
|
||||
path: `:competencyId/${ADDON_COMPETENCY_SUMMARY_PAGE}`,
|
||||
component: AddonCompetencyCompetencySummaryPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
AddonCompetencyCourseCompetenciesPageModule,
|
||||
AddonCompetencyCompetenciesPageModule,
|
||||
AddonCompetencyCompetencyPageModule,
|
||||
AddonCompetencyCompetencySummaryPageModule,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyCourseDetailsLazyModule {}
|
|
@ -20,11 +20,13 @@ import { CoreScreen } from '@services/screen';
|
|||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { AddonCompetencyPlanPage } from './pages/plan/plan';
|
||||
import { AddonCompetencyPlanListPage } from './pages/planlist/planlist';
|
||||
import { AddonCompetencyCompetenciesPage } from './pages/competencies/competencies';
|
||||
import { AddonCompetencyCompetencyPage } from './pages/competency/competency';
|
||||
import { AddonCompetencyCompetencySummaryPage } from './pages/competencysummary/competencysummary';
|
||||
import { AddonCompetencyCourseCompetenciesPage } from './pages/coursecompetencies/coursecompetencies.page';
|
||||
import { AddonCompetencyCourseCompetenciesPageModule } from './pages/coursecompetencies/coursecompetencies.module';
|
||||
import { AddonCompetencyCompetencyPage } from './pages/competency/competency.page';
|
||||
import { AddonCompetencyCompetencySummaryPage } from './pages/competencysummary/competencysummary.page';
|
||||
import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_SUMMARY_PAGE } from './competency.module';
|
||||
import { AddonCompetencyCompetencyPageModule } from './pages/competency/competency.module';
|
||||
import { AddonCompetencyCompetencySummaryPageModule } from './pages/competencysummary/competencysummary.module';
|
||||
import { AddonCompetencyCompetenciesPage } from './pages/competencies/competencies.page';
|
||||
import { AddonCompetencyCompetenciesPageModule } from './pages/competencies/competencies.module';
|
||||
|
||||
const mobileRoutes: Routes = [
|
||||
{
|
||||
|
@ -33,34 +35,28 @@ const mobileRoutes: Routes = [
|
|||
component: AddonCompetencyPlanListPage,
|
||||
},
|
||||
{
|
||||
path: 'competencies',
|
||||
component: AddonCompetencyCompetenciesPage,
|
||||
},
|
||||
{
|
||||
path: 'competencies/:competencyId',
|
||||
component: AddonCompetencyCompetencyPage,
|
||||
},
|
||||
{
|
||||
path: 'course/:courseId',
|
||||
component: AddonCompetencyCourseCompetenciesPage,
|
||||
},
|
||||
{
|
||||
path: 'summary/:competencyId',
|
||||
component: AddonCompetencyCompetencySummaryPage,
|
||||
},
|
||||
{
|
||||
path: ':planId',
|
||||
path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`,
|
||||
component: AddonCompetencyPlanPage,
|
||||
},
|
||||
{
|
||||
path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}/:competencyId`,
|
||||
component: AddonCompetencyCompetencyPage,
|
||||
},
|
||||
];
|
||||
|
||||
const tabletRoutes: Routes = [
|
||||
{
|
||||
path: 'summary/:competencyId',
|
||||
component: AddonCompetencyCompetencySummaryPage,
|
||||
path: '',
|
||||
component: AddonCompetencyPlanListPage,
|
||||
children: [
|
||||
{
|
||||
path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`,
|
||||
component: AddonCompetencyPlanPage,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'competencies',
|
||||
path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`,
|
||||
component: AddonCompetencyCompetenciesPage,
|
||||
children: [
|
||||
{
|
||||
|
@ -69,40 +65,28 @@ const tabletRoutes: Routes = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'course/:courseId',
|
||||
component: AddonCompetencyCourseCompetenciesPage,
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: AddonCompetencyPlanListPage,
|
||||
children: [
|
||||
{
|
||||
path: ':planId',
|
||||
component: AddonCompetencyPlanPage,
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
...conditionalRoutes(mobileRoutes, () => CoreScreen.isMobile),
|
||||
...conditionalRoutes(tabletRoutes, () => CoreScreen.isTablet),
|
||||
{
|
||||
path: `:planId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}/:competencyId/${ADDON_COMPETENCY_SUMMARY_PAGE}`,
|
||||
component: AddonCompetencyCompetencySummaryPage,
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CoreSharedModule,
|
||||
AddonCompetencyCourseCompetenciesPageModule,
|
||||
AddonCompetencyCompetenciesPageModule,
|
||||
AddonCompetencyCompetencyPageModule,
|
||||
AddonCompetencyCompetencySummaryPageModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonCompetencyPlanPage,
|
||||
AddonCompetencyPlanListPage,
|
||||
AddonCompetencyCompetenciesPage,
|
||||
AddonCompetencyCompetencyPage,
|
||||
AddonCompetencyCompetencySummaryPage,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyLazyModule {}
|
||||
export class AddonCompetencyLearningPlansLazyModule {}
|
|
@ -30,6 +30,8 @@ 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';
|
||||
import { COURSE_PAGE_NAME } from '@features/course/course.module';
|
||||
import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module';
|
||||
|
||||
// List of providers (without handlers).
|
||||
export const ADDON_COMPETENCY_SERVICES: Type<unknown>[] = [
|
||||
|
@ -37,19 +39,29 @@ export const ADDON_COMPETENCY_SERVICES: Type<unknown>[] = [
|
|||
AddonCompetencyHelperProvider,
|
||||
];
|
||||
|
||||
export const ADDON_COMPETENCY_MAIN_PAGE_NAME = 'competency';
|
||||
export const ADDON_COMPETENCY_LEARNING_PLANS_PAGE = 'learning-plans';
|
||||
export const ADDON_COMPETENCY_COMPETENCIES_PAGE = 'competencies';
|
||||
export const ADDON_COMPETENCY_SUMMARY_PAGE = 'summary';
|
||||
|
||||
const mainMenuChildrenRoutes: Routes = [
|
||||
{
|
||||
path: ADDON_COMPETENCY_MAIN_PAGE_NAME,
|
||||
loadChildren: () => import('./competency-lazy.module').then(m => m.AddonCompetencyLazyModule),
|
||||
path: ADDON_COMPETENCY_LEARNING_PLANS_PAGE,
|
||||
loadChildren: () => import('./competency-learning-plans-lazy.module').then(m => m.AddonCompetencyLearningPlansLazyModule),
|
||||
},
|
||||
{
|
||||
path: `${COURSE_PAGE_NAME}/:courseId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`,
|
||||
loadChildren: () => import('./competency-course-details-lazy.module').then(m => m.AddonCompetencyCourseDetailsLazyModule),
|
||||
},
|
||||
{
|
||||
path: `${COURSE_PAGE_NAME}/:courseId/${PARTICIPANTS_PAGE_NAME}/:userId/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`,
|
||||
loadChildren: () => import('./competency-course-details-lazy.module').then(m => m.AddonCompetencyCourseDetailsLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
const courseIndexRoutes: Routes = [
|
||||
{
|
||||
path: ADDON_COMPETENCY_MAIN_PAGE_NAME,
|
||||
loadChildren: () => import('@addons/competency/competency-course-lazy.module').then(m => m.AddonCompetencyCourseLazyModule),
|
||||
path: ADDON_COMPETENCY_COMPETENCIES_PAGE,
|
||||
loadChildren: () => import('./competency-course-contents-lazy.module').then(m => m.AddonCompetencyCourseContentsLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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 { AddonCompetencyCompetenciesPage } from './competencies.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonCompetencyCompetenciesPage,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyCompetenciesPageModule {}
|
|
@ -0,0 +1,125 @@
|
|||
// (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,
|
||||
} from '../../services/competency';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
|
||||
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
competencies: CoreListItemsManager<
|
||||
AddonCompetencyDataForPlanPageCompetency | AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||
AddonCompetencyPlanCompetenciesSource | AddonCompetencyCourseCompetenciesSource
|
||||
>;
|
||||
|
||||
title = '';
|
||||
|
||||
constructor() {
|
||||
const planId = CoreNavigator.getRouteNumberParam('planId');
|
||||
|
||||
if (!planId) {
|
||||
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||
AddonCompetencyCourseCompetenciesSource,
|
||||
[courseId, userId],
|
||||
);
|
||||
|
||||
this.competencies = new CoreListItemsManager(source, AddonCompetencyCompetenciesPage);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlanCompetenciesSource, [planId]);
|
||||
|
||||
this.competencies = new CoreListItemsManager(source, AddonCompetencyCompetenciesPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
const source = this.competencies.getSource();
|
||||
|
||||
await this.competencies.load();
|
||||
|
||||
if (source instanceof AddonCompetencyPlanCompetenciesSource) {
|
||||
if (!source.plan || source.plan && source.plan.competencycount <= 0) {
|
||||
throw new CoreError(Translate.instant('addon.competency.errornocompetenciesfound'));
|
||||
}
|
||||
|
||||
this.title = source.plan.plan.name;
|
||||
} else {
|
||||
this.title = Translate.instant('addon.competency.coursecompetencies');
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting competencies data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the competencies.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
async refreshCompetencies(refresher?: IonRefresher): Promise<void> {
|
||||
await this.competencies.getSource().invalidateCache();
|
||||
|
||||
this.competencies.getSource().setDirty(true);
|
||||
this.fetchCompetencies().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.competencies.destroy();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,165 +0,0 @@
|
|||
// (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, 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 (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, '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 };
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-content [core-swipe-navigation]="competencies">
|
||||
<ion-refresher slot="fixed" [disabled]="!competencyLoaded" (ionRefresh)="refreshCompetency($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
@ -36,9 +36,7 @@
|
|||
<ion-label>
|
||||
<p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
|
||||
<p>
|
||||
<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>
|
||||
<a *ngIf="competency.competency.comppath.showlinks" [href]="competencyFrameworkUrl" core-link>
|
||||
{{ competency.competency.comppath.framework.name }}
|
||||
</a>
|
||||
<ng-container *ngIf="!competency.competency.comppath.showlinks">
|
||||
|
@ -79,7 +77,8 @@
|
|||
</p>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let activity of coursemodules" [href]="activity.url"
|
||||
[attr.aria-label]="activity.name" core-link capture="true">
|
||||
<core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl"></core-mod-icon>
|
||||
<core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl">
|
||||
</core-mod-icon>
|
||||
<ion-label>
|
||||
<core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id"
|
||||
[courseId]="courseId">
|
||||
|
|
|
@ -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 { AddonCompetencyCompetencyPage } from './competency.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonCompetencyCompetencyPage,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyCompetencyPageModule {}
|
|
@ -0,0 +1,308 @@
|
|||
// (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, OnDestroy, 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,
|
||||
AddonCompetencyDataForPlanPageCompetency,
|
||||
AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||
} 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 { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
|
||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
|
||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
|
||||
|
||||
/**
|
||||
* Page that displays the competency information.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-addon-competency-competency',
|
||||
templateUrl: 'competency.html',
|
||||
})
|
||||
export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
|
||||
|
||||
competencyLoaded = false;
|
||||
competencies!: AddonCompetencyCompetenciesSwipeManager;
|
||||
planStatus?: number;
|
||||
coursemodules?: CoreCourseModuleSummary[];
|
||||
user?: CoreUserSummary;
|
||||
competency?: AddonCompetencyDataForUserCompetencySummaryWSResponse;
|
||||
userCompetency?: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse;
|
||||
contextLevel?: string;
|
||||
contextInstanceId?: number;
|
||||
|
||||
constructor() {
|
||||
try {
|
||||
const planId = CoreNavigator.getRouteNumberParam('planId');
|
||||
|
||||
if (!planId) {
|
||||
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||
AddonCompetencyCourseCompetenciesSource,
|
||||
[courseId, userId],
|
||||
);
|
||||
|
||||
this.competencies = new AddonCompetencyCompetenciesSwipeManager(source);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlanCompetenciesSource, [planId]);
|
||||
|
||||
this.competencies = new AddonCompetencyCompetenciesSwipeManager(source);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
get competencyFrameworkUrl(): string | undefined {
|
||||
if (!this.competency) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { pluginbaseurl, framework, pagecontextid } = this.competency.competency.comppath;
|
||||
|
||||
return `${pluginbaseurl}/competencies.php?competencyframeworkid=${framework.id}&pagecontextid=${pagecontextid}`;
|
||||
}
|
||||
|
||||
get courseId(): number | undefined {
|
||||
const source = this.competencies.getSource();
|
||||
|
||||
if (!(source instanceof AddonCompetencyCourseCompetenciesSource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return source.COURSE_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
try {
|
||||
const source = this.competencies.getSource();
|
||||
|
||||
await source.reload();
|
||||
await this.competencies.start();
|
||||
await this.fetchCompetency();
|
||||
|
||||
if (!this.competency) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = this.competency.competency.competency.shortname;
|
||||
|
||||
if (source instanceof AddonCompetencyPlanCompetenciesSource) {
|
||||
this.planStatus && await CoreUtils.ignoreErrors(
|
||||
AddonCompetency.logCompetencyInPlanView(
|
||||
source.PLAN_ID,
|
||||
this.requireCompetencyId(),
|
||||
this.planStatus,
|
||||
name,
|
||||
source.user?.id,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await CoreUtils.ignoreErrors(
|
||||
AddonCompetency.logCompetencyInCourseView(
|
||||
source.COURSE_ID,
|
||||
this.requireCompetencyId(),
|
||||
name,
|
||||
source.USER_ID,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
this.competencyLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.competencies.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the competency and updates the view.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async fetchCompetency(): Promise<void> {
|
||||
try {
|
||||
const source = this.competencies.getSource();
|
||||
|
||||
this.competency = source instanceof AddonCompetencyPlanCompetenciesSource
|
||||
? await this.fetchCompetencySummaryFromPlan(source)
|
||||
: await this.fetchCompetencySummaryFromCourse(source);
|
||||
|
||||
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> {
|
||||
const source = this.competencies.getSource();
|
||||
|
||||
await CoreUtils.ignoreErrors(
|
||||
source instanceof AddonCompetencyPlanCompetenciesSource
|
||||
? AddonCompetency.invalidateCompetencyInPlan(source.PLAN_ID, this.requireCompetencyId())
|
||||
: AddonCompetency.invalidateCompetencyInCourse(source.COURSE_ID, this.requireCompetencyId(), source.USER_ID),
|
||||
);
|
||||
|
||||
this.fetchCompetency().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the summary of a competency.
|
||||
*
|
||||
* @param competencyId
|
||||
*/
|
||||
openCompetencySummary(competencyId: number): void {
|
||||
CoreNavigator.navigate(
|
||||
`../${competencyId}/${ADDON_COMPETENCY_SUMMARY_PAGE}`,
|
||||
{
|
||||
params: { contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get competency id or fail.
|
||||
*
|
||||
* @returns Competency id.
|
||||
*/
|
||||
private requireCompetencyId(): number {
|
||||
const selectedItem = this.competencies.getSelectedItem();
|
||||
|
||||
if (!selectedItem) {
|
||||
throw new Error('Failed to get competency id from selected item');
|
||||
}
|
||||
|
||||
return selectedItem.competency.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch competency summary from a plan source.
|
||||
*
|
||||
* @param source Plan competencies source.
|
||||
* @returns Competency summary.
|
||||
*/
|
||||
private async fetchCompetencySummaryFromPlan(
|
||||
source: AddonCompetencyPlanCompetenciesSource,
|
||||
): Promise<AddonCompetencyDataForUserCompetencySummaryWSResponse> {
|
||||
const competency = await AddonCompetency.getCompetencyInPlan(
|
||||
source.PLAN_ID,
|
||||
this.requireCompetencyId(),
|
||||
);
|
||||
|
||||
this.planStatus = competency.plan.status;
|
||||
|
||||
if (competency.usercompetencysummary.usercompetency) {
|
||||
competency.usercompetencysummary.usercompetency.statusname =
|
||||
AddonCompetencyHelper.getCompetencyStatusName(competency.usercompetencysummary.usercompetency.status);
|
||||
}
|
||||
|
||||
this.contextLevel = ContextLevel.USER;
|
||||
this.contextInstanceId = source.user?.id || competency.usercompetencysummary.user.id;
|
||||
this.userCompetency = competency.usercompetencysummary.usercompetencyplan
|
||||
|| competency.usercompetencysummary.usercompetency;
|
||||
|
||||
return competency.usercompetencysummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch competency summary from a course source.
|
||||
*
|
||||
* @param source Course competencies source.
|
||||
* @returns Competency summary.
|
||||
*/
|
||||
private async fetchCompetencySummaryFromCourse(
|
||||
source: AddonCompetencyCourseCompetenciesSource,
|
||||
): Promise<AddonCompetencyDataForUserCompetencySummaryWSResponse> {
|
||||
const competency = await AddonCompetency.getCompetencyInCourse(
|
||||
source.COURSE_ID,
|
||||
this.requireCompetencyId(),
|
||||
source.USER_ID,
|
||||
);
|
||||
|
||||
this.coursemodules = competency.coursemodules;
|
||||
|
||||
this.contextLevel = ContextLevel.COURSE;
|
||||
this.contextInstanceId = source.COURSE_ID;
|
||||
this.userCompetency = competency.usercompetencysummary.usercompetencycourse
|
||||
|| competency.usercompetencysummary.usercompetency;
|
||||
|
||||
return competency.usercompetencysummary;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to manage swiping within a collection of competencies.
|
||||
*/
|
||||
class AddonCompetencyCompetenciesSwipeManager
|
||||
extends CoreSwipeNavigationItemsManager<
|
||||
AddonCompetencyDataForPlanPageCompetency | AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||
AddonCompetencyPlanCompetenciesSource | AddonCompetencyCourseCompetenciesSource
|
||||
> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||
return route.params.competencyId;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
// (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 { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
|
||||
/**
|
||||
* Page that displays the competency information.
|
||||
*/
|
||||
@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> {
|
||||
try {
|
||||
this.competencyId = CoreNavigator.getRequiredRouteNumberParam('competencyId');
|
||||
this.planId = CoreNavigator.getRouteNumberParam('planId');
|
||||
if (!this.planId) {
|
||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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(
|
||||
ADDON_COMPETENCY_MAIN_PAGE_NAME + '/summary/' + competencyId,
|
||||
{
|
||||
params: { contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 { AddonCompetencyCompetencySummaryPage } from './competencysummary.page';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreSharedModule,
|
||||
],
|
||||
declarations: [
|
||||
AddonCompetencyCompetencySummaryPage,
|
||||
],
|
||||
})
|
||||
export class AddonCompetencyCompetencySummaryPageModule {}
|
|
@ -19,7 +19,7 @@ import { IonRefresher } from '@ionic/angular';
|
|||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
|
||||
|
||||
/**
|
||||
* Page that displays the competency summary.
|
||||
|
@ -101,8 +101,8 @@ export class AddonCompetencyCompetencySummaryPage implements OnInit {
|
|||
* @param competencyId
|
||||
*/
|
||||
openCompetencySummary(competencyId: number): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_PAGE_NAME + '/summary/' + competencyId,
|
||||
CoreNavigator.navigate(
|
||||
`../../${competencyId}/${ADDON_COMPETENCY_SUMMARY_PAGE}`,
|
||||
{
|
||||
params: { contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId },
|
||||
},
|
|
@ -9,35 +9,35 @@
|
|||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!competenciesLoaded" (ionRefresh)="refreshCourseCompetencies($event.target)">
|
||||
<ion-refresher slot="fixed" [disabled]="!competencies.loaded" (ionRefresh)="refreshCourseCompetencies($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<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">
|
||||
<core-loading [hideUntil]="competencies.loaded">
|
||||
<ion-card *ngIf="!user && courseCompetencies && courseCompetencies.statistics.competencycount > 0">
|
||||
<ng-container *ngIf="courseCompetencies.cangradecompetencies">
|
||||
<ion-item class="ion-text-wrap" *ngIf="courseCompetencies.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-item class="ion-text-wrap" *ngIf="!courseCompetencies.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-item class="ion-text-wrap" *ngIf="courseCompetencies.statistics.canbegradedincourse">
|
||||
<ion-label>
|
||||
<span id="addon-competency-course-{{courseId}}-progress">
|
||||
{{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate: {$a:
|
||||
{x: competencies.statistics.proficientcompetencycount, y: competencies.statistics.competencycount} } }}
|
||||
{x: courseCompetencies.statistics.proficientcompetencycount, y: courseCompetencies.statistics.competencycount} } }}
|
||||
</span>
|
||||
<core-progress-bar [progress]="competencies.statistics.proficientcompetencypercentage"
|
||||
<core-progress-bar [progress]="courseCompetencies.statistics.proficientcompetencypercentage"
|
||||
ariaDescribedBy="addon-competency-course-{{courseId}}-progress">
|
||||
</core-progress-bar>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item class="ion-text-wrap"
|
||||
*ngIf="competencies.statistics.canmanagecoursecompetencies && competencies.statistics.leastproficientcount > 0">
|
||||
*ngIf="courseCompetencies.statistics.canmanagecoursecompetencies && courseCompetencies.statistics.leastproficientcount > 0">
|
||||
<ion-label>
|
||||
<p class="item-heading">{{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}</p>
|
||||
<p *ngFor="let comp of competencies.statistics.leastproficient">
|
||||
<p *ngFor="let comp of courseCompetencies.statistics.leastproficient">
|
||||
<button class="as-link" (click)="openCompetencySummary(comp.id)">
|
||||
{{ comp.shortname }} - {{ comp.idnumber }}
|
||||
</button>
|
||||
|
@ -46,7 +46,7 @@
|
|||
</ion-item>
|
||||
</ion-card>
|
||||
|
||||
<h2 class="ion-margin-horizontal" *ngIf="competencies && competencies.statistics.competencycount > 0">
|
||||
<h2 class="ion-margin-horizontal" *ngIf="courseCompetencies && courseCompetencies.statistics.competencycount > 0">
|
||||
{{ 'addon.competency.coursecompetencies' | translate }}
|
||||
</h2>
|
||||
<ion-card *ngIf="user">
|
||||
|
@ -57,13 +57,13 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-card>
|
||||
<core-empty-box *ngIf="competencies && competencies.statistics.competencycount == 0" icon="fas-award"
|
||||
<core-empty-box *ngIf="courseCompetencies && courseCompetencies.statistics.competencycount == 0" icon="fas-award"
|
||||
message="{{ 'addon.competency.nocompetenciesincourse' | translate }}">
|
||||
</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)"
|
||||
<div *ngIf="competencies.loaded">
|
||||
<ion-card *ngFor="let competency of competencies.items">
|
||||
<ion-item class="ion-text-wrap" (click)="competencies.select(competency)"
|
||||
[attr.aria-label]="competency.competency.shortname" detail="true" button>
|
||||
<ion-label>
|
||||
<p class="item-heading">
|
||||
|
@ -85,8 +85,7 @@
|
|||
<div>
|
||||
<p class="item-heading">{{ 'addon.competency.path' | translate }}</p>
|
||||
<p>
|
||||
<a *ngIf="competency.comppath.showlinks" [href]="competency.comppath.pluginbaseurl + '/competencies.php?competencyframeworkid=' +
|
||||
competency.comppath.framework.id + '&pagecontextid=' + competency.comppath.pagecontextid" core-link
|
||||
<a *ngIf="competency.comppath.showlinks" [href]="getCompetencyFrameworkUrl(competency)" core-link
|
||||
[title]="competency.comppath.framework.name">
|
||||
{{ competency.comppath.framework.name }}
|
||||
</a>
|
||||
|
@ -104,7 +103,7 @@
|
|||
</ng-container>
|
||||
</p>
|
||||
</div>
|
||||
<div *ngIf="competencies.statistics.canmanagecoursecompetencies">
|
||||
<div *ngIf="courseCompetencies?.statistics.canmanagecoursecompetencies">
|
||||
<p class="item-heading">{{ 'addon.competency.uponcoursecompletion' | translate }}</p>
|
||||
<ng-container *ngFor="let ruleoutcome of competency.ruleoutcomeoptions">
|
||||
<span *ngIf="ruleoutcome.selected">{{ ruleoutcome.text }}</span>
|
||||
|
|
|
@ -12,15 +12,20 @@
|
|||
// 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 { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import {
|
||||
AddonCompetencyDataForCourseCompetenciesPageWSResponse,
|
||||
AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||
} from '@addons/competency/services/competency';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { ContextLevel } from '@/core/constants';
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
|
||||
|
||||
/**
|
||||
* Page that displays the list of competencies of a course.
|
||||
|
@ -29,22 +34,23 @@ import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.m
|
|||
selector: 'page-addon-competency-coursecompetencies',
|
||||
templateUrl: 'coursecompetencies.html',
|
||||
})
|
||||
export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
||||
export class AddonCompetencyCourseCompetenciesPage implements OnInit, OnDestroy {
|
||||
|
||||
competenciesLoaded = false;
|
||||
competencies?: AddonCompetencyDataForCourseCompetenciesPageWSResponse;
|
||||
user?: CoreUserProfile;
|
||||
courseId!: number;
|
||||
competencies!: CoreListItemsManager<
|
||||
AddonCompetencyDataForCourseCompetenciesPageCompetency,
|
||||
AddonCompetencyCourseCompetenciesSource
|
||||
>;
|
||||
|
||||
protected userId?: number;
|
||||
|
||||
/**
|
||||
* View loaded.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
constructor() {
|
||||
try {
|
||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||
AddonCompetencyCourseCompetenciesSource,
|
||||
[courseId, userId],
|
||||
);
|
||||
|
||||
this.competencies = new CoreListItemsManager(source, AddonCompetencyCourseCompetenciesPage);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
|
||||
|
@ -52,10 +58,50 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
|||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.fetchCourseCompetencies().finally(() => {
|
||||
this.competenciesLoaded = true;
|
||||
});
|
||||
get courseCompetencies(): AddonCompetencyDataForCourseCompetenciesPageWSResponse | undefined {
|
||||
return this.competencies.getSource().courseCompetencies;
|
||||
}
|
||||
|
||||
get courseId(): number {
|
||||
return this.competencies.getSource().COURSE_ID;
|
||||
}
|
||||
|
||||
get user(): CoreUserProfile | undefined {
|
||||
return this.competencies.getSource().user;
|
||||
}
|
||||
|
||||
get showLeastProficientCompetencies(): boolean {
|
||||
return !!this.courseCompetencies?.statistics.canmanagecoursecompetencies
|
||||
&& this.courseCompetencies?.statistics.leastproficientcount > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.fetchCourseCompetencies();
|
||||
await this.competencies.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.competencies.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get competency framework url.
|
||||
*
|
||||
* @param competency Competency.
|
||||
* @returns Competency framework url.
|
||||
*/
|
||||
getCompetencyFrameworkUrl(competency: AddonCompetencyDataForCourseCompetenciesPageCompetency): string {
|
||||
const { pluginbaseurl, framework, pagecontextid } = competency.comppath;
|
||||
|
||||
return `${pluginbaseurl}/competencies.php?competencyframeworkid=${framework.id}&pagecontextid=${pagecontextid}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -65,40 +111,27 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
|||
*/
|
||||
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);
|
||||
await this.competencies.getSource().reload();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting course competencies data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a competency.
|
||||
*
|
||||
* @param competencyId
|
||||
*/
|
||||
openCompetency(competencyId: number): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_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(ADDON_COMPETENCY_MAIN_PAGE_NAME + '/summary/' + competencyId, {
|
||||
params: {
|
||||
contextLevel: ContextLevel.COURSE,
|
||||
contextInstanceId: this.courseId,
|
||||
} });
|
||||
CoreNavigator.navigateToSitePath(
|
||||
`./${competencyId}/${ADDON_COMPETENCY_SUMMARY_PAGE}`,
|
||||
{
|
||||
params: {
|
||||
contextLevel: ContextLevel.COURSE,
|
||||
contextInstanceId: this.courseId,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,11 +139,11 @@ export class AddonCompetencyCourseCompetenciesPage implements OnInit {
|
|||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshCourseCompetencies(refresher?: IonRefresher): void {
|
||||
AddonCompetency.invalidateCourseCompetencies(this.courseId, this.userId).finally(() => {
|
||||
this.fetchCourseCompetencies().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
async refreshCourseCompetencies(refresher?: IonRefresher): Promise<void> {
|
||||
await this.competencies.getSource().invalidateCache();
|
||||
|
||||
this.fetchCourseCompetencies().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshLearningPlan($event.target)">
|
||||
<ion-content [core-swipe-navigation]="plans">
|
||||
<ion-refresher slot="fixed" [disabled]="!competencies.loaded" (ionRefresh)="refreshLearningPlan($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<core-loading [hideUntil]="competencies.loaded">
|
||||
<ion-card *ngIf="user">
|
||||
<ion-item class="ion-text-wrap">
|
||||
<ion-label>
|
||||
|
@ -74,9 +74,8 @@
|
|||
<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)" [attr.aria-label]="competency.competency.shortname" detail="true"
|
||||
button>
|
||||
<ion-item class="ion-text-wrap" *ngFor="let competency of competencies.items" (click)="competencies.select(competency)"
|
||||
[attr.aria-label]="competency.competency.shortname" detail="true" button>
|
||||
<ion-label>
|
||||
<p class="item-heading">{{competency.competency.shortname}} <em>{{competency.competency.idnumber}}</em></p>
|
||||
</ion-label>
|
||||
|
|
|
@ -12,14 +12,17 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { AddonCompetencyDataForPlanPageWSResponse, AddonCompetency } from '../../services/competency';
|
||||
import { AddonCompetencyHelper } from '../../services/competency-helper';
|
||||
import { AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForPlanPageWSResponse } from '../../services/competency';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
import { AddonCompetencyPlanCompetenciesSource } from '@addons/competency/classes/competency-plan-competencies-source';
|
||||
|
||||
/**
|
||||
* Page that displays a learning plan.
|
||||
|
@ -28,19 +31,26 @@ import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.m
|
|||
selector: 'page-addon-competency-plan',
|
||||
templateUrl: 'plan.html',
|
||||
})
|
||||
export class AddonCompetencyPlanPage implements OnInit {
|
||||
export class AddonCompetencyPlanPage implements OnInit, OnDestroy {
|
||||
|
||||
protected planId!: number;
|
||||
loaded = false;
|
||||
plan?: AddonCompetencyDataForPlanPageWSResponse;
|
||||
user?: CoreUserProfile;
|
||||
plans!: CoreSwipeNavigationItemsManager;
|
||||
competencies!: CoreListItemsManager<AddonCompetencyDataForPlanPageCompetency, AddonCompetencyPlanCompetenciesSource>;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
constructor() {
|
||||
try {
|
||||
this.planId = CoreNavigator.getRequiredRouteNumberParam('planId');
|
||||
const planId = CoreNavigator.getRequiredRouteNumberParam('planId');
|
||||
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
const plansSource = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||
AddonCompetencyPlansSource,
|
||||
[userId],
|
||||
);
|
||||
const competenciesSource = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||
AddonCompetencyPlanCompetenciesSource,
|
||||
[planId],
|
||||
);
|
||||
|
||||
this.competencies = new CoreListItemsManager(competenciesSource, AddonCompetencyPlanPage);
|
||||
this.plans = new CoreSwipeNavigationItemsManager(plansSource);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
|
||||
|
@ -48,10 +58,31 @@ export class AddonCompetencyPlanPage implements OnInit {
|
|||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.fetchLearningPlan().finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
get plan(): AddonCompetencyDataForPlanPageWSResponse | undefined {
|
||||
return this.competencies.getSource().plan;
|
||||
}
|
||||
|
||||
get user(): CoreUserProfile | undefined {
|
||||
return this.competencies.getSource().user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
await this.fetchLearningPlan();
|
||||
await this.plans.start();
|
||||
await this.competencies.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.plans.destroy();
|
||||
this.competencies.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,40 +92,22 @@ export class AddonCompetencyPlanPage implements OnInit {
|
|||
*/
|
||||
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;
|
||||
await this.competencies.getSource().reload();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plan data.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to a particular competency.
|
||||
*
|
||||
* @param competencyId
|
||||
*/
|
||||
openCompetency(competencyId: number): void {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_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();
|
||||
});
|
||||
async refreshLearningPlan(refresher: IonRefresher): Promise<void> {
|
||||
await this.competencies.getSource().invalidateCache();
|
||||
|
||||
this.fetchLearningPlan().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
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 { 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 { AddonCompetencyPlanFormatted, AddonCompetencyPlansSource } from '@addons/competency/classes/competency-plans-source';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
|
||||
/**
|
||||
* Page that displays the list of learning plans.
|
||||
|
@ -28,22 +28,17 @@ import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
|||
selector: 'page-addon-competency-planlist',
|
||||
templateUrl: 'planlist.html',
|
||||
})
|
||||
export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDestroy {
|
||||
export class AddonCompetencyPlanListPage implements AfterViewInit, OnDestroy {
|
||||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
protected userId?: number;
|
||||
plans: AddonCompetencyPlanListManager;
|
||||
plans: CoreListItemsManager<AddonCompetencyPlanFormatted, AddonCompetencyPlansSource>;
|
||||
|
||||
constructor() {
|
||||
this.plans = new AddonCompetencyPlanListManager(AddonCompetencyPlanListPage);
|
||||
}
|
||||
const userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(AddonCompetencyPlansSource, [userId]);
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId');
|
||||
this.plans = new CoreListItemsManager(source, AddonCompetencyPlanListPage);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,23 +57,7 @@ export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDes
|
|||
*/
|
||||
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);
|
||||
|
||||
await this.plans.load();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error getting learning plans data.');
|
||||
}
|
||||
|
@ -89,11 +68,12 @@ export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDes
|
|||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshLearningPlans(refresher: IonRefresher): void {
|
||||
AddonCompetency.invalidateLearningPlans(this.userId).finally(() => {
|
||||
this.fetchLearningPlans().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
async refreshLearningPlans(refresher: IonRefresher): Promise<void> {
|
||||
await this.plans.getSource().invalidateCache();
|
||||
|
||||
this.plans.getSource().setDirty(true);
|
||||
this.fetchLearningPlans().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -105,28 +85,3 @@ export class AddonCompetencyPlanListPage implements OnInit, AfterViewInit, OnDes
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,10 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { COURSE_PAGE_NAME } from '@features/course/course.module';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
|
@ -37,17 +38,29 @@ export class AddonCompetencyCompetencyLinkHandlerService extends CoreContentLink
|
|||
|
||||
return [{
|
||||
action: (siteId: string): void => {
|
||||
const pageParams = {
|
||||
planId: params.planid,
|
||||
courseId: courseId,
|
||||
userId: params.userid,
|
||||
};
|
||||
if (courseId) {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
`${COURSE_PAGE_NAME}/${courseId}/${ADDON_COMPETENCY_COMPETENCIES_PAGE}`,
|
||||
{
|
||||
params: { userId: params.userid },
|
||||
siteId,
|
||||
},
|
||||
);
|
||||
|
||||
CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_PAGE_NAME + '/competencies/' + params.competencyid,
|
||||
{ params: pageParams, siteId },
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.planid) {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
`${ADDON_COMPETENCY_LEARNING_PLANS_PAGE}/competencies/${params.planid}`,
|
||||
{
|
||||
params: { userId: params.userid },
|
||||
siteId,
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/service
|
|||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
|
||||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||
import { ContextLevel } from '@/core/constants';
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_COMPETENCIES_PAGE } from '@addons/competency/competency.module';
|
||||
|
||||
/**
|
||||
* Course nav handler.
|
||||
|
@ -75,7 +75,7 @@ export class AddonCompetencyCourseOptionHandlerService implements CoreCourseOpti
|
|||
return {
|
||||
title: 'addon.competency.competencies',
|
||||
class: 'addon-competency-course-handler',
|
||||
page: ADDON_COMPETENCY_MAIN_PAGE_NAME,
|
||||
page: ADDON_COMPETENCY_COMPETENCIES_PAGE,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
|
@ -36,7 +36,7 @@ export class AddonCompetencyPlanLinkHandlerService extends CoreContentLinksHandl
|
|||
return [{
|
||||
action: (siteId: string): void => {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_PAGE_NAME + '/' + params.id,
|
||||
`${ADDON_COMPETENCY_LEARNING_PLANS_PAGE}/${params.id}`,
|
||||
{ siteId },
|
||||
);
|
||||
},
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
|
@ -36,7 +36,7 @@ export class AddonCompetencyPlansLinkHandlerService extends CoreContentLinksHand
|
|||
return [{
|
||||
action: (siteId: string): void => {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_PAGE_NAME,
|
||||
ADDON_COMPETENCY_LEARNING_PLANS_PAGE,
|
||||
{ params: { userId: params.userid }, siteId },
|
||||
);
|
||||
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { COURSE_PAGE_NAME } from '@features/course/course.module';
|
||||
import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate';
|
||||
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
|
@ -56,7 +57,7 @@ export class AddonCompetencyPushClickHandlerService implements CorePushNotificat
|
|||
|
||||
await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlan(planId, notification.site));
|
||||
|
||||
await CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_MAIN_PAGE_NAME + '/' + planId, {
|
||||
await CoreNavigator.navigateToSitePath(`${ADDON_COMPETENCY_LEARNING_PLANS_PAGE}/${planId}`, {
|
||||
siteId: notification.site,
|
||||
});
|
||||
|
||||
|
@ -71,15 +72,30 @@ export class AddonCompetencyPushClickHandlerService implements CorePushNotificat
|
|||
const userId = Number(contextUrlParams.userid);
|
||||
|
||||
await CoreUtils.ignoreErrors(AddonCompetency.invalidateCompetencyInPlan(planId, competencyId, notification.site));
|
||||
await CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_PAGE_NAME + '/competencies/' + competencyId,
|
||||
{
|
||||
params: { planId, courseId, userId },
|
||||
siteId: notification.site,
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
if (courseId) {
|
||||
await CoreNavigator.navigateToSitePath(
|
||||
`${COURSE_PAGE_NAME}/${courseId}/${ADDON_COMPETENCY_COMPETENCIES_PAGE}/${competencyId}`,
|
||||
{
|
||||
params: { userId },
|
||||
siteId: notification.site,
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (planId) {
|
||||
await CoreNavigator.navigateToSitePath(
|
||||
`${ADDON_COMPETENCY_LEARNING_PLANS_PAGE}/competencies/${planId}/${competencyId}`,
|
||||
{
|
||||
params: { userId },
|
||||
siteId: notification.site,
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Open the list of plans.
|
||||
|
@ -87,7 +103,7 @@ export class AddonCompetencyPushClickHandlerService implements CorePushNotificat
|
|||
|
||||
await CoreUtils.ignoreErrors(AddonCompetency.invalidateLearningPlans(userId, notification.site));
|
||||
|
||||
await CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_MAIN_PAGE_NAME, {
|
||||
await CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_LEARNING_PLANS_PAGE, {
|
||||
params: { userId },
|
||||
siteId: notification.site,
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_SUMMARY_PAGE } from '@addons/competency/competency.module';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
|
@ -33,14 +33,12 @@ export class AddonCompetencyUserCompetencyLinkHandlerService extends CoreContent
|
|||
* @inheritdoc
|
||||
*/
|
||||
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
|
||||
|
||||
return [{
|
||||
action: (siteId: string): void => {
|
||||
CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_PAGE_NAME + '/summary/' + params.id,
|
||||
`${ADDON_COMPETENCY_COMPETENCIES_PAGE}/${params.id}/${ADDON_COMPETENCY_SUMMARY_PAGE}`,
|
||||
{ siteId },
|
||||
);
|
||||
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { ADDON_COMPETENCY_MAIN_PAGE_NAME } from '@addons/competency/competency.module';
|
||||
import { ADDON_COMPETENCY_COMPETENCIES_PAGE, ADDON_COMPETENCY_LEARNING_PLANS_PAGE } from '@addons/competency/competency.module';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { COURSE_PAGE_NAME } from '@features/course/course.module';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
import { CoreUserProfileHandler, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
|
||||
import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonCompetency } from '../competency';
|
||||
|
@ -69,12 +71,8 @@ export class AddonCompetencyUserHandlerService implements CoreUserProfileHandler
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
CoreNavigator.navigateToSitePath(
|
||||
ADDON_COMPETENCY_MAIN_PAGE_NAME + '/course/' + courseId,
|
||||
{
|
||||
params: { userId: user.id },
|
||||
},
|
||||
[COURSE_PAGE_NAME, courseId, PARTICIPANTS_PAGE_NAME, user.id, ADDON_COMPETENCY_COMPETENCIES_PAGE].join('/'),
|
||||
);
|
||||
|
||||
},
|
||||
};
|
||||
} else {
|
||||
|
@ -85,7 +83,7 @@ export class AddonCompetencyUserHandlerService implements CoreUserProfileHandler
|
|||
action: (event, user): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_MAIN_PAGE_NAME, {
|
||||
CoreNavigator.navigateToSitePath(ADDON_COMPETENCY_LEARNING_PLANS_PAGE, {
|
||||
params: { userId: user.id },
|
||||
});
|
||||
},
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { CoreUser } from '@features/user/services/user';
|
||||
import { CoreGroupInfo, CoreGroups } from '@services/groups';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate } from '@singletons';
|
||||
import { AddonModChat, AddonModChatSession, AddonModChatSessionUser } from '../services/chat';
|
||||
|
||||
/**
|
||||
* Provides a collection of sessions.
|
||||
*/
|
||||
export class AddonModChatSessionsSource extends CoreRoutedItemsManagerSource<AddonModChatSessionFormatted> {
|
||||
|
||||
readonly COURSE_ID: number;
|
||||
readonly CHAT_ID: number;
|
||||
readonly CM_ID: number;
|
||||
|
||||
showAll = false;
|
||||
groupId = 0;
|
||||
groupInfo?: CoreGroupInfo;
|
||||
|
||||
constructor(courseId: number, chatId: number, cmId: number) {
|
||||
super();
|
||||
|
||||
this.COURSE_ID = courseId;
|
||||
this.CHAT_ID = chatId;
|
||||
this.CM_ID = cmId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate chat cache.
|
||||
*/
|
||||
async invalidateCache(): Promise<void> {
|
||||
await CoreUtils.ignoreErrors(CoreUtils.allPromises([
|
||||
CoreGroups.invalidateActivityGroupInfo(this.CM_ID),
|
||||
AddonModChat.invalidateSessions(this.CHAT_ID, this.groupId, this.showAll),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async loadPageItems(): Promise<{ items: AddonModChatSessionFormatted[] }> {
|
||||
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.CM_ID, false);
|
||||
|
||||
this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo);
|
||||
|
||||
const sessions = await AddonModChat.getSessions(this.CHAT_ID, this.groupId, this.showAll, { cmId: this.CM_ID });
|
||||
|
||||
// Fetch user profiles.
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
const formattedSessions = sessions.map((session: AddonModChatSessionFormatted) => {
|
||||
session.duration = session.sessionend - session.sessionstart;
|
||||
session.sessionusers.forEach((sessionUser) => {
|
||||
// The WS does not return the user name, fetch user profile.
|
||||
promises.push(this.loadUserFullname(sessionUser));
|
||||
});
|
||||
|
||||
// If session has more than 4 users we display a "Show more" link.
|
||||
session.allsessionusers = session.sessionusers;
|
||||
if (session.sessionusers.length > 4) {
|
||||
session.sessionusers = session.allsessionusers.slice(0, 3);
|
||||
}
|
||||
|
||||
return session;
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return { items: formattedSessions };
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemPath(session: AddonModChatSessionFormatted): string {
|
||||
return `${session.sessionstart}/${session.sessionend}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemQueryParams(): Params {
|
||||
return {
|
||||
chatId: this.CHAT_ID,
|
||||
groupId: this.groupId,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the fullname of a user.
|
||||
*
|
||||
* @param id User ID.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async loadUserFullname(sessionUser: AddonModChatUserSessionFormatted): Promise<void> {
|
||||
if (sessionUser.userfullname) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await CoreUser.getProfile(sessionUser.userid, this.COURSE_ID, true);
|
||||
|
||||
sessionUser.userfullname = user.fullname;
|
||||
} catch {
|
||||
// Error getting profile, most probably the user is deleted.
|
||||
sessionUser.userfullname = Translate.instant('core.deleteduser') + ' ' + sessionUser.userid;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields added to chat session in this view.
|
||||
*/
|
||||
export type AddonModChatSessionFormatted = Omit<AddonModChatSession, 'sessionusers'> & {
|
||||
duration?: number; // Session duration.
|
||||
sessionusers: AddonModChatUserSessionFormatted[];
|
||||
allsessionusers?: AddonModChatUserSessionFormatted[]; // All session users.
|
||||
};
|
||||
|
||||
/**
|
||||
* Fields added to user session in this view.
|
||||
*/
|
||||
export type AddonModChatUserSessionFormatted = AddonModChatSessionUser & {
|
||||
userfullname?: string; // User full name.
|
||||
};
|
|
@ -19,7 +19,7 @@
|
|||
<ng-container *ngIf="groupInfo.separateGroups">{{'core.groupsseparate' | translate }}</ng-container>
|
||||
<ng-container *ngIf="groupInfo.visibleGroups">{{'core.groupsvisible' | translate }}</ng-container>
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="groupId" (ionChange)="fetchSessions(true)" aria-labelledby="addon-chat-groupslabel"
|
||||
<ion-select [(ngModel)]="groupId" (ionChange)="reloadSessions()" aria-labelledby="addon-chat-groupslabel"
|
||||
interface="action-sheet" [interfaceOptions]="{header: 'core.group' | translate}">
|
||||
<ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">
|
||||
{{groupOpt.name}}
|
||||
|
@ -29,7 +29,7 @@
|
|||
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.mod_chat.showincompletesessions' | translate }}</ion-label>
|
||||
<ion-toggle [(ngModel)]="showAll" (ionChange)="fetchSessions(true)"></ion-toggle>
|
||||
<ion-toggle [(ngModel)]="showAll" (ionChange)="reloadSessions()"></ion-toggle>
|
||||
</ion-item>
|
||||
|
||||
<ion-card *ngFor="let session of sessions.items" (click)="sessions.select(session)" button
|
||||
|
|
|
@ -13,17 +13,14 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreUser } from '@features/user/services/user';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreGroupInfo, CoreGroups } from '@services/groups';
|
||||
import { CoreGroupInfo } from '@services/groups';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate } from '@singletons';
|
||||
import { AddonModChat, AddonModChatSession, AddonModChatSessionUser } from '../../services/chat';
|
||||
import { AddonModChatSessionFormatted, AddonModChatSessionsSource } from '../../classes/chat-sessions-source';
|
||||
|
||||
/**
|
||||
* Page that displays list of chat sessions.
|
||||
|
@ -36,28 +33,19 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
|
|||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
sessions!: AddonChatSessionsManager;
|
||||
showAll = false;
|
||||
groupId = 0;
|
||||
groupInfo?: CoreGroupInfo;
|
||||
|
||||
protected courseId!: number;
|
||||
protected cmId!: number;
|
||||
protected chatId!: number;
|
||||
sessions!: CoreListItemsManager<AddonModChatSessionFormatted, AddonModChatSessionsSource>;
|
||||
|
||||
constructor() {
|
||||
this.sessions = new AddonChatSessionsManager(AddonModChatSessionsPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
try {
|
||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
||||
this.chatId = CoreNavigator.getRequiredRouteNumberParam('chatId');
|
||||
this.sessions.setChatId(this.chatId);
|
||||
const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
const chatId = CoreNavigator.getRequiredRouteNumberParam('chatId');
|
||||
const cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||
AddonModChatSessionsSource,
|
||||
[courseId, chatId, cmId],
|
||||
);
|
||||
|
||||
this.sessions = new CoreListItemsManager(source, AddonModChatSessionsPage);
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
|
||||
|
@ -65,7 +53,32 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
|
|||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
get groupId(): number {
|
||||
return this.sessions.getSource().groupId;
|
||||
}
|
||||
|
||||
set groupId(value: number) {
|
||||
this.sessions.getSource().groupId = value;
|
||||
}
|
||||
|
||||
get showAll(): boolean {
|
||||
return this.sessions.getSource().showAll;
|
||||
}
|
||||
|
||||
set showAll(value: boolean) {
|
||||
this.sessions.getSource().showAll = value;
|
||||
}
|
||||
|
||||
get groupInfo(): CoreGroupInfo | undefined {
|
||||
return this.sessions.getSource().groupInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
await this.fetchSessions();
|
||||
|
||||
this.sessions.start(this.splitView);
|
||||
|
@ -75,66 +88,27 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
|
|||
* Fetch chat sessions.
|
||||
*
|
||||
* @param showLoading Display a loading modal.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async fetchSessions(showLoading?: boolean): Promise<void> {
|
||||
const modal = showLoading ? await CoreDomUtils.showModalLoading() : null;
|
||||
|
||||
async fetchSessions(): Promise<void> {
|
||||
try {
|
||||
this.groupInfo = await CoreGroups.getActivityGroupInfo(this.cmId, false);
|
||||
|
||||
this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo);
|
||||
this.sessions.setGroupId(this.groupId);
|
||||
|
||||
const sessions = await AddonModChat.getSessions(this.chatId, this.groupId, this.showAll, { cmId: this.cmId });
|
||||
|
||||
// Fetch user profiles.
|
||||
const promises: Promise<unknown>[] = [];
|
||||
|
||||
const formattedSessions = sessions.map((session: AddonModChatSessionFormatted) => {
|
||||
session.duration = session.sessionend - session.sessionstart;
|
||||
session.sessionusers.forEach((sessionUser) => {
|
||||
// The WS does not return the user name, fetch user profile.
|
||||
promises.push(this.loadUserFullname(sessionUser));
|
||||
});
|
||||
|
||||
// If session has more than 4 users we display a "Show more" link.
|
||||
session.allsessionusers = session.sessionusers;
|
||||
if (session.sessionusers.length > 4) {
|
||||
session.sessionusers = session.allsessionusers.slice(0, 3);
|
||||
}
|
||||
|
||||
return session;
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
this.sessions.setItems(formattedSessions);
|
||||
await this.sessions.load();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true);
|
||||
} finally {
|
||||
modal?.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the fullname of a user.
|
||||
*
|
||||
* @param id User ID.
|
||||
* @return Promise resolved when done.
|
||||
* Reload chat sessions.
|
||||
*/
|
||||
protected async loadUserFullname(sessionUser: AddonModChatUserSessionFormatted): Promise<void> {
|
||||
if (sessionUser.userfullname) {
|
||||
return;
|
||||
}
|
||||
async reloadSessions(): Promise<void> {
|
||||
const modal = await CoreDomUtils.showModalLoading();
|
||||
|
||||
try {
|
||||
const user = await CoreUser.getProfile(sessionUser.userid, this.courseId, true);
|
||||
|
||||
sessionUser.userfullname = user.fullname;
|
||||
} catch {
|
||||
// Error getting profile, most probably the user is deleted.
|
||||
sessionUser.userfullname = Translate.instant('core.deleteduser') + ' ' + sessionUser.userid;
|
||||
await this.sessions.reload();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true);
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,11 +119,9 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
|
|||
*/
|
||||
async refreshSessions(refresher: IonRefresher): Promise<void> {
|
||||
try {
|
||||
await CoreUtils.ignoreErrors(CoreUtils.allPromises([
|
||||
CoreGroups.invalidateActivityGroupInfo(this.cmId),
|
||||
AddonModChat.invalidateSessions(this.chatId, this.groupId, this.showAll),
|
||||
]));
|
||||
this.sessions.getSource().setDirty(true);
|
||||
|
||||
await this.sessions.getSource().invalidateCache();
|
||||
await this.fetchSessions();
|
||||
} finally {
|
||||
refresher.complete();
|
||||
|
@ -163,7 +135,10 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
|
|||
* @param event The event.
|
||||
*/
|
||||
showMoreUsers(session: AddonModChatSessionFormatted, event: Event): void {
|
||||
session.sessionusers = session.allsessionusers!;
|
||||
if (session.allsessionusers) {
|
||||
session.sessionusers = session.allsessionusers;
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
|
@ -175,68 +150,3 @@ export class AddonModChatSessionsPage implements AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to manage sessions.
|
||||
*/
|
||||
class AddonChatSessionsManager extends CorePageItemsListManager<AddonModChatSessionFormatted> {
|
||||
|
||||
chatId = -1;
|
||||
groupId = 0;
|
||||
|
||||
constructor(pageComponent: unknown) {
|
||||
super(pageComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set chat ID.
|
||||
*
|
||||
* @param chatId Chat ID.
|
||||
*/
|
||||
setChatId(chatId: number): void {
|
||||
this.chatId = chatId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set group ID.
|
||||
*
|
||||
* @param groupId Group ID.
|
||||
*/
|
||||
setGroupId(groupId: number): void {
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemPath(session: AddonModChatSessionFormatted): string {
|
||||
return `${session.sessionstart}/${session.sessionend}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemQueryParams(): Params {
|
||||
return {
|
||||
chatId: this.chatId,
|
||||
groupId: this.groupId,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields added to chat session in this view.
|
||||
*/
|
||||
type AddonModChatSessionFormatted = Omit<AddonModChatSession, 'sessionusers'> & {
|
||||
duration?: number; // Session duration.
|
||||
sessionusers: AddonModChatUserSessionFormatted[];
|
||||
allsessionusers?: AddonModChatUserSessionFormatted[]; // All session users.
|
||||
};
|
||||
|
||||
/**
|
||||
* Fields added to user session in this view.
|
||||
*/
|
||||
type AddonModChatUserSessionFormatted = AddonModChatSessionUser & {
|
||||
userfullname?: string; // User full name.
|
||||
};
|
||||
|
|
|
@ -64,8 +64,10 @@ export class CoreListItemsManager<
|
|||
*
|
||||
* @param splitView Split view component.
|
||||
*/
|
||||
async start(splitView: CoreSplitViewComponent): Promise<void> {
|
||||
this.watchSplitViewOutlet(splitView);
|
||||
async start(splitView?: CoreSplitViewComponent): Promise<void> {
|
||||
if (splitView) {
|
||||
this.watchSplitViewOutlet(splitView);
|
||||
}
|
||||
|
||||
// Calculate current selected item.
|
||||
this.updateSelectedItem();
|
||||
|
@ -121,7 +123,13 @@ export class CoreListItemsManager<
|
|||
*
|
||||
* @param item Item.
|
||||
*/
|
||||
async select(item: Item): Promise<void> {
|
||||
async select(item: Item | null): Promise<void> {
|
||||
if (!item) {
|
||||
await this.navigateToIndex({ reset: this.resetNavigation() });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await this.navigateToItem(item, { reset: this.resetNavigation() });
|
||||
}
|
||||
|
||||
|
@ -172,17 +180,9 @@ export class CoreListItemsManager<
|
|||
protected updateSelectedItem(route: ActivatedRouteSnapshot | null = null): void {
|
||||
super.updateSelectedItem(route);
|
||||
|
||||
if (CoreScreen.isMobile || this.selectedItem !== null || this.splitView?.isNested) {
|
||||
return;
|
||||
}
|
||||
const selectDefault = CoreScreen.isTablet && this.selectedItem === null && this.splitView && !this.splitView.isNested;
|
||||
|
||||
const defaultItem = this.getDefaultItem();
|
||||
|
||||
if (!defaultItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.select(defaultItem);
|
||||
this.select(selectDefault ? this.getDefaultItem() : this.selectedItem);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -119,6 +119,34 @@ export abstract class CoreRoutedItemsManager<
|
|||
await CoreNavigator.navigate(pathPrefix + itemPath, { params, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the index page.
|
||||
*
|
||||
* @param options Navigation options.
|
||||
*/
|
||||
protected async navigateToIndex(
|
||||
options: Pick<CoreNavigationOptions, 'reset' | 'replace' | 'animationDirection'> = {},
|
||||
): Promise<void> {
|
||||
// Get current route in the page.
|
||||
const route = this.getCurrentPageRoute();
|
||||
|
||||
if (route === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the current page is already the index, do nothing.
|
||||
const selectedItemPath = this.getSelectedItemPath(route.snapshot);
|
||||
|
||||
if (selectedItemPath === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate to index.
|
||||
const indexPath = selectedItemPath ? selectedItemPath.split('/').fill('../').join('') : '';
|
||||
|
||||
await CoreNavigator.navigate(indexPath, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
|
|
@ -1,300 +0,0 @@
|
|||
// (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 { ActivatedRoute, ActivatedRouteSnapshot, Params, UrlSegment } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
||||
/**
|
||||
* Helper class to manage the state and routing of a list of items in a page, for example on pages using a split view.
|
||||
*
|
||||
* @deprecated use CoreListItemsManager instead.
|
||||
*/
|
||||
export abstract class CorePageItemsListManager<Item> {
|
||||
|
||||
protected itemsList: Item[] | null = null;
|
||||
protected itemsMap: Record<string, Item> | null = null;
|
||||
protected hasMoreItems = true;
|
||||
protected selectedItem: Item | null = null;
|
||||
protected pageRouteLocator?: unknown | ActivatedRoute;
|
||||
protected splitView?: CoreSplitViewComponent;
|
||||
protected splitViewOutletSubscription?: Subscription;
|
||||
|
||||
constructor(pageRouteLocator: unknown | ActivatedRoute) {
|
||||
this.pageRouteLocator = pageRouteLocator;
|
||||
}
|
||||
|
||||
get items(): Item[] {
|
||||
return this.itemsList || [];
|
||||
}
|
||||
|
||||
get loaded(): boolean {
|
||||
return this.itemsMap !== null;
|
||||
}
|
||||
|
||||
get completed(): boolean {
|
||||
return !this.hasMoreItems;
|
||||
}
|
||||
|
||||
get empty(): boolean {
|
||||
return this.itemsList === null || this.itemsList.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process page started operations.
|
||||
*
|
||||
* @param splitView Split view component.
|
||||
*/
|
||||
async start(splitView: CoreSplitViewComponent): Promise<void> {
|
||||
this.watchSplitViewOutlet(splitView);
|
||||
|
||||
// Calculate current selected item.
|
||||
const route = this.getCurrentPageRoute();
|
||||
this.updateSelectedItem(route?.snapshot ?? null);
|
||||
|
||||
// Select default item if none is selected on a non-mobile layout.
|
||||
if (!CoreScreen.isMobile && this.selectedItem === null && !splitView.isNested) {
|
||||
const defaultItem = this.getDefaultItem();
|
||||
|
||||
if (defaultItem) {
|
||||
this.select(defaultItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Log activity.
|
||||
await CoreUtils.ignoreErrors(this.logActivity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process page destroyed operations.
|
||||
*/
|
||||
destroy(): void {
|
||||
this.splitViewOutletSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch a split view outlet to keep track of the selected item.
|
||||
*
|
||||
* @param splitView Split view component.
|
||||
*/
|
||||
watchSplitViewOutlet(splitView: CoreSplitViewComponent): void {
|
||||
this.splitView = splitView;
|
||||
this.splitViewOutletSubscription = splitView.outletRouteObservable.subscribe(
|
||||
route => this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(route)),
|
||||
);
|
||||
|
||||
this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(splitView.outletRoute) ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset items data.
|
||||
*/
|
||||
resetItems(): void {
|
||||
this.itemsList = null;
|
||||
this.itemsMap = null;
|
||||
this.hasMoreItems = true;
|
||||
this.selectedItem = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given item is selected or not.
|
||||
*
|
||||
* @param item Item.
|
||||
* @return Whether the given item is selected.
|
||||
*/
|
||||
isSelected(item: Item): boolean {
|
||||
return this.selectedItem === item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current aria value.
|
||||
*
|
||||
* @param item Item.
|
||||
* @return Will return the current value of the item if selected, false otherwise.
|
||||
*/
|
||||
getItemAriaCurrent(item: Item): string {
|
||||
return this.isSelected(item) ? 'page' : 'false';
|
||||
}
|
||||
|
||||
/**
|
||||
* Select an item.
|
||||
*
|
||||
* @param item Item.
|
||||
*/
|
||||
async select(item: Item): Promise<void> {
|
||||
// Get current route in the page.
|
||||
const route = this.getCurrentPageRoute();
|
||||
|
||||
if (route === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this item is already selected, do nothing.
|
||||
const itemPath = this.getItemPath(item);
|
||||
const selectedItemPath = this.getSelectedItemPath(route.snapshot);
|
||||
|
||||
if (selectedItemPath === itemPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate to item.
|
||||
const params = this.getItemQueryParams(item);
|
||||
const reset = this.resetNavigation();
|
||||
const pathPrefix = selectedItemPath ? selectedItemPath.split('/').fill('../').join('') : '';
|
||||
|
||||
await CoreNavigator.navigate(pathPrefix + itemPath, { params, reset });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of items.
|
||||
*
|
||||
* @param items Items.
|
||||
* @param hasMoreItems Whether the list has more items that haven't been loaded.
|
||||
*/
|
||||
setItems(items: Item[], hasMoreItems: boolean = false): void {
|
||||
this.hasMoreItems = hasMoreItems;
|
||||
this.itemsList = items.slice(0);
|
||||
this.itemsMap = items.reduce((map, item) => {
|
||||
map[this.getItemPath(item)] = item;
|
||||
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(this.splitView?.outletRoute ?? null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log activity when the page starts.
|
||||
*/
|
||||
protected async logActivity(): Promise<void> {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the selected item given the current route.
|
||||
*
|
||||
* @param route Current route.
|
||||
*/
|
||||
protected updateSelectedItem(route: ActivatedRouteSnapshot | null): void {
|
||||
const selectedItemPath = this.getSelectedItemPath(route);
|
||||
|
||||
this.selectedItem = selectedItemPath
|
||||
? this.itemsMap?.[selectedItemPath] ?? null
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether to reset navigation when selecting an item.
|
||||
*
|
||||
* @returns boolean Whether navigation should be reset.
|
||||
*/
|
||||
protected resetNavigation(): boolean {
|
||||
if (!CoreScreen.isTablet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!this.splitView && !this.splitView?.isNested;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item that should be selected by default.
|
||||
*/
|
||||
protected getDefaultItem(): Item | null {
|
||||
return this.itemsList?.[0] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query parameters to use when navigating to an item page.
|
||||
*
|
||||
* @param item Item.
|
||||
* @return Query parameters to use when navigating to the item page.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
protected getItemQueryParams(item: Item): Params {
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to use when navigating to an item page.
|
||||
*
|
||||
* @param item Item.
|
||||
* @return Path to use when navigating to the item page.
|
||||
*/
|
||||
protected abstract getItemPath(item: Item): string;
|
||||
|
||||
/**
|
||||
* Get the path of the selected item given the current route.
|
||||
*
|
||||
* @param route Page route.
|
||||
* @return Path of the selected item in the given route.
|
||||
*/
|
||||
protected getSelectedItemPath(route?: ActivatedRouteSnapshot | null): string | null {
|
||||
const segments: UrlSegment[] = [];
|
||||
|
||||
while ((route = route?.firstChild)) {
|
||||
segments.push(...route.url);
|
||||
}
|
||||
|
||||
return segments.map(segment => segment.path).join('/').replace(/\/+/, '/').trim() || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page route.
|
||||
*
|
||||
* @returns Current page route, if any.
|
||||
*/
|
||||
private getCurrentPageRoute(): ActivatedRoute | null {
|
||||
if (this.pageRouteLocator instanceof ActivatedRoute) {
|
||||
return CoreNavigator.isRouteActive(this.pageRouteLocator) ? this.pageRouteLocator : null;
|
||||
}
|
||||
|
||||
return CoreNavigator.getCurrentRoute({ pageComponent: this.pageRouteLocator });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the page route given a child route on the splitview outlet.
|
||||
*
|
||||
* @param route Child route.
|
||||
* @return Page route.
|
||||
*/
|
||||
private getPageRouteFromSplitViewOutlet(route: ActivatedRouteSnapshot | null): ActivatedRouteSnapshot | null {
|
||||
const isPageRoute = this.buildRouteMatcher();
|
||||
|
||||
while (route && !isPageRoute(route)) {
|
||||
route = route.parent;
|
||||
}
|
||||
|
||||
return route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a function to check whether the given snapshot belongs to the page.
|
||||
*
|
||||
* @returns Route matcher.
|
||||
*/
|
||||
private buildRouteMatcher(): (route: ActivatedRouteSnapshot) => boolean {
|
||||
if (this.pageRouteLocator instanceof ActivatedRoute) {
|
||||
const pageRoutePath = CoreNavigator.getRouteFullPath(this.pageRouteLocator.snapshot);
|
||||
|
||||
return route => CoreNavigator.getRouteFullPath(route) === pageRoutePath;
|
||||
}
|
||||
|
||||
return route => route.component === this.pageRouteLocator;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { CoreGrades } from '../services/grades';
|
||||
import { CoreGradesGradeOverviewWithCourseData, CoreGradesHelper } from '../services/grades-helper';
|
||||
|
||||
/**
|
||||
* Provides a collection of courses.
|
||||
*/
|
||||
export class CoreGradesCoursesSource extends CoreRoutedItemsManagerSource<CoreGradesGradeOverviewWithCourseData> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async loadPageItems(): Promise<{ items: CoreGradesGradeOverviewWithCourseData[] }> {
|
||||
const grades = await CoreGrades.getCoursesGrades();
|
||||
const courses = await CoreGradesHelper.getGradesCourseData(grades);
|
||||
|
||||
return { items: courses };
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemPath(course: CoreGradesGradeOverviewWithCourseData): string {
|
||||
return course.courseid.toString();
|
||||
}
|
||||
|
||||
}
|
|
@ -22,10 +22,7 @@ const routes: Routes = [
|
|||
{
|
||||
path: '',
|
||||
component: CoreGradesCoursePage,
|
||||
data: {
|
||||
useSplitView: false,
|
||||
outsideGradesTab: true,
|
||||
},
|
||||
data: { swipeEnabled: false },
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -22,33 +22,21 @@ import { CoreSharedModule } from '@/core/shared.module';
|
|||
import { CoreGradesCoursePage } from './pages/course/course.page';
|
||||
import { CoreGradesCoursePageModule } from './pages/course/course.module';
|
||||
import { CoreGradesCoursesPage } from './pages/courses/courses.page';
|
||||
import { CoreGradesGradePage } from './pages/grade/grade.page';
|
||||
import { CoreGradesUserHandlerService } from './services/handlers/user';
|
||||
|
||||
const mobileRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
data: {
|
||||
mainMenuTabRoot: CoreGradesUserHandlerService.PAGE_NAME,
|
||||
},
|
||||
component: CoreGradesCoursesPage,
|
||||
},
|
||||
{
|
||||
path: ':courseId',
|
||||
component: CoreGradesCoursePage,
|
||||
},
|
||||
{
|
||||
path: ':courseId/:gradeId',
|
||||
component: CoreGradesGradePage,
|
||||
},
|
||||
];
|
||||
|
||||
const tabletRoutes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
data: {
|
||||
mainMenuTabRoot: CoreGradesUserHandlerService.PAGE_NAME,
|
||||
},
|
||||
component: CoreGradesCoursesPage,
|
||||
children: [
|
||||
{
|
||||
|
@ -57,16 +45,6 @@ const tabletRoutes: Routes = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ':courseId',
|
||||
component: CoreGradesCoursePage,
|
||||
children: [
|
||||
{
|
||||
path: ':gradeId',
|
||||
component: CoreGradesGradePage,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
|
@ -82,7 +60,6 @@ const routes: Routes = [
|
|||
],
|
||||
declarations: [
|
||||
CoreGradesCoursesPage,
|
||||
CoreGradesGradePage,
|
||||
],
|
||||
})
|
||||
export class CoreGradesLazyModule {}
|
||||
export class CoreGradesCoursesLazyModule {}
|
|
@ -15,16 +15,17 @@
|
|||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
||||
import { Routes } from '@angular/router';
|
||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { COURSE_PAGE_NAME } from '@features/course/course.module';
|
||||
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
|
||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||
import { CoreMainMenuRoutingModule } from '@features/mainmenu/mainmenu-routing.module';
|
||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||
import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module';
|
||||
import { CoreGradesProvider } from './services/grades';
|
||||
import { CoreGradesHelperProvider } from './services/grades-helper';
|
||||
import { CoreGradesCourseOptionHandler } from './services/handlers/course-option';
|
||||
import { CoreGradesOverviewLinkHandler } from './services/handlers/overview-link';
|
||||
import { CoreGradesUserHandler, CoreGradesUserHandlerService } from './services/handlers/user';
|
||||
import { CoreGradesUserHandler } from './services/handlers/user';
|
||||
import { CoreGradesUserLinkHandler } from './services/handlers/user-link';
|
||||
|
||||
export const CORE_GRADES_SERVICES: Type<unknown>[] = [
|
||||
|
@ -32,28 +33,29 @@ export const CORE_GRADES_SERVICES: Type<unknown>[] = [
|
|||
CoreGradesHelperProvider,
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
export const GRADES_PAGE_NAME = 'grades';
|
||||
|
||||
const mainMenuChildrenRoutes: Routes = [
|
||||
{
|
||||
path: CoreGradesUserHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('@features/grades/grades-lazy.module').then(m => m.CoreGradesLazyModule),
|
||||
path: GRADES_PAGE_NAME,
|
||||
loadChildren: () => import('./grades-courses-lazy.module').then(m => m.CoreGradesCoursesLazyModule),
|
||||
},
|
||||
{
|
||||
path: 'user-grades/:courseId',
|
||||
loadChildren: () => import('@features/grades/grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||
path: `${COURSE_PAGE_NAME}/:courseId/${PARTICIPANTS_PAGE_NAME}/:userId/${GRADES_PAGE_NAME}`,
|
||||
loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
const courseIndexRoutes: Routes = [
|
||||
{
|
||||
path: 'grades',
|
||||
loadChildren: () => import('@features/grades/grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||
path: GRADES_PAGE_NAME,
|
||||
loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||
CoreMainMenuRoutingModule.forChild({ children: routes }),
|
||||
CoreMainMenuTabRoutingModule.forChild(mainMenuChildrenRoutes),
|
||||
CoreCourseIndexRoutingModule.forChild({ children: courseIndexRoutes }),
|
||||
],
|
||||
providers: [
|
||||
|
|
|
@ -4,36 +4,41 @@
|
|||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
<h1>{{ 'core.grades.grades' | translate }}</h1>
|
||||
<h1>{{ title }}</h1>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<core-split-view [mode]="splitViewMode">
|
||||
<ion-refresher slot="fixed" [disabled]="!grades.loaded" (ionRefresh)="refreshGrades($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="grades.loaded" class="safe-area-padding">
|
||||
<core-empty-box *ngIf="grades.empty" icon="fas-chart-bar" [message]="'core.grades.nogradesreturned' | translate">
|
||||
</core-empty-box>
|
||||
<div *ngIf="!grades.empty" class="core-grades-container">
|
||||
<table cellspacing="0" cellpadding="0" class="core-grades-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th *ngFor="let column of grades.columns" id="{{column.name}}" class="ion-text-start"
|
||||
[class.ion-hide-md-down]="column.hiddenPhone" [attr.colspan]="column.colspan">
|
||||
{{ 'core.grades.' + column.name | translate }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let row of grades.rows" role="button row" [attr.tabindex]="row.itemtype != 'category' ? 0 : null"
|
||||
(ariaButtonClick)="row.itemtype != 'category' && grades.select(row)" [class]="row.rowclass"
|
||||
[ngClass]='{"core-grades-grade-clickable": row.itemtype != "category"}'>
|
||||
<ion-content [core-swipe-navigation]="courses">
|
||||
<ion-refresher slot="fixed" [disabled]="!columns || !rows" (ionRefresh)="refreshGrades($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="columns && rows" class="safe-area-padding">
|
||||
<core-empty-box *ngIf="rows && rows.length === 0" icon="fas-chart-bar" [message]="'core.grades.nogradesreturned' | translate">
|
||||
</core-empty-box>
|
||||
<div *ngIf="rows && rows.length > 0" class="core-grades-container">
|
||||
<table cellspacing="0" cellpadding="0" class="core-grades-table" [class.summary]="showSummary">
|
||||
<thead>
|
||||
<tr>
|
||||
<th *ngFor="let column of columns" id="{{column.name}}" class="ion-text-start"
|
||||
[class.ion-hide-md-down]="column.hiddenPhone" [attr.colspan]="column.colspan">
|
||||
{{ 'core.grades.' + column.name | translate }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let row of rows">
|
||||
<tr [attr.role]="row.expandable && showSummary ? 'button row' : 'row'"
|
||||
[attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded"
|
||||
[attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid"
|
||||
(ariaButtonClick)="row.expandable && showSummary && toggleRow(row)" [class]="row.rowclass"
|
||||
[class.core-grades-grade-clickable]="row.expandable && showSummary">
|
||||
<ng-container *ngIf="row.itemtype">
|
||||
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan"></td>
|
||||
<th class="core-grades-table-gradeitem ion-text-start" [class.column-itemname]="row.itemtype == 'category'"
|
||||
[attr.aria-current]="grades.getItemAriaCurrent(row)" [attr.colspan]="row.colspan">
|
||||
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan">
|
||||
</td>
|
||||
<th class="core-grades-table-gradeitem ion-text-start" [attr.colspan]="row.colspan">
|
||||
<ion-icon *ngIf="row.expandable && showSummary" aria-hidden="true" slot="start"
|
||||
[name]="row.expanded ? 'fas-caret-down' : 'fas-caret-right'">
|
||||
</ion-icon>
|
||||
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
|
||||
</ion-icon>
|
||||
<img *ngIf="row.image && !row.itemmodule" [src]="row.image" slot="start" class="core-module-icon"
|
||||
|
@ -43,16 +48,129 @@
|
|||
</core-mod-icon>
|
||||
<span [innerHTML]="row.gradeitem"></span>
|
||||
</th>
|
||||
<ng-container *ngFor="let column of grades.columns">
|
||||
<td *ngIf="column.name != 'gradeitem' && row[column.name] != undefined"
|
||||
<ng-container *ngFor="let column of columns">
|
||||
<td *ngIf="column.name !== 'gradeitem' && column.name !== 'feedback' && row[column.name] != undefined"
|
||||
[class]="'ion-text-start core-grades-table-' + column.name"
|
||||
[class.ion-hide-md-down]="column.hiddenPhone" [innerHTML]="row[column.name]"></td>
|
||||
[class.ion-hide-md-down]="column.hiddenPhone" [innerHTML]="row[column.name]">
|
||||
</td>
|
||||
<td *ngIf="column.name === 'feedback' && row.feedback !== undefined"
|
||||
class="ion-text-start core-grades-table-feedback" [class.ion-hide-md-down]="column.hiddenPhone">
|
||||
<core-format-text [maxHeight]="120" [text]="row.feedback" contextLevel="course"
|
||||
[contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</td>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</core-loading>
|
||||
</core-split-view>
|
||||
<tr *ngIf="row.expandable" [id]="row.detailsid" [class]="row.rowclass" [hidden]="!row.expanded">
|
||||
<td [attr.colspan]="totalColumnsSpan">
|
||||
<ion-list>
|
||||
<ion-item *ngIf="row.itemname && row.link" class="ion-text-wrap" detail="true" [href]="row.link"
|
||||
core-link capture="true">
|
||||
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
|
||||
</ion-icon>
|
||||
<img *ngIf="row.image && !row.itemmodule" [src]="row.image && row.itemmodule" slot="start"
|
||||
[alt]="row.iconAlt" />
|
||||
<core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start"
|
||||
[modname]="row.itemmodule">
|
||||
</core-mod-icon>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="row.itemname" contextLevel="course"
|
||||
[contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="row.itemname && !row.link" class="ion-text-wrap">
|
||||
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
|
||||
</ion-icon>
|
||||
<img *ngIf="row.image && !row.itemmodule" [src]="row.image" slot="start" [alt]="row.iconAlt" />
|
||||
<core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start"
|
||||
[modname]="row.itemmodule">
|
||||
</core-mod-icon>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="row.itemname" contextLevel="course"
|
||||
[contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.weight">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.weight' | translate}}</h2>
|
||||
<p [innerHTML]="row.weight"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.grade">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.grade' | translate}}</h2>
|
||||
<p [innerHTML]="row.grade"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.range">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.range' | translate}}</h2>
|
||||
<p [innerHTML]="row.range"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.percentage">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.percentage' | translate}}</h2>
|
||||
<p [innerHTML]="row.percentage"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.lettergrade">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.lettergrade' | translate}}</h2>
|
||||
<p [innerHTML]="row.lettergrade"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.rank">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.rank' | translate}}</h2>
|
||||
<p [innerHTML]="row.rank"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.average">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.average' | translate}}</h2>
|
||||
<p [innerHTML]="row.average"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.feedback">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.feedback' | translate}}</h2>
|
||||
<p>
|
||||
<core-format-text [maxHeight]="120" [text]="row.feedback" contextLevel="course"
|
||||
[contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="row.contributiontocoursetotal">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.contributiontocoursetotal' | translate}}</h2>
|
||||
<p [innerHTML]="row.contributiontocoursetotal"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -12,23 +12,25 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { ActivatedRoute, Params } from '@angular/router';
|
||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { AfterViewInit, Component, ElementRef, OnDestroy } from '@angular/core';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreGrades } from '@features/grades/services/grades';
|
||||
import {
|
||||
CoreGradesFormattedTable,
|
||||
CoreGradesFormattedTableColumn,
|
||||
CoreGradesFormattedTableRow,
|
||||
CoreGradesHelper,
|
||||
} from '@features/grades/services/grades-helper';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreSplitViewComponent, CoreSplitViewMode } from '@components/split-view/split-view';
|
||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreScreen } from '@services/screen';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source';
|
||||
|
||||
/**
|
||||
* Page that displays a course grades.
|
||||
|
@ -40,18 +42,29 @@ import { CoreNavigator } from '@services/navigator';
|
|||
})
|
||||
export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||
|
||||
grades!: CoreGradesCourseManager;
|
||||
splitViewMode?: CoreSplitViewMode;
|
||||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
constructor(protected route: ActivatedRoute) {
|
||||
let courseId: number;
|
||||
let userId: number;
|
||||
courseId!: number;
|
||||
userId!: number;
|
||||
expandLabel!: string;
|
||||
collapseLabel!: string;
|
||||
title?: string;
|
||||
courses?: CoreSwipeNavigationItemsManager;
|
||||
columns?: CoreGradesFormattedTableColumn[];
|
||||
rows?: CoreGradesFormattedTableRow[];
|
||||
totalColumnsSpan?: number;
|
||||
withinSplitView?: boolean;
|
||||
|
||||
constructor(protected route: ActivatedRoute, protected element: ElementRef<HTMLElement>) {
|
||||
try {
|
||||
courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route });
|
||||
userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId();
|
||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route });
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId();
|
||||
this.expandLabel = Translate.instant('core.expand');
|
||||
this.collapseLabel = Translate.instant('core.collapse');
|
||||
|
||||
if (route.snapshot.data.swipeEnabled ?? true) {
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []);
|
||||
|
||||
this.courses = new CoreSwipeNavigationItemsManager(source);
|
||||
}
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
|
||||
|
@ -59,28 +72,69 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
|||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const useSplitView = route.snapshot.data.useSplitView ?? true;
|
||||
const outsideGradesTab = route.snapshot.data.outsideGradesTab ?? false;
|
||||
|
||||
this.splitViewMode = useSplitView ? undefined : CoreSplitViewMode.MENU_ONLY;
|
||||
this.grades = new CoreGradesCourseManager(CoreGradesCoursePage, courseId, userId, outsideGradesTab);
|
||||
get showSummary(): boolean {
|
||||
return CoreScreen.isMobile || !!this.withinSplitView;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
await this.fetchInitialGrades();
|
||||
this.withinSplitView = !!this.element.nativeElement.parentElement?.closest('core-split-view');
|
||||
|
||||
this.grades.start(this.splitView);
|
||||
await this.courses?.start();
|
||||
await this.fetchInitialGrades();
|
||||
await CoreGrades.logCourseGradesView(this.courseId, this.userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.grades.destroy();
|
||||
this.courses?.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get aria label for row.
|
||||
*
|
||||
* @param row Row.
|
||||
* @returns Aria label, if applicable.
|
||||
*/
|
||||
rowAriaLabel(row: CoreGradesFormattedTableRow): string | undefined {
|
||||
if (!row.expandable || !this.showSummary) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actionLabel = row.expanded ? this.collapseLabel : this.expandLabel;
|
||||
|
||||
return `${actionLabel} ${row.ariaLabel}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle whether a row is expanded or collapsed.
|
||||
*
|
||||
* @param row Row.
|
||||
*/
|
||||
toggleRow(row: CoreGradesFormattedTableRow): void {
|
||||
if (!this.rows || !this.columns) {
|
||||
return;
|
||||
}
|
||||
|
||||
row.expanded = !row.expanded;
|
||||
|
||||
let colspan: number = this.columns.length + (row.colspan ?? 0) - 1;
|
||||
for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) {
|
||||
const previousRow = this.rows[i];
|
||||
|
||||
if (previousRow.expandable || !previousRow.colspan || !previousRow.rowspan || previousRow.colspan !== colspan) {
|
||||
continue;
|
||||
}
|
||||
|
||||
colspan++;
|
||||
previousRow.rowspan += row.expanded ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,9 +143,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
|||
* @param refresher Refresher.
|
||||
*/
|
||||
async refreshGrades(refresher: IonRefresher): Promise<void> {
|
||||
const { courseId, userId } = this.grades;
|
||||
|
||||
await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(courseId, userId));
|
||||
await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(this.courseId, this.userId));
|
||||
await CoreUtils.ignoreErrors(this.fetchGrades());
|
||||
|
||||
refresher?.complete();
|
||||
|
@ -106,7 +158,8 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
|||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading course');
|
||||
|
||||
this.grades.setTable({ columns: [], rows: [] });
|
||||
this.columns = [];
|
||||
this.rows = [];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,99 +167,13 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
|||
* Update the table of grades.
|
||||
*/
|
||||
private async fetchGrades(): Promise<void> {
|
||||
const table = await CoreGrades.getCourseGradesTable(this.grades.courseId, this.grades.userId);
|
||||
const table = await CoreGrades.getCourseGradesTable(this.courseId, this.userId);
|
||||
const formattedTable = await CoreGradesHelper.formatGradesTable(table);
|
||||
|
||||
this.grades.setTable(formattedTable);
|
||||
this.title = formattedTable.rows[0]?.gradeitem ?? Translate.instant('core.grades.grades');
|
||||
this.columns = formattedTable.columns;
|
||||
this.rows = formattedTable.rows;
|
||||
this.totalColumnsSpan = formattedTable.columns.reduce((total, column) => total + column.colspan, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to manage the table of grades.
|
||||
*/
|
||||
class CoreGradesCourseManager extends CorePageItemsListManager<CoreGradesFormattedTableRowFilled> {
|
||||
|
||||
courseId: number;
|
||||
userId: number;
|
||||
columns?: CoreGradesFormattedTableColumn[];
|
||||
rows?: CoreGradesFormattedTableRow[];
|
||||
|
||||
private outsideGradesTab: boolean;
|
||||
|
||||
constructor(pageComponent: unknown, courseId: number, userId: number, outsideGradesTab: boolean) {
|
||||
super(pageComponent);
|
||||
|
||||
this.courseId = courseId;
|
||||
this.userId = userId;
|
||||
this.outsideGradesTab = outsideGradesTab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set grades table.
|
||||
*
|
||||
* @param table Grades table.
|
||||
*/
|
||||
setTable(table: CoreGradesFormattedTable): void {
|
||||
this.columns = table.columns;
|
||||
this.rows = table.rows;
|
||||
|
||||
this.setItems(table.rows.filter(this.isFilledRow));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async select(row: CoreGradesFormattedTableRowFilled): Promise<void> {
|
||||
if (this.outsideGradesTab) {
|
||||
await CoreNavigator.navigateToSitePath(`/grades/${this.courseId}/${row.id}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return super.select(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getDefaultItem(): CoreGradesFormattedTableRowFilled | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemPath(row: CoreGradesFormattedTableRowFilled): string {
|
||||
return row.id.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemQueryParams(): Params {
|
||||
return { userId: this.userId };
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async logActivity(): Promise<void> {
|
||||
await CoreGrades.logCourseGradesView(this.courseId, this.userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given row is filled or not.
|
||||
*
|
||||
* @param row Grades table row.
|
||||
* @return Whether the given row is filled or not.
|
||||
*/
|
||||
private isFilledRow(row: CoreGradesFormattedTableRow): row is CoreGradesFormattedTableRowFilled {
|
||||
return 'id' in row;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export type CoreGradesFormattedTableRowFilled = Omit<CoreGradesFormattedTableRow, 'id'> & {
|
||||
id: number;
|
||||
};
|
||||
|
|
|
@ -61,11 +61,15 @@
|
|||
background-color: var(--header-background);
|
||||
}
|
||||
|
||||
thead #gradeitem {
|
||||
@include padding(null, null, null, 23px);
|
||||
}
|
||||
|
||||
tbody th {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#gradeitem {
|
||||
tbody #gradeitem {
|
||||
@include padding(null, null, null, 5px);
|
||||
}
|
||||
|
||||
|
@ -131,12 +135,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
ion-list, ion-item::part(native) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&.summary .ion-hide-md-down {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
core-split-view.nested .core-grades-table .ion-hide-md-down,
|
||||
core-split-view.menu-and-content .core-grades-table .ion-hide-md-down {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
|
|
|
@ -13,11 +13,12 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source';
|
||||
import { CoreGrades } from '@features/grades/services/grades';
|
||||
import { CoreGradesGradeOverviewWithCourseData, CoreGradesHelper } from '@features/grades/services/grades-helper';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
@ -31,10 +32,16 @@ import { CoreUtils } from '@services/utils/utils';
|
|||
})
|
||||
export class CoreGradesCoursesPage implements OnDestroy, AfterViewInit {
|
||||
|
||||
courses: CoreGradesCoursesManager = new CoreGradesCoursesManager(CoreGradesCoursesPage);
|
||||
courses: CoreGradesCoursesManager;
|
||||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
constructor() {
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []);
|
||||
|
||||
this.courses = new CoreGradesCoursesManager(source, CoreGradesCoursesPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
|
@ -58,7 +65,7 @@ export class CoreGradesCoursesPage implements OnDestroy, AfterViewInit {
|
|||
*/
|
||||
async refreshCourses(refresher: IonRefresher): Promise<void> {
|
||||
await CoreUtils.ignoreErrors(CoreGrades.invalidateCoursesGradesData());
|
||||
await CoreUtils.ignoreErrors(this.fetchCourses());
|
||||
await CoreUtils.ignoreErrors(this.courses.reload());
|
||||
|
||||
refresher?.complete();
|
||||
}
|
||||
|
@ -68,37 +75,18 @@ export class CoreGradesCoursesPage implements OnDestroy, AfterViewInit {
|
|||
*/
|
||||
private async fetchInitialCourses(): Promise<void> {
|
||||
try {
|
||||
await this.fetchCourses();
|
||||
await this.courses.load();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading courses');
|
||||
|
||||
this.courses.setItems([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the list of courses.
|
||||
*/
|
||||
private async fetchCourses(): Promise<void> {
|
||||
const grades = await CoreGrades.getCoursesGrades();
|
||||
const courses = await CoreGradesHelper.getGradesCourseData(grades);
|
||||
|
||||
this.courses.setItems(courses);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to manage courses.
|
||||
*/
|
||||
class CoreGradesCoursesManager extends CorePageItemsListManager<CoreGradesGradeOverviewWithCourseData> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemPath(courseGrade: CoreGradesGradeOverviewWithCourseData): string {
|
||||
return courseGrade.courseid.toString();
|
||||
}
|
||||
class CoreGradesCoursesManager extends CoreListItemsManager {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
|
|
|
@ -1,112 +0,0 @@
|
|||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>
|
||||
<h1>{{ 'core.grades.grade' | translate }}</h1>
|
||||
</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher slot="fixed" [disabled]="!gradeLoaded" (ionRefresh)="refreshGrade($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="gradeLoaded">
|
||||
<core-empty-box *ngIf="!grade" icon="fas-chart-bar" [message]="'core.grades.nogradesreturned' | translate"></core-empty-box>
|
||||
|
||||
<ion-list *ngIf="grade">
|
||||
<ion-item *ngIf="grade.itemname && grade.link" class="ion-text-wrap" detail="true" [href]="grade.link" core-link capture="true">
|
||||
<ion-icon *ngIf="grade.icon" name="{{grade.icon}}" slot="start" [attr.aria-label]="grade.iconAlt"></ion-icon>
|
||||
<img *ngIf="grade.image && !grade.itemmodule" [src]="grade.image && grade.itemmodule" slot="start" [alt]="grade.iconAlt" />
|
||||
<core-mod-icon *ngIf="grade.image && grade.itemmodule" [modicon]="grade.image" slot="start" [modname]="grade.itemmodule">
|
||||
</core-mod-icon>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="grade.itemname" contextLevel="course" [contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="grade.itemname && !grade.link" class="ion-text-wrap">
|
||||
<ion-icon *ngIf="grade.icon" name="{{grade.icon}}" slot="start" [attr.aria-label]="grade.iconAlt"></ion-icon>
|
||||
<img *ngIf="grade.image && !grade.itemmodule" [src]="grade.image" slot="start" [alt]="grade.iconAlt" />
|
||||
<core-mod-icon *ngIf="grade.image && grade.itemmodule" [modicon]="grade.image" slot="start" [modname]="grade.itemmodule">
|
||||
</core-mod-icon>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="grade.itemname" contextLevel="course" [contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.weight">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.weight' | translate}}</h2>
|
||||
<p [innerHTML]="grade.weight"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.grade">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.grade' | translate}}</h2>
|
||||
<p [innerHTML]="grade.grade"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.range">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.range' | translate}}</h2>
|
||||
<p [innerHTML]="grade.range"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.percentage">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.percentage' | translate}}</h2>
|
||||
<p [innerHTML]="grade.percentage"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.lettergrade">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.lettergrade' | translate}}</h2>
|
||||
<p [innerHTML]="grade.lettergrade"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.rank">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.rank' | translate}}</h2>
|
||||
<p [innerHTML]="grade.rank"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.average">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.average' | translate}}</h2>
|
||||
<p [innerHTML]="grade.average"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.feedback">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.feedback' | translate}}</h2>
|
||||
<p>
|
||||
<core-format-text [maxHeight]="120" [text]="grade.feedback" contextLevel="course" [contextInstanceId]="courseId">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item class="ion-text-wrap" *ngIf="grade.contributiontocoursetotal">
|
||||
<ion-label>
|
||||
<h2>{{ 'core.grades.contributiontocoursetotal' | translate}}</h2>
|
||||
<p [innerHTML]="grade.contributiontocoursetotal"></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
|
@ -1,85 +0,0 @@
|
|||
// (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 { IonRefresher } from '@ionic/angular';
|
||||
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreGrades } from '@features/grades/services/grades';
|
||||
import { CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
|
||||
/**
|
||||
* Page that displays activity grade.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-grades-grade',
|
||||
templateUrl: 'grade.html',
|
||||
})
|
||||
export class CoreGradesGradePage implements OnInit {
|
||||
|
||||
courseId!: number;
|
||||
userId!: number;
|
||||
gradeId!: number;
|
||||
grade?: CoreGradesFormattedRow | null;
|
||||
gradeLoaded = false;
|
||||
|
||||
constructor() {
|
||||
try {
|
||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
this.gradeId = CoreNavigator.getRequiredRouteNumberParam('gradeId');
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
|
||||
CoreNavigator.back();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.fetchGrade();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all the data required for the view.
|
||||
*/
|
||||
async fetchGrade(): Promise<void> {
|
||||
try {
|
||||
this.grade = await CoreGradesHelper.getGradeItem(this.courseId, this.gradeId, this.userId);
|
||||
this.gradeLoaded = true;
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading grade item');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh data.
|
||||
*
|
||||
* @param refresher Refresher.
|
||||
*/
|
||||
async refreshGrade(refresher: IonRefresher): Promise<void> {
|
||||
await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(this.courseId, this.userId));
|
||||
await CoreUtils.ignoreErrors(this.fetchGrade());
|
||||
|
||||
refresher.complete();
|
||||
}
|
||||
|
||||
}
|
|
@ -35,6 +35,7 @@ import { CoreNavigator } from '@services/navigator';
|
|||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||
import { GRADES_PAGE_NAME } from '../grades.module';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding grades information.
|
||||
|
@ -53,6 +54,7 @@ export class CoreGradesHelperProvider {
|
|||
*
|
||||
* @param tableRow JSON object representing row of grades table data.
|
||||
* @return Formatted row object.
|
||||
* @deprecated since app 4.0
|
||||
*/
|
||||
protected async formatGradeRow(tableRow: CoreGradesTableRow): Promise<CoreGradesFormattedRow> {
|
||||
const row: CoreGradesFormattedRow = {
|
||||
|
@ -125,6 +127,13 @@ export class CoreGradesHelperProvider {
|
|||
content = CoreTextUtils.replaceNewLines(content, '<br>');
|
||||
}
|
||||
|
||||
if (row.itemtype !== 'category') {
|
||||
row.expandable = true;
|
||||
row.expanded = false;
|
||||
row.detailsid = `grade-item-${row.id}-details`;
|
||||
row.ariaLabel = `${row.gradeitem} (${row.grade})`;
|
||||
}
|
||||
|
||||
if (content == ' ') {
|
||||
content = '';
|
||||
}
|
||||
|
@ -279,6 +288,7 @@ export class CoreGradesHelperProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
||||
* @return Promise to be resolved when the grades are retrieved.
|
||||
* @deprecated since app 4.0
|
||||
*/
|
||||
async getGradeItem(
|
||||
courseId: number,
|
||||
|
@ -381,6 +391,7 @@ export class CoreGradesHelperProvider {
|
|||
* @param table JSON object representing a table with data.
|
||||
* @param gradeId Grade Object identifier.
|
||||
* @return Formatted HTML table.
|
||||
* @deprecated since app 4.0
|
||||
*/
|
||||
async getGradesTableRow(table: CoreGradesTable, gradeId: number): Promise<CoreGradesFormattedRow | null> {
|
||||
if (table.tabledata) {
|
||||
|
@ -472,10 +483,7 @@ export class CoreGradesHelperProvider {
|
|||
const gradeId = item.id;
|
||||
|
||||
await CoreUtils.ignoreErrors(
|
||||
CoreNavigator.navigateToSitePath(`/grades/${courseId}/${gradeId}`, {
|
||||
siteId,
|
||||
params: { userId },
|
||||
}),
|
||||
CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}/${gradeId}`, { siteId }),
|
||||
);
|
||||
} catch (error) {
|
||||
try {
|
||||
|
@ -483,10 +491,7 @@ export class CoreGradesHelperProvider {
|
|||
if (userId && userId != currentUserId) {
|
||||
// View another user grades. Open the grades page directly.
|
||||
await CoreUtils.ignoreErrors(
|
||||
CoreNavigator.navigateToSitePath(`/grades/${courseId}`, {
|
||||
siteId,
|
||||
params: { userId },
|
||||
}),
|
||||
CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}`, { siteId }),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -502,7 +507,7 @@ export class CoreGradesHelperProvider {
|
|||
await CoreCourseHelper.getAndOpenCourse(courseId, { selectedTab: 'CoreGrades' }, siteId);
|
||||
} catch (error) {
|
||||
// Cannot get course for some reason, just open the grades page.
|
||||
await CoreNavigator.navigateToSitePath(`/grades/${courseId}`, { siteId });
|
||||
await CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}`, { siteId });
|
||||
}
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
|
@ -710,8 +715,12 @@ export type CoreGradesFormattedTable = {
|
|||
|
||||
export type CoreGradesFormattedTableRow = CoreGradesFormattedRowCommonData & {
|
||||
id?: number;
|
||||
detailsid?: string;
|
||||
colspan?: number;
|
||||
gradeitem?: string; // The item returned data.
|
||||
ariaLabel?: string;
|
||||
expandable?: boolean;
|
||||
expanded?: boolean;
|
||||
};
|
||||
|
||||
export type CoreGradesFormattedTableColumn = {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { GRADES_PAGE_NAME } from '@features/grades/grades.module';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreGrades } from '../grades';
|
||||
|
@ -36,10 +37,7 @@ export class CoreGradesOverviewLinkHandlerService extends CoreContentLinksHandle
|
|||
getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
|
||||
return [{
|
||||
action: siteId => {
|
||||
CoreNavigator.navigateToSitePath('/grades', {
|
||||
siteId,
|
||||
preferCurrentTab: false,
|
||||
});
|
||||
CoreNavigator.navigateToSitePath(GRADES_PAGE_NAME, { siteId });
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { COURSE_PAGE_NAME } from '@features/course/course.module';
|
||||
import { GRADES_PAGE_NAME } from '@features/grades/grades.module';
|
||||
|
||||
import { CoreGrades } from '@features/grades/services/grades';
|
||||
import { CoreUserProfile } from '@features/user/services/user';
|
||||
|
@ -21,6 +23,7 @@ import {
|
|||
CoreUserProfileHandler,
|
||||
CoreUserProfileHandlerData,
|
||||
} from '@features/user/services/user-delegate';
|
||||
import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
|
@ -32,8 +35,6 @@ import { makeSingleton } from '@singletons';
|
|||
@Injectable({ providedIn: 'root' })
|
||||
export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
|
||||
|
||||
static readonly PAGE_NAME = 'grades';
|
||||
|
||||
name = 'CoreGrades:viewGrades';
|
||||
priority = 400;
|
||||
type = CoreUserDelegateService.TYPE_NEW_PAGE;
|
||||
|
@ -81,9 +82,9 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
|
|||
action: (event, user, courseId): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
CoreNavigator.navigateToSitePath(`/user-grades/${courseId}`, {
|
||||
params: { userId: user.id },
|
||||
});
|
||||
CoreNavigator.navigateToSitePath(
|
||||
[COURSE_PAGE_NAME, courseId, PARTICIPANTS_PAGE_NAME, user.id, GRADES_PAGE_NAME].join('/'),
|
||||
);
|
||||
},
|
||||
};
|
||||
} else {
|
||||
|
@ -94,7 +95,7 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
|
|||
action: (event): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
CoreNavigator.navigateToSitePath(CoreGradesUserHandlerService.PAGE_NAME);
|
||||
CoreNavigator.navigateToSitePath(GRADES_PAGE_NAME);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { CoreSettingsDelegate, CoreSettingsHandlerToDisplay } from '../services/settings-delegate';
|
||||
|
||||
/**
|
||||
* Provides a collection of site settings.
|
||||
*/
|
||||
export class CoreSettingsHandlersSource extends CoreRoutedItemsManagerSource<CoreSettingsHandlerToDisplay> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async loadPageItems(): Promise<{ items: CoreSettingsHandlerToDisplay[] }> {
|
||||
return { items: CoreSettingsDelegate.getHandlers() };
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemPath(handler: CoreSettingsHandlerToDisplay): string {
|
||||
return handler.page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemQueryParams(handler: CoreSettingsHandlerToDisplay): Params {
|
||||
return handler.params || {};
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
// (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 { CoreConstants } from '@/core/constants';
|
||||
import { Params } from '@angular/router';
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { SHAREDFILES_PAGE_NAME } from '@features/sharedfiles/sharedfiles.module';
|
||||
import { CoreApp } from '@services/app';
|
||||
|
||||
/**
|
||||
* Provides a collection of setting sections.
|
||||
*/
|
||||
export class CoreSettingsSectionsSource extends CoreRoutedItemsManagerSource<CoreSettingsSection> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected async loadPageItems(): Promise<{ items: CoreSettingsSection[] }> {
|
||||
const sections: CoreSettingsSection[] = [
|
||||
{
|
||||
name: 'core.settings.general',
|
||||
path: 'general',
|
||||
icon: 'fas-wrench',
|
||||
},
|
||||
{
|
||||
name: 'core.settings.spaceusage',
|
||||
path: 'spaceusage',
|
||||
icon: 'fas-tasks',
|
||||
},
|
||||
{
|
||||
name: 'core.settings.synchronization',
|
||||
path: 'sync',
|
||||
icon: CoreConstants.ICON_SYNC,
|
||||
},
|
||||
];
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
sections.push({
|
||||
name: 'core.sharedfiles.sharedfiles',
|
||||
path: SHAREDFILES_PAGE_NAME + '/list/root',
|
||||
icon: 'fas-folder',
|
||||
params: { manage: true },
|
||||
});
|
||||
}
|
||||
|
||||
sections.push({
|
||||
name: 'core.settings.about',
|
||||
path: 'about',
|
||||
icon: 'fas-id-card',
|
||||
});
|
||||
|
||||
return { items: sections };
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemPath(section: CoreSettingsSection): string {
|
||||
return section.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getItemQueryParams(section: CoreSettingsSection): Params {
|
||||
return section.params || {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings section.
|
||||
*/
|
||||
export type CoreSettingsSection = {
|
||||
name: string;
|
||||
path: string;
|
||||
icon: string;
|
||||
params?: Params;
|
||||
};
|
|
@ -13,13 +13,11 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
|
||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreConstants } from '@/core/constants';
|
||||
import { SHAREDFILES_PAGE_NAME } from '@features/sharedfiles/sharedfiles.module';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
import { CoreSettingsSection, CoreSettingsSectionsSource } from '@features/settings/classes/settings-sections-source';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
|
||||
@Component({
|
||||
selector: 'page-core-settings-index',
|
||||
|
@ -27,16 +25,22 @@ import { CoreApp } from '@services/app';
|
|||
})
|
||||
export class CoreSettingsIndexPage implements AfterViewInit, OnDestroy {
|
||||
|
||||
sections: CoreSettingsSectionsManager = new CoreSettingsSectionsManager(CoreSettingsIndexPage);
|
||||
sections: CoreListItemsManager<CoreSettingsSection>;
|
||||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
constructor() {
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreSettingsSectionsSource, []);
|
||||
|
||||
this.sections = new CoreListItemsManager(source, CoreSettingsIndexPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngAfterViewInit(): void {
|
||||
this.sections.setItems(this.getSections());
|
||||
this.sections.start(this.splitView);
|
||||
async ngAfterViewInit(): Promise<void> {
|
||||
await this.sections.load();
|
||||
await this.sections.start(this.splitView);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,77 +50,4 @@ export class CoreSettingsIndexPage implements AfterViewInit, OnDestroy {
|
|||
this.sections.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sections.
|
||||
*
|
||||
* @returns Sections.
|
||||
*/
|
||||
protected getSections(): CoreSettingsSection[] {
|
||||
const sections: CoreSettingsSection[] = [
|
||||
{
|
||||
name: 'core.settings.general',
|
||||
path: 'general',
|
||||
icon: 'fas-wrench',
|
||||
},
|
||||
{
|
||||
name: 'core.settings.spaceusage',
|
||||
path: 'spaceusage',
|
||||
icon: 'fas-tasks',
|
||||
},
|
||||
{
|
||||
name: 'core.settings.synchronization',
|
||||
path: 'sync',
|
||||
icon: CoreConstants.ICON_SYNC,
|
||||
},
|
||||
];
|
||||
|
||||
if (CoreApp.isIOS()) {
|
||||
sections.push({
|
||||
name: 'core.sharedfiles.sharedfiles',
|
||||
path: SHAREDFILES_PAGE_NAME + '/list/root',
|
||||
icon: 'fas-folder',
|
||||
params: { manage: true },
|
||||
});
|
||||
}
|
||||
|
||||
sections.push({
|
||||
name: 'core.settings.about',
|
||||
path: 'about',
|
||||
icon: 'fas-id-card',
|
||||
});
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to manage sections.
|
||||
*/
|
||||
class CoreSettingsSectionsManager extends CorePageItemsListManager<CoreSettingsSection> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemPath(section: CoreSettingsSection): string {
|
||||
return section.path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemQueryParams(section: CoreSettingsSection): Params {
|
||||
return section.params || {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings section.
|
||||
*/
|
||||
export type CoreSettingsSection = {
|
||||
name: string;
|
||||
path: string;
|
||||
icon: string;
|
||||
params?: Params;
|
||||
};
|
||||
|
|
|
@ -13,10 +13,9 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Params } from '@angular/router';
|
||||
import { IonRefresher } from '@ionic/angular';
|
||||
|
||||
import { CoreSettingsDelegate, CoreSettingsHandlerToDisplay } from '../../services/settings-delegate';
|
||||
import { CoreSettingsHandlerToDisplay } from '../../services/settings-delegate';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
|
@ -24,8 +23,10 @@ import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../services/settings-
|
|||
import { CoreApp } from '@services/app';
|
||||
import { Translate } from '@singletons';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||
import { CoreSettingsHandlersSource } from '@features/settings/classes/settings-handlers-source';
|
||||
|
||||
/**
|
||||
* Page that displays the list of site settings pages.
|
||||
|
@ -38,7 +39,7 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
|
|||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
handlers: CoreSettingsSitePreferencesManager;
|
||||
handlers: CoreListItemsManager<CoreSettingsHandlerToDisplay>;
|
||||
|
||||
isIOS: boolean;
|
||||
siteId: string;
|
||||
|
@ -51,10 +52,12 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
|
|||
protected isDestroyed = false;
|
||||
|
||||
constructor() {
|
||||
|
||||
this.isIOS = CoreApp.isIOS();
|
||||
this.siteId = CoreSites.getCurrentSiteId();
|
||||
this.handlers = new CoreSettingsSitePreferencesManager(CoreSitePreferencesPage);
|
||||
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreSettingsHandlersSource, []);
|
||||
|
||||
this.handlers = new CoreListItemsManager(source, CoreSitePreferencesPage);
|
||||
|
||||
this.sitesObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
|
||||
this.refreshData();
|
||||
|
@ -70,14 +73,14 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
|
|||
try {
|
||||
await this.fetchData();
|
||||
} finally {
|
||||
|
||||
const handler = pageToOpen ? this.handlers.items.find(handler => handler.page == pageToOpen) : undefined;
|
||||
|
||||
if (handler) {
|
||||
this.handlers.select(handler);
|
||||
this.handlers.watchSplitViewOutlet(this.splitView);
|
||||
|
||||
await this.handlers.select(handler);
|
||||
} else {
|
||||
this.handlers.start(this.splitView);
|
||||
await this.handlers.start(this.splitView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +89,7 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
|
|||
* Fetch Data.
|
||||
*/
|
||||
protected async fetchData(): Promise<void> {
|
||||
this.handlers.setItems(CoreSettingsDelegate.getHandlers());
|
||||
await this.handlers.load();
|
||||
|
||||
this.spaceUsage = await CoreSettingsHelper.getSiteSpaceUsage(this.siteId);
|
||||
}
|
||||
|
@ -122,6 +125,7 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
|
|||
* @param refresher Refresher.
|
||||
*/
|
||||
refreshData(refresher?: IonRefresher): void {
|
||||
this.handlers.getSource().setDirty(true);
|
||||
this.fetchData().finally(() => {
|
||||
refresher?.complete();
|
||||
});
|
||||
|
@ -171,24 +175,3 @@ export class CoreSitePreferencesPage implements AfterViewInit, OnDestroy {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to manage sections.
|
||||
*/
|
||||
class CoreSettingsSitePreferencesManager extends CorePageItemsListManager<CoreSettingsHandlerToDisplay> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemPath(handler: CoreSettingsHandlerToDisplay): string {
|
||||
return handler.page;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getItemQueryParams(handler: CoreSettingsHandlerToDisplay): Params {
|
||||
return handler.params || {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue