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 0f8631355..a29289c29 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 @@ -61,6 +61,19 @@ const dossierTemplateIdRoutes: IqserRoutes = [ }, loadChildren: () => import('./screens/rules/rules.module').then(m => m.RulesModule), }, + { + path: 'component-rules', + component: BaseDossierTemplateScreenComponent, + canActivate: [CompositeRouteGuard, IqserPermissionsGuard], + data: { + routeGuards: [IqserAuthGuard, RedRoleGuard], + permissions: { + allow: [Roles.rules.read], + redirectTo: 'info', + }, + }, + loadChildren: () => import('./screens/component-rules/component-rules.module').then(m => m.ComponentRulesModule), + }, { 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 fc8df4e81..495528870 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 { DossierTemplateActionsComponent } from './shared/components/dossier-tem import { IqserUsersModule } from '@iqser/common-ui/lib/users'; import { TenantPipe } from '@iqser/common-ui/lib/tenants'; import { SelectComponent } from '@shared/components/select/select.component'; +import { ComponentRulesService } from './services/component-rules.service'; const dialogs = [ AddEditCloneDossierTemplateDialogComponent, @@ -97,7 +98,7 @@ const components = [ @NgModule({ declarations: [...components], - providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, SmtpConfigService], + providers: [AdminDialogService, AuditService, DigitalSignatureService, RulesService, ComponentRulesService, SmtpConfigService], imports: [ CommonModule, SharedModule, diff --git a/apps/red-ui/src/app/modules/admin/screens/component-rules/component-rules.module.ts b/apps/red-ui/src/app/modules/admin/screens/component-rules/component-rules.module.ts new file mode 100644 index 000000000..14c171ff2 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-rules/component-rules.module.ts @@ -0,0 +1,26 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { ComponentRulesScreenComponent } from './rules-screen/component-rules-screen.component'; +import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor'; +import { PendingChangesGuard } from '@guards/can-deactivate.guard'; +import { TranslateModule } from '@ngx-translate/core'; +import { IconButtonComponent } from '@iqser/common-ui'; +import { FormsModule } from '@angular/forms'; +import { MatIconModule } from '@angular/material/icon'; + +const routes = [{ path: '', component: ComponentRulesScreenComponent, canDeactivate: [PendingChangesGuard] }]; + +@NgModule({ + declarations: [ComponentRulesScreenComponent], + imports: [ + RouterModule.forChild(routes), + CommonModule, + MonacoEditorModule, + TranslateModule, + IconButtonComponent, + FormsModule, + MatIconModule, + ], +}) +export class ComponentRulesModule {} diff --git a/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.html b/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.html new file mode 100644 index 000000000..13c6432f7 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.html @@ -0,0 +1,26 @@ +
+
+
+
+ + + +
+
+ + + + + +
+
+ + +
+
+
diff --git a/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.scss new file mode 100644 index 000000000..0d44f017a --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.scss @@ -0,0 +1,67 @@ +:host { + flex-grow: 1; + overflow: hidden; + padding: 15px 0 0 15px; +} + +ngx-monaco-editor { + height: 100%; + width: 100%; +} + +.header-container { + display: flex; + align-items: center; + padding-bottom: 15px; + margin-left: 30px; + + .error { + color: red; + font-size: 8px; + font-weight: 500; + margin-left: 20px; + font-style: italic; + } +} + +.changes-box { + flex-direction: column; + justify-content: center; + align-items: start; + gap: 10px; + + > div { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; + } + + .errors { + cursor: pointer; + + .icon { + scale: 0.7; + } + + :first-child { + display: flex; + align-items: center; + gap: 0.5em; + font-weight: 500; + /* TODO: Update this to --iqser-error when added */ + color: #dd4d50; + } + + .face-up { + transform: rotate(90deg); + } + + margin-right: 0; + } + + .actions { + gap: 24px; + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.ts new file mode 100644 index 000000000..c17ee1beb --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/screens/component-rules/rules-screen/component-rules-screen.component.ts @@ -0,0 +1,216 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, ElementRef, OnInit, signal, ViewChild } from '@angular/core'; +import { PermissionsService } from '@services/permissions.service'; +import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; +import { saveAs } from 'file-saver'; +import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; +import { firstValueFrom } from 'rxjs'; +import { DOSSIER_TEMPLATE_ID } from '@red/domain'; +import { EditorThemeService } from '@services/editor-theme.service'; +import { ComponentCanDeactivate } from '@guards/can-deactivate.guard'; +import { Debounce, getParam } from '@iqser/common-ui/lib/utils'; +import { ComponentRulesService } from '../../../services/component-rules.service'; +import ICodeEditor = monaco.editor.ICodeEditor; +import IModelDeltaDecoration = monaco.editor.IModelDeltaDecoration; +import IStandaloneEditorConstructionOptions = monaco.editor.IStandaloneEditorConstructionOptions; + +interface SyntaxError { + line: number; + column: number; + message: string; +} + +@Component({ + templateUrl: './component-rules-screen.component.html', + styleUrls: ['./component-rules-screen.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComponentRulesScreenComponent implements OnInit, ComponentCanDeactivate { + readonly iconButtonTypes = IconButtonTypes; + readonly editorOptions: IStandaloneEditorConstructionOptions = { + theme: 'vs', + language: 'java', + automaticLayout: true, + readOnly: !this.permissionsService.canEditRules(), + glyphMargin: true, + }; + initialLines: string[] = []; + currentLines: string[] = []; + isLeaving = false; + @ViewChild('fileInput') + private _fileInput: ElementRef; + private _codeEditor: ICodeEditor; + private _decorations: string[] = []; + readonly #errorGlyphs = signal([]); + readonly numberOfErrors = computed(() => this.#errorGlyphs().length); + readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); + + constructor( + readonly permissionsService: PermissionsService, + private readonly _componentRulesService: ComponentRulesService, + private readonly _changeDetectorRef: ChangeDetectorRef, + private readonly _toaster: Toaster, + private readonly _loadingService: LoadingService, + private readonly _editorThemeService: EditorThemeService, + ) {} + + set isLeavingPage(isLeaving: boolean) { + this.isLeaving = isLeaving; + this._changeDetectorRef.detectChanges(); + } + + get changed(): boolean { + return this.currentLines.toString() !== this.initialLines.toString(); + } + + get codeEditorText() { + return this.currentLines.join('\n'); + } + + set codeEditorText($event: any) { + this.currentLines = $event.split('\n'); + this.codeEditorTextChanged(); + this._closeProblemsView(); + } + + async ngOnInit() { + await this._initialize(); + } + + onCodeEditorInit(editor: ICodeEditor) { + this._codeEditor = editor; + for (const theme of this._editorThemeService.themes) { + (window as any).monaco.editor.defineTheme(theme, this._editorThemeService.configurations[theme]); + } + (window as any).monaco.editor.setTheme(this._editorThemeService.getTheme(true)); + this._changeDetectorRef.detectChanges(); + } + + @Debounce() + codeEditorTextChanged() { + const newDecorations = this.currentLines.filter(entry => this._isNew(entry)).map(entry => this._makeDecorationFor(entry)); + this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations); + } + + goToErrors() { + this._codeEditor.trigger(null, 'editor.action.marker.next', null); + } + + async save(): Promise { + this._loadingService.start(); + this._removeErrorMarkers(); + await firstValueFrom( + this._componentRulesService.uploadRules({ + rules: this._codeEditor.getModel().getValue(), + dossierTemplateId: this.#dossierTemplateId, + }), + ).then( + async () => { + await this._initialize(); + this._toaster.success(_('component-rules-screen.success.generic')); + }, + error => { + const errors = error.error as SyntaxError[] | undefined; + this._drawErrorMarkers(errors); + this._loadingService.stop(); + this._toaster.error(_('component-rules-screen.error.generic')); + }, + ); + } + + revert(): void { + this.currentLines = this.initialLines; + this._decorations = this._codeEditor?.deltaDecorations(this._decorations, []) || []; + this._removeErrorMarkers(); + this._changeDetectorRef.detectChanges(); + this._loadingService.stop(); + } + + download(): void { + const content = this._codeEditor.getModel().getValue(); + const blob = new Blob([content], { + type: 'text/plain;charset=utf-8', + }); + saveAs(blob, 'rules.txt'); + } + + upload($event): void { + const file: File = $event.target.files[0]; + const fileReader = new FileReader(); + + if (file) { + fileReader.onload = () => { + this._codeEditor.getModel().setValue(fileReader.result as string); + this._fileInput.nativeElement.value = null; + }; + fileReader.readAsText(file); + } + } + + private _isNew(entry: string): boolean { + return this.initialLines.indexOf(entry) < 0 && entry?.trim().length > 0; + } + + private _makeDecorationFor(entry: string): IModelDeltaDecoration { + const line = this.currentLines.indexOf(entry) + 1; + + return { + range: new monaco.Range(line, 1, line, 1), + options: { isWholeLine: true, className: 'changed-row-marker' }, + } as IModelDeltaDecoration; + } + + private _drawErrorMarkers(errors: SyntaxError[] | undefined) { + const model = this._codeEditor?.getModel(); + if (!model || !errors?.length) { + return; + } + const markers = []; + const glyphs = []; + errors + .filter(e => e.line > 0) + .forEach(e => { + const endColumn = model.getLineLength(e.line) + 1; + markers.push({ + message: e.message, + severity: monaco.MarkerSeverity.Error, + startLineNumber: e.line, + startColumn: e.column, + endLineNumber: e.line, + endColumn, + }); + glyphs.push({ + range: new monaco.Range(e.line, e.column, e.line, endColumn), + options: { + glyphMarginClassName: 'error-glyph-margin', + }, + }); + }); + this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), glyphs)); + (window as any).monaco.editor.setModelMarkers(model, model.id, markers); + } + + private _closeProblemsView() { + this._codeEditor.trigger(null, 'closeMarkersNavigation', null); + } + + private _removeErrorMarkers() { + const model = this._codeEditor?.getModel(); + if (!model) { + return; + } + (window as any).monaco.editor.setModelMarkers(model, model.id, []); + this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), []) || []); + this._closeProblemsView(); + } + + private async _initialize() { + this._loadingService.start(); + await firstValueFrom(this._componentRulesService.download(this.#dossierTemplateId)).then( + rules => { + this.currentLines = this.initialLines = rules.rules.split('\n'); + this.revert(); + }, + () => this._loadingService.stop(), + ); + } +} diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.scss b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.scss index 978ec8419..0d44f017a 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.scss +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.scss @@ -51,7 +51,7 @@ ngx-monaco-editor { gap: 0.5em; font-weight: 500; /* TODO: Update this to --iqser-error when added */ - color: var(--iqser-red-1); + color: #dd4d50; } .face-up { diff --git a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.ts b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.ts index a38f1facb..2635ec949 100644 --- a/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.ts +++ b/apps/red-ui/src/app/modules/admin/screens/rules/rules-screen/rules-screen.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, signal, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, ElementRef, OnInit, signal, ViewChild } from '@angular/core'; import { PermissionsService } from '@services/permissions.service'; import { IconButtonTypes, LoadingService, Toaster } from '@iqser/common-ui'; import { saveAs } from 'file-saver'; @@ -25,7 +25,6 @@ interface SyntaxError { changeDetection: ChangeDetectionStrategy.OnPush, }) export class RulesScreenComponent implements OnInit, ComponentCanDeactivate { - readonly numberOfErrors = signal(0); readonly iconButtonTypes = IconButtonTypes; readonly editorOptions: IStandaloneEditorConstructionOptions = { theme: 'vs', @@ -41,7 +40,8 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate { private _fileInput: ElementRef; private _codeEditor: ICodeEditor; private _decorations: string[] = []; - private _errorGlyphs: string[] = []; + readonly #errorGlyphs = signal([]); + readonly numberOfErrors = computed(() => this.#errorGlyphs().length); readonly #dossierTemplateId = getParam(DOSSIER_TEMPLATE_ID); constructor( @@ -69,6 +69,7 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate { set codeEditorText($event: any) { this.currentLines = $event.split('\n'); this.codeEditorTextChanged(); + this._closeProblemsView(); } async ngOnInit() { @@ -87,17 +88,16 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate { @Debounce() codeEditorTextChanged() { const newDecorations = this.currentLines.filter(entry => this._isNew(entry)).map(entry => this._makeDecorationFor(entry)); - this._decorations = this._codeEditor.deltaDecorations(this._decorations, newDecorations); } goToErrors() { - this._codeEditor.trigger('keyboard', 'editor.action.marker.next', null); + this._codeEditor.trigger(null, 'editor.action.marker.next', null); } async save(): Promise { this._loadingService.start(); - this.numberOfErrors.set(0); + this._removeErrorMarkers(); await firstValueFrom( this._rulesService.uploadRules({ rules: this._codeEditor.getModel().getValue(), @@ -106,7 +106,6 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate { ).then( async () => { await this._initialize(); - this._removeErrorMarkers(); this._toaster.success(_('rules-screen.success.generic')); }, error => { @@ -186,19 +185,22 @@ export class RulesScreenComponent implements OnInit, ComponentCanDeactivate { }, }); }); - this.numberOfErrors.set(markers.length); - this._errorGlyphs = this._codeEditor.deltaDecorations(this._errorGlyphs, glyphs); + this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), glyphs)); (window as any).monaco.editor.setModelMarkers(model, model.id, markers); } + private _closeProblemsView() { + this._codeEditor.trigger(null, 'closeMarkersNavigation', null); + } + private _removeErrorMarkers() { const model = this._codeEditor?.getModel(); if (!model) { return; } (window as any).monaco.editor.setModelMarkers(model, model.id, []); - this._errorGlyphs = this._codeEditor?.deltaDecorations(this._errorGlyphs, []) || []; - this.numberOfErrors.set(0); + this.#errorGlyphs.set(this._codeEditor.deltaDecorations(this.#errorGlyphs(), []) || []); + this._closeProblemsView(); } private async _initialize() { diff --git a/apps/red-ui/src/app/modules/admin/services/component-rules.service.ts b/apps/red-ui/src/app/modules/admin/services/component-rules.service.ts new file mode 100644 index 000000000..74b4ffa61 --- /dev/null +++ b/apps/red-ui/src/app/modules/admin/services/component-rules.service.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@angular/core'; +import { GenericService } from '@iqser/common-ui'; +import { IComponentRules } from '@red/domain'; + +@Injectable() +export class ComponentRulesService extends GenericService { + protected readonly _defaultModelPath = 'rules'; + + download(dossierTemplateId: string) { + return this._getOne([dossierTemplateId]); + } + + uploadRules(body: IComponentRules) { + return this._post(body); + } +} 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 c6d00944c..71888466f 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 @@ -33,10 +33,9 @@ export class AdminSideNavComponent implements OnInit { readonly translations = adminSideNavTranslations; readonly currentUser = getCurrentUser(); readonly roles = Roles; + prefix: string; readonly isDocumine = this.#config.IS_DOCUMINE; readonly canAccessRulesInDocumine = this.isDocumine && !this.#config.RULE_EDITOR_DEV_ONLY; - prefix: string; - readonly items: { readonly [key in AdminSideNavType]: NavItem[] } = { settings: [ { @@ -100,6 +99,14 @@ export class AdminSideNavComponent implements OnInit { label: _('admin-side-nav.rule-editor'), show: (this.isIqserDevMode || this.canAccessRulesInDocumine) && this._permissionsService.has(Roles.rules.read), }, + { + screen: 'component-rules', + label: _('admin-side-nav.component-rule-editor'), + show: + this.isDocumine && + (this.isIqserDevMode || this.canAccessRulesInDocumine) && + this._permissionsService.has(Roles.rules.read), + }, { screen: 'default-colors', label: _('admin-side-nav.default-colors'), diff --git a/apps/red-ui/src/assets/config/config.json b/apps/red-ui/src/assets/config/config.json index 42bf5941f..b022421b1 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://dan.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", @@ -18,8 +18,8 @@ "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 491127258..fbcb70ed8 100644 --- a/apps/red-ui/src/assets/i18n/redact/de.json +++ b/apps/red-ui/src/assets/i18n/redact/de.json @@ -234,6 +234,7 @@ }, "admin-side-nav": { "audit": "", + "component-rule-editor": "", "configurations": "", "default-colors": "", "dictionary": "", @@ -557,6 +558,19 @@ "title": "Aktion bestätigen" } }, + "component-rules-screen": { + "error": { + "generic": "" + }, + "errors-found": "", + "revert-changes": "", + "save-changes": "", + "success": { + "generic": "" + }, + "title": "", + "warning-text": "" + }, "configurations": "Einstellungen", "confirm-archive-dossier": { "archive": "", diff --git a/apps/red-ui/src/assets/i18n/redact/en.json b/apps/red-ui/src/assets/i18n/redact/en.json index 90be40ba8..a2a36c885 100644 --- a/apps/red-ui/src/assets/i18n/redact/en.json +++ b/apps/red-ui/src/assets/i18n/redact/en.json @@ -234,6 +234,7 @@ }, "admin-side-nav": { "audit": "Audit", + "component-rule-editor": "", "configurations": "Configurations", "default-colors": "Default Colors", "dictionary": "Dictionary", @@ -557,6 +558,19 @@ "title": "Confirm Action" } }, + "component-rules-screen": { + "error": { + "generic": "" + }, + "errors-found": "", + "revert-changes": "", + "save-changes": "", + "success": { + "generic": "" + }, + "title": "", + "warning-text": "" + }, "configurations": "Configurations", "confirm-archive-dossier": { "archive": "Archive Dossier", diff --git a/apps/red-ui/src/assets/i18n/scm/de.json b/apps/red-ui/src/assets/i18n/scm/de.json index d1471dccf..2147d843e 100644 --- a/apps/red-ui/src/assets/i18n/scm/de.json +++ b/apps/red-ui/src/assets/i18n/scm/de.json @@ -234,6 +234,7 @@ }, "admin-side-nav": { "audit": "", + "component-rule-editor": "", "configurations": "", "default-colors": "", "dictionary": "", @@ -557,6 +558,19 @@ "title": "Aktion bestätigen" } }, + "component-rules-screen": { + "error": { + "generic": "" + }, + "errors-found": "", + "revert-changes": "", + "save-changes": "", + "success": { + "generic": "" + }, + "title": "", + "warning-text": "" + }, "configurations": "Einstellungen", "confirm-archive-dossier": { "archive": "", diff --git a/apps/red-ui/src/assets/i18n/scm/en.json b/apps/red-ui/src/assets/i18n/scm/en.json index caf96dbd9..61d544385 100644 --- a/apps/red-ui/src/assets/i18n/scm/en.json +++ b/apps/red-ui/src/assets/i18n/scm/en.json @@ -234,6 +234,7 @@ }, "admin-side-nav": { "audit": "Audit", + "component-rule-editor": "Component Rule Editor", "configurations": "Configurations", "default-colors": "Default Colors", "dictionary": "Dictionary", @@ -557,6 +558,19 @@ "title": "Confirm Action" } }, + "component-rules-screen": { + "error": { + "generic": "Something went wrong... Component rules update failed!" + }, + "errors-found": "{errors, plural, one{An error} other{{errors} errors}} found in rules", + "revert-changes": "Revert", + "save-changes": "Save Changes", + "success": { + "generic": "Component rules updated!" + }, + "title": "Component Rule Editor", + "warning-text": "Warning: experimental feature!" + }, "configurations": "Configurations", "confirm-archive-dossier": { "archive": "Archive Dossier", @@ -2213,7 +2227,7 @@ "error": { "generic": "Something went wrong... Rules update failed!" }, - "errors-found": "", + "errors-found": "{errors, plural, one{An error} other{{errors} errors}} found in rules", "revert-changes": "Revert", "save-changes": "Save Changes", "success": { diff --git a/libs/red-domain/src/lib/shared/component-rules.ts b/libs/red-domain/src/lib/shared/component-rules.ts new file mode 100644 index 000000000..c2f8c5fb2 --- /dev/null +++ b/libs/red-domain/src/lib/shared/component-rules.ts @@ -0,0 +1,13 @@ +/** + * Object containing a string of Drools rules. + */ +export interface IComponentRules { + /** + * The DossierTemplate Id for these rules + */ + dossierTemplateId?: string; + /** + * The actual string of rules. + */ + rules?: string; +} diff --git a/libs/red-domain/src/lib/shared/index.ts b/libs/red-domain/src/lib/shared/index.ts index 3c25911a6..b6973f8bf 100644 --- a/libs/red-domain/src/lib/shared/index.ts +++ b/libs/red-domain/src/lib/shared/index.ts @@ -11,3 +11,4 @@ export * from './admin-side-nav-types'; export * from './charts'; export * from './app-config'; export * from './system-preferences'; +export * from './component-rules';