fix icons bg, filter padding, make filter popup reactive

This commit is contained in:
Dan Percic 2021-08-13 21:46:48 +03:00
parent 3b58497fed
commit 10cdc205f3
14 changed files with 228 additions and 240 deletions

View File

@ -15,7 +15,7 @@ export class FileStatusWrapper implements FileStatus {
readonly analysisDuration = this.fileStatus.analysisDuration;
readonly analysisRequired = this.fileStatus.analysisRequired && !this.fileStatus.excluded;
readonly approvalDate = this.fileStatus.approvalDate;
currentReviewer = this.fileStatus.currentReviewer;
readonly currentReviewer = this.fileStatus.currentReviewer;
readonly dictionaryVersion = this.fileStatus.dictionaryVersion;
readonly dossierDictionaryVersion = this.fileStatus.dossierDictionaryVersion;
readonly dossierId = this.fileStatus.dossierId;
@ -67,6 +67,7 @@ export class FileStatusWrapper implements FileStatus {
readonly hintsOnly = this.hasHints && !this.hasRedactions;
readonly hasNone = !this.hasRedactions && !this.hasHints && !this.hasSuggestions;
readonly isUnassigned = !this.currentReviewer
readonly isError = this.status === FileStatus.StatusEnum.ERROR;
readonly isProcessing = processingStatuses.includes(this.status);
readonly isApproved = this.status === FileStatus.StatusEnum.APPROVED;
@ -78,10 +79,6 @@ export class FileStatusWrapper implements FileStatus {
readonly isWorkable = !this.isProcessing && this.canBeOpened;
readonly canBeOCRed = !this.excluded && !this.lastOCRTime && ['UNASSIGNED', 'UNDER_REVIEW', 'UNDER_APPROVAL'].includes(this.status);
get isUnassigned() {
return !this.currentReviewer;
}
private get _pages() {
if (this.fileStatus.status === 'ERROR') {
return -1;

View File

@ -7,11 +7,9 @@
translate="file-preview.tabs.annotations.select"
></div>
<redaction-popup-filter
(filtersChanged)="filtersChanged($event)"
[actionsTemplate]="annotationFilterActionTemplate"
[filterTemplate]="annotationFilterTemplate"
[primaryFilters]="primaryFilters"
[secondaryFilters]="secondaryFilters"
[primaryFiltersSlug]="'primaryFilters'"
[secondaryFiltersSlug]="'secondaryFilters'"
></redaction-popup-filter>
</div>
</div>
@ -233,14 +231,6 @@
</div>
</div>
<ng-template #annotationFilterTemplate let-filter="filter">
<redaction-type-filter *ngIf="_(filter).topLevelFilter" [filter]="filter"></redaction-type-filter>
<ng-container *ngIf="!_(filter).topLevelFilter">
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.key"></redaction-dictionary-annotation-icon>
{{ filter.key | humanize: false }}
</ng-container>
</ng-template>
<ng-template #annotationFilterActionTemplate let-filter="filter">
<iqser-circle-button
(action)="toggleSkipped.emit($event)"

View File

@ -38,8 +38,6 @@ export class FileWorkloadComponent {
@Input() activeViewerPage: number;
@Input() shouldDeselectAnnotationsOnPageChange: boolean;
@Input() dialogRef: MatDialogRef<any>;
@Input() primaryFilters: NestedFilter[];
@Input() secondaryFilters: NestedFilter[];
@Input() fileData: FileDataModel;
@Input() hideSkipped: boolean;
@Input() excludePages: boolean;
@ -267,10 +265,6 @@ export class FileWorkloadComponent {
this.selectPage.emit(this._nextPageWithAnnotations());
}
_(filter): NestedFilter {
return filter as NestedFilter;
}
private _selectFirstAnnotationOnCurrentPageIfNecessary() {
if (
(!this._firstSelectedAnnotation || this.activeViewerPage !== this._firstSelectedAnnotation.pageNumber) &&

View File

@ -23,54 +23,54 @@
<cdk-virtual-scroll-viewport #scrollViewport [itemSize]="itemSize" redactionHasScrollbar>
<div
*cdkVirtualFor="let item of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[class.pointer]="!!item"
[routerLink]="[item.routerLink]"
*cdkVirtualFor="let dossier of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[class.pointer]="!!dossier"
[routerLink]="['/main/dossiers/' + dossier.dossierId.toString()]"
class="table-item"
>
<div class="filename">
<div [matTooltip]="item.dossierName" class="table-item-title heading" matTooltipPosition="above">
{{ item.dossierName }}
<div [matTooltip]="dossier.dossierName" class="table-item-title heading" matTooltipPosition="above">
{{ dossier.dossierName }}
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:template"></mat-icon>
{{ item.dossierTemplateName }}
{{ dossier.dossierTemplateName }}
</div>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:document"></mat-icon>
{{ item.dossier.filesLength }}
{{ dossier.filesLength }}
</div>
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ item.dossier.totalNumberOfPages }}
{{ dossier.totalNumberOfPages }}
</div>
<div>
<mat-icon svgIcon="red:user"></mat-icon>
{{ item.dossier.memberCount }}
{{ dossier.memberCount }}
</div>
<div>
<mat-icon svgIcon="red:calendar"></mat-icon>
{{ item.dossier.date | date: 'mediumDate' }}
{{ dossier.date | date: 'mediumDate' }}
</div>
<div *ngIf="item.dossier.dueDate">
<div *ngIf="dossier.dueDate">
<mat-icon svgIcon="red:lightning"></mat-icon>
{{ item.dossier.dueDate | date: 'mediumDate' }}
{{ dossier.dueDate | date: 'mediumDate' }}
</div>
</div>
</div>
<div>
<redaction-needs-work-badge [needsWorkInput]="item.dossier"></redaction-needs-work-badge>
<redaction-needs-work-badge [needsWorkInput]="dossier"></redaction-needs-work-badge>
</div>
<div class="user-column">
<redaction-initials-avatar [userId]="item.dossier.ownerId" [withName]="true"></redaction-initials-avatar>
<redaction-initials-avatar [userId]="dossier.ownerId" [withName]="true"></redaction-initials-avatar>
</div>
<div class="status-container">
<redaction-dossier-listing-actions
(actionPerformed)="calculateData()"
[dossier]="item.dossier"
[dossier]="dossier"
></redaction-dossier-listing-actions>
</div>
<div class="scrollbar-placeholder"></div>

View File

@ -29,20 +29,13 @@ import {
import { PermissionsService } from '@services/permissions.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
interface ListItem {
readonly dossierName: string;
readonly routerLink: string;
readonly dossierTemplateName: string;
readonly dossier: DossierWrapper;
}
@Component({
templateUrl: './dossier-listing-screen.component.html',
styleUrls: ['./dossier-listing-screen.component.scss'],
providers: [...DefaultListingServices]
})
export class DossierListingScreenComponent
extends ListingComponent<ListItem>
extends ListingComponent<DossierWrapper>
implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach
{
readonly itemSize = 85;
@ -58,7 +51,7 @@ export class DossierListingScreenComponent
type: 'primary'
}
];
readonly tableColumnConfigs: readonly TableColumnConfig<ListItem>[] = [
readonly tableColumnConfigs: readonly TableColumnConfig<DossierWrapper>[] = [
{
label: _('dossier-listing.table-col-names.name'),
sortByKey: 'dossierName'
@ -102,7 +95,7 @@ export class DossierListingScreenComponent
}
private get _activeDossiersCount(): number {
return this.entitiesService.all.filter(p => p.dossier.status === Dossier.StatusEnum.ACTIVE).length;
return this.entitiesService.all.filter(p => p.status === Dossier.StatusEnum.ACTIVE).length;
}
private get _inactiveDossiersCount(): number {
@ -178,17 +171,8 @@ export class DossierListingScreenComponent
}
private _loadEntitiesFromState() {
const entities = this._appStateService.allDossiers.map(dossier => this._toListItem(dossier));
this.entitiesService.setEntities(entities);
}
private _toListItem(dossier: DossierWrapper): ListItem {
return {
dossierName: dossier.dossierName,
routerLink: '/main/dossiers/' + dossier.dossierId.toString(),
dossierTemplateName: this._getDossierTemplate(dossier.dossierTemplateId).name,
dossier
};
console.log(this._appStateService.allDossiers);
this.entitiesService.setEntities(this._appStateService.allDossiers);
}
private _computeAllFilters() {
@ -199,18 +183,18 @@ export class DossierListingScreenComponent
this.entitiesService.all?.forEach(entry => {
// all people
entry.dossier.memberIds.forEach(f => allDistinctPeople.add(f));
entry.memberIds.forEach(f => allDistinctPeople.add(f));
// Needs work
entry.dossier.files.forEach(file => {
entry.files.forEach(file => {
allDistinctFileStatus.add(file.status);
if (file.analysisRequired) allDistinctNeedsWork.add('analysis');
if (entry.dossier.hintsOnly) allDistinctNeedsWork.add('hint');
if (entry.dossier.hasRedactions) allDistinctNeedsWork.add('redaction');
if (entry.dossier.hasSuggestions) allDistinctNeedsWork.add('suggestion');
if (entry.dossier.hasNone) allDistinctNeedsWork.add('none');
if (entry.hintsOnly) allDistinctNeedsWork.add('hint');
if (entry.hasRedactions) allDistinctNeedsWork.add('redaction');
if (entry.hasSuggestions) allDistinctNeedsWork.add('suggestion');
if (entry.hasNone) allDistinctNeedsWork.add('none');
});
allDistinctDossierTemplates.add(entry.dossier.dossierTemplateId);
allDistinctDossierTemplates.add(entry.dossierTemplateId);
});
const statusFilters = [...allDistinctFileStatus].map<NestedFilter>(status => ({

View File

@ -177,8 +177,6 @@
[excludePages]="excludePages"
[fileData]="fileData"
[hideSkipped]="hideSkipped"
[primaryFilters]="primaryFilters"
[secondaryFilters]="secondaryFilters"
[selectedAnnotations]="selectedAnnotations"
[viewer]="activeViewer"
></redaction-file-workload>
@ -194,3 +192,11 @@
[viewer]="activeViewer"
></redaction-annotation-actions>
</ng-template>
<ng-template #annotationFilterTemplate let-filter="filter">
<redaction-type-filter *ngIf="filter.topLevelFilter" [filter]="filter"></redaction-type-filter>
<ng-container *ngIf="!filter.topLevelFilter">
<redaction-dictionary-annotation-icon [dictionaryKey]="filter.key"></redaction-dictionary-annotation-icon>
{{ filter.key | humanize: false }}
</ng-container>
</ng-template>

View File

@ -1,9 +1,9 @@
import { ChangeDetectorRef, Component, HostListener, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { ChangeDetectorRef, Component, HostListener, NgZone, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationExtras, Router } from '@angular/router';
import { AppStateService } from '@state/app-state.service';
import { Annotations, WebViewerInstance } from '@pdftron/webviewer';
import { PdfViewerComponent } from '../../components/pdf-viewer/pdf-viewer.component';
import { AutoUnsubscribe, CircleButtonTypes, Debounce, NestedFilter, processFilters } from '@iqser/common-ui';
import { AutoUnsubscribe, CircleButtonTypes, Debounce, FilterService, NestedFilter, processFilters } from '@iqser/common-ui';
import { MatDialogRef, MatDialogState } from '@angular/material/dialog';
import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
@ -41,7 +41,8 @@ const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
@Component({
templateUrl: './file-preview-screen.component.html',
styleUrls: ['./file-preview-screen.component.scss']
styleUrls: ['./file-preview-screen.component.scss'],
providers: [FilterService]
})
export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnInit, OnDestroy, OnAttach, OnDetach {
readonly circleButtonTypes = CircleButtonTypes;
@ -55,8 +56,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
fileData: FileDataModel;
annotationData: AnnotationData;
selectedAnnotations: AnnotationWrapper[];
primaryFilters: NestedFilter[];
secondaryFilters: NestedFilter[];
canPerformAnnotationActions: boolean;
hideSkipped = false;
displayPDFViewer = false;
@ -67,6 +66,11 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private _lastPage: string;
private _reloadFileOnReanalysis = false;
@ViewChild('fileWorkloadComponent') private _workloadComponent: FileWorkloadComponent;
@ViewChild('annotationFilterTemplate', {
read: TemplateRef,
static: true
})
private readonly _filterTemplate: TemplateRef<NestedFilter>;
constructor(
readonly appStateService: AppStateService,
@ -87,6 +91,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
private readonly _ngZone: NgZone,
private readonly _fileManagementControllerService: FileManagementControllerService,
private readonly _loadingService: LoadingService,
private readonly _filterService: FilterService,
private readonly _translateService: TranslateService
) {
super();
@ -269,11 +274,16 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
this.userPreferenceService.areDevFeaturesEnabled
);
const annotationFilters = this._annotationProcessingService.getAnnotationFilter(this.annotations);
this.primaryFilters = processFilters(this.primaryFilters, annotationFilters);
this.secondaryFilters = processFilters(this.secondaryFilters, AnnotationProcessingService.secondaryAnnotationFilters);
this._workloadComponent?.filtersChanged({
primary: this.primaryFilters,
secondary: this.secondaryFilters
const primaryFilters = this._filterService.getGroup('primaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'primaryFilters',
filterTemplate: this._filterTemplate,
filters: processFilters(primaryFilters, annotationFilters)
});
const secondaryFilters = this._filterService.getGroup('secondaryFilters')?.filters;
this._filterService.addFilterGroup({
slug: 'secondaryFilters',
filters: processFilters(secondaryFilters, AnnotationProcessingService.secondaryAnnotationFilters)
});
console.log('[REDACTION] Process time: ' + (new Date().getTime() - processStartTime) + 'ms');
console.log(
@ -625,8 +635,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
}
private _handleDeltaAnnotationFilters(currentPageAnnotations: AnnotationWrapper[], newPageAnnotations: AnnotationWrapper[]) {
const hasAnyFilterSet =
this.primaryFilters.find(f => f.checked || f.indeterminate) || this.secondaryFilters.find(f => f.checked || f.indeterminate);
const primaryFilterGroup = this._filterService.getGroup('primaryFilters');
const primaryFilters = primaryFilterGroup.filters;
const secondaryFilters = this._filterService.getGroup('secondaryFilters').filters;
const hasAnyFilterSet = [...primaryFilters, ...secondaryFilters].find(f => f.checked || f.indeterminate);
if (!hasAnyFilterSet) {
return;
@ -634,10 +646,10 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
const oldPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(currentPageAnnotations);
const newPageSpecificFilters = this._annotationProcessingService.getAnnotationFilter(newPageAnnotations);
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, this.primaryFilters);
this._workloadComponent.filtersChanged({
primary: this.primaryFilters,
secondary: this.secondaryFilters
handleFilterDelta(oldPageSpecificFilters, newPageSpecificFilters, primaryFilters);
this._filterService.addFilterGroup({
...primaryFilterGroup,
filters: primaryFilters
});
}

View File

@ -1,101 +1,109 @@
<iqser-icon-button
*ngIf="icon"
[icon]="icon"
[label]="filterLabel || ('filter-menu.label' | translate)"
[matMenuTriggerFor]="filterMenu"
[showDot]="hasActiveFilters"
></iqser-icon-button>
<ng-container *ngIf="primaryFilterGroup$ | async as primaryGroup">
<iqser-icon-button
*ngIf="primaryGroup.icon"
[icon]="primaryGroup.icon"
[label]="primaryGroup.label || ('filter-menu.label' | translate)"
[matMenuTriggerFor]="filterMenu"
[showDot]="hasActiveFilters$ | async"
[attr.aria-expanded]="expanded$ | async"
></iqser-icon-button>
<iqser-chevron-button
*ngIf="!icon"
[label]="filterLabel || ('filter-menu.label' | translate)"
[matMenuTriggerFor]="filterMenu"
[showDot]="hasActiveFilters"
></iqser-chevron-button>
<iqser-chevron-button
*ngIf="!primaryGroup.icon"
[label]="primaryGroup.label || ('filter-menu.label' | translate)"
[matMenuTriggerFor]="filterMenu"
[showDot]="hasActiveFilters$ | async"
[attr.aria-expanded]="expanded$ | async"
></iqser-chevron-button>
<mat-menu #filterMenu="matMenu" (closed)="applyFilters()" [class.padding-bottom-0]="secondaryFilters?.length > 0" xPosition="before">
<ng-template matMenuContent>
<div class="filter-menu-header">
<div class="all-caps-label" translate="filter-menu.filter-types"></div>
<div class="actions">
<div
(click)="activatePrimaryFilters(); $event.stopPropagation()"
class="all-caps-label primary pointer"
translate="actions.all"
></div>
<div
(click)="deactivateFilters(); $event.stopPropagation()"
class="all-caps-label primary pointer"
translate="actions.none"
></div>
<mat-menu
#filterMenu="matMenu"
(close)="expanded.next(false)"
[class]="(secondaryFilterGroup$ | async)?.filters.length > 0 ? 'padding-bottom-0' : ''"
xPosition="before"
>
<ng-template matMenuContent>
<div class="filter-menu-header">
<div class="all-caps-label" translate="filter-menu.filter-types"></div>
<div class="actions">
<div
(click)="activatePrimaryFilters(); $event.stopPropagation()"
class="all-caps-label primary pointer"
translate="actions.all"
></div>
<div
(click)="deactivateFilters(); $event.stopPropagation()"
class="all-caps-label primary pointer"
translate="actions.none"
></div>
</div>
</div>
</div>
<div class="filter-content">
<div *ngFor="let filter of primaryFilters">
<div class="filter-content">
<ng-container
*ngFor="let filter of primaryGroup.filters"
[ngTemplateOutletContext]="{
filter: filter,
atLeastOneIsExpandable: atLeastOneFilterIsExpandable
atLeastOneIsExpandable: atLeastOneFilterIsExpandable$ | async
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-container>
</div>
<div *ngIf="secondaryFilterGroup$ | async as secondaryGroup" 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 secondaryGroup.filters">
<ng-container
[ngTemplateOutletContext]="{
filter: filter,
atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable$ | async
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-container>
</div>
</div>
</ng-template>
</mat-menu>
<ng-template #defaultFilterLabelTemplate let-filter="filter">
{{ filter?.label }}
</ng-template>
<ng-template #defaultFilterTemplate let-atLeastOneIsExpandable="atLeastOneIsExpandable" let-filter="filter">
<div (click)="toggleFilterExpanded($event, filter)" class="mat-menu-item flex">
<div *ngIf="filter.children?.length > 0" class="arrow-wrapper">
<mat-icon *ngIf="filter.expanded" color="accent" svgIcon="iqser:arrow-down"></mat-icon>
<mat-icon *ngIf="!filter.expanded" color="accent" svgIcon="red:arrow-right"></mat-icon>
</div>
<div *ngIf="atLeastOneIsExpandable && filter.children?.length === 0" class="arrow-wrapper spacer">&nbsp;</div>
<mat-checkbox
(click)="filterCheckboxClicked($event, filter)"
[checked]="filter.checked"
[indeterminate]="filter.indeterminate"
class="filter-menu-checkbox"
>
<ng-container
[ngTemplateOutletContext]="{ filter: filter }"
[ngTemplateOutlet]="primaryGroup.filterTemplate ?? defaultFilterLabelTemplate"
></ng-container>
</mat-checkbox>
<ng-container [ngTemplateOutletContext]="{ filter: filter }" [ngTemplateOutlet]="actionsTemplate"></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 *ngIf="filter.children?.length && filter.expanded">
<div (click)="$event.stopPropagation()" *ngFor="let child of filter.children" class="padding-left mat-menu-item">
<mat-checkbox (click)="filterCheckboxClicked($event, child, filter)" [checked]="child.checked">
<ng-container
[ngTemplateOutletContext]="{ filter: child }"
[ngTemplateOutlet]="primaryGroup.filterTemplate ?? defaultFilterLabelTemplate"
></ng-container>
</mat-checkbox>
<div *ngFor="let filter of secondaryFilters">
<ng-container
[ngTemplateOutletContext]="{
filter: filter,
atLeastOneIsExpandable: atLeastOneSecondaryFilterIsExpandable
}"
[ngTemplateOutlet]="defaultFilterTemplate"
></ng-container>
<ng-container [ngTemplateOutletContext]="{ filter: child }" [ngTemplateOutlet]="actionsTemplate"></ng-container>
</div>
</div>
</ng-template>
</mat-menu>
<ng-template #defaultFilterLabelTemplate let-filter="filter">
{{ _(filter)?.label }}
</ng-template>
<ng-template #defaultFilterTemplate let-atLeastOneIsExpandable="atLeastOneIsExpandable" let-filter="filter">
<div (click)="toggleFilterExpanded($event, filter)" class="mat-menu-item flex">
<div *ngIf="isExpandable(filter)" class="arrow-wrapper">
<mat-icon *ngIf="_(filter).expanded" color="accent" svgIcon="iqser:arrow-down"></mat-icon>
<mat-icon *ngIf="!_(filter).expanded" color="accent" svgIcon="red:arrow-right"></mat-icon>
</div>
<div *ngIf="atLeastOneIsExpandable && !isExpandable(filter)" class="arrow-wrapper spacer">&nbsp;</div>
<mat-checkbox
(click)="filterCheckboxClicked($event, filter)"
[checked]="_(filter).checked"
[indeterminate]="_(filter).indeterminate"
class="filter-menu-checkbox"
>
<ng-container
[ngTemplateOutletContext]="{ filter: filter }"
[ngTemplateOutlet]="filterTemplate ?? defaultFilterLabelTemplate"
></ng-container>
</mat-checkbox>
<ng-container [ngTemplateOutletContext]="{ filter: filter }" [ngTemplateOutlet]="actionsTemplate ?? null"></ng-container>
</div>
<div *ngIf="_(filter).children?.length && _(filter).expanded">
<div (click)="$event.stopPropagation()" *ngFor="let subFilter of _(filter).children" class="padding-left mat-menu-item">
<mat-checkbox (click)="filterCheckboxClicked($event, subFilter, filter)" [checked]="subFilter.checked">
<ng-container
[ngTemplateOutletContext]="{ filter: subFilter }"
[ngTemplateOutlet]="filterTemplate ?? defaultFilterLabelTemplate"
></ng-container>
</mat-checkbox>
<ng-container [ngTemplateOutletContext]="{ filter: subFilter }" [ngTemplateOutlet]="actionsTemplate ?? null"></ng-container>
</div>
</div>
</ng-template>
</ng-container>

View File

@ -1,12 +1,14 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, TemplateRef } from '@angular/core';
import { NestedFilter } from '@iqser/common-ui';
import { handleCheckedValue } from '@iqser/common-ui';
import { ChangeDetectionStrategy, Component, Input, OnInit, TemplateRef } from '@angular/core';
import { FilterGroup, FilterService, handleCheckedValue, NestedFilter } from '@iqser/common-ui';
import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { delay, distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
@Component({
selector: 'redaction-popup-filter',
templateUrl: './popup-filter.component.html',
styleUrls: ['./popup-filter.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: MAT_CHECKBOX_DEFAULT_OPTIONS,
@ -17,88 +19,84 @@ import { MAT_CHECKBOX_DEFAULT_OPTIONS } from '@angular/material/checkbox';
}
]
})
export class PopupFilterComponent implements OnChanges {
@Output() filtersChanged = new EventEmitter<{
primary: NestedFilter[];
secondary?: NestedFilter[];
}>();
@Input() filterTemplate: TemplateRef<any>;
export class PopupFilterComponent implements OnInit {
@Input() actionsTemplate: TemplateRef<any>;
@Input() primaryFilters: NestedFilter[] = [];
@Input() secondaryFilters: NestedFilter[] = [];
@Input() filterLabel;
@Input() icon: string;
@Input() primaryFiltersSlug: string;
@Input() secondaryFiltersSlug: string;
atLeastOneFilterIsExpandable = false;
atLeastOneSecondaryFilterIsExpandable = false;
atLeastOneFilterIsExpandable$?: Observable<boolean>;
atLeastOneSecondaryFilterIsExpandable$?: Observable<boolean>;
hasActiveFilters$?: Observable<boolean>;
expanded = new BehaviorSubject<boolean>(null);
expanded$ = this.expanded.asObservable().pipe(delay(200));
constructor(private readonly _changeDetectorRef: ChangeDetectorRef) {}
primaryFilterGroup$?: Observable<FilterGroup>;
secondaryFilterGroup$?: Observable<FilterGroup>;
get hasActiveFilters(): boolean {
return !!this._allFilters.find(f => f.checked || f.indeterminate);
constructor(readonly filterService: FilterService) {}
ngOnInit() {
this.primaryFilterGroup$ = this.filterService.getGroup$(this.primaryFiltersSlug);
this.secondaryFilterGroup$ = this.filterService.getGroup$(this.secondaryFiltersSlug);
this.hasActiveFilters$ = combineLatest([this.primaryFilterGroup$, this.secondaryFilterGroup$]).pipe(
map(([primary, secondary]) => [...primary.filters, ...(secondary?.filters || [])]),
map(filters => filters.some(f => f.checked || f.indeterminate)),
distinctUntilChanged()
);
this.atLeastOneFilterIsExpandable$ = this.primaryFilterGroup$.pipe(
map(group => group.filters.some(f => this.isExpandable(f))),
distinctUntilChanged(),
shareReplay()
);
this.atLeastOneSecondaryFilterIsExpandable$ = this.secondaryFilterGroup$.pipe(
map(group => group?.filters.some(f => this.isExpandable(f))),
distinctUntilChanged(),
shareReplay()
);
}
private get _allFilters(): NestedFilter[] {
return [...(this.primaryFilters ?? []), ...(this.secondaryFilters ?? [])];
}
ngOnChanges(): void {
this.atLeastOneFilterIsExpandable = !!this.primaryFilters?.find(f => this.isExpandable(f));
this.atLeastOneSecondaryFilterIsExpandable = !!this.secondaryFilters?.find(f => this.isExpandable(f));
}
filterCheckboxClicked($event: any, filter: NestedFilter, parent?: NestedFilter) {
filterCheckboxClicked($event: any, nestedFilter: NestedFilter, parent?: NestedFilter) {
$event.stopPropagation();
filter.checked = !filter.checked;
nestedFilter.checked = !nestedFilter.checked;
if (parent) {
handleCheckedValue(parent);
} else {
if (filter.indeterminate) filter.checked = false;
filter.indeterminate = false;
filter.children?.forEach(f => (f.checked = filter.checked));
if (nestedFilter.indeterminate) nestedFilter.checked = false;
nestedFilter.indeterminate = false;
nestedFilter.children?.forEach(f => (f.checked = nestedFilter.checked));
}
this.applyFilters();
this.filterService.refresh();
}
activatePrimaryFilters() {
this._setFilters(true);
this._setFilters(this.primaryFiltersSlug, true);
}
deactivateFilters() {
this._setFilters();
this._setFilters(this.primaryFiltersSlug);
this._setFilters(this.secondaryFiltersSlug);
}
applyFilters() {
this.filtersChanged.emit({
primary: this.primaryFilters,
secondary: this.secondaryFilters
});
this._changeDetectorRef.detectChanges();
}
toggleFilterExpanded($event: MouseEvent, filter: NestedFilter) {
toggleFilterExpanded($event: MouseEvent, nestedFilter: NestedFilter) {
$event.stopPropagation();
filter.expanded = !filter.expanded;
nestedFilter.expanded = !nestedFilter.expanded;
this.filterService.refresh();
}
isExpandable(filter: NestedFilter) {
return filter?.children?.length > 0;
isExpandable(nestedFilter: NestedFilter) {
return nestedFilter?.children?.length > 0;
}
_(obj): NestedFilter {
return obj as NestedFilter;
}
private _setFilters(onlyPrimaryFilters = false) {
const filters = onlyPrimaryFilters ? this.primaryFilters : this._allFilters;
private _setFilters(filterGroup: string, checked = false) {
const filters = this.filterService.getGroup(filterGroup).filters;
filters.forEach(f => {
f.checked = onlyPrimaryFilters;
f.checked = checked;
f.indeterminate = false;
f.children?.forEach(ff => (ff.checked = onlyPrimaryFilters));
f.children?.forEach(ff => (ff.checked = checked));
});
this.applyFilters();
this.filterService.refresh();
}
}

View File

@ -38,7 +38,7 @@ export class InitialsAvatarComponent extends AutoUnsubscribe implements OnChange
}
get disabled(): boolean {
return this.user && !this.user?.isActive;
return this.user && !this.user.isActive;
}
private get _colorClass() {

View File

@ -7,14 +7,7 @@
<ng-container *ngIf="searchPosition === searchPositions.beforeFilters" [ngTemplateOutlet]="searchBar"></ng-container>
<ng-container *ngFor="let config of filters; trackBy: trackByLabel">
<redaction-popup-filter
(filtersChanged)="filterService.refresh()"
*ngIf="!config.hide"
[filterLabel]="config.label"
[filterTemplate]="config.filterTemplate"
[icon]="config.icon"
[primaryFilters]="config.filters"
></redaction-popup-filter>
<redaction-popup-filter *ngIf="!config.hide" [primaryFiltersSlug]="config.slug"></redaction-popup-filter>
</ng-container>
<ng-container *ngIf="searchPosition === searchPositions.afterFilters" [ngTemplateOutlet]="searchBar"></ng-container>

View File

@ -222,7 +222,9 @@ export class AppStateService {
return;
}
const mappedDossiers = dossiers.map(p => new DossierWrapper(p, this._getExistingFiles(p.dossierId)));
const mappedDossiers = dossiers.map(
p => new DossierWrapper(p, this.getDossierTemplateById(p.dossierTemplateId).name, this._getExistingFiles(p.dossierId))
);
const fileData = await this._statusControllerService.getFileStatusForDossiers(mappedDossiers.map(p => p.dossierId)).toPromise();
for (const dossierId of Object.keys(fileData)) {
@ -348,9 +350,13 @@ export class AppStateService {
let foundDossier = this.allDossiers.find(p => p.dossierId === updatedDossier.dossierId);
if (foundDossier) {
this._appState.dossiers.splice(this._appState.dossiers.indexOf(foundDossier), 1);
foundDossier = new DossierWrapper(updatedDossier, foundDossier.files);
foundDossier = new DossierWrapper(
updatedDossier,
this.getDossierTemplateById(updatedDossier.dossierTemplateId).name,
foundDossier.files
);
} else {
foundDossier = new DossierWrapper(updatedDossier, []);
foundDossier = new DossierWrapper(updatedDossier, this.getDossierTemplateById(updatedDossier.dossierTemplateId).name, []);
}
this._appState.dossiers.push(foundDossier);

View File

@ -38,7 +38,7 @@ export class DossierWrapper implements Dossier {
allFilesApproved?: boolean;
type?: Dictionary;
constructor(private readonly _dossier: Dossier, private _files: FileStatusWrapper[] = []) {
constructor(private readonly _dossier: Dossier, readonly dossierTemplateName, private _files: FileStatusWrapper[] = []) {
this._recomputeFileStatus();
}
@ -55,8 +55,8 @@ export class DossierWrapper implements Dossier {
return !!this._files.find(f => f.status === status);
}
hasMember(key: string) {
return this._dossier.memberIds.indexOf(key) >= 0;
hasMember(memberId: string) {
return this._dossier.memberIds.indexOf(memberId) >= 0;
}
addedDateMatches(key: string) {

@ -1 +1 @@
Subproject commit 0bb0e4454f3015d1c81ccd23415c8135c89eeeff
Subproject commit 908256aacab10e189b04b7471cd7bf813723d76a