MOBILE-4077 ReportBuilder: Create page and module
This commit is contained in:
		
							parent
							
								
									6f869d1453
								
							
						
					
					
						commit
						cb2d17af9b
					
				| @ -43,6 +43,7 @@ import { CoreUserModule } from './user/user.module'; | ||||
| import { CoreUserToursModule } from './usertours/user-tours.module'; | ||||
| import { CoreViewerModule } from './viewer/viewer.module'; | ||||
| import { CoreXAPIModule } from './xapi/xapi.module'; | ||||
| import { CoreReportBuilderModule } from './reportbuilder/reportbuilder.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
| @ -74,6 +75,7 @@ import { CoreXAPIModule } from './xapi/xapi.module'; | ||||
|         CoreUserToursModule, | ||||
|         CoreViewerModule, | ||||
|         CoreXAPIModule, | ||||
|         CoreReportBuilderModule, | ||||
| 
 | ||||
|         // Import last to allow overrides.
 | ||||
|         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(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										35
									
								
								src/core/features/reportbuilder/pages/list/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/core/features/reportbuilder/pages/list/list.html
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
							
								
								
									
										128
									
								
								src/core/features/reportbuilder/pages/list/list.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/core/features/reportbuilder/pages/list/list.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| }; | ||||
							
								
								
									
										21
									
								
								src/core/features/reportbuilder/pages/report/report.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/core/features/reportbuilder/pages/report/report.html
									
									
									
									
									
										Normal file
									
								
							| @ -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> | ||||
							
								
								
									
										52
									
								
								src/core/features/reportbuilder/pages/report/report.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/core/features/reportbuilder/pages/report/report.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 }, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										44
									
								
								src/core/features/reportbuilder/reportbuilder-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/core/features/reportbuilder/reportbuilder-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 {} | ||||
							
								
								
									
										39
									
								
								src/core/features/reportbuilder/reportbuilder.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/core/features/reportbuilder/reportbuilder.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user