RED-6712 - Add in-place editing to the primary attribute in file list
This commit is contained in:
parent
2cc58508e1
commit
1948168d43
@ -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>
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {}
|
||||
}
|
||||
|
||||
@ -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 {}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user