From d3f293f622387c464d2094596e6e82678fadaf74 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 29 May 2024 20:16:23 +0300 Subject: [PATCH 1/4] RED-9201 - UI for Component Mapping Tables --- .../app/modules/admin/admin-routing.module.ts | 9 + .../src/app/modules/admin/admin.module.ts | 2 + .../component-mappings-screen.component.html | 91 ++++++++++ .../component-mappings-screen.component.ts | 47 +++++ .../admin-side-nav.component.ts | 5 + .../component-mappings.service.ts | 52 ++++++ apps/red-ui/src/app/users/roles.ts | 4 + apps/red-ui/src/assets/i18n/redact/de.json | 167 ++++++++++-------- apps/red-ui/src/assets/i18n/redact/en.json | 21 ++- apps/red-ui/src/assets/i18n/scm/de.json | 167 ++++++++++-------- apps/red-ui/src/assets/i18n/scm/en.json | 21 ++- libs/red-domain/src/index.ts | 1 + .../component-mapping.model.ts | 18 ++ .../component-mappings/component-mapping.ts | 5 + .../src/lib/component-mappings/index.ts | 2 + 15 files changed, 462 insertions(+), 150 deletions(-) create mode 100644 apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.html create mode 100644 apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts create mode 100644 apps/red-ui/src/app/services/entity-services/component-mappings.service.ts create mode 100644 libs/red-domain/src/lib/component-mappings/component-mapping.model.ts create mode 100644 libs/red-domain/src/lib/component-mappings/component-mapping.ts create mode 100644 libs/red-domain/src/lib/component-mappings/index.ts diff --git a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts index 252f8806d..3d4098c35 100644 --- a/apps/red-ui/src/app/modules/admin/admin-routing.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin-routing.module.ts @@ -19,6 +19,7 @@ import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-scre import { PermissionsGuard } from '@guards/permissions-guard'; import { Roles } from '@users/roles'; import { IqserAuthGuard } from '@iqser/common-ui/lib/users'; +import { ComponentMappingsScreenComponent } from './screens/component-mappings/component-mappings-screen.component'; const dossierTemplateIdRoutes: IqserRoutes = [ { @@ -76,6 +77,14 @@ const dossierTemplateIdRoutes: IqserRoutes = [ }, loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule), }, + { + path: 'component-mappings', + component: ComponentMappingsScreenComponent, + canActivate: [CompositeRouteGuard], + data: { + routeGuards: [IqserAuthGuard, RedRoleGuard], + }, + }, { path: 'file-attributes', component: BaseDossierTemplateScreenComponent, diff --git a/apps/red-ui/src/app/modules/admin/admin.module.ts b/apps/red-ui/src/app/modules/admin/admin.module.ts index f34a45611..f41dbc11b 100644 --- a/apps/red-ui/src/app/modules/admin/admin.module.ts +++ b/apps/red-ui/src/app/modules/admin/admin.module.ts @@ -57,6 +57,7 @@ import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { SelectComponent } from '@shared/components/select/select.component'; import { PaginationComponent } from '@common-ui/pagination/pagination.component'; import { AddCloneDossierTemplateDialogComponent } from './dialogs/add-clone-dossier-template-dialog/add-clone-dossier-template-dialog.component'; +import { ComponentMappingsScreenComponent } from './screens/component-mappings/component-mappings-screen.component'; const dialogs = [ AddCloneDossierTemplateDialogComponent, @@ -76,6 +77,7 @@ const screens = [ DigitalSignatureScreenComponent, UserListingScreenComponent, GeneralConfigScreenComponent, + ComponentMappingsScreenComponent, ]; const components = [ diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.html new file mode 100644 index 000000000..863ea0597 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.html @@ -0,0 +1,91 @@ +
+ + +
+
+ + + +
+ + +
+
+
+ + +
+ +
+ +
+
+
+ + + + + + + + +
+
+ {{ entity.name }} +
+ +
+ {{ entity.value }} +
+ +
+
+ + + +
+
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts new file mode 100644 index 000000000..510462288 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts @@ -0,0 +1,47 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ListingComponent, TableColumnConfig, listingProvidersFactory, LoadingService, IconButtonTypes } from '@iqser/common-ui'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { defaultColorsTranslations } from '@translations/default-colors-translations'; +import { Roles } from '@users/roles'; +import { getCurrentUser } from '@common-ui/users'; +import { User } from '@red/domain'; +import { ComponentMapping } from '@red/domain'; +import { combineLatest } from 'rxjs'; +import { ComponentMappingsService } from '@services/entity-services/component-mappings.service'; +import { tap } from 'rxjs/operators'; + +@Component({ + templateUrl: './component-mappings-screen.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + providers: listingProvidersFactory(ComponentMappingsScreenComponent), +}) +export class ComponentMappingsScreenComponent extends ListingComponent implements OnInit { + tableColumnConfigs: readonly TableColumnConfig[] = [ + { label: _('component-mappings-screen.table-col-names.name'), sortByKey: 'searchKey' }, + { label: _('component-mappings-screen.table-col-names.version'), width: '2fr' }, + ]; + readonly tableHeaderLabel = _('component-mappings-screen.table-header.title'); + readonly context$; + protected readonly currentUser = getCurrentUser(); + protected readonly translations = defaultColorsTranslations; + protected readonly roles = Roles; + protected readonly iconButtonTypes = IconButtonTypes; + + constructor( + private readonly _loadingService: LoadingService, + private readonly _componentMappingService: ComponentMappingsService, + ) { + super(); + this.context$ = combineLatest([this._componentMappingService.loadData()]).pipe( + tap(([mappings]) => this.entitiesService.setEntities(mappings)), + ); + } + + ngOnInit() { + this._loadingService.stop(); + } + + openAddEditComponentMappingDialog(entity?: ComponentMapping) {} + + openDeleteComponentMappingDialog(entity?: ComponentMapping) {} +} diff --git a/apps/red-ui/src/app/modules/admin/shared/components/admin-side-nav/admin-side-nav.component.ts b/apps/red-ui/src/app/modules/admin/shared/components/admin-side-nav/admin-side-nav.component.ts index 1d9d63c4a..83620ad86 100644 --- a/apps/red-ui/src/app/modules/admin/shared/components/admin-side-nav/admin-side-nav.component.ts +++ b/apps/red-ui/src/app/modules/admin/shared/components/admin-side-nav/admin-side-nav.component.ts @@ -106,6 +106,11 @@ export class AdminSideNavComponent implements OnInit { (this.isIqserDevMode || this.canAccessRulesInDocumine) && this._permissionsService.has(Roles.rules.read), }, + { + screen: 'component-mappings', + label: _('admin-side-nav.component-mappings'), + show: this.isDocumine, + }, { screen: 'default-colors', label: _('admin-side-nav.default-colors'), diff --git a/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts b/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts new file mode 100644 index 000000000..384af91bc --- /dev/null +++ b/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts @@ -0,0 +1,52 @@ +import { Injectable } from '@angular/core'; +import { EntitiesService } from '@iqser/common-ui'; +import { ComponentMapping, IComponentMapping } from '@red/domain'; +import { Observable, of } from 'rxjs'; + +const DATA = [ + { + id: '1', + name: 'OECD Number', + version: 2, + searchKey: 'OECD Number', + }, + { + id: '2', + name: 'Study Title', + version: 1, + searchKey: 'Study Title', + }, + { + id: '3', + name: 'Report Number', + version: 2, + searchKey: 'Report Number', + }, + { + id: '4', + name: 'GLP Study', + version: 5, + searchKey: 'GLP Study', + }, + { + id: '5', + name: 'Performing Laboratory', + version: 2, + searchKey: 'Performing Laboratory', + }, + { + id: '6', + name: 'Test', + version: 3, + searchKey: 'Test', + }, +]; + +@Injectable({ + providedIn: 'root', +}) +export class ComponentMappingsService extends EntitiesService { + loadData(): Observable { + return of(DATA); + } +} diff --git a/apps/red-ui/src/app/users/roles.ts b/apps/red-ui/src/app/users/roles.ts index 2ae41147f..dc7ee3b70 100644 --- a/apps/red-ui/src/app/users/roles.ts +++ b/apps/red-ui/src/app/users/roles.ts @@ -21,6 +21,10 @@ export const Roles = { read: 'red-read-rules', write: 'red-write-rules', }, + componentMappings: { + read: 'red-read-rules', + write: 'red-write-rules', + }, redactions: { write: 'red-add-redaction', readManual: 'red-read-manual-redactions', diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index 67efc99c7..f0e9c8432 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -231,6 +231,7 @@ }, "admin-side-nav": { "audit": "Audit", + "component-mappings": "", "component-rule-editor": "", "configurations": "Configurations", "default-colors": "Default colors", @@ -254,9 +255,6 @@ "watermarks": "Watermarks" }, "analysis-disabled": "", - "annotation": { - "pending": "(Pending analysis)" - }, "annotation-actions": { "accept-recommendation": { "label": "Empfehlung annehmen" @@ -311,14 +309,14 @@ "error": "Rekategorisierung des Bildes gescheitert: {error}", "success": "Bild wurde einer neuen Kategorie zugeordnet." }, - "remove": { - "error": "Fehler beim Entfernen der Schwärzung: {error}", - "success": "Schwärzung entfernt!" - }, "remove-hint": { "error": "Failed to remove hint: {error}", "success": "Hint removed" }, + "remove": { + "error": "Fehler beim Entfernen der Schwärzung: {error}", + "success": "Schwärzung entfernt!" + }, "undo": { "error": "Die Aktion konnte nicht rückgängig gemacht werden. Fehler: {error}", "success": "erfolgreich Rückgängig gemacht" @@ -331,15 +329,15 @@ "remove-highlights": { "label": "Remove selected earmarks" }, - "resize": { - "label": "Größe ändern" - }, "resize-accept": { "label": "Größe speichern" }, "resize-cancel": { "label": "Größenänderung abbrechen" }, + "resize": { + "label": "Größe ändern" + }, "see-references": { "label": "See references" }, @@ -371,6 +369,9 @@ "skipped": "Übersprungen", "text-highlight": "Earmark" }, + "annotation": { + "pending": "(Pending analysis)" + }, "archived-dossiers-listing": { "no-data": { "title": "No archived dossiers." @@ -516,6 +517,24 @@ }, "title": "Component view" }, + "component-mappings-screen": { + "action": { + "delete": "", + "edit": "" + }, + "add-new": "", + "bulk-actions": { + "delete": "" + }, + "search": "", + "table-col-names": { + "name": "", + "version": "" + }, + "table-header": { + "title": "" + } + }, "component-rules-screen": { "error": { "generic": "" @@ -576,18 +595,14 @@ "warning": "Achtung: Diese Aktion kann nicht rückgängig gemacht werden!" }, "confirmation-dialog": { - "approve-file": { - "question": "Dieses Dokument enthält ungesehene Änderungen. Möchten Sie es trotzdem genehmigen?", - "title": "Warnung!" - }, "approve-file-without-analysis": { "confirmationText": "Approve without analysis", "denyText": "Cancel", "question": "Analysis required to detect new redactions.", "title": "Warning!" }, - "approve-multiple-files": { - "question": "Mindestens eine der ausgewählten Dateien enthält ungesehene Änderungen. Möchten Sie sie trotzdem genehmigen?", + "approve-file": { + "question": "Dieses Dokument enthält ungesehene Änderungen. Möchten Sie es trotzdem genehmigen?", "title": "Warnung!" }, "approve-multiple-files-without-analysis": { @@ -596,6 +611,10 @@ "question": "Analysis required to detect new redactions for at least one file.", "title": "Warning" }, + "approve-multiple-files": { + "question": "Mindestens eine der ausgewählten Dateien enthält ungesehene Änderungen. Möchten Sie sie trotzdem genehmigen?", + "title": "Warnung!" + }, "assign-file-to-me": { "question": { "multiple": "Dieses Dokument wird gerade von einer anderen Person geprüft. Möchten Sie Reviewer werden und sich selbst dem Dokument zuweisen?", @@ -959,13 +978,13 @@ "recent": "Neu ({hours} h)", "unassigned": "Niemandem zugewiesen" }, - "reanalyse": { - "action": "Datei analysieren" - }, "reanalyse-dossier": { "error": "Die Dateien konnten nicht für eine Reanalyse eingeplant werden. Bitte versuchen Sie es erneut.", "success": "Dateien für Reanalyse vorgesehen." }, + "reanalyse": { + "action": "Datei analysieren" + }, "start-auto-analysis": "Enable auto-analysis", "stop-auto-analysis": "Stop auto-analysis", "table-col-names": { @@ -1035,14 +1054,6 @@ "total-documents": "Anzahl der Dokumente", "total-people": "{count} {count, plural, one{user} other {users}}" }, - "dossier-templates": { - "label": "Dossier-Vorlagen", - "status": { - "active": "Active", - "inactive": "Inactive", - "incomplete": "Incomplete" - } - }, "dossier-templates-listing": { "action": { "clone": "Clone template", @@ -1078,6 +1089,14 @@ "title": "{length} {length, plural, one{Dossier-Vorlage} other{Dossier-Vorlagen}}" } }, + "dossier-templates": { + "label": "Dossier-Vorlagen", + "status": { + "active": "Active", + "inactive": "Inactive", + "incomplete": "Incomplete" + } + }, "dossier-watermark-selector": { "heading": "Watermarks on documents", "no-watermark": "There is no watermark defined for the dossier template.
Contact your app admin to define one.", @@ -1273,15 +1292,6 @@ "title": "{length} {length, plural, one{Wörterbuch} other{Wörterbücher}}" } }, - "entity": { - "info": { - "actions": { - "revert": "Revert", - "save": "Save changes" - }, - "heading": "Edit entity" - } - }, "entity-rules-screen": { "error": { "generic": "An error ocurred... Entity rules update failed!" @@ -1296,19 +1306,28 @@ "warning-text": "Warning: experimental feature!", "warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} found in rules" }, + "entity": { + "info": { + "actions": { + "revert": "Revert", + "save": "Save changes" + }, + "heading": "Edit entity" + } + }, "error": { "deleted-entity": { "dossier": { "action": "Zurück zur Übersicht", "label": "Dieses Dossier wurde gelöscht!" }, - "file": { - "action": "Zurück zum Dossier", - "label": "Diese Datei wurde gelöscht!" - }, "file-dossier": { "action": "Zurück zur Übersicht", "label": "Das Dossier dieser Datei wurde gelöscht!" + }, + "file": { + "action": "Zurück zum Dossier", + "label": "Diese Datei wurde gelöscht!" } }, "file-preview": { @@ -1326,12 +1345,6 @@ }, "exact-date": "{day} {month} {year} um {hour}:{minute} Uhr", "file": "Datei", - "file-attribute": { - "update": { - "error": "Failed to update file attribute value!", - "success": "File attribute value has been updated successfully!" - } - }, "file-attribute-encoding-types": { "ascii": "ASCII", "iso": "ISO-8859-1", @@ -1342,6 +1355,12 @@ "number": "Nummer", "text": "Freier Text" }, + "file-attribute": { + "update": { + "error": "Failed to update file attribute value!", + "success": "File attribute value has been updated successfully!" + } + }, "file-attributes-configurations": { "cancel": "Cancel", "form": { @@ -1560,15 +1579,6 @@ "csv": "File attributes were imported successfully from uploaded CSV file." } }, - "filter": { - "analysis": "Analyse erforderlich", - "comment": "Kommentare", - "hint": "Nut Hinweise", - "image": "Bilder", - "none": "Keine Anmerkungen", - "redaction": "Geschwärzt", - "updated": "Aktualisiert" - }, "filter-menu": { "filter-options": "Filteroptionen", "filter-types": "Filter", @@ -1578,6 +1588,15 @@ "unseen-pages": "Nur Anmerkungen auf unsichtbaren Seiten", "with-comments": "Nur Anmerkungen mit Kommentaren" }, + "filter": { + "analysis": "Analyse erforderlich", + "comment": "Kommentare", + "hint": "Nut Hinweise", + "image": "Bilder", + "none": "Keine Anmerkungen", + "redaction": "Geschwärzt", + "updated": "Aktualisiert" + }, "filters": { "assigned-people": "Beauftragt", "documents-status": "Documents state", @@ -1850,13 +1869,6 @@ "user-promoted-to-approver": "{user} wurde im Dossier {dossierHref, select, null{{dossierName}} other{{dossierName}}} zum Genehmiger ernannt!", "user-removed-as-dossier-member": "{user} wurde als Mitglied von: {dossierHref, select, null{{dossierName}} other{{dossierName}}} entfernt!" }, - "notifications": { - "button-text": "Notifications", - "deleted-dossier": "Deleted dossier", - "label": "Benachrichtigungen", - "mark-all-as-read": "Alle als gelesen markieren", - "mark-as": "Mark as {type, select, read{read} unread{unread} other{}}" - }, "notifications-screen": { "category": { "email-notifications": "E-Mail Benachrichtigungen", @@ -1870,6 +1882,7 @@ "dossier": "Dossierbezogene Benachrichtigungen", "other": "Andere Benachrichtigungen" }, + "options-title": "Wählen Sie aus, in welcher Kategorie Sie benachrichtigt werden möchten", "options": { "ASSIGN_APPROVER": "Wenn ich einem Dokument als Genehmiger zugewiesen bin", "ASSIGN_REVIEWER": "Wenn ich einem Dokument als Überprüfer zugewiesen bin", @@ -1887,7 +1900,6 @@ "USER_PROMOTED_TO_APPROVER": "Wenn ich Genehmiger in einem Dossier werde", "USER_REMOVED_AS_DOSSIER_MEMBER": "Wenn ich die Dossier-Mitgliedschaft verliere" }, - "options-title": "Wählen Sie aus, in welcher Kategorie Sie benachrichtigt werden möchten", "schedule": { "daily": "Tägliche Zusammenfassung", "instant": "Sofortig", @@ -1895,6 +1907,13 @@ }, "title": "Benachrichtigungseinstellungen" }, + "notifications": { + "button-text": "Notifications", + "deleted-dossier": "Deleted dossier", + "label": "Benachrichtigungen", + "mark-all-as-read": "Alle als gelesen markieren", + "mark-as": "Mark as {type, select, read{read} unread{unread} other{}}" + }, "ocr": { "confirmation-dialog": { "cancel": "Cancel", @@ -1986,16 +2005,16 @@ "warnings-label": "Prompts and dialogs", "warnings-subtitle": "Do not show again options" }, - "processing": { - "basic": "Processing", - "ocr": "OCR" - }, "processing-status": { "ocr": "OCR", "pending": "Pending", "processed": "processed", "processing": "Processing" }, + "processing": { + "basic": "Processing", + "ocr": "OCR" + }, "readonly": "Lesemodus", "readonly-archived": "Read only (archived)", "redact-text": { @@ -2219,12 +2238,6 @@ "red-user-admin": "Benutzer-Admin", "regular": "Regulär" }, - "search": { - "active-dossiers": "ganze Plattform", - "all-dossiers": "all documents", - "placeholder": "Nach Dokumenten oder Dokumenteninhalt suchen", - "this-dossier": "in diesem Dossier" - }, "search-screen": { "cols": { "assignee": "Bevollmächtigter", @@ -2248,6 +2261,12 @@ "no-match": "Keine Dokumente entsprechen Ihren aktuellen Filtern.", "table-header": "{length} {length, plural, one{Suchergebnis} other{Suchergebnisse}}" }, + "search": { + "active-dossiers": "ganze Plattform", + "all-dossiers": "all documents", + "placeholder": "Nach Dokumenten oder Dokumenteninhalt suchen", + "this-dossier": "in diesem Dossier" + }, "seconds": "seconds", "size": "Size", "smtp-auth-config": { @@ -2502,4 +2521,4 @@ } }, "yesterday": "Gestern" -} \ No newline at end of file +} diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 860fd294f..992f36c85 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -231,6 +231,7 @@ }, "admin-side-nav": { "audit": "Audit", + "component-mappings": "Component mappings", "component-rule-editor": "", "configurations": "Configurations", "default-colors": "Default colors", @@ -516,6 +517,24 @@ }, "title": "Component view" }, + "component-mappings-screen": { + "action": { + "delete": "Delete mapping", + "edit": "Edit mapping" + }, + "add-new": "New Mapping", + "bulk-actions": { + "delete": "Delete selected mappings" + }, + "search": "Search by name...", + "table-col-names": { + "name": "name", + "version": "version" + }, + "table-header": { + "title": "Component mapping" + } + }, "component-rules-screen": { "error": { "generic": "" @@ -2502,4 +2521,4 @@ } }, "yesterday": "Yesterday" -} \ No newline at end of file +} diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index f04ac163a..99d638f9c 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -231,6 +231,7 @@ }, "admin-side-nav": { "audit": "Audit", + "component-mappings": "", "component-rule-editor": "Component rule editor", "configurations": "Configurations", "default-colors": "Default colors", @@ -254,9 +255,6 @@ "watermarks": "Watermarks" }, "analysis-disabled": "Analysis disabled", - "annotation": { - "pending": "(Pending analysis)" - }, "annotation-actions": { "accept-recommendation": { "label": "Empfehlung annehmen" @@ -311,14 +309,14 @@ "error": "Rekategorisierung des Bildes gescheitert: {error}", "success": "Bild wurde einer neuen Kategorie zugeordnet." }, - "remove": { - "error": "Fehler beim Entfernen der Schwärzung: {error}", - "success": "Schwärzung entfernt!" - }, "remove-hint": { "error": "Failed to remove hint: {error}", "success": "Hint removed!" }, + "remove": { + "error": "Fehler beim Entfernen der Schwärzung: {error}", + "success": "Schwärzung entfernt!" + }, "undo": { "error": "Die Aktion konnte nicht rückgängig gemacht werden. Fehler: {error}", "success": "erfolgreich Rückgängig gemacht" @@ -331,15 +329,15 @@ "remove-highlights": { "label": "Remove selected earmarks" }, - "resize": { - "label": "Größe ändern" - }, "resize-accept": { "label": "Größe speichern" }, "resize-cancel": { "label": "Größenänderung abbrechen" }, + "resize": { + "label": "Größe ändern" + }, "see-references": { "label": "See references" }, @@ -371,6 +369,9 @@ "skipped": "Übersprungen", "text-highlight": "Earmark" }, + "annotation": { + "pending": "(Pending analysis)" + }, "archived-dossiers-listing": { "no-data": { "title": "No archived dossiers." @@ -516,6 +517,24 @@ }, "title": "Structured Component Management" }, + "component-mappings-screen": { + "action": { + "delete": "", + "edit": "" + }, + "add-new": "", + "bulk-actions": { + "delete": "" + }, + "search": "", + "table-col-names": { + "name": "", + "version": "" + }, + "table-header": { + "title": "" + } + }, "component-rules-screen": { "error": { "generic": "Something went wrong... Component rules update failed!" @@ -576,18 +595,14 @@ "warning": "Achtung: Diese Aktion kann nicht rückgängig gemacht werden!" }, "confirmation-dialog": { - "approve-file": { - "question": "Dieses Dokument enthält ungesehene Änderungen. Möchten Sie es trotzdem genehmigen?", - "title": "Warnung!" - }, "approve-file-without-analysis": { "confirmationText": "Approve without analysis", "denyText": "Cancel", "question": "Analysis required to detect new components.", "title": "Warning!" }, - "approve-multiple-files": { - "question": "Mindestens eine der ausgewählten Dateien enthält ungesehene Änderungen. Möchten Sie sie trotzdem genehmigen?", + "approve-file": { + "question": "Dieses Dokument enthält ungesehene Änderungen. Möchten Sie es trotzdem genehmigen?", "title": "Warnung!" }, "approve-multiple-files-without-analysis": { @@ -596,6 +611,10 @@ "question": "Analysis required to detect new components for at least one file.", "title": "Warning" }, + "approve-multiple-files": { + "question": "Mindestens eine der ausgewählten Dateien enthält ungesehene Änderungen. Möchten Sie sie trotzdem genehmigen?", + "title": "Warnung!" + }, "assign-file-to-me": { "question": { "multiple": "Dieses Dokument wird gerade von einer anderen Person geprüft. Möchten Sie Reviewer werden und sich selbst dem Dokument zuweisen?", @@ -959,13 +978,13 @@ "recent": "Neu ({hours} h)", "unassigned": "Niemandem zugewiesen" }, - "reanalyse": { - "action": "Datei analysieren" - }, "reanalyse-dossier": { "error": "Die Dateien konnten nicht für eine Reanalyse eingeplant werden. Bitte versuchen Sie es erneut.", "success": "Dateien für Reanalyse vorgesehen." }, + "reanalyse": { + "action": "Datei analysieren" + }, "start-auto-analysis": "Enable auto-analysis", "stop-auto-analysis": "Stop auto-analysis", "table-col-names": { @@ -1035,14 +1054,6 @@ "total-documents": "Anzahl der Dokumente", "total-people": "{count} {count, plural, one{user} other {users}}" }, - "dossier-templates": { - "label": "Dossier-Vorlagen", - "status": { - "active": "Active", - "inactive": "Inactive", - "incomplete": "Incomplete" - } - }, "dossier-templates-listing": { "action": { "clone": "Clone template", @@ -1078,6 +1089,14 @@ "title": "{length} dossier {length, plural, one{template} other{templates}}" } }, + "dossier-templates": { + "label": "Dossier-Vorlagen", + "status": { + "active": "Active", + "inactive": "Inactive", + "incomplete": "Incomplete" + } + }, "dossier-watermark-selector": { "heading": "Watermarks on documents", "no-watermark": "There is no watermark defined for the dossier template.
Contact your app admin to define one.", @@ -1273,15 +1292,6 @@ "title": "{length} {length, plural, one{entity} other{entities}}" } }, - "entity": { - "info": { - "actions": { - "revert": "Revert", - "save": "Save changes" - }, - "heading": "Edit entity" - } - }, "entity-rules-screen": { "error": { "generic": "Something went wrong... Entity rules update failed!" @@ -1296,19 +1306,28 @@ "warning-text": "Warning: experimental feature!", "warnings-found": "{warnings, plural, one{A warning} other{{warnings} warnings}} found in rules" }, + "entity": { + "info": { + "actions": { + "revert": "Revert", + "save": "Save changes" + }, + "heading": "Edit entity" + } + }, "error": { "deleted-entity": { "dossier": { "action": "Zurück zur Übersicht", "label": "Dieses Dossier wurde gelöscht!" }, - "file": { - "action": "Zurück zum Dossier", - "label": "Diese Datei wurde gelöscht!" - }, "file-dossier": { "action": "Zurück zur Übersicht", "label": "Das Dossier dieser Datei wurde gelöscht!" + }, + "file": { + "action": "Zurück zum Dossier", + "label": "Diese Datei wurde gelöscht!" } }, "file-preview": { @@ -1326,12 +1345,6 @@ }, "exact-date": "{day} {month} {year} um {hour}:{minute} Uhr", "file": "Datei", - "file-attribute": { - "update": { - "error": "Failed to update file attribute value!", - "success": "File attribute value has been updated successfully!" - } - }, "file-attribute-encoding-types": { "ascii": "ASCII", "iso": "ISO-8859-1", @@ -1342,6 +1355,12 @@ "number": "Nummer", "text": "Freier Text" }, + "file-attribute": { + "update": { + "error": "Failed to update file attribute value!", + "success": "File attribute value has been updated successfully!" + } + }, "file-attributes-configurations": { "cancel": "Cancel", "form": { @@ -1560,15 +1579,6 @@ "csv": "File attributes were imported successfully from uploaded CSV file." } }, - "filter": { - "analysis": "Analyse erforderlich", - "comment": "Kommentare", - "hint": "Nut Hinweise", - "image": "Bilder", - "none": "Keine Anmerkungen", - "redaction": "Geschwärzt", - "updated": "Aktualisiert" - }, "filter-menu": { "filter-options": "Filteroptionen", "filter-types": "Filter", @@ -1578,6 +1588,15 @@ "unseen-pages": "Nur Anmerkungen auf unsichtbaren Seiten", "with-comments": "Nur Anmerkungen mit Kommentaren" }, + "filter": { + "analysis": "Analyse erforderlich", + "comment": "Kommentare", + "hint": "Nut Hinweise", + "image": "Bilder", + "none": "Keine Anmerkungen", + "redaction": "Geschwärzt", + "updated": "Aktualisiert" + }, "filters": { "assigned-people": "Beauftragt", "documents-status": "Documents state", @@ -1850,13 +1869,6 @@ "user-promoted-to-approver": "{user} wurde im Dossier {dossierHref, select, null{{dossierName}} other{{dossierName}}} zum Genehmiger ernannt!", "user-removed-as-dossier-member": "{user} wurde als Mitglied von: {dossierHref, select, null{{dossierName}} other{{dossierName}}} entfernt!" }, - "notifications": { - "button-text": "Notifications", - "deleted-dossier": "Deleted dossier", - "label": "Benachrichtigungen", - "mark-all-as-read": "Alle als gelesen markieren", - "mark-as": "Mark as {type, select, read{read} unread{unread} other{}}" - }, "notifications-screen": { "category": { "email-notifications": "E-Mail Benachrichtigungen", @@ -1870,6 +1882,7 @@ "dossier": "Dossierbezogene Benachrichtigungen", "other": "Andere Benachrichtigungen" }, + "options-title": "Wählen Sie aus, in welcher Kategorie Sie benachrichtigt werden möchten", "options": { "ASSIGN_APPROVER": "Wenn ich einem Dokument als Genehmiger zugewiesen bin", "ASSIGN_REVIEWER": "Wenn ich einem Dokument als Überprüfer zugewiesen bin", @@ -1887,7 +1900,6 @@ "USER_PROMOTED_TO_APPROVER": "Wenn ich Genehmiger in einem Dossier werde", "USER_REMOVED_AS_DOSSIER_MEMBER": "Wenn ich die Dossier-Mitgliedschaft verliere" }, - "options-title": "Wählen Sie aus, in welcher Kategorie Sie benachrichtigt werden möchten", "schedule": { "daily": "Tägliche Zusammenfassung", "instant": "Sofortig", @@ -1895,6 +1907,13 @@ }, "title": "Benachrichtigungseinstellungen" }, + "notifications": { + "button-text": "Notifications", + "deleted-dossier": "Deleted dossier", + "label": "Benachrichtigungen", + "mark-all-as-read": "Alle als gelesen markieren", + "mark-as": "Mark as {type, select, read{read} unread{unread} other{}}" + }, "ocr": { "confirmation-dialog": { "cancel": "Cancel", @@ -1986,16 +2005,16 @@ "warnings-label": "Prompts and dialogs", "warnings-subtitle": "Do not show again options" }, - "processing": { - "basic": "Processing", - "ocr": "OCR" - }, "processing-status": { "ocr": "OCR", "pending": "Pending", "processed": "Processed", "processing": "Processing" }, + "processing": { + "basic": "Processing", + "ocr": "OCR" + }, "readonly": "Lesemodus", "readonly-archived": "Read only (archived)", "redact-text": { @@ -2219,12 +2238,6 @@ "red-user-admin": "Benutzer-Admin", "regular": "Regulär" }, - "search": { - "active-dossiers": "ganze Plattform", - "all-dossiers": "all documents", - "placeholder": "Nach Dokumenten oder Dokumenteninhalt suchen", - "this-dossier": "in diesem Dossier" - }, "search-screen": { "cols": { "assignee": "Bevollmächtigter", @@ -2248,6 +2261,12 @@ "no-match": "Keine Dokumente entsprechen Ihren aktuellen Filtern.", "table-header": "{length} search {length, plural, one{result} other{results}}" }, + "search": { + "active-dossiers": "ganze Plattform", + "all-dossiers": "all documents", + "placeholder": "Nach Dokumenten oder Dokumenteninhalt suchen", + "this-dossier": "in diesem Dossier" + }, "seconds": "seconds", "size": "Size", "smtp-auth-config": { @@ -2502,4 +2521,4 @@ } }, "yesterday": "Gestern" -} \ No newline at end of file +} diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index b005ac747..2b40f93a9 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -231,6 +231,7 @@ }, "admin-side-nav": { "audit": "Audit", + "component-mappings": "Component mappings", "component-rule-editor": "Component rule editor", "configurations": "Configurations", "default-colors": "Default colors", @@ -516,6 +517,24 @@ }, "title": "Component view" }, + "component-mappings-screen": { + "action": { + "delete": "Delete mapping", + "edit": "Edit mapping" + }, + "add-new": "New Mapping", + "bulk-actions": { + "delete": "Delete selected mappings" + }, + "search": "Search by name...", + "table-col-names": { + "name": "name", + "version": "version" + }, + "table-header": { + "title": "Component mapping" + } + }, "component-rules-screen": { "error": { "generic": "Something went wrong... Component rules update failed!" @@ -2502,4 +2521,4 @@ } }, "yesterday": "Yesterday" -} \ No newline at end of file +} diff --git a/libs/red-domain/src/index.ts b/libs/red-domain/src/index.ts index e324c83a5..4d040b981 100644 --- a/libs/red-domain/src/index.ts +++ b/libs/red-domain/src/index.ts @@ -29,3 +29,4 @@ export * from './lib/digital-signature'; export * from './lib/watermarks'; export * from './lib/colors'; export * from './lib/component-log'; +export * from './lib/component-mappings'; diff --git a/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts b/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts new file mode 100644 index 000000000..e0efbcccb --- /dev/null +++ b/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts @@ -0,0 +1,18 @@ +import { IComponentMapping } from './component-mapping'; +import { IListable } from '@iqser/common-ui'; + +export class ComponentMapping implements IComponentMapping, IListable { + readonly id: string; + readonly name: string; + readonly version: number; + + constructor(componentMapping: IComponentMapping) { + this.id = componentMapping.id; + this.name = componentMapping.name; + this.version = componentMapping.version; + } + + get searchKey(): string { + return this.name; + } +} diff --git a/libs/red-domain/src/lib/component-mappings/component-mapping.ts b/libs/red-domain/src/lib/component-mappings/component-mapping.ts new file mode 100644 index 000000000..eb1465f42 --- /dev/null +++ b/libs/red-domain/src/lib/component-mappings/component-mapping.ts @@ -0,0 +1,5 @@ +export interface IComponentMapping { + id: string; + name: string; + version: number; +} diff --git a/libs/red-domain/src/lib/component-mappings/index.ts b/libs/red-domain/src/lib/component-mappings/index.ts new file mode 100644 index 000000000..c0a040a07 --- /dev/null +++ b/libs/red-domain/src/lib/component-mappings/index.ts @@ -0,0 +1,2 @@ +export * from './component-mapping.model'; +export * from './component-mapping'; From a960b837df159c21f6a5ba39b9b1aeb096020cff Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Wed, 5 Jun 2024 00:09:32 +0300 Subject: [PATCH 2/4] RED-9201 - UI for Component Mapping Tables --- ...it-component-mapping-dialog.component.html | 66 +++++++++++++++ ...it-component-mapping-dialog.component.scss | 21 +++++ ...edit-component-mapping-dialog.component.ts | 81 +++++++++++++++++++ .../component-mappings-screen.component.ts | 34 +++++++- .../component-mappings.service.ts | 28 +++++++ apps/red-ui/src/assets/config/config.json | 10 +-- apps/red-ui/src/assets/i18n/redact/de.json | 17 ++++ apps/red-ui/src/assets/i18n/redact/en.json | 17 ++++ apps/red-ui/src/assets/i18n/scm/de.json | 17 ++++ apps/red-ui/src/assets/i18n/scm/en.json | 17 ++++ .../component-mapping.model.ts | 6 ++ .../component-mappings/component-mapping.ts | 3 + 12 files changed, 308 insertions(+), 9 deletions(-) create mode 100644 apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.html create mode 100644 apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.scss create mode 100644 apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.ts diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.html b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.html new file mode 100644 index 000000000..bb918f40a --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.html @@ -0,0 +1,66 @@ +
+
+
+
+
+
+ + +
+ +
+ + {{ data.mapping.version }} + +
+
+ +
+ + +
+ +
+
+ + +
+ +
+ + + + + {{ translations[type] | translate }} + + + +
+
+
+ +
+ +
+
+ + +
diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.scss b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.scss new file mode 100644 index 000000000..56ceac53a --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.scss @@ -0,0 +1,21 @@ +.row { + display: flex; + margin-top: 14px; + + > *:not(:last-child) { + margin-right: 16px; + } + + .iqser-input-group { + margin-top: 0; + } + + .version { + margin-left: 50px; + + span { + margin-top: 10px; + font-size: 15px; + } + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.ts b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.ts new file mode 100644 index 000000000..2a9a4a488 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.ts @@ -0,0 +1,81 @@ +import { Component } from '@angular/core'; +import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, IqserDialogComponent, IqserUploadFileModule } from '@iqser/common-ui'; +import { FileAttributeEncodingTypes, IComponentMapping } from '@red/domain'; +import { FormBuilder, ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { NgForOf, NgIf } from '@angular/common'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatDialogClose } from '@angular/material/dialog'; +import { MatOption } from '@angular/material/autocomplete'; +import { MatSelect } from '@angular/material/select'; +import { fileAttributeEncodingTypesTranslations } from '@translations/file-attribute-encoding-types-translations'; + +interface DialogData { + mapping: IComponentMapping; +} +interface DialogResult {} + +@Component({ + templateUrl: './add-edit-component-mapping-dialog.component.html', + styleUrls: ['./add-edit-component-mapping-dialog.component.scss'], + standalone: true, + imports: [ + TranslateModule, + ReactiveFormsModule, + NgIf, + MatFormFieldModule, + NgForOf, + CircleButtonComponent, + MatDialogClose, + IqserUploadFileModule, + MatOption, + MatSelect, + IconButtonComponent, + ], +}) +export class AddEditComponentMappingDialogComponent extends IqserDialogComponent< + AddEditComponentMappingDialogComponent, + DialogData, + DialogResult +> { + protected readonly encodingTypeOptions = Object.keys(FileAttributeEncodingTypes); + protected readonly translations = fileAttributeEncodingTypesTranslations; + protected readonly iconButtonTypes = IconButtonTypes; + form!: UntypedFormGroup; + + constructor(private readonly _formBuilder: FormBuilder) { + super(); + + this.form = this.#getForm(); + } + + get file() { + return this.form.get('file').value; + } + + fileChanged(file: Blob | null) { + if (file) { + const fileReader = new FileReader(); + fileReader.onload = () => { + const binaryContent = fileReader.result; + this.form.get('file').setValue(binaryContent); + }; + fileReader.readAsBinaryString(file as Blob); + } else { + this.form.controls['file'].setValue(''); + } + } + + save() { + this.dialogRef.close({ ...this.data.mapping, ...this.form.getRawValue() }); + } + + #getForm(): UntypedFormGroup { + return this._formBuilder.group({ + name: [this.data?.mapping?.name, Validators.required], + file: [this.data?.mapping?.file, Validators.required], + delimiter: [this.data?.mapping?.delimiter ?? ',', Validators.required], + encodingType: this.encodingTypeOptions.find(e => e === this.data?.mapping?.encodingType) ?? this.encodingTypeOptions[0], + }); + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts index 510462288..085dcefd9 100644 --- a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts @@ -1,14 +1,23 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { ListingComponent, TableColumnConfig, listingProvidersFactory, LoadingService, IconButtonTypes } from '@iqser/common-ui'; +import { + ListingComponent, + TableColumnConfig, + listingProvidersFactory, + LoadingService, + IconButtonTypes, + IqserDialog, +} from '@iqser/common-ui'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { defaultColorsTranslations } from '@translations/default-colors-translations'; import { Roles } from '@users/roles'; import { getCurrentUser } from '@common-ui/users'; import { User } from '@red/domain'; import { ComponentMapping } from '@red/domain'; -import { combineLatest } from 'rxjs'; +import { combineLatest, firstValueFrom } from 'rxjs'; import { ComponentMappingsService } from '@services/entity-services/component-mappings.service'; import { tap } from 'rxjs/operators'; +import { AddEditComponentMappingDialogComponent } from './add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component'; +import { AdminDialogService } from '../../services/admin-dialog.service'; @Component({ templateUrl: './component-mappings-screen.component.html', @@ -30,6 +39,8 @@ export class ComponentMappingsScreenComponent extends ListingComponent { + // this._loadingService.start(); + // this._loadingService.stop(); + }); + } } diff --git a/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts b/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts index 384af91bc..e3c9ac956 100644 --- a/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts +++ b/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts @@ -7,36 +7,54 @@ const DATA = [ { id: '1', name: 'OECD Number', + file: 'some file name', + delimiter: ',', + encodingType: 'UTF-8', version: 2, searchKey: 'OECD Number', }, { id: '2', name: 'Study Title', + file: 'some file name', + delimiter: ';', + encodingType: 'ASCII', version: 1, searchKey: 'Study Title', }, { id: '3', name: 'Report Number', + file: 'some file name', + delimiter: '.', + encodingType: 'ISO', version: 2, searchKey: 'Report Number', }, { id: '4', name: 'GLP Study', + file: 'some file name', + delimiter: '"', + encodingType: 'UTF-8', version: 5, searchKey: 'GLP Study', }, { id: '5', name: 'Performing Laboratory', + file: 'some file name', + delimiter: ']', + encodingType: 'UTF-8', version: 2, searchKey: 'Performing Laboratory', }, { id: '6', name: 'Test', + file: 'some file name', + delimiter: ';', + encodingType: 'UTF-8', version: 3, searchKey: 'Test', }, @@ -49,4 +67,14 @@ export class ComponentMappingsService extends EntitiesService { return of(DATA); } + + saveData(mapping: any): Observable { + if (!mapping.id) { + mapping.id = (Number(DATA[DATA.length - 1].id) + 1).toString(); + mapping.version = 1; + mapping.searchKey = mapping.name; + } + DATA.push(mapping); + return of(mapping); + } } diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 42bf5941f..2ccc6f6d1 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -1,9 +1,9 @@ { "ADMIN_CONTACT_NAME": null, "ADMIN_CONTACT_URL": null, - "API_URL": "https://dan.iqser.cloud", + "API_URL": "https://frontend2.iqser.cloud", "APP_NAME": "RedactManager", - "IS_DOCUMINE": false, + "IS_DOCUMINE": true, "RULE_EDITOR_DEV_ONLY": false, "AUTO_READ_TIME": 3, "BACKEND_APP_VERSION": "4.4.40", @@ -13,13 +13,13 @@ "MAX_RETRIES_ON_SERVER_ERROR": 3, "OAUTH_CLIENT_ID": "redaction", "OAUTH_IDP_HINT": null, - "OAUTH_URL": "https://dan.iqser.cloud/auth", + "OAUTH_URL": "https://frontend2.iqser.cloud/auth", "RECENT_PERIOD_IN_HOURS": 24, "SELECTION_MODE": "structural", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview", "ANNOTATIONS_THRESHOLD": 1000, - "THEME": "redact", - "BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/redact/", + "THEME": "scm", + "BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/scm/", "AVAILABLE_NOTIFICATIONS_DAYS": 30, "AVAILABLE_OLD_NOTIFICATIONS_MINUTES": 60, "NOTIFICATIONS_THRESHOLD": 1000, diff --git a/apps/red-ui/src/assets/i18n/redact/de.json b/apps/red-ui/src/assets/i18n/redact/de.json index f0e9c8432..519e9fcd7 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -94,6 +94,23 @@ }, "save": "Dossier-Vorlage speichern" }, + "add-edit-component-mapping": { + "actions": { + "save": "" + }, + "dialog": { + "title": "" + }, + "form": { + "delimiter": "", + "delimiter-placeholder": "", + "encoding-type": "", + "file": "", + "name": "", + "name-placeholder": "", + "version": "" + } + }, "add-edit-dossier-attribute": { "error": { "generic": "Attribut konnte nicht gespeichert werden!" diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 992f36c85..dcd8dafe6 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -94,6 +94,23 @@ }, "save": "Save dossier template" }, + "add-edit-component-mapping": { + "actions": { + "save": "Save mapping" + }, + "dialog": { + "title": "{type, select, add{Add New} edit{Edit} other{}} Component Mapping" + }, + "form": { + "delimiter": "", + "delimiter-placeholder": "", + "encoding-type": "", + "file": "Mapping file", + "name": "Mapping name", + "name-placeholder": "Mapping name", + "version": "Version" + } + }, "add-edit-dossier-attribute": { "error": { "generic": "Failed to save attribute" diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index 99d638f9c..527e888e4 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -94,6 +94,23 @@ }, "save": "Dossier-Vorlage speichern" }, + "add-edit-component-mapping": { + "actions": { + "save": "" + }, + "dialog": { + "title": "" + }, + "form": { + "delimiter": "", + "delimiter-placeholder": "", + "encoding-type": "", + "file": "", + "name": "", + "name-placeholder": "", + "version": "" + } + }, "add-edit-dossier-attribute": { "error": { "generic": "Attribut konnte nicht gespeichert werden!" diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index 2b40f93a9..13e936c33 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -94,6 +94,23 @@ }, "save": "Save dossier template" }, + "add-edit-component-mapping": { + "actions": { + "save": "Save mapping" + }, + "dialog": { + "title": "{type, select, add{Add New} edit{Edit} other{}} Component Mapping" + }, + "form": { + "delimiter": "Delimiter", + "delimiter-placeholder": "Delimiter", + "encoding-type": "Encoding type", + "file": "Mapping file", + "name": "Mapping name", + "name-placeholder": "Mapping name", + "version": "Version" + } + }, "add-edit-dossier-attribute": { "error": { "generic": "Failed to save attribute!" diff --git a/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts b/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts index e0efbcccb..35fa1ef58 100644 --- a/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts +++ b/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts @@ -4,11 +4,17 @@ import { IListable } from '@iqser/common-ui'; export class ComponentMapping implements IComponentMapping, IListable { readonly id: string; readonly name: string; + readonly file: string; + readonly delimiter: string; + readonly encodingType: string; readonly version: number; constructor(componentMapping: IComponentMapping) { this.id = componentMapping.id; this.name = componentMapping.name; + this.file = componentMapping.file; + this.delimiter = componentMapping.delimiter; + this.encodingType = componentMapping.encodingType; this.version = componentMapping.version; } diff --git a/libs/red-domain/src/lib/component-mappings/component-mapping.ts b/libs/red-domain/src/lib/component-mappings/component-mapping.ts index eb1465f42..501c4c3d1 100644 --- a/libs/red-domain/src/lib/component-mappings/component-mapping.ts +++ b/libs/red-domain/src/lib/component-mappings/component-mapping.ts @@ -1,5 +1,8 @@ export interface IComponentMapping { id: string; name: string; + file: string; version: number; + delimiter: string; + encodingType: string; } From 13c1a2a0b0e0f71d8a9577813a14be1dc5ec7371 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 6 Jun 2024 18:33:18 +0300 Subject: [PATCH 3/4] RED-9201 - added all mapping actions --- ...it-component-mapping-dialog.component.html | 7 +- ...edit-component-mapping-dialog.component.ts | 58 +++++---- .../component-mappings-screen.component.html | 22 +--- .../component-mappings-screen.component.ts | 39 ++++-- .../component-mappings.service.ts | 116 ++++++++---------- libs/common-ui | 2 +- .../component-mapping-list.model.ts | 16 +++ .../component-mapping.model.ts | 24 ---- .../component-mappings/component-mapping.ts | 36 +++++- .../src/lib/component-mappings/index.ts | 2 +- 10 files changed, 166 insertions(+), 156 deletions(-) create mode 100644 libs/red-domain/src/lib/component-mappings/component-mapping-list.model.ts delete mode 100644 libs/red-domain/src/lib/component-mappings/component-mapping.model.ts diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.html b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.html index bb918f40a..a43c42fcf 100644 --- a/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.html @@ -16,16 +16,15 @@ /> -
+
{{ data.mapping.version }} -
- +
@@ -42,7 +41,7 @@
- + {{ translations[type] | translate }} diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.ts b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.ts index 2a9a4a488..680565a72 100644 --- a/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { CircleButtonComponent, IconButtonComponent, IconButtonTypes, IqserDialogComponent, IqserUploadFileModule } from '@iqser/common-ui'; import { FileAttributeEncodingTypes, IComponentMapping } from '@red/domain'; import { FormBuilder, ReactiveFormsModule, UntypedFormGroup, Validators } from '@angular/forms'; @@ -9,11 +9,20 @@ import { MatDialogClose } from '@angular/material/dialog'; import { MatOption } from '@angular/material/autocomplete'; import { MatSelect } from '@angular/material/select'; import { fileAttributeEncodingTypesTranslations } from '@translations/file-attribute-encoding-types-translations'; +import { firstValueFrom } from 'rxjs'; +import { ComponentMappingsService } from '@services/entity-services/component-mappings.service'; interface DialogData { + dossierTemplateId: string; mapping: IComponentMapping; } -interface DialogResult {} +interface DialogResult { + id: string; + name: string; + file: Blob; + encoding: string; + delimiter: string; +} @Component({ templateUrl: './add-edit-component-mapping-dialog.component.html', @@ -33,37 +42,38 @@ interface DialogResult {} IconButtonComponent, ], }) -export class AddEditComponentMappingDialogComponent extends IqserDialogComponent< - AddEditComponentMappingDialogComponent, - DialogData, - DialogResult -> { +export class AddEditComponentMappingDialogComponent + extends IqserDialogComponent + implements OnInit +{ protected readonly encodingTypeOptions = Object.keys(FileAttributeEncodingTypes); protected readonly translations = fileAttributeEncodingTypesTranslations; protected readonly iconButtonTypes = IconButtonTypes; + activeFile: File; form!: UntypedFormGroup; - constructor(private readonly _formBuilder: FormBuilder) { + constructor( + private readonly _formBuilder: FormBuilder, + private readonly _componentMappingService: ComponentMappingsService, + ) { super(); - this.form = this.#getForm(); } - get file() { - return this.form.get('file').value; + async ngOnInit() { + if (this.data.mapping?.fileName) { + this.activeFile = { name: this.data.mapping.fileName } as File; + const fileContent = await firstValueFrom( + this._componentMappingService.getComponentMappingFile(this.data.dossierTemplateId, this.data.mapping.id), + ); + const file = new Blob([fileContent], { type: 'text/csv' }); + this.form.get('file').setValue(file); + this.initialFormValue = this.form.getRawValue(); + } } - fileChanged(file: Blob | null) { - if (file) { - const fileReader = new FileReader(); - fileReader.onload = () => { - const binaryContent = fileReader.result; - this.form.get('file').setValue(binaryContent); - }; - fileReader.readAsBinaryString(file as Blob); - } else { - this.form.controls['file'].setValue(''); - } + fileChanged(file: Blob) { + this.form.get('file').setValue(file); } save() { @@ -73,9 +83,9 @@ export class AddEditComponentMappingDialogComponent extends IqserDialogComponent #getForm(): UntypedFormGroup { return this._formBuilder.group({ name: [this.data?.mapping?.name, Validators.required], - file: [this.data?.mapping?.file, Validators.required], + file: [null, Validators.required], + encoding: this.encodingTypeOptions.find(e => e === this.data?.mapping?.encoding) ?? this.encodingTypeOptions[0], delimiter: [this.data?.mapping?.delimiter ?? ',', Validators.required], - encodingType: this.encodingTypeOptions.find(e => e === this.data?.mapping?.encodingType) ?? this.encodingTypeOptions[0], }); } } diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.html index 863ea0597..ad4765486 100644 --- a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.html +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.html @@ -20,14 +20,7 @@
- +
@@ -51,17 +44,6 @@
- - - - - -
@@ -69,7 +51,7 @@
- {{ entity.value }} + {{ entity.version }}
diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts index 085dcefd9..3044059f0 100644 --- a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts @@ -11,13 +11,14 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { defaultColorsTranslations } from '@translations/default-colors-translations'; import { Roles } from '@users/roles'; import { getCurrentUser } from '@common-ui/users'; -import { User } from '@red/domain'; +import { DOSSIER_TEMPLATE_ID, User } from '@red/domain'; import { ComponentMapping } from '@red/domain'; import { combineLatest, firstValueFrom } from 'rxjs'; import { ComponentMappingsService } from '@services/entity-services/component-mappings.service'; -import { tap } from 'rxjs/operators'; +import { map, tap } from 'rxjs/operators'; import { AddEditComponentMappingDialogComponent } from './add-edit-component-mapping-dialog/add-edit-component-mapping-dialog.component'; import { AdminDialogService } from '../../services/admin-dialog.service'; +import { getParam } from '@common-ui/utils'; @Component({ templateUrl: './component-mappings-screen.component.html', @@ -27,14 +28,15 @@ import { AdminDialogService } from '../../services/admin-dialog.service'; export class ComponentMappingsScreenComponent extends ListingComponent implements OnInit { tableColumnConfigs: readonly TableColumnConfig[] = [ { label: _('component-mappings-screen.table-col-names.name'), sortByKey: 'searchKey' }, - { label: _('component-mappings-screen.table-col-names.version'), width: '2fr' }, + { label: _('component-mappings-screen.table-col-names.version') }, ]; readonly tableHeaderLabel = _('component-mappings-screen.table-header.title'); - readonly context$; + protected readonly context$; protected readonly currentUser = getCurrentUser(); protected readonly translations = defaultColorsTranslations; protected readonly roles = Roles; protected readonly iconButtonTypes = IconButtonTypes; + readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); constructor( private readonly _loadingService: LoadingService, @@ -43,8 +45,13 @@ export class ComponentMappingsScreenComponent extends ListingComponent this.entitiesService.setEntities(mappings)), + this.context$ = this.loadData$(); + } + + loadData$() { + return combineLatest([this._componentMappingService.getComponentMappings(this.#dossierTemplateId)]).pipe( + map(([mappingList]) => mappingList.componentMappingList), + tap(mappings => this.entitiesService.setEntities(mappings)), ); } @@ -55,19 +62,29 @@ export class ComponentMappingsScreenComponent extends ListingComponent { - // this._loadingService.start(); - // this._loadingService.stop(); + openDeleteComponentMappingDialog(entity: ComponentMapping) { + this._dialogService.openDialog('confirm', null, async confirmation => { + if (confirmation) { + this._loadingService.start(); + await firstValueFrom(this._componentMappingService.deleteComponentMapping(this.#dossierTemplateId, entity.id)); + await firstValueFrom(this.loadData$()); + this._loadingService.stop(); + } }); } } diff --git a/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts b/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts index e3c9ac956..86d8c0d21 100644 --- a/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts +++ b/apps/red-ui/src/app/services/entity-services/component-mappings.service.ts @@ -1,80 +1,60 @@ import { Injectable } from '@angular/core'; -import { EntitiesService } from '@iqser/common-ui'; -import { ComponentMapping, IComponentMapping } from '@red/domain'; -import { Observable, of } from 'rxjs'; +import { EntitiesService, QueryParam } from '@iqser/common-ui'; +import { ComponentMapping, IComponentMapping, IComponentMappingList } from '@red/domain'; +import { Observable } from 'rxjs'; +import { HeadersConfiguration, List } from '@common-ui/utils'; -const DATA = [ - { - id: '1', - name: 'OECD Number', - file: 'some file name', - delimiter: ',', - encodingType: 'UTF-8', - version: 2, - searchKey: 'OECD Number', - }, - { - id: '2', - name: 'Study Title', - file: 'some file name', - delimiter: ';', - encodingType: 'ASCII', - version: 1, - searchKey: 'Study Title', - }, - { - id: '3', - name: 'Report Number', - file: 'some file name', - delimiter: '.', - encodingType: 'ISO', - version: 2, - searchKey: 'Report Number', - }, - { - id: '4', - name: 'GLP Study', - file: 'some file name', - delimiter: '"', - encodingType: 'UTF-8', - version: 5, - searchKey: 'GLP Study', - }, - { - id: '5', - name: 'Performing Laboratory', - file: 'some file name', - delimiter: ']', - encodingType: 'UTF-8', - version: 2, - searchKey: 'Performing Laboratory', - }, - { - id: '6', - name: 'Test', - file: 'some file name', - delimiter: ';', - encodingType: 'UTF-8', - version: 3, - searchKey: 'Test', - }, -]; +interface CreateMappingParams { + dossierTemplateId: string; + name: string; + encoding: string; + delimiter: string; +} @Injectable({ providedIn: 'root', }) export class ComponentMappingsService extends EntitiesService { - loadData(): Observable { - return of(DATA); + getComponentMappings(dossierTemplateId: string): Observable { + return this._http.get(`/api/dossier-templates/${dossierTemplateId}/component-mappings`); } - saveData(mapping: any): Observable { - if (!mapping.id) { - mapping.id = (Number(DATA[DATA.length - 1].id) + 1).toString(); - mapping.version = 1; - mapping.searchKey = mapping.name; + createComponentMapping( + dossierTemplateId: string, + componentMapping: Partial, + file: Blob, + ): Observable { + const formParams = new FormData(); + formParams.append('file', file); + + const queryParams: List = [ + { key: 'name', value: componentMapping.name }, + { key: 'encoding', value: componentMapping.encoding }, + { key: 'delimiter', value: componentMapping.delimiter }, + ]; + + if (componentMapping.id) { + return this._http.put( + `/api/dossier-templates/${dossierTemplateId}/component-mappings/${componentMapping.id}`, + formParams, + { + params: this._queryParams(queryParams), + }, + ); } - DATA.push(mapping); - return of(mapping); + + return this._http.post(`/api/dossier-templates/${dossierTemplateId}/component-mappings`, formParams, { + params: this._queryParams(queryParams), + }); + } + + deleteComponentMapping(dossierTemplateId: string, componentMappingId: string) { + return this._http.delete(`/api/dossier-templates/${dossierTemplateId}/component-mappings/${componentMappingId}`); + } + + getComponentMappingFile(dossierTemplateId: string, componentMappingId: string) { + return this._http.get(`/api/dossier-templates/${dossierTemplateId}/component-mappings/${componentMappingId}`, { + responseType: 'text', + }); } } diff --git a/libs/common-ui b/libs/common-ui index 3a9f36e71..5eef6d403 160000 --- a/libs/common-ui +++ b/libs/common-ui @@ -1 +1 @@ -Subproject commit 3a9f36e71b184e66eb791af19deeb258f8ec333d +Subproject commit 5eef6d403a57c224eec836d9d98787c6a128c50a diff --git a/libs/red-domain/src/lib/component-mappings/component-mapping-list.model.ts b/libs/red-domain/src/lib/component-mappings/component-mapping-list.model.ts new file mode 100644 index 000000000..24425c4a7 --- /dev/null +++ b/libs/red-domain/src/lib/component-mappings/component-mapping-list.model.ts @@ -0,0 +1,16 @@ +import { ComponentMapping, IComponentMapping } from './component-mapping'; + +export interface IComponentMappingList { + dossierTemplateId: string; + componentMappingList: IComponentMapping[]; +} + +export class ComponentMappingList implements IComponentMappingList { + readonly dossierTemplateId: string; + readonly componentMappingList: ComponentMapping[]; + + constructor(componentMappingList: IComponentMappingList) { + this.dossierTemplateId = componentMappingList.dossierTemplateId; + this.componentMappingList = componentMappingList.componentMappingList; + } +} diff --git a/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts b/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts deleted file mode 100644 index 35fa1ef58..000000000 --- a/libs/red-domain/src/lib/component-mappings/component-mapping.model.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IComponentMapping } from './component-mapping'; -import { IListable } from '@iqser/common-ui'; - -export class ComponentMapping implements IComponentMapping, IListable { - readonly id: string; - readonly name: string; - readonly file: string; - readonly delimiter: string; - readonly encodingType: string; - readonly version: number; - - constructor(componentMapping: IComponentMapping) { - this.id = componentMapping.id; - this.name = componentMapping.name; - this.file = componentMapping.file; - this.delimiter = componentMapping.delimiter; - this.encodingType = componentMapping.encodingType; - this.version = componentMapping.version; - } - - get searchKey(): string { - return this.name; - } -} diff --git a/libs/red-domain/src/lib/component-mappings/component-mapping.ts b/libs/red-domain/src/lib/component-mappings/component-mapping.ts index 501c4c3d1..b8e1e7745 100644 --- a/libs/red-domain/src/lib/component-mappings/component-mapping.ts +++ b/libs/red-domain/src/lib/component-mappings/component-mapping.ts @@ -1,8 +1,38 @@ -export interface IComponentMapping { +import { IListable } from '@iqser/common-ui'; + +export interface IComponentMapping extends IListable { id: string; name: string; - file: string; + fileName: string; version: number; + columnLabels: string[]; + numberOfLines: number; + encoding: string; delimiter: string; - encodingType: string; +} + +export class ComponentMapping implements IComponentMapping, IListable { + readonly id: string; + readonly name: string; + readonly fileName: string; + readonly version: number; + readonly columnLabels: string[]; + readonly numberOfLines: number; + readonly encoding: string; + readonly delimiter: string; + + constructor(componentMapping: IComponentMapping) { + this.id = componentMapping.id; + this.name = componentMapping.name; + this.fileName = componentMapping.fileName; + this.version = componentMapping.version; + this.columnLabels = componentMapping.columnLabels; + this.numberOfLines = componentMapping.numberOfLines; + this.encoding = componentMapping.encoding; + this.delimiter = componentMapping.delimiter; + } + + get searchKey(): string { + return this.name; + } } diff --git a/libs/red-domain/src/lib/component-mappings/index.ts b/libs/red-domain/src/lib/component-mappings/index.ts index c0a040a07..88026faa7 100644 --- a/libs/red-domain/src/lib/component-mappings/index.ts +++ b/libs/red-domain/src/lib/component-mappings/index.ts @@ -1,2 +1,2 @@ -export * from './component-mapping.model'; +export * from './component-mapping-list.model'; export * from './component-mapping'; From 3dfe705da11dee6eb3670143464c40c426953494 Mon Sep 17 00:00:00 2001 From: Valentin Mihai Date: Thu, 6 Jun 2024 18:39:31 +0300 Subject: [PATCH 4/4] RED-9201 - update config --- .../component-mappings-screen.component.ts | 4 +++- .../services/entity-services/component-mappings.service.ts | 2 +- apps/red-ui/src/assets/config/config.json | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts index 3044059f0..966ff36c7 100644 --- a/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/component-mappings/component-mappings-screen.component.ts @@ -71,7 +71,9 @@ export class ComponentMappingsScreenComponent extends ListingComponent(`/api/dossier-templates/${dossierTemplateId}/component-mappings`); } - createComponentMapping( + createUpdateComponentMapping( dossierTemplateId: string, componentMapping: Partial, file: Blob, diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 441ade243..deb14e6da 100644 --- a/apps/red-ui/src/assets/config/config.json +++ b/apps/red-ui/src/assets/config/config.json @@ -3,7 +3,7 @@ "ADMIN_CONTACT_URL": null, "API_URL": "https://dan1.iqser.cloud", "APP_NAME": "RedactManager", - "IS_DOCUMINE": true, + "IS_DOCUMINE": false, "RULE_EDITOR_DEV_ONLY": false, "AUTO_READ_TIME": 3, "BACKEND_APP_VERSION": "4.4.40", @@ -18,8 +18,8 @@ "SELECTION_MODE": "structural", "MANUAL_BASE_URL": "https://docs.redactmanager.com/preview", "ANNOTATIONS_THRESHOLD": 1000, - "THEME": "scm", - "BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/scm/", + "THEME": "redact", + "BASE_TRANSLATIONS_DIRECTORY": "/assets/i18n/redact/", "AVAILABLE_NOTIFICATIONS_DAYS": 30, "AVAILABLE_OLD_NOTIFICATIONS_MINUTES": 60, "NOTIFICATIONS_THRESHOLD": 1000,