RED-6408, clone components using the old filter service in the searchscreen.

This commit is contained in:
George 2023-07-17 11:32:05 +03:00
parent 4fede31e8c
commit c78b00e21a
5 changed files with 177 additions and 11 deletions

View File

@ -1,9 +1,4 @@
import { computed, Injectable, signal } from '@angular/core';
const PARENT_PROPS = ['children', 'indeterminate', 'expanded'] as const;
const PARENT_FILTER_ID = 'parentFilterId';
type ParentProps = (typeof PARENT_PROPS)[number];
type ChildFilter = Omit<Filter, ParentProps> & { [PARENT_FILTER_ID]: string };
import { Injectable, signal } from '@angular/core';
interface Filter {
id: string;
@ -13,7 +8,7 @@ interface Filter {
checked: boolean;
disabled: boolean;
meta: Record<string, unknown> | undefined;
children: Record<string, ChildFilter> | undefined;
children: Record<string, Omit<Filter, 'children' | 'indeterminate' | 'expanded'>> | undefined;
}
interface FilterGroup {
name: string;
@ -22,11 +17,9 @@ interface FilterGroup {
}
const safeObj = <T>(obj: T | undefined) => obj ?? ({} as T);
const getFiltersWithChildren = (filters: Filter[]) => filters.flatMap(filter => [filter, ...Object.values(safeObj(filter.children))]);
const isChild = (filter: Filter | ChildFilter): filter is ChildFilter => PARENT_FILTER_ID in filter;
@Injectable()
export class FilterService {
export class SearcnScreenFilterService {
readonly #filterGroups = signal<Record<string, FilterGroup> | undefined>(undefined);
readonly filterGroups = this.#filterGroups.asReadonly();

View File

@ -0,0 +1,94 @@
<div class="page-header">
<div *ngIf="pageLabel" class="breadcrumb">{{ pageLabel }}</div>
<div *ngIf="filters$ | async as filters" class="filters">
<ng-content select="[slot=beforeFilters]"></ng-content>
<div
*ngIf="filters.length && searchPosition !== searchPositions.beforeFilters"
class="text-muted"
translate="filters.filter-by"
></div>
<ng-container *ngIf="searchPosition === searchPositions.beforeFilters" [ngTemplateOutlet]="searchBar"></ng-container>
<ng-container *ngFor="let filter of filters; trackBy: trackByLabel">
<iqser-popup-filter
*ngIf="!filter.hide"
[primaryFiltersSlug]="filter.slug"
[attr.help-mode-key]="filterHelpModeKey"
></iqser-popup-filter>
</ng-container>
<ng-container *ngFor="let filter$ of filterService.singleFilters">
<iqser-single-filter *ngIf="filter$ | async as filter" [filter]="filter"></iqser-single-filter>
</ng-container>
<ng-container *ngIf="searchPosition === searchPositions.afterFilters" [ngTemplateOutlet]="searchBar"></ng-container>
<div
(click)="resetFilters()"
*ngIf="!hideResetButton && (showResetFilters$ | async) === true"
[attr.help-mode-key]="'filter_' + helpModeKey + '_list'"
class="reset-filters"
translate="reset-filters"
></div>
</div>
<div *ngIf="showCloseButton || actionConfigs || buttonConfigs || viewModeSelection" class="actions">
<ng-container *ngIf="searchPosition === searchPositions.withActions" [ngTemplateOutlet]="searchBar"></ng-container>
<ng-container [ngTemplateOutlet]="viewModeSelection"></ng-container>
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
<iqser-icon-button
(action)="config.action($event)"
*ngIf="!config.hide"
[buttonId]="config.label.replace('.', '-')"
[icon]="config.icon"
[label]="config.label | translate"
[type]="config.type"
[attr.help-mode-key]="config.helpModeKey"
></iqser-icon-button>
</ng-container>
<div class="actions">
<ng-container *ngFor="let config of actionConfigs; trackBy: trackByLabel">
<iqser-circle-button
(action)="config.action($event)"
*ngIf="!config.hide"
[buttonId]="config.id"
[disabled]="config.disabled$ && (config.disabled$ | async)"
[icon]="config.icon"
[tooltip]="config.label"
[attr.help-mode-key]="config.helpModeKey"
></iqser-circle-button>
</ng-container>
<!-- Extra custom actions here -->
<ng-content select="[slot=right]"></ng-content>
<iqser-circle-button
buttonId="close-view-btn"
(action)="closeAction.emit()"
*ngIf="showCloseButton"
[class.ml-6]="actionConfigs"
[icon]="'iqser:close'"
[attr.help-mode-key]="'edit_dossier_in_dossier'"
[tooltip]="'common.close' | translate"
></iqser-circle-button>
</div>
</div>
</div>
<ng-template #searchBar>
<iqser-input-with-action
[inputId]="searchInputId"
(valueChange)="searchService.searchValue = $event"
*ngIf="searchPlaceholder && searchService"
[class.mr-8]="searchPosition === searchPositions.beforeFilters"
[placeholder]="searchPlaceholder"
[value]="searchService.valueChanges$ | async"
[width]="searchWidth"
></iqser-input-with-action>
</ng-template>

View File

@ -0,0 +1,3 @@
.actions iqser-input-with-action {
margin-right: 16px;
}

View File

@ -0,0 +1,75 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Optional, Output, TemplateRef } from '@angular/core';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { combineLatest, Observable, of } from 'rxjs';
import { CircleButtonComponent, IconButtonComponent, IconButtonTypes } from '../../buttons';
import { SearchService } from '../../search';
import { FilterService } from '../../filtering';
import { filterEach, List } from '../../utils';
import { ActionConfig, ButtonConfig, IListable, SearchPosition, SearchPositions } from '../../listing';
import { AsyncPipe, NgFor, NgTemplateOutlet } from '@angular/common';
import { IqserTranslateModule } from '../../translations';
import { InputWithActionComponent } from '../../inputs';
@Component({
selector: 'iqser-page-header',
templateUrl: './page-header.component.html',
styleUrls: ['./page-header.component.scss'],
standalone: true,
imports: [
NgTemplateOutlet,
AsyncPipe,
NgFor,
IqserTranslateModule,
IconButtonComponent,
InputWithActionComponent,
CircleButtonComponent,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchScreenPageHeaderComponent<T extends IListable> {
readonly searchPositions = SearchPositions;
readonly iconButtonTypes = IconButtonTypes;
@Input() pageLabel?: string;
@Input() searchInputId?: string;
@Input() showCloseButton = false;
@Input() hideResetButton = false;
@Input() actionConfigs?: List<ActionConfig>;
@Input() buttonConfigs?: List<ButtonConfig>;
@Input() viewModeSelection?: TemplateRef<unknown>;
@Input() searchPlaceholder?: string;
@Input() searchWidth?: number | 'full';
@Input() helpModeKey?: 'dossier' | 'document';
@Input() searchPosition: SearchPosition = SearchPositions.afterFilters;
@Output() readonly closeAction = new EventEmitter();
readonly filters$ = this.filterService?.filterGroups$.pipe(filterEach(f => !!f.icon));
readonly showResetFilters$ = this.#showResetFilters$;
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService<T>) {}
get filterHelpModeKey() {
return this.helpModeKey ? `filter_${this.helpModeKey}_list` : '';
}
get #showResetFilters$(): Observable<boolean> {
if (!this.filterService) {
return of(false);
}
return combineLatest([this.filterService.showResetFilters$, this.searchService.valueChanges$]).pipe(
map(([showResetFilters, searchValue]) => showResetFilters || !!searchValue),
distinctUntilChanged(),
);
}
resetFilters(): void {
this.filterService.reset();
this.searchService.reset();
}
trackByLabel<K extends { label?: string }>(_index: number, item: K): string | undefined {
return item.label;
}
}

View File

@ -22,7 +22,8 @@
"allowSyntheticDefaultImports": true,
"paths": {
"@biesbjerg/ngx-translate-extract-marker": ["src/lib/translations/ngx-translate-extract-marker"]
}
},
"noUncheckedIndexedAccess": true
},
"include": ["./**/*"],
"angularCompilerOptions": {