MOBILE-3934 grades: Move details into accordion
parent
803ad32ec8
commit
275991d9ef
|
@ -22,10 +22,6 @@ const routes: Routes = [
|
|||
{
|
||||
path: '',
|
||||
component: CoreGradesCoursePage,
|
||||
data: {
|
||||
useSplitView: false,
|
||||
outsideGradesTab: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -9,31 +9,36 @@
|
|||
</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-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,122 @@
|
|||
</core-mod-icon>
|
||||
<span [innerHTML]="row.gradeitem"></span>
|
||||
</th>
|
||||
<ng-container *ngFor="let column of grades.columns">
|
||||
<ng-container *ngFor="let column of columns">
|
||||
<td *ngIf="column.name != 'gradeitem' && 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>
|
||||
</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,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<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');
|
||||
} 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<void> {
|
||||
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<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 +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<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.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}`, {
|
||||
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<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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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<CoreGradesFormattedRow> {
|
||||
const row: CoreGradesFormattedRow = {
|
||||
|
@ -126,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 = '';
|
||||
}
|
||||
|
@ -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<CoreGradesFormattedRow | null> {
|
||||
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 = {
|
||||
|
|
Loading…
Reference in New Issue