refactor plan listing filters, add css styles for buttons (WIP), add typing to base-criteria

This commit is contained in:
mchouliara 2024-08-29 17:57:03 +03:00
parent f6453bfe6a
commit 99ca662398
8 changed files with 214 additions and 104 deletions

View File

@ -29,7 +29,7 @@ import { nameof } from 'ts-simple-nameof';
templateUrl: './description-filter.component.html',
styleUrls: ['./description-filter.component.scss']
})
export class DescriptionFilterComponent extends BaseCriteriaComponent implements OnInit, OnChanges {
export class DescriptionFilterComponent extends BaseCriteriaComponent<any> implements OnInit, OnChanges {
@Input() status;
@Input() isPublic: boolean;

View File

@ -1,13 +1,13 @@
import { OnInit, Directive } from '@angular/core';
import { AbstractControl, UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { AbstractControl, FormGroup, UntypedFormArray, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { BaseComponent } from '@common/base/base.component';
import { ValidationErrorModel } from '@common/forms/validation/error-model/validation-error-model';
@Directive()
export class BaseCriteriaComponent extends BaseComponent implements OnInit {
export class BaseCriteriaComponent<T extends { [K in keyof T]: AbstractControl<any, any>; }> extends BaseComponent implements OnInit {
public refreshCallback: Function = null;
public formGroup: UntypedFormGroup = null;
public formGroup: FormGroup<T> = null;
constructor(
public validationErrorModel: ValidationErrorModel,

View File

@ -1,7 +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]="formGroup"
[referencesWithTypeItems]="data.referencesWithTypeItems"
[filters]="filters"
[isPublic]="data.isPublic"
[hasSelectedTenant]="data.hasSelectedTenant"
(filterChanged)="onFilterChanged($event)"

View File

@ -1,8 +1,8 @@
import { Inject, Component, ViewChild, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { FormGroup, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { UntypedFormGroup } from '@angular/forms';
import { AnalyticsService } from '@app/core/services/matomo/analytics-service';
import { PlanFilterComponent, PlanListingFilterForm, PlanListingFilters } from '../plan-filter.component';
import { PlanFilterComponent, PlanListingFilters } from '../plan-filter.component';
import { ReferencesWithType } from '@app/core/query/description.lookup';
import { PlanLookup } from '@app/core/query/plan.lookup';
@ -15,8 +15,7 @@ import { PlanLookup } from '@app/core/query/plan.lookup';
export class PlanFilterDialogComponent implements OnInit {
@ViewChild(PlanFilterComponent, { static: true }) filter: PlanFilterComponent;
formGroup: FormGroup<PlanListingFilterForm>;
filters: PlanListingFilters;
constructor(
public dialogRef: MatDialogRef<PlanFilterDialogComponent>,
@ -27,21 +26,22 @@ export class PlanFilterDialogComponent implements OnInit {
lookup: PlanLookup,
referencesWithTypeItems: ReferencesWithType[],
}) {
this.formGroup = this._buildFormFromLookup(data?.lookup)
this.filters = this._buildPlanFilters(data?.lookup, data?.referencesWithTypeItems)
}
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,
});
private _buildPlanFilters(lookup: PlanLookup, references: ReferencesWithType[]): PlanListingFilters {
return {
status: lookup.statuses?.[0] ?? null,
viewOnlyTenant: lookup.tenantSubQuery?.codes?.length > 0,
descriptionTemplates: lookup.planDescriptionTemplateSubQuery?.descriptionTemplateGroupIds ? lookup.planDescriptionTemplateSubQuery?.descriptionTemplateGroupIds : [],
planBlueprints: lookup.planBlueprintSubQuery?.ids ?? [],
role: lookup.planUserSubQuery?.userRoles?.[0] ?? null,
references: references ?? []
}
}
onNoClick(): void {
@ -52,8 +52,8 @@ export class PlanFilterDialogComponent implements OnInit {
this.dialogRef.close();
}
onFilterChanged(formGroup: UntypedFormGroup) {
this.dialogRef.close(formGroup.value as PlanListingFilters);
onFilterChanged(filters: PlanListingFilters) {
this.dialogRef.close(filters);
}
}

View File

@ -65,7 +65,7 @@
<!-- Reference Types -->
<div class="col-10 mb-1">
<h6 class="category-title">{{'DESCRIPTION-LISTING.FILTERS.REFERENCE-TYPES.NAME' | translate }}</h6>
<ng-container *ngFor="let referenceForm of filterFormGroup.get('references')?.controls; let i=index">
<ng-container *ngFor="let referenceForm of formGroup.get('references')?.controls; let i=index">
<div class="row">
<div class="col-12">
<mat-form-field class="w-100" *ngIf="referenceForm.get('referenceTypeId')">
@ -103,12 +103,22 @@
</div>
<!-- End of Reference Types -->
<div class="col-auto ml-auto mb-4">
<!-- <div class="col-10 d-flex justify-content-between mb-4">
<button mat-button class="rounded-btn secondary" (click)="resetFilters()" style="font-size: 0.87rem;">
Reset
</button>
<button class="normal-btn-sm" (click)="controlModified()">
{{'PLAN-LISTING.FILTERS.APPLY-FILTERS' | translate}}
</button>
</div> -->
<div class="d-flex mb-4">
<button class="normal-btn-sm" (click)="controlModified()">
{{'PLAN-LISTING.FILTERS.APPLY-FILTERS' | translate}}
</button>
</div>
</div>
</div>
</div>

View File

@ -12,7 +12,7 @@ import { PlanUserRole } from '@app/core/common/enum/plan-user-role';
import { SingleAutoCompleteConfiguration } from '@app/library/auto-complete/single/single-auto-complete-configuration';
import { Guid } from '@common/types/guid';
import { ReferenceTypeService } from '@app/core/services/reference-type/reference-type.service';
import { takeUntil } from 'rxjs';
import { filter, map, Observable, takeUntil } from 'rxjs';
import { ReferenceService } from '@app/core/services/reference/reference.service';
import { ReferenceLookup } from '@app/core/query/reference.lookup';
import { IsActive } from '@notification-service/core/enum/is-active.enum';
@ -27,13 +27,24 @@ import { ReferencesWithType } from '@app/core/query/description.lookup';
templateUrl: './plan-filter.component.html',
styleUrls: ['./plan-filter.component.scss'],
})
export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit, OnChanges {
export class PlanFilterComponent extends BaseCriteriaComponent<PlanListingFilterForm> implements OnInit {
@Input() isPublic: boolean;
@Input() hasSelectedTenant: boolean;
@Input() referencesWithTypeItems: ReferencesWithType[];
@Input() filterFormGroup: FormGroup<PlanListingFilterForm>;
@Output() filterChanged: EventEmitter<any> = new EventEmitter();
private _filters: PlanListingFilters;
@Input() set filters(v: PlanListingFilters){
if(v){
this._filters = v;
this.buildForm(v);
}
}
get filters(){
return this._filters;
}
@Output() filterChanged: EventEmitter<PlanListingFilters> = new EventEmitter();
status = PlanStatusEnum;
role = PlanUserRole;
@ -41,10 +52,10 @@ export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit
sizeError = false;
maxFileSize: number = 1048576;
descriptionTemplateAutoCompleteConfiguration: MultipleAutoCompleteConfiguration;
planBlueprintAutoCompleteConfiguration: MultipleAutoCompleteConfiguration;
referenceTypeAutocompleteConfiguration: SingleAutoCompleteConfiguration;
referenceAutocompleteConfiguration: Map<string, MultipleAutoCompleteConfiguration>;
descriptionTemplateAutoCompleteConfiguration: MultipleAutoCompleteConfiguration = this.descriptionTemplateService.buildDescriptionTempalteGroupMultipleAutocompleteConfiguration();
planBlueprintAutoCompleteConfiguration: MultipleAutoCompleteConfiguration = this.planBlueprintService.multipleAutocompleteConfiguration;
referenceTypeAutocompleteConfiguration: SingleAutoCompleteConfiguration = this.getReferenceTypeAutocompleteConfiguration();
referenceAutocompleteConfiguration: Map<Guid, MultipleAutoCompleteConfiguration>;
constructor(
public language: TranslateService,
@ -62,40 +73,39 @@ export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit
super.ngOnInit();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['filterFormGroup']) {
buildForm(filters: PlanListingFilters){
this.formGroup = new FormGroup<PlanListingFilterForm>({
status: new FormControl(filters?.status),
descriptionTemplates: new FormControl(filters?.descriptionTemplates),
planBlueprints: new FormControl(filters?.planBlueprints),
role: new FormControl(filters?.role),
viewOnlyTenant: new FormControl(filters?.viewOnlyTenant ?? false),
references: new FormArray([])
});
this.descriptionTemplateAutoCompleteConfiguration = this.descriptionTemplateService.buildDescriptionTempalteGroupMultipleAutocompleteConfiguration();
this.planBlueprintAutoCompleteConfiguration = this.planBlueprintService.multipleAutocompleteConfiguration;
this.referenceTypeAutocompleteConfiguration = this.getReferenceTypeAutocompleteConfiguration();
this.referenceAutocompleteConfiguration = new Map<string, MultipleAutoCompleteConfiguration>();
this.referenceAutocompleteConfiguration = new Map<Guid, MultipleAutoCompleteConfiguration>();
this.formGroup = this.filterFormGroup;
filters?.references?.forEach((item: ReferencesWithType) => {
if (item.referenceTypeId) {
this.addReferenceType(item.referenceTypeId, item.referenceIds);
}
else if (item.referenceIds && item.referenceIds?.length > 0) {
this.referenceService.query(this._referenceLookup(item.referenceIds)).pipe(takeUntil(this._destroyed), map((x) => x.items))
.subscribe((references: Reference[]) => {
const types = new Set(references.map((x) => x.type.id));
this.referencesWithTypeItems.forEach((referencesWithType: ReferencesWithType) => {
if (referencesWithType.referenceTypeId) {
this.addReferenceType(referencesWithType.referenceTypeId.toString(), referencesWithType.referenceIds.map(x => x.toString()));
}
else if (referencesWithType.referenceIds && referencesWithType.referenceIds?.length > 0) {
this.referenceService.query(this._referenceLookup(referencesWithType.referenceIds)).pipe(takeUntil(this._destroyed))
.subscribe((result: QueryResult<Reference>) => {
const references: Reference[] = result.items;
types.forEach((typeId) => {
const referenceIds = references.filter((x) => x.type.id === typeId).map((x) => x.id) ?? []
this.addReferenceType(typeId, referenceIds);
})
});
}
});
}
const groupedReferencesByTypeIds = new Map<string, string[]>();
references.forEach(reference => {
if (!groupedReferencesByTypeIds.has(reference.type.id.toString())) groupedReferencesByTypeIds.set(reference.type.id.toString(), []);
groupedReferencesByTypeIds.get(reference.type.id.toString()).push(reference.id.toString());
});
groupedReferencesByTypeIds.forEach((referenceIds: string[], referenceId: string) => {
this.addReferenceType(referenceId, referenceIds);
});
});
}
});
}
}
resetFilters(){
this.buildForm(null);
}
onCallbackError(error: any) {
this.setErrorModel(error.error);
@ -103,37 +113,28 @@ export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit
controlModified(): void {
this.clearErrorModel();
this.filterChanged.emit(this.formGroup);
if (this.refreshCallback != null &&
(this.formGroup.get('like').value == null || this.formGroup.get('like').value.length === 0 || this.formGroup.get('like').value.length > 2)
) {
setTimeout(() => this.refreshCallback(true));
}
this.filterChanged.emit(this.formGroup.value as PlanListingFilters);
}
addReferenceType(referenceTypeId: string = null, referenceIds: string[] = null): void {
if (!this.formGroup.get('references')) this.formGroup.addControl('references', this.formBuilder.array([]));
const formArray = this.formGroup.get('references') as UntypedFormArray;
const referenceForm: UntypedFormGroup = this.formBuilder.group({
referenceTypeId: referenceTypeId,
referenceIds: referenceIds ? [referenceIds] : null,
addReferenceType(referenceTypeId: Guid = null, referenceIds: Guid[] = null): void {
const referenceForm = new FormGroup<PlanListingFilterReferencesForm>({
referenceTypeId: new FormControl(referenceTypeId),
referenceIds: new FormControl(referenceIds ? referenceIds : []),
});
if (referenceTypeId && referenceTypeId != '' && referenceIds && referenceIds.length > 0) {
let referenceAutocomplete = this.getReferenceAutocompleteConfiguration(Guid.parse(referenceTypeId));
this.referenceAutocompleteConfiguration.set(referenceTypeId, referenceAutocomplete);
if (referenceTypeId && referenceIds?.length) {
let referenceAutocomplete = this.getReferenceAutocompleteConfiguration(referenceTypeId);
this.referenceAutocompleteConfiguration.set(referenceTypeId, referenceAutocomplete);
}
this._registerReferenceTypeListener(referenceForm);
this._registerReferencesListener(referenceForm);
formArray.push(referenceForm);
this.formGroup.controls.references.push(referenceForm);
}
deleteRow(index: number): void {
const formArray = this.formGroup.get('references') as UntypedFormArray;
const formArray = this.formGroup.controls.references;
formArray.removeAt(index);
}
@ -142,7 +143,7 @@ export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit
return this.authentication.currentAccountIsAuthenticated();
}
selectReferenceAutocompleteConfiguration(referenceTypeId: string): MultipleAutoCompleteConfiguration {
selectReferenceAutocompleteConfiguration(referenceTypeId: Guid): MultipleAutoCompleteConfiguration {
return this.referenceAutocompleteConfiguration.get(referenceTypeId);
};
@ -172,26 +173,26 @@ export class PlanFilterComponent extends BaseCriteriaComponent implements OnInit
return lookup;
}
private _registerReferenceTypeListener(control: AbstractControl) {
control.get('referenceTypeId')?.valueChanges.pipe(takeUntil(this._destroyed))
.subscribe((referenceTypeId: string) => {
private _registerReferenceTypeListener(formGroup: FormGroup<PlanListingFilterReferencesForm>) {
formGroup.controls.referenceTypeId?.valueChanges.pipe(takeUntil(this._destroyed))
.subscribe((referenceTypeId: Guid) => {
this.referenceTypeAutocompleteConfiguration = this.getReferenceTypeAutocompleteConfiguration();
control.get('referenceIds')?.reset();
formGroup.controls.referenceIds?.reset();
if (referenceTypeId && referenceTypeId != '') {
let referenceAutocomplete = this.getReferenceAutocompleteConfiguration(Guid.parse(referenceTypeId));
if (referenceTypeId) {
let referenceAutocomplete = this.getReferenceAutocompleteConfiguration(referenceTypeId);
this.referenceAutocompleteConfiguration.set(referenceTypeId, referenceAutocomplete);
}
});
}
private _registerReferencesListener(control: AbstractControl) {
control.get('referenceIds')?.valueChanges.pipe(takeUntil(this._destroyed))
private _registerReferencesListener(formGroup: FormGroup<PlanListingFilterReferencesForm>) {
formGroup.controls.referenceIds?.valueChanges.pipe(takeUntil(this._destroyed))
.subscribe(references => {
let referenceTypeId = control.get('referenceTypeId').value;
let referenceTypeId = formGroup.controls.referenceTypeId.value;
if (!referenceTypeId) return;
let referenceAutocomplete = this.getReferenceAutocompleteConfiguration(Guid.parse(referenceTypeId));
let referenceAutocomplete = this.getReferenceAutocompleteConfiguration(referenceTypeId);
this.referenceAutocompleteConfiguration.set(referenceTypeId, referenceAutocomplete);
});
}
@ -206,11 +207,16 @@ export interface PlanListingFilters {
references: ReferencesWithType[]
}
export interface PlanListingFilterForm {
interface PlanListingFilterForm {
status: FormControl<PlanStatusEnum>,
viewOnlyTenant: FormControl<boolean>,
descriptionTemplates: FormControl<Guid[]>,
planBlueprints: FormControl<Guid[]>,
role: FormControl<Guid>,
references: FormArray<any>
references: FormArray<FormGroup<PlanListingFilterReferencesForm>>
}
interface PlanListingFilterReferencesForm {
referenceTypeId: FormControl<Guid>;
referenceIds: FormControl<Guid[]>
}

View File

@ -316,9 +316,7 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
});
dialogRef.afterClosed().pipe(takeUntil(this._destroyed)).subscribe(result => {
if (result) {
this.updateDataFn(result);
}
this.updateDataFn(result);
});
}
@ -409,12 +407,11 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
}
private _patchLookupFromFilters(filters: PlanListingFilters): PlanLookup {
if (!filters) return;
this.lookup.statuses = filters.status !== null ? [filters.status] : [];
this.lookup.statuses = filters?.status !== null ? [filters.status] : [];
// Tenants
let viewOnlyTenant = filters.viewOnlyTenant ?? 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) {
@ -425,7 +422,7 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
} else this.lookup.tenantSubQuery = null;
// Description Templates
let descriptionTemplates = filters.descriptionTemplates ?? null;
let descriptionTemplates = filters?.descriptionTemplates ?? null;
if (descriptionTemplates && descriptionTemplates?.length > 0) {
this.lookup.planDescriptionTemplateSubQuery = PlanFilterService.initializePlanDescriptionTemplateLookup();
this.lookup.planDescriptionTemplateSubQuery.descriptionTemplateGroupIds = descriptionTemplates;
@ -433,20 +430,20 @@ export class PlanListingComponent extends BaseListingComponent<BasePlan, PlanLoo
// Blueprints
let planBlueprints = filters.planBlueprints ?? null;
let planBlueprints = filters?.planBlueprints ?? null;
if (planBlueprints && planBlueprints?.length > 0) {
this.lookup.planBlueprintSubQuery = PlanFilterService.initializePlanBlueprintLookup();
this.lookup.planBlueprintSubQuery.ids = planBlueprints;
} else this.lookup.planBlueprintSubQuery = null;
// plans
let roles = filters.role !== null ? [filters.role] : null;
let roles = filters?.role !== null ? [filters.role] : null;
if (roles && roles?.length > 0) {
this.lookup.planUserSubQuery = PlanFilterService.initializePlanUserLookup();
this.lookup.planUserSubQuery.userRoles = roles;
} else this.lookup.planUserSubQuery = null;
let references: Guid[] = filters.references
let references: Guid[] = filters?.references
?.filter((reference: ReferencesWithType) => reference.referenceTypeId != null && reference.referenceIds?.length > 0)
?.flatMap((referencesWithType: ReferencesWithType) => referencesWithType.referenceIds) as Guid[];

View File

@ -400,3 +400,101 @@ $mat-dark-theme: mat.m2-define-dark-theme((color: (primary: $mat-dark-theme-prim
.dense-10 {
@include mat.all-component-densities(-10);
}
.mdc-button,.mat-mdc-button, .mdc-button:has(.material-icons,mat-icon,[matButtonIcon]) {
&.rounded-btn{
height: 40px;
border-radius: 30px;
border: none;
opacity: 1;
padding-left: 2em;
padding-right: 2em;
box-shadow: 0px 3px 6px #1E202029;
&:hover {
box-shadow: var(--mdc-protected-button-hover-container-elevation-shadow);
}
}
&.neutral {
background: #ffffff 0% 0% no-repeat padding-box;
border: 1px solid #b5b5b5;
}
&.primary {
background: var(--primary-color) 0% 0% no-repeat padding-box;
color: #ffffff;
}
&.primary-inverted {
border: 1px solid var(--primary-color);
background: transparent;
color: var(--primary-color);
}
&.secondary {
background: var(--secondary-color) 0% 0% no-repeat padding-box;
color: #000000;
}
// &.save-btn {
// }
// &.cancel-btn {
// }
// &.finalize-btn {
// }
&.delete-btn {
background: #ffffff;
color: #ff3d33;;
border: 1px solid #ff3d33;
}
&.md-button {
padding: 0.62rem 1.87rem;
font-weight: 400;
}
}
.mat-mdc-button.mat-mdc-button-disabled {
background-color: #CBCBCB; //!important
color: #FFF; //!important
border: 0px; //!important
}
.status-chip {
border-radius: 20px;
padding-left: 1em;
padding-right: 1em;
padding-top: 0.2em;
font-size: .8em;
&.status-chip-finalized {
color: #568b5a;
background: #9dd1a1 0% 0% no-repeat padding-box;
}
&.status-chip-draft {
color: #00c4ff;
background: #d3f5ff 0% 0% no-repeat padding-box;
}
&.status-chip-error {
color: #cf1407;
background: #ffe6e5 0% 0% no-repeat padding-box;
}
}
.gap-1-rem{
gap: 1rem;
}
.gap-2-rem{
gap: 2rem;
}
.gap-half-rem {
gap: 0.5rem;
}