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>
<div class="overlay-shadow"></div>
<div class="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>
<redaction-page-header pageLabel="trash.label" [showCloseButton]="true"></redaction-page-header>
<div class="red-content-inner">
<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>
<div class="page-header">
<div class="filters">
<div translate="filters.filter-by"></div>
<redaction-popup-filter
(filtersChanged)="filtersChanged()"
[filterLabel]="'filters.status'"
[icon]="'red:status'"
[primaryFilters]="statusFilters"
></redaction-popup-filter>
<redaction-popup-filter
(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>
<redaction-page-header
(filtersChanged)="filtersChanged()"
(searchChanged)="executeSearch($event)"
[filterConfigs]="filterConfigs"
[buttonConfigs]="buttonConfigs"
[searchPlaceholder]="'dossier-listing.search'"
></redaction-page-header>
<div class="overlay-shadow"></div>
@ -135,7 +93,7 @@
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:document"></mat-icon>
{{ documentCount(dw) }}
{{ filesCount(dw) }}
</div>
<div>
<mat-icon svgIcon="red:pages"></mat-icon>

View File

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

View File

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

View File

@ -5,9 +5,8 @@ import {
Injector,
OnDestroy,
OnInit,
QueryList,
ViewChild,
ViewChildren
TemplateRef,
ViewChild
} from '@angular/core';
import { NavigationStart, Router } from '@angular/router';
import { NotificationService, NotificationType } from '@services/notification.service';
@ -38,9 +37,12 @@ import {
processFilters
} from '@shared/components/filters/popup-filter/utils/filter-utils';
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 { AppConfigService } from '../../../app-config/app-config.service';
import {
ActionConfig,
FilterConfig
} from '../../../shared/components/page-header/page-header.component';
@Component({
selector: 'redaction-dossier-overview-screen',
@ -61,6 +63,8 @@ export class DossierOverviewScreenComponent
} = { needsWorkFilters: [], statusFilters: [] };
readonly itemSize = 80;
quickFilters: FilterModel[];
filterConfigs: FilterConfig[];
actionConfigs: ActionConfig[];
protected readonly _searchKey = 'searchField';
protected readonly _selectionKey = 'fileId';
@ -74,11 +78,10 @@ export class DossierOverviewScreenComponent
private _lastScrollPosition: number;
private _lastOpenedFileId = '';
@ViewChildren(PopupFilterComponent)
private readonly _filterList: QueryList<PopupFilterComponent>;
@ViewChild(QuickFiltersComponent)
protected readonly _quickFiltersComponent: QuickFiltersComponent;
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
private readonly _needsWorkTemplate: TemplateRef<any>;
@ViewChild('fileInput') private _fileInput: ElementRef;
constructor(
@ -182,7 +185,7 @@ export class DossierOverviewScreenComponent
}
});
this._filterComponents = [...this._filterList.toArray(), this._quickFiltersComponent];
this._filterComponents = [this._quickFiltersComponent];
}
ngOnDestroy(): void {
@ -393,10 +396,12 @@ export class DossierOverviewScreenComponent
);
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) {
const recentPeriodInHours = this._appConfigService.getConfig('RECENT_PERIOD_IN_HOURS');
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
protected _filterComponents: (PopupFilterComponent | QuickFiltersComponent)[] = [];
private _searchValue = '';
protected constructor(protected readonly _injector: Injector) {
this._formBuilder = this._injector.get<FormBuilder>(FormBuilder);
this._changeDetectorRef = this._injector.get<ChangeDetectorRef>(ChangeDetectorRef);
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() {
@ -108,10 +100,9 @@ export abstract class BaseListingComponent<T = any> {
resetFilters() {
for (const filterComponent of this._filterComponents.filter(f => !!f)) {
filterComponent.deactivateAllFilters();
filterComponent.deactivateFilters();
}
this.filtersChanged();
this.searchForm.reset({ query: '' });
}
// Filter
@ -155,7 +146,8 @@ export abstract class BaseListingComponent<T = any> {
}
@debounce(200)
protected _executeSearch() {
executeSearch(value: string) {
this._searchValue = value;
this._executeSearchImmediately();
}
@ -163,10 +155,9 @@ export abstract class BaseListingComponent<T = any> {
this.displayedEntities = (
this._filters.length ? this.filteredEntities : this.allEntities
).filter(entity =>
this._searchField(entity)
.toLowerCase()
.includes(this.searchForm.get('query').value.toLowerCase())
this._searchField(entity).toLowerCase().includes(this._searchValue.toLowerCase())
);
this._updateSelection();
}
@ -183,15 +174,7 @@ export abstract class BaseListingComponent<T = any> {
protected _filterEntities() {
this._preFilter();
this.filteredEntities = getFilteredEntities(this.allEntities, this._filters);
this._executeSearch();
this.executeSearch(this._searchValue);
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
#filterMenu="matMenu"
(closed)="applyFilters()"
[class]="secondaryFilters?.length > 0 ? 'padding-bottom-0' : ''"
[class.padding-bottom-0]="secondaryFilters?.length > 0"
xPosition="before"
>
<div (mouseenter)="filterMouseEnter()" (mouseleave)="filterMouseLeave()">
<ng-template matMenuContent>
<div class="filter-menu-header">
<div class="all-caps-label" translate="filter-menu.filter-types"></div>
<div class="actions">
@ -35,30 +35,33 @@
></div>
</div>
</div>
<div *ngFor="let filter of primaryFilters">
<ng-template
<ng-container
[ngTemplateOutlet]="defaultFilterTemplate"
[ngTemplateOutletContext]="{
filter: filter,
atLeastOneIsExpandable: atLeastOneFilterIsExpandable
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-template>
></ng-container>
</div>
<div *ngIf="secondaryFilters?.length > 0" class="filter-options">
<div class="filter-menu-options">
<div class="all-caps-label" translate="filter-menu.filter-options"></div>
</div>
<div *ngFor="let filter of secondaryFilters">
<ng-template
<ng-container
[ngTemplateOutlet]="defaultFilterTemplate"
[ngTemplateOutletContext]="{
filter: filter,
atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-template>
></ng-container>
</div>
</div>
</div>
</ng-template>
</mat-menu>
<ng-template #defaultFilterLabelTemplate let-filter="filter">
@ -88,16 +91,17 @@
[indeterminate]="_(filter).indeterminate"
class="filter-menu-checkbox"
>
<ng-template
<ng-container
[ngTemplateOutlet]="filterTemplate ?? defaultFilterLabelTemplate"
[ngTemplateOutletContext]="{ filter: filter }"
[ngTemplateOutlet]="filterTemplate ? filterTemplate : defaultFilterLabelTemplate"
></ng-template>
></ng-container>
</mat-checkbox>
<ng-template
<ng-container
[ngTemplateOutlet]="actionsTemplate ?? null"
[ngTemplateOutletContext]="{ filter: filter }"
[ngTemplateOutlet]="actionsTemplate ? actionsTemplate : null"
></ng-template>
></ng-container>
</div>
<div *ngIf="_(filter).filters?.length && _(filter).expanded">
<div
(click)="$event.stopPropagation()"
@ -108,17 +112,16 @@
(click)="filterCheckboxClicked($event, subFilter, filter)"
[checked]="subFilter.checked"
>
<ng-template
<ng-container
[ngTemplateOutlet]="filterTemplate ?? defaultFilterLabelTemplate"
[ngTemplateOutletContext]="{ filter: subFilter }"
[ngTemplateOutlet]="
filterTemplate ? filterTemplate : defaultFilterLabelTemplate
"
></ng-template>
></ng-container>
</mat-checkbox>
<ng-template
<ng-container
[ngTemplateOutlet]="actionsTemplate ?? null"
[ngTemplateOutletContext]="{ filter: subFilter }"
[ngTemplateOutlet]="actionsTemplate ? actionsTemplate : null"
></ng-template>
></ng-container>
</div>
</div>
</ng-template>

View File

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

View File

@ -10,16 +10,12 @@ export class QuickFiltersComponent {
@Output() filtersChanged = new EventEmitter<FilterModel[]>();
@Input() filters: FilterModel[];
constructor() {}
get hasActiveFilters(): boolean {
return this.filters.filter(f => f.checked).length > 0;
}
deactivateAllFilters() {
for (const filter of this.filters) {
filter.checked = false;
}
deactivateFilters() {
for (const filter of this.filters) filter.checked = false;
}
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 { AssignUserDropdownComponent } from './components/assign-user-dropdown/assign-user-dropdown.component';
import { InputWithActionComponent } from '@shared/components/input-with-action/input-with-action.component';
import { PageHeaderComponent } from './components/page-header/page-header.component';
const buttons = [
ChevronButtonComponent,
@ -67,6 +68,7 @@ const components = [
DictionaryManagerComponent,
QuickFiltersComponent,
AssignUserDropdownComponent,
PageHeaderComponent,
...buttons
];