Merge branch 'VM/FileAttributes'

This commit is contained in:
Timo Bejan 2021-08-23 14:57:06 +03:00
commit 75cf938abe
12 changed files with 128 additions and 12 deletions

View File

@ -57,6 +57,9 @@ export class FileStatusWrapper implements FileStatus {
this.primaryAttribute = '-';
}
}
if (!this.fileAttributes || !this.fileAttributes.attributeIdToValue) {
this.fileAttributes = { attributeIdToValue: {} };
}
}
readonly excludedPagesCount = this.excludedPages?.length ?? 0;
@ -67,7 +70,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 isUnassigned = !this.currentReviewer;
readonly isError = this.status === FileStatus.StatusEnum.ERROR;
readonly isProcessing = processingStatuses.includes(this.status);
readonly isApproved = this.status === FileStatus.StatusEnum.APPROVED;

View File

@ -51,6 +51,18 @@
{{ 'add-edit-file-attribute.form.primary' | translate }}
</mat-checkbox>
</div>
<div class="red-input-group mt-0">
<mat-checkbox color="primary" formControlName="filterable" name="filterable">
{{ 'add-edit-file-attribute.form.filterable' | translate }}
</mat-checkbox>
</div>
<div class="red-input-group mt-0">
<mat-checkbox color="primary" formControlName="displayedInFileList" name="displayedInFileList">
{{ 'add-edit-file-attribute.form.displayedInFileList' | translate }}
</mat-checkbox>
</div>
</div>
</div>
<div class="dialog-actions">

View File

@ -32,7 +32,9 @@ export class AddEditFileAttributeDialogComponent {
csvColumnHeader: [this.fileAttribute?.csvColumnHeader, Validators.required],
type: [this.fileAttribute?.type || FileAttributeConfig.TypeEnum.TEXT, Validators.required],
readonly: [this.fileAttribute ? !this.fileAttribute.editable : false],
primaryAttribute: [this.fileAttribute?.primaryAttribute]
primaryAttribute: [this.fileAttribute?.primaryAttribute],
filterable: [this.fileAttribute?.filterable],
displayedInFileList: [this.fileAttribute?.displayedInFileList]
});
}

View File

@ -62,6 +62,12 @@
<div class="small-label">
{{ attribute.csvColumnHeader }}
</div>
<div class="center">
<iqser-round-checkbox *ngIf="attribute.filterable" [active]="true" [size]="18"></iqser-round-checkbox>
</div>
<div class="center">
<iqser-round-checkbox *ngIf="attribute.displayedInFileList" [active]="true" [size]="18"></iqser-round-checkbox>
</div>
<div class="center">
<iqser-round-checkbox *ngIf="attribute.primaryAttribute" [active]="true" [size]="18"></iqser-round-checkbox>
</div>

View File

@ -11,7 +11,7 @@ iqser-table-header::ng-deep .header-item {
.content-container cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 2fr 1fr 1fr 1fr 1fr 1fr 11px;
grid-template-columns: auto 2fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 11px;
.table-item {
> div:not(.scrollbar-placeholder) {
@ -36,7 +36,7 @@ iqser-table-header::ng-deep .header-item {
}
&.has-scrollbar:hover ::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 2fr 1fr 1fr 1fr 1fr 1fr;
grid-template-columns: auto 2fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
}
}

View File

@ -37,6 +37,14 @@ export class FileAttributesListingScreenComponent extends ListingComponent<FileA
class: 'flex-center'
},
{ label: _('file-attributes-listing.table-col-names.csv-column') },
{
label: _('file-attributes-listing.table-col-names.filterable'),
class: 'flex-center'
},
{
label: _('file-attributes-listing.table-col-names.displayed-in-file-list'),
class: 'flex-center'
},
{
label: _('file-attributes-listing.table-col-names.primary'),
class: 'flex-center',

View File

@ -1,6 +1,7 @@
<section *ngIf="!!currentDossier">
<redaction-page-header
[actionConfigs]="actionConfigs"
[fileAttributeConfigs]="fileAttributeConfigs"
[searchPlaceholder]="'dossier-overview.search' | translate"
[showCloseButton]="true"
>
@ -104,6 +105,10 @@
</div>
</div>
<div *ngFor="let config of displayedInFileListAttributes">
{{ fileStatus.fileAttributes.attributeIdToValue[config.id] }}
</div>
<div>
<div [class.error]="fileStatus.isError" class="small-label">
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}

View File

@ -1,6 +1,10 @@
@import '../../../../../assets/styles/variables';
@import '../../../../../assets/styles/red-mixins';
:root {
--dynamic-columns: '0';
}
.file-upload-input {
display: none;
}
@ -17,7 +21,7 @@ iqser-table-column-name::ng-deep {
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 3fr 2fr 1fr 2fr 1fr auto 11px;
grid-template-columns: auto 3fr repeat(var(--dynamic-columns), 1fr) 2fr 1fr 2fr 1fr auto 11px;
.table-item {
> div {
@ -67,7 +71,7 @@ cdk-virtual-scroll-viewport {
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 3fr 2fr 1fr 2fr 1fr auto;
grid-template-columns: auto 3fr repeat(var(--dynamic-columns), 1fr) 2fr 1fr 2fr 1fr auto;
}
}
}

View File

@ -1,4 +1,15 @@
import { ChangeDetectorRef, Component, ElementRef, HostListener, Injector, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import {
ChangeDetectorRef,
Component,
ElementRef,
HostListener,
Injector,
OnDestroy,
OnInit,
Renderer2,
TemplateRef,
ViewChild
} from '@angular/core';
import { Toaster } from '@services/toaster.service';
import { AppStateService } from '@state/app-state.service';
import { FileDropOverlayService } from '@upload-download/services/file-drop-overlay.service';
@ -30,6 +41,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { annotationFilterChecker } from '@shared/components/filters/popup-filter/utils/filter-utils';
import { PermissionsService } from '@services/permissions.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { FileAttributeConfig } from '@redaction/red-ui-http';
@Component({
templateUrl: './dossier-overview-screen.component.html',
@ -50,7 +62,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
hide: !this.currentUser.isManager
}
];
readonly tableColumnConfigs: readonly TableColumnConfig<FileStatusWrapper>[] = [
private readonly _defaultTableConfigs: readonly TableColumnConfig<FileStatusWrapper>[] = [
{
label: _('dossier-overview.table-col-names.name'),
sortByKey: 'filename'
@ -77,9 +89,13 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
sortByKey: 'statusSort'
}
];
tableColumnConfigs: readonly TableColumnConfig<FileStatusWrapper>[] = [];
collapsedDetails = false;
dossierAttributes: DossierAttributeWithValue[] = [];
fileAttributeConfigs: FileAttributeConfig[];
protected readonly _primaryKey = 'filename';
dynamicColumnsCount = 0;
@ViewChild(DossierDetailsComponent, { static: false })
private readonly _dossierDetailsComponent: DossierDetailsComponent;
@ -105,10 +121,14 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
private readonly _statusOverlayService: StatusOverlayService,
private readonly _userPreferenceService: UserPreferenceService,
private readonly _fileDropOverlayService: FileDropOverlayService,
private readonly _dossierAttributesService: DossierAttributesService
private readonly _dossierAttributesService: DossierAttributesService,
private readonly _renderer: Renderer2,
private readonly _elementRef: ElementRef
) {
super(_injector);
this._loadEntitiesFromState();
this.fileAttributeConfigs = this._appStateService.activeFileAttributesConfig.fileAttributeConfigs;
}
get checkedRequiredFilters() {
@ -141,6 +161,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier);
this.searchService.setSearchKey('filename');
this._configureTableColumns();
} catch (e) {
} finally {
this._loadingService.stop();
@ -235,9 +256,25 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
this.collapsedDetails = !this.collapsedDetails;
}
get displayedInFileListAttributes() {
return this.fileAttributeConfigs.filter(config => config.displayedInFileList);
}
recentlyModifiedChecker = (file: FileStatusWrapper) =>
moment(file.lastUpdated).add(this._appConfigService.getConfig(AppConfigKey.RECENT_PERIOD_IN_HOURS), 'hours').isAfter(moment());
private _configureTableColumns() {
const dynamicColumns: TableColumnConfig<FileStatusWrapper>[] = [];
for (const config of this.displayedInFileListAttributes) {
if (config.displayedInFileList) {
dynamicColumns.push({ label: config.label, notTranslatable: true });
}
}
this.tableColumnConfigs = [this._defaultTableConfigs[0], ...dynamicColumns, ...this._defaultTableConfigs.slice(1)];
this.dynamicColumnsCount = dynamicColumns.length;
(this._elementRef.nativeElement as HTMLElement).style.setProperty('--dynamic-columns', `${this.dynamicColumnsCount}`);
}
private _loadEntitiesFromState() {
this.currentDossier = this._appStateService.activeDossier;
if (this.currentDossier) this.entitiesService.setEntities(this.currentDossier.files);
@ -256,6 +293,8 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
const allDistinctAddedDates = new Set<string>();
const allDistinctNeedsWork = new Set<string>();
const dynamicFilters = new Map<string, Set<string>>();
this.entitiesService.all.forEach(file => {
allDistinctPeople.add(file.currentReviewer);
allDistinctFileStatusWrapper.add(file.status);
@ -269,6 +308,24 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
if (file.hasImages) allDistinctNeedsWork.add('image');
if (file.hasNone) allDistinctNeedsWork.add('none');
if (file.hasAnnotationComments) allDistinctNeedsWork.add('comment');
// extract values for dynamic filters
this.fileAttributeConfigs.forEach(config => {
if (config.filterable) {
const filterKey = `${config.id}:${config.label}`;
let filters = dynamicFilters.get(filterKey);
if (!filters) {
dynamicFilters.set(filterKey, new Set<string>());
filters = dynamicFilters.get(filterKey);
}
let filterValue = file.fileAttributes?.attributeIdToValue[config.id];
if (!filterValue) {
filterValue = this._translateService.instant('filters.empty');
file.fileAttributes.attributeIdToValue[config.id] = '-';
}
filters.add(filterValue);
}
});
});
const statusFilters = [...allDistinctFileStatusWrapper].map<NestedFilter>(item => ({
@ -322,6 +379,18 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
matchAll: true
});
dynamicFilters.forEach((filterValue: Set<string>, filterKey: string) => {
const id = filterKey.split(':')[0];
const key = filterKey.split(':')[1];
this.filterService.addFilterGroup({
slug: key,
label: key,
icon: 'red:template',
filters: [...filterValue].map<NestedFilter>((value: string) => ({ key: value, label: value })),
checker: (input: FileStatusWrapper, filter: NestedFilter) => filter.label === input.fileAttributes.attributeIdToValue[id]
});
});
this.filterService.addFilterGroup({
slug: 'quickFilters',
filters: this._createQuickFilters(),

View File

@ -5,6 +5,7 @@ import { FilterService, SearchService } from '@iqser/common-ui';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { combineLatest, Observable, of } from 'rxjs';
import { SearchPosition, SearchPositions } from '@shared/components/page-header/models/search-positions.type';
import { FileAttributeConfig } from '@redaction/red-ui-http';
@Component({
selector: 'redaction-page-header',
@ -18,6 +19,7 @@ export class PageHeaderComponent<T> {
@Input() showCloseButton: boolean;
@Input() actionConfigs: readonly ActionConfig[];
@Input() buttonConfigs: readonly ButtonConfig[];
@Input() fileAttributeConfigs: readonly FileAttributeConfig[];
@Input() searchPlaceholder: string;
@Input() searchWidth: number | 'full';
@Input() searchPosition: SearchPosition = SearchPositions.afterFilters;

View File

@ -1,6 +1,6 @@
{
"OAUTH_URL": "https://red-staging.iqser.cloud/auth/realms/redaction",
"API_URL": "https://red-staging.iqser.cloud/redaction-gateway-v1",
"OAUTH_URL": "https://dom1.iqser.cloud/auth/realms/redaction",
"API_URL": "https://dom1.iqser.cloud/redaction-gateway-v1",
"OAUTH_CLIENT_ID": "redaction",
"BACKEND_APP_VERSION": "4.4.40",
"FRONTEND_APP_VERSION": "1.1",

View File

@ -82,6 +82,8 @@
"form": {
"column-header": "CSV Column Header",
"column-header-placeholder": "Enter CSV Column Header",
"displayedInFileList": "Displayed In File List",
"filterable": "Filterable",
"name": "Attribute Name",
"name-placeholder": "Enter Name",
"primary": "Set as Primary",
@ -928,6 +930,8 @@
"search": "Search by attribute name...",
"table-col-names": {
"csv-column": "CSV Column",
"displayed-in-file-list": "Dis. In File List",
"filterable": "Filterable",
"name": "Name",
"primary": "Primary",
"primary-info-tooltip": "The value of the attribute set as primary shows up under the file name in the documents list.",
@ -1040,7 +1044,8 @@
"filter-by": "Filter:",
"needs-work": "Workload",
"people": "Dossier Member(s)",
"status": "Status"
"status": "Status",
"empty": "Empty"
},
"general-config-screen": {
"actions": {