diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html index e60075d6a..74e4e0b8f 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.html @@ -1,19 +1,82 @@ -
-
-
-
{{ dossierDictionary?.label }}
-
-
- - {{ 'edit-dossier-dialog.dictionary.entries' | translate : { length: (dossierDictionary?.entries || []).length } }} +
+
+
+ +
+ {{ dictionary.label }} +
+
+ + {{ + dictionary.entries.length + + dictionary.falsePositiveEntries.length + + dictionary.falseRecommendationEntries.length + }} + entries +
-
+
+
+
+
+
+ {{ selectedDictionary?.label }} +
+
+
+ + + {{ 'edit-dossier-dialog.dictionary.entries' | translate : { length: entriesToDisplay.length } }} + + + {{ + 'edit-dossier-dialog.dictionary.false-positive-entries' + | translate : { length: entriesToDisplay.length } + }} + + + {{ + 'edit-dossier-dialog.dictionary.false-recommendation-entries' + | translate : { length: entriesToDisplay.length } + }} + +
+
+
+
+
+ + + +
+
- + +
+
diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.scss b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.scss index 7c1305a41..80786cb85 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.scss +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.scss @@ -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; + } + } } } } diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts index 35f011932..18f531fd3 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/dictionary/edit-dossier-dictionary.component.ts @@ -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; + } } } diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.scss b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.scss index 47f54c0b8..2b8c3be56 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.scss +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.scss @@ -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 { diff --git a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts index d787fca08..75bcc993e 100644 --- a/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts +++ b/apps/red-ui/src/app/modules/shared-dossiers/dialogs/edit-dossier-dialog/edit-dossier-dialog.component.ts @@ -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 { diff --git a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts index 8f35df1a1..dcada2b58 100644 --- a/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts +++ b/apps/red-ui/src/app/modules/shared/components/dictionary-manager/dictionary-manager.component.ts @@ -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(); @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 { diff --git a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts index 35681d847..6402c0485 100644 --- a/apps/red-ui/src/app/services/entity-services/dictionary.service.ts +++ b/apps/red-ui/src/app/services/entity-services/dictionary.service.ts @@ -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 ); } + loadDictionaryEntriesByType(types: string[], dossierTemplateId: string, dossierId: string): Observable { + 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 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)); } diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index 81eb0aa52..cc5cea4ca 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -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": { diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 208d0bf4e..8cc21d464 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -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": { diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 4d505af32..6039ec1e7 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -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": { diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 527f75897..62b8845b5 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -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": {