Merge branch 'VM/RED-6801'

This commit is contained in:
Valentin Mihai 2023-07-19 22:52:09 +03:00
commit 98dd16ca0d
11 changed files with 262 additions and 54 deletions

View File

@ -1,19 +1,82 @@
<div class="header-wrapper">
<div class="header-left">
<div class="heading">
<div>{{ dossierDictionary?.label }}</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
{{ 'edit-dossier-dialog.dictionary.entries' | translate : { length: (dossierDictionary?.entries || []).length } }}
<div class="dictionary-content" *ngIf="dictionaries && selectedDictionary">
<div class="dictionaries">
<div
class="dictionary"
*ngFor="let dictionary of dictionaries"
[class.active]="dictionary.label === selectedDictionary.label"
(click)="selectDictionary(dictionary)"
>
<redaction-annotation-icon [color]="dictionary.hexColor" label="R" type="square"></redaction-annotation-icon>
<div class="details">
<span> {{ dictionary.label }} </span>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
{{
dictionary.entries.length +
dictionary.falsePositiveEntries.length +
dictionary.falseRecommendationEntries.length
}}
entries
</div>
</div>
</div>
</div>
</div>
</div>
<div class="entries">
<div class="header-wrapper">
<div class="header-left">
<div class="heading">
<div class="flex-align-items-center">
{{ selectedDictionary?.label }}
</div>
<div class="small-label stats-subtitle">
<div>
<mat-icon svgIcon="red:entries"></mat-icon>
<ng-container *ngIf="activeEntryType === entryTypes.ENTRY">
{{ 'edit-dossier-dialog.dictionary.entries' | translate : { length: entriesToDisplay.length } }}
</ng-container>
<ng-container *ngIf="activeEntryType === entryTypes.FALSE_POSITIVE">
{{
'edit-dossier-dialog.dictionary.false-positive-entries'
| translate : { length: entriesToDisplay.length }
}}
</ng-container>
<ng-container *ngIf="activeEntryType === entryTypes.FALSE_RECOMMENDATION">
{{
'edit-dossier-dialog.dictionary.false-recommendation-entries'
| translate : { length: entriesToDisplay.length }
}}
</ng-container>
</div>
</div>
</div>
</div>
<div class="header-right flex">
<iqser-icon-button
[label]="'edit-dossier-dialog.dictionary.to-redact' | translate"
[active]="activeEntryType === entryTypes.ENTRY"
(click)="selectEntryType(entryTypes.ENTRY)"
></iqser-icon-button>
<iqser-icon-button
[label]="'edit-dossier-dialog.dictionary.false-positives' | translate"
[active]="activeEntryType === entryTypes.FALSE_POSITIVE"
(click)="selectEntryType(entryTypes.FALSE_POSITIVE)"
></iqser-icon-button>
<iqser-icon-button
[label]="'edit-dossier-dialog.dictionary.false-recommendations' | translate"
[active]="activeEntryType === entryTypes.FALSE_RECOMMENDATION"
(click)="selectEntryType(entryTypes.FALSE_RECOMMENDATION)"
></iqser-icon-button>
</div>
</div>
<redaction-dictionary-manager
[canEdit]="canEdit"
[initialEntries]="dossierDictionary?.entries || []"
[withFloatingActions]="false"
></redaction-dictionary-manager>
<redaction-dictionary-manager
[canEdit]="canEdit"
[initialEntries]="entriesToDisplay || []"
[withFloatingActions]="false"
[selectedDictionaryType]="selectedDictionary.type"
[activeEntryType]="activeEntryType"
></redaction-dictionary-manager>
</div>
</div>

View File

@ -1,29 +1,68 @@
:host {
flex: 1;
:host ::ng-deep .content {
padding: 0;
}
.dictionary-content {
display: flex;
flex-direction: column;
height: 100%;
}
.header-wrapper {
display: flex;
justify-content: space-between;
.dictionaries {
border-right: 1px solid var(--iqser-separator);
width: 30%;
overflow-y: scroll;
.header-left {
display: flex;
.dictionary {
height: 40px;
padding: 15px;
border: 1px solid var(--iqser-separator);
display: flex;
gap: 10px;
cursor: pointer;
.iqser-input-group {
margin-left: 24px;
span {
font-weight: bold;
white-space: nowrap;
overflow: hidden !important;
text-overflow: ellipsis;
}
.details {
display: flex;
flex-direction: column;
gap: 5px;
}
&.active {
background: var(--iqser-grey-8);
box-shadow: 0 5px 4px -4px rgba(0, 0, 0, 0.2);
cursor: default;
}
}
}
.entries {
width: 100%;
padding: 24px 32px;
.header-wrapper {
display: flex;
justify-content: space-between;
.display-name {
display: flex;
align-items: center;
margin-bottom: 24px;
.header-left {
display: flex;
> div {
font-weight: 600;
.iqser-input-group {
margin-left: 24px;
}
}
.display-name {
display: flex;
align-items: center;
margin-bottom: 24px;
> div {
font-weight: 600;
}
}
}
}
}

View File

@ -1,12 +1,13 @@
import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { Dictionary, Dossier } from '@red/domain';
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, Dossier } from '@red/domain';
import { EditDossierSaveResult, EditDossierSectionInterface } from '../edit-dossier-section.interface';
import { PermissionsService } from '@services/permissions.service';
import { DictionaryManagerComponent } from '@shared/components/dictionary-manager/dictionary-manager.component';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { CircleButtonTypes, LoadingService } from '@iqser/common-ui';
import { firstValueFrom } from 'rxjs';
import { DossiersDialogService } from '../../../services/dossiers-dialog.service';
import { firstValueFrom } from 'rxjs';
import { List } from '@iqser/common-ui/lib/utils';
@Component({
selector: 'redaction-edit-dossier-dictionary',
@ -17,9 +18,13 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
@Input() dossier: Dossier;
canEdit = false;
dossierDictionary: Dictionary;
dictionaries: Dictionary[];
selectedDictionary: Dictionary;
activeEntryType = DictionaryEntryTypes.ENTRY;
entriesToDisplay: List = [];
readonly circleButtonTypes = CircleButtonTypes;
readonly entryTypes = DictionaryEntryTypes;
@ViewChild(DictionaryManagerComponent, { static: false }) private readonly _dictionaryManager: DictionaryManagerComponent;
constructor(
@ -30,7 +35,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
) {}
get changed(): boolean {
return this._dictionaryManager.editor.hasChanges;
return this._dictionaryManager?.editor.hasChanges;
}
get disabled(): boolean {
@ -38,7 +43,7 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
}
get valid(): boolean {
return this._dictionaryManager.editor.hasChanges;
return this._dictionaryManager?.editor.hasChanges;
}
async ngOnInit() {
@ -54,9 +59,10 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
this._dictionaryManager.editor.currentEntries,
this._dictionaryManager.initialEntries,
this.dossier.dossierTemplateId,
this.dossierDictionary.type,
this.selectedDictionary.type,
this.dossier.id,
false,
this.activeEntryType,
);
await this.#updateDossierDictionary();
@ -70,8 +76,47 @@ export class EditDossierDictionaryComponent implements EditDossierSectionInterfa
this._dictionaryManager.revert();
}
selectDictionary(dictionary: Dictionary, entryType: DictionaryEntryType = DictionaryEntryTypes.ENTRY) {
this.selectedDictionary = dictionary;
this.selectEntryType(entryType);
}
selectEntryType(selectedEntryType: DictionaryEntryType) {
this.activeEntryType = selectedEntryType;
switch (selectedEntryType) {
case DictionaryEntryTypes.ENTRY: {
this.entriesToDisplay = this.selectedDictionary.entries;
break;
}
case DictionaryEntryTypes.FALSE_POSITIVE: {
this.entriesToDisplay = this.selectedDictionary.falsePositiveEntries;
break;
}
case DictionaryEntryTypes.FALSE_RECOMMENDATION: {
this.entriesToDisplay = this.selectedDictionary.falseRecommendationEntries;
break;
}
}
}
async #updateDossierDictionary() {
const { dossierId, dossierTemplateId } = this.dossier;
this.dossierDictionary = await this._dictionaryService.loadDossierDictionary(dossierTemplateId, dossierId);
const dictionaryTypes = this._dictionaryService.getPossibleDictionaries(dossierTemplateId, false, true).map(d => d.type);
this.dictionaries = await firstValueFrom(
this._dictionaryService.loadDictionaryEntriesByType(dictionaryTypes, dossierTemplateId, dossierId),
);
//TODO remove this when backend will send also the type
this.#setType(dictionaryTypes);
this.selectDictionary(this.dictionaries[0], this.activeEntryType);
}
//TODO remove this when backend will send also the type
#setType(dictionaryTypes: string[]) {
for (let i = 0; i < this.dictionaries.length; i++) {
const { ...dictionaryWithType } = this.dictionaries[i];
dictionaryWithType.type = dictionaryTypes[i];
this.dictionaries[i] = dictionaryWithType as Dictionary;
}
}
}

View File

@ -13,14 +13,16 @@
flex: 1;
.content {
padding: 24px 32px;
overflow: auto;
@include common-mixins.scroll-bar;
flex: 1;
box-sizing: border-box;
padding: 24px 32px;
margin-right: 16px;
&.no-padding {
padding: 0;
overflow: hidden;
}
&.no-actions {

View File

@ -83,7 +83,7 @@ export class EditDossierDialogComponent extends BaseDialogComponent implements A
}
get noPaddingTab(): boolean {
return ['dossierAttributes'].includes(this.activeNav);
return ['dossierAttributes', 'dossierDictionary'].includes(this.activeNav);
}
get showHeading(): boolean {

View File

@ -1,6 +1,14 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild } from '@angular/core';
import { IconButtonTypes, LoadingService } from '@iqser/common-ui';
import { Dictionary, DICTIONARY_TYPE_KEY_MAP, DictionaryType, Dossier, DossierTemplate } from '@red/domain';
import {
Dictionary,
DICTIONARY_TYPE_KEY_MAP,
DictionaryEntryType,
DictionaryEntryTypes,
DictionaryType,
Dossier,
DossierTemplate,
} from '@red/domain';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
@ -11,6 +19,7 @@ import { saveAs } from 'file-saver';
import { Debounce, List } from '@iqser/common-ui/lib/utils';
import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration;
import FindMatch = monaco.editor.FindMatch;
import { firstValueFrom } from 'rxjs';
const SMOOTH_SCROLL = 0;
const HELP_MODE_KEYS = {
@ -35,6 +44,8 @@ export class DictionaryManagerComponent implements OnChanges {
@Input() canEdit = false;
@Input() canDownload = false;
@Input() isLeavingPage = false;
@Input() selectedDictionaryType = 'dossier_redaction';
@Input() activeEntryType: DictionaryEntryType = DictionaryEntryTypes.ENTRY;
@Output() readonly saveDictionary = new EventEmitter<string[]>();
@ViewChild(EditorComponent) readonly editor: EditorComponent;
readonly iconButtonTypes = IconButtonTypes;
@ -197,6 +208,12 @@ export class DictionaryManagerComponent implements OnChanges {
if (!changes.isLeavingPage) {
this.revert();
}
if (changes.activeEntryType && this._dossier?.dossierTemplateId && this.dossier?.dossierId) {
this.#onDossierChanged(this._dossier.dossierTemplateId, this._dossier.dossierId).then(entries => {
this.diffEditorText = entries;
this.showDiffEditor = true;
});
}
}
private _applySearchDecorations() {
@ -223,9 +240,19 @@ export class DictionaryManagerComponent implements OnChanges {
this.editor.codeEditor.revealLineInCenter(range.startLineNumber, SMOOTH_SCROLL);
}
async #onDossierChanged(dossierTemplateId: string, dossierId?: string, type = 'dossier_redaction') {
const dictionary = await this._dictionaryService.getForType(dossierTemplateId, type, dossierId);
return this.#toString([...dictionary.entries]);
async #onDossierChanged(dossierTemplateId: string, dossierId?: string) {
const dictionary = (
await firstValueFrom(
this._dictionaryService.loadDictionaryEntriesByType([this.selectedDictionaryType], dossierTemplateId, dossierId),
)
)[0];
const activeEntries =
this.activeEntryType === DictionaryEntryTypes.ENTRY
? [...dictionary.entries]
: this.activeEntryType === DictionaryEntryTypes.FALSE_POSITIVE
? [...dictionary.falsePositiveEntries]
: [...dictionary.falseRecommendationEntries];
return this.#toString(activeEntries);
}
#toString(entries: string[]): string {

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { firstValueFrom, forkJoin, Observable, throwError } from 'rxjs';
import { firstValueFrom, forkJoin, Observable, of, throwError } from 'rxjs';
import { EntitiesService, QueryParam, Toaster } from '@iqser/common-ui';
import { Dictionary, DictionaryEntryType, DictionaryEntryTypes, IDictionary, IUpdateDictionary, SuperTypes } from '@red/domain';
import { catchError, map, switchMap, tap } from 'rxjs/operators';
@ -261,6 +261,17 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
);
}
loadDictionaryEntriesByType(types: string[], dossierTemplateId: string, dossierId: string): Observable<Dictionary[]> {
const queryParams = [{ key: 'dossierId', value: dossierId }];
const requests = [];
for (const type of types) {
const request = this.getAll(`${this._defaultModelPath}/merged/${type}/${dossierTemplateId}`, queryParams);
requests.push(request);
}
return forkJoin(requests);
}
/**
* Add dictionary entries with entry type.
*/
@ -291,9 +302,10 @@ export class DictionaryService extends EntitiesService<IDictionary, Dictionary>
dictionaryEntryType: DictionaryEntryType,
dossierId: string,
) {
const queryParams = dossierId
? [{ key: 'dossierId', value: dossierId }]
: [{ key: 'dictionaryEntryType', value: dictionaryEntryType }];
const queryParams = [
{ key: 'dossierId', value: dossierId },
{ key: 'dictionaryEntryType', value: dictionaryEntryType },
];
const url = `${this._defaultModelPath}/delete/${type}/${dossierTemplateId}`;
return firstValueFrom(this._post(entries, url, queryParams));
}

View File

@ -1119,7 +1119,12 @@
"change-successful": "Dossier wurde aktualisiert.",
"delete-successful": "Dossier wurde gelöscht.",
"dictionary": {
"entries": "{length} {length, plural, one{entry} other{entries}}"
"entries": "{length} {length, plural, one{entry} other{entries}}",
"false-positive-entries": "",
"false-positives": "",
"false-recommendation-entries": "",
"false-recommendations": "",
"to-redact": ""
},
"general-info": {
"form": {

View File

@ -675,7 +675,7 @@
},
"revert-changes": "Revert",
"save-changes": "Save Changes",
"search": "Search...",
"search": "Search entries ...",
"select-dictionary": "Select a dictionary above to compare with the current one.",
"success": {
"generic": "Dictionary updated!"
@ -1119,7 +1119,12 @@
"change-successful": "Dossier {dossierName} was updated.",
"delete-successful": "Dossier {dossierName} was deleted.",
"dictionary": {
"entries": "{length} {length, plural, one{entry} other{entries}}"
"entries": "{length} {length, plural, one{entry} other{entries}} to redact",
"false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}",
"false-positives": "False Positives",
"false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}",
"false-recommendations": "False Recommendations",
"to-redact": "To Redact"
},
"general-info": {
"form": {

View File

@ -1119,7 +1119,12 @@
"change-successful": "Dossier wurde aktualisiert.",
"delete-successful": "Dossier wurde gelöscht.",
"dictionary": {
"entries": "{length} {length, plural, one{entry} other{entries}}"
"entries": "{length} {length, plural, one{entry} other{entries}}",
"false-positive-entries": "",
"false-positives": "",
"false-recommendation-entries": "",
"false-recommendations": "",
"to-redact": ""
},
"general-info": {
"form": {

View File

@ -675,7 +675,7 @@
},
"revert-changes": "Revert",
"save-changes": "Save Changes",
"search": "Search...",
"search": "Search entries ...",
"select-dictionary": "Select a dictionary above to compare with the current one.",
"success": {
"generic": "Dictionary updated!"
@ -1119,7 +1119,12 @@
"change-successful": "Dossier {dossierName} was updated.",
"delete-successful": "Dossier {dossierName} was deleted.",
"dictionary": {
"entries": "{length} {length, plural, one{entry} other{entries}}"
"entries": "{length} {length, plural, one{entry} other{entries}} to redact",
"false-positive-entries": "{length} false positive {length, plural, one{entry} other{entries}}",
"false-positives": "False Positives",
"false-recommendation-entries": "{length} false recommendation {length, plural, one{entry} other{entries}}",
"false-recommendations": "False Recommendations",
"to-redact": "To Redact"
},
"general-info": {
"form": {