wip page header component

This commit is contained in:
Dan Percic 2021-07-01 19:12:36 +03:00
parent 5adf7379c9
commit fe8bc8826d
14 changed files with 412 additions and 268 deletions

View File

@ -1,17 +1,7 @@
<section> <section>
<div class="overlay-shadow"></div> <div class="overlay-shadow"></div>
<div class="page-header"> <redaction-page-header pageLabel="trash.label" [showCloseButton]="true"></redaction-page-header>
<div class="breadcrumb" translate="trash.label"></div>
<redaction-circle-button
*ngIf="permissionsService.isUser()"
icon="red:close"
redactionNavigateLastDossiersScreen
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>
<div class="red-content-inner"> <div class="red-content-inner">
<div class="content-container"> <div class="content-container">

View File

@ -38,7 +38,3 @@ redaction-table-col-name::ng-deep {
} }
} }
} }
.page-header .actions > *:not(:last-child) {
margin-right: 16px;
}

View File

@ -1,53 +1,11 @@
<section> <section>
<div class="page-header"> <redaction-page-header
<div class="filters">
<div translate="filters.filter-by"></div>
<redaction-popup-filter
(filtersChanged)="filtersChanged()" (filtersChanged)="filtersChanged()"
[filterLabel]="'filters.status'" (searchChanged)="executeSearch($event)"
[icon]="'red:status'" [filterConfigs]="filterConfigs"
[primaryFilters]="statusFilters" [buttonConfigs]="buttonConfigs"
></redaction-popup-filter> [searchPlaceholder]="'dossier-listing.search'"
<redaction-popup-filter ></redaction-page-header>
(filtersChanged)="filtersChanged()"
[filterLabel]="'filters.people'"
[icon]="'red:user'"
[primaryFilters]="peopleFilters"
></redaction-popup-filter>
<redaction-popup-filter
(filtersChanged)="filtersChanged()"
[filterLabel]="'filters.needs-work'"
[filterTemplate]="needsWorkTemplate"
[icon]="'red:needs-work'"
[primaryFilters]="needsWorkFilters"
></redaction-popup-filter>
<redaction-popup-filter
(filtersChanged)="filtersChanged()"
*ngIf="dossierTemplateFilters.length > 1"
[filterLabel]="'filters.dossier-templates'"
[icon]="'red:template'"
[primaryFilters]="dossierTemplateFilters"
></redaction-popup-filter>
<redaction-input-with-action
[form]="searchForm"
placeholder="dossier-listing.search"
type="search"
></redaction-input-with-action>
<div
(click)="resetFilters()"
*ngIf="hasActiveFilters"
class="reset-filters"
translate="reset-filters"
></div>
</div>
<redaction-icon-button
(action)="openAddDossierDialog()"
*ngIf="permissionsService.isManager()"
icon="red:plus"
text="dossier-listing.add-new"
type="primary"
></redaction-icon-button>
</div>
<div class="overlay-shadow"></div> <div class="overlay-shadow"></div>
@ -135,7 +93,7 @@
<div class="small-label stats-subtitle"> <div class="small-label stats-subtitle">
<div> <div>
<mat-icon svgIcon="red:document"></mat-icon> <mat-icon svgIcon="red:document"></mat-icon>
{{ documentCount(dw) }} {{ filesCount(dw) }}
</div> </div>
<div> <div>
<mat-icon svgIcon="red:pages"></mat-icon> <mat-icon svgIcon="red:pages"></mat-icon>

View File

@ -1,12 +1,4 @@
import { import { Component, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
Component,
Injector,
OnDestroy,
OnInit,
QueryList,
ViewChild,
ViewChildren
} from '@angular/core';
import { Dossier, DossierTemplateModel } from '@redaction/red-ui-http'; import { Dossier, DossierTemplateModel } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service'; import { AppStateService } from '@state/app-state.service';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
@ -25,7 +17,6 @@ import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { BaseListingComponent } from '@shared/base/base-listing.component'; import { BaseListingComponent } from '@shared/base/base-listing.component';
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy'; import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component';
import { import {
annotationFilterChecker, annotationFilterChecker,
dossierMemberChecker, dossierMemberChecker,
@ -33,11 +24,11 @@ import {
dossierTemplateChecker, dossierTemplateChecker,
processFilters processFilters
} from '@shared/components/filters/popup-filter/utils/filter-utils'; } from '@shared/components/filters/popup-filter/utils/filter-utils';
import { QuickFiltersComponent } from '../../../shared/components/filters/quick-filters/quick-filters.component'; import { QuickFiltersComponent } from '@shared/components/filters/quick-filters/quick-filters.component';
import { UserPreferenceService } from '../../../../services/user-preference.service'; import { UserPreferenceService } from '../../../../services/user-preference.service';
import { ButtonConfig, FilterConfig } from '@shared/components/page-header/page-header.component';
@Component({ @Component({
selector: 'redaction-dossier-listing-screen',
templateUrl: './dossier-listing-screen.component.html', templateUrl: './dossier-listing-screen.component.html',
styleUrls: ['./dossier-listing-screen.component.scss'] styleUrls: ['./dossier-listing-screen.component.scss']
}) })
@ -58,6 +49,16 @@ export class DossierListingScreenComponent
}; };
quickFilters: FilterModel[]; quickFilters: FilterModel[];
readonly itemSize = 85; readonly itemSize = 85;
filterConfigs: FilterConfig[];
buttonConfigs: ButtonConfig[] = [
{
label: 'dossier-listing.add-new',
action: () => this.openAddDossierDialog(),
hide: !this.permissionsService.isManager(),
icon: 'red:plus',
type: 'primary'
}
];
protected readonly _searchKey = 'name'; protected readonly _searchKey = 'name';
protected readonly _sortKey = 'dossier-listing'; protected readonly _sortKey = 'dossier-listing';
@ -67,10 +68,10 @@ export class DossierListingScreenComponent
private _routerEventsScrollPositionSub: Subscription; private _routerEventsScrollPositionSub: Subscription;
private _fileChangedSub: Subscription; private _fileChangedSub: Subscription;
@ViewChildren(PopupFilterComponent)
private readonly _filterList: QueryList<PopupFilterComponent>;
@ViewChild(QuickFiltersComponent) @ViewChild(QuickFiltersComponent)
protected readonly _quickFiltersComponent: QuickFiltersComponent; protected readonly _quickFiltersComponent: QuickFiltersComponent;
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
private readonly _needsWorkTemplate: TemplateRef<any>;
constructor( constructor(
readonly permissionsService: PermissionsService, readonly permissionsService: PermissionsService,
@ -155,7 +156,7 @@ export class DossierListingScreenComponent
this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top'); this._lastScrollPosition = this.scrollViewport.measureScrollOffset('top');
} }
}); });
this._filterComponents = [...this._filterList.toArray(), this._quickFiltersComponent]; this._filterComponents = [this._quickFiltersComponent];
} }
ngOnAttach() { ngOnAttach() {
@ -175,7 +176,7 @@ export class DossierListingScreenComponent
this._fileChangedSub.unsubscribe(); this._fileChangedSub.unsubscribe();
} }
documentCount(dossier: DossierWrapper) { filesCount(dossier: DossierWrapper) {
return dossier.files.length; return dossier.files.length;
} }
@ -248,12 +249,9 @@ export class DossierListingScreenComponent
const allDistinctDossierTemplates = new Set<string>(); const allDistinctDossierTemplates = new Set<string>();
this.allEntities.forEach(entry => { this.allEntities.forEach(entry => {
// all people // all people
entry.dossier.memberIds.forEach(memberId => allDistinctPeople.add(memberId)); entry.dossier.memberIds.forEach(f => allDistinctPeople.add(f));
// file statuses // file statuses
entry.files.forEach(file => { entry.files.forEach(f => allDistinctFileStatus.add(f.status));
allDistinctFileStatus.add(file.status);
});
// Needs work // Needs work
entry.files.forEach(file => { entry.files.forEach(file => {
if (this.permissionsService.fileRequiresReanalysis(file)) if (this.permissionsService.fileRequiresReanalysis(file))
@ -312,7 +310,39 @@ export class DossierListingScreenComponent
dossierTemplateFilters dossierTemplateFilters
); );
this.quickFilters = [ this._createFilterConfigs();
this._createQuickFilters();
}
private _createFilterConfigs() {
this.filterConfigs = [
{
label: 'filters.status',
primaryFilters: this.statusFilters,
icon: 'red:status'
},
{
label: 'filters.people',
primaryFilters: this.peopleFilters,
icon: 'red:user'
},
{
label: 'filters.needs-work',
primaryFilters: this.needsWorkFilters,
icon: 'red:needs-work',
filterTemplate: this._needsWorkTemplate
},
{
label: 'filters.dossier-templates',
primaryFilters: this.dossierTemplateFilters,
icon: 'red:template',
hide: this.dossierTemplateFilters.length <= 1
}
];
}
private _createQuickFilters() {
const filters = [
{ {
key: this.user.id, key: this.user.id,
label: 'dossier-listing.quick-filters.my-dossiers', label: 'dossier-listing.quick-filters.my-dossiers',
@ -333,7 +363,9 @@ export class DossierListingScreenComponent
label: 'dossier-listing.quick-filters.other', label: 'dossier-listing.quick-filters.other',
checker: (dw: DossierWrapper) => !dw.memberIds.includes(this.user.id) checker: (dw: DossierWrapper) => !dw.memberIds.includes(this.user.id)
} }
].filter( ];
this.quickFilters = filters.filter(
f => f =>
f.label === 'dossier-listing.quick-filters.my-dossiers' || f.label === 'dossier-listing.quick-filters.my-dossiers' ||
this._userPreferenceService.areDevFeaturesEnabled this._userPreferenceService.areDevFeaturesEnabled

View File

@ -1,84 +1,91 @@
<section *ngIf="!!activeDossier"> <section *ngIf="!!activeDossier">
<div class="page-header"> <redaction-page-header
<div class="filters">
<div translate="filters.filter-by"></div>
<redaction-popup-filter
(filtersChanged)="filtersChanged()" (filtersChanged)="filtersChanged()"
[filterLabel]="'filters.status'" (searchChanged)="executeSearch($event)"
[icon]="'red:status'" [filterConfigs]="filterConfigs"
[primaryFilters]="statusFilters" [actionConfigs]="actionConfigs"
></redaction-popup-filter> [searchPlaceholder]="'dossier-overview.search'"
<redaction-popup-filter ></redaction-page-header>
(filtersChanged)="filtersChanged()" <!-- <div class="page-header">-->
[filterLabel]="'filters.assigned-people'" <!-- <div class="filters">-->
[icon]="'red:user'" <!-- <div translate="filters.filter-by"></div>-->
[primaryFilters]="peopleFilters" <!-- <redaction-popup-filter-->
></redaction-popup-filter> <!-- (filtersChanged)="filtersChanged()"-->
<redaction-popup-filter <!-- [filterLabel]="'filters.status'"-->
(filtersChanged)="filtersChanged()" <!-- [icon]="'red:status'"-->
[filterLabel]="'filters.needs-work'" <!-- [primaryFilters]="statusFilters"-->
[filterTemplate]="needsWorkTemplate" <!-- ></redaction-popup-filter>-->
[icon]="'red:needs-work'" <!-- <redaction-popup-filter-->
[primaryFilters]="needsWorkFilters" <!-- (filtersChanged)="filtersChanged()"-->
></redaction-popup-filter> <!-- [filterLabel]="'filters.assigned-people'"-->
<!-- [icon]="'red:user'"-->
<!-- [primaryFilters]="peopleFilters"-->
<!-- ></redaction-popup-filter>-->
<!-- <redaction-popup-filter-->
<!-- (filtersChanged)="filtersChanged()"-->
<!-- [filterLabel]="'filters.needs-work'"-->
<!-- [filterTemplate]="needsWorkTemplate"-->
<!-- [icon]="'red:needs-work'"-->
<!-- [primaryFilters]="needsWorkFilters"-->
<!-- ></redaction-popup-filter>-->
<redaction-input-with-action <!-- <redaction-input-with-action-->
[form]="searchForm" <!-- [form]="searchForm"-->
placeholder="dossier-overview.search" <!-- [placeholder]="'dossier-overview.search'"-->
type="search" <!-- type="search"-->
></redaction-input-with-action> <!-- ></redaction-input-with-action>-->
<div <!-- <div-->
(click)="resetFilters()" <!-- (click)="resetFilters()"-->
*ngIf="hasActiveFilters" <!-- *ngIf="hasActiveFilters"-->
class="reset-filters" <!-- class="reset-filters"-->
translate="reset-filters" <!-- translate="reset-filters"-->
></div> <!-- ></div>-->
</div> <!-- </div>-->
<div class="actions"> <!-- <div class="actions">-->
<redaction-circle-button <!-- <redaction-circle-button-->
(action)="openEditDossierDialog($event)" <!-- (action)="openEditDossierDialog($event)"-->
*ngIf="permissionsService.isManager()" <!-- *ngIf="permissionsService.isManager()"-->
icon="red:edit" <!-- icon="red:edit"-->
tooltip="dossier-overview.header-actions.edit" <!-- tooltip="dossier-overview.header-actions.edit"-->
tooltipPosition="below" <!-- tooltipPosition="below"-->
></redaction-circle-button> <!-- ></redaction-circle-button>-->
<redaction-file-download-btn <!-- <redaction-file-download-btn-->
[disabled]="areSomeEntitiesSelected" <!-- [disabled]="areSomeEntitiesSelected"-->
[dossier]="activeDossier" <!-- [dossier]="activeDossier"-->
[file]="allEntities" <!-- [file]="allEntities"-->
tooltipPosition="below" <!-- tooltipPosition="below"-->
></redaction-file-download-btn> <!-- ></redaction-file-download-btn>-->
<redaction-circle-button <!-- <redaction-circle-button-->
(action)="reanalyseDossier()" <!-- (action)="reanalyseDossier()"-->
*ngIf="permissionsService.displayReanalyseBtn()" <!-- *ngIf="permissionsService.displayReanalyseBtn()"-->
[disabled]="areSomeEntitiesSelected" <!-- [disabled]="areSomeEntitiesSelected"-->
[tooltipClass]="'small ' + (areSomeEntitiesSelected ? '' : 'warn')" <!-- [tooltipClass]="'small ' + (areSomeEntitiesSelected ? '' : 'warn')"-->
[tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all'" <!-- [tooltip]="'dossier-overview.new-rule.toast.actions.reanalyse-all'"-->
icon="red:refresh" <!-- icon="red:refresh"-->
tooltipPosition="below" <!-- tooltipPosition="below"-->
type="warn" <!-- type="warn"-->
></redaction-circle-button> <!-- ></redaction-circle-button>-->
<redaction-circle-button <!-- <redaction-circle-button-->
(action)="fileInput.click()" <!-- (action)="fileInput.click()"-->
class="ml-14" <!-- class="ml-14"-->
icon="red:upload" <!-- icon="red:upload"-->
tooltip="dossier-overview.header-actions.upload-document" <!-- tooltip="dossier-overview.header-actions.upload-document"-->
tooltipPosition="below" <!-- tooltipPosition="below"-->
type="primary" <!-- type="primary"-->
></redaction-circle-button> <!-- ></redaction-circle-button>-->
<redaction-circle-button <!-- <redaction-circle-button-->
[routerLink]="['/main/dossiers/']" <!-- [routerLink]="['/main/dossiers/']"-->
class="ml-6" <!-- class="ml-6"-->
icon="red:close" <!-- icon="red:close"-->
tooltip="common.close" <!-- tooltip="common.close"-->
tooltipPosition="below" <!-- tooltipPosition="below"-->
></redaction-circle-button> <!-- ></redaction-circle-button>-->
</div> <!-- </div>-->
</div> <!-- </div>-->
<div class="overlay-shadow"></div> <div class="overlay-shadow"></div>

View File

@ -5,9 +5,8 @@ import {
Injector, Injector,
OnDestroy, OnDestroy,
OnInit, OnInit,
QueryList, TemplateRef,
ViewChild, ViewChild
ViewChildren
} from '@angular/core'; } from '@angular/core';
import { NavigationStart, Router } from '@angular/router'; import { NavigationStart, Router } from '@angular/router';
import { NotificationService, NotificationType } from '@services/notification.service'; import { NotificationService, NotificationType } from '@services/notification.service';
@ -38,9 +37,12 @@ import {
processFilters processFilters
} from '@shared/components/filters/popup-filter/utils/filter-utils'; } from '@shared/components/filters/popup-filter/utils/filter-utils';
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model'; import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component';
import { QuickFiltersComponent } from '../../../shared/components/filters/quick-filters/quick-filters.component'; import { QuickFiltersComponent } from '../../../shared/components/filters/quick-filters/quick-filters.component';
import { AppConfigService } from '../../../app-config/app-config.service'; import { AppConfigService } from '../../../app-config/app-config.service';
import {
ActionConfig,
FilterConfig
} from '../../../shared/components/page-header/page-header.component';
@Component({ @Component({
selector: 'redaction-dossier-overview-screen', selector: 'redaction-dossier-overview-screen',
@ -61,6 +63,8 @@ export class DossierOverviewScreenComponent
} = { needsWorkFilters: [], statusFilters: [] }; } = { needsWorkFilters: [], statusFilters: [] };
readonly itemSize = 80; readonly itemSize = 80;
quickFilters: FilterModel[]; quickFilters: FilterModel[];
filterConfigs: FilterConfig[];
actionConfigs: ActionConfig[];
protected readonly _searchKey = 'searchField'; protected readonly _searchKey = 'searchField';
protected readonly _selectionKey = 'fileId'; protected readonly _selectionKey = 'fileId';
@ -74,11 +78,10 @@ export class DossierOverviewScreenComponent
private _lastScrollPosition: number; private _lastScrollPosition: number;
private _lastOpenedFileId = ''; private _lastOpenedFileId = '';
@ViewChildren(PopupFilterComponent)
private readonly _filterList: QueryList<PopupFilterComponent>;
@ViewChild(QuickFiltersComponent) @ViewChild(QuickFiltersComponent)
protected readonly _quickFiltersComponent: QuickFiltersComponent; protected readonly _quickFiltersComponent: QuickFiltersComponent;
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
private readonly _needsWorkTemplate: TemplateRef<any>;
@ViewChild('fileInput') private _fileInput: ElementRef; @ViewChild('fileInput') private _fileInput: ElementRef;
constructor( constructor(
@ -182,7 +185,7 @@ export class DossierOverviewScreenComponent
} }
}); });
this._filterComponents = [...this._filterList.toArray(), this._quickFiltersComponent]; this._filterComponents = [this._quickFiltersComponent];
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -393,10 +396,12 @@ export class DossierOverviewScreenComponent
); );
this.needsWorkFilters = processFilters(this.needsWorkFilters, needsWorkFilters); this.needsWorkFilters = processFilters(this.needsWorkFilters, needsWorkFilters);
this._computeQuickFilters(); this._createQuickFilters();
this._createFilterConfigs();
this._createActionConfigs();
} }
private _computeQuickFilters() { private _createQuickFilters() {
if (this.allEntities.filter(this.recentlyModifiedChecker).length > 0) { if (this.allEntities.filter(this.recentlyModifiedChecker).length > 0) {
const recentPeriodInHours = this._appConfigService.getConfig('RECENT_PERIOD_IN_HOURS'); const recentPeriodInHours = this._appConfigService.getConfig('RECENT_PERIOD_IN_HOURS');
this.quickFilters = [ this.quickFilters = [
@ -432,4 +437,42 @@ export class DossierOverviewScreenComponent
} }
]; ];
} }
private _createFilterConfigs() {
this.filterConfigs = [
{
label: 'filters.status',
primaryFilters: this.statusFilters,
icon: 'red:status'
},
{
label: 'filters.assigned-people',
primaryFilters: this.peopleFilters,
icon: 'red:user'
},
{
label: 'filters.needs-work',
primaryFilters: this.needsWorkFilters,
icon: 'red:needs-work',
filterTemplate: this._needsWorkTemplate
}
];
}
private _createActionConfigs() {
this.actionConfigs = [
{
label: 'dossier-overview.header-actions.edit',
action: $event => this.openEditDossierDialog($event),
icon: 'red:edit',
hide: !this.permissionsService.isManager()
},
{
label: 'dossier-overview.header-actions.edit',
action: $event => this.openEditDossierDialog($event),
icon: 'red:edit',
hide: !this.permissionsService.isManager()
}
];
}
} }

View File

@ -33,20 +33,12 @@ export abstract class BaseListingComponent<T = any> {
// Overwrite this in ngOnInit // Overwrite this in ngOnInit
protected _filterComponents: (PopupFilterComponent | QuickFiltersComponent)[] = []; protected _filterComponents: (PopupFilterComponent | QuickFiltersComponent)[] = [];
private _searchValue = '';
protected constructor(protected readonly _injector: Injector) { protected constructor(protected readonly _injector: Injector) {
this._formBuilder = this._injector.get<FormBuilder>(FormBuilder); this._formBuilder = this._injector.get<FormBuilder>(FormBuilder);
this._changeDetectorRef = this._injector.get<ChangeDetectorRef>(ChangeDetectorRef); this._changeDetectorRef = this._injector.get<ChangeDetectorRef>(ChangeDetectorRef);
this._sortingService = this._injector.get<SortingService>(SortingService); this._sortingService = this._injector.get<SortingService>(SortingService);
this._initSearch();
}
get hasActiveFilters() {
return (
this._filterComponents
?.filter(f => !!f)
.reduce((prev, component) => prev || component?.hasActiveFilters, false) ||
this.searchForm.get('query').value
);
} }
get areAllEntitiesSelected() { get areAllEntitiesSelected() {
@ -108,10 +100,9 @@ export abstract class BaseListingComponent<T = any> {
resetFilters() { resetFilters() {
for (const filterComponent of this._filterComponents.filter(f => !!f)) { for (const filterComponent of this._filterComponents.filter(f => !!f)) {
filterComponent.deactivateAllFilters(); filterComponent.deactivateFilters();
} }
this.filtersChanged(); this.filtersChanged();
this.searchForm.reset({ query: '' });
} }
// Filter // Filter
@ -155,7 +146,8 @@ export abstract class BaseListingComponent<T = any> {
} }
@debounce(200) @debounce(200)
protected _executeSearch() { executeSearch(value: string) {
this._searchValue = value;
this._executeSearchImmediately(); this._executeSearchImmediately();
} }
@ -163,10 +155,9 @@ export abstract class BaseListingComponent<T = any> {
this.displayedEntities = ( this.displayedEntities = (
this._filters.length ? this.filteredEntities : this.allEntities this._filters.length ? this.filteredEntities : this.allEntities
).filter(entity => ).filter(entity =>
this._searchField(entity) this._searchField(entity).toLowerCase().includes(this._searchValue.toLowerCase())
.toLowerCase()
.includes(this.searchForm.get('query').value.toLowerCase())
); );
this._updateSelection(); this._updateSelection();
} }
@ -183,15 +174,7 @@ export abstract class BaseListingComponent<T = any> {
protected _filterEntities() { protected _filterEntities() {
this._preFilter(); this._preFilter();
this.filteredEntities = getFilteredEntities(this.allEntities, this._filters); this.filteredEntities = getFilteredEntities(this.allEntities, this._filters);
this._executeSearch(); this.executeSearch(this._searchValue);
this._changeDetectorRef.detectChanges(); this._changeDetectorRef.detectChanges();
} }
private _initSearch() {
this.searchForm = this._formBuilder.group({
query: ['']
});
this.searchForm.valueChanges.subscribe(() => this._executeSearch());
}
} }

View File

@ -16,10 +16,10 @@
<mat-menu <mat-menu
#filterMenu="matMenu" #filterMenu="matMenu"
(closed)="applyFilters()" (closed)="applyFilters()"
[class]="secondaryFilters?.length > 0 ? 'padding-bottom-0' : ''" [class.padding-bottom-0]="secondaryFilters?.length > 0"
xPosition="before" xPosition="before"
> >
<div (mouseenter)="filterMouseEnter()" (mouseleave)="filterMouseLeave()"> <ng-template matMenuContent>
<div class="filter-menu-header"> <div class="filter-menu-header">
<div class="all-caps-label" translate="filter-menu.filter-types"></div> <div class="all-caps-label" translate="filter-menu.filter-types"></div>
<div class="actions"> <div class="actions">
@ -35,30 +35,33 @@
></div> ></div>
</div> </div>
</div> </div>
<div *ngFor="let filter of primaryFilters"> <div *ngFor="let filter of primaryFilters">
<ng-template <ng-container
[ngTemplateOutlet]="defaultFilterTemplate"
[ngTemplateOutletContext]="{ [ngTemplateOutletContext]="{
filter: filter, filter: filter,
atLeastOneIsExpandable: atLeastOneFilterIsExpandable atLeastOneIsExpandable: atLeastOneFilterIsExpandable
}" }"
[ngTemplateOutlet]="defaultFilterTemplate" ></ng-container>
></ng-template>
</div> </div>
<div *ngIf="secondaryFilters?.length > 0" class="filter-options"> <div *ngIf="secondaryFilters?.length > 0" class="filter-options">
<div class="filter-menu-options"> <div class="filter-menu-options">
<div class="all-caps-label" translate="filter-menu.filter-options"></div> <div class="all-caps-label" translate="filter-menu.filter-options"></div>
</div> </div>
<div *ngFor="let filter of secondaryFilters"> <div *ngFor="let filter of secondaryFilters">
<ng-template <ng-container
[ngTemplateOutlet]="defaultFilterTemplate"
[ngTemplateOutletContext]="{ [ngTemplateOutletContext]="{
filter: filter, filter: filter,
atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable
}" }"
[ngTemplateOutlet]="defaultFilterTemplate" ></ng-container>
></ng-template>
</div>
</div> </div>
</div> </div>
</ng-template>
</mat-menu> </mat-menu>
<ng-template #defaultFilterLabelTemplate let-filter="filter"> <ng-template #defaultFilterLabelTemplate let-filter="filter">
@ -88,16 +91,17 @@
[indeterminate]="_(filter).indeterminate" [indeterminate]="_(filter).indeterminate"
class="filter-menu-checkbox" class="filter-menu-checkbox"
> >
<ng-template <ng-container
[ngTemplateOutlet]="filterTemplate ?? defaultFilterLabelTemplate"
[ngTemplateOutletContext]="{ filter: filter }" [ngTemplateOutletContext]="{ filter: filter }"
[ngTemplateOutlet]="filterTemplate ? filterTemplate : defaultFilterLabelTemplate" ></ng-container>
></ng-template>
</mat-checkbox> </mat-checkbox>
<ng-template <ng-container
[ngTemplateOutlet]="actionsTemplate ?? null"
[ngTemplateOutletContext]="{ filter: filter }" [ngTemplateOutletContext]="{ filter: filter }"
[ngTemplateOutlet]="actionsTemplate ? actionsTemplate : null" ></ng-container>
></ng-template>
</div> </div>
<div *ngIf="_(filter).filters?.length && _(filter).expanded"> <div *ngIf="_(filter).filters?.length && _(filter).expanded">
<div <div
(click)="$event.stopPropagation()" (click)="$event.stopPropagation()"
@ -108,17 +112,16 @@
(click)="filterCheckboxClicked($event, subFilter, filter)" (click)="filterCheckboxClicked($event, subFilter, filter)"
[checked]="subFilter.checked" [checked]="subFilter.checked"
> >
<ng-template <ng-container
[ngTemplateOutlet]="filterTemplate ?? defaultFilterLabelTemplate"
[ngTemplateOutletContext]="{ filter: subFilter }" [ngTemplateOutletContext]="{ filter: subFilter }"
[ngTemplateOutlet]=" ></ng-container>
filterTemplate ? filterTemplate : defaultFilterLabelTemplate
"
></ng-template>
</mat-checkbox> </mat-checkbox>
<ng-template
<ng-container
[ngTemplateOutlet]="actionsTemplate ?? null"
[ngTemplateOutletContext]="{ filter: subFilter }" [ngTemplateOutletContext]="{ filter: subFilter }"
[ngTemplateOutlet]="actionsTemplate ? actionsTemplate : null" ></ng-container>
></ng-template>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -1,4 +1,5 @@
import { import {
ChangeDetectionStrategy,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
EventEmitter, EventEmitter,
@ -15,6 +16,7 @@ import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
selector: 'redaction-popup-filter', selector: 'redaction-popup-filter',
templateUrl: './popup-filter.component.html', templateUrl: './popup-filter.component.html',
styleUrls: ['./popup-filter.component.scss'], styleUrls: ['./popup-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [ providers: [
{ {
provide: MAT_CHECKBOX_DEFAULT_OPTIONS, provide: MAT_CHECKBOX_DEFAULT_OPTIONS,
@ -38,9 +40,6 @@ export class PopupFilterComponent implements OnChanges {
@Input() icon: string; @Input() icon: string;
@Input() chevron = false; @Input() chevron = false;
mouseOver = true;
mouseOverTimeout: number;
atLeastOneFilterIsExpandable = false; atLeastOneFilterIsExpandable = false;
atLeastOneSecondaryFilterIsExpandable = false; atLeastOneSecondaryFilterIsExpandable = false;
@ -60,44 +59,38 @@ export class PopupFilterComponent implements OnChanges {
} }
ngOnChanges(): void { ngOnChanges(): void {
this.atLeastOneFilterIsExpandable = false; this.atLeastOneFilterIsExpandable = !!this.primaryFilters?.find(f => this.isExpandable(f));
this.atLeastOneSecondaryFilterIsExpandable = false; this.atLeastOneSecondaryFilterIsExpandable = !!this.secondaryFilters?.find(f =>
this.primaryFilters?.forEach(f => { this.isExpandable(f)
this.atLeastOneFilterIsExpandable = );
this.atLeastOneFilterIsExpandable || this.isExpandable(f);
});
this.secondaryFilters?.forEach(f => {
this.atLeastOneSecondaryFilterIsExpandable =
this.atLeastOneSecondaryFilterIsExpandable || this.isExpandable(f);
});
} }
filterCheckboxClicked($event: any, filter: FilterModel, parent?: FilterModel) { filterCheckboxClicked($event: any, filter: FilterModel, parent?: FilterModel) {
$event.stopPropagation(); $event.stopPropagation();
filter.checked = !filter.checked; filter.checked = !filter.checked;
if (parent) { if (parent) {
handleCheckedValue(parent); handleCheckedValue(parent);
} else { } else {
if (filter.indeterminate) { if (filter.indeterminate) filter.checked = false;
filter.checked = false;
}
filter.indeterminate = false; filter.indeterminate = false;
filter.filters?.forEach(f => (f.checked = filter.checked)); filter.filters?.forEach(f => (f.checked = filter.checked));
} }
this._changeDetectorRef.detectChanges();
this.applyFilters(); this.applyFilters();
} }
activateAllFilters() { activatePrimaryFilters() {
this._setAllFilters(true); this._setFilters(true);
} }
deactivateAllFilters() { deactivateFilters() {
this._setAllFilters(false); this._setFilters();
} }
applyFilters() { applyFilters() {
this._changeDetectorRef.detectChanges();
this.filtersChanged.emit({ this.filtersChanged.emit({
primary: this.primaryFilters, primary: this.primaryFilters,
secondary: this.secondaryFilters secondary: this.secondaryFilters
@ -109,20 +102,6 @@ export class PopupFilterComponent implements OnChanges {
filter.expanded = !filter.expanded; filter.expanded = !filter.expanded;
} }
filterMouseEnter() {
this.mouseOver = true;
if (this.mouseOverTimeout) {
clearTimeout(this.mouseOverTimeout);
}
}
filterMouseLeave() {
this.mouseOver = false;
this.mouseOverTimeout = setTimeout(() => {
// this.trigger.closeMenu();
}, 1000);
}
isExpandable(filter: FilterModel) { isExpandable(filter: FilterModel) {
return filter.filters && filter.filters.length > 0; return filter.filters && filter.filters.length > 0;
} }
@ -131,14 +110,13 @@ export class PopupFilterComponent implements OnChanges {
return obj as FilterModel; return obj as FilterModel;
} }
private _setAllFilters(value: boolean) { private _setFilters(onlyPrimaryFilters = false) {
const filters = value ? this.primaryFilters : this._allFilters; const filters = onlyPrimaryFilters ? this.primaryFilters : this._allFilters;
filters.forEach(f => { filters.forEach(f => {
f.checked = value; f.checked = onlyPrimaryFilters;
f.indeterminate = false; f.indeterminate = false;
f.filters?.forEach(ff => { f.filters?.forEach(ff => (ff.checked = onlyPrimaryFilters));
ff.checked = value;
});
}); });
this.applyFilters();
} }
} }

View File

@ -10,16 +10,12 @@ export class QuickFiltersComponent {
@Output() filtersChanged = new EventEmitter<FilterModel[]>(); @Output() filtersChanged = new EventEmitter<FilterModel[]>();
@Input() filters: FilterModel[]; @Input() filters: FilterModel[];
constructor() {}
get hasActiveFilters(): boolean { get hasActiveFilters(): boolean {
return this.filters.filter(f => f.checked).length > 0; return this.filters.filter(f => f.checked).length > 0;
} }
deactivateAllFilters() { deactivateFilters() {
for (const filter of this.filters) { for (const filter of this.filters) filter.checked = false;
filter.checked = false;
}
} }
toggle(filter: FilterModel) { toggle(filter: FilterModel) {

View File

@ -0,0 +1,61 @@
<div class="page-header">
<div *ngIf="pageLabel" class="breadcrumb" translate>{{ pageLabel }}</div>
<div class="filters" *ngIf="filterConfigs">
<div translate="filters.filter-by"></div>
<ng-container *ngFor="let config of filterConfigs; trackBy: trackByLabel">
<redaction-popup-filter
(filtersChanged)="filtersChanged.emit($event)"
*ngIf="!config.hide"
[filterLabel]="config.label"
[icon]="config.icon"
[primaryFilters]="config.primaryFilters"
[filterTemplate]="config.filterTemplate"
></redaction-popup-filter>
</ng-container>
<redaction-input-with-action
[form]="searchForm"
[placeholder]="searchPlaceholder"
type="search"
></redaction-input-with-action>
<div
(click)="resetFilters()"
*ngIf="hasActiveFilters"
class="reset-filters"
translate="reset-filters"
></div>
</div>
<div class="actions" *ngIf="actionConfigs">
<ng-container *ngFor="let config of actionConfigs; trackBy: trackByLabel">
<redaction-circle-button
(action)="config.action($event)"
*ngIf="!config.hide"
[icon]="config.icon"
[tooltip]="config.label"
tooltipPosition="below"
></redaction-circle-button>
</ng-container>
</div>
<ng-container *ngFor="let config of buttonConfigs; trackBy: trackByLabel">
<redaction-icon-button
(action)="config.action($event)"
*ngIf="!config.hide"
[icon]="config.icon"
[text]="config.label"
[type]="config.type"
></redaction-icon-button>
</ng-container>
<redaction-circle-button
*ngIf="showCloseButton && permissionsService.isUser()"
icon="red:close"
redactionNavigateLastDossiersScreen
tooltip="common.close"
tooltipPosition="below"
></redaction-circle-button>
</div>

View File

@ -0,0 +1,3 @@
.page-header .actions > *:not(:last-child) {
margin-right: 16px;
}

View File

@ -0,0 +1,92 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
Output,
QueryList,
TemplateRef,
ViewChildren
} from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { FilterModel } from '@shared/components/filters/popup-filter/model/filter.model';
import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component';
import { FormBuilder, FormGroup } from '@angular/forms';
interface BaseHeaderConfig {
label: string;
icon?: string;
hide?: boolean;
}
export interface FilterConfig extends BaseHeaderConfig {
primaryFilters: FilterModel[];
filterTemplate?: TemplateRef<any>;
}
export interface ActionConfig extends BaseHeaderConfig {
action: ($event) => void;
}
export interface ButtonConfig extends BaseHeaderConfig {
action: ($event) => void;
type?: 'default' | 'show-bg' | 'primary';
}
@Component({
selector: 'redaction-page-header',
templateUrl: './page-header.component.html',
styleUrls: ['./page-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PageHeaderComponent {
@Input() pageLabel: string;
@Input() showCloseButton: boolean;
@Input() filterConfigs: FilterConfig[];
@Input() actionConfigs: ActionConfig[];
@Input() buttonConfigs: ButtonConfig[];
@Input() searchPlaceholder: string;
@Output() filtersChanged = new EventEmitter<any>();
@Output() searchChanged = new EventEmitter<string>();
searchForm: FormGroup;
@ViewChildren(PopupFilterComponent)
private readonly _filterComponents: QueryList<PopupFilterComponent>;
constructor(
readonly permissionsService: PermissionsService,
private readonly _formBuilder: FormBuilder
) {
this._initSearch();
}
get hasActiveFilters() {
return (
this._filterComponents
?.filter(f => !!f)
.reduce((prev, component) => prev || component?.hasActiveFilters, false) ||
this.searchForm.get('query').value
);
}
resetFilters() {
for (const filterComponent of this._filterComponents.filter(f => !!f)) {
filterComponent.deactivateFilters();
}
this.filtersChanged.emit();
this.searchForm.reset({ query: '' });
}
private _initSearch() {
this.searchForm = this._formBuilder.group({
query: ['']
});
this.searchForm.valueChanges.subscribe(value => this.searchChanged.emit(value.query));
}
trackByLabel(index: number, item: BaseHeaderConfig) {
return item.label;
}
}

View File

@ -37,6 +37,7 @@ import { QuickFiltersComponent } from './components/filters/quick-filters/quick-
import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component'; import { PopupFilterComponent } from '@shared/components/filters/popup-filter/popup-filter.component';
import { AssignUserDropdownComponent } from './components/assign-user-dropdown/assign-user-dropdown.component'; import { AssignUserDropdownComponent } from './components/assign-user-dropdown/assign-user-dropdown.component';
import { InputWithActionComponent } from '@shared/components/input-with-action/input-with-action.component'; import { InputWithActionComponent } from '@shared/components/input-with-action/input-with-action.component';
import { PageHeaderComponent } from './components/page-header/page-header.component';
const buttons = [ const buttons = [
ChevronButtonComponent, ChevronButtonComponent,
@ -67,6 +68,7 @@ const components = [
DictionaryManagerComponent, DictionaryManagerComponent,
QuickFiltersComponent, QuickFiltersComponent,
AssignUserDropdownComponent, AssignUserDropdownComponent,
PageHeaderComponent,
...buttons ...buttons
]; ];