Permissions

This commit is contained in:
Adina Țeudan 2022-03-24 01:02:07 +02:00
parent ed84299160
commit c93eabbdc2
25 changed files with 161 additions and 107 deletions

View File

@ -2,6 +2,7 @@
<ng-container *ngFor="let item of items[type]"> <ng-container *ngFor="let item of items[type]">
<a <a
*ngIf="!item.hideIf" *ngIf="!item.hideIf"
[class.disabled]="isDisabled(item.screen)"
[routerLinkActiveOptions]="{ exact: false }" [routerLinkActiveOptions]="{ exact: false }"
[routerLink]="prefix + item.screen" [routerLink]="prefix + item.screen"
class="item" class="item"

View File

@ -20,6 +20,7 @@ interface NavItem {
}) })
export class AdminSideNavComponent implements OnInit { export class AdminSideNavComponent implements OnInit {
@Input() type: AdminSideNavType; @Input() type: AdminSideNavType;
@Input() disabledItems: string[] = [];
readonly translations = adminSideNavTranslations; readonly translations = adminSideNavTranslations;
readonly currentUser = this._userService.currentUser; readonly currentUser = this._userService.currentUser;
prefix: string; prefix: string;
@ -89,4 +90,8 @@ export class AdminSideNavComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.prefix = this._route.snapshot.paramMap.get(ENTITY_TYPE) ? '' : '../'; this.prefix = this._route.snapshot.paramMap.get(ENTITY_TYPE) ? '' : '../';
} }
isDisabled(screen: string): boolean {
return this.disabledItems.includes(screen);
}
} }

View File

@ -13,7 +13,7 @@ import { DossierTemplateBreadcrumbsComponent } from './components/dossier-templa
import { ColorPickerModule } from 'ngx-color-picker'; import { ColorPickerModule } from 'ngx-color-picker';
import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component'; import { AddEditFileAttributeDialogComponent } from './dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
import { AddEditDossierTemplateDialogComponent } from './dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component'; import { AddEditDossierTemplateDialogComponent } from './dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component';
import { AddEditDictionaryDialogComponent } from './dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component'; import { AddEntityDialogComponent } from './dialogs/add-entity-dialog/add-entity-dialog.component';
import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component'; import { EditColorDialogComponent } from './dialogs/edit-color-dialog/edit-color-dialog.component';
import { ComboChartComponent, ComboSeriesVerticalComponent } from './components/combo-chart'; import { ComboChartComponent, ComboSeriesVerticalComponent } from './components/combo-chart';
import { NgxChartsModule } from '@swimlane/ngx-charts'; import { NgxChartsModule } from '@swimlane/ngx-charts';
@ -51,7 +51,7 @@ import { BaseEntityScreenComponent } from './base-entity-screen/base-entity-scre
const dialogs = [ const dialogs = [
AddEditDossierTemplateDialogComponent, AddEditDossierTemplateDialogComponent,
AddEditDictionaryDialogComponent, AddEntityDialogComponent,
AddEditFileAttributeDialogComponent, AddEditFileAttributeDialogComponent,
EditColorDialogComponent, EditColorDialogComponent,
SmtpAuthDialogComponent, SmtpAuthDialogComponent,

View File

@ -22,7 +22,7 @@
<div class="content-inner"> <div class="content-inner">
<div class="overlay-shadow"></div> <div class="overlay-shadow"></div>
<redaction-admin-side-nav type="entities"></redaction-admin-side-nav> <redaction-admin-side-nav [disabledItems]="disabledItems$ | async" type="entities"></redaction-admin-side-nav>
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>

View File

@ -1,12 +1,14 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'; import { ChangeDetectionStrategy, Component } from '@angular/core';
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { firstValueFrom } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
import { AdminDialogService } from '../services/admin-dialog.service'; import { AdminDialogService } from '../services/admin-dialog.service';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { LoadingService } from '@iqser/common-ui'; import { LoadingService } from '@iqser/common-ui';
import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service'; import { DossierTemplatesService } from '@services/entity-services/dossier-templates.service';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { map } from 'rxjs/operators';
@Component({ @Component({
templateUrl: './base-entity-screen.component.html', templateUrl: './base-entity-screen.component.html',
@ -14,6 +16,7 @@ import { DossierTemplatesService } from '@services/entity-services/dossier-templ
}) })
export class BaseEntityScreenComponent { export class BaseEntityScreenComponent {
readonly currentUser = this._userService.currentUser; readonly currentUser = this._userService.currentUser;
readonly disabledItems$: Observable<string[]>;
readonly #dossierTemplateId: string; readonly #dossierTemplateId: string;
readonly #entityType: string; readonly #entityType: string;
@ -23,11 +26,15 @@ export class BaseEntityScreenComponent {
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
private readonly _dialogService: AdminDialogService, private readonly _dialogService: AdminDialogService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
private readonly _dictionaryMapService: DictionariesMapService,
private readonly _dossierTemplatesService: DossierTemplatesService, private readonly _dossierTemplatesService: DossierTemplatesService,
private readonly _router: Router, private readonly _router: Router,
) { ) {
this.#dossierTemplateId = this._route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); this.#dossierTemplateId = this._route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
this.#entityType = this._route.snapshot.paramMap.get(ENTITY_TYPE); this.#entityType = this._route.snapshot.paramMap.get(ENTITY_TYPE);
this.disabledItems$ = this._dictionaryMapService
.watch$(this.#dossierTemplateId, this.#entityType)
.pipe(map(entity => (entity.hasDictionary ? [] : ['dictionary', 'false-positive', 'false-recommendations'])));
} }
openDeleteDictionariesDialog() { openDeleteDictionariesDialog() {

View File

@ -6,31 +6,31 @@
<form [formGroup]="form"> <form [formGroup]="form">
<div class="dialog-content"> <div class="dialog-content">
<div class="iqser-input-group mb-14"> <div class="iqser-input-group mb-14">
<label translate="add-edit-dictionary.form.technical-name"></label> <label translate="add-entity.form.technical-name"></label>
<div class="technical-name">{{ dictionary?.type || (technicalName$ | async) || '-' }}</div> <div class="technical-name">{{ dictionary?.type || (technicalName$ | async) || '-' }}</div>
</div> </div>
<div *ngIf="(canEditLabel$ | async) === false" class="iqser-input-group mb-14"> <div *ngIf="(canEditLabel$ | async) === false" class="iqser-input-group mb-14">
<label translate="add-edit-dictionary.form.name"></label> <label translate="add-entity.form.name"></label>
{{ dictionary.label }} {{ dictionary.label }}
</div> </div>
<div class="first-row"> <div class="first-row">
<div *ngIf="canEditLabel$ | async" class="iqser-input-group required"> <div *ngIf="canEditLabel$ | async" class="iqser-input-group required">
<label translate="add-edit-dictionary.form.name"></label> <label translate="add-entity.form.name"></label>
<input <input
[placeholder]="'add-edit-dictionary.form.name-placeholder' | translate" [placeholder]="'add-entity.form.name-placeholder' | translate"
formControlName="label" formControlName="label"
name="label" name="label"
type="text" type="text"
/> />
<span class="hint" translate="add-edit-dictionary.form.name-hint"></span> <span class="hint" translate="add-entity.form.name-hint"></span>
</div> </div>
<div class="iqser-input-group required w-75"> <div class="iqser-input-group required w-75">
<label translate="add-edit-dictionary.form.rank"></label> <label translate="add-entity.form.rank"></label>
<input <input
[placeholder]="'add-edit-dictionary.form.rank-placeholder' | translate" [placeholder]="'add-entity.form.rank-placeholder' | translate"
formControlName="rank" formControlName="rank"
name="rank" name="rank"
type="number" type="number"
@ -38,9 +38,9 @@
</div> </div>
<div class="iqser-input-group required"> <div class="iqser-input-group required">
<label translate="add-edit-dictionary.form.color"></label> <label translate="add-entity.form.color"></label>
<input <input
[placeholder]="'add-edit-dictionary.form.color-placeholder' | translate" [placeholder]="'add-entity.form.color-placeholder' | translate"
class="hex-color-input" class="hex-color-input"
formControlName="hexColor" formControlName="hexColor"
name="hexColor" name="hexColor"
@ -59,9 +59,9 @@
</div> </div>
<div class="iqser-input-group w-450"> <div class="iqser-input-group w-450">
<label translate="add-edit-dictionary.form.description"></label> <label translate="add-entity.form.description"></label>
<textarea <textarea
[placeholder]="'add-edit-dictionary.form.description-placeholder' | translate" [placeholder]="'add-entity.form.description-placeholder' | translate"
formControlName="description" formControlName="description"
iqserHasScrollbar iqserHasScrollbar
name="description" name="description"
@ -73,30 +73,30 @@
<div class="iqser-input-group slider-row"> <div class="iqser-input-group slider-row">
<mat-button-toggle-group appearance="legacy" formControlName="hint" name="hint"> <mat-button-toggle-group appearance="legacy" formControlName="hint" name="hint">
<mat-button-toggle [value]="false"> <mat-button-toggle [value]="false">
{{ 'add-edit-dictionary.form.redaction' | translate }} {{ 'add-entity.form.redaction' | translate }}
</mat-button-toggle> </mat-button-toggle>
<mat-button-toggle [value]="true"> <mat-button-toggle [value]="true">
{{ 'add-edit-dictionary.form.hint' | translate }} {{ 'add-entity.form.hint' | translate }}
</mat-button-toggle> </mat-button-toggle>
</mat-button-toggle-group> </mat-button-toggle-group>
</div> </div>
<div class="iqser-input-group"> <div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="caseSensitive" name="caseSensitive"> <mat-checkbox color="primary" formControlName="caseSensitive" name="caseSensitive">
{{ 'add-edit-dictionary.form.case-sensitive' | translate }} {{ 'add-entity.form.case-sensitive' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="iqser-input-group"> <div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction"> <mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
{{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }} {{ 'add-entity.form.add-to-dictionary-action' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
</div> </div>
<div class="dialog-actions"> <div class="dialog-actions">
<button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button"> <button (click)="save()" [disabled]="disabled" color="primary" mat-flat-button type="button">
{{ 'add-edit-dictionary.save' | translate }} {{ 'add-entity.save' | translate }}
</button> </button>
</div> </div>
</form> </form>

View File

@ -13,19 +13,15 @@ import { map } from 'rxjs/operators';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
@Component({ @Component({
selector: 'redaction-add-edit-dictionary-dialog', templateUrl: './add-entity-dialog.component.html',
templateUrl: './add-edit-dictionary-dialog.component.html', styleUrls: ['./add-entity-dialog.component.scss'],
styleUrls: ['./add-edit-dictionary-dialog.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AddEditDictionaryDialogComponent extends BaseDialogComponent { export class AddEntityDialogComponent extends BaseDialogComponent {
readonly dictionary = this._data.dictionary; readonly dictionary = this._data.dictionary;
readonly canEditLabel$ = this._canEditLabel$; readonly canEditLabel$ = this._canEditLabel$;
readonly technicalName$: Observable<string>; readonly technicalName$: Observable<string>;
readonly dialogHeader = this._translateService.instant('add-edit-dictionary.title', { readonly dialogHeader = this._translateService.instant('add-entity.title');
type: this._data.dictionary ? 'edit' : 'create',
name: this._data.dictionary?.label,
});
readonly hasColor$: Observable<boolean>; readonly hasColor$: Observable<boolean>;
private readonly _dossierTemplateId = this._data.dossierTemplateId; private readonly _dossierTemplateId = this._data.dossierTemplateId;
@ -38,7 +34,7 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
protected readonly _injector: Injector, protected readonly _injector: Injector,
protected readonly _dialogRef: MatDialogRef<AddEditDictionaryDialogComponent>, protected readonly _dialogRef: MatDialogRef<AddEntityDialogComponent>,
@Inject(MAT_DIALOG_DATA) @Inject(MAT_DIALOG_DATA)
private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string }, private readonly _data: { readonly dictionary: Dictionary; readonly dossierTemplateId: string },
) { ) {
@ -73,11 +69,11 @@ export class AddEditDictionaryDialogComponent extends BaseDialogComponent {
if (this.dictionary) { if (this.dictionary) {
// edit mode // edit mode
await firstValueFrom(this._dictionaryService.updateDictionary(dictionary, dossierTemplateId, dictionary.type)); await firstValueFrom(this._dictionaryService.updateDictionary(dictionary, dossierTemplateId, dictionary.type));
this._toaster.success(_('add-edit-dictionary.success.edit')); this._toaster.success(_('add-entity.success.edit'));
} else { } else {
// create mode // create mode
await firstValueFrom(this._dictionaryService.addDictionary({ ...dictionary, dossierTemplateId })); await firstValueFrom(this._dictionaryService.addDictionary({ ...dictionary, dossierTemplateId }));
this._toaster.success(_('add-edit-dictionary.success.create')); this._toaster.success(_('add-entity.success.create'));
} }
this._dialogRef.close(true); this._dialogRef.close(true);
} catch (e) {} } catch (e) {}

View File

@ -21,15 +21,16 @@
<div class="content-container"> <div class="content-container">
<iqser-table <iqser-table
(noDataAction)="openAddEditDictionaryDialog()" (noDataAction)="openAddEntityDialog()"
[bulkActions]="bulkActions" [bulkActions]="bulkActions"
[headerTemplate]="headerTemplate" [headerTemplate]="headerTemplate"
[itemSize]="80" [itemSize]="80"
[noDataButtonIcon]="'iqser:plus'"
[noDataButtonLabel]="'entities-listing.no-data.action' | translate" [noDataButtonLabel]="'entities-listing.no-data.action' | translate"
[noDataText]="'entities-listing.no-data.title' | translate" [noDataText]="'entities-listing.no-data.title' | translate"
[noMatchText]="'entities-listing.no-match.title' | translate" [noMatchText]="'entities-listing.no-match.title' | translate"
[selectionEnabled]="true" [selectionEnabled]="true"
[showNoDataButton]="currentUser.isAdmin" [showNoDataButton]="permissionsService.canEditEntities()"
[tableColumnConfigs]="tableColumnConfigs" [tableColumnConfigs]="tableColumnConfigs"
noDataIcon="red:dictionary" noDataIcon="red:dictionary"
></iqser-table> ></iqser-table>
@ -39,8 +40,8 @@
<ng-template #bulkActions> <ng-template #bulkActions>
<iqser-circle-button <iqser-circle-button
(action)="openDeleteDictionariesDialog($event)" (action)="openDeleteEntitiesDialog($event)"
*ngIf="currentUser.isAdmin && (listingService.areSomeSelected$ | async)" *ngIf="permissionsService.canEditEntities() && (listingService.areSomeSelected$ | async)"
[tooltip]="'entities-listing.bulk.delete' | translate" [tooltip]="'entities-listing.bulk.delete' | translate"
[type]="circleButtonTypes.dark" [type]="circleButtonTypes.dark"
icon="iqser:trash" icon="iqser:trash"
@ -55,8 +56,8 @@
></iqser-input-with-action> ></iqser-input-with-action>
<div class="actions"> <div class="actions">
<iqser-icon-button <iqser-icon-button
(action)="openAddEditDictionaryDialog()" (action)="openAddEntityDialog()"
*ngIf="currentUser.isAdmin" *ngIf="permissionsService.canEditEntities()"
[label]="'entities-listing.add-new' | translate" [label]="'entities-listing.add-new' | translate"
[type]="iconButtonTypes.primary" [type]="iconButtonTypes.primary"
icon="iqser:plus" icon="iqser:plus"
@ -90,9 +91,9 @@
{{ templateStats.dictionarySummary(dict.type)?.entriesCount || 0 }} {{ templateStats.dictionarySummary(dict.type)?.entriesCount || 0 }}
</div> </div>
<div *ngIf="currentUser.isAdmin" class="action-buttons"> <div *ngIf="permissionsService.canEditEntities()" class="action-buttons">
<iqser-circle-button <iqser-circle-button
(action)="openDeleteDictionariesDialog($event, [dict])" (action)="openDeleteEntitiesDialog($event, [dict])"
[tooltip]="'entities-listing.action.delete' | translate" [tooltip]="'entities-listing.action.delete' | translate"
[type]="circleButtonTypes.dark" [type]="circleButtonTypes.dark"
icon="iqser:trash" icon="iqser:trash"

View File

@ -9,7 +9,6 @@ import {
} from '@iqser/common-ui'; } from '@iqser/common-ui';
import { AdminDialogService } from '../../services/admin-dialog.service'; import { AdminDialogService } from '../../services/admin-dialog.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserService } from '@services/user.service';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { Dictionary, DossierTemplateStats } from '@red/domain'; import { Dictionary, DossierTemplateStats } from '@red/domain';
import { firstValueFrom, Observable } from 'rxjs'; import { firstValueFrom, Observable } from 'rxjs';
@ -18,6 +17,7 @@ import { ActivatedRoute } from '@angular/router';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { DOSSIER_TEMPLATE_ID } from '@utils/constants'; import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
import { PermissionsService } from '@services/permissions.service';
@Component({ @Component({
templateUrl: './entities-listing-screen.component.html', templateUrl: './entities-listing-screen.component.html',
@ -27,7 +27,6 @@ import { DOSSIER_TEMPLATE_ID } from '@utils/constants';
export class EntitiesListingScreenComponent extends ListingComponent<Dictionary> { export class EntitiesListingScreenComponent extends ListingComponent<Dictionary> {
readonly iconButtonTypes = IconButtonTypes; readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes; readonly circleButtonTypes = CircleButtonTypes;
readonly currentUser = this._userService.currentUser;
readonly tableHeaderLabel = _('entities-listing.table-header.title'); readonly tableHeaderLabel = _('entities-listing.table-header.title');
readonly tableColumnConfigs: TableColumnConfig<Dictionary>[] = [ readonly tableColumnConfigs: TableColumnConfig<Dictionary>[] = [
{ label: _('entities-listing.table-col-names.type'), sortByKey: 'searchKey', width: '2fr' }, { label: _('entities-listing.table-col-names.type'), sortByKey: 'searchKey', width: '2fr' },
@ -40,20 +39,20 @@ export class EntitiesListingScreenComponent extends ListingComponent<Dictionary>
constructor( constructor(
protected readonly _injector: Injector, protected readonly _injector: Injector,
private readonly _userService: UserService,
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
private readonly _dictionariesMapService: DictionariesMapService, private readonly _dictionariesMapService: DictionariesMapService,
private readonly _dialogService: AdminDialogService, private readonly _dialogService: AdminDialogService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
private readonly _dossierTemplateStatsService: DossierTemplateStatsService, private readonly _dossierTemplateStatsService: DossierTemplateStatsService,
private readonly _route: ActivatedRoute, private readonly _route: ActivatedRoute,
readonly permissionsService: PermissionsService,
) { ) {
super(_injector); super(_injector);
this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); this.#dossierTemplateId = _route.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
this.templateStats$ = this._dossierTemplateStatsService.watch$(this.#dossierTemplateId).pipe(tap(() => this._loadDictionaryData())); this.templateStats$ = this._dossierTemplateStatsService.watch$(this.#dossierTemplateId).pipe(tap(() => this._loadDictionaryData()));
} }
openDeleteDictionariesDialog($event?: MouseEvent, types = this.listingService.selected) { openDeleteEntitiesDialog($event?: MouseEvent, types = this.listingService.selected) {
this._dialogService.openDialog('confirm', $event, null, async () => { this._dialogService.openDialog('confirm', $event, null, async () => {
this._loadingService.start(); this._loadingService.start();
await firstValueFrom( await firstValueFrom(
@ -66,11 +65,8 @@ export class EntitiesListingScreenComponent extends ListingComponent<Dictionary>
}); });
} }
openAddEditDictionaryDialog($event?: MouseEvent, dictionary?: Dictionary) { openAddEntityDialog($event?: MouseEvent) {
this._dialogService.openDialog('addEditDictionary', $event, { this._dialogService.openDialog('addEntity', $event, { dossierTemplateId: this.#dossierTemplateId });
dictionary,
dossierTemplateId: this.#dossierTemplateId,
});
} }
private _loadDictionaryData(): void { private _loadDictionaryData(): void {

View File

@ -2,6 +2,11 @@
<div class="dialog"> <div class="dialog">
<div class="dialog-header"> <div class="dialog-header">
<div [translate]="'entity.info.heading'" class="heading-l"></div> <div [translate]="'entity.info.heading'" class="heading-l"></div>
<div *ngIf="!permissionsService.canEditEntities()" class="read-only-indicator all-caps-label primary">
<mat-icon class="mr-8" svgIcon="red:read-only"></mat-icon>
{{ 'readonly' | translate }}
</div>
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
@ -41,6 +46,7 @@
<div <div
(colorPickerChange)="form.get('hexColor').setValue($event)" (colorPickerChange)="form.get('hexColor').setValue($event)"
[colorPicker]="form.get('hexColor').value" [colorPicker]="form.get('hexColor').value"
[cpDisabled]="form.get('hexColor').disabled"
[cpOutputFormat]="'hex'" [cpOutputFormat]="'hex'"
[style.background]="form.get('hexColor').value" [style.background]="form.get('hexColor').value"
class="input-icon" class="input-icon"
@ -61,6 +67,7 @@
<div <div
(colorPickerChange)="form.get('recommendationHexColor').setValue($event)" (colorPickerChange)="form.get('recommendationHexColor').setValue($event)"
[colorPicker]="form.get('recommendationHexColor').value" [colorPicker]="form.get('recommendationHexColor').value"
[cpDisabled]="form.get('recommendationHexColor').disabled"
[cpOutputFormat]="'hex'" [cpOutputFormat]="'hex'"
[style.background]="form.get('recommendationHexColor').value" [style.background]="form.get('recommendationHexColor').value"
class="input-icon" class="input-icon"
@ -72,7 +79,7 @@
<div class="iqser-input-group mb-14"> <div class="iqser-input-group mb-14">
<label translate="entity.info.form.technical-name"></label> <label translate="entity.info.form.technical-name"></label>
<div class="technical-name">{{ entity.type }}</div> <div class="technical-name">{{ initialFormValue.type }}</div>
<span class="hint" translate="entity.info.form.technical-name-hint"></span> <span class="hint" translate="entity.info.form.technical-name-hint"></span>
</div> </div>
@ -87,7 +94,7 @@
</mat-button-toggle-group> </mat-button-toggle-group>
</div> </div>
<div class="iqser-input-group w-400"> <div *ngIf="form.get('defaultReason')" class="iqser-input-group w-400">
<label translate="entity.info.form.default-reason"></label> <label translate="entity.info.form.default-reason"></label>
<mat-select <mat-select
[placeholder]="'entity.info.form.default-reason-placeholder' | translate" [placeholder]="'entity.info.form.default-reason-placeholder' | translate"
@ -108,18 +115,18 @@
</div> </div>
<div class="iqser-input-group"> <div class="iqser-input-group">
<mat-slide-toggle color="primary" formControlName="hasDictionary">{{ <mat-slide-toggle color="primary" formControlName="hasDictionary">
'entity.info.form.has-dictionary' | translate {{ 'entity.info.form.has-dictionary' | translate }}
}}</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<div class="iqser-input-group"> <div *ngIf="form.get('caseSensitive')" class="iqser-input-group">
<mat-checkbox color="primary" formControlName="caseSensitive" name="caseSensitive"> <mat-checkbox color="primary" formControlName="caseSensitive" name="caseSensitive">
{{ 'entity.info.form.case-sensitive' | translate }} {{ 'entity.info.form.case-sensitive' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
<div class="iqser-input-group"> <div *ngIf="form.get('addToDictionaryAction')" class="iqser-input-group">
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction"> <mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
{{ 'entity.info.form.add-to-dictionary-action' | translate }} {{ 'entity.info.form.add-to-dictionary-action' | translate }}
</mat-checkbox> </mat-checkbox>
@ -127,7 +134,7 @@
</form> </form>
</div> </div>
<div class="dialog-actions"> <div *ngIf="permissionsService.canEditEntities()" class="dialog-actions">
<button (click)="save()" [disabled]="form.invalid || !changed" color="primary" mat-flat-button> <button (click)="save()" [disabled]="form.invalid || !changed" color="primary" mat-flat-button>
{{ 'entity.info.actions.save' | translate }} {{ 'entity.info.actions.save' | translate }}
</button> </button>

View File

@ -6,6 +6,12 @@
overflow: hidden; overflow: hidden;
} }
.dialog-header {
padding-right: 32px;
display: flex;
justify-content: space-between;
}
.content-container { .content-container {
flex: 1; flex: 1;
display: flex; display: flex;

View File

@ -1,6 +1,6 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui'; import { BaseFormComponent, LoadingService, Toaster } from '@iqser/common-ui';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { Dictionary, IDictionary } from '@red/domain'; import { Dictionary, IDictionary } from '@red/domain';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants'; import { DOSSIER_TEMPLATE_ID, ENTITY_TYPE } from '@utils/constants';
@ -10,6 +10,9 @@ import { firstValueFrom, Observable } from 'rxjs';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { DictionaryService } from '@services/entity-services/dictionary.service'; import { DictionaryService } from '@services/entity-services/dictionary.service';
import { PermissionsService } from '@services/permissions.service';
const REDACTION_FIELDS = ['defaultReason', 'caseSensitive', 'addToDictionaryAction'];
@Component({ @Component({
selector: 'redaction-info', selector: 'redaction-info',
@ -18,10 +21,10 @@ import { DictionaryService } from '@services/entity-services/dictionary.service'
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class InfoComponent extends BaseFormComponent { export class InfoComponent extends BaseFormComponent {
entity: Dictionary;
readonly hasHexColor$: Observable<boolean>; readonly hasHexColor$: Observable<boolean>;
readonly hasRecommendationHexColor$: Observable<boolean>; readonly hasRecommendationHexColor$: Observable<boolean>;
readonly currentUser = this._userService.currentUser; readonly currentUser = this._userService.currentUser;
entity: Dictionary;
readonly #dossierTemplateId: string; readonly #dossierTemplateId: string;
constructor( constructor(
@ -33,13 +36,11 @@ export class InfoComponent extends BaseFormComponent {
private readonly _loadingService: LoadingService, private readonly _loadingService: LoadingService,
private readonly _dictionaryService: DictionaryService, private readonly _dictionaryService: DictionaryService,
private readonly _changeRef: ChangeDetectorRef, private readonly _changeRef: ChangeDetectorRef,
readonly permissionsService: PermissionsService,
) { ) {
super(); super();
this.#dossierTemplateId = this._route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID); this.#dossierTemplateId = this._route.parent.snapshot.paramMap.get(DOSSIER_TEMPLATE_ID);
const entityType = this._route.parent.snapshot.paramMap.get(ENTITY_TYPE); this.form = this._initializeForm();
this.entity = this._dictionariesMapService.getDictionary(entityType, this.#dossierTemplateId);
this.form = this._getForm();
this.initialFormValue = this.form.getRawValue();
this.hasHexColor$ = this._colorEmpty$('hexColor'); this.hasHexColor$ = this._colorEmpty$('hexColor');
this.hasRecommendationHexColor$ = this._colorEmpty$('recommendationHexColor'); this.hasRecommendationHexColor$ = this._colorEmpty$('recommendationHexColor');
} }
@ -53,18 +54,21 @@ export class InfoComponent extends BaseFormComponent {
this._toaster.success(_('entity.info.actions.save-success')); this._toaster.success(_('entity.info.actions.save-success'));
this.initialFormValue = this.form.getRawValue(); this.initialFormValue = this.form.getRawValue();
this._changeRef.markForCheck(); this._changeRef.markForCheck();
} catch (e) {} } catch (e) {
this._toaster.error(_('entity.info.actions.save-failed'));
console.error(e);
}
this._loadingService.stop(); this._loadingService.stop();
} }
revert(): void { revert(): void {
this.form = this._getForm(); this.form = this._initializeForm();
} }
private _colorEmpty$(field: string) { private _colorEmpty$(field: string) {
return this.form.get(field).valueChanges.pipe( return this.form.get(field).valueChanges.pipe(
startWith(''), startWith(this.form.get(field).value),
map((value: string) => !value || value?.length === 0), map((value: string) => !value || value?.length === 0),
); );
} }
@ -73,30 +77,63 @@ export class InfoComponent extends BaseFormComponent {
return { return {
type: this.entity.type, type: this.entity.type,
label: this.form.get('label').value, label: this.form.get('label').value,
caseInsensitive: !this.form.get('caseSensitive').value, caseInsensitive: !this.form.get('caseSensitive')?.value,
description: this.form.get('description').value, description: this.form.get('description').value,
hexColor: this.form.get('hexColor').value, hexColor: this.form.get('hexColor').value,
recommendationHexColor: this.form.get('recommendationHexColor').value, recommendationHexColor: this.form.get('recommendationHexColor').value,
hint: this.form.get('hint').value, hint: this.form.get('hint').value,
rank: this.form.get('rank').value, rank: this.form.get('rank').value,
addToDictionaryAction: this.form.get('addToDictionaryAction').value, addToDictionaryAction: this.form.get('addToDictionaryAction')?.value,
dossierTemplateId: this.#dossierTemplateId, dossierTemplateId: this.#dossierTemplateId,
hasDictionary: this.form.get('hasDictionary').value, hasDictionary: this.form.get('hasDictionary').value,
}; };
} }
private _getForm(): FormGroup { private _initializeForm(): FormGroup {
return this._formBuilder.group({ const entityType = this._route.parent.snapshot.paramMap.get(ENTITY_TYPE);
label: [{ value: this.entity.label, disabled: !this.currentUser.isAdmin }, [Validators.required, Validators.minLength(3)]], this.entity = this._dictionariesMapService.getDictionary(entityType, this.#dossierTemplateId);
const controlsConfig = {
label: [
{
value: this.entity.label,
disabled: !this.currentUser.isAdmin,
},
[Validators.required, Validators.minLength(3)],
],
description: [this.entity.description], description: [this.entity.description],
rank: [this.entity.rank, Validators.required], rank: [this.entity.rank, Validators.required],
hexColor: [this.entity.hexColor, [Validators.required, Validators.minLength(7)]], hexColor: [this.entity.hexColor, [Validators.required, Validators.minLength(7)]],
recommendationHexColor: [this.entity.recommendationHexColor, [Validators.required, Validators.minLength(7)]], recommendationHexColor: [this.entity.recommendationHexColor, [Validators.required, Validators.minLength(7)]],
hint: [this.entity.hint], hint: [this.entity.hint],
addToDictionaryAction: [this.entity.addToDictionaryAction],
caseSensitive: [!this.entity.caseInsensitive],
defaultReason: [{ value: null, disabled: true }],
hasDictionary: [this.entity.hasDictionary], hasDictionary: [this.entity.hasDictionary],
};
if (!this.entity.hint) {
Object.assign(controlsConfig, {
defaultReason: [{ value: null, disabled: true }],
caseSensitive: [!this.entity.caseInsensitive],
addToDictionaryAction: [this.entity.addToDictionaryAction],
});
}
const form = this._formBuilder.group(controlsConfig);
this.initialFormValue = form.getRawValue();
form.get('hint').valueChanges.subscribe(isHint => {
if (isHint) {
REDACTION_FIELDS.forEach(field => this.form.removeControl(field));
} else {
form.addControl('addToDictionaryAction', new FormControl(this.entity.addToDictionaryAction));
form.addControl('caseSensitive', new FormControl(!this.entity.caseInsensitive));
form.addControl('defaultReason', new FormControl({ value: null, disabled: true }));
}
}); });
if (!this.permissionsService.canEditEntities()) {
form.disable();
}
return form;
} }
} }

View File

@ -56,7 +56,7 @@
<div class="iqser-input-group w-150"> <div class="iqser-input-group w-150">
<label class="all-caps-label mb-5" translate="watermark-screen.form.color"></label> <label class="all-caps-label mb-5" translate="watermark-screen.form.color"></label>
<input <input
[placeholder]="'add-edit-dictionary.form.color-placeholder' | translate" [placeholder]="'add-entity.form.color-placeholder' | translate"
class="hex-color-input" class="hex-color-input"
formControlName="hexColor" formControlName="hexColor"
name="hexColor" name="hexColor"

View File

@ -1,7 +1,7 @@
import { Injectable, TemplateRef } from '@angular/core'; import { Injectable, TemplateRef } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { AddEditFileAttributeDialogComponent } from '../dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component'; import { AddEditFileAttributeDialogComponent } from '../dialogs/add-edit-file-attribute-dialog/add-edit-file-attribute-dialog.component';
import { AddEditDictionaryDialogComponent } from '../dialogs/add-edit-dictionary-dialog/add-edit-dictionary-dialog.component'; import { AddEntityDialogComponent } from '../dialogs/add-entity-dialog/add-entity-dialog.component';
import { AddEditDossierTemplateDialogComponent } from '../dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component'; import { AddEditDossierTemplateDialogComponent } from '../dialogs/add-edit-dossier-template-dialog/add-edit-dossier-template-dialog.component';
import { EditColorDialogComponent } from '../dialogs/edit-color-dialog/edit-color-dialog.component'; import { EditColorDialogComponent } from '../dialogs/edit-color-dialog/edit-color-dialog.component';
import { SmtpAuthDialogComponent } from '../dialogs/smtp-auth-dialog/smtp-auth-dialog.component'; import { SmtpAuthDialogComponent } from '../dialogs/smtp-auth-dialog/smtp-auth-dialog.component';
@ -27,11 +27,11 @@ import { firstValueFrom, forkJoin } from 'rxjs';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service'; import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { IDossierAttributeConfig, IFileAttributeConfig, IReportTemplate } from '@red/domain'; import { IDossierAttributeConfig, IFileAttributeConfig, IReportTemplate } from '@red/domain';
import { ReportTemplateService } from '../../../services/report-template.service'; import { ReportTemplateService } from '@services/report-template.service';
type DialogType = type DialogType =
| 'confirm' | 'confirm'
| 'addEditDictionary' | 'addEntity'
| 'editColor' | 'editColor'
| 'addEditFileAttribute' | 'addEditFileAttribute'
| 'importFileAttributes' | 'importFileAttributes'
@ -51,8 +51,8 @@ export class AdminDialogService extends DialogService<DialogType> {
component: ConfirmationDialogComponent, component: ConfirmationDialogComponent,
dialogConfig: { disableClose: false }, dialogConfig: { disableClose: false },
}, },
addEditDictionary: { addEntity: {
component: AddEditDictionaryDialogComponent, component: AddEntityDialogComponent,
dialogConfig: { autoFocus: true }, dialogConfig: { autoFocus: true },
}, },
editColor: { editColor: {

View File

@ -13,7 +13,7 @@
<form *ngIf="form" [formGroup]="form"> <form *ngIf="form" [formGroup]="form">
<div class="iqser-input-group"> <div class="iqser-input-group">
<mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction"> <mat-checkbox color="primary" formControlName="addToDictionaryAction" name="addToDictionaryAction">
{{ 'add-edit-dictionary.form.add-to-dictionary-action' | translate }} {{ 'add-entity.form.add-to-dictionary-action' | translate }}
</mat-checkbox> </mat-checkbox>
</div> </div>
</form> </form>

View File

@ -17,7 +17,7 @@
{{ activeNavItem.title | translate }} {{ activeNavItem.title | translate }}
</div> </div>
<div *ngIf="activeNavItem.readonly" class="read-only all-caps-label primary"> <div *ngIf="activeNavItem.readonly" class="read-only-indicator all-caps-label primary">
<mat-icon class="mr-8" svgIcon="red:read-only"></mat-icon> <mat-icon class="mr-8" svgIcon="red:read-only"></mat-icon>
{{ 'readonly' | translate }} {{ 'readonly' | translate }}
</div> </div>

View File

@ -42,14 +42,8 @@ redaction-edit-dossier-dictionary {
margin-top: 6px; margin-top: 6px;
} }
.read-only { .read-only-indicator {
display: flex;
position: absolute; position: absolute;
right: 32px; right: 32px;
top: 108px; top: 108px;
mat-icon {
height: 14px;
width: 14px;
}
} }

View File

@ -16,12 +16,12 @@ import { RedactionLogEntry } from '../../../models/file/redaction-log.entry';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FilePreviewStateService } from './file-preview-state.service'; import { FilePreviewStateService } from './file-preview-state.service';
import { ViewedPagesService } from '@services/entity-services/viewed-pages.service'; import { ViewedPagesService } from '@services/entity-services/viewed-pages.service';
import { UserPreferenceService } from '../../../services/user-preference.service'; import { UserPreferenceService } from '@services/user-preference.service';
import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service'; import { DictionariesMapService } from '@services/entity-services/dictionaries-map.service';
import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators'; import { map, switchMap, tap, withLatestFrom } from 'rxjs/operators';
import { PermissionsService } from '../../../services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { shareDistinctLast, shareLast, Toaster } from '../../../../../../../libs/common-ui/src'; import { shareDistinctLast, shareLast, Toaster } from '@iqser/common-ui';
import { RedactionLogService } from '../../dossier/services/redaction-log.service'; import { RedactionLogService } from '../../dossier/services/redaction-log.service';
import { TextHighlightService } from '../../dossier/services/text-highlight.service'; import { TextHighlightService } from '../../dossier/services/text-highlight.service';
import { ViewModeService } from './view-mode.service'; import { ViewModeService } from './view-mode.service';

View File

@ -10,7 +10,7 @@ import { DISABLED_HOTKEYS } from '../shared/constants';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { NGXLogger } from 'ngx-logger'; import { NGXLogger } from 'ngx-logger';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';
import { shareLast } from '../../../../../../../libs/common-ui/src'; import { shareLast } from '@iqser/common-ui';
import Annotation = Core.Annotations.Annotation; import Annotation = Core.Annotations.Annotation;
@Injectable() @Injectable()

View File

@ -274,11 +274,11 @@ export class DictionaryService extends EntitiesService<Dictionary, IDictionary>
#addUpdateDictionaryErrorToast(error: HttpErrorResponse): Observable<never> { #addUpdateDictionaryErrorToast(error: HttpErrorResponse): Observable<never> {
if (error.status === HttpStatusCode.Conflict) { if (error.status === HttpStatusCode.Conflict) {
this._toaster.error(_('add-edit-dictionary.error.dictionary-already-exists')); this._toaster.error(_('add-entity.error.entity-already-exists'));
} else if (error.status === HttpStatusCode.BadRequest) { } else if (error.status === HttpStatusCode.BadRequest) {
this._toaster.error(_('add-edit-dictionary.error.invalid-color-or-rank')); this._toaster.error(_('add-entity.error.invalid-color-or-rank'));
} else { } else {
this._toaster.error(_('add-edit-dictionary.error.generic')); this._toaster.error(_('add-entity.error.generic'));
} }
return throwError(() => error); return throwError(() => error);
} }

View File

@ -13,6 +13,10 @@ export class PermissionsService {
private readonly _featuresService: FeaturesService, private readonly _featuresService: FeaturesService,
) {} ) {}
canEditEntities(user = this._userService.currentUser): boolean {
return user.isAdmin;
}
canPerformDossierStatesActions(user = this._userService.currentUser): boolean { canPerformDossierStatesActions(user = this._userService.currentUser): boolean {
return user.isAdmin; return user.isAdmin;
} }

View File

@ -36,9 +36,9 @@
}, },
"header-new": "Dossier erstellen" "header-new": "Dossier erstellen"
}, },
"add-edit-dictionary": { "add-entity": {
"error": { "error": {
"dictionary-already-exists": "Ein Wörterbuch mit diesem Namen existiert bereits!", "entity-already-exists": "Ein Wörterbuch mit diesem Namen existiert bereits!",
"generic": "Wörterbuch konnte nicht gespeichert werden!", "generic": "Wörterbuch konnte nicht gespeichert werden!",
"invalid-color-or-rank": "Ungültige Farbe oder Rang! Der Rang wird bereits von einem anderen Wörterbuch verwendet oder die Farbe ist kein gültiger Hex-Farbcode!" "invalid-color-or-rank": "Ungültige Farbe oder Rang! Der Rang wird bereits von einem anderen Wörterbuch verwendet oder die Farbe ist kein gültiger Hex-Farbcode!"
}, },
@ -63,7 +63,7 @@
"create": "", "create": "",
"edit": "" "edit": ""
}, },
"title": "{type, select, edit{Wörterbuch {name} bearbeiten} create{Wörterbuch erstellen} other{}}" "title": "Wörterbuch erstellen"
}, },
"add-edit-dossier-attribute": { "add-edit-dossier-attribute": {
"error": { "error": {

View File

@ -36,11 +36,11 @@
}, },
"header-new": "Create Dossier" "header-new": "Create Dossier"
}, },
"add-edit-dictionary": { "add-entity": {
"error": { "error": {
"dictionary-already-exists": "Dictionary with this name already exists!", "entity-already-exists": "Entity with this name already exists!",
"generic": "Failed to save dictionary!", "generic": "Failed to save entity!",
"invalid-color-or-rank": "Invalid color or rank! Rank is already used by another dictionary or the color is not a valid hexColor!" "invalid-color-or-rank": "Invalid color or rank! Rank is already used by another entity or the color is not a valid hexColor!"
}, },
"form": { "form": {
"add-to-dictionary-action": "Enable 'Add to dictionary'", "add-to-dictionary-action": "Enable 'Add to dictionary'",
@ -58,12 +58,12 @@
"redaction": "Redaction", "redaction": "Redaction",
"technical-name": "Technical Name" "technical-name": "Technical Name"
}, },
"save": "Save Dictionary", "save": "Save Entity",
"success": { "success": {
"create": "Dictionary added!", "create": "Entity added!",
"edit": "Dictionary updated!" "edit": "Entity updated!"
}, },
"title": "{type, select, edit{Edit {name}} create{Create} other{}} Dictionary" "title": "Create Entity"
}, },
"add-edit-dossier-attribute": { "add-edit-dossier-attribute": {
"error": { "error": {

@ -1 +1 @@
Subproject commit 9463df1a16ab52fe12b882a401eb56b04ffff010 Subproject commit 22434a2627d71f7cddc8bdd7d9adab9762c41e4c