RED-8748 - filtered components using popup filter, filtered annotations based on selected component, wip on revert value dialog

This commit is contained in:
Valentin Mihai 2024-04-29 22:22:01 +03:00
parent cae3f2dec3
commit c2f40a8d50
16 changed files with 170 additions and 48 deletions

View File

@ -46,7 +46,7 @@
<div class="flex right">
<iqser-circle-button
*ngIf="entry.componentValues[0].value !== entry.componentValues[0].originalValue && canEdit"
(action)="undo(entry.originalKey)"
(action)="undo()"
[showDot]="true"
[tooltip]="'component-management.actions.undo' | translate"
class="undo-value"

View File

@ -1,7 +1,9 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ComponentLogEntry } from '@red/domain';
import { FormBuilder, FormControl, UntypedFormGroup } from '@angular/forms';
import { BaseFormComponent } from '@iqser/common-ui';
import { BaseFormComponent, IqserDialog } from '@iqser/common-ui';
import { FilterService } from '@common-ui/filtering';
import { RevertValueDialogComponent } from '../../dialogs/docu-mine/revert-value-dialog/revert-value-dialog.component';
@Component({
selector: 'redaction-editable-structured-component-value [entry] [canEdit]',
@ -17,7 +19,11 @@ export class EditableStructuredComponentValueComponent extends BaseFormComponent
selected = false;
editing = false;
constructor(private readonly _formBuilder: FormBuilder) {
constructor(
private readonly _formBuilder: FormBuilder,
private readonly _filtersService: FilterService,
private readonly _iqserDialog: IqserDialog,
) {
super();
}
@ -34,6 +40,7 @@ export class EditableStructuredComponentValueComponent extends BaseFormComponent
}
this.deselectLast.emit();
this.selected = true;
this.#setWorkloadFilters();
}
}
@ -47,6 +54,7 @@ export class EditableStructuredComponentValueComponent extends BaseFormComponent
$event?.stopImmediatePropagation();
this.selected = false;
this.editing = false;
this._filtersService.deactivateFilters({ primaryFiltersSlug: 'primaryFilters' });
}
removeValue(key: string) {
@ -57,7 +65,9 @@ export class EditableStructuredComponentValueComponent extends BaseFormComponent
const value = this.form.getRawValue();
}
undo(originalKey: string) {}
undo() {
this._iqserDialog.openDefault(RevertValueDialogComponent);
}
add() {
const key = Object.keys(this.form.controls).length.toString();
@ -71,4 +81,17 @@ export class EditableStructuredComponentValueComponent extends BaseFormComponent
});
return form;
}
#setWorkloadFilters() {
this._filtersService.deactivateFilters({ primaryFiltersSlug: 'primaryFilters' });
const filterGroup = this._filtersService.getGroup('primaryFilters');
for (const filter of filterGroup.filters) {
const nestedFilter = filter.children.find(f => f.label === this.entry.name);
if (nestedFilter) {
this._filtersService.filterCheckboxClicked({ nestedFilter, filterGroup, primaryFiltersSlug: 'primaryFilters' });
return;
}
}
}
}

View File

@ -3,7 +3,7 @@
<iqser-popup-filter [primaryFiltersSlug]="'componentLogFilters'"></iqser-popup-filter>
</div>
<div *ngIf="componentLogData() as componentLogEntries" class="components-container">
<div *ngIf="displayedComponents$ | async as displayedComponents" class="components-container">
<div class="component-row">
<div class="header">
<div class="component">{{ 'component-management.table-header.component' | translate }}</div>
@ -12,7 +12,7 @@
<div class="row-separator"></div>
</div>
<div *ngFor="let entry of componentLogEntries" class="component-row">
<div *ngFor="let entry of displayedComponents" class="component-row">
<redaction-editable-structured-component-value
#editableComponent
[entry]="entry"

View File

@ -1,33 +1,31 @@
import { Component, Input, signal, ViewChildren } from '@angular/core';
import { Component, Input, OnInit, signal, ViewChildren } from '@angular/core';
import { ComponentLogEntry, Dictionary, File, WorkflowFileStatuses } from '@red/domain';
import { IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { ComponentLogService } from '@services/files/component-log.service';
import { FilesMapService } from '@services/files/files-map.service';
import { UserPreferenceService } from '@users/user-preference.service';
import { firstValueFrom } from 'rxjs';
import { combineLatest, firstValueFrom, Observable } from 'rxjs';
import { List } from '@common-ui/utils';
import { EditableStructuredComponentValueComponent } from '../editable-structured-component-value/editable-structured-component-value.component';
import { FilterService } from '@common-ui/filtering';
import { ComponentLogFilterService } from '../../services/component-log-filter.service';
interface DeselectEvent {
name: string;
}
import { map } from 'rxjs/operators';
@Component({
selector: 'redaction-structured-component-management',
templateUrl: './structured-component-management.component.html',
styleUrls: ['/structured-component-management.component.scss'],
})
export class StructuredComponentManagementComponent {
export class StructuredComponentManagementComponent implements OnInit {
@Input() file: File;
@Input() dictionaries: Dictionary[];
@ViewChildren('editableComponent') editableComponents: List<EditableStructuredComponentValueComponent>;
readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
readonly openScmDialogByDefault = signal(this.userPreferences.getOpenScmDialogByDefault());
readonly iconButtonTypes = IconButtonTypes;
protected readonly componentLogData = signal<ComponentLogEntry[] | undefined>(undefined);
protected readonly openScmDialogByDefault = signal(this.userPreferences.getOpenScmDialogByDefault());
protected readonly iconButtonTypes = IconButtonTypes;
protected displayedComponents$: Observable<ComponentLogEntry[]>;
constructor(
private readonly _componentLogService: ComponentLogService,
@ -38,6 +36,24 @@ export class StructuredComponentManagementComponent {
readonly userPreferences: UserPreferenceService,
) {}
async ngOnInit(): Promise<void> {
await this.#loadData();
this.displayedComponents$ = this.#displayedComponents$();
}
#displayedComponents$() {
const componentLogData$ = this._componentLogService.getComponentLogData(
this.file.dossierTemplateId,
this.file.dossierId,
this.file.fileId,
this.dictionaries,
);
const componentLogFilters$ = this._filterService.getFilterModels$('componentLogFilters');
return combineLatest([componentLogData$, componentLogFilters$]).pipe(
map(([components, filters]) => this._componentLogFilterService.filterComponents(components, filters)),
);
}
deselectLast() {
const lastSelected = this.editableComponents.find(c => c.selected);
if (lastSelected) {
@ -49,10 +65,6 @@ export class StructuredComponentManagementComponent {
return this.file.workflowStatus !== WorkflowFileStatuses.APPROVED;
}
async ngOnInit(): Promise<void> {
await this.#loadData();
}
getValueCellId(index: number) {
return `value-cell-${index}`;
}
@ -102,30 +114,18 @@ export class StructuredComponentManagementComponent {
async #loadData(): Promise<void> {
this._loadingService.start();
const componentLogData = await firstValueFrom(
this._componentLogService.getComponentLogData(this.file.dossierTemplateId, this.file.dossierId, this.file.fileId),
this._componentLogService.getComponentLogData(
this.file.dossierTemplateId,
this.file.dossierId,
this.file.fileId,
this.dictionaries,
),
);
this.#computeFilters(componentLogData);
this.#updateDisplayValue(componentLogData);
this.componentLogData.set(componentLogData);
this._loadingService.stop();
}
#updateDisplayValue(componentLogs: ComponentLogEntry[]) {
const dictionaries = this.dictionaries;
for (const componentLog of componentLogs) {
let foundDictionary: Dictionary;
for (const reference of componentLog.componentValues[0].entityReferences) {
if (foundDictionary) {
reference.displayValue = foundDictionary.label;
continue;
}
foundDictionary = dictionaries.find(dict => dict.type === reference.type);
foundDictionary = foundDictionary ?? ({ label: reference.type } as Dictionary);
reference.displayValue = foundDictionary.label;
}
}
}
#computeFilters(componentLogs: ComponentLogEntry[]) {
const filterGroups = this._componentLogFilterService.filterGroups(componentLogs);
this._filterService.addFilterGroups(filterGroups);

View File

@ -0,0 +1,19 @@
<section class="dialog">
<form (submit)="save()">
<div [translate]="'revert-value-dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content"></div>
<div class="dialog-actions">
<iqser-icon-button
[label]="'revert-value-dialog.actions.revert' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
/>
<div [translate]="'revert-value-dialog.actions.cancel'" class="all-caps-label cancel" mat-dialog-close></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close" />
</section>

View File

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { CircleButtonComponent, IconButtonComponent, IqserDialogComponent } from '@iqser/common-ui';
import { MatDialogClose } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
interface RevertValueData {}
interface RevertValueResult {}
@Component({
templateUrl: 'revert-value-dialog.component.html',
styleUrls: ['./revert-value-dialog.component.scss'],
standalone: true,
imports: [CircleButtonComponent, IconButtonComponent, MatDialogClose, TranslateModule],
})
export class RevertValueDialogComponent extends IqserDialogComponent<RevertValueDialogComponent, RevertValueData, RevertValueResult> {
constructor() {
super();
}
save() {}
}

View File

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { ComponentLogEntry } from '@red/domain';
import { NestedFilter } from '@common-ui/filtering';
import { componentLogChecker } from '@utils/filter-utils';
import { INestedFilter, NestedFilter } from '@common-ui/filtering';
@Injectable()
export class ComponentLogFilterService {
@ -22,8 +21,23 @@ export class ComponentLogFilterService {
{
slug: 'componentLogFilters',
filters: componentLogFilters,
checker: componentLogChecker,
},
];
}
filterComponents(components: ComponentLogEntry[], filters: INestedFilter[]) {
const someFiltersChecked = !!filters.find(f => f.checked);
if (!someFiltersChecked) {
return components;
}
const checkedFiltersIds = filters.reduce((ids, f) => {
if (f.checked) {
ids.push(f.id);
}
return ids;
}, []);
return components.filter(c => checkedFiltersIds.includes(c.name));
}
}

View File

@ -76,6 +76,4 @@ export class EditDictionaryDialogComponent extends IqserDialogComponent<EditDict
this._loadingService.stop();
this.close(this.form.value as ReturnType);
}
protected readonly iconButtonTypes = IconButtonTypes;
}

View File

@ -4,8 +4,9 @@ import { catchError, map, tap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import { HttpHeaders } from '@angular/common/http';
import { saveAs } from 'file-saver';
import { ComponentDetails, ComponentLogEntry, IComponentLogData, IComponentLogEntry, IFile } from '@red/domain';
import { ComponentDetails, ComponentLogEntry, Dictionary, IComponentLogData, IComponentLogEntry, IFile } from '@red/domain';
import { mapEach } from '@common-ui/utils';
import { FilePreviewStateService } from '../../modules/file-preview/services/file-preview-state.service';
@Injectable({ providedIn: 'root' })
export class ComponentLogService extends GenericService<void> {
@ -34,12 +35,18 @@ export class ComponentLogService extends GenericService<void> {
);
}
getComponentLogData(dossierTemplateId: string, dossierId: string, fileId: string): Observable<ComponentLogEntry[]> {
getComponentLogData(
dossierTemplateId: string,
dossierId: string,
fileId: string,
dictionaries: Dictionary[],
): Observable<ComponentLogEntry[]> {
return this.#componentLogRequest(dossierTemplateId, dossierId, fileId).pipe(
map(data => data.componentDetails),
catchError(() => of({} as ComponentDetails)),
map(componentDetails => this.#mapComponentDetails(componentDetails)),
mapEach(log => new ComponentLogEntry(log)),
map(log => this.#updateDisplayValue(log, dictionaries)),
);
}
@ -91,4 +98,17 @@ export class ComponentLogService extends GenericService<void> {
#mapComponentDetails(componentDetails: ComponentDetails): IComponentLogEntry[] {
return Object.keys(componentDetails).reduce((res, key) => (res.push(componentDetails[key]), res), []);
}
#updateDisplayValue(componentLogs: ComponentLogEntry[], dictionaries: Dictionary[]): ComponentLogEntry[] {
for (const componentLog of componentLogs) {
for (const componentValue of componentLog.componentValues) {
for (const reference of componentValue.entityReferences) {
const foundDictionary =
dictionaries.find(dict => dict.type === reference.type) ?? ({ label: reference.type } as Dictionary);
reference.displayValue = foundDictionary.label;
}
}
}
return componentLogs;
}
}

View File

@ -84,8 +84,6 @@ export const dossierTemplateChecker = (dw: Dossier, filter: INestedFilter) => dw
export const dossierStateChecker = (dw: Dossier, filter: INestedFilter) =>
dw.dossierStatusId === (filter.id === 'undefined' ? null : filter.id);
export const componentLogChecker = (componentLogEntry: ComponentLogEntry, filter: INestedFilter) => componentLogEntry.name === filter.id;
export const userTypeFilters: { [key in UserType]: (user: User) => boolean } = {
INACTIVE: (user: User) => !user.hasAnyRole,
REGULAR: (user: User) => user.roles.length === 1 && user.roles[0] === 'RED_USER',

View File

@ -367,6 +367,7 @@
"annotation": {
"pending": "(Pending analysis)"
},
"annotations": "",
"archived-dossiers-listing": {
"no-data": {
"title": "No archived dossiers."
@ -2182,6 +2183,13 @@
"header": "Resize {type}"
}
},
"revert-value-dialog": {
"actions": {
"cancel": "",
"revert": ""
},
"title": ""
},
"roles": {
"inactive": "Inaktiv",
"manager-admin": "Manager & admin",

View File

@ -2183,6 +2183,13 @@
"header": "Resize {type}"
}
},
"revert-value-dialog": {
"actions": {
"cancel": "Cancel",
"revert": "Revert to original values"
},
"title": "Revert to the original values?"
},
"roles": {
"inactive": "Inactive",
"manager-admin": "Manager & admin",

View File

@ -367,6 +367,7 @@
"annotation": {
"pending": "(Pending analysis)"
},
"annotations": "",
"archived-dossiers-listing": {
"no-data": {
"title": "No archived dossiers."
@ -2182,6 +2183,13 @@
"header": "Resize {type}"
}
},
"revert-value-dialog": {
"actions": {
"cancel": "",
"revert": ""
},
"title": ""
},
"roles": {
"inactive": "Inaktiv",
"manager-admin": "Manager & admin",

View File

@ -2183,6 +2183,13 @@
"header": "Resize {type}"
}
},
"revert-value-dialog": {
"actions": {
"cancel": "Cancel",
"revert": "Revert to original values"
},
"title": "Revert to the original values?"
},
"roles": {
"inactive": "Inactive",
"manager-admin": "Manager & admin",

@ -1 +1 @@
Subproject commit 301ea99abe1be09687cdbe6d0fbae3bec7eefc23
Subproject commit 0d85f78d9a42e2ef8de387ec3b86e2857a4cc3e9