From 50c78cb7e87e46847f9b6a87797014e5b243d9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johnny=20Marie=CC=81thoz?= Date: Thu, 13 Feb 2025 07:27:56 +0100 Subject: [PATCH] acquisitions: add expand/collapse all acquisition accounts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Uniformizes placeholders. * Closes rero/rero-ils#3771. * Closes rero/rero-ils#2874. * Closes rero/rero-ils#2873. Co-Authored-by: Johnny Mariéthoz Co-Authored-by: Bertrand Zuchuat --- .../account-list/account-list.component.html | 5 +- .../account-list/account-list.component.ts | 169 +++++++++--------- ...elect-account-editor-widget.component.html | 2 +- .../select-account-editor-widget.component.ts | 17 +- .../receipt-form/order-receipt-form.ts | 2 +- .../select-account.component.html | 62 ------- .../select-account.component.ts | 63 ------- .../item-switch-location.component.html | 2 +- ...document-advanced-search-form.component.ts | 2 +- 9 files changed, 113 insertions(+), 211 deletions(-) delete mode 100644 projects/admin/src/app/acquisition/formly/type/select-account/select-account.component.html delete mode 100644 projects/admin/src/app/acquisition/formly/type/select-account/select-account.component.ts diff --git a/projects/admin/src/app/acquisition/components/account/account-list/account-list.component.html b/projects/admin/src/app/acquisition/components/account/account-list/account-list.component.html index e7e650ce0..8d536c19f 100644 --- a/projects/admin/src/app/acquisition/components/account/account-list/account-list.component.html +++ b/projects/admin/src/app/acquisition/components/account/account-list/account-list.component.html @@ -47,10 +47,13 @@

Acquisition accounts

@if (rootAccounts.length > 0) { +
+ + +
diff --git a/projects/admin/src/app/acquisition/components/account/account-list/account-list.component.ts b/projects/admin/src/app/acquisition/components/account/account-list/account-list.component.ts index bd63fe5ef..49d8c9133 100644 --- a/projects/admin/src/app/acquisition/components/account/account-list/account-list.component.ts +++ b/projects/admin/src/app/acquisition/components/account/account-list/account-list.component.ts @@ -19,22 +19,20 @@ import { HttpParams } from '@angular/common/http'; import { Component, inject, OnInit } from '@angular/core'; import { AcqAccountApiService } from '@app/admin/acquisition/api/acq-account-api.service'; import { IAcqAccount } from '@app/admin/acquisition/classes/account'; -import { CONFIG } from '@rero/ng-core'; import { exportFormats } from '@app/admin/acquisition/routes/accounts-route'; import { OrganisationService } from '@app/admin/service/organisation.service'; import { RecordPermissionService } from '@app/admin/service/record-permission.service'; import { TranslateService } from '@ngx-translate/core'; -import { ApiService, RecordService } from '@rero/ng-core'; +import { ApiService, CONFIG, RecordService } from '@rero/ng-core'; import { IPermissions, PERMISSIONS, UserService } from '@rero/shared'; -import { MessageService } from 'primeng/api'; -import { forkJoin, map, switchMap, tap } from 'rxjs'; +import { MessageService, TreeNode, TreeTableNode } from 'primeng/api'; +import { forkJoin, switchMap, tap } from 'rxjs'; @Component({ selector: 'admin-account-list', - templateUrl: './account-list.component.html' + templateUrl: './account-list.component.html', }) export class AccountListComponent implements OnInit { - private userService: UserService = inject(UserService); private acqAccountApiService: AcqAccountApiService = inject(AcqAccountApiService); private organisationService: OrganisationService = inject(OrganisationService); @@ -45,14 +43,14 @@ export class AccountListComponent implements OnInit { // COMPONENT ATTRIBUTES ======================================================= /** Root account to display */ - rootAccounts: any[] = []; + rootAccounts: TreeTableNode[] = []; /** Export options configuration. */ exportOptions: { - label: string, - url: string, - disabled?: boolean, - disabled_message?: string + label: string; + url: string; + disabled?: boolean; + disabled_message?: string; }[]; /** All user permissions */ @@ -61,7 +59,6 @@ export class AccountListComponent implements OnInit { /** Store library pid */ private _libraryPid: string; - // GETTER & SETTER ============================================================ /** Get the current organisation */ get organisation(): any { @@ -70,43 +67,64 @@ export class AccountListComponent implements OnInit { /** Get a message containing the reasons why record list cannot be exported */ get exportInfoMessage(): string { - return (this.rootAccounts.length === 0) + return this.rootAccounts.length === 0 ? this.translateService.instant('Result list is empty.') - : this.translateService.instant('Too many items. Should be less than {{max}}.', - { max: RecordService.MAX_REST_RESULTS_SIZE } - ); + : this.translateService.instant('Too many items. Should be less than {{max}}.', { + max: RecordService.MAX_REST_RESULTS_SIZE, + }); + } + + private orderAccountsAsTree(accounts): TreeTableNode[] { + let accountsByPid = {}; + accounts = this.processAccount(accounts); + accounts.map((val) => { + const key = val?.data?.parent?.pid ? val.data.parent.pid : -1; + if (!accountsByPid[key]) { + accountsByPid[key] = []; + } + accountsByPid[key].push(val); + }); + const localAccounts = accountsByPid[-1].sort((a, b) => a.label.localeCompare(b.label)); + this.attachChildren(localAccounts, accountsByPid); + return localAccounts; } + private attachChildren(accounts, accountsByPid) { + accounts.map((val) => { + if (accountsByPid[val.data.pid]) { + val.children = accountsByPid[val.data.pid].sort((a, b) => a.label.localeCompare(b.label)); + this.attachChildren(val.children, accountsByPid); + } + }); + } /** OnInit hook */ ngOnInit(): void { this._libraryPid = this.userService.user.currentLibrary; - let localAccounts = []; - this.acqAccountApiService.getAccounts(this._libraryPid, null).pipe( - map(accounts => this.processAccount(accounts)), - tap(accounts => localAccounts = accounts), - switchMap(accounts => { - const obs = accounts.map(account => this.recordPermissionService - .getPermission('acq_accounts', account.data.pid)) - return forkJoin(obs); - }), - map((permissions: any) => { - permissions.forEach((permission, i) => { - localAccounts[i].data.permissions = permission; - }); - }) - ).subscribe( - () => { - this.rootAccounts = localAccounts; - this.exportOptions = this._exportFormats(); - }); + let localAccounts; + this.acqAccountApiService + .getAccounts(this._libraryPid, undefined, { sort: 'depth' }) + .pipe( + tap((accounts: IAcqAccount[]) => (localAccounts = accounts)), + switchMap((accounts: any[]) => { + const obs = accounts.map((account) => + this.recordPermissionService + .getPermission('acq_accounts', account.pid) + .pipe(tap((permission) => (account.permissions = permission))) + ); + return forkJoin(obs); + }), + tap(() => (this.rootAccounts = this.orderAccountsAsTree(localAccounts))), + tap(() => (this.exportOptions = this._exportFormats())) + ) + .subscribe(); } processAccount(accounts) { - return accounts.map(account => { + return accounts.map((account) => { return { data: account, label: account.name, - leaf: !(account?.number_of_children > 0) + leaf: !(account?.number_of_children > 0), }; }); } @@ -118,10 +136,10 @@ export class AccountListComponent implements OnInit { .pipe( tap(() => { if (node.parent) { - node.parent.children = node.parent.children.filter(account => account.data.pid !== node.node.data.pid); + node.parent.children = node.parent.children.filter((account) => account.data.pid !== node.node.data.pid); node.parent.leaf = !(node.parent.children.length > 0); } else { - this.rootAccounts = this.rootAccounts.filter(account => account.data.pid !== node.node.data.pid); + this.rootAccounts = this.rootAccounts.filter((account) => account.data.pid !== node.node.data.pid); } this.rootAccounts = [...this.rootAccounts]; }) @@ -131,7 +149,7 @@ export class AccountListComponent implements OnInit { severity: 'success', summary: this.translateService.instant('Account'), detail: this.translateService.instant('Account deleted'), - life: CONFIG.MESSAGE_LIFE + life: CONFIG.MESSAGE_LIFE, }); }); } @@ -140,28 +158,26 @@ export class AccountListComponent implements OnInit { return this.recordPermissionService.generateDeleteMessage(permissions.delete.reasons); } - onNodeExpand(event: any) { - if (!event.node.children) { - let localAccounts = []; - this.acqAccountApiService.getAccounts(this._libraryPid, event.node.data.pid).pipe( - map(accounts => this.processAccount(accounts)), - tap(accounts => localAccounts = accounts), - switchMap(accounts => { - const obs = accounts.map(account => this.recordPermissionService - .getPermission('acq_accounts', account.data.pid)) - return forkJoin(obs); - }), - map((permissions: any) => { - permissions.forEach((permission, i) => { - localAccounts[i].data.permissions = permission; - }); - }) - ).subscribe( - () => { - event.node.children = localAccounts; - this.rootAccounts = [...this.rootAccounts]; - } - ); + expandAll() { + this.rootAccounts.forEach((node) => { + this.expandRecursive(node, true); + }); + this.rootAccounts = [...this.rootAccounts]; + } + + collapseAll() { + this.rootAccounts.forEach((node) => { + this.expandRecursive(node, false); + }); + this.rootAccounts = [...this.rootAccounts]; + } + + private expandRecursive(node: TreeNode, isExpand: boolean) { + node.expanded = isExpand; + if (node.children) { + node.children.forEach((childNode) => { + this.expandRecursive(childNode, isExpand); + }); } } @@ -170,16 +186,14 @@ export class AccountListComponent implements OnInit { * @return Array of export format to generate an `export as` button or an empty array. */ private _exportFormats(): Array { - return exportFormats.map( - (format) => { - return { - label: format.label, - url: this.getExportFormatUrl(format), - disabled: !this.canExport(format), - disabled_message: this.exportInfoMessage - }; - } - ); + return exportFormats.map((format) => { + return { + label: format.label, + url: this.getExportFormatUrl(format), + disabled: !this.canExport(format), + disabled_message: this.exportInfoMessage, + }; + }); } /** @@ -188,15 +202,10 @@ export class AccountListComponent implements OnInit { * @return formatted url for an export format. */ getExportFormatUrl(format: any) { - const defaultQueryParams = [ - 'is_active:true', - `library.pid:${this._libraryPid}` - ]; + const defaultQueryParams = ['is_active:true', `library.pid:${this._libraryPid}`]; const query = defaultQueryParams.join(' AND '); const baseUrl = format.endpoint || this.apiService.getExportEndpointByType('acq_accounts'); - const params = new HttpParams() - .set('q', query) - .set('format', format.format); + const params = new HttpParams().set('q', query).set('format', format.format); if (!format.disableMaxRestResultsSize) { params.append('size', String(RecordService.MAX_REST_RESULTS_SIZE)); } @@ -210,7 +219,7 @@ export class AccountListComponent implements OnInit { */ canExport(format: any): boolean { const totalResults = this.rootAccounts.length; - return (format.hasOwnProperty('disableMaxRestResultsSize') && format.disableMaxRestResultsSize) + return format.hasOwnProperty('disableMaxRestResultsSize') && format.disableMaxRestResultsSize ? totalResults > 0 : totalResults > 0 && totalResults < RecordService.MAX_REST_RESULTS_SIZE; } diff --git a/projects/admin/src/app/acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component.html b/projects/admin/src/app/acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component.html index 9287ae2c4..54bf8f2d9 100644 --- a/projects/admin/src/app/acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component.html +++ b/projects/admin/src/app/acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component.html @@ -19,7 +19,7 @@ styleClass="w-full" [showClear]="true" [options]="accountList" - [placeholder]="to.placeholder" + [placeholder]="'Select…' | translate" [(ngModel)]="selectedAccount" (onChange)="selectAccount($event)" [loading]="loading" diff --git a/projects/admin/src/app/acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component.ts b/projects/admin/src/app/acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component.ts index 2ff985d59..6a22550b8 100644 --- a/projects/admin/src/app/acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component.ts +++ b/projects/admin/src/app/acquisition/components/editor/widget/select-account-editor-widget/select-account-editor-widget.component.ts @@ -58,7 +58,22 @@ export class SelectAccountEditorWidgetComponent extends FieldType implements OnI this.acqAccountApiService.getAccounts(libraryPid).subscribe({ next: (accounts: IAcqAccount[]) => { accounts = orderAccountsAsTree(accounts); - this.accountList = accounts; + // filter me and my children to avoid backend recursion errors + let accountPid = this.field.props.editorConfig.pid; + if(accountPid) { + let newAccounts = []; + let removed = []; + accounts.map(account => { + if (account.pid !== accountPid && !removed.some(removedAccountPid => removedAccountPid === account?.parent?.pid)) { + newAccounts.push(account); + } else { + removed.push(account.pid); + } + }); + this.accountList = newAccounts; + } else { + this.accountList = accounts; + } if (this.formControl.value) { const currentPid = this.formControl.value.substring(this.formControl.value.lastIndexOf('/') + 1); diff --git a/projects/admin/src/app/acquisition/components/receipt/receipt-form/order-receipt-form.ts b/projects/admin/src/app/acquisition/components/receipt/receipt-form/order-receipt-form.ts index 78bbabdf1..8c00acd07 100644 --- a/projects/admin/src/app/acquisition/components/receipt/receipt-form/order-receipt-form.ts +++ b/projects/admin/src/app/acquisition/components/receipt/receipt-form/order-receipt-form.ts @@ -330,7 +330,7 @@ export class OrderReceiptForm { type: 'account-select', wrappers: ['form-field'], props: { - placeholder: _('Select an account'), + placeholder: _('Select…'), label: _('Account'), required: true, options: [ diff --git a/projects/admin/src/app/acquisition/formly/type/select-account/select-account.component.html b/projects/admin/src/app/acquisition/formly/type/select-account/select-account.component.html deleted file mode 100644 index f25e1bfb7..000000000 --- a/projects/admin/src/app/acquisition/formly/type/select-account/select-account.component.html +++ /dev/null @@ -1,62 +0,0 @@ - -
-
- - -
-
- Please select an account. -
-
diff --git a/projects/admin/src/app/acquisition/formly/type/select-account/select-account.component.ts b/projects/admin/src/app/acquisition/formly/type/select-account/select-account.component.ts deleted file mode 100644 index 1b54561c3..000000000 --- a/projects/admin/src/app/acquisition/formly/type/select-account/select-account.component.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * RERO ILS UI - * Copyright (C) 2021-2024 RERO - * Copyright (C) 2021-2023 UCLouvain - * - * 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 { ChangeDetectorRef, Component, inject, OnInit } from '@angular/core'; -import { IAcqAccount } from '@app/admin/acquisition/classes/account'; -import { FieldType } from '@ngx-formly/core'; -import { ApiService } from '@rero/ng-core'; - -@Component({ - selector: 'admin-select-account', - templateUrl: './select-account.component.html' -}) -export class SelectAccountComponent extends FieldType implements OnInit { - - private changeDetectorRef: ChangeDetectorRef = inject(ChangeDetectorRef); - private apiService: ApiService = inject(ApiService); - - /** accounts list */ - accountList: IAcqAccount[] = []; - /** the selected account */ - selectedAccount: IAcqAccount = null; - /** currency */ - currency: string; - - /** OnInit Hook */ - ngOnInit() { - this.props.options.forEach((option: any) => this.accountList.push(option)); - this.currency = this.props.currency; - - if (this.formControl.value) { - const currentPid = this.formControl.value.substring(this.formControl.value.lastIndexOf('/') + 1); - const currentAccount = this.accountList.find((account: IAcqAccount) => account.pid === currentPid); - if (currentAccount !== undefined) { - this.selectedAccount = currentAccount; - } - } - } - - /** - * Store the selected option, when an option is clicked in the list - * @param account - The selected account. - */ - selectAccount(account: IAcqAccount): void { - const accountRef = this.apiService.getRefEndpoint('acq_accounts', account.pid); - this.selectedAccount = account; - this.formControl.patchValue(accountRef); - this.changeDetectorRef.markForCheck(); - } -} diff --git a/projects/admin/src/app/components/items/switch-location/item-switch-location/item-switch-location.component.html b/projects/admin/src/app/components/items/switch-location/item-switch-location/item-switch-location.component.html index 95f97dd5d..20fa378b9 100644 --- a/projects/admin/src/app/components/items/switch-location/item-switch-location/item-switch-location.component.html +++ b/projects/admin/src/app/components/items/switch-location/item-switch-location/item-switch-location.component.html @@ -36,7 +36,7 @@ formControlName="target" [options]="options" [group]="true" - [placeholder]="'Select a new location' | translate" + [placeholder]="'Select…' | translate" [styleClass]="'w-100'" [scrollHeight]="'40vh'" [filter]="isFilterActive()" diff --git a/projects/admin/src/app/record/search-view/document-advanced-search-form/document-advanced-search-form.component.ts b/projects/admin/src/app/record/search-view/document-advanced-search-form/document-advanced-search-form.component.ts index 7c7221b77..2077231f3 100644 --- a/projects/admin/src/app/record/search-view/document-advanced-search-form/document-advanced-search-form.component.ts +++ b/projects/admin/src/app/record/search-view/document-advanced-search-form/document-advanced-search-form.component.ts @@ -152,7 +152,7 @@ export class DocumentAdvancedSearchFormComponent implements OnInit { field.props.translated = false; field.props.minItemsToDisplaySearch = 10; field.props.sort = true; - field.props.placeholder = this.translateService.instant("Select an option…"); + field.props.placeholder = this.translateService.instant("Select…"); field.props.sortOrder = "asc"; field.props.class = "w-full"; field.props.styleClass = "w-full";