Dossier overview

This commit is contained in:
Adina Țeudan 2021-08-30 13:09:11 +03:00
parent ce113ce464
commit 90aca7debc
11 changed files with 233 additions and 286 deletions

View File

@ -61,7 +61,7 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
routerLinkFn = (entity: TypeValueWrapper) => [entity.type];
ngOnInit(): void {
this._setColumnConfig();
this._configureTableColumns();
this._loadDictionaryData();
}
@ -100,7 +100,7 @@ export class DictionaryListingScreenComponent extends ListingComponent<TypeValue
);
}
private _setColumnConfig() {
private _configureTableColumns() {
this.tableColumnConfigs = [
{
label: _('dictionary-listing.table-col-names.type'),

View File

@ -53,7 +53,7 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
routerLinkFn = (dossierTemplate: DossierTemplateModelWrapper) => [dossierTemplate.dossierTemplateId, 'dictionaries'];
ngOnInit(): void {
this._setColumnConfig();
this._configureTableColumns();
this.loadDossierTemplatesData();
}
@ -75,7 +75,7 @@ export class DossierTemplatesListingScreenComponent extends ListingComponent<Dos
});
}
private _setColumnConfig() {
private _configureTableColumns() {
this.tableColumnConfigs = [
{
label: _('dossier-templates-listing.table-col-names.name'),

View File

@ -20,9 +20,9 @@ cdk-virtual-scroll-viewport {
@include line-clamp(1);
}
&.stats-subtitle > div {
width: fit-content;
}
//&.stats-subtitle > div {
// width: fit-content;
//}
}
}

View File

@ -86,7 +86,7 @@ export class DossierListingScreenComponent
routerLinkFn = (dossier: DossierWrapper) => ['/main/dossiers/' + dossier.dossierId];
ngOnInit(): void {
this._setColumnConfig();
this._configureTableColumns();
this.calculateData();
this.addSubscription = timer(0, 10000).subscribe(async () => {
@ -152,7 +152,7 @@ export class DossierListingScreenComponent
this.documentsChartData = this._translateChartService.translateStatus(this.documentsChartData);
}
private _setColumnConfig() {
private _configureTableColumns() {
this.tableColumnConfigs = [
{
label: _('dossier-listing.table-col-names.name'),

View File

@ -36,143 +36,19 @@
<div class="red-content-inner">
<div [class.extended]="collapsedDetails" class="content-container">
<iqser-table-header
<iqser-table
(noDataAction)="fileInput.click()"
[bulkActions]="bulkActions"
[hasScrollButton]="true"
[itemSize]="80"
[noDataButtonLabel]="'dossier-overview.no-data.action' | translate"
[noDataIcon]="'red:upload'"
[noDataText]="'dossier-overview.no-data.title' | translate"
[noMatchText]="'dossier-overview.no-match.title' | translate"
[selectionEnabled]="true"
[tableColumnConfigs]="tableColumnConfigs"
[tableHeaderLabel]="tableHeaderLabel"
></iqser-table-header>
<iqser-empty-state
(action)="fileInput.click()"
*ngIf="entitiesService.noData$ | async"
[buttonLabel]="'dossier-overview.no-data.action' | translate"
[text]="'dossier-overview.no-data.title' | translate"
buttonIcon="red:upload"
icon="red:document"
></iqser-empty-state>
<iqser-empty-state *ngIf="noMatch$ | async" [text]="'dossier-overview.no-match.title' | translate"></iqser-empty-state>
<cdk-virtual-scroll-viewport #scrollViewport [itemSize]="itemSize" iqserHasScrollbar>
<div
*cdkVirtualFor="let fileStatus of sortedDisplayedEntities$ | async; trackBy: trackByPrimaryKey"
[class.disabled]="fileStatus.excluded"
[class.last-opened]="fileStatus.lastOpened"
[class.pointer]="fileStatus.canBeOpened"
[routerLink]="fileLink(fileStatus)"
class="table-item"
>
<div (click)="toggleEntitySelected($event, fileStatus)" class="selection-column">
<iqser-round-checkbox [active]="isSelected(fileStatus)"></iqser-round-checkbox>
</div>
<div>
<div class="filename-wrapper">
<div
[class.disabled]="fileStatus.isPending"
[class.error]="fileStatus.isError"
class="table-item-title text-overflow"
>
<span [matTooltip]="fileStatus.filename" matTooltipPosition="above">
{{ fileStatus.filename }}
</span>
</div>
</div>
<div *ngIf="fileStatus.primaryAttribute" class="small-label stats-subtitle">
<div class="primary-attribute text-overflow">
<span [matTooltip]="fileStatus.primaryAttribute" matTooltipPosition="above">
{{ fileStatus.primaryAttribute }}
</span>
</div>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
<div>
<mat-icon svgIcon="red:exclude-pages"></mat-icon>
{{ fileStatus.excludedPagesCount }}
</div>
<div
*ngIf="fileStatus.lastOCRTime"
[matTooltipPosition]="'above'"
[matTooltip]="'dossier-overview.ocr-performed' | translate"
>
<mat-icon svgIcon="red:ocr"></mat-icon>
{{ fileStatus.lastOCRTime | date: 'mediumDate' }}
</div>
</div>
</div>
<div>
<div [class.error]="fileStatus.isError" class="small-label">
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}
</div>
</div>
<div *ngFor="let config of displayedInFileListAttributes">
{{ fileStatus.fileAttributes.attributeIdToValue[config.id] }}
</div>
<!-- always show A for error-->
<div *ngIf="fileStatus.isError">
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
</div>
<div *ngIf="!fileStatus.isError">
<redaction-needs-work-badge [needsWorkInput]="fileStatus"></redaction-needs-work-badge>
</div>
<div *ngIf="!fileStatus.isError" class="user-column">
<redaction-initials-avatar [userId]="fileStatus.currentReviewer" [withName]="true"></redaction-initials-avatar>
</div>
<div *ngIf="!fileStatus.isError">
<div class="quick-navigation">
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
</div>
<div [class.extend-cols]="fileStatus.isError" class="status-container">
<div
*ngIf="fileStatus.isError"
class="small-label error"
translate="dossier-overview.file-listing.file-entry.file-error"
></div>
<div
*ngIf="fileStatus.isPending"
class="small-label"
translate="dossier-overview.file-listing.file-entry.file-pending"
></div>
<div
*ngIf="fileStatus.isProcessing"
class="small-label loading"
translate="dossier-overview.file-listing.file-entry.file-processing"
></div>
<iqser-status-bar
*ngIf="fileStatus.isWorkable"
[configs]="[
{
color: fileStatus.status,
length: 1
}
]"
></iqser-status-bar>
<redaction-file-actions
(actionPerformed)="calculateData()"
*ngIf="!fileStatus.isProcessing"
[fileStatus]="fileStatus"
class="mr-4"
></redaction-file-actions>
</div>
<div class="scrollbar-placeholder"></div>
</div>
</cdk-virtual-scroll-viewport>
<iqser-scroll-button [itemSize]="itemSize" [scrollViewport]="scrollViewport"></iqser-scroll-button>
[showNoDataButton]="true"
[tableItemClasses]="{ disabled: disabledFn, 'last-opened': lastOpenedFn }"
></iqser-table>
</div>
<div [class.collapsed]="collapsedDetails" class="right-container" iqserHasScrollbar>
@ -186,7 +62,7 @@
</div>
</section>
<ng-template #needsWorkTemplate let-filter="filter">
<ng-template #needsWorkFilterTemplate let-filter="filter">
<redaction-type-filter [filter]="filter"></redaction-type-filter>
</ng-template>
@ -195,3 +71,104 @@
<ng-template #bulkActions>
<redaction-dossier-overview-bulk-actions (reload)="bulkActionPerformed()"></redaction-dossier-overview-bulk-actions>
</ng-template>
<ng-template #filenameTemplate let-fileStatus="entity">
<div class="cell">
<div class="filename-wrapper">
<div [class.disabled]="fileStatus.isPending" [class.error]="fileStatus.isError" class="table-item-title text-overflow">
<span [matTooltip]="fileStatus.filename" matTooltipPosition="above">
{{ fileStatus.filename }}
</span>
</div>
</div>
<div *ngIf="fileStatus.primaryAttribute" class="small-label">
<div class="primary-attribute text-overflow">
<span [matTooltip]="fileStatus.primaryAttribute" matTooltipPosition="above">
{{ fileStatus.primaryAttribute }}
</span>
</div>
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
<div>
<mat-icon svgIcon="red:exclude-pages"></mat-icon>
{{ fileStatus.excludedPagesCount }}
</div>
<div *ngIf="fileStatus.lastOCRTime" [matTooltipPosition]="'above'" [matTooltip]="'dossier-overview.ocr-performed' | translate">
<mat-icon svgIcon="red:ocr"></mat-icon>
{{ fileStatus.lastOCRTime | date: 'mediumDate' }}
</div>
</div>
</div>
</ng-template>
<ng-template #addedOnTemplate let-fileStatus="entity">
<div class="cell">
<div [class.error]="fileStatus.isError" class="small-label">
{{ fileStatus.added | date: 'd MMM. yyyy, hh:mm a' }}
</div>
</div>
</ng-template>
<ng-template #attributeTemplate let-config="extra" let-fileStatus="entity">
<div class="cell">
{{ fileStatus.fileAttributes.attributeIdToValue[config.id] }}
</div>
</ng-template>
<ng-template #needsWorkTemplate let-fileStatus="entity">
<!-- always show A for error-->
<div *ngIf="fileStatus.isError" class="cell">
<redaction-annotation-icon color="#dd4d50" label="A" type="square"></redaction-annotation-icon>
</div>
<div *ngIf="!fileStatus.isError" class="cell">
<redaction-needs-work-badge [needsWorkInput]="fileStatus"></redaction-needs-work-badge>
</div>
</ng-template>
<ng-template #reviewerTemplate let-fileStatus="entity">
<div *ngIf="!fileStatus.isError" class="user-column cell">
<redaction-initials-avatar [userId]="fileStatus.currentReviewer" [withName]="true"></redaction-initials-avatar>
</div>
</ng-template>
<ng-template #pagesTemplate let-fileStatus="entity">
<div *ngIf="!fileStatus.isError" class="cell small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:pages"></mat-icon>
{{ fileStatus.numberOfPages }}
</div>
</div>
</ng-template>
<ng-template #statusTemplate let-fileStatus="entity">
<div [class.extend-cols]="fileStatus.isError" class="status-container cell">
<div *ngIf="fileStatus.isError" class="small-label error" translate="dossier-overview.file-listing.file-entry.file-error"></div>
<div *ngIf="fileStatus.isPending" class="small-label" translate="dossier-overview.file-listing.file-entry.file-pending"></div>
<div
*ngIf="fileStatus.isProcessing"
class="small-label loading"
translate="dossier-overview.file-listing.file-entry.file-processing"
></div>
<iqser-status-bar
*ngIf="fileStatus.isWorkable"
[configs]="[
{
color: fileStatus.status,
length: 1
}
]"
></iqser-status-bar>
<redaction-file-actions
(actionPerformed)="calculateData()"
*ngIf="!fileStatus.isProcessing"
[fileStatus]="fileStatus"
class="mr-4"
></redaction-file-actions>
</div>
</ng-template>

View File

@ -1,62 +1,56 @@
@import '../../../../../assets/styles/variables';
:root {
--dynamic-columns: '1fr';
}
.file-upload-input {
display: none;
}
cdk-virtual-scroll-viewport {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 3fr 2fr repeat(var(--dynamic-columns, 1), 1fr) 2fr 1fr auto 11px;
:host ::ng-deep iqser-table cdk-virtual-scroll-viewport .cdk-virtual-scroll-content-wrapper .table-item {
&.disabled {
> div {
background-color: $grey-2;
color: $grey-7;
}
.table-item {
.disabled {
color: $grey-7;
}
.error {
color: $primary;
}
.extend-cols {
grid-column-end: span 3;
align-items: flex-end;
}
.table-item-title {
max-width: 25vw;
}
.quick-navigation {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
opacity: 0.7;
color: $accent;
font-size: 11px;
letter-spacing: 0;
line-height: 14px;
.mat-icon {
width: 10px;
height: 10px;
margin-right: 4px;
}
}
.status-container {
align-items: flex-end;
}
redaction-file-actions {
color: initial;
}
}
&.has-scrollbar:hover {
::ng-deep.cdk-virtual-scroll-content-wrapper {
grid-template-columns: auto 3fr 2fr repeat(var(--dynamic-columns, 1), 1fr) 2fr 1fr auto;
&.last-opened {
> .selection-column {
padding-left: 6px !important;
border-left: 4px solid $primary;
}
> div {
animation: red-fading-background 3s 1;
}
}
> div.cell {
.disabled {
color: $grey-7;
}
.error {
color: $primary;
}
.table-item-title {
max-width: 25vw;
}
.primary-attribute {
padding-top: 6px;
}
&.extend-cols {
grid-column-end: span 3;
align-items: flex-end;
}
&.status-container {
align-items: flex-end;
}
}
}
@ -76,45 +70,6 @@ cdk-virtual-scroll-viewport {
}
}
.reanalyse-link {
color: $accent;
text-decoration: underline;
&:hover {
color: lighten($accent, 10%);
}
}
.mr-4 {
margin-right: 4px;
}
.disabled {
> div {
background-color: $grey-2;
color: $grey-7;
}
redaction-file-actions {
color: initial;
}
}
.primary-attribute {
padding-top: 6px;
}
.last-opened {
> .selection-column {
padding-left: 6px !important;
border-left: 4px solid $primary;
}
> div {
animation: red-fading-background 3s 1;
}
}
@keyframes red-fading-background {
0% {
background-color: rgba($primary, 0.1);

View File

@ -2,6 +2,7 @@ import {
ChangeDetectorRef,
Component,
ElementRef,
forwardRef,
HostListener,
Injector,
OnDestroy,
@ -37,6 +38,7 @@ import {
LoadingService,
NestedFilter,
TableColumnConfig,
TableComponent,
Toaster
} from '@iqser/common-ui';
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
@ -47,17 +49,16 @@ import { fileStatusTranslations } from '../../translations/file-status-translati
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { annotationFilterChecker } from '@utils/filter-utils';
import { PermissionsService } from '@services/permissions.service';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { RouterHistoryService } from '@services/router-history.service';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { DossierWrapper } from '../../../../state/model/dossier.wrapper';
@Component({
templateUrl: './dossier-overview-screen.component.html',
styleUrls: ['./dossier-overview-screen.component.scss'],
providers: [...DefaultListingServices]
providers: [...DefaultListingServices, { provide: ListingComponent, useExisting: forwardRef(() => DossierOverviewScreenComponent) }]
})
export class DossierOverviewScreenComponent extends ListingComponent<FileStatusWrapper> implements OnInit, OnDestroy, OnDetach, OnAttach {
readonly itemSize = 80;
readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
currentDossier = this._appStateService.activeDossier;
@ -74,43 +75,21 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
collapsedDetails = false;
dossierAttributes: DossierAttributeWithValue[] = [];
fileAttributeConfigs: FileAttributeConfig[];
dynamicColumnsCount = 0;
@ViewChild('filenameTemplate', { static: true }) filenameTemplate: TemplateRef<never>;
@ViewChild('addedOnTemplate', { static: true }) addedOnTemplate: TemplateRef<never>;
@ViewChild('attributeTemplate', { static: true }) attributeTemplate: TemplateRef<never>;
@ViewChild('needsWorkTemplate', { static: true }) needsWorkTemplate: TemplateRef<never>;
@ViewChild('reviewerTemplate', { static: true }) reviewerTemplate: TemplateRef<never>;
@ViewChild('pagesTemplate', { static: true }) pagesTemplate: TemplateRef<never>;
@ViewChild('statusTemplate', { static: true }) statusTemplate: TemplateRef<never>;
protected readonly _primaryKey = 'filename';
private readonly _defaultTableConfigs: readonly TableColumnConfig<FileStatusWrapper>[] = [
{
label: _('dossier-overview.table-col-names.name'),
sortByKey: 'filename'
},
{
label: _('dossier-overview.table-col-names.added-on'),
sortByKey: 'added'
},
{
label: _('dossier-overview.table-col-names.needs-work')
},
{
label: _('dossier-overview.table-col-names.assigned-to'),
class: 'user-column',
sortByKey: 'reviewerName'
},
{
label: _('dossier-overview.table-col-names.pages'),
sortByKey: 'pages'
},
{
label: _('dossier-overview.table-col-names.status'),
class: 'flex-end',
sortByKey: 'statusSort'
}
];
@ViewChild(DossierDetailsComponent, { static: false })
private readonly _dossierDetailsComponent: DossierDetailsComponent;
private _lastScrolledIndex: number;
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
private readonly _needsWorkTemplate: TemplateRef<unknown>;
@ViewChild('needsWorkFilterTemplate', { read: TemplateRef, static: true })
private readonly _needsWorkFilterTemplate: TemplateRef<unknown>;
@ViewChild('fileInput') private readonly _fileInput: ElementRef;
@ViewChild(CdkVirtualScrollViewport)
private readonly _scrollViewport: CdkVirtualScrollViewport;
@ViewChild(TableComponent) private readonly _tableComponent: TableComponent<DossierWrapper>;
constructor(
private readonly _toaster: Toaster,
@ -151,7 +130,14 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
return this.fileAttributeConfigs.filter(config => config.displayedInFileList);
}
routerLinkFn = (fileStatus: FileStatusWrapper) =>
fileStatus.canBeOpened ? [`/main/dossiers/${this.currentDossier.dossierId}/file/${fileStatus.fileId}`] : [];
disabledFn = (fileStatus: FileStatusWrapper) => fileStatus.excluded;
lastOpenedFn = (fileStatus: FileStatusWrapper) => fileStatus.lastOpened;
async ngOnInit(): Promise<void> {
this._configureTableColumns();
this._loadingService.start();
try {
this._fileDropOverlayService.initFileDropHandling();
@ -173,12 +159,11 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
).fileAttributeConfigs;
});
this.addSubscription = this._scrollViewport.scrolledIndexChange
this.addSubscription = this._tableComponent.scrollViewport.scrolledIndexChange
.pipe(tap(index => (this._lastScrolledIndex = index)))
.subscribe();
this.searchService.setSearchKey('filename');
this._configureTableColumns();
this.dossierAttributes = await this._dossierAttributesService.getValues(this.currentDossier);
} catch (e) {
@ -196,7 +181,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
await this._appStateService.reloadActiveDossierFiles();
this._loadEntitiesFromState();
await this.ngOnInit();
this._scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
this._tableComponent.scrollViewport.scrollToIndex(this._lastScrolledIndex, 'smooth');
}
ngOnDetach() {
@ -246,10 +231,6 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
this._fileInput.nativeElement.value = null;
}
fileLink(fileStatus: FileStatusWrapper) {
return fileStatus.canBeOpened ? [`/main/dossiers/${this.currentDossier.dossierId}/file/${fileStatus.fileId}`] : [];
}
async bulkActionPerformed() {
this.entitiesService.setSelected([]);
await this.reloadDossiers();
@ -284,17 +265,46 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
const dynamicColumns: TableColumnConfig<FileStatusWrapper>[] = [];
for (const config of this.displayedInFileListAttributes) {
if (config.displayedInFileList) {
dynamicColumns.push({ label: config.label, notTranslatable: true });
dynamicColumns.push({ label: config.label, notTranslatable: true, template: this.attributeTemplate, extra: config });
}
}
this.tableColumnConfigs = [
this._defaultTableConfigs[0],
this._defaultTableConfigs[1],
{
label: _('dossier-overview.table-col-names.name'),
sortByKey: 'filename',
template: this.filenameTemplate,
width: '3fr'
},
{
label: _('dossier-overview.table-col-names.added-on'),
sortByKey: 'added',
template: this.addedOnTemplate,
width: '2fr'
},
...dynamicColumns,
...this._defaultTableConfigs.slice(2)
{
label: _('dossier-overview.table-col-names.needs-work'),
template: this.needsWorkTemplate
},
{
label: _('dossier-overview.table-col-names.assigned-to'),
class: 'user-column',
sortByKey: 'reviewerName',
template: this.reviewerTemplate,
width: '2fr'
},
{
label: _('dossier-overview.table-col-names.pages'),
sortByKey: 'pages',
template: this.pagesTemplate
},
{
label: _('dossier-overview.table-col-names.status'),
class: 'flex-end',
sortByKey: 'statusSort',
template: this.statusTemplate
}
];
this.dynamicColumnsCount = dynamicColumns.length + 1; // zero is not allowed in repeat, by default we repeat 1 column
document.documentElement.style.setProperty('--dynamic-columns', `${this.dynamicColumnsCount}`);
}
private _loadEntitiesFromState() {
@ -417,7 +427,7 @@ export class DossierOverviewScreenComponent extends ListingComponent<FileStatusW
slug: 'needsWorkFilters',
label: this._translateService.instant('filters.needs-work'),
icon: 'red:needs-work',
filterTemplate: this._needsWorkTemplate,
filterTemplate: this._needsWorkFilterTemplate,
filters: needsWorkFilters.sort(RedactionFilterSorter.byKey),
checker: annotationFilterChecker,
matchAll: true

View File

@ -27,9 +27,9 @@
@include line-clamp(1);
}
.stats-subtitle > div {
width: fit-content;
}
//.stats-subtitle > div {
// width: fit-content;
//}
}
}

View File

@ -77,6 +77,7 @@
display: flex;
justify-content: center;
align-items: center;
width: fit-content;
mat-icon {
width: 10px;

View File

@ -349,6 +349,10 @@ section.settings {
cursor: pointer;
}
.mr-4 {
margin-right: 4px !important;
}
.mr-8 {
margin-right: 8px !important;
}

@ -1 +1 @@
Subproject commit 704ea8221e6a6a198811c91f2a5fd15ed25c46da
Subproject commit 3c693625e774ff61f89a49bf8a915e012320acc8