From f7bec4ccdfe875dc36ad22c3096391604155b789 Mon Sep 17 00:00:00 2001 From: Florent Poittevin <florent.poittevin@unige.ch> Date: Mon, 28 Oct 2019 16:41:23 +0100 Subject: [PATCH] feat: allow to filter by org unit on deposit list --- .../deposit-form.presentational.ts | 7 +- .../deposit-list/deposit-list.routable.ts | 15 ++- .../shared-data-table.presentational.html | 117 ++++++++++-------- .../shared-data-table.presentational.scss | 70 +++++++++-- .../shared-data-table.presentational.ts | 19 ++- ...hable-abstract-content.presentational.scss | 3 +- ...searchable-single-select.presentational.ts | 11 ++ src/app/shared/enums/field-type.enum.ts | 2 + .../shared/models/data-table-columns.model.ts | 5 + 9 files changed, 174 insertions(+), 75 deletions(-) diff --git a/src/app/features/deposit/components/presentationals/deposit-form/deposit-form.presentational.ts b/src/app/features/deposit/components/presentationals/deposit-form/deposit-form.presentational.ts index b7371a3b7..009353e2b 100644 --- a/src/app/features/deposit/components/presentationals/deposit-form/deposit-form.presentational.ts +++ b/src/app/features/deposit/components/presentationals/deposit-form/deposit-form.presentational.ts @@ -211,7 +211,12 @@ export class DepositFormPresentational extends SharedAbstractFormPresentational< return this.form.get(this.formDefinition.authors).value.includes(this.personConnected.resId); } - orgUnitSelectionChange(orgUnitResId: string): void { + orgUnitSelectionChange(orgUnitResId: string | undefined): void { + if (isNullOrUndefined(orgUnitResId)) { + this.form.get(this.formDefinition.preservationPolicyId).setValue(undefined); + this.form.get(this.formDefinition.submissionPolicyId).setValue(undefined); + return; + } this._orgUnitValueBS.next(orgUnitResId); let valueChange: boolean = false; this.subscribe(this.selectedOrgUnit.pipe( diff --git a/src/app/features/deposit/components/routables/deposit-list/deposit-list.routable.ts b/src/app/features/deposit/components/routables/deposit-list/deposit-list.routable.ts index cb60e3e3d..e30942420 100644 --- a/src/app/features/deposit/components/routables/deposit-list/deposit-list.routable.ts +++ b/src/app/features/deposit/components/routables/deposit-list/deposit-list.routable.ts @@ -11,10 +11,13 @@ import {Deposit} from "@app/generated-api/model/deposit.model"; import {SharedAbstractListRoutable} from "@app/shared/components/routables/shared-abstract-list/shared-abstract-list.routable"; import {FieldTypeEnum} from "@app/shared/enums/field-type.enum"; import {LocalStateEnum} from "@app/shared/enums/local-state.enum"; +import {appAuthorizedOrganizationalUnitNameSpace} from "@app/stores/authorized-organizational-unit/app-authorized-organizational-unit.action"; +import {AppAuthorizedOrganizationalUnitState} from "@app/stores/authorized-organizational-unit/app-authorized-organizational-unit.state"; import {TranslateService} from "@ngx-translate/core"; import {Store} from "@ngxs/store"; import { OrderEnum, + ResourceNameSpace, TRANSLATE, } from "solidify-frontend"; @@ -29,6 +32,9 @@ export class DepositListRoutable extends SharedAbstractListRoutable<DepositExten readonly KEY_REFRESH_BUTTON: string = TRANSLATE("deposit.refresh"); readonly KEY_BACK_BUTTON: string | undefined = undefined; + appAuthorizedOrganizationalUnitNameSpace: ResourceNameSpace = appAuthorizedOrganizationalUnitNameSpace; + appAuthorizedOrganizationalUnitState: typeof AppAuthorizedOrganizationalUnitState = AppAuthorizedOrganizationalUnitState; + constructor(protected store: Store, protected _changeDetector: ChangeDetectorRef, private translate: TranslateService) { @@ -52,10 +58,13 @@ export class DepositListRoutable extends SharedAbstractListRoutable<DepositExten { field: "organizationalUnit.name" as any, header: TRANSLATE("deposit.table.header.organizationalUnit"), - type: FieldTypeEnum.string, + type: FieldTypeEnum.searchableSingleSelect, order: OrderEnum.none, - // isFilterable: true, + isFilterable: true, // isSortable: true, + resourceNameSpace: this.appAuthorizedOrganizationalUnitNameSpace, + resourceState: this.appAuthorizedOrganizationalUnitState as any, + filterableField: "organizationalUnitId", }, { field: "publicationDate", @@ -84,7 +93,7 @@ export class DepositListRoutable extends SharedAbstractListRoutable<DepositExten { field: "status", header: TRANSLATE("deposit.table.header.status"), - type: FieldTypeEnum.string, + type: FieldTypeEnum.singleSelect, order: OrderEnum.none, isFilterable: true, isSortable: true, diff --git a/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.html b/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.html index 1cd06e97c..561bc60dd 100644 --- a/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.html +++ b/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.html @@ -12,7 +12,6 @@ [sortOrder]="defaultSortColumn ? defaultSortColumn?.order : undefined" > <ng-template pTemplate="header" - let-columns > <tr> <th *ngFor="let col of columns" @@ -31,62 +30,78 @@ class="data-table-filter" > <ng-container *ngIf="col.isFilterable"> - <div *ngSwitchCase="fieldTypeString" - class="filter-text" + + <div *ngSwitchCase="fieldTypeEnum.searchableSingleSelect" + class="filter-searchable-single-select" > - <ng-template [ngIf]="col.filterEnum" - [ngIfElse]="inputFilter" + <dlcm-shared-searchable-single-select solidifyValidation + [resourceNameSpace]="col.resourceNameSpace" + [state]="col.resourceState" + [ngModel]="getValue(col)" + [labelKey]="'name'" + [valueKey]="'resId'" + (valueChange)="onValueChange(col, $event)" > - <mat-select #select - [class.no-value]="!select.value" - (selectionChange)="onValueChange(col, $event.value)" + </dlcm-shared-searchable-single-select> + </div> + + <div *ngSwitchCase="fieldTypeEnum.singleSelect" + class="filter-single-select" + > + <mat-select #select + [class.no-value]="!select.value" + [value]="getValue(col)" + (selectionChange)="onValueChange(col, $event.value)" + > + <mat-option>{{'table.filter.all' | translate}}</mat-option> + <mat-option *ngFor="let enum of col.filterEnum" + [value]="enum.key" > - <mat-option>{{'table.filter.all' | translate}}</mat-option> - <mat-option *ngFor="let enum of col.filterEnum" - [value]="enum.key" + <ng-template [ngIf]="col.translate" + [ngIfElse]="noTranslate" > - <ng-template [ngIf]="col.translate" - [ngIfElse]="noTranslate" - > - {{enum.value | translate}} - </ng-template> - <ng-template #noTranslate> - {{enum.value}} - </ng-template> - </mat-option> - </mat-select> - <button mat-button - *ngIf="select.value" - matSuffix - mat-icon-button - aria-label="Clear" - (click)="select.value='';onValueChange(col, select.value)" - > - <fa-icon matSuffix - icon="times" - ></fa-icon> - </button> - </ng-template> + {{enum.value | translate}} + </ng-template> + <ng-template #noTranslate> + {{enum.value}} + </ng-template> + </mat-option> + </mat-select> + <button mat-button + *ngIf="select.value" + matSuffix + mat-icon-button + aria-label="Clear" + (click)="select.value='';onValueChange(col, select.value)" + > + <fa-icon matSuffix + icon="times" + ></fa-icon> + </button> + </div> - <ng-template #inputFilter> - <input type="text" - #filter - (keyup)="onValueChange(col, $event.target.value)" - [value]="getValue(col.field)" - > - <button mat-button - *ngIf="filter.value" - matSuffix - mat-icon-button - aria-label="Clear" - (click)="filter.value='';onValueChange(col, filter.value)" - > - <fa-icon matSuffix - icon="times" - ></fa-icon> - </button> - </ng-template> + <div *ngSwitchCase="fieldTypeEnum.string" + class="filter-string" + > + <input class="filter-input" + type="text" + #filter + (keyup)="onValueChange(col, $event.target.value)" + [value]="getValue(col)" + > + <button mat-button + *ngIf="filter.value" + matSuffix + mat-icon-button + aria-label="Clear" + (click)="filter.value='';onValueChange(col, filter.value)" + > + <fa-icon matSuffix + icon="times" + ></fa-icon> + </button> </div> + </ng-container> </th> </tr> diff --git a/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.scss b/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.scss index 2e41ad075..092193cdb 100644 --- a/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.scss +++ b/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.scss @@ -113,20 +113,36 @@ $inputGroupTextColor: #444444; .data-table-filter { padding: 4px !important; + $height-input: 30px; + + @mixin input-filter { + background-color: $primary-color-lighter !important; + border: 0; + border-radius: 5px; + padding: 6px 30px 6px 10px; + width: 100%; + color: $white; + height: $height-input; + } + - .filter-text { + .filter-searchable-single-select, + .filter-single-select, + .filter-string { position: relative; - input, mat-select { - background-color: $primary-color-lighter !important; - border: 0; - border-radius: 5px; - padding: 6px 30px 6px 10px; - width: 100%; - color: $white; - height: 30px; + .filter-input, mat-select { + @include input-filter; } + button { + position: absolute; + top: -6px; + right: -5px; + } + } + + .filter-single-select { mat-select { .mat-select-value { color: $white; @@ -142,13 +158,41 @@ $inputGroupTextColor: #444444; padding-right: 6px; } } + } - button { - position: absolute; - top: -6px; + .filter-searchable-single-select { + height: $height-input; + + form input { + @include input-filter; + text-align: left; + font-weight: initial; + margin: 0 !important; + } + + .mat-form-field-wrapper { + padding: 0; + + .mat-form-field-infix { + padding: 0; + border: 0; + } + } + + .mat-form-field-underline { + display: none; + } + + .clear { + top: -32px; right: -5px; + width: 40px; + height: 40px; + + .mat-button-wrapper { + color: white; + } } } } } - diff --git a/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.ts b/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.ts index 7ba9db52a..eb40cf17b 100644 --- a/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.ts +++ b/src/app/shared/components/presentationals/shared-data-table/shared-data-table.presentational.ts @@ -102,7 +102,9 @@ export class SharedDataTablePresentational<T> extends SharedAbstractPresentation isDatasPresent: boolean; - fieldTypeString: FieldTypeEnum = FieldTypeEnum.string; + get fieldTypeEnum(): typeof FieldTypeEnum { + return FieldTypeEnum; + } private readonly _SEPARATOR: string = "."; @@ -175,21 +177,26 @@ export class SharedDataTablePresentational<T> extends SharedAbstractPresentation } onValueChange(col: DataTableColumns, value: string): void { + const fieldName = this.getFilterableField(col); let queryParameters = ObjectUtil.clone(this.queryParameters); if (isEmptyString(value) || isNullOrUndefined(value)) { - queryParameters.search.searchItems.delete(col.field); + queryParameters.search.searchItems.delete(fieldName); } else { - MapUtil.addOrUpdate(queryParameters.search.searchItems, col.field, value); + MapUtil.addOrUpdate(queryParameters.search.searchItems, fieldName, value); } queryParameters = QueryParametersUtil.resetToFirstPage(queryParameters); this._queryParametersBS.next(queryParameters); } - getValue(field: string): string { - const value = this.queryParameters.search.searchItems.get(field); - if (value === null || value === undefined || value === "") { + getValue(col: DataTableColumns): string | null { + const value = this.queryParameters.search.searchItems.get(this.getFilterableField(col)); + if (value === null || value === undefined || value === StringUtil.stringEmpty) { return null; } return value; } + + private getFilterableField(col: DataTableColumns): string { + return isNullOrUndefined(col.filterableField) ? col.field : col.filterableField; + } } diff --git a/src/app/shared/components/presentationals/shared-searchable-abstract-content/shared-searchable-abstract-content.presentational.scss b/src/app/shared/components/presentationals/shared-searchable-abstract-content/shared-searchable-abstract-content.presentational.scss index 0e19a2b33..5a247948a 100644 --- a/src/app/shared/components/presentationals/shared-searchable-abstract-content/shared-searchable-abstract-content.presentational.scss +++ b/src/app/shared/components/presentationals/shared-searchable-abstract-content/shared-searchable-abstract-content.presentational.scss @@ -15,7 +15,8 @@ $min-height-result: $height-overlay/2 + $height-input; transform-origin: 50% 20.5px 0px; font-size: 14px; opacity: 1; - min-width: calc(100% + 32px); + //min-width: calc(100% + 32px); + min-width: 266px; transform: scaleY(1); height: $height-overlay; diff --git a/src/app/shared/components/presentationals/shared-searchable-single-select/shared-searchable-single-select.presentational.ts b/src/app/shared/components/presentationals/shared-searchable-single-select/shared-searchable-single-select.presentational.ts index 190ced136..76d9b75df 100644 --- a/src/app/shared/components/presentationals/shared-searchable-single-select/shared-searchable-single-select.presentational.ts +++ b/src/app/shared/components/presentationals/shared-searchable-single-select/shared-searchable-single-select.presentational.ts @@ -94,6 +94,7 @@ export class SharedSearchableSingleSelectPresentational extends SharedAbstractPr floatLabel: FloatLabelType; alreadyInit: boolean = false; + noFormControl: boolean; get formValidationHelper(): typeof FormValidationHelper { return FormValidationHelper; @@ -113,6 +114,10 @@ export class SharedSearchableSingleSelectPresentational extends SharedAbstractPr } ngOnInit(): void { + if (isNullOrUndefined(this.formControl)) { + this.noFormControl = true; + this.formControl = this._fb.control(undefined); + } if (isTrue(this.alreadyInit)) { // When validator is updated, the ngOnInit is call a new time // This hack prevent this to happen (case with deposit license validator impacted by access level @@ -181,6 +186,10 @@ export class SharedSearchableSingleSelectPresentational extends SharedAbstractPr } writeValue(value: string[]): void { + if (!isNullOrUndefined(value) && this.noFormControl) { + this.formControl.setValue(value); + this._dispatchGetActionToRetrieveLabel(); + } } openOverlay(): void { @@ -208,6 +217,7 @@ export class SharedSearchableSingleSelectPresentational extends SharedAbstractPr tap(value => { const valueKey = value[this.valueKey]; this.propagateChange(valueKey); + this.formControl.setValue(valueKey); this._valueBS.next(valueKey); this.formLabel.get(this.formDefinition.value).setValue(this.labelCallback(value)); this.computeFloatLabel(); @@ -259,6 +269,7 @@ export class SharedSearchableSingleSelectPresentational extends SharedAbstractPr clearValue($event: MouseEvent): void { this.formControl.setValue(""); this.formLabel.get(this.formDefinition.value).setValue(null); + this._valueBS.next(undefined); $event.stopPropagation(); this.computeFloatLabel(); } diff --git a/src/app/shared/enums/field-type.enum.ts b/src/app/shared/enums/field-type.enum.ts index fe1d6ea28..c8f751315 100644 --- a/src/app/shared/enums/field-type.enum.ts +++ b/src/app/shared/enums/field-type.enum.ts @@ -3,4 +3,6 @@ export enum FieldTypeEnum { date, datetime, number, + searchableSingleSelect, + singleSelect, } diff --git a/src/app/shared/models/data-table-columns.model.ts b/src/app/shared/models/data-table-columns.model.ts index ad9fa0bf5..c563a452f 100644 --- a/src/app/shared/models/data-table-columns.model.ts +++ b/src/app/shared/models/data-table-columns.model.ts @@ -2,6 +2,8 @@ import {FieldTypeEnum} from "@app/shared/enums/field-type.enum"; import { KeyValue, OrderEnum, + ResourceNameSpace, + ResourceState, } from "solidify-frontend"; export interface DataTableColumns<T = any> { @@ -14,4 +16,7 @@ export interface DataTableColumns<T = any> { translate?: boolean; isSortable?: boolean; isFilterable?: boolean; + filterableField?: keyof T & string; + resourceNameSpace?: ResourceNameSpace; + resourceState?: ResourceState<any>; } -- GitLab