diff --git a/src/core/features/grades/grades-course-lazy.module.ts b/src/core/features/grades/grades-course-lazy.module.ts index e6164e920..877a0fec9 100644 --- a/src/core/features/grades/grades-course-lazy.module.ts +++ b/src/core/features/grades/grades-course-lazy.module.ts @@ -22,10 +22,6 @@ const routes: Routes = [ { path: '', component: CoreGradesCoursePage, - data: { - useSplitView: false, - outsideGradesTab: true, - }, }, ]; diff --git a/src/core/features/grades/grades-courses-lazy.module.ts b/src/core/features/grades/grades-courses-lazy.module.ts index 94dee326b..8dbc73cf5 100644 --- a/src/core/features/grades/grades-courses-lazy.module.ts +++ b/src/core/features/grades/grades-courses-lazy.module.ts @@ -22,7 +22,6 @@ 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'; const mobileRoutes: Routes = [ { @@ -33,10 +32,6 @@ const mobileRoutes: Routes = [ path: ':courseId', component: CoreGradesCoursePage, }, - { - path: ':courseId/:gradeId', - component: CoreGradesGradePage, - }, ]; const tabletRoutes: Routes = [ @@ -50,16 +45,6 @@ const tabletRoutes: Routes = [ }, ], }, - { - path: ':courseId', - component: CoreGradesCoursePage, - children: [ - { - path: ':gradeId', - component: CoreGradesGradePage, - }, - ], - }, ]; const routes: Routes = [ @@ -75,7 +60,6 @@ const routes: Routes = [ ], declarations: [ CoreGradesCoursesPage, - CoreGradesGradePage, ], }) export class CoreGradesCoursesLazyModule {} diff --git a/src/core/features/grades/pages/course/course.html b/src/core/features/grades/pages/course/course.html index b4e94d4d0..c0e19a660 100644 --- a/src/core/features/grades/pages/course/course.html +++ b/src/core/features/grades/pages/course/course.html @@ -9,31 +9,36 @@ - - - - - - - -
- - - - - - - - + + + + + + +
+
- {{ 'core.grades.' + column.name | translate }} -
+ + + + + + + + - - + - + - -
+ {{ 'core.grades.' + column.name | translate }} +
+ + +
-
-
-
+ + + + + + + + + + +

+ + +

+
+
+ + + + + + + + +

+ + +

+
+
+ + + +

{{ 'core.grades.weight' | translate}}

+

+
+
+ + + +

{{ 'core.grades.grade' | translate}}

+

+
+
+ + + +

{{ 'core.grades.range' | translate}}

+

+
+
+ + + +

{{ 'core.grades.percentage' | translate}}

+

+
+
+ + + +

{{ 'core.grades.lettergrade' | translate}}

+

+
+
+ + + +

{{ 'core.grades.rank' | translate}}

+

+
+
+ + + +

{{ 'core.grades.average' | translate}}

+

+
+
+ + + +

{{ 'core.grades.feedback' | translate}}

+

+ + +

+
+
+ + + +

{{ 'core.grades.contributiontocoursetotal' | translate}}

+

+
+
+
+ + + + + + +
diff --git a/src/core/features/grades/pages/course/course.page.ts b/src/core/features/grades/pages/course/course.page.ts index 51ef0fe4f..3073c5aa3 100644 --- a/src/core/features/grades/pages/course/course.page.ts +++ b/src/core/features/grades/pages/course/course.page.ts @@ -12,23 +12,22 @@ // 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 } 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'; /** * Page that displays a course grades. @@ -38,20 +37,23 @@ import { CoreNavigator } from '@services/navigator'; templateUrl: 'course.html', styleUrls: ['course.scss'], }) -export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { +export class CoreGradesCoursePage implements AfterViewInit { - 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; + columns?: CoreGradesFormattedTableColumn[]; + rows?: CoreGradesFormattedTableRow[]; + totalColumnsSpan?: number; + withinSplitView?: boolean; + constructor(protected route: ActivatedRoute, protected element: ElementRef) { 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'); } catch (error) { CoreDomUtils.showErrorModal(error); @@ -59,28 +61,61 @@ 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 { - await this.fetchInitialGrades(); + this.withinSplitView = !!this.element.nativeElement.parentElement?.closest('core-split-view'); - this.grades.start(this.splitView); + await this.fetchInitialGrades(); + await CoreGrades.logCourseGradesView(this.courseId, this.userId); } /** - * @inheritdoc + * Get aria label for row. + * + * @param row Row. + * @returns Aria label, if applicable. */ - ngOnDestroy(): void { - this.grades.destroy(); + 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 +124,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { * @param refresher Refresher. */ async refreshGrades(refresher: IonRefresher): Promise { - 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 +139,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,103 +148,12 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy { * Update the table of grades. */ private async fetchGrades(): Promise { - 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.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 { - - 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 { - if (this.outsideGradesTab) { - await CoreNavigator.navigateToSitePath(`/grades/${this.courseId}/${row.id}`, { - params: { - userId: this.userId, - }, - }); - - 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 { - 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 & { - id: number; -}; diff --git a/src/core/features/grades/pages/course/course.scss b/src/core/features/grades/pages/course/course.scss index 2f0239c09..3a004d4a6 100644 --- a/src/core/features/grades/pages/course/course.scss +++ b/src/core/features/grades/pages/course/course.scss @@ -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) { diff --git a/src/core/features/grades/pages/grade/grade.html b/src/core/features/grades/pages/grade/grade.html deleted file mode 100644 index ddb6bf6d2..000000000 --- a/src/core/features/grades/pages/grade/grade.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - -

{{ 'core.grades.grade' | translate }}

-
-
-
- - - - - - - - - - - - - - -

- - -

-
-
- - - - - - - -

- - -

-
-
- - - -

{{ 'core.grades.weight' | translate}}

-

-
-
- - - -

{{ 'core.grades.grade' | translate}}

-

-
-
- - - -

{{ 'core.grades.range' | translate}}

-

-
-
- - - -

{{ 'core.grades.percentage' | translate}}

-

-
-
- - - -

{{ 'core.grades.lettergrade' | translate}}

-

-
-
- - - -

{{ 'core.grades.rank' | translate}}

-

-
-
- - - -

{{ 'core.grades.average' | translate}}

-

-
-
- - - -

{{ 'core.grades.feedback' | translate}}

-

- - -

-
-
- - - -

{{ 'core.grades.contributiontocoursetotal' | translate}}

-

-
-
-
-
-
diff --git a/src/core/features/grades/pages/grade/grade.page.ts b/src/core/features/grades/pages/grade/grade.page.ts deleted file mode 100644 index b77e08ce6..000000000 --- a/src/core/features/grades/pages/grade/grade.page.ts +++ /dev/null @@ -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 { - 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 { - await CoreUtils.ignoreErrors(CoreGrades.invalidateCourseGradesData(this.courseId, this.userId)); - await CoreUtils.ignoreErrors(this.fetchGrade()); - - refresher.complete(); - } - -} diff --git a/src/core/features/grades/services/grades-helper.ts b/src/core/features/grades/services/grades-helper.ts index add464673..91af997c6 100644 --- a/src/core/features/grades/services/grades-helper.ts +++ b/src/core/features/grades/services/grades-helper.ts @@ -54,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 { const row: CoreGradesFormattedRow = { @@ -126,6 +127,13 @@ export class CoreGradesHelperProvider { content = CoreTextUtils.replaceNewLines(content, '
'); } + 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 = ''; } @@ -280,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, @@ -382,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 { if (table.tabledata) { @@ -705,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 = {