From aaf3d9ad132f104414412a106a51ab5e35822ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Marie=CC=81thoz?= Date: Mon, 6 Nov 2023 12:08:16 +0100 Subject: [PATCH] statistics: add report list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds menu entry with default filters. * Adds indicator to the stat config brief view. * Replaces the organisation by library. * Adds facets to the stat config search view. * Adds updated and created date. * Adds filter by libraries. * Adds live values. * Adds report list. * Exposes settings service into the route tools service. * Fixes delete messsages by adding missing resources in the record permission service. Co-Authored-by: Johnny MarieĢthoz --- projects/admin/src/app/app.module.ts | 481 +++++++++--------- .../src/app/menu/menu-definition/menu-app.ts | 22 +- .../statistics-cfg-brief-view-component.ts | 3 + .../report-data/report-data.component.html | 28 + .../report-data/report-data.component.ts | 28 + .../reports-list/reports-list.component.html | 37 ++ .../reports-list/reports-list.component.ts | 65 +++ .../statistics-cfg-detail-view.component.html | 37 +- .../statistics-cfg-detail-view.component.ts | 56 +- .../src/app/routes/route-tool.service.ts | 109 ++-- .../src/app/routes/statistics-cfg-route.ts | 184 +++++-- .../app/service/record-permission.service.ts | 112 ++-- 12 files changed, 795 insertions(+), 367 deletions(-) create mode 100644 projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/report-data/report-data.component.html create mode 100644 projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/report-data/report-data.component.ts create mode 100644 projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component.html create mode 100644 projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component.ts diff --git a/projects/admin/src/app/app.module.ts b/projects/admin/src/app/app.module.ts index 2b8224c79..b0321f4f5 100644 --- a/projects/admin/src/app/app.module.ts +++ b/projects/admin/src/app/app.module.ts @@ -40,6 +40,7 @@ import { PopoverModule } from 'ngx-bootstrap/popover'; import { TabsModule } from 'ngx-bootstrap/tabs'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import { TypeaheadModule } from 'ngx-bootstrap/typeahead'; +import { TableModule } from "primeng/table"; import { SelectAccountEditorWidgetComponent } from './acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component'; @@ -168,6 +169,8 @@ import { PatronPermissionsComponent } from './record/detail-view/patron-detail-v import { PatronTypesDetailViewComponent } from './record/detail-view/patron-types-detail-view/patron-types-detail-view.component'; import { PermissionDetailViewComponent } from './record/detail-view/permission-detail-view/permission-detail-view.component'; import { RecordMaskedComponent } from './record/detail-view/record-masked/record-masked.component'; +import { ReportDataComponent } from './record/detail-view/statistics-cfg-detail-view/report-data/report-data.component'; +import { ReportsListComponent } from './record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component'; import { StatisticsCfgDetailViewComponent } from './record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component'; import { TemplateDetailViewComponent } from './record/detail-view/template-detail-view/template-detail-view.component'; import { VendorDetailViewComponent } from './record/detail-view/vendor-detail-view/vendor-detail-view.component'; @@ -200,243 +203,247 @@ export function appInitFactory(appInitializerService: AppInitializerService): () } @NgModule({ - declarations: [ - AppComponent, - CircPoliciesBriefViewComponent, - DocumentEditorComponent, - DocumentsBriefViewComponent, - ExceptionDatesEditComponent, - ExceptionDatesListComponent, - FrontpageComponent, - ItemTypesBriefViewComponent, - ItemTypeDetailViewComponent, - LibrariesBriefViewComponent, - LibraryComponent, - MenuComponent, - PatronsBriefViewComponent, - PatronTypesBriefViewComponent, - PatronTypesDetailViewComponent, - LibraryDetailViewComponent, - DayOpeningHoursComponent, - ExceptionDateComponent, - DocumentDetailViewComponent, - HoldingEditorComponent, - HoldingComponent, - HoldingsComponent, - LibraryComponent, - ExceptionDatesListComponent, - ExceptionDatesEditComponent, - CircPolicyDetailViewComponent, - ExceptionDateComponent, - LocationDetailViewComponent, - LocationComponent, - ItemDetailViewComponent, - ItemTransactionComponent, - ItemTransactionsComponent, - ItemsBriefViewComponent, - IssuesBriefViewComponent, - PatronDetailViewComponent, - VendorDetailViewComponent, - VendorBriefViewComponent, - AddressTypeComponent, - OrganisationDetailViewComponent, - RelatedResourceComponent, - ItemRequestComponent, - ErrorPageComponent, - SerialHoldingItemComponent, - SerialHoldingDetailViewComponent, - HoldingDetailViewComponent, - DefaultHoldingItemComponent, - NotesFormatPipe, - MarcPipe, - TabOrderDirective, - TemplatesBriefViewComponent, - TemplateDetailViewComponent, - CollectionBriefViewComponent, - CollectionDetailViewComponent, - CollectionItemsComponent, - DocumentRecordSearchComponent, - HoldingDetailComponent, - RemoteEntitiesPersonDetailViewComponent, - IllRequestsBriefViewComponent, - IllRequestDetailViewComponent, - CustomShortcutHelpComponent, - HoldingItemNoteComponent, - MenuSwitchLibraryComponent, - LocalFieldComponent, - MenuUserServicesComponent, - MenuLanguageComponent, - MenuUserComponent, - MenuDashboardComponent, - MenuMobileComponent, - SubMenuComponent, - HoldingItemTemporaryItemTypeComponent, - OperationLogsComponent, - HoldingSharedViewComponent, - OperationLogsDialogComponent, - CipoPatronTypeItemTypeComponent, - UserIdComponent, - UserIdEditorComponent, - RecordMaskedComponent, - IdentifiedbyValueComponent, - DialogImportComponent, - NotificationTypePipe, - CirculationLogsComponent, - CirculationLogsDialogComponent, - CirculationLogComponent, - ItemInCollectionPipe, - CountryCodeTranslatePipe, - DocumentDescriptionComponent, - OtherEditionComponent, - DescriptionZoneComponent, - DocumentProvisionActivityPipe, - MainTitleRelationPipe, - HoldingOrganisationComponent, - ExpectedIssueComponent, - ReceivedIssueComponent, - LoansBriefViewComponent, - PermissionDetailViewComponent, - PatronTransactionEventsBriefViewComponent, - PatronNamePipe, - PatronTransactionEventOverdueComponent, - PatronTransactionEventDefaultComponent, - PatronTransactionEventSearchViewComponent, - PaymentsDataComponent, - PaymentsDataTableComponent, - PaymentDataPieComponent, - PatronPermissionsComponent, - PatronPermissionComponent, - CirculationLogLoanComponent, - CirculationLogNotificationComponent, - CirculationStatsComponent, - ItemSwitchLocationStandaloneComponent, - ItemSwitchLocationComponent, - IssueEmailComponent, - EntityTypeaheadComponent, - RemoteEntitiesDetailViewComponent, - RemoteEntitiesOrganisationDetailViewComponent, - EntitiesLocalDetailViewComponent, - LocalPersonDetailViewComponent, - EntitiesLocalGlobalComponent, - LocalTopicDetailViewComponent, - LocalOrganisationDetailViewComponent, - LocalPlaceDetailViewComponent, - LocalWorkDetailViewComponent, - RemoteTopicDetailViewComponent, - AddEntityLocalComponent, - AddEntityLocalFormComponent, - StatisticsCfgBriefViewComponent, - StatisticsCfgDetailViewComponent, - ], - imports: [ - AppRoutingModule, - BrowserAnimationsModule, - BrowserModule, - BsDatepickerModule.forRoot(), - BsDropdownModule.forRoot(), - NgxChartsModule, - CollapseModule.forRoot(), - FormsModule, - HttpClientModule, - ReactiveFormsModule, - RecordModule, - TabsModule.forRoot(), - TooltipModule.forRoot(), - PopoverModule.forRoot(), - FormlyModule.forRoot({ - types: [ - {name: 'cipo-pt-it', component: CipoPatronTypeItemTypeComponent}, - {name: 'account-select', component: SelectAccountEditorWidgetComponent}, - {name: 'entityTypeahead', component: EntityTypeaheadComponent} - ], - wrappers: [ - {name: 'user-id', component: UserIdComponent}, - {name: 'identifiedby-value', component: IdentifiedbyValueComponent} - ] - }), - TranslateModule.forRoot({ - loader: { - provide: BaseTranslateLoader, - useClass: TranslateLoader, - deps: [CoreConfigService, HttpClient] - } - }), - TypeaheadModule, - HotkeysModule, - SharedModule, - LoadingBarHttpClientModule, - PrimengImportModule, - PreviewEmailModule - ], - providers: [ + declarations: [ + AppComponent, + CircPoliciesBriefViewComponent, + DocumentEditorComponent, + DocumentsBriefViewComponent, + ExceptionDatesEditComponent, + ExceptionDatesListComponent, + FrontpageComponent, + ItemTypesBriefViewComponent, + ItemTypeDetailViewComponent, + LibrariesBriefViewComponent, + LibraryComponent, + MenuComponent, + PatronsBriefViewComponent, + PatronTypesBriefViewComponent, + PatronTypesDetailViewComponent, + LibraryDetailViewComponent, + DayOpeningHoursComponent, + ExceptionDateComponent, + DocumentDetailViewComponent, + HoldingEditorComponent, + HoldingComponent, + HoldingsComponent, + LibraryComponent, + ExceptionDatesListComponent, + ExceptionDatesEditComponent, + CircPolicyDetailViewComponent, + ExceptionDateComponent, + LocationDetailViewComponent, + LocationComponent, + ItemDetailViewComponent, + ItemTransactionComponent, + ItemTransactionsComponent, + ItemsBriefViewComponent, + IssuesBriefViewComponent, + PatronDetailViewComponent, + VendorDetailViewComponent, + VendorBriefViewComponent, + AddressTypeComponent, + OrganisationDetailViewComponent, + RelatedResourceComponent, + ItemRequestComponent, + ErrorPageComponent, + SerialHoldingItemComponent, + SerialHoldingDetailViewComponent, + HoldingDetailViewComponent, + DefaultHoldingItemComponent, + NotesFormatPipe, + MarcPipe, + TabOrderDirective, + TemplatesBriefViewComponent, + TemplateDetailViewComponent, + CollectionBriefViewComponent, + CollectionDetailViewComponent, + CollectionItemsComponent, + DocumentRecordSearchComponent, + HoldingDetailComponent, + RemoteEntitiesPersonDetailViewComponent, + IllRequestsBriefViewComponent, + IllRequestDetailViewComponent, + CustomShortcutHelpComponent, + HoldingItemNoteComponent, + MenuSwitchLibraryComponent, + LocalFieldComponent, + MenuUserServicesComponent, + MenuLanguageComponent, + MenuUserComponent, + MenuDashboardComponent, + MenuMobileComponent, + SubMenuComponent, + HoldingItemTemporaryItemTypeComponent, + OperationLogsComponent, + HoldingSharedViewComponent, + OperationLogsDialogComponent, + CipoPatronTypeItemTypeComponent, + UserIdComponent, + UserIdEditorComponent, + RecordMaskedComponent, + IdentifiedbyValueComponent, + DialogImportComponent, + NotificationTypePipe, + CirculationLogsComponent, + CirculationLogsDialogComponent, + CirculationLogComponent, + ItemInCollectionPipe, + CountryCodeTranslatePipe, + DocumentDescriptionComponent, + OtherEditionComponent, + DescriptionZoneComponent, + DocumentProvisionActivityPipe, + MainTitleRelationPipe, + HoldingOrganisationComponent, + ExpectedIssueComponent, + ReceivedIssueComponent, + LoansBriefViewComponent, + PermissionDetailViewComponent, + PatronTransactionEventsBriefViewComponent, + PatronNamePipe, + PatronTransactionEventOverdueComponent, + PatronTransactionEventDefaultComponent, + PatronTransactionEventSearchViewComponent, + PaymentsDataComponent, + PaymentsDataTableComponent, + PaymentDataPieComponent, + PatronPermissionsComponent, + PatronPermissionComponent, + CirculationLogLoanComponent, + CirculationLogNotificationComponent, + CirculationStatsComponent, + ItemSwitchLocationStandaloneComponent, + ItemSwitchLocationComponent, + IssueEmailComponent, + EntityTypeaheadComponent, + RemoteEntitiesDetailViewComponent, + RemoteEntitiesOrganisationDetailViewComponent, + EntitiesLocalDetailViewComponent, + LocalPersonDetailViewComponent, + EntitiesLocalGlobalComponent, + LocalTopicDetailViewComponent, + LocalOrganisationDetailViewComponent, + LocalPlaceDetailViewComponent, + LocalWorkDetailViewComponent, + RemoteTopicDetailViewComponent, + AddEntityLocalComponent, + AddEntityLocalFormComponent, + StatisticsCfgBriefViewComponent, + StatisticsCfgDetailViewComponent, + ReportDataComponent, + ReportsListComponent, + ], + imports: [ + AppRoutingModule, + BrowserAnimationsModule, + BrowserModule, + BsDatepickerModule.forRoot(), + BsDropdownModule.forRoot(), + NgxChartsModule, + CollapseModule.forRoot(), + FormsModule, + HttpClientModule, + ReactiveFormsModule, + RecordModule, + TabsModule.forRoot(), + TableModule, + TooltipModule.forRoot(), + PopoverModule.forRoot(), + FormlyModule.forRoot({ + types: [ + { name: "cipo-pt-it", component: CipoPatronTypeItemTypeComponent }, { - provide: APP_BASE_HREF, - useFactory: (s: PlatformLocation) => s.getBaseHrefFromDOM(), - deps: [PlatformLocation] + name: "account-select", + component: SelectAccountEditorWidgetComponent, }, - { - provide: APP_INITIALIZER, - useFactory: appInitFactory, - deps: [ - AppInitializerService, - UserService, - AppConfigService, - TranslateService, - OrganisationService, - LocalStorageService, - LibrarySwitchService, - TypeaheadFactoryService - ], - multi: true - }, - { - provide: HTTP_INTERCEPTORS, - useClass: NoCacheHeaderInterceptor, - multi: true - }, - { - provide: HTTP_INTERCEPTORS, - useClass: UserCurrentLibraryInterceptor, - multi: true - }, - { provide: RemoteTypeaheadService, useExisting: UiRemoteTypeaheadService }, - // Use the "multi" parameter to allow the recovery of several services in the injector. - { provide: typeaheadToken, useExisting: DocumentsTypeahead, multi: true }, - { provide: typeaheadToken, useExisting: ItemsTypeahead, multi: true }, - { provide: typeaheadToken, useExisting: MefTypeahead, multi: true }, - { provide: typeaheadToken, useExisting: PatronsTypeahead, multi: true }, - { - provide: CoreConfigService, - useClass: AppConfigService - }, - { - provide: LOCALE_ID, - useFactory: (translate: TranslateService) => translate.currentLanguage, - deps: [TranslateService] - }, - BsLocaleService, - MefTypeahead, - DocumentsTypeahead, - ItemsTypeahead, - PatronsTypeahead, - MainTitlePipe, - MefTypeahead, - TruncateTextPipe, - CurrentLibraryPermissionValidator, - ReceivedOrderPermissionValidator, - // TODO: needed for production build, remove this after it is fixed in the - // @ngneat/hotkeys library - { - provide: HotkeysService, - useClass: HotkeysService - }, - MainTitlePipe, - ItemHoldingsCallNumberPipe, - CountryCodeTranslatePipe - ], - bootstrap: [AppComponent], - schemas: [ - CUSTOM_ELEMENTS_SCHEMA - ] + { name: "entityTypeahead", component: EntityTypeaheadComponent }, + ], + wrappers: [ + { name: "user-id", component: UserIdComponent }, + { name: "identifiedby-value", component: IdentifiedbyValueComponent }, + ], + }), + TranslateModule.forRoot({ + loader: { + provide: BaseTranslateLoader, + useClass: TranslateLoader, + deps: [CoreConfigService, HttpClient], + }, + }), + TypeaheadModule, + HotkeysModule, + SharedModule, + LoadingBarHttpClientModule, + PrimengImportModule, + PreviewEmailModule, + ], + providers: [ + { + provide: APP_BASE_HREF, + useFactory: (s: PlatformLocation) => s.getBaseHrefFromDOM(), + deps: [PlatformLocation], + }, + { + provide: APP_INITIALIZER, + useFactory: appInitFactory, + deps: [ + AppInitializerService, + UserService, + AppConfigService, + TranslateService, + OrganisationService, + LocalStorageService, + LibrarySwitchService, + TypeaheadFactoryService, + ], + multi: true, + }, + { + provide: HTTP_INTERCEPTORS, + useClass: NoCacheHeaderInterceptor, + multi: true, + }, + { + provide: HTTP_INTERCEPTORS, + useClass: UserCurrentLibraryInterceptor, + multi: true, + }, + { provide: RemoteTypeaheadService, useExisting: UiRemoteTypeaheadService }, + // Use the "multi" parameter to allow the recovery of several services in the injector. + { provide: typeaheadToken, useExisting: DocumentsTypeahead, multi: true }, + { provide: typeaheadToken, useExisting: ItemsTypeahead, multi: true }, + { provide: typeaheadToken, useExisting: MefTypeahead, multi: true }, + { provide: typeaheadToken, useExisting: PatronsTypeahead, multi: true }, + { + provide: CoreConfigService, + useClass: AppConfigService, + }, + { + provide: LOCALE_ID, + useFactory: (translate: TranslateService) => translate.currentLanguage, + deps: [TranslateService], + }, + BsLocaleService, + MefTypeahead, + DocumentsTypeahead, + ItemsTypeahead, + PatronsTypeahead, + MainTitlePipe, + MefTypeahead, + TruncateTextPipe, + CurrentLibraryPermissionValidator, + ReceivedOrderPermissionValidator, + // TODO: needed for production build, remove this after it is fixed in the + // @ngneat/hotkeys library + { + provide: HotkeysService, + useClass: HotkeysService, + }, + MainTitlePipe, + ItemHoldingsCallNumberPipe, + CountryCodeTranslatePipe, + ], + bootstrap: [AppComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], }) -export class AppModule { } +export class AppModule {} diff --git a/projects/admin/src/app/menu/menu-definition/menu-app.ts b/projects/admin/src/app/menu/menu-definition/menu-app.ts index 60122891f..de0e4e2b3 100644 --- a/projects/admin/src/app/menu/menu-definition/menu-app.ts +++ b/projects/admin/src/app/menu/menu-definition/menu-app.ts @@ -215,6 +215,19 @@ export const MENU_APP: IMenuParent[] = [ access: { permissions: [PERMISSIONS.PTTR_ACCESS] } + }, + { + name: "Report Configuration", + router_link: ["/", "records", "stats_cfg"], + attributes: { id: "stats-cfg-menu" }, + extras: { iconClass: "fa fa-cog" }, + query_params: { + library: "$currentLibrary", + active: "true", + }, + access: { + permissions: [PERMISSIONS.STAT_CFG_ACCESS], + }, } ] }, @@ -287,15 +300,6 @@ export const MENU_APP: IMenuParent[] = [ permissions: [PERMISSIONS.TMPL_ACCESS] } }, - { - name: 'Statistics configuration', - router_link: ['/', 'records', 'stats_cfg'], - attributes: { id: 'stats-cfg-menu' }, - extras: { iconClass: 'fa fa-cog' }, - access: { - permissions: [PERMISSIONS.STAT_CFG_ACCESS] - } - }, { name: 'Permissions matrix', router_link: ['/', 'permissions', 'matrix'], diff --git a/projects/admin/src/app/record/brief-view/statistics-cfg-brief-view-component.ts b/projects/admin/src/app/record/brief-view/statistics-cfg-brief-view-component.ts index 018cba482..98b5c3087 100644 --- a/projects/admin/src/app/record/brief-view/statistics-cfg-brief-view-component.ts +++ b/projects/admin/src/app/record/brief-view/statistics-cfg-brief-view-component.ts @@ -34,6 +34,9 @@ import { ResultItem } from '@rero/ng-core';
Category: {{ record.metadata.category.type | translate }}
+
+ Indicator: {{ record.metadata.category.indicator.type | translate }} +
` }) diff --git a/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/report-data/report-data.component.html b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/report-data/report-data.component.html new file mode 100644 index 000000000..c41b7ef8e --- /dev/null +++ b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/report-data/report-data.component.html @@ -0,0 +1,28 @@ + + + + + + {{ value }} + + + + + +

No result

+
diff --git a/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/report-data/report-data.component.ts b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/report-data/report-data.component.ts new file mode 100644 index 000000000..a53a661f1 --- /dev/null +++ b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/report-data/report-data.component.ts @@ -0,0 +1,28 @@ +/* + * RERO ILS UI + * Copyright (C) 2023 RERO + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Component, Input } from "@angular/core"; + +@Component({ + selector: "admin-report-data", + templateUrl: "./report-data.component.html", +}) +export class ReportDataComponent { + + // the report data to display + @Input() data: any; +} diff --git a/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component.html b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component.html new file mode 100644 index 000000000..7fd092514 --- /dev/null +++ b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component.html @@ -0,0 +1,37 @@ + + + + + + + + + +
+ + {{ report.created | dateTranslate: 'longDate' }} + + + + + + +
+ +

No result

+
diff --git a/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component.ts b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component.ts new file mode 100644 index 000000000..7b96c6e11 --- /dev/null +++ b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/reports-list/reports-list.component.ts @@ -0,0 +1,65 @@ +/* + * RERO ILS UI + * Copyright (C) 2023 RERO + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { Component, Input, OnInit } from "@angular/core"; +import { ApiService, Record, RecordService } from "@rero/ng-core"; +import { map } from "rxjs/operators"; + +@Component({ + selector: "admin-reports-list", + templateUrl: "./reports-list.component.html", +}) +export class ReportsListComponent implements OnInit { + // persistent identifier of the current stat report configuration + @Input() pid: any; + + // list of the corresponding reports from elasticsearch + reports: Array; + + /** + * Constructor + * @param _recordService - RecordService + * @param _apiService - ApiService + */ + constructor( + private _recordService: RecordService, + private _apiService: ApiService + ) {} + + /** + * Get the report item URL + * + * @param pid - Persistent Identifier value. + * @returns the URL as string. + */ + getReportUrl(pid: string): string { + return `${this._apiService.getEndpointByType("stats")}/${pid}`; + } + + /** OnInit hook */ + ngOnInit(): void { + this._recordService + .getRecords("stats", `config.pid:${this.pid}`, 1, 100) + .pipe( + map((result: Record) => + this._recordService.totalHits(result.hits.total) === 0 + ? [] + : result.hits.hits + ) + ).subscribe((res: any) => (this.reports = res)); + } +} diff --git a/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component.html b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component.html index 2079b6096..32e2283f1 100644 --- a/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component.html +++ b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component.html @@ -15,13 +15,16 @@ along with this program. If not, see . --> - +

{{ record.metadata.name }}

+
Library
+
{{ record.metadata.library.$ref | getRecord: 'libraries' : 'field' : 'name' | async }}
+
Is active
{{ record.metadata.name }}
Frequency
{{ record.metadata.frequency | translate }}
+ +
Filter numbers by libraries
+
+
+ {{ lib.$ref| getRecord: 'libraries' : 'field' : 'name' | async }} +
+
+
Configuration
+
Category
{{ record.metadata.category.type | translate }}
@@ -64,8 +76,31 @@
Configuration
Filter
{{ record.metadata.category.indicator.filter }}
+ +
Created at
+
{{ record.created | dateTranslate: 'medium' }}
+
+ +
Updated at
+
{{ record.updated | dateTranslate: 'medium' }}
+
+ + + + Reports + + + + + + {{ 'Live Values' | translate }} + + + + +
diff --git a/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component.ts b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component.ts index fd73c381b..958da8f59 100644 --- a/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component.ts +++ b/projects/admin/src/app/record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component.ts @@ -14,19 +14,65 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { Component } from '@angular/core'; +import { HttpClient } from "@angular/common/http"; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { ApiService } from "@rero/ng-core"; import { DetailRecord } from '@rero/ng-core/lib/record/detail/view/detail-record'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; @Component({ - selector: 'admin-statitics-cfg-view', - templateUrl: './statistics-cfg-detail-view.component.html' + selector: "admin-statitics-cfg-view", + templateUrl: "./statistics-cfg-detail-view.component.html", }) -export class StatisticsCfgDetailViewComponent implements DetailRecord { +export class StatisticsCfgDetailViewComponent + implements DetailRecord, OnInit, OnDestroy +{ /** Observable resolving record data */ record$: Observable; /** Resource type */ type: string; + + /** the api response record */ + record: any; + + // the current preview values + liveData: any = null; + + /** Subscription to (un)follow the record$ Observable */ + private _subscriptions = new Subscription(); + + /** + * Constructor + * + * @param _http - HttpClient + * @param _apiService = ApiService + */ + constructor(private _http: HttpClient, private _apiService: ApiService) {} + + /** OnInit hook */ + ngOnInit() { + this._subscriptions = this.record$.subscribe((record) => { + this.record = record; + }); + } + + /** onDestroy hook */ + ngOnDestroy(): void { + this._subscriptions.unsubscribe(); + } + + /** Preview values corresponding to the current configuration. */ + getLiveValues(): void { + // only once + if (this.liveData != null) { + return; + } + const pid = this.record.metadata.pid; + const baseUrl = this._apiService.endpointPrefix; + this._http + .get(`${baseUrl}/stats_cfg/live/${pid}`) + .subscribe((res) => (this.liveData = res)); + } } diff --git a/projects/admin/src/app/routes/route-tool.service.ts b/projects/admin/src/app/routes/route-tool.service.ts index 149d09fcf..30d2322bc 100644 --- a/projects/admin/src/app/routes/route-tool.service.ts +++ b/projects/admin/src/app/routes/route-tool.service.ts @@ -21,17 +21,16 @@ import { ActivatedRoute, Router, UrlSerializer } from '@angular/router'; import { OrganisationService } from '@app/admin/service/organisation.service'; import { TranslateService } from '@ngx-translate/core'; import { ActionStatus, ApiService, RecordService } from '@rero/ng-core'; -import { PermissionsService, UserService } from '@rero/shared'; +import { AppSettingsService, PermissionsService, UserService } from '@rero/shared'; import { Observable, of, Subscriber } from 'rxjs'; import { map } from 'rxjs/operators'; import { RecordPermissions } from '../classes/permissions'; import { RecordPermissionService } from '../service/record-permission.service'; @Injectable({ - providedIn: 'root' + providedIn: "root", }) export class RouteToolService { - /** * Proxy for permissions service * @return PermissionsService @@ -48,7 +47,6 @@ export class RouteToolService { return this._injector.get(TranslateService); } - /** Proxy for organisation service * @return OrganisationService */ @@ -64,6 +62,14 @@ export class RouteToolService { return this._injector.get(UserService); } + /** + * Proxy for settings service + * @return AppSettingsService + */ + get settingsService() { + return this._injector.get(AppSettingsService); + } + /** * Proxy for api service * @return ApiService @@ -140,7 +146,7 @@ export class RouteToolService { * Constructor * @param _injector - Injector */ - constructor(private _injector: Injector) { } + constructor(private _injector: Injector) {} /** * Get Token in injector @@ -155,7 +161,7 @@ export class RouteToolService { * @param message - string * @return Observable */ - can(message: string = ''): Observable { + can(message: string = ""): Observable { return of({ can: true, message }); } @@ -164,7 +170,7 @@ export class RouteToolService { * @param message - string * @return Observable */ - canNot(message: string = ''): Observable { + canNot(message: string = ""): Observable { return of({ can: false, message }); } @@ -179,10 +185,10 @@ export class RouteToolService { canUpdate(record: any, recordType: string): Observable { return new Observable((observer: Subscriber): void => { this.recordPermissionService - .getPermission(recordType, record.metadata.pid) - .subscribe((permission: RecordPermissions) => { - observer.next({ can: permission.update.can, message: '' }); - }); + .getPermission(recordType, record.metadata.pid) + .subscribe((permission: RecordPermissions) => { + observer.next({ can: permission.update.can, message: "" }); + }); }); } @@ -197,19 +203,20 @@ export class RouteToolService { canDelete(record: any, recordType: string): Observable { return new Observable((observer: Subscriber): void => { this.recordPermissionService - .getPermission(recordType, record.metadata.pid) - .subscribe((permission: RecordPermissions) => { - observer.next({ - can: permission.delete.can, - message: (permission.delete.can) - ? '' - : this.recordPermissionService.generateDeleteMessage(permission.delete.reasons) - }); + .getPermission(recordType, record.metadata.pid) + .subscribe((permission: RecordPermissions) => { + observer.next({ + can: permission.delete.can, + message: permission.delete.can + ? "" + : this.recordPermissionService.generateDeleteMessage( + permission.delete.reasons + ), }); + }); }); } - /** * Check if a record can be read * @param record - Object: the resource object to check @@ -223,7 +230,7 @@ export class RouteToolService { this.recordPermissionService .getPermission(recordType, record.metadata.pid) .subscribe((permission: RecordPermissions) => { - observer.next({ can: permission.read.can, message: '' }); + observer.next({ can: permission.read.can, message: "" }); }); }); } @@ -238,30 +245,40 @@ export class RouteToolService { * - canUpdate permission * - canDelete permission */ - permissions(record: any, recordType: string, membership = false): Observable { + permissions( record: any, recordType: string, membership = false): Observable { return new Observable((observer: Subscriber): void => { const permissionService = this.recordPermissionService; - permissionService.getPermission(recordType, record.metadata.pid) - .pipe(map((permission: RecordPermissions) => { - const user = this.userService.user; - if (membership && ('library' in record.metadata)) { - // Extract library pid - const libraryPid = ('$ref' in record.metadata.library) - ? record.metadata.library.$ref.split('/').pop() - : record.metadata.library.pid; - permission = permissionService.membership(user, libraryPid, permission); - } - return { - canRead: { can: permission.read.can, message: '' }, - canUpdate: { can: permission.update.can, message: '' }, - canDelete: { - can: permission.delete.can, - message: (permission.delete.can) - ? '' - : permissionService.generateDeleteMessage(permission.delete.reasons) + permissionService + .getPermission(recordType, record.metadata.pid) + .pipe( + map((permission: RecordPermissions) => { + const user = this.userService.user; + if (membership && "library" in record.metadata) { + // Extract library pid + const libraryPid = + "$ref" in record.metadata.library + ? record.metadata.library.$ref.split("/").pop() + : record.metadata.library.pid; + permission = permissionService.membership( + user, + libraryPid, + permission + ); } - }; - })) + return { + canRead: { can: permission.read.can, message: "" }, + canUpdate: { can: permission.update.can, message: "" }, + canDelete: { + can: permission.delete.can, + message: permission.delete.can + ? "" + : permissionService.generateDeleteMessage( + permission.delete.reasons + ), + }, + }; + }) + ) .subscribe((permission: any) => { observer.next(permission); }); @@ -278,7 +295,7 @@ export class RouteToolService { */ getRouteQueryParam(name: string, defaultValue = null) { const queryParams = this.activatedRoute.snapshot.queryParams; - return (name in queryParams && queryParams[name].length > 0) + return name in queryParams && queryParams[name].length > 0 ? queryParams[name] : defaultValue; } @@ -305,9 +322,9 @@ export class RouteToolService { */ private _aggFilter(aggregations: object) { const aggs = {}; - Object.keys(aggregations).map(aggregation => { - if (aggregation.indexOf('__') > -1) { - const splitted = aggregation.split('__'); + Object.keys(aggregations).map((aggregation) => { + if (aggregation.indexOf("__") > -1) { + const splitted = aggregation.split("__"); if (this.translateService.currentLang === splitted[1]) { aggs[aggregation] = aggregations[aggregation]; } diff --git a/projects/admin/src/app/routes/statistics-cfg-route.ts b/projects/admin/src/app/routes/statistics-cfg-route.ts index 4f2ca030f..6523409c1 100644 --- a/projects/admin/src/app/routes/statistics-cfg-route.ts +++ b/projects/admin/src/app/routes/statistics-cfg-route.ts @@ -15,65 +15,187 @@ * along with this program. If not, see . */ -import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; -import { DetailComponent, EditorComponent, RecordSearchPageComponent, RouteInterface } from "@rero/ng-core"; -import { PERMISSIONS, PERMISSION_OPERATOR } from '@rero/shared'; -import { of } from 'rxjs'; -import { CanAccessGuard, CAN_ACCESS_ACTIONS } from '../guard/can-access.guard'; -import { PermissionGuard } from '../guard/permission.guard'; -import { StatisticsCfgBriefViewComponent } from '../record/brief-view/statistics-cfg-brief-view-component'; -import { StatisticsCfgDetailViewComponent } from '../record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component'; +import { marker as _ } from "@biesbjerg/ngx-translate-extract-marker"; +import { + DetailComponent, + EditorComponent, + JSONSchema7, + Record, + RecordSearchPageComponent, + RecordService, + RouteInterface, +} from "@rero/ng-core"; +import { + PERMISSIONS, + PERMISSION_OPERATOR, +} from "@rero/shared"; +import { of } from "rxjs"; +import { CanAccessGuard, CAN_ACCESS_ACTIONS } from "../guard/can-access.guard"; +import { PermissionGuard } from "../guard/permission.guard"; +import { StatisticsCfgBriefViewComponent } from "../record/brief-view/statistics-cfg-brief-view-component"; +import { StatisticsCfgDetailViewComponent } from "../record/detail-view/statistics-cfg-detail-view/statistics-cfg-detail-view.component"; import { BaseRoute } from "./base-route"; +import { FormlyFieldConfig } from "@ngx-formly/core"; +import { map } from "rxjs/operators"; export class StatisticsCfgRoute extends BaseRoute implements RouteInterface { - /** Route name */ - readonly name = 'stats_cfg'; + readonly name = "stats_cfg"; /** Record type */ - readonly recordType = 'stats_cfg'; + readonly recordType = "stats_cfg"; getConfiguration() { return { matcher: (url: any) => this.routeMatcher(url, this.name), children: [ - { path: '', component: RecordSearchPageComponent, canActivate: [ PermissionGuard ], data: { permissions: [ PERMISSIONS.STAT_CFG_ACCESS, PERMISSIONS.STAT_CFG_SEARCH ], operator: PERMISSION_OPERATOR.AND }}, - { path: 'detail/:pid', component: DetailComponent, canActivate: [ CanAccessGuard ], data: { action: CAN_ACCESS_ACTIONS.READ } }, - { path: 'edit/:pid', component: EditorComponent, canActivate: [ CanAccessGuard ], data: { action: CAN_ACCESS_ACTIONS.UPDATE } }, - { path: 'new', component: EditorComponent, canActivate: [ PermissionGuard ], data: { permissions: [ PERMISSIONS.STAT_CFG_CREATE ] } } + { + path: "", + component: RecordSearchPageComponent, + canActivate: [PermissionGuard], + data: { + permissions: [ + PERMISSIONS.STAT_CFG_ACCESS, + PERMISSIONS.STAT_CFG_SEARCH, + ], + operator: PERMISSION_OPERATOR.AND, + }, + }, + { + path: "detail/:pid", + component: DetailComponent, + canActivate: [CanAccessGuard], + data: { action: CAN_ACCESS_ACTIONS.READ }, + }, + { + path: "edit/:pid", + component: EditorComponent, + canActivate: [CanAccessGuard], + data: { action: CAN_ACCESS_ACTIONS.UPDATE }, + }, + { + path: "new", + component: EditorComponent, + canActivate: [PermissionGuard], + data: { permissions: [PERMISSIONS.STAT_CFG_CREATE] }, + }, ], data: { types: [ { key: this.name, - label: _('Statistics configuration'), + label: _("Statistics configuration"), editorSettings: { - longMode: true, + longMode: false, }, component: StatisticsCfgBriefViewComponent, detailComponent: StatisticsCfgDetailViewComponent, aggregationsBucketSize: 10, - aggregationsExpand: [ - 'category' - ], - aggregationsOrder: [ - 'category' + aggregationsExpand: ["library", "category", "frequency"], + aggregationsOrder: ["library", "category", "frequency"], + searchFilters: [ + this.expertSearchFilter(), + { + label: _('Active Only'), + filter: 'active', + value: 'true' + } ], - canAdd: () => of({ can: this._routeToolService.permissionsService.canAccess(PERMISSIONS.STAT_CFG_CREATE) }), - permissions: (record: any) => this._routeToolService.permissions(record, this.recordType), + listHeaders: { + Accept: 'application/rero+json' + }, + canAdd: () => + of({ + can: this._routeToolService.permissionsService.canAccess( + PERMISSIONS.STAT_CFG_CREATE + ), + }), + permissions: (record: any) => + this._routeToolService.permissions(record, this.recordType), + formFieldMap: ( + field: FormlyFieldConfig, + jsonSchema: JSONSchema7 + ): FormlyFieldConfig => { + return this._populateLocationsByCurrentUserLibrary(field, jsonSchema); + }, preCreateRecord: (data: any) => { const user = this._routeToolService.userService.user; - data.organisation = { + data.library = { $ref: this._routeToolService.apiService.getRefEndpoint( - 'organisations', - user.currentOrganisation - ) + "libraries", + user.currentLibrary + ), }; return data; }, - } - ] - } + }, + ], + }, }; } + + /** + * Populate select menu with the current user library + * @param field - FormlyFieldConfig + * @param jsonSchema - JSONSchema7 + * @return FormlyFieldConfig + */ + private _populateLocationsByCurrentUserLibrary(field: FormlyFieldConfig, jsonSchema: JSONSchema7): FormlyFieldConfig { + const formWidget = jsonSchema.widget; + if ( + formWidget?.formlyConfig?.templateOptions?.fieldMap === + "libraries" + ) { + field.type = "select"; + field.hooks = { + ...field.hooks, + onInit: (field: FormlyFieldConfig): void => { + const user = this._routeToolService.userService.user; + const baseUrl = this._routeToolService.settingsService.baseUrl; + const prefix = this._routeToolService.apiService.getEndpointByType('libraries'); + if (user.currentLibrary != null && field.formControl.value == null) { + field.formControl.setValue( + `${baseUrl}${prefix}/${user.currentLibrary}`); + } + }, + afterContentInit: (f: FormlyFieldConfig) => { + const recordService = this._routeToolService.recordService; + const apiService = this._routeToolService.apiService; + + f.templateOptions.options = recordService + .getRecords( + "libraries", + "", + 1, + RecordService.MAX_REST_RESULTS_SIZE, + undefined, + undefined, + undefined, + "name" + ) + .pipe( + map((result: Record) => + this._routeToolService.recordService.totalHits( + result.hits.total + ) === 0 + ? [] + : result.hits.hits + ), + map((hits) => + hits.map((hit: any) => { + return { + label: hit.metadata.name, + value: apiService.getRefEndpoint( + "libraries", + hit.metadata.pid + ), + }; + }) + ) + ); + }, + }; + } + return field; + } } diff --git a/projects/admin/src/app/service/record-permission.service.ts b/projects/admin/src/app/service/record-permission.service.ts index bd3341c00..e8be6e7a8 100644 --- a/projects/admin/src/app/service/record-permission.service.ts +++ b/projects/admin/src/app/service/record-permission.service.ts @@ -164,80 +164,116 @@ export class RecordPermissionService { private plurialLinksMessages() { return { acq_order_lines: { - '=1': this._translateService.instant('has 1 acquisition order line attached'), - other: this._translateService.instant('has # acquisition order lines attached') + "=1": this._translateService.instant( + "has 1 acquisition order line attached" + ), + other: this._translateService.instant( + "has # acquisition order lines attached" + ), }, acq_orders: { - '=1': this._translateService.instant('has 1 acquisition order attached'), - other: this._translateService.instant('has # acquisition orders attached') + "=1": this._translateService.instant( + "has 1 acquisition order attached" + ), + other: this._translateService.instant( + "has # acquisition orders attached" + ), }, acq_receipt_lines: { - '=1': this._translateService.instant('has 1 acquisition receipt line attached'), - other: this._translateService.instant('has # acquisition receipts lines attached') + "=1": this._translateService.instant( + "has 1 acquisition receipt line attached" + ), + other: this._translateService.instant( + "has # acquisition receipts lines attached" + ), }, acq_receipt: { - '=1': this._translateService.instant('has 1 acquisition receipt attached'), - other: this._translateService.instant('has # acquisition receipts attached') + "=1": this._translateService.instant( + "has 1 acquisition receipt attached" + ), + other: this._translateService.instant( + "has # acquisition receipts attached" + ), }, acq_accounts: { - '=1': this._translateService.instant('has 1 acquisition account attached'), - other: this._translateService.instant('has # acquisition accounts attached') + "=1": this._translateService.instant( + "has 1 acquisition account attached" + ), + other: this._translateService.instant( + "has # acquisition accounts attached" + ), }, budgets: { - '=1': this._translateService.instant('has 1 budget attached'), - other: this._translateService.instant('has # budgets attached') + "=1": this._translateService.instant("has 1 budget attached"), + other: this._translateService.instant("has # budgets attached"), }, circ_policies: { - '=1': this._translateService.instant('has 1 circulation policy attached'), - other: this._translateService.instant('has # circulation policies attached') + "=1": this._translateService.instant( + "has 1 circulation policy attached" + ), + other: this._translateService.instant( + "has # circulation policies attached" + ), }, documents: { - '=1': this._translateService.instant('has 1 document attached'), - other: this._translateService.instant('has # documents attached') + "=1": this._translateService.instant("has 1 document attached"), + other: this._translateService.instant("has # documents attached"), }, fees: { - '=1': this._translateService.instant('has 1 fee attached'), - other: this._translateService.instant('has # fees attached') + "=1": this._translateService.instant("has 1 fee attached"), + other: this._translateService.instant("has # fees attached"), }, holdings: { - '=1': this._translateService.instant('has 1 holding attached'), - other: this._translateService.instant('has # holdings attached') + "=1": this._translateService.instant("has 1 holding attached"), + other: this._translateService.instant("has # holdings attached"), }, item_types: { - '=1': this._translateService.instant('has 1 item type attached'), - other: this._translateService.instant('has # item types attached') + "=1": this._translateService.instant("has 1 item type attached"), + other: this._translateService.instant("has # item types attached"), }, items: { - '=1': this._translateService.instant('has 1 item attached'), - other: this._translateService.instant('has # items attached') + "=1": this._translateService.instant("has 1 item attached"), + other: this._translateService.instant("has # items attached"), }, libraries: { - '=1': this._translateService.instant('has 1 library attached'), - other: this._translateService.instant('has # libraries attached') + "=1": this._translateService.instant("has 1 library attached"), + other: this._translateService.instant("has # libraries attached"), }, loans: { - '=1': this._translateService.instant('has 1 loan attached'), - other: this._translateService.instant('has # loans attached') + "=1": this._translateService.instant("has 1 loan attached"), + other: this._translateService.instant("has # loans attached"), }, locations: { - '=1': this._translateService.instant('has 1 location attached'), - other: this._translateService.instant('has # locations attached') + "=1": this._translateService.instant("has 1 location attached"), + other: this._translateService.instant("has # locations attached"), }, organisations: { - '=1': this._translateService.instant('has 1 organisation attached'), - other: this._translateService.instant('has # organisations attached') + "=1": this._translateService.instant("has 1 organisation attached"), + other: this._translateService.instant("has # organisations attached"), }, patron_types: { - '=1': this._translateService.instant('has 1 patron type attached'), - other: this._translateService.instant('has # patron types attached') + "=1": this._translateService.instant("has 1 patron type attached"), + other: this._translateService.instant("has # patron types attached"), }, patrons: { - '=1': this._translateService.instant('has 1 patron attached'), - other: this._translateService.instant('has # patrons attached') + "=1": this._translateService.instant("has 1 patron attached"), + other: this._translateService.instant("has # patrons attached"), + }, + reports: { + "=1": this._translateService.instant("has 1 report attached"), + other: this._translateService.instant("has # reports attached"), }, rolled_over: { - other: this._translateService.instant('Fiscal year closed') - } + other: this._translateService.instant("Fiscal year closed"), + }, + templates: { + "=1": this._translateService.instant("has 1 template attached"), + other: this._translateService.instant("has # templates attached"), + }, + transactions: { + "=1": this._translateService.instant("has 1 transaction attached"), + other: this._translateService.instant("has # transactions attached"), + }, }; }