fix icons bg, filter padding, make filter popup reactive
This commit is contained in:
parent
3b58497fed
commit
10cdc205f3
@ -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;
|
||||
|
||||
@ -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)"
|
||||
|
||||
@ -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) &&
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 => ({
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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"> </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"> </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>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
Loading…
x
Reference in New Issue
Block a user