RED-6712 - Add in-place editing to the primary attribute in file list

This commit is contained in:
Valentin Mihai 2023-09-20 12:39:07 +02:00
parent 2cc58508e1
commit 1948168d43
10 changed files with 120 additions and 38 deletions

View File

@ -2,7 +2,7 @@
<div
(mousedown)="handleClick($event)"
(click)="editFileAttribute($event)"
[ngClass]="{ 'workflow-attribute': mode === 'workflow' }"
[ngClass]="{ 'workflow-attribute': mode === 'workflow', 'file-name-column': fileNameColumn }"
class="file-attribute"
>
<div [ngClass]="{ 'workflow-value': mode === 'workflow' }" class="value">
@ -16,7 +16,7 @@
>
<ng-template #date>
<span [ngClass]="{ hide: isInEditMode, 'clamp-3': mode !== 'workflow' }">
{{ fileAttributeValue ? (fileAttributeValue | date : 'd MMM yyyy') : '-' }}</span
{{ fileAttributeValue ? (fileAttributeValue | date: 'd MMM yyyy') : '-' }}</span
>
</ng-template>
</div>
@ -33,10 +33,9 @@
*ngIf="!isInEditMode; else input"
[attr.help-mode-key]="'edit-file-attributes'"
[class.help-mode-button]="helpModeService.isHelpModeActive$ | async"
[ngClass]="{ 'workflow-edit-button': mode === 'workflow' }"
class="action-buttons edit-button"
[ngClass]="{ 'workflow-edit-button': mode === 'workflow', 'action-buttons edit-button': !fileNameColumn }"
>
<div [ngClass]="{ 'workflow-edit-icon': mode === 'workflow' }" class="edit-icon">
<div [ngClass]="{ 'workflow-edit-icon': mode === 'workflow', 'edit-icon': !fileNameColumn }">
<mat-icon [svgIcon]="'iqser:edit'"></mat-icon>
</div>
</div>
@ -44,14 +43,18 @@
</div>
<ng-template #input>
<div [ngClass]="{ 'workflow-edit-input': mode === 'workflow' }" class="edit-input" iqserStopPropagation>
<div
[ngClass]="{ 'workflow-edit-input': mode === 'workflow', 'file-name-column-input': fileNameColumn }"
class="edit-input"
iqserStopPropagation
>
<form (ngSubmit)="form.valid && save()" [formGroup]="form">
<iqser-dynamic-input
(closedDatepicker)="closedDatepicker = $event"
(keydown.escape)="close()"
[formControlName]="fileAttribute.id"
[id]="fileAttribute.id"
[ngClass]="{ 'workflow-input': mode === 'workflow' }"
[ngClass]="{ 'workflow-input': mode === 'workflow' || fileNameColumn, 'file-name-input': fileNameColumn }"
[type]="fileAttribute.type"
></iqser-dynamic-input>

View File

@ -4,6 +4,20 @@
display: flex;
align-items: center;
&.file-name-column {
height: 20px;
width: fit-content;
border-radius: 4px;
font-size: 11px;
line-height: 14px;
font-weight: initial;
mat-icon {
transform: scale(0.5);
visibility: hidden;
}
}
&.workflow-attribute {
padding: 2px;
position: relative;
@ -54,18 +68,26 @@
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15);
margin-left: -10px;
&.workflow-edit-input {
justify-content: space-between;
&.file-name-column-input {
background: transparent;
}
&.file-name-column-input,
.workflow-edit-input {
box-shadow: none;
width: 100%;
position: absolute;
left: 0;
top: -5px;
border: none;
form {
width: 100%;
}
}
&.workflow-edit-input {
justify-content: space-between;
left: 0;
top: -5px;
iqser-circle-button {
margin: 0 5px;
@ -93,6 +115,7 @@
}
}
.file-name-input,
.workflow-input {
width: 100%;
padding-left: 2px;

View File

@ -1,8 +1,15 @@
import { Component, computed, effect, HostListener, Input, OnDestroy } from '@angular/core';
import { Dossier, File, FileAttributeConfigTypes, IFileAttributeConfig } from '@red/domain';
import { BaseFormComponent, HelpModeService, ListingService, Toaster } from '@iqser/common-ui';
import {
BaseFormComponent,
CircleButtonComponent,
DynamicInputComponent,
HelpModeService,
ListingService,
Toaster,
} from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import { AbstractControl, FormBuilder, UntypedFormGroup, ValidatorFn } from '@angular/forms';
import { AbstractControl, FormBuilder, FormsModule, ReactiveFormsModule, UntypedFormGroup, ValidatorFn } from '@angular/forms';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { firstValueFrom, Subscription } from 'rxjs';
import { FilesService } from '@services/files/files.service';
@ -12,11 +19,28 @@ import { NavigationEnd, Router } from '@angular/router';
import { filter, map, tap } from 'rxjs/operators';
import { ConfigService } from '../../config.service';
import { Debounce } from '@iqser/common-ui/lib/utils';
import { AsyncPipe, DatePipe, NgClass, NgIf, NgTemplateOutlet } from '@angular/common';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'redaction-file-attribute [fileAttribute] [file] [dossier]',
templateUrl: './file-attribute.component.html',
styleUrls: ['./file-attribute.component.scss'],
standalone: true,
imports: [
NgIf,
NgClass,
MatTooltipModule,
MatIconModule,
DatePipe,
AsyncPipe,
FormsModule,
ReactiveFormsModule,
DynamicInputComponent,
CircleButtonComponent,
NgTemplateOutlet,
],
})
export class FileAttributeComponent extends BaseFormComponent implements OnDestroy {
readonly #subscriptions = new Subscription();
@ -36,6 +60,7 @@ export class FileAttributeComponent extends BaseFormComponent implements OnDestr
@Input() fileAttribute!: IFileAttributeConfig;
@Input() file!: File;
@Input() dossier!: Dossier;
@Input() fileNameColumn = false;
constructor(
router: Router,

View File

@ -1,5 +1,5 @@
<div class="cell">
<redaction-file-name-column [dossierTemplateId]="dossierTemplateId" [file]="file"></redaction-file-name-column>
<div class="cell" [class.file-name-cell]="!fileAttributesService.isEditingFileAttribute()">
<redaction-file-name-column [dossierTemplateId]="dossierTemplateId" [file]="file" [dossier]="dossier"></redaction-file-name-column>
</div>
<div class="cell">

View File

@ -28,3 +28,15 @@
justify-content: flex-start;
align-items: center;
}
.file-name-cell:hover {
::ng-deep.file-name-column {
background: var(--iqser-grey-6);
div {
mat-icon {
visibility: visible;
}
}
}
}

View File

@ -1,6 +1,7 @@
import { Component, Input } from '@angular/core';
import { Dossier, File, IFileAttributeConfig } from '@red/domain';
import { getConfig } from '@iqser/common-ui';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
@Component({
selector: 'redaction-table-item [file] [dossier] [displayedAttributes] [dossierTemplateId]',
@ -14,4 +15,6 @@ export class TableItemComponent {
@Input() dossierTemplateId: string;
readonly isDocumine = getConfig().IS_DOCUMINE;
constructor(readonly fileAttributesService: FileAttributesService) {}
}

View File

@ -48,7 +48,6 @@ const routes: IqserRoutes = [
DossierDetailsComponent,
DossierDetailsStatsComponent,
FileWorkloadComponent,
FileAttributeComponent,
TableItemComponent,
WorkflowItemComponent,
DossierOverviewScreenHeaderComponent,
@ -72,6 +71,7 @@ const routes: IqserRoutes = [
IqserAllowDirective,
TenantPipe,
DisableStopPropagationDirective,
FileAttributeComponent,
],
})
export class DossierOverviewModule {}

View File

@ -12,12 +12,22 @@
</div>
</div>
<div *ngIf="ctx.primaryAttribute" class="small-label">
<div class="primary-attribute">
<span [matTooltip]="ctx.primaryAttribute" matTooltipPosition="above">
{{ ctx.primaryAttribute }}
</span>
<div *ngIf="ctx.primaryAttribute" class="primary-attribute">
<div class="small-label" *ngIf="file?.softDeletedTime; else editableFileAttribute">
<div>
<span [matTooltip]="ctx.primaryAttribute.label" matTooltipPosition="above">
{{ ctx.primaryAttribute.label }}
</span>
</div>
</div>
<ng-template #editableFileAttribute>
<redaction-file-attribute
[file]="defaultFile"
[dossier]="dossier"
[fileAttribute]="ctx.primaryAttribute"
[fileNameColumn]="true"
></redaction-file-attribute>
</ng-template>
</div>
<redaction-file-stats [file]="file"></redaction-file-stats>

View File

@ -1,24 +1,13 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core';
import { PrimaryFileAttributeService } from '@services/primary-file-attribute.service';
import { FileAttributes } from '@red/domain';
import { Dossier, File, IFileAttributeConfig, TrashFile } from '@red/domain';
import { FileAttributesService } from '@services/entity-services/file-attributes.service';
import { combineLatest, map, ReplaySubject } from 'rxjs';
import { ContextComponent } from '@iqser/common-ui/lib/utils';
import { DossierTemplatesService } from '@services/dossier-templates/dossier-templates.service';
interface PartialFile {
readonly isError: boolean;
readonly isInitialProcessing: boolean;
readonly filename: string;
readonly numberOfPages: number;
readonly excludedPages: number[];
readonly lastOCRTime?: string;
readonly fileAttributes: FileAttributes;
readonly lastManualChangeDate?: string;
}
interface FileNameColumnContext {
primaryAttribute: string;
primaryAttribute: IFileAttributeConfig;
ocrByDefault: boolean;
}
@ -29,9 +18,10 @@ interface FileNameColumnContext {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileNameColumnComponent extends ContextComponent<FileNameColumnContext> implements OnInit, OnChanges {
@Input() file: PartialFile;
@Input() dossierTemplateId: string;
readonly #reloadAttribute = new ReplaySubject<void>(1);
@Input() file?: File | TrashFile;
@Input() dossier: Dossier;
@Input() dossierTemplateId: string;
constructor(
private readonly _fileAttributeService: FileAttributesService,
@ -43,7 +33,7 @@ export class FileNameColumnComponent extends ContextComponent<FileNameColumnCont
ngOnInit(): void {
const primaryAttribute$ = combineLatest([this._fileAttributeService.fileAttributesConfig$, this.#reloadAttribute]).pipe(
map(() => this._primaryFileAttributeService.getPrimaryFileAttributeValue(this.file, this.dossierTemplateId)),
map(() => this.#findPrimaryAttribute()),
);
const ocrByDefault$ = this._dossierTemplateService.get(this.dossierTemplateId).pipe(map(template => template.ocrByDefault));
super._initContext({
@ -57,4 +47,17 @@ export class FileNameColumnComponent extends ContextComponent<FileNameColumnCont
this.#reloadAttribute.next();
}
}
get defaultFile() {
return this.file as File;
}
#findPrimaryAttribute() {
if (this.file.softDeletedTime) {
const primaryAttribute = this._primaryFileAttributeService.getPrimaryFileAttributeValue(this.file, this.dossierTemplateId);
return { label: primaryAttribute } as IFileAttributeConfig;
}
const fileAttributes = this._fileAttributeService.getFileAttributeConfig(this.dossierTemplateId);
return fileAttributes?.fileAttributeConfigs.find(a => a.primaryAttribute);
}
}

View File

@ -10,6 +10,7 @@ import { AnnotationIconComponent } from './components/annotation-icon/annotation
import { DonutChartComponent } from './components/donut-chart/donut-chart.component';
import {
CircleButtonComponent,
DynamicInputComponent,
HasScrollbarDirective,
IconButtonComponent,
IqserAllowDirective,
@ -47,6 +48,7 @@ import { CustomDateAdapter } from '@shared/CustomDateAdapter';
import { IqserUsersModule } from '@iqser/common-ui/lib/users';
import { SmallChipComponent } from '@iqser/common-ui/lib/shared';
import { SelectComponent } from '@shared/components/select/select.component';
import { FileAttributeComponent } from '../dossier-overview/components/file-attribute/file-attribute.component';
const buttons = [FileDownloadBtnComponent];
@ -78,7 +80,7 @@ const services = [SharedDialogService];
const modules = [MatConfigModule, ScrollingModule, IconsModule, FormsModule, ReactiveFormsModule, ColorPickerModule];
const deleteThisWhenAllComponentsAreStandalone = [DonutChartComponent];
const deleteThisWhenAllComponentsAreStandalone = [DonutChartComponent, FileAttributeComponent];
@NgModule({
declarations: [...components, ...utils, EditorComponent],
@ -100,6 +102,7 @@ const deleteThisWhenAllComponentsAreStandalone = [DonutChartComponent];
IqserDenyDirective,
SelectComponent,
RoundCheckboxComponent,
DynamicInputComponent,
],
exports: [...modules, ...components, ...utils, ...deleteThisWhenAllComponentsAreStandalone],
providers: [