MOBILE-4077 ReportBuilder: Create page and module
parent
6f869d1453
commit
cb2d17af9b
|
@ -43,6 +43,7 @@ import { CoreUserModule } from './user/user.module';
|
||||||
import { CoreUserToursModule } from './usertours/user-tours.module';
|
import { CoreUserToursModule } from './usertours/user-tours.module';
|
||||||
import { CoreViewerModule } from './viewer/viewer.module';
|
import { CoreViewerModule } from './viewer/viewer.module';
|
||||||
import { CoreXAPIModule } from './xapi/xapi.module';
|
import { CoreXAPIModule } from './xapi/xapi.module';
|
||||||
|
import { CoreReportBuilderModule } from './reportbuilder/reportbuilder.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -74,6 +75,7 @@ import { CoreXAPIModule } from './xapi/xapi.module';
|
||||||
CoreUserToursModule,
|
CoreUserToursModule,
|
||||||
CoreViewerModule,
|
CoreViewerModule,
|
||||||
CoreXAPIModule,
|
CoreXAPIModule,
|
||||||
|
CoreReportBuilderModule,
|
||||||
|
|
||||||
// Import last to allow overrides.
|
// Import last to allow overrides.
|
||||||
CoreEmulatorModule,
|
CoreEmulatorModule,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
// (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 { CoreReportBuilderReportColumnComponent } from './report-column/report-column';
|
||||||
|
import { CoreReportBuilderReportDetailComponent } from './report-detail/report-detail';
|
||||||
|
import { CoreReportBuilderReportSummaryComponent } from './report-summary/report-summary';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CoreReportBuilderReportDetailComponent,
|
||||||
|
CoreReportBuilderReportColumnComponent,
|
||||||
|
CoreReportBuilderReportSummaryComponent,
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
CoreReportBuilderReportDetailComponent,
|
||||||
|
CoreReportBuilderReportColumnComponent,
|
||||||
|
CoreReportBuilderReportSummaryComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreReportBuilderComponentsModule {}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<ion-item class="ion-text-wrap" lines="inset" [detail]="false" [button]="isExpandable" [attr.aria-expanded]="isExpanded"
|
||||||
|
[attr.aria-controls]="'core-report-builder-column-' + rowIndex"
|
||||||
|
[attr.aria-label]="(isExpanded ? 'core.hidecolumns' : 'core.showcolumns') | translate" (click)="toggleRow()">
|
||||||
|
<ion-label>
|
||||||
|
<h3 *ngIf="columnIndex !== 0 || (columnIndex === 0 && showFirstTitle)"> {{ header }} </h3>
|
||||||
|
<core-format-text [text]="column" contextLevel="site" [contextInstanceId]="contextId"></core-format-text>
|
||||||
|
</ion-label>
|
||||||
|
<ion-icon [class.expandable-status-icon-expanded]="!isExpanded" slot="end" aria-hidden="true" name="fas-chevron-up"
|
||||||
|
class="expandable-status-icon" *ngIf="isExpandable" flip-rtl>
|
||||||
|
</ion-icon>
|
||||||
|
</ion-item>
|
|
@ -0,0 +1,11 @@
|
||||||
|
@import "~theme/globals";
|
||||||
|
|
||||||
|
:host {
|
||||||
|
--rotate-expandable: rotate(180deg);
|
||||||
|
|
||||||
|
.expandable-status-icon {
|
||||||
|
font-size: var(--text-size);
|
||||||
|
@include margin-horizontal(0, 2px);
|
||||||
|
@include core-transition(transform, 200ms);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'core-report-builder-report-column',
|
||||||
|
templateUrl: './report-column.html',
|
||||||
|
styleUrls: ['./report-column.scss'],
|
||||||
|
})
|
||||||
|
export class CoreReportBuilderReportColumnComponent {
|
||||||
|
|
||||||
|
@Input() isExpanded = false;
|
||||||
|
@Input() isExpandable = false;
|
||||||
|
@Input() showFirstTitle = false;
|
||||||
|
@Input() columnIndex!: number;
|
||||||
|
@Input() rowIndex!: number;
|
||||||
|
@Input() column!: string;
|
||||||
|
@Input() contextId!: number;
|
||||||
|
@Input() header!: string;
|
||||||
|
@Output() onToggleRow: EventEmitter<number> = new EventEmitter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits row click
|
||||||
|
*/
|
||||||
|
toggleRow(): void {
|
||||||
|
this.onToggleRow.emit(this.rowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
<ng-container *ngIf="state$ | async as state">
|
||||||
|
|
||||||
|
<core-loading [hideUntil]="state.loaded">
|
||||||
|
|
||||||
|
<ng-container *ngIf="state.report?.data?.rows && state.report?.data?.headers && state.report?.details; else empty">
|
||||||
|
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!state.loaded" (ionRefresh)="refreshReport($event.target)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
|
||||||
|
<ng-container *ngIf="isCardLayout">
|
||||||
|
<ion-card *ngFor="let row of state.report.data.rows; let rowIndex = index">
|
||||||
|
<ion-list class="ion-text-wrap">
|
||||||
|
<core-report-builder-report-column *ngFor="let column of row.columns | slice:0:row.isExpanded ?
|
||||||
|
row.columns.length : state.cardVisibleColumns; let columnIndex = index" [columnIndex]="columnIndex"
|
||||||
|
[rowIndex]="rowIndex" [isExpandable]="columnIndex === 0 && row.columns.length > state.cardVisibleColumns"
|
||||||
|
[isExpanded]="row.isExpanded" [showFirstTitle]="state.cardviewShowFirstTitle"
|
||||||
|
[contextId]="state.report.details.contextid" [header]="state.report.data.headers[columnIndex]" [column]="column"
|
||||||
|
(onToggleRow)="toggleRow(rowIndex)">
|
||||||
|
</core-report-builder-report-column>
|
||||||
|
</ion-list>
|
||||||
|
</ion-card>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!isCardLayout">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th *ngFor="let header of state.report.data.headers">
|
||||||
|
{{ header }}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let row of state.report.data.rows">
|
||||||
|
<td *ngFor="let column of row.columns">
|
||||||
|
<core-format-text [text]="column" [contextLevel]="'site'"
|
||||||
|
[contextInstanceId]="state.report.details.contextid">
|
||||||
|
</core-format-text>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #empty>
|
||||||
|
<core-empty-box *ngIf="!state.report?.data?.rows || !state.report?.data?.headers || !state.report?.details" icon="fa-list-alt"
|
||||||
|
[message]="'core.course.nocontentavailable' | translate"></core-empty-box>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<core-infinite-loading *ngIf="!isBlock && state.report?.data?.rows && state.report?.data?.headers && state.report?.details"
|
||||||
|
[enabled]="state.canLoadMoreRows" (action)="fetchMoreInfo($event)" [error]="state.errorLoadingRows">
|
||||||
|
</core-infinite-loading>
|
||||||
|
|
||||||
|
|
||||||
|
</core-loading>
|
||||||
|
|
||||||
|
</ng-container>
|
|
@ -0,0 +1,44 @@
|
||||||
|
@import "~theme/globals";
|
||||||
|
|
||||||
|
:host {
|
||||||
|
--header-background: var(--white);
|
||||||
|
--border-color: var(--stroke);
|
||||||
|
|
||||||
|
.report-title {
|
||||||
|
ion-item {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 98%;
|
||||||
|
margin: 1em auto;
|
||||||
|
border-collapse: collapse;
|
||||||
|
color: var(--ion-text-color);
|
||||||
|
overflow-x: auto;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
tbody {
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: var(--header-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: var(--light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
@include padding(8px, 8px, 8px, null);
|
||||||
|
text-align: start;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
// (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 { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
import {
|
||||||
|
CoreReportBuilder,
|
||||||
|
CoreReportBuilderReportDetail,
|
||||||
|
CoreReportBuilderRetrieveReportMapped,
|
||||||
|
REPORT_ROWS_LIMIT,
|
||||||
|
} from '@features/reportbuilder/services/reportbuilder';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'core-report-builder-report-detail',
|
||||||
|
templateUrl: './report-detail.html',
|
||||||
|
styleUrls: ['./report-detail.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class CoreReportBuilderReportDetailComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() reportId!: string;
|
||||||
|
@Input() isBlock = true;
|
||||||
|
@Input() perPage?: number;
|
||||||
|
@Input() layout: 'card' | 'table' | 'adaptative' = 'adaptative';
|
||||||
|
@Output() onReportLoaded = new EventEmitter<CoreReportBuilderReportDetail>();
|
||||||
|
|
||||||
|
get isCardLayout(): boolean {
|
||||||
|
return this.layout === 'card' || (CoreScreen.isMobile && this.layout === 'adaptative');
|
||||||
|
}
|
||||||
|
|
||||||
|
state$: Readonly<BehaviorSubject<CoreReportBuilderReportDetailState>> = new BehaviorSubject({
|
||||||
|
report: null,
|
||||||
|
loaded: false,
|
||||||
|
canLoadMoreRows: true,
|
||||||
|
errorLoadingRows: false,
|
||||||
|
cardviewShowFirstTitle: false,
|
||||||
|
cardVisibleColumns: 1,
|
||||||
|
page: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
await this.getReport();
|
||||||
|
this.updateState({ loaded: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get report data.
|
||||||
|
*/
|
||||||
|
async getReport(): Promise<void> {
|
||||||
|
if (!this.reportId) {
|
||||||
|
CoreDomUtils.showErrorModal(new CoreError('No report found'));
|
||||||
|
CoreNavigator.back();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { page } = this.state$.getValue();
|
||||||
|
|
||||||
|
const report = await CoreReportBuilder.loadReport(parseInt(this.reportId), page,this.perPage ?? REPORT_ROWS_LIMIT);
|
||||||
|
|
||||||
|
if (!report) {
|
||||||
|
CoreDomUtils.showErrorModal(new CoreError('No report found'));
|
||||||
|
CoreNavigator.back();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CoreReportBuilder.viewReport(this.reportId);
|
||||||
|
|
||||||
|
this.updateState({
|
||||||
|
report,
|
||||||
|
cardVisibleColumns: report.details.settingsdata.cardviewVisibleColumns,
|
||||||
|
cardviewShowFirstTitle: report.details.settingsdata.cardviewShowFirstTitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.onReportLoaded.emit(report.details);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateState(state: Partial<CoreReportBuilderReportDetailState>): void {
|
||||||
|
const previousState = this.state$.getValue();
|
||||||
|
this.state$.next({ ...previousState, ...state });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update report data.
|
||||||
|
*
|
||||||
|
* @param ionRefresher ionic refresher.
|
||||||
|
*/
|
||||||
|
async refreshReport(ionRefresher?: IonRefresher): Promise<void> {
|
||||||
|
await CoreUtils.ignoreErrors(CoreReportBuilder.invalidateReport());
|
||||||
|
this.updateState({ page: 0, canLoadMoreRows: false });
|
||||||
|
await CoreUtils.ignoreErrors(this.getReport());
|
||||||
|
await ionRefresher?.complete();
|
||||||
|
this.updateState({ canLoadMoreRows: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increment page of report rows.
|
||||||
|
*/
|
||||||
|
protected incrementPage(): void {
|
||||||
|
const { page } = this.state$.getValue();
|
||||||
|
this.updateState({ page: page + 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a new batch of pages.
|
||||||
|
*
|
||||||
|
* @param complete Completion callback.
|
||||||
|
*/
|
||||||
|
async fetchMoreInfo(complete: () => void): Promise<void> {
|
||||||
|
const { canLoadMoreRows, report } = this.state$.getValue();
|
||||||
|
|
||||||
|
if (!canLoadMoreRows) {
|
||||||
|
complete();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.incrementPage();
|
||||||
|
|
||||||
|
const { page: currentPage } = this.state$.getValue();
|
||||||
|
|
||||||
|
const newReport = await CoreReportBuilder.loadReport(parseInt(this.reportId), currentPage, REPORT_ROWS_LIMIT);
|
||||||
|
|
||||||
|
if (!report || !newReport || newReport.data.rows.length === 0) {
|
||||||
|
this.updateState({ canLoadMoreRows: false });
|
||||||
|
complete();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateState({
|
||||||
|
report: {
|
||||||
|
...report,
|
||||||
|
data: {
|
||||||
|
...report.data,
|
||||||
|
rows: [
|
||||||
|
...report.data.rows,
|
||||||
|
...newReport.data.rows,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'Error loading more reports');
|
||||||
|
|
||||||
|
this.updateState({ canLoadMoreRows: false });
|
||||||
|
this.updateState({ errorLoadingRows: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand or close card.
|
||||||
|
*
|
||||||
|
* @param rowIndex card to expand or close.
|
||||||
|
*/
|
||||||
|
toggleRow(rowIndex: number): void {
|
||||||
|
const { report } = this.state$.getValue();
|
||||||
|
|
||||||
|
if (!report?.data?.rows[rowIndex]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
report.data.rows[rowIndex].isExpanded = !report.data.rows[rowIndex].isExpanded;
|
||||||
|
this.updateState({ report });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CoreReportBuilderReportDetailState = {
|
||||||
|
report: CoreReportBuilderRetrieveReportMapped | null;
|
||||||
|
loaded: boolean;
|
||||||
|
canLoadMoreRows: boolean;
|
||||||
|
errorLoadingRows: boolean;
|
||||||
|
cardviewShowFirstTitle: boolean;
|
||||||
|
cardVisibleColumns: number;
|
||||||
|
page: number;
|
||||||
|
};
|
|
@ -0,0 +1,39 @@
|
||||||
|
<ion-header class="no-title">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<div class="list-item-limited-width">
|
||||||
|
<ion-item class="ion-text-wrap course-name">
|
||||||
|
<ion-label>
|
||||||
|
<h1>
|
||||||
|
<core-format-text [text]="reportDetail.name" contextLevel="report" [contextInstanceId]="reportDetail.id">
|
||||||
|
</core-format-text>
|
||||||
|
</h1>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button fill="clear" [href]="reportUrl" core-link [showBrowserWarning]="false"
|
||||||
|
[attr.aria-label]="'core.openinbrowser' | translate" slot="end">
|
||||||
|
<ion-icon name="fas-external-link-alt" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" [detail]="false" *ngFor="let item of reportDetailToDisplay">
|
||||||
|
<ion-label>
|
||||||
|
<p>{{ item.title | translate }}</p>
|
||||||
|
<core-format-text [text]="item.text" contextLevel="report" [contextInstanceId]="reportDetail.id">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,60 @@
|
||||||
|
// (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 { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { CoreReportBuilderReportDetail } from '@features/reportbuilder/services/reportbuilder';
|
||||||
|
import { CoreFormatDatePipe } from '@pipes/format-date';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { ModalController } from '@singletons';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'core-report-builder-report-summary',
|
||||||
|
templateUrl: './report-summary.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class CoreReportBuilderReportSummaryComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() reportDetail!: CoreReportBuilderReportDetail;
|
||||||
|
reportUrl!: string;
|
||||||
|
reportDetailToDisplay!: { title: string; text: string }[];
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const formatDate = new CoreFormatDatePipe();
|
||||||
|
const site = CoreSites.getRequiredCurrentSite();
|
||||||
|
this.reportUrl = `${site.getURL()}/reportbuilder/view.php?id=${this.reportDetail.id}`;
|
||||||
|
this.reportDetailToDisplay = [
|
||||||
|
{
|
||||||
|
title: 'core.reportbuilder.reportsource',
|
||||||
|
text: this.reportDetail.sourcename,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'core.reportbuilder.timecreated',
|
||||||
|
text: formatDate.transform(this.reportDetail.timecreated * 1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'addon.mod_data.timemodified',
|
||||||
|
text: formatDate.transform(this.reportDetail.timemodified * 1000),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'core.reportbuilder.modifiedby',
|
||||||
|
text: this.reportDetail.modifiedby.fullname,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
closeModal(): void {
|
||||||
|
ModalController.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
<h1>{{ 'core.reportbuilder.reportstab' | translate }}</h1>
|
||||||
|
</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content *ngIf="state$ | async as state">
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!state.loaded" (ionRefresh)="refreshReports($event.target)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="state.loaded">
|
||||||
|
<ion-list *ngIf="reports.items?.length; else empty">
|
||||||
|
<ion-item [attr.aria-current]="reports.getItemAriaCurrent(report)" [detail]="true" class="ion-text-wrap" [button]="true"
|
||||||
|
*ngFor="let report of reports.items" (click)="reports.select(report)">
|
||||||
|
<ion-label>
|
||||||
|
<h3>{{ report.name }}</h3>
|
||||||
|
<p>{{ report.sourcename }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<ng-template #empty>
|
||||||
|
<core-empty-box *ngIf="!reports.items?.length" icon="fa-list-alt" [message]="'core.course.nocontentavailable' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<core-infinite-loading *ngIf="reports.items?.length" [enabled]="reports.loaded && !reports.completed"
|
||||||
|
(action)="fetchMoreReports($event)" [error]="state.loadMoreError">
|
||||||
|
</core-infinite-loading>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,128 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy } from '@angular/core';
|
||||||
|
import { CoreListItemsManager } from '@classes/items-management/list-items-manager';
|
||||||
|
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
|
||||||
|
import { CoreReportBuilderReportsSource } from '@features/reportbuilder/classes/reports-source';
|
||||||
|
import { CoreReportBuilder, CoreReportBuilderReport, REPORTS_LIST_LIMIT } from '@features/reportbuilder/services/reportbuilder';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'core-report-builder-list',
|
||||||
|
templateUrl: './list.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class CoreReportBuilderListPage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
|
reports!: CoreListItemsManager<CoreReportBuilderReport, CoreReportBuilderReportsSource>;
|
||||||
|
|
||||||
|
state$: Readonly<BehaviorSubject<CoreReportBuilderListState>> = new BehaviorSubject({
|
||||||
|
page: 1,
|
||||||
|
perpage: REPORTS_LIST_LIMIT,
|
||||||
|
loaded: false,
|
||||||
|
loadMoreError: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
try {
|
||||||
|
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreReportBuilderReportsSource, []);
|
||||||
|
this.reports = new CoreListItemsManager(source, CoreReportBuilderListPage);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
CoreNavigator.back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngAfterViewInit(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.fetchReports(true);
|
||||||
|
this.updateState({ loaded: true });
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'Error loading reports');
|
||||||
|
|
||||||
|
this.reports.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update reports list or loads it.
|
||||||
|
*
|
||||||
|
* @param reload is reoading or not.
|
||||||
|
*/
|
||||||
|
async fetchReports(reload: boolean): Promise<void> {
|
||||||
|
reload ? await this.reports.reload() : await this.reports.load();
|
||||||
|
this.updateState({ loadMoreError: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties of the state to update.
|
||||||
|
*
|
||||||
|
* @param state Object to update.
|
||||||
|
*/
|
||||||
|
updateState(state: Partial<CoreReportBuilderListState>): void {
|
||||||
|
const previousState = this.state$.getValue();
|
||||||
|
this.state$.next({ ...previousState, ...state });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a new batch of Reports.
|
||||||
|
*
|
||||||
|
* @param complete Completion callback.
|
||||||
|
*/
|
||||||
|
async fetchMoreReports(complete: () => void): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.fetchReports(false);
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'Error loading more reports');
|
||||||
|
|
||||||
|
this.updateState({ loadMoreError: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh reports list.
|
||||||
|
*
|
||||||
|
* @param ionRefresher ionRefresher.
|
||||||
|
*/
|
||||||
|
async refreshReports(ionRefresher?: IonRefresher): Promise<void> {
|
||||||
|
await CoreUtils.ignoreErrors(CoreReportBuilder.invalidateReportsList());
|
||||||
|
await CoreUtils.ignoreErrors(this.fetchReports(true));
|
||||||
|
await ionRefresher?.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.reports.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type CoreReportBuilderListState = {
|
||||||
|
page: number;
|
||||||
|
perpage: number;
|
||||||
|
loaded: boolean;
|
||||||
|
loadMoreError: boolean;
|
||||||
|
};
|
|
@ -0,0 +1,21 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [text]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="clear" (click)="openInfo()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon slot="icon-only" name="fas-info-circle" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title *ngIf="reportDetail">
|
||||||
|
<h1> {{ reportDetail.name }} </h1>
|
||||||
|
<p class="subheading"> {{ reportDetail.sourcename }} </p>
|
||||||
|
</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
|
<core-report-builder-report-detail [isBlock]="false" [reportId]="reportId" (onReportLoaded)="loadReportDetail($event)">
|
||||||
|
</core-report-builder-report-detail>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,52 @@
|
||||||
|
// (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 { CoreReportBuilderReportSummaryComponent } from '@features/reportbuilder/components/report-summary/report-summary';
|
||||||
|
import { CoreReportBuilderReportDetail } from '@features/reportbuilder/services/reportbuilder';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'core-report-builder-report',
|
||||||
|
templateUrl: './report.html',
|
||||||
|
})
|
||||||
|
export class CoreReportBuilderReportPage implements OnInit {
|
||||||
|
|
||||||
|
reportId!: string;
|
||||||
|
reportDetail?: CoreReportBuilderReportDetail;
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.reportId = CoreNavigator.getRequiredRouteParam('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the report detail
|
||||||
|
*
|
||||||
|
* @param reportDetail it contents the detail of the report.
|
||||||
|
*/
|
||||||
|
loadReportDetail(reportDetail: CoreReportBuilderReportDetail): void {
|
||||||
|
this.reportDetail = reportDetail;
|
||||||
|
}
|
||||||
|
|
||||||
|
openInfo(): void {
|
||||||
|
CoreDomUtils.openSideModal<void>({
|
||||||
|
component: CoreReportBuilderReportSummaryComponent,
|
||||||
|
componentProps: { reportDetail: this.reportDetail },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// (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 { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { CoreReportBuilderComponentsModule } from './components/components.module';
|
||||||
|
import { CoreReportBuilderListPage } from './pages/list/list';
|
||||||
|
import { CoreReportBuilderReportPage } from './pages/report/report';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CoreReportBuilderListPage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: ':id',
|
||||||
|
component: CoreReportBuilderReportPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CoreSharedModule,
|
||||||
|
CoreReportBuilderComponentsModule,
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CoreReportBuilderListPage,
|
||||||
|
CoreReportBuilderReportPage,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreReportBuilderLazyModule {}
|
|
@ -0,0 +1,39 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||||
|
import { CoreReportBuilderHandler, CoreReportBuilderHandlerService } from './services/handlers/reportbuilder';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: CoreReportBuilderHandlerService.PAGE_NAME,
|
||||||
|
loadChildren: () => import('./reportbuilder-lazy.module').then(m => m.CoreReportBuilderLazyModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: APP_INITIALIZER,
|
||||||
|
multi: true,
|
||||||
|
useValue: () => {
|
||||||
|
CoreUserDelegate.registerHandler(CoreReportBuilderHandler.instance);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreReportBuilderModule {}
|
Loading…
Reference in New Issue