Merge pull request #3583 from NoelDeMartin/MOBILE-4188
MOBILE-4188: Implement teacher gradebookmain
commit
e87408418b
|
@ -22,7 +22,6 @@ const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: CoreGradesCoursePage,
|
component: CoreGradesCoursePage,
|
||||||
data: { swipeEnabled: false },
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
// (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 { conditionalRoutes } from '@/app/app-routing.module';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreUserParticipantsPageModule } from '@features/user/pages/participants/participants.module';
|
||||||
|
import { CoreUserParticipantsPage } from '@features/user/pages/participants/participants.page';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CoreUserParticipantsPage,
|
||||||
|
children: conditionalRoutes([
|
||||||
|
{
|
||||||
|
path: ':userId',
|
||||||
|
loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||||
|
data: { swipeManagerSource: 'participants' },
|
||||||
|
},
|
||||||
|
], () => CoreScreen.isTablet),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CoreUserParticipantsPageModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreGradesCourseParticipantsLazyModule {}
|
|
@ -22,12 +22,16 @@ import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-ro
|
||||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||||
import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module';
|
import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module';
|
||||||
import { CoreGradesProvider } from './services/grades';
|
import { CoreGradesProvider } from './services/grades';
|
||||||
import { CoreGradesHelperProvider, GRADES_PAGE_NAME } from './services/grades-helper';
|
import { CoreGradesHelperProvider, GRADES_PAGE_NAME, GRADES_PARTICIPANTS_PAGE_NAME } from './services/grades-helper';
|
||||||
import { CoreGradesCourseOptionHandler } from './services/handlers/course-option';
|
import { CoreGradesCourseOptionHandler } from './services/handlers/course-option';
|
||||||
import { CoreGradesOverviewLinkHandler } from './services/handlers/overview-link';
|
import { CoreGradesOverviewLinkHandler } from './services/handlers/overview-link';
|
||||||
import { CoreGradesUserHandler } from './services/handlers/user';
|
import { CoreGradesUserHandler } from './services/handlers/user';
|
||||||
import { CoreGradesReportLinkHandler } from './services/handlers/report-link';
|
import { CoreGradesReportLinkHandler } from './services/handlers/report-link';
|
||||||
import { CoreGradesUserLinkHandler } from './services/handlers/user-link';
|
import { CoreGradesUserLinkHandler } from './services/handlers/user-link';
|
||||||
|
import { CoreGradesCourseParticipantsOptionHandler } from '@features/grades/services/handlers/course-participants-option';
|
||||||
|
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||||
|
import { COURSE_INDEX_PATH } from '@features/course/course-lazy.module';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
|
|
||||||
export const CORE_GRADES_SERVICES: Type<unknown>[] = [
|
export const CORE_GRADES_SERVICES: Type<unknown>[] = [
|
||||||
CoreGradesProvider,
|
CoreGradesProvider,
|
||||||
|
@ -38,11 +42,19 @@ const mainMenuChildrenRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: GRADES_PAGE_NAME,
|
path: GRADES_PAGE_NAME,
|
||||||
loadChildren: () => import('./grades-courses-lazy.module').then(m => m.CoreGradesCoursesLazyModule),
|
loadChildren: () => import('./grades-courses-lazy.module').then(m => m.CoreGradesCoursesLazyModule),
|
||||||
|
data: { swipeManagerSource: 'courses' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: `${COURSE_PAGE_NAME}/:courseId/${PARTICIPANTS_PAGE_NAME}/:userId/${GRADES_PAGE_NAME}`,
|
path: `${COURSE_PAGE_NAME}/:courseId/${PARTICIPANTS_PAGE_NAME}/:userId/${GRADES_PAGE_NAME}`,
|
||||||
loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||||
},
|
},
|
||||||
|
...conditionalRoutes([
|
||||||
|
{
|
||||||
|
path: `${COURSE_PAGE_NAME}/${COURSE_INDEX_PATH}/${GRADES_PARTICIPANTS_PAGE_NAME}/:userId`,
|
||||||
|
loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||||
|
data: { swipeManagerSource: 'participants' },
|
||||||
|
},
|
||||||
|
], () => CoreScreen.isMobile),
|
||||||
];
|
];
|
||||||
|
|
||||||
const courseIndexRoutes: Routes = [
|
const courseIndexRoutes: Routes = [
|
||||||
|
@ -50,6 +62,10 @@ const courseIndexRoutes: Routes = [
|
||||||
path: GRADES_PAGE_NAME,
|
path: GRADES_PAGE_NAME,
|
||||||
loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
loadChildren: () => import('./grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: GRADES_PARTICIPANTS_PAGE_NAME,
|
||||||
|
loadChildren: () => import('./grades-course-participants-lazy.module').then(m => m.CoreGradesCourseParticipantsLazyModule),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -67,6 +83,7 @@ const courseIndexRoutes: Routes = [
|
||||||
CoreContentLinksDelegate.registerHandler(CoreGradesUserLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(CoreGradesUserLinkHandler.instance);
|
||||||
CoreContentLinksDelegate.registerHandler(CoreGradesOverviewLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(CoreGradesOverviewLinkHandler.instance);
|
||||||
CoreCourseOptionsDelegate.registerHandler(CoreGradesCourseOptionHandler.instance);
|
CoreCourseOptionsDelegate.registerHandler(CoreGradesCourseOptionHandler.instance);
|
||||||
|
CoreCourseOptionsDelegate.registerHandler(CoreGradesCourseParticipantsOptionHandler.instance);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
</ion-title>
|
</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content [core-swipe-navigation]="courses">
|
<ion-content [core-swipe-navigation]="swipeManager">
|
||||||
<ion-refresher slot="fixed" [disabled]="!columns || !rows" (ionRefresh)="refreshGrades($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!columns || !rows" (ionRefresh)="refreshGrades($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
||||||
import { AfterViewInit, Component, ElementRef, OnDestroy } from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, OnDestroy } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import { CoreGrades } from '@features/grades/services/grades';
|
||||||
import {
|
import {
|
||||||
CoreGradesFormattedTableColumn,
|
CoreGradesFormattedTableColumn,
|
||||||
CoreGradesFormattedTableRow,
|
CoreGradesFormattedTableRow,
|
||||||
|
CoreGradesGradeOverviewWithCourseData,
|
||||||
CoreGradesHelper,
|
CoreGradesHelper,
|
||||||
} from '@features/grades/services/grades-helper';
|
} from '@features/grades/services/grades-helper';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
|
@ -30,6 +31,8 @@ import { CoreScreen } from '@services/screen';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||||
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
|
import { CoreUserParticipantsSource } from '@features/user/classes/participants-source';
|
||||||
|
import { CoreUserData, CoreUserParticipant } from '@features/user/services/user';
|
||||||
import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source';
|
import { CoreGradesCoursesSource } from '@features/grades/classes/grades-courses-source';
|
||||||
import { CoreDom } from '@singletons/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
|
@ -49,7 +52,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
expandLabel!: string;
|
expandLabel!: string;
|
||||||
collapseLabel!: string;
|
collapseLabel!: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
courses?: CoreSwipeNavigationItemsManager;
|
swipeManager?: CoreGradesCourseSwipeManager;
|
||||||
columns: CoreGradesFormattedTableColumn[] = [];
|
columns: CoreGradesFormattedTableColumn[] = [];
|
||||||
rows: CoreGradesFormattedTableRow[] = [];
|
rows: CoreGradesFormattedTableRow[] = [];
|
||||||
rowsOnView = 0;
|
rowsOnView = 0;
|
||||||
|
@ -72,10 +75,17 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
this.collapseLabel = Translate.instant('core.collapse');
|
this.collapseLabel = Translate.instant('core.collapse');
|
||||||
this.useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1');
|
this.useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1');
|
||||||
|
|
||||||
if (route.snapshot.data.swipeEnabled ?? true) {
|
switch (route.snapshot.data.swipeManagerSource) {
|
||||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []);
|
case 'courses':
|
||||||
|
this.swipeManager = new CoreGradesCourseCoursesSwipeManager(
|
||||||
this.courses = new CoreSwipeNavigationItemsManager(source);
|
CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'participants':
|
||||||
|
this.swipeManager = new CoreGradesCourseParticipantsSwipeManager(
|
||||||
|
CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreUserParticipantsSource, [this.courseId]),
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
@ -96,7 +106,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
async ngAfterViewInit(): Promise<void> {
|
async ngAfterViewInit(): Promise<void> {
|
||||||
this.withinSplitView = !!this.element.nativeElement.parentElement?.closest('core-split-view');
|
this.withinSplitView = !!this.element.nativeElement.parentElement?.closest('core-split-view');
|
||||||
|
|
||||||
await this.courses?.start();
|
await this.swipeManager?.start();
|
||||||
await this.fetchInitialGrades();
|
await this.fetchInitialGrades();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +114,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.courses?.destroy();
|
this.swipeManager?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,7 +218,9 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
const table = await CoreGrades.getCourseGradesTable(this.courseId, this.userId);
|
const table = await CoreGrades.getCourseGradesTable(this.courseId, this.userId);
|
||||||
const formattedTable = await CoreGradesHelper.formatGradesTable(table);
|
const formattedTable = await CoreGradesHelper.formatGradesTable(table);
|
||||||
|
|
||||||
this.title = formattedTable.rows[0]?.gradeitem ?? Translate.instant('core.grades.grades');
|
this.title = this.swipeManager?.getPageTitle()
|
||||||
|
?? formattedTable.rows[0]?.gradeitem
|
||||||
|
?? Translate.instant('core.grades.grades');
|
||||||
this.columns = formattedTable.columns;
|
this.columns = formattedTable.columns;
|
||||||
this.rows = formattedTable.rows;
|
this.rows = formattedTable.rows;
|
||||||
this.rowsOnView = this.getRowsOnHeight();
|
this.rowsOnView = this.getRowsOnHeight();
|
||||||
|
@ -240,3 +252,64 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swipe manager helper methods.
|
||||||
|
*/
|
||||||
|
interface CoreGradesCourseSwipeManager extends CoreSwipeNavigationItemsManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get title to use in the current page.
|
||||||
|
*/
|
||||||
|
getPageTitle(): string | undefined;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swipe manager for courses grades.
|
||||||
|
*/
|
||||||
|
class CoreGradesCourseCoursesSwipeManager extends CoreSwipeNavigationItemsManager<CoreGradesGradeOverviewWithCourseData>
|
||||||
|
implements CoreGradesCourseSwipeManager {
|
||||||
|
|
||||||
|
constructor(source: CoreGradesCoursesSource) {
|
||||||
|
super(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getPageTitle(): string | undefined {
|
||||||
|
const selectedItem = this.getSelectedItem();
|
||||||
|
|
||||||
|
return selectedItem?.courseFullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swipe manager for participants grades.
|
||||||
|
*/
|
||||||
|
class CoreGradesCourseParticipantsSwipeManager extends CoreSwipeNavigationItemsManager<CoreUserParticipant | CoreUserData>
|
||||||
|
implements CoreGradesCourseSwipeManager {
|
||||||
|
|
||||||
|
constructor(source: CoreUserParticipantsSource) {
|
||||||
|
super(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getPageTitle(): string | undefined {
|
||||||
|
const selectedItem = this.getSelectedItem();
|
||||||
|
|
||||||
|
return selectedItem?.fullname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null {
|
||||||
|
return route.params.userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -16,8 +16,13 @@ import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||||
import { CoreCourses, CoreEnrolledCourseData, CoreCourseSearchedData } from '@features/courses/services/courses';
|
import {
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
CoreCourses,
|
||||||
|
CoreEnrolledCourseData,
|
||||||
|
CoreCourseSearchedData,
|
||||||
|
CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
} from '@features/courses/services/courses';
|
||||||
|
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
||||||
import {
|
import {
|
||||||
CoreGrades,
|
CoreGrades,
|
||||||
CoreGradesGradeItem,
|
CoreGradesGradeItem,
|
||||||
|
@ -38,8 +43,10 @@ import { CoreError } from '@classes/errors/error';
|
||||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||||
import { CoreAppProvider } from '@services/app';
|
import { CoreAppProvider } from '@services/app';
|
||||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||||
|
import { CoreCourseAccess } from '@features/course/services/course-options-delegate';
|
||||||
|
|
||||||
export const GRADES_PAGE_NAME = 'grades';
|
export const GRADES_PAGE_NAME = 'grades';
|
||||||
|
export const GRADES_PARTICIPANTS_PAGE_NAME = 'participant-grades';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features regarding grades information.
|
* Service that provides some features regarding grades information.
|
||||||
|
@ -787,6 +794,30 @@ export class CoreGradesHelperProvider {
|
||||||
return 'outcomeid' in item;
|
return 'outcomeid' in item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether to show the gradebook to this user.
|
||||||
|
*
|
||||||
|
* @param courseId The course ID.
|
||||||
|
* @param accessData Access type and data. Default, guest, ...
|
||||||
|
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||||
|
* @returns Whether to show the gradebook to this user.
|
||||||
|
*/
|
||||||
|
async showGradebook(
|
||||||
|
courseId: number,
|
||||||
|
accessData: CoreCourseAccess,
|
||||||
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
|
||||||
|
return false; // Not enabled for guests.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navOptions && navOptions.grades !== undefined) {
|
||||||
|
return navOptions.grades;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CoreGrades.isPluginEnabledForCourse(courseId);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreGradesHelper = makeSingleton(CoreGradesHelperProvider);
|
export const CoreGradesHelper = makeSingleton(CoreGradesHelperProvider);
|
||||||
|
|
|
@ -77,6 +77,16 @@ export class CoreGradesProvider {
|
||||||
return this.ROOT_CACHE_KEY + 'items:' + courseId + ':';
|
return this.ROOT_CACHE_KEY + 'items:' + courseId + ':';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prefix cache key for grade permissions WS calls.
|
||||||
|
*
|
||||||
|
* @param courseId ID of the course to check permissions.
|
||||||
|
* @returns Cache key.
|
||||||
|
*/
|
||||||
|
protected getCourseGradesPermissionsCacheKey(courseId: number): string {
|
||||||
|
return this.getCourseGradesPrefixCacheKey(courseId) + ':canviewallgrades';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get cache key for courses grade WS calls.
|
* Get cache key for courses grade WS calls.
|
||||||
*
|
*
|
||||||
|
@ -290,6 +300,17 @@ export class CoreGradesProvider {
|
||||||
await site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId));
|
await site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates course grade permissions WS calls.
|
||||||
|
*
|
||||||
|
* @param courseId ID of the course to get the permissions from.
|
||||||
|
*/
|
||||||
|
async invalidateCourseGradesPermissionsData(courseId: number): Promise<void> {
|
||||||
|
const site = CoreSites.getRequiredCurrentSite();
|
||||||
|
|
||||||
|
await site.invalidateWsCacheForKey(this.getCourseGradesPermissionsCacheKey(courseId));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the plugin is enabled for a certain site.
|
* Returns whether or not the plugin is enabled for a certain site.
|
||||||
*
|
*
|
||||||
|
@ -389,6 +410,30 @@ export class CoreGradesProvider {
|
||||||
await site?.write('gradereport_overview_view_grade_report', params);
|
await site?.write('gradereport_overview_view_grade_report', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the current user can view all the grades in the course.
|
||||||
|
*
|
||||||
|
* @param courseId Course id.
|
||||||
|
* @returns Whether the current user can view all the grades.
|
||||||
|
*/
|
||||||
|
async canViewAllGrades(courseId: number): Promise<boolean> {
|
||||||
|
const site = CoreSites.getRequiredCurrentSite();
|
||||||
|
|
||||||
|
if (!site.wsAvailable('gradereport_user_get_access_information')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: CoreGradesGetUserAccessInformationWSParams = { courseid: courseId };
|
||||||
|
const preSets: CoreSiteWSPreSets = { cacheKey: this.getCourseGradesPermissionsCacheKey(courseId) };
|
||||||
|
const access = await site.read<CoreGradesGetUserAccessInformationWSResponse>(
|
||||||
|
'gradereport_user_get_access_information',
|
||||||
|
params,
|
||||||
|
preSets,
|
||||||
|
);
|
||||||
|
|
||||||
|
return access.canviewallgrades;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreGrades = makeSingleton(CoreGradesProvider);
|
export const CoreGrades = makeSingleton(CoreGradesProvider);
|
||||||
|
@ -426,6 +471,13 @@ type CoreGradesGetOverviewCourseGradesWSParams = {
|
||||||
userid?: number; // Get grades for this user (optional, default current).
|
userid?: number; // Get grades for this user (optional, default current).
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of gradereport_user_get_access_information WS.
|
||||||
|
*/
|
||||||
|
type CoreGradesGetUserAccessInformationWSParams = {
|
||||||
|
courseid: number; // Id of the course.
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data returned by gradereport_user_get_grade_items WS.
|
* Data returned by gradereport_user_get_grade_items WS.
|
||||||
*/
|
*/
|
||||||
|
@ -457,6 +509,15 @@ export type CoreGradesGetOverviewCourseGradesWSResponse = {
|
||||||
warnings?: CoreWSExternalWarning[];
|
warnings?: CoreWSExternalWarning[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by gradereport_user_get_access_information WS.
|
||||||
|
*/
|
||||||
|
type CoreGradesGetUserAccessInformationWSResponse = {
|
||||||
|
canviewusergradereport: boolean;
|
||||||
|
canviewmygrades: boolean;
|
||||||
|
canviewallgrades: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Grade item data.
|
* Grade item data.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -13,13 +13,13 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreCourseProvider } from '@features/course/services/course';
|
|
||||||
import {
|
import {
|
||||||
CoreCourseAccess,
|
CoreCourseAccess,
|
||||||
CoreCourseOptionsHandler,
|
CoreCourseOptionsHandler,
|
||||||
CoreCourseOptionsHandlerData,
|
CoreCourseOptionsHandlerData,
|
||||||
} from '@features/course/services/course-options-delegate';
|
} from '@features/course/services/course-options-delegate';
|
||||||
import { CoreCourseAnyCourseData, CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData, CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
||||||
|
import { CoreGradesHelper, GRADES_PAGE_NAME } from '@features/grades/services/grades-helper';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreGrades } from '../grades';
|
import { CoreGrades } from '../grades';
|
||||||
|
|
||||||
|
@ -35,13 +35,15 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||||
|
await CoreGrades.invalidateCourseGradesPermissionsData(courseId);
|
||||||
|
|
||||||
if (navOptions && navOptions.grades !== undefined) {
|
if (navOptions && navOptions.grades !== undefined) {
|
||||||
// No need to invalidate anything.
|
// No need to invalidate user courses.
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CoreCourses.invalidateUserCourses();
|
await CoreCourses.invalidateUserCourses();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,20 +56,20 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
isEnabledForCourse(
|
async isEnabledForCourse(
|
||||||
courseId: number,
|
courseId: number,
|
||||||
accessData: CoreCourseAccess,
|
accessData: CoreCourseAccess,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): boolean | Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
|
const showGradebook = await CoreGradesHelper.showGradebook(courseId, accessData, navOptions);
|
||||||
return false; // Not enabled for guests.
|
|
||||||
|
if (!showGradebook) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navOptions && navOptions.grades !== undefined) {
|
const canViewAllGrades = await CoreGrades.canViewAllGrades(courseId);
|
||||||
return navOptions.grades;
|
|
||||||
}
|
|
||||||
|
|
||||||
return CoreGrades.isPluginEnabledForCourse(courseId);
|
return !canViewAllGrades;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -77,7 +79,7 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa
|
||||||
return {
|
return {
|
||||||
title: 'core.grades.grades',
|
title: 'core.grades.grades',
|
||||||
class: 'core-grades-course-handler',
|
class: 'core-grades-course-handler',
|
||||||
page: 'grades',
|
page: GRADES_PAGE_NAME,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 { Injectable } from '@angular/core';
|
||||||
|
import {
|
||||||
|
CoreCourseAccess,
|
||||||
|
CoreCourseOptionsHandler,
|
||||||
|
CoreCourseOptionsHandlerData,
|
||||||
|
} from '@features/course/services/course-options-delegate';
|
||||||
|
import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
||||||
|
import { CoreGrades } from '@features/grades/services/grades';
|
||||||
|
import { CoreGradesHelper, GRADES_PARTICIPANTS_PAGE_NAME } from '@features/grades/services/grades-helper';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Course nav handler.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class CoreGradesCourseParticipantsOptionHandlerService implements CoreCourseOptionsHandler {
|
||||||
|
|
||||||
|
name = 'CoreGradesParticipants';
|
||||||
|
priority = 400;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async invalidateEnabledForCourse(courseId: number, navOptions?: CoreCourseUserAdminOrNavOptionIndexed): Promise<void> {
|
||||||
|
await CoreGrades.invalidateCourseGradesPermissionsData(courseId);
|
||||||
|
|
||||||
|
if (navOptions && navOptions.grades !== undefined) {
|
||||||
|
// No need to invalidate user courses.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CoreCourses.invalidateUserCourses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabledForCourse(
|
||||||
|
courseId: number,
|
||||||
|
accessData: CoreCourseAccess,
|
||||||
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const showGradebook = await CoreGradesHelper.showGradebook(courseId, accessData, navOptions);
|
||||||
|
|
||||||
|
if (!showGradebook) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canViewAllGrades = await CoreGrades.canViewAllGrades(courseId);
|
||||||
|
|
||||||
|
return canViewAllGrades;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getDisplayData(): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
|
||||||
|
return {
|
||||||
|
title: 'core.grades.grades',
|
||||||
|
class: 'core-grades-course-participants-handler',
|
||||||
|
page: GRADES_PARTICIPANTS_PAGE_NAME,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CoreGradesCourseParticipantsOptionHandler = makeSingleton(CoreGradesCourseParticipantsOptionHandlerService);
|
|
@ -0,0 +1,100 @@
|
||||||
|
@app @javascript @lms_upto4.1
|
||||||
|
Feature: Grades navigation
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given the following "users" exist:
|
||||||
|
| username | firstname | lastname |
|
||||||
|
| student1 | Student | first |
|
||||||
|
| student2 | Student | second |
|
||||||
|
| teacher1 | Teacher | first |
|
||||||
|
And the following "courses" exist:
|
||||||
|
| fullname | shortname |
|
||||||
|
| Course 2 | C2 |
|
||||||
|
| Course 1 | C1 |
|
||||||
|
And the following "course enrolments" exist:
|
||||||
|
| user | course | role |
|
||||||
|
| student1 | C1 | student |
|
||||||
|
| student1 | C2 | student |
|
||||||
|
| student2 | C2 | student |
|
||||||
|
| teacher1 | C2 | editingteacher |
|
||||||
|
And the following "grade categories" exist:
|
||||||
|
| fullname | course |
|
||||||
|
| GC C1 | C1 |
|
||||||
|
| GC C2.1 | C2 |
|
||||||
|
| GC C2.2 | C2 |
|
||||||
|
And the following "grade items" exist:
|
||||||
|
| gradecategory | itemname | grademin | grademax | course |
|
||||||
|
| GC C1 | GI C1 | 20 | 40 | C1 |
|
||||||
|
| GC C2.1 | GI C2.1.1 | 60 | 80 | C2 |
|
||||||
|
| GC C2.1 | GI C2.1.2 | 10 | 90 | C2 |
|
||||||
|
| GC C2.2 | GI C2.2.1 | 0 | 100 | C2 |
|
||||||
|
And the following "grade grades" exist:
|
||||||
|
| gradeitem | user | grade |
|
||||||
|
| GI C1 | student1 | 30 |
|
||||||
|
| GI C2.1.1 | student1 | 70 |
|
||||||
|
| GI C2.1.2 | student1 | 20 |
|
||||||
|
| GI C2.2.1 | student1 | 40 |
|
||||||
|
|
||||||
|
Scenario: Mobile navigation (teacher)
|
||||||
|
Given I entered the course "Course 2" as "teacher1" in the app
|
||||||
|
|
||||||
|
# Course grades
|
||||||
|
When I press "Participants" in the app
|
||||||
|
And I press "Student first" in the app
|
||||||
|
And I press "Grades" in the app
|
||||||
|
Then I should find "GC C2.1" in the app
|
||||||
|
And I should find "70" within "GI C2.1.1" "tr" in the app
|
||||||
|
And I should find "20" within "GI C2.1.2" "tr" in the app
|
||||||
|
And I should find "90" within "GC C2.1 total" "tr" in the app
|
||||||
|
And I should find "GC C2.2" in the app
|
||||||
|
And I should find "40" within "GI C2.2.1" "tr" in the app
|
||||||
|
And I should find "40" within "GC C2.2 total" "tr" in the app
|
||||||
|
And I should find "130" within "Course total" "tr" in the app
|
||||||
|
But I should not find "GC C1" in the app
|
||||||
|
And I should not find "GI C1" in the app
|
||||||
|
|
||||||
|
# Course grades details
|
||||||
|
When I press "GI C2.1.1" in the app
|
||||||
|
Then I should find "Weight" in the app
|
||||||
|
And I should find "70.00" within "Grade" "ion-item" in the app
|
||||||
|
And I should find "60–80" within "Range" "ion-item" in the app
|
||||||
|
And I should find "50.00 %" within "Percentage" "ion-item" in the app
|
||||||
|
And I should find "Contribution to course total" in the app
|
||||||
|
And I should find "GI C2.1.2" in the app
|
||||||
|
|
||||||
|
When I press "GI C2.1.1" in the app
|
||||||
|
Then I should not find "Weight" in the app
|
||||||
|
And I should not find "Range" in the app
|
||||||
|
And I should not find "Percentage" in the app
|
||||||
|
And I should not find "Contribution to course total" in the app
|
||||||
|
But I should find "GI C2.1.1" in the app
|
||||||
|
And I should find "GI C2.1.2" in the app
|
||||||
|
|
||||||
|
When I press "Course total" in the app
|
||||||
|
Then I should find "130" within "Grade" "ion-item" in the app
|
||||||
|
And I should find "0–270" within "Range" "ion-item" in the app
|
||||||
|
|
||||||
|
When I press "Course total" in the app
|
||||||
|
Then I should not find "Weight" in the app
|
||||||
|
And I should not find "Percentage" in the app
|
||||||
|
|
||||||
|
Scenario: Tablet navigation (teacher)
|
||||||
|
Given I entered the course "Course 2" as "teacher1" in the app
|
||||||
|
And I change viewport size to "1200x640"
|
||||||
|
|
||||||
|
# Course grades
|
||||||
|
When I press "Participants" in the app
|
||||||
|
And I press "Student first" in the app
|
||||||
|
And I press "Grades" in the app
|
||||||
|
Then I should find "GC C2.1" in the app
|
||||||
|
And I should find "Weight" in the app
|
||||||
|
And I should find "Contribution to course total" in the app
|
||||||
|
And I should find "70.00" within "GI C2.1.1" "tr" in the app
|
||||||
|
And I should find "60–80" within "GI C2.1.1" "tr" in the app
|
||||||
|
And I should find "50.00 %" within "GI C2.1.1" "tr" in the app
|
||||||
|
And I should find "20" within "GI C2.1.2" "tr" in the app
|
||||||
|
And I should find "90" within "GC C2.1 total" "tr" in the app
|
||||||
|
And I should find "GC C2.2" in the app
|
||||||
|
And I should find "40" within "GI C2.2.1" "tr" in the app
|
||||||
|
And I should find "40" within "GC C2.2 total" "tr" in the app
|
||||||
|
And I should find "130" within "Course total" "tr" in the app
|
|
@ -151,13 +151,13 @@ Feature: Grades navigation
|
||||||
Then I should find "Course 1" in the app
|
Then I should find "Course 1" in the app
|
||||||
And I should find "Course 2" in the app
|
And I should find "Course 2" in the app
|
||||||
|
|
||||||
|
@lms_from4.2
|
||||||
Scenario: Mobile navigation (teacher)
|
Scenario: Mobile navigation (teacher)
|
||||||
Given I entered the course "Course 2" as "teacher1" in the app
|
Given I entered the course "Course 2" as "teacher1" in the app
|
||||||
|
|
||||||
# Course grades
|
# Course grades
|
||||||
When I press "Participants" in the app
|
When I press "Grades" in the app
|
||||||
And I press "Student first" in the app
|
And I press "Student first" in the app
|
||||||
And I press "Grades" in the app
|
|
||||||
Then I should find "GC C2.1" in the app
|
Then I should find "GC C2.1" in the app
|
||||||
And I should find "70" within "GI C2.1.1" "tr" in the app
|
And I should find "70" within "GI C2.1.1" "tr" in the app
|
||||||
And I should find "20" within "GI C2.1.2" "tr" in the app
|
And I should find "20" within "GI C2.1.2" "tr" in the app
|
||||||
|
@ -194,6 +194,13 @@ Feature: Grades navigation
|
||||||
Then I should not find "Weight" in the app
|
Then I should not find "Weight" in the app
|
||||||
And I should not find "Percentage" in the app
|
And I should not find "Percentage" in the app
|
||||||
|
|
||||||
|
# Profile grades
|
||||||
|
When I press the back button in the app
|
||||||
|
And I press "Participants" in the app
|
||||||
|
And I press "Student first" in the app
|
||||||
|
And I press "Grades" in the app
|
||||||
|
Then I should find "GC C2.1" in the app
|
||||||
|
|
||||||
Scenario: Tablet navigation (student)
|
Scenario: Tablet navigation (student)
|
||||||
Given I entered the course "Course 2" as "student1" in the app
|
Given I entered the course "Course 2" as "student1" in the app
|
||||||
And I change viewport size to "1200x640"
|
And I change viewport size to "1200x640"
|
||||||
|
@ -274,23 +281,53 @@ Feature: Grades navigation
|
||||||
Then I should not find "Weight" inside the split-view content in the app
|
Then I should not find "Weight" inside the split-view content in the app
|
||||||
And I should not find "Percentage" inside the split-view content in the app
|
And I should not find "Percentage" inside the split-view content in the app
|
||||||
|
|
||||||
|
@lms_from4.2
|
||||||
Scenario: Tablet navigation (teacher)
|
Scenario: Tablet navigation (teacher)
|
||||||
Given I entered the course "Course 2" as "teacher1" in the app
|
Given I entered the course "Course 2" as "teacher1" in the app
|
||||||
And I change viewport size to "1200x640"
|
And I change viewport size to "1200x640"
|
||||||
|
|
||||||
# Course grades
|
# User grades
|
||||||
|
When I press "Grades" in the app
|
||||||
|
And I press "Student first" in the app
|
||||||
|
Then "Student first" should be selected in the app
|
||||||
|
And I should find "GC C2.1" inside the split-view content in the app
|
||||||
|
And I should find "70" within "GI C2.1.1" "tr" inside the split-view content in the app
|
||||||
|
And I should find "20" within "GI C2.1.2" "tr" inside the split-view content in the app
|
||||||
|
And I should find "90" within "GC C2.1 total" "tr" inside the split-view content in the app
|
||||||
|
And I should find "GC C2.2" inside the split-view content in the app
|
||||||
|
And I should find "40" within "GI C2.2.1" "tr" inside the split-view content in the app
|
||||||
|
And I should find "40" within "GC C2.2 total" "tr" inside the split-view content in the app
|
||||||
|
And I should find "130" within "Course total" "tr" inside the split-view content in the app
|
||||||
|
But I should not find "GC C1" inside the split-view content in the app
|
||||||
|
And I should not find "GI C1" inside the split-view content in the app
|
||||||
|
|
||||||
|
# User grades details
|
||||||
|
When I press "GI C2.1.1" in the app
|
||||||
|
Then I should find "Weight" inside the split-view content in the app
|
||||||
|
And I should find "70.00" within "Grade" "ion-item" inside the split-view content in the app
|
||||||
|
And I should find "60–80" within "Range" "ion-item" inside the split-view content in the app
|
||||||
|
And I should find "50.00 %" within "Percentage" "ion-item" inside the split-view content in the app
|
||||||
|
And I should find "Contribution to course total" inside the split-view content in the app
|
||||||
|
And I should find "GI C2.1.2" inside the split-view content in the app
|
||||||
|
|
||||||
|
When I press "GI C2.1.1" in the app
|
||||||
|
Then I should not find "Weight" inside the split-view content in the app
|
||||||
|
And I should not find "Range" inside the split-view content in the app
|
||||||
|
And I should not find "Percentage" inside the split-view content in the app
|
||||||
|
And I should not find "Contribution to course total" inside the split-view content in the app
|
||||||
|
But I should find "GI C2.1.1" inside the split-view content in the app
|
||||||
|
And I should find "GI C2.1.2" inside the split-view content in the app
|
||||||
|
|
||||||
|
When I press "Course total" in the app
|
||||||
|
Then I should find "130" within "Grade" "ion-item" inside the split-view content in the app
|
||||||
|
And I should find "0–270" within "Range" "ion-item" inside the split-view content in the app
|
||||||
|
|
||||||
|
When I press "Course total" in the app
|
||||||
|
Then I should not find "Weight" inside the split-view content in the app
|
||||||
|
And I should not find "Percentage" inside the split-view content in the app
|
||||||
|
|
||||||
|
# Profile grades
|
||||||
When I press "Participants" in the app
|
When I press "Participants" in the app
|
||||||
And I press "Student first" in the app
|
And I press "Student first" in the app
|
||||||
And I press "Grades" in the app
|
And I press "Grades" in the app
|
||||||
Then I should find "GC C2.1" in the app
|
Then I should find "GC C2.1" in the app
|
||||||
And I should find "Weight" in the app
|
|
||||||
And I should find "Contribution to course total" in the app
|
|
||||||
And I should find "70.00" within "GI C2.1.1" "tr" in the app
|
|
||||||
And I should find "60–80" within "GI C2.1.1" "tr" in the app
|
|
||||||
And I should find "50.00 %" within "GI C2.1.1" "tr" in the app
|
|
||||||
And I should find "20" within "GI C2.1.2" "tr" in the app
|
|
||||||
And I should find "90" within "GC C2.1 total" "tr" in the app
|
|
||||||
And I should find "GC C2.2" in the app
|
|
||||||
And I should find "40" within "GI C2.2.1" "tr" in the app
|
|
||||||
And I should find "40" within "GC C2.2 total" "tr" in the app
|
|
||||||
And I should find "130" within "Course total" "tr" in the app
|
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
<core-navbar-buttons slot="end">
|
|
||||||
<ion-button [hidden]="!searchEnabled" (click)="toggleSearch()" [attr.aria-label]="'core.search' | translate">
|
|
||||||
<ion-icon name="fas-magnifying-glass" slot="icon-only" aria-hidden="true"></ion-icon>
|
|
||||||
</ion-button>
|
|
||||||
</core-navbar-buttons>
|
|
||||||
|
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<core-split-view>
|
<core-split-view>
|
||||||
<ion-refresher slot="fixed" [disabled]="!participants.loaded || searchInProgress" (ionRefresh)="refreshParticipants($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!participants.loaded || searchInProgress" (ionRefresh)="refreshParticipants($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
||||||
<core-search-box *ngIf="showSearchBox" [disabled]="searchInProgress" [spellcheck]="false" [autoFocus]="true" [lengthCheck]="1"
|
<core-search-box [disabled]="searchInProgress" [spellcheck]="false" [autoFocus]="true" [lengthCheck]="1" autocorrect="off"
|
||||||
autocorrect="off" searchArea="CoreUserParticipants" (onSubmit)="search($event)" (onClear)="clearSearch()">
|
searchArea="CoreUserParticipants" (onSubmit)="search($event)" (onClear)="clearSearch()">
|
||||||
</core-search-box>
|
</core-search-box>
|
||||||
|
|
||||||
<core-loading [hideUntil]="participants.loaded">
|
<core-loading [hideUntil]="participants.loaded">
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
// (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 { CoreUserParticipantsPage } from './participants.page';
|
||||||
|
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CoreSharedModule,
|
||||||
|
CoreSearchComponentsModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CoreUserParticipantsPage,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreUserParticipantsPageModule {}
|
|
@ -31,6 +31,7 @@ import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-user-participants',
|
selector: 'page-core-user-participants',
|
||||||
templateUrl: 'participants.html',
|
templateUrl: 'participants.html',
|
||||||
|
styleUrls: ['participants.scss'],
|
||||||
})
|
})
|
||||||
export class CoreUserParticipantsPage implements OnInit, AfterViewInit, OnDestroy {
|
export class CoreUserParticipantsPage implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
|
@ -39,7 +40,6 @@ export class CoreUserParticipantsPage implements OnInit, AfterViewInit, OnDestro
|
||||||
searchQuery: string | null = null;
|
searchQuery: string | null = null;
|
||||||
searchInProgress = false;
|
searchInProgress = false;
|
||||||
searchEnabled = false;
|
searchEnabled = false;
|
||||||
showSearchBox = false;
|
|
||||||
fetchMoreParticipantsFailed = false;
|
fetchMoreParticipantsFailed = false;
|
||||||
|
|
||||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
@ -84,20 +84,6 @@ export class CoreUserParticipantsPage implements OnInit, AfterViewInit, OnDestro
|
||||||
this.participants.destroy();
|
this.participants.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show or hide search box.
|
|
||||||
*/
|
|
||||||
toggleSearch(): void {
|
|
||||||
this.showSearchBox = !this.showSearchBox;
|
|
||||||
|
|
||||||
if (this.showSearchBox) {
|
|
||||||
// Make search bar visible.
|
|
||||||
this.splitView.menuContent.scrollToTop();
|
|
||||||
} else {
|
|
||||||
this.clearSearch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear search.
|
* Clear search.
|
||||||
*/
|
*/
|
|
@ -0,0 +1,13 @@
|
||||||
|
:host {
|
||||||
|
|
||||||
|
core-split-view {
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
|
||||||
|
core-search-box {
|
||||||
|
position: sticky;
|
||||||
|
top: 8px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -15,12 +15,10 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreUserParticipantsPage } from './pages/participants/participants.page';
|
||||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
|
||||||
|
|
||||||
import { CoreUserParticipantsPage } from './pages/participants/participants';
|
|
||||||
import { conditionalRoutes } from '@/app/app-routing.module';
|
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||||
import { CoreScreen } from '@services/screen';
|
import { CoreScreen } from '@services/screen';
|
||||||
|
import { CoreUserParticipantsPageModule } from '@features/user/pages/participants/participants.module';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
@ -38,11 +36,7 @@ const routes: Routes = [
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
CoreSharedModule,
|
CoreUserParticipantsPageModule,
|
||||||
CoreSearchComponentsModule,
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
CoreUserParticipantsPage,
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreUserCourseLazyModule {}
|
export class CoreUserCourseLazyModule {}
|
||||||
|
|
Loading…
Reference in New Issue