plan listing > expandable search field, fix search view bug, small code refactor

This commit is contained in:
mchouliara 2024-08-29 14:25:27 +03:00
parent 759cfd9a77
commit 17b6a39c74
6 changed files with 124 additions and 90 deletions

View File

@ -1,6 +1,6 @@
<a class="col-auto d-flex pointer" (click)="onClose()"><span class="ml-auto mt-3 material-icons clear-icon">clear</span></a>
<app-plan-filter-component
[filterFormGroup]="data.filterForm"
[filterFormGroup]="formGroup"
[referencesWithTypeItems]="data.referencesWithTypeItems"
[isPublic]="data.isPublic"
[hasSelectedTenant]="data.hasSelectedTenant"

View File

@ -1,9 +1,10 @@
import { Inject, Component, ViewChild, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { UntypedFormGroup } from '@angular/forms';
import { FormGroup, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { AnalyticsService } from '@app/core/services/matomo/analytics-service';
import { PlanFilterComponent } from '../plan-filter.component';
import { PlanFilterComponent, PlanListingFilterForm, PlanListingFilters } from '../plan-filter.component';
import { ReferencesWithType } from '@app/core/query/description.lookup';
import { PlanLookup } from '@app/core/query/plan.lookup';
@Component({
selector: 'plan-filter-dialog-component',
@ -15,20 +16,35 @@ export class PlanFilterDialogComponent implements OnInit {
@ViewChild(PlanFilterComponent, { static: true }) filter: PlanFilterComponent;
formGroup: FormGroup<PlanListingFilterForm>;
constructor(
public dialogRef: MatDialogRef<PlanFilterDialogComponent>,
private analyticsService: AnalyticsService,
@Inject(MAT_DIALOG_DATA) public data: {
isPublic: boolean,
hasSelectedTenant: boolean,
filterForm: UntypedFormGroup,
lookup: PlanLookup,
//filterForm: UntypedFormGroup,
referencesWithTypeItems: ReferencesWithType[],
}) { }
}) {
this.formGroup = this._buildFormFromLookup(data?.lookup)
}
ngOnInit() {
this.analyticsService.trackPageView(AnalyticsService.PlanFilterDialog);
}
private _buildFormFromLookup(lookup: PlanLookup): FormGroup<PlanListingFilterForm> {
return (new UntypedFormBuilder()).group({
status: [lookup.statuses?.length > 0 ? lookup.statuses[0] : null],
viewOnlyTenant: [lookup.tenantSubQuery?.codes?.length > 0 ? true : false],
descriptionTemplates: lookup.planDescriptionTemplateSubQuery?.descriptionTemplateGroupIds ? [lookup.planDescriptionTemplateSubQuery?.descriptionTemplateGroupIds] : [],
planBlueprints: lookup.planBlueprintSubQuery?.ids ? [lookup.planBlueprintSubQuery?.ids]: [],
role: lookup.planUserSubQuery?.userRoles ? lookup.planUserSubQuery?.userRoles[0] : null,
});
}
onNoClick(): void {
this.dialogRef.close();
}
@ -38,7 +54,7 @@ export class PlanFilterDialogComponent implements OnInit {
}
onFilterChanged(formGroup: UntypedFormGroup) {
this.dialogRef.close(formGroup);
this.dialogRef.close(formGroup.value as PlanListingFilters);
}
}

View File

@ -1,5 +1,5 @@
import { Component, Input, OnInit, Output, EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { AbstractControl, FormArray, FormControl, FormGroup, UntypedFormArray, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { BaseCriteriaComponent } from '@app/ui/misc/criteria/base-criteria.component';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
import { TranslateService } from '@ngx-translate/core';
@ -32,7 +32,7 @@ export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit
@Input() isPublic: boolean;
@Input() hasSelectedTenant: boolean;
@Input() referencesWithTypeItems: ReferencesWithType[];
@Input() filterFormGroup: UntypedFormGroup;
@Input() filterFormGroup: FormGroup<PlanListingFilterForm>;
@Output() filterChanged: EventEmitter<any> = new EventEmitter();
status = PlanStatusEnum;
@ -196,3 +196,21 @@ export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit
});
}
}
export interface PlanListingFilters {
status: PlanStatusEnum,
viewOnlyTenant: boolean,
descriptionTemplates: Guid[],
planBlueprints: Guid[],
role: Guid,
references: ReferencesWithType[]
}
export interface PlanListingFilterForm {
status: FormControl<PlanStatusEnum>,
viewOnlyTenant: FormControl<boolean>,
descriptionTemplates: FormControl<Guid[]>,
planBlueprints: FormControl<Guid[]>,
role: FormControl<Guid>,
references: FormArray<any>
}

View File

@ -1,7 +1,7 @@
<div class="main-content listing-main-container h-100">
<div class="container-fluid">
<div class="row">
<div *ngIf="hasLoadedListingItems && !hasListingItems && !hasFilters" class="col-12 card mt-0">
<div *ngIf="!isLoading && !hasListingItems && !hasFilters" class="col-12 card mt-0">
<div class="card-content info-text mb-0">
<p>{{'PLAN-LISTING.TEXT-INFO' | translate}}</p>
<p class="mt-4 pt-2">{{'PLAN-LISTING.TEXT-INFO-QUESTION' | translate}} <a class="zenodo-link" href="https://zenodo.org/communities/liber-plan-cat/?page=1&size=20" target="_blank">{{'PLAN-LISTING.LINK-ZENODO' | translate}}</a> {{'PLAN-LISTING.GET-IDEA' | translate}}</p>
@ -11,18 +11,18 @@
</div>
</div>
</div>
<div *ngIf="hasLoadedListingItems && (hasListingItems || hasFilters)" class="col-12">
<div class="col-12">
<app-navigation-breadcrumb />
</div>
<div *ngIf="!versionsModeEnabled && hasLoadedListingItems && (hasListingItems || hasFilters) && !isPublic" class="filter-btn" [style.right]="dialog.getDialogById('filters') ? '446px' : '0px'" [style.width]="listingItems.length > 2 ? '57px' : '37px'" (click)="openFiltersDialog()">
<div *ngIf="!versionsModeEnabled && !isLoading && (hasListingItems || hasFilters) && !isPublic" class="filter-btn" [style.right]="dialog.getDialogById('filters') ? '446px' : '0px'" [style.width]="listingItems.length > 2 ? '57px' : '37px'" (click)="openFiltersDialog()">
<button mat-raised-button class="p-0" [matBadge]="filtersCount" [matBadgeHidden]="filtersCount === 0" matBadgePosition="before">
<mat-icon class="mr-4 filter-icon">filter_alt</mat-icon>
</button>
</div>
</div>
<div>
<div *ngIf="hasLoadedListingItems" class="listing row pb-2">
<div *ngIf="hasListingItems || hasFilters" class="col-md-12">
<div class="listing row pb-2">
<div class="col-md-12">
<div class="row pt-4">
<!-- Sort by -->
<div class="col-auto d-flex align-items-center order-1">
@ -50,36 +50,40 @@
<span *ngIf="!isPublic" class="center-content mb-1 mb-xl-4" (click)="restartTour()">{{ 'GENERAL.ACTIONS.TAKE-A-TOUR'| translate }}</span>
</div>
<div class="col-12 col-xl-auto">
<mat-form-field class="search-form w-100" floatLabel="never">
<app-text-filter [(value)]=lookup.like (valueChange)="controlModified()" />
<!-- <mat-form-field class="search-form w-100" floatLabel="never">
<mat-icon matSuffix>search</mat-icon>
<input matInput placeholder="{{'GENERAL.CRITERIA.LIKE'| translate}}" name="likeCriteria" [formControl]="formGroup.get('like')">
<mat-error *ngIf="formGroup.get('like').hasError('backendError')">{{formGroup.get('like').getError('backendError').message}}</mat-error>
</mat-form-field>
</mat-form-field> -->
</div>
</div>
</div>
</div>
</div>
<div class="col-md-12 col-sm-12 col-md-9">
<div *ngFor="let item of listingItems; let i = index">
<app-plan-listing-item-component
[showDivider]="i != (listingItems.length - 1)"
[plan]="item"
[isPublic]="isPublic"
[tenants]="tenants"
></app-plan-listing-item-component>
</div>
<div *ngIf="hasListingItems && this.lookup?.page?.offset < this.totalCount - 1 && this.pageSize < this.totalCount - 1" class="d-flex justify-content-center">
<button type="button" class="btn-load-more" (click)="loadMore()">{{'GENERAL.ACTIONS.LOAD-MORE' | translate}}</button>
</div>
</div>
<div *ngIf="!hasListingItems && !hasFilters" class="col-md-12 d-flex justify-content-center pt-4 mt-4 mb-4 pb-4">
<span class="empty-list">{{'PLAN-LISTING.EMPTY-LIST' | translate}}</span>
</div>
<div *ngIf="!hasListingItems && hasFilters" class="col-md-12 d-flex justify-content-center pt-4 mt-4 mb-4 pb-4">
<span class="empty-list">{{'PLAN-LISTING.FILTERS.NO-ITEMS-FOUND' | translate}}</span>
</div>
</div>
@if(!isLoading){
<div class="col-md-12 col-sm-12 col-md-9">
<div *ngFor="let item of listingItems; let i = index">
<app-plan-listing-item-component
[showDivider]="i != (listingItems.length - 1)"
[plan]="item"
[isPublic]="isPublic"
[tenants]="tenants"
></app-plan-listing-item-component>
</div>
<div *ngIf="hasListingItems && this.lookup?.page?.offset < this.totalCount - 1 && this.pageSize < this.totalCount - 1" class="d-flex justify-content-center">
<button type="button" class="btn-load-more" (click)="loadMore()">{{'GENERAL.ACTIONS.LOAD-MORE' | translate}}</button>
</div>
</div>
<div *ngIf="!hasListingItems && !hasFilters" class="col-md-12 d-flex justify-content-center pt-4 mt-4 mb-4 pb-4">
<span class="empty-list">{{'PLAN-LISTING.EMPTY-LIST' | translate}}</span>
</div>
<div *ngIf="!hasListingItems && hasFilters" class="col-md-12 d-flex justify-content-center pt-4 mt-4 mb-4 pb-4">
<span class="empty-list">{{'PLAN-LISTING.FILTERS.NO-ITEMS-FOUND' | translate}}</span>
</div>
}
</div>
</div>
</div>
</div>

View File

@ -1,6 +1,6 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { ActivatedRoute, Params, Router } from '@angular/router';
@ -25,9 +25,8 @@ import { HttpErrorHandlingService } from '@common/modules/errors/error-handling/
import { PageLoadEvent, SortDirection } from '@common/modules/hybrid-listing/hybrid-listing.component';
import { TranslateService } from '@ngx-translate/core';
import { NgDialogAnimationService } from "ng-dialog-animation";
import { debounceTime, takeUntil, tap } from 'rxjs/operators';
import { takeUntil, tap } from 'rxjs/operators';
import { nameof } from 'ts-simple-nameof';
import { PlanFilterDialogComponent } from './filtering/plan-filter-dialog/plan-filter-dialog.component';
import { BaseListingComponent } from '@common/base/base-listing-component';
import { QueryResult } from '@common/model/query-result';
import { Observable } from 'rxjs';
@ -41,7 +40,8 @@ import { BaseHttpParams } from '@common/http/base-http-params';
import { InterceptorType } from '@common/http/interceptors/interceptor-type';
import { PrincipalService } from '@app/core/services/http/principal.service';
import { BreadcrumbService } from '@app/ui/misc/breadcrumb/breadcrumb.service';
import { PlanStatusEnum } from '@app/core/common/enum/plan-status';
import { PlanFilterDialogComponent } from './filtering/plan-filter-dialog/plan-filter-dialog.component';
import { PlanListingFilters } from './filtering/plan-filter.component';
@Component({
selector: 'app-plan-listing-component',
@ -58,7 +58,7 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
totalCount: number;
listingItems: any[] = [];
isPublic: boolean = false;
hasLoadedListingItems: boolean = false;
isLoading: boolean;
protected ITEMS_PER_PAGE = 5;
pageSize: number = 5;
filtersCount: number;
@ -168,9 +168,9 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
});
}
this.formGroup.get('like').valueChanges
.pipe(takeUntil(this._destroyed), debounceTime(500))
.subscribe(x => this.controlModified());
// this.formGroup.get('like').valueChanges
// .pipe(takeUntil(this._destroyed), debounceTime(500))
// .subscribe(x => this.controlModified());
}
public dashboardTour: GuidedTour = {
@ -201,10 +201,13 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
this.scrollbar = this.hasScrollbar();
}
protected loadListing(): Observable<QueryResult<BasePlan>> {
this.isLoading = true;
if (this.isPublic) {
return this.planService.publicQuery(this.lookup).pipe(takeUntil(this._destroyed))
.pipe(tap(result => {
this.isLoading = false;
if (this.versionsModeEnabled) {
const latestVersionPlan: BasePlan = this._getLatestVersion(result.items);
this.breadcrumbService.addIdResolvedValue(this.groupId.toString(), latestVersionPlan.label);
@ -214,11 +217,11 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
this.totalCount = result.count;
if (this.lookup?.page?.offset === 0) this.listingItems = [];
this.listingItems.push(...result.items);
this.hasLoadedListingItems = true;
}));
} else {
return this.planService.query(this.lookup).pipe(takeUntil(this._destroyed))
.pipe(tap(result => {
this.isLoading = false;
if (this.versionsModeEnabled) {
const latestVersionPlan: BasePlan = this._getLatestVersion(result.items);
this.breadcrumbService.addIdResolvedValue(this.groupId.toString(), latestVersionPlan.label);
@ -229,7 +232,6 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
if (this.lookup?.page?.offset === 0) this.listingItems = [];
const plans = this._filterPlan([...result.items]);
this.listingItems.push(...plans);
this.hasLoadedListingItems = true;
}));
}
}
@ -298,13 +300,13 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
}
controlModified(): void {
this.lookup.like = this.formGroup.get("like").value != '' ? this.formGroup.get('like').value : null;
//this.lookup.like = this.lookup.like != '' ? this.lookup.like : null;
this.lookup.page = { size: this.pageSize, offset: 0 };
this.filterChanged(this.lookup, true);
}
openFiltersDialog(): void {
let dialogRef = this.dialog.open(PlanFilterDialogComponent, {
let dialogRef: MatDialogRef<PlanFilterDialogComponent, PlanListingFilters> = this.dialog.open(PlanFilterDialogComponent, {
width: '456px',
height: '100%',
id: 'filters',
@ -314,7 +316,8 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
data: {
isPublic: this.isPublic ?? true,
hasSelectedTenant: this.authService.selectedTenant() != 'default',
filterForm: this._buildFormFromLookup(this.lookup),
lookup: this.lookup,
//filterForm: this._buildFormFromLookup(this.lookup),
referencesWithTypeItems: this.referenceFilters ?? [],
}
});
@ -326,11 +329,11 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
});
}
updateDataFn(filterForm: UntypedFormGroup): void {
this.referenceFilters = this._patchReferenceFiltersFromForm(filterForm);
this.lookup = this._patchLookupFromForm(this.lookup, filterForm);
updateDataFn(filters: PlanListingFilters): void {
this.referenceFilters = this._patchReferenceFilters(filters);
this._patchLookupFromFilters(filters);
this.filtersCount = this._countFilters(this.lookup);
this.hasLoadedListingItems = false;
this.isLoading = false;
this.filterChanged(this.lookup)
}
@ -410,71 +413,62 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
}
}
private _patchLookupFromForm(lookup: PlanLookup, formGroup: UntypedFormGroup): PlanLookup {
if (!formGroup) return;
private _patchLookupFromFilters(filters: PlanListingFilters): PlanLookup {
if (!filters) return;
lookup.statuses = formGroup.get("status")?.value !== null ? [formGroup.get("status")?.value] : null;
this.lookup.statuses = filters.status !== null ? [filters.status] : [];
// Tenants
let viewOnlyTenant = formGroup.get("viewOnlyTenant")?.value ?? false;
let viewOnlyTenant = filters.viewOnlyTenant ?? false;
if (viewOnlyTenant) {
let tenant = this.tenants?.find(t => t.code && t.code?.toString() == this.authService.selectedTenant());
if (tenant && tenant?.code) {
lookup.tenantSubQuery = PlanFilterService.initializeTenantLookup();
lookup.tenantSubQuery.codes = [tenant.code]
this.lookup.tenantSubQuery = PlanFilterService.initializeTenantLookup();
this.lookup.tenantSubQuery.codes = [tenant.code]
}
else lookup.tenantSubQuery = null;
} else lookup.tenantSubQuery = null;
else this.lookup.tenantSubQuery = null;
} else this.lookup.tenantSubQuery = null;
// Description Templates
let descriptionTemplates = formGroup.get("descriptionTemplates")?.value ?? null;
let descriptionTemplates = filters.descriptionTemplates ?? null;
if (descriptionTemplates && descriptionTemplates?.length > 0) {
lookup.planDescriptionTemplateSubQuery = PlanFilterService.initializePlanDescriptionTemplateLookup();
lookup.planDescriptionTemplateSubQuery.descriptionTemplateGroupIds = descriptionTemplates;
} else lookup.planDescriptionTemplateSubQuery = null;
this.lookup.planDescriptionTemplateSubQuery = PlanFilterService.initializePlanDescriptionTemplateLookup();
this.lookup.planDescriptionTemplateSubQuery.descriptionTemplateGroupIds = descriptionTemplates;
} else this.lookup.planDescriptionTemplateSubQuery = null;
// Blueprints
let planBlueprints = formGroup.get("planBlueprints")?.value ?? null;
let planBlueprints = filters.planBlueprints ?? null;
if (planBlueprints && planBlueprints?.length > 0) {
lookup.planBlueprintSubQuery = PlanFilterService.initializePlanBlueprintLookup();
lookup.planBlueprintSubQuery.ids = planBlueprints;
} else lookup.planBlueprintSubQuery = null;
this.lookup.planBlueprintSubQuery = PlanFilterService.initializePlanBlueprintLookup();
this.lookup.planBlueprintSubQuery.ids = planBlueprints;
} else this.lookup.planBlueprintSubQuery = null;
// plans
let roles = formGroup.get("role")?.value !== null ? [formGroup.get("role")?.value] : null;
let roles = filters.role !== null ? [filters.role] : null;
if (roles && roles?.length > 0) {
lookup.planUserSubQuery = PlanFilterService.initializePlanUserLookup();
lookup.planUserSubQuery.userRoles = roles;
} else lookup.planUserSubQuery = null;
this.lookup.planUserSubQuery = PlanFilterService.initializePlanUserLookup();
this.lookup.planUserSubQuery.userRoles = roles;
} else this.lookup.planUserSubQuery = null;
let references: Guid[] = formGroup.get("references")?.value
let references: Guid[] = filters.references
?.filter((reference: ReferencesWithType) => reference.referenceTypeId != null && reference.referenceIds?.length > 0)
?.flatMap((referencesWithType: ReferencesWithType) => referencesWithType.referenceIds) as Guid[];
if (references && references?.length > 0) {
lookup.planReferenceSubQuery = PlanFilterService.initializePlanReferenceLookup();
lookup.planReferenceSubQuery.referenceIds = references;
} else lookup.planReferenceSubQuery = null;
this.lookup.planReferenceSubQuery = PlanFilterService.initializePlanReferenceLookup();
this.lookup.planReferenceSubQuery.referenceIds = references;
} else this.lookup.planReferenceSubQuery = null;
return lookup;
return this.lookup;
}
_patchReferenceFiltersFromForm(formGroup: UntypedFormGroup): ReferencesWithType[] {
return formGroup?.get("references")?.value?.filter(( referencesWithType: ReferencesWithType ) => referencesWithType != null && referencesWithType.referenceIds?.length > 0) ?? null;
_patchReferenceFilters(filters: PlanListingFilters): ReferencesWithType[] {
return filters?.references?.filter(( referencesWithType: ReferencesWithType ) => referencesWithType != null && referencesWithType.referenceIds?.length > 0) ?? null;
}
private _buildFormFromLookup(lookup: PlanLookup): UntypedFormGroup {
return (new UntypedFormBuilder()).group({
like: lookup.like ?? null,
status: [lookup.statuses?.length > 0 ? lookup.statuses[0] : null],
viewOnlyTenant: [lookup.tenantSubQuery?.codes?.length > 0 ? true : false],
descriptionTemplates: lookup.planDescriptionTemplateSubQuery?.descriptionTemplateGroupIds ? [lookup.planDescriptionTemplateSubQuery?.descriptionTemplateGroupIds] : [],
planBlueprints: lookup.planBlueprintSubQuery?.ids ? [lookup.planBlueprintSubQuery?.ids]: [],
role: lookup.planUserSubQuery?.userRoles ? lookup.planUserSubQuery?.userRoles[0] : null,
});
}
private _countFilters(lookup: PlanLookup): number {
let count = 0;

View File

@ -12,6 +12,7 @@ import { ClonePlanDialogModule } from '../clone-dialog/plan-clone-dialog.module'
import { PlanListingComponent } from './plan-listing.component';
import { PlanListingItemComponent } from './listing-item/plan-listing-item.component';
import { PlanListingRoutingModule } from './plan-listing.routing';
import { TextFilterModule } from '@common/modules/text-filter/text-filter.module';
@NgModule({
imports: [
@ -22,7 +23,8 @@ import { PlanListingRoutingModule } from './plan-listing.routing';
ClonePlanDialogModule,
NewVersionPlanDialogModule,
PlanInvitationDialogModule,
PlanListingRoutingModule
PlanListingRoutingModule,
TextFilterModule
],
declarations: [
PlanListingComponent,