move search service, update user wrapper and service

This commit is contained in:
Dan Percic 2021-08-06 14:34:44 +03:00
parent 2cb86ab702
commit 9d7c343d5f
43 changed files with 207 additions and 338 deletions

View File

@ -78,7 +78,7 @@ export class BaseScreenComponent {
}
get user() {
return this._userService.user;
return this._userService.currentUser;
}
get showPendingDownloadsDot() {

View File

@ -2,10 +2,8 @@ import { Component, Injector, OnInit } from '@angular/core';
import { FileDownloadService } from '@upload-download/services/file-download.service';
import { DownloadStatusWrapper } from '@upload-download/model/download-status.wrapper';
import { DownloadControllerService } from '@redaction/red-ui-http';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes, FilterService, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { CircleButtonTypes } from '@iqser/common-ui';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { TableColConfig } from '@shared/components/table-col-name/table-col-name.component';
@ -13,7 +11,7 @@ import { TableColConfig } from '@shared/components/table-col-name/table-col-name
selector: 'redaction-downloads-list-screen',
templateUrl: './downloads-list-screen.component.html',
styleUrls: ['./downloads-list-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class DownloadsListScreenComponent extends BaseListingComponent<DownloadStatusWrapper> implements OnInit {
readonly circleButtonTypes = CircleButtonTypes;

View File

@ -98,12 +98,12 @@ export class UserProfileScreenComponent implements OnInit {
private _initializeForm(): void {
try {
this._profileModel = {
email: this._userService.user.email,
firstName: this._userService.user.firstName,
lastName: this._userService.user.lastName,
email: this._userService.currentUser.email,
firstName: this._userService.currentUser.firstName,
lastName: this._userService.currentUser.lastName,
language: this._languageService.currentLanguage
};
if (this._userService.user.email) {
if (this._userService.currentUser.email) {
// disable email if it's already set
this.formGroup.get('email').disable();
}

View File

@ -1,6 +1,6 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from '@redaction/red-ui-http';
import { UserWrapper } from '@services/user.service';
@Component({
selector: 'redaction-add-edit-user-dialog',
@ -10,7 +10,7 @@ import { User } from '@redaction/red-ui-http';
export class AddEditUserDialogComponent {
resettingPassword = false;
constructor(public dialogRef: MatDialogRef<AddEditUserDialogComponent>, @Inject(MAT_DIALOG_DATA) public user: User) {}
constructor(readonly dialogRef: MatDialogRef<AddEditUserDialogComponent>, @Inject(MAT_DIALOG_DATA) readonly user: UserWrapper) {}
toggleResetPassword() {
this.resettingPassword = !this.resettingPassword;

View File

@ -1,7 +1,7 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { UserService } from '@services/user.service';
import { FormBuilder, Validators } from '@angular/forms';
import { UserControllerService } from '@redaction/red-ui-http';
import { UserService, UserWrapper } from '@services/user.service';
import { LoadingService } from '@services/loading.service';
@Component({
@ -10,8 +10,10 @@ import { LoadingService } from '@services/loading.service';
styleUrls: ['./reset-password.component.scss']
})
export class ResetPasswordComponent {
passwordForm: FormGroup;
@Input() user: User;
readonly passwordForm = this._formBuilder.group({
temporaryPassword: [null, Validators.required]
});
@Input() user: UserWrapper;
@Output() toggleResetPassword = new EventEmitter();
constructor(
@ -19,14 +21,10 @@ export class ResetPasswordComponent {
private readonly _userControllerService: UserControllerService,
private readonly _userService: UserService,
private readonly _loadingService: LoadingService
) {
this.passwordForm = this._formBuilder.group({
temporaryPassword: [null, Validators.required]
});
}
) {}
get userName() {
return this._userService.getNameForId(this.user.userId);
return this._userService.getNameForId(this.user.id);
}
async save() {
@ -37,7 +35,7 @@ export class ResetPasswordComponent {
password: this.passwordForm.get('temporaryPassword').value,
temporary: true
},
this.user.userId
this.user.id
)
.toPromise();
this._loadingService.stop();

View File

@ -1,10 +1,11 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { UserControllerService } from '@redaction/red-ui-http';
import { AdminDialogService } from '../../../services/admin-dialog.service';
import { LoadingService } from '@services/loading.service';
import { rolesTranslations } from '../../../../../translations/roles-translations';
import { IconButtonTypes } from '@iqser/common-ui';
import { UserWrapper } from '@services/user.service';
@Component({
selector: 'redaction-user-details',
@ -14,7 +15,7 @@ import { IconButtonTypes } from '@iqser/common-ui';
export class UserDetailsComponent implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
@Input() user: User;
@Input() user: UserWrapper;
@Output() toggleResetPassword = new EventEmitter();
@Output() closeDialog = new EventEmitter<any>();
userForm: FormGroup;
@ -99,7 +100,7 @@ export class UserDetailsComponent implements OnInit {
if (!this.user) {
await this._userControllerService.createUser(userData).toPromise();
} else {
await this._userControllerService.updateProfile(userData, this.user.userId).toPromise();
await this._userControllerService.updateProfile(userData, this.user.id).toPromise();
}
this.closeDialog.emit(true);

View File

@ -1,9 +1,10 @@
import { Component, Inject } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { UserControllerService } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { LoadingService } from '@services/loading.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { UserWrapper } from '@services/user.service';
@Component({
selector: 'redaction-confirm-delete-users-dialog',
@ -11,7 +12,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
styleUrls: ['./confirm-delete-users-dialog.component.scss']
})
export class ConfirmDeleteUsersDialogComponent {
checkboxes = [
readonly checkboxes = [
{ value: false, label: _('confirm-delete-users.impacted-dossiers') },
{ value: false, label: _('confirm-delete-users.impacted-documents') }
];
@ -22,12 +23,12 @@ export class ConfirmDeleteUsersDialogComponent {
private readonly _appStateService: AppStateService,
private readonly _loadingService: LoadingService,
private readonly _userControllerService: UserControllerService,
public dialogRef: MatDialogRef<ConfirmDeleteUsersDialogComponent>,
@Inject(MAT_DIALOG_DATA) public users: User[]
readonly dialogRef: MatDialogRef<ConfirmDeleteUsersDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly users: UserWrapper[]
) {
this.dossiersCount = this._appStateService.allDossiers.filter(dw => {
for (const user of this.users) {
if (dw.memberIds.indexOf(user.userId) !== -1) {
if (dw.memberIds.indexOf(user.id) !== -1) {
return true;
}
}
@ -42,7 +43,7 @@ export class ConfirmDeleteUsersDialogComponent {
async deleteUser() {
if (this.valid) {
this._loadingService.start();
await this._userControllerService.deleteUsers(this.users.map(u => u.userId)).toPromise();
await this._userControllerService.deleteUsers(this.users.map(u => u.id)).toPromise();
this.dialogRef.close(true);
} else {
this.showToast = true;

View File

@ -1,17 +1,15 @@
import { Component, EventEmitter, Injector, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { Field } from '../file-attributes-csv-import-dialog.component';
import { FileAttributeConfig } from '@redaction/red-ui-http';
import { CircleButtonTypes, FilterService, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { fileAttributeTypesTranslations } from '../../../translations/file-attribute-types-translations';
@Component({
selector: 'redaction-active-fields-listing',
templateUrl: './active-fields-listing.component.html',
styleUrls: ['./active-fields-listing.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class ActiveFieldsListingComponent extends BaseListingComponent<Field> implements OnChanges {
readonly circleButtonTypes = CircleButtonTypes;

View File

@ -8,10 +8,7 @@ import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';
import { Toaster } from '@services/toaster.service';
import { TranslateService } from '@ngx-translate/core';
import { FilterService, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
export interface Field {
@ -28,7 +25,7 @@ export interface Field {
@Component({
templateUrl: './file-attributes-csv-import-dialog.component.html',
styleUrls: ['./file-attributes-csv-import-dialog.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class FileAttributesCsvImportDialogComponent extends BaseListingComponent<Field> {
protected readonly _primaryKey = 'csvColumn';

View File

@ -19,7 +19,7 @@ export class SmtpAuthDialogComponent {
@Inject(MAT_DIALOG_DATA) public data: SMTPConfigurationModel
) {
this.authForm = this._formBuilder.group({
user: [data?.user || this._userService.user.email, [Validators.required]],
user: [data?.user || this._userService.currentUser.email, [Validators.required]],
password: [data?.password, Validators.required]
});
}

View File

@ -5,10 +5,8 @@ import { ActivatedRoute } from '@angular/router';
import { PermissionsService } from '@services/permissions.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { LoadingService } from '@services/loading.service';
import { CircleButtonTypes, FilterService, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { DefaultColorType } from '@models/default-color-key.model';
import { defaultColorsTranslations } from '../../translations/default-colors-translations';
@ -16,7 +14,7 @@ import { defaultColorsTranslations } from '../../translations/default-colors-tra
templateUrl: './default-colors-screen.component.html',
styleUrls: ['./default-colors-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class DefaultColorsScreenComponent
extends BaseListingComponent<{

View File

@ -9,10 +9,8 @@ import { ActivatedRoute } from '@angular/router';
import { TypeValueWrapper } from '@models/file/type-value.wrapper';
import { TranslateService } from '@ngx-translate/core';
import { LoadingService } from '@services/loading.service';
import { CircleButtonTypes, FilterService, IconButtonTypes, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes, IconButtonTypes } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { AdminDialogService } from '../../services/admin-dialog.service';
const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
@ -25,7 +23,7 @@ const toChartConfig = (dict: TypeValueWrapper): DoughnutChartConfig => ({
@Component({
templateUrl: './dictionary-listing-screen.component.html',
styleUrls: ['./dictionary-listing-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class DictionaryListingScreenComponent extends BaseListingComponent<TypeValueWrapper> implements OnInit {
readonly iconButtonTypes = IconButtonTypes;

View File

@ -1,13 +1,11 @@
import { Component, Injector, OnInit } from '@angular/core';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { DossierAttributeConfig } from '@redaction/red-ui-http';
import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { LoadingService } from '@services/loading.service';
import { CircleButtonTypes, FilterService, IconButtonTypes, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { CircleButtonTypes, IconButtonTypes } from '@iqser/common-ui';
import { PermissionsService } from '@services/permissions.service';
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
import { dossierAttributeTypesTranslations } from '../../translations/dossier-attribute-types-translations';
@ -15,7 +13,7 @@ import { dossierAttributeTypesTranslations } from '../../translations/dossier-at
@Component({
templateUrl: './dossier-attributes-listing-screen.component.html',
styleUrls: ['./dossier-attributes-listing-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class DossierAttributesListingScreenComponent extends BaseListingComponent<DossierAttributeConfig> implements OnInit {
readonly iconButtonTypes = IconButtonTypes;

View File

@ -6,16 +6,14 @@ import { AdminDialogService } from '../../services/admin-dialog.service';
import { DossierTemplateModelWrapper } from '@models/file/dossier-template-model.wrapper';
import { LoadingService } from '@services/loading.service';
import { DossierTemplateControllerService } from '@redaction/red-ui-http';
import { CircleButtonTypes, FilterService, IconButtonTypes, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes, IconButtonTypes } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
@Component({
templateUrl: './dossier-templates-listing-screen.component.html',
styleUrls: ['./dossier-templates-listing-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class DossierTemplatesListingScreenComponent extends BaseListingComponent<DossierTemplateModelWrapper> implements OnInit {
readonly iconButtonTypes = IconButtonTypes;

View File

@ -5,17 +5,15 @@ import { AppStateService } from '@state/app-state.service';
import { ActivatedRoute } from '@angular/router';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { LoadingService } from '@services/loading.service';
import { CircleButtonTypes, FilterService, IconButtonTypes, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes, IconButtonTypes } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { fileAttributeTypesTranslations } from '../../translations/file-attribute-types-translations';
@Component({
templateUrl: './file-attributes-listing-screen.component.html',
styleUrls: ['./file-attributes-listing-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class FileAttributesListingScreenComponent extends BaseListingComponent<FileAttributeConfig> implements OnInit, OnDestroy {
readonly iconButtonTypes = IconButtonTypes;

View File

@ -4,10 +4,8 @@ import { Dossier } from '@redaction/red-ui-http';
import { LoadingService } from '@services/loading.service';
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
import * as moment from 'moment';
import { CircleButtonTypes, FilterService, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { DossiersService } from '../../../dossier/services/dossiers.service';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { ConfirmationDialogInput, TitleColors } from '@shared/dialogs/confirmation-dialog/confirmation-dialog.component';
@ -20,7 +18,7 @@ const MINUTES_IN_AN_HOUR = 60;
templateUrl: './trash-screen.component.html',
styleUrls: ['./trash-screen.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [FilterService, SearchService, ScreenStateService, SortingService, DossiersService]
providers: [...DefaultListingServices, DossiersService]
})
export class TrashScreenComponent extends BaseListingComponent<Dossier> implements OnInit {
readonly circleButtonTypes = CircleButtonTypes;

View File

@ -87,11 +87,7 @@
</div>
<div>
<redaction-initials-avatar
[showYou]="true"
[userId]="user.userId"
[withName]="true"
></redaction-initials-avatar>
<redaction-initials-avatar [showYou]="true" [userId]="user.id" [withName]="true"></redaction-initials-avatar>
</div>
<div class="small-label">{{ user.email || '-' }}</div>
<div class="center">
@ -112,7 +108,7 @@
></iqser-circle-button>
<iqser-circle-button
(action)="openDeleteUsersDialog([user], $event)"
[disabled]="user.userId === userService.userId"
[disabled]="user.id === userService.currentUser.id"
[tooltip]="'user-listing.action.delete' | translate"
icon="red:trash"
[type]="circleButtonTypes.dark"

View File

@ -1,17 +1,15 @@
import { Component, Injector, OnInit, QueryList, ViewChildren } from '@angular/core';
import { PermissionsService } from '@services/permissions.service';
import { UserService } from '@services/user.service';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { UserService, UserWrapper } from '@services/user.service';
import { UserControllerService } from '@redaction/red-ui-http';
import { AdminDialogService } from '../../services/admin-dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/simple-doughnut-chart.component';
import { TranslateChartService } from '@services/translate-chart.service';
import { LoadingService } from '@services/loading.service';
import { InitialsAvatarComponent } from '@shared/components/initials-avatar/initials-avatar.component';
import { CircleButtonTypes, FilterService, IconButtonTypes, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes, IconButtonTypes } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { rolesTranslations } from '../../../../translations/roles-translations';
@ -19,16 +17,16 @@ import { rolesTranslations } from '../../../../translations/roles-translations';
@Component({
templateUrl: './user-listing-screen.component.html',
styleUrls: ['./user-listing-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class UserListingScreenComponent extends BaseListingComponent<User> implements OnInit {
export class UserListingScreenComponent extends BaseListingComponent<UserWrapper> implements OnInit {
readonly iconButtonTypes = IconButtonTypes;
readonly circleButtonTypes = CircleButtonTypes;
readonly canDeleteSelected$ = this._canDeleteSelected$;
collapsedDetails = false;
chartData: DoughnutChartConfig[] = [];
readonly translations = rolesTranslations;
protected readonly _primaryKey = 'userId';
protected readonly _primaryKey = 'id';
@ViewChildren(InitialsAvatarComponent)
private readonly _avatars: QueryList<InitialsAvatarComponent>;
@ -47,7 +45,7 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
get _canDeleteSelected$(): Observable<boolean> {
const entities$ = this.screenStateService.selectedEntities$;
return entities$.pipe(map(all => all.indexOf(this.userService.user) === -1));
return entities$.pipe(map(all => all.indexOf(this.userService.currentUser) === -1));
}
async ngOnInit() {
@ -55,19 +53,19 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
this.searchService.setSearchKey('searchKey');
}
openAddEditUserDialog($event: MouseEvent, user?: User) {
openAddEditUserDialog($event: MouseEvent, user?: UserWrapper) {
this._dialogService.openDialog('addEditUser', $event, user, async () => {
await this._loadData();
});
}
openDeleteUsersDialog(users: User[], $event?: MouseEvent) {
openDeleteUsersDialog(users: UserWrapper[], $event?: MouseEvent) {
this._dialogService.openDialog('deleteUsers', $event, users, async () => {
await this._loadData();
});
}
getDisplayRoles(user: User) {
getDisplayRoles(user: UserWrapper) {
const separator = ', ';
return (
user.roles.map(role => this._translateService.instant(this.translations[role])).join(separator) ||
@ -75,12 +73,12 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
);
}
async toggleActive(user: User) {
async toggleActive(user: UserWrapper) {
this._loadingService.start();
user.roles = this.userService.isActive(user) ? [] : ['RED_USER'];
await this._userControllerService.updateProfile(user, user.userId).toPromise();
await this._userControllerService.updateProfile(user, user.id).toPromise();
await this._loadData();
this._avatars.find(item => item.userId === user.userId).detectChanges();
this._avatars.find(item => item.userId === user.id).detectChanges();
}
bulkDelete() {
@ -108,22 +106,22 @@ export class UserListingScreenComponent extends BaseListingComponent<User> imple
label: 'REGULAR'
},
{
value: this.allEntities.filter(user => this.userService.isManager(user) && !this.userService.isAdmin(user)).length,
value: this.allEntities.filter(user => user.isManager && !user.isAdmin).length,
color: 'MANAGER',
label: 'RED_MANAGER'
},
{
value: this.allEntities.filter(user => this.userService.isManager(user) && this.userService.isAdmin(user)).length,
value: this.allEntities.filter(user => user.isManager && user.isAdmin).length,
color: 'MANAGER_ADMIN',
label: 'MANAGER_ADMIN'
},
{
value: this.allEntities.filter(user => this.userService.isUserAdmin(user) && !this.userService.isAdmin(user)).length,
value: this.allEntities.filter(user => user.isUserAdmin && !user.isAdmin).length,
color: 'USER_ADMIN',
label: 'RED_USER_ADMIN'
},
{
value: this.allEntities.filter(user => this.userService.isAdmin(user) && !this.userService.isManager(user)).length,
value: this.allEntities.filter(user => user.isAdmin && !user.isManager).length,
color: 'ADMIN',
label: 'RED_ADMIN'
}

View File

@ -16,7 +16,7 @@ export class RedRoleGuard implements CanActivate {
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return new Observable(obs => {
if (!this._userService.user.hasAnyREDRoles) {
if (!this._userService.currentUser.hasAnyREDRoles) {
this._router.navigate(['/auth-error']);
this._loadingService.stop();
obs.next(false);
@ -26,8 +26,8 @@ export class RedRoleGuard implements CanActivate {
// we have at least 1 RED Role -> if it's not user he must be admin
if (
this._userService.user.isUserAdmin &&
!this._userService.user.isAdmin &&
this._userService.currentUser.isUserAdmin &&
!this._userService.currentUser.isAdmin &&
!(state.url.startsWith('/main/admin/users') || state.url.startsWith('/main/my-profile'))
) {
this._router.navigate(['/main/admin/users']);

View File

@ -5,6 +5,7 @@ import { PermissionsService } from '@services/permissions.service';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { AnnotationActionsService } from '../../services/annotation-actions.service';
import { WebViewerInstance } from '@pdftron/webviewer';
import { UserService } from '@services/user.service';
export const AnnotationButtonTypes = {
dark: 'dark',
@ -28,9 +29,10 @@ export class AnnotationActionsComponent implements OnInit {
annotationPermissions: AnnotationPermissions;
constructor(
public appStateService: AppStateService,
public annotationActionsService: AnnotationActionsService,
private _permissionsService: PermissionsService
readonly appStateService: AppStateService,
readonly annotationActionsService: AnnotationActionsService,
private readonly _permissionsService: PermissionsService,
private readonly _userService: UserService
) {}
private _annotations: AnnotationWrapper[];
@ -89,7 +91,7 @@ export class AnnotationActionsComponent implements OnInit {
private _setPermissions() {
this.annotationPermissions = AnnotationPermissions.forUser(
this._permissionsService.isApprover(),
this._permissionsService.currentUser,
this._userService.currentUser,
this.annotations
);
}

View File

@ -39,7 +39,7 @@ export class CommentsComponent {
this.annotation.comments.push({
text: value,
id: commentResponse.commentId,
user: this._userService.userId
user: this._userService.currentUser.id
});
});
this.commentForm.reset();

View File

@ -15,7 +15,7 @@
<div class="all-caps-label" translate="dossier-details.owner"></div>
<div class="mt-12 d-flex">
<ng-container *ngIf="!editingOwner; else editOwner">
<redaction-initials-avatar [userId]="owner?.userId" [withName]="true" color="gray" size="large"></redaction-initials-avatar>
<redaction-initials-avatar [userId]="owner?.id" [withName]="true" color="gray" size="large"></redaction-initials-avatar>
<iqser-circle-button
(action)="editingOwner = true"

View File

@ -5,8 +5,7 @@ import { DoughnutChartConfig } from '@shared/components/simple-doughnut-chart/si
import { PermissionsService } from '@services/permissions.service';
import { TranslateChartService } from '@services/translate-chart.service';
import { StatusSorter } from '@utils/sorters/status-sorter';
import { UserService } from '@services/user.service';
import { User } from '@redaction/red-ui-http';
import { UserService, UserWrapper } from '@services/user.service';
import { Toaster } from '@services/toaster.service';
import { FilterService } from '@iqser/common-ui';
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
@ -20,7 +19,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
})
export class DossierDetailsComponent implements OnInit {
documentsChartData: DoughnutChartConfig[] = [];
owner: User;
owner: UserWrapper;
editingOwner = false;
@Input() dossierAttributes: DossierAttributeWithValue[];
@Output() openAssignDossierMembersDialog = new EventEmitter();
@ -81,13 +80,13 @@ export class DossierDetailsComponent implements OnInit {
this._changeDetectorRef.detectChanges();
}
async assignOwner(user: User | string) {
async assignOwner(user: UserWrapper | string) {
this.owner = typeof user === 'string' ? this._userService.getRedUserById(user) : user;
const dw = Object.assign({}, this.appStateService.activeDossier);
dw.dossier.ownerId = this.owner.userId;
dw.dossier.ownerId = this.owner.id;
await this.appStateService.createOrUpdateDossier(dw.dossier);
const ownerName = this._userService.getNameForId(this.owner.userId);
const ownerName = this._userService.getNameForId(this.owner.id);
const dossierName = this.appStateService.activeDossier.name;
const msg = 'Successfully assigned ' + ownerName + ' to dossier: ' + dossierName;
this._toaster.info(msg);

View File

@ -43,15 +43,15 @@ export class TeamMembersManagerComponent implements OnInit {
}
get ownersSelectOptions() {
return this.userService.managerUsers.map(m => m.userId);
return this.userService.managerUsers.map(m => m.id);
}
get membersSelectOptions() {
const searchQuery = this.searchForm.get('query').value;
return this.userService.eligibleUsers
.filter(user => this.userService.getNameForId(user.userId).toLowerCase().includes(searchQuery.toLowerCase()))
.filter(user => this.selectedOwnerId !== user.userId)
.map(user => user.userId);
.filter(user => this.userService.getNameForId(user.id).toLowerCase().includes(searchQuery.toLowerCase()))
.filter(user => this.selectedOwnerId !== user.id)
.map(user => user.id);
}
get valid(): boolean {

View File

@ -103,7 +103,7 @@ export class AssignReviewerApproverDialogComponent {
uniqueReviewers.add(file.currentReviewer);
}
}
let singleUser = uniqueReviewers.size === 1 ? uniqueReviewers.values().next().value : this.userService.userId;
let singleUser = uniqueReviewers.size === 1 ? uniqueReviewers.values().next().value : this.userService.currentUser.id;
singleUser = this.singleUsersSelectOptions.indexOf(singleUser) >= 0 ? singleUser : this.singleUsersSelectOptions[0];

View File

@ -16,10 +16,8 @@ import { DossiersDialogService } from '../../services/dossiers-dialog.service';
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
import { UserPreferenceService } from '@services/user-preference.service';
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
import { FilterService, NestedFilter, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { NestedFilter } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { TableColConfig } from '@shared/components/table-col-name/table-col-name.component';
import { workloadTranslations } from '../../translations/workload-translations';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
@ -36,14 +34,14 @@ const isLeavingScreen = event => event instanceof NavigationStart && event.url !
@Component({
templateUrl: './dossier-listing-screen.component.html',
styleUrls: ['./dossier-listing-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class DossierListingScreenComponent
extends BaseListingComponent<DossierWrapper>
implements OnInit, AfterViewInit, OnDestroy, OnAttach, OnDetach
{
readonly itemSize = 95;
buttonConfigs: ButtonConfig[] = [
readonly buttonConfigs: ButtonConfig[] = [
{
label: _('dossier-listing.add-new'),
action: () => this.openAddDossierDialog(),
@ -52,7 +50,7 @@ export class DossierListingScreenComponent
type: 'primary'
}
];
tableColConfigs: TableColConfig[] = [
readonly tableColConfigs: TableColConfig[] = [
{
label: _('dossier-listing.table-col-names.name'),
withSort: true,
@ -72,7 +70,7 @@ export class DossierListingScreenComponent
];
dossiersChartData: DoughnutChartConfig[] = [];
documentsChartData: DoughnutChartConfig[] = [];
protected readonly _primaryKey = 'dossierName';
protected readonly _primaryKey = 'numberOfMembers';
protected _tableHeaderLabel = _('dossier-listing.table-header.title');
private _lastScrollPosition: number;
@ViewChild('needsWorkTemplate', { read: TemplateRef, static: true })
@ -95,7 +93,7 @@ export class DossierListingScreenComponent
}
private get _userId() {
return this._userService.userId;
return this._userService.currentUser.id;
}
private get _activeDossiersCount(): number {

View File

@ -21,10 +21,8 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
import { AppConfigKey, AppConfigService } from '@app-config/app-config.service';
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
import { CircleButtonTypes, FilterService, keyChecker, NestedFilter, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { CircleButtonTypes, keyChecker, NestedFilter } from '@iqser/common-ui';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { LoadingService } from '@services/loading.service';
import { DossierAttributesService } from '@shared/services/controller-wrappers/dossier-attributes.service';
import { DossierAttributeWithValue } from '@models/dossier-attributes.model';
@ -38,7 +36,7 @@ import { annotationFilterChecker } from '@shared/components/filters/popup-filter
@Component({
templateUrl: './dossier-overview-screen.component.html',
styleUrls: ['./dossier-overview-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class DossierOverviewScreenComponent
extends BaseListingComponent<FileStatusWrapper>
@ -60,7 +58,7 @@ export class DossierOverviewScreenComponent
}
];
dossierAttributes: DossierAttributeWithValue[] = [];
tableColConfigs: TableColConfig[] = [
readonly tableColConfigs: TableColConfig[] = [
{
label: _('dossier-overview.table-col-names.name'),
withSort: true,
@ -125,7 +123,7 @@ export class DossierOverviewScreenComponent
}
get userId() {
return this._userService.userId;
return this._userService.currentUser.id;
}
get checkedRequiredFilters() {

View File

@ -17,12 +17,11 @@ import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { PermissionsService } from '@services/permissions.service';
import { timer } from 'rxjs';
import { UserPreferenceService } from '@services/user-preference.service';
import { UserService } from '@services/user.service';
import { UserService, UserWrapper } from '@services/user.service';
import {
FileManagementControllerService,
FileStatus,
StatusControllerService,
User,
UserPreferenceControllerService
} from '@redaction/red-ui-http';
import { PdfViewerDataService } from '../../services/pdf-viewer-data.service';
@ -34,10 +33,8 @@ import { OnAttach, OnDetach } from '@utils/custom-route-reuse.strategy';
import { LoadingService } from '@services/loading.service';
import { stampPDFPage } from '@utils/page-stamper';
import { TranslateService } from '@ngx-translate/core';
import { AutoUnsubscribeComponent, CircleButtonTypes } from '@iqser/common-ui';
import { AutoUnsubscribeComponent, CircleButtonTypes, NestedFilter, processFilters } from '@iqser/common-ui';
import { fileStatusTranslations } from '../../translations/file-status-translations';
import { NestedFilter } from '@iqser/common-ui';
import { processFilters } from '@iqser/common-ui';
import { handleFilterDelta } from '@shared/components/filters/popup-filter/utils/filter-utils';
const ALL_HOTKEY_ARRAY = ['Escape', 'F', 'f'];
@ -276,7 +273,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribeComponent impleme
const processStartTime = new Date().getTime();
this.annotationData = this.fileData.getAnnotations(
this.appStateService.dictionaryData[this.appStateService.activeDossier.dossierTemplateId],
this.permissionsService.currentUser,
this.userService.currentUser,
this.viewMode,
this.userPreferenceService.areDevFeaturesEnabled
);
@ -467,8 +464,8 @@ export class FilePreviewScreenComponent extends AutoUnsubscribeComponent impleme
});
}
async assignReviewer(user: User | string) {
const reviewerId = typeof user === 'string' ? user : user.userId;
async assignReviewer(user: UserWrapper | string) {
const reviewerId = typeof user === 'string' ? user : user.id;
const reviewerName = this.userService.getNameForId(reviewerId);
const { dossierId, fileId, filename } = this.fileData.fileStatus;

View File

@ -1,13 +1,11 @@
import { Component, Injector, OnDestroy } from '@angular/core';
import { BaseListingComponent } from '@shared/base/base-listing.component';
import { BaseListingComponent, DefaultListingServices } from '@shared/base/base-listing.component';
import { MatchedDocument, SearchControllerService, SearchResult } from '@redaction/red-ui-http';
import { BehaviorSubject, Observable } from 'rxjs';
import { debounceTime, map, skip, switchMap, tap } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { TableColConfig } from '@shared/components/table-col-name/table-col-name.component';
import { FilterService, keyChecker, SortingService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { ScreenStateService } from '@shared/services/screen-state.service';
import { keyChecker } from '@iqser/common-ui';
import { AppStateService } from '@state/app-state.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { LoadingService } from '@services/loading.service';
@ -36,7 +34,7 @@ interface SearchInput {
@Component({
templateUrl: './search-screen.component.html',
styleUrls: ['./search-screen.component.scss'],
providers: [FilterService, SearchService, ScreenStateService, SortingService]
providers: [...DefaultListingServices]
})
export class SearchScreenComponent extends BaseListingComponent<ListItem> implements OnDestroy {
readonly fileStatusTranslations = fileStatusTranslations;
@ -65,7 +63,7 @@ export class SearchScreenComponent extends BaseListingComponent<ListItem> implem
label: _('search-screen.cols.pages')
}
];
protected readonly _primaryKey = 'fileName';
protected readonly _primaryKey = 'filename';
protected readonly _tableHeaderLabel = _('search-screen.table-header');
constructor(

View File

@ -9,13 +9,15 @@ import { getFirstRelevantTextPart } from '@utils/functions';
import { AnnotationPermissions } from '@models/file/annotation.permissions';
import { DossiersDialogService } from './dossiers-dialog.service';
import { BASE_HREF } from '../../../tokens';
import { UserService } from '@services/user.service';
@Injectable()
export class AnnotationActionsService {
constructor(
@Inject(BASE_HREF) private readonly _baseHref: string,
private readonly _permissionsService: PermissionsService,
private readonly _ngZone: NgZone,
private readonly _userService: UserService,
private readonly _permissionsService: PermissionsService,
private readonly _manualAnnotationService: ManualAnnotationService,
private readonly _translateService: TranslateService,
private readonly _dialogService: DossiersDialogService
@ -89,7 +91,7 @@ export class AnnotationActionsService {
annotations.forEach(annotation => {
const permissions = AnnotationPermissions.forUser(
this._permissionsService.isApprover(),
this._permissionsService.currentUser,
this._userService.currentUser,
annotation
);
const value = permissions.canMarkTextOnlyAsFalsePositive ? annotation.value : this._getFalsePositiveText(annotation);
@ -132,9 +134,9 @@ export class AnnotationActionsService {
): Record<string, unknown>[] {
const availableActions = [];
const annotationPermissions = annotations.map(a => ({
annotation: a,
permissions: AnnotationPermissions.forUser(this._permissionsService.isApprover(), this._permissionsService.currentUser, a)
const annotationPermissions = annotations.map(annotation => ({
annotation,
permissions: AnnotationPermissions.forUser(this._permissionsService.isApprover(), this._userService.currentUser, annotation)
}));
const canRecategorizeImage = annotations.length === 1 && annotationPermissions[0].permissions.canRecategorizeImage;

View File

@ -154,7 +154,7 @@ export class FileActionService {
.setFileReviewerForList(
fileStatus.map(f => f.fileId),
this._appStateService.activeDossierId,
this._userService.userId
this._userService.currentUser.id
)
.toPromise();
await this._appStateService.reloadActiveDossierFiles();

View File

@ -1,21 +1,22 @@
import { Component, Injector, OnDestroy, ViewChild } from '@angular/core';
import { AutoUnsubscribeComponent, FilterService, SortingOrders, SortingService } from '@iqser/common-ui';
import { AutoUnsubscribeComponent, FilterService, KeysOf, SearchService, SortingOrders, SortingService } from '@iqser/common-ui';
import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
import { SearchService } from '../services/search.service';
import { ScreenStateService } from '../services/screen-state.service';
import { combineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
import { PermissionsService } from '@services/permissions.service';
export const DefaultListingServices = [FilterService, SearchService, ScreenStateService, SortingService] as const;
@Component({ template: '' })
export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent implements OnDestroy {
export abstract class BaseListingComponent<T extends object> extends AutoUnsubscribeComponent implements OnDestroy {
@ViewChild(CdkVirtualScrollViewport)
readonly scrollViewport: CdkVirtualScrollViewport;
readonly permissionsService = this._injector.get(PermissionsService);
readonly filterService = this._injector.get(FilterService);
readonly sortingService = this._injector.get(SortingService);
readonly searchService = this._injector.get(SearchService);
readonly sortingService = this._injector.get<SortingService<T>>(SortingService);
readonly searchService = this._injector.get<SearchService<T>>(SearchService);
readonly screenStateService = this._injector.get<ScreenStateService<T>>(ScreenStateService);
readonly sortedDisplayedEntities$ = this._sortedDisplayedEntities$;
@ -27,7 +28,7 @@ export abstract class BaseListingComponent<T> extends AutoUnsubscribeComponent i
* and in the default sorting and as the search field
* @protected
*/
protected abstract _primaryKey: string;
protected readonly _primaryKey: KeysOf<T>;
protected constructor(protected readonly _injector: Injector) {
super();

View File

@ -1,6 +1,5 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { User } from '@redaction/red-ui-http';
import { UserService } from '@services/user.service';
import { UserService, UserWrapper } from '@services/user.service';
@Component({
selector: 'redaction-assign-user-dropdown',
@ -9,25 +8,25 @@ import { UserService } from '@services/user.service';
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssignUserDropdownComponent {
oldUser: User | string;
@Input() options: (User | string)[];
@Output() save = new EventEmitter<User | string>();
oldUser: UserWrapper | string;
@Input() options: (UserWrapper | string)[];
@Output() save = new EventEmitter<UserWrapper | string>();
@Output() cancel = new EventEmitter<never>();
private _currentUser: User | string;
private _currentUser: UserWrapper | string;
constructor(private readonly _userService: UserService) {}
get value(): User | string {
get value(): UserWrapper | string {
return this._currentUser;
}
@Input()
set value(value: User | string) {
set value(value: UserWrapper | string) {
if (this.oldUser === undefined) this.oldUser = value;
this._currentUser = value;
}
getContext(user: User | string) {
return { userId: typeof user === 'string' ? user : user?.userId };
getContext(user: UserWrapper | string) {
return { userId: typeof user === 'string' ? user : user?.id };
}
}

View File

@ -1,5 +1,5 @@
<button [class.overlay]="showDot" mat-button>
<redaction-initials-avatar [userId]="user.userId" [withName]="true" size="small"></redaction-initials-avatar>
<redaction-initials-avatar [userId]="user.id" [withName]="true" size="small"></redaction-initials-avatar>
<mat-icon svgIcon="iqser:arrow-down"></mat-icon>
</button>
<div *ngIf="showDot" class="dot"></div>

View File

@ -1,8 +1,7 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy } from '@angular/core';
import { UserService } from '@services/user.service';
import { User } from '@redaction/red-ui-http';
import { UserService, UserWrapper } from '@services/user.service';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { AutoUnsubscribeComponent } from '@iqser/common-ui';
@Component({
selector: 'redaction-initials-avatar',
@ -10,7 +9,7 @@ import { Subscription } from 'rxjs';
styleUrls: ['./initials-avatar.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class InitialsAvatarComponent implements OnChanges, OnDestroy {
export class InitialsAvatarComponent extends AutoUnsubscribeComponent implements OnChanges, OnDestroy {
@Input() userId: string;
@Input() color = 'lightgray';
@Input() size: 'small' | 'large' = 'small';
@ -21,22 +20,21 @@ export class InitialsAvatarComponent implements OnChanges, OnDestroy {
displayName: string;
initials: string;
colorClass: string;
user: User;
private _subscription: Subscription;
user: UserWrapper;
constructor(
private readonly _userService: UserService,
private readonly _translateService: TranslateService,
private readonly _changeDetectorRef: ChangeDetectorRef
) {
this._subscription = _userService.usersReloaded$.subscribe(() => {
super();
this.addSubscription = _userService.usersReloaded$.subscribe(() => {
this.detectChanges();
});
}
get hasBorder(): boolean {
return !!this.user && !this._isCurrentUser && this._userService.isManager(this.user);
return !!this.user && !this._isCurrentUser && this.user.isManager;
}
get disabled(): boolean {
@ -52,11 +50,7 @@ export class InitialsAvatarComponent implements OnChanges, OnDestroy {
}
private get _isCurrentUser(): boolean {
return this._userService.userId === this.user?.userId;
}
ngOnDestroy() {
this._subscription?.unsubscribe();
return this._userService.currentUser.id === this.user?.id;
}
ngOnChanges(): void {

View File

@ -1,8 +1,7 @@
import { Component, Input, Optional } from '@angular/core';
import { ActionConfig } from '@shared/components/page-header/models/action-config.model';
import { ButtonConfig } from '@shared/components/page-header/models/button-config.model';
import { FilterService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { FilterService, SearchService } from '@iqser/common-ui';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { combineLatest, Observable, of } from 'rxjs';
import { SearchPosition, SearchPositions } from '@shared/components/page-header/models/search-positions.type';
@ -12,7 +11,7 @@ import { SearchPosition, SearchPositions } from '@shared/components/page-header/
templateUrl: './page-header.component.html',
styleUrls: ['./page-header.component.scss']
})
export class PageHeaderComponent {
export class PageHeaderComponent<T extends object> {
readonly searchPositions = SearchPositions;
@Input() pageLabel: string;
@ -26,7 +25,7 @@ export class PageHeaderComponent {
readonly filters$ = this.filterService?.filterGroups$.pipe(map(all => all.filter(f => f.icon)));
readonly showResetFilters$ = this._showResetFilters$;
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService) {}
constructor(@Optional() readonly filterService: FilterService, @Optional() readonly searchService: SearchService<T>) {}
get _showResetFilters$(): Observable<boolean> {
if (!this.filterService) return of(false);

View File

@ -1,5 +1,5 @@
import { Component, Input, Optional } from '@angular/core';
import { SortingService } from '@iqser/common-ui';
import { KeysOf, SortingService } from '@iqser/common-ui';
export interface TableColConfig {
readonly column?: string;
@ -16,8 +16,8 @@ export interface TableColConfig {
templateUrl: './table-col-name.component.html',
styleUrls: ['./table-col-name.component.scss']
})
export class TableColNameComponent {
@Input() column: string;
export class TableColNameComponent<T extends object> {
@Input() column: KeysOf<T>;
@Input() label: string;
@Input() withSort = false;
@Input() class: string;
@ -25,5 +25,5 @@ export class TableColNameComponent {
@Input() rightIcon: string;
@Input() rightIconTooltip: string;
constructor(@Optional() readonly sortingService: SortingService) {}
constructor(@Optional() readonly sortingService: SortingService<T>) {}
}

View File

@ -8,7 +8,7 @@ import { ScreenStateService } from '@shared/services/screen-state.service';
styleUrls: ['./table-header.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TableHeaderComponent<T> {
export class TableHeaderComponent<T extends object> {
@Input() tableHeaderLabel: string;
@Input() tableColConfigs: TableColConfig[];
@Input() hasEmptyColumn = false;

View File

@ -1,15 +1,13 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, pipe } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import { FilterService } from '@iqser/common-ui';
import { SearchService } from '@shared/services/search.service';
import { getFilteredEntities } from '@iqser/common-ui';
import { FilterService, getFilteredEntities, SearchService } from '@iqser/common-ui';
const toLengthValue = (entities: unknown[]) => entities?.length ?? 0;
const getLength = pipe(map(toLengthValue), distinctUntilChanged());
@Injectable()
export class ScreenStateService<T> {
export class ScreenStateService<T extends object> {
private readonly _allEntities$ = new BehaviorSubject<T[]>([]);
readonly allEntities$ = this._allEntities$.asObservable();
readonly allEntitiesLength$ = this._allEntities$.pipe(getLength);
@ -27,7 +25,7 @@ export class ScreenStateService<T> {
readonly areSomeEntitiesSelected$ = this._areSomeEntitiesSelected$;
readonly notAllEntitiesSelected$ = this._notAllEntitiesSelected$;
constructor(private readonly _filterService: FilterService, private readonly _searchService: SearchService) {
constructor(private readonly _filterService: FilterService, private readonly _searchService: SearchService<T>) {
// setInterval(() => {
// console.log('All entities subs: ', this._allEntities$.observers);
// console.log('Displayed entities subs: ', this._displayedEntities$.observers);

View File

@ -1,41 +0,0 @@
import { Injectable } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { startWith } from 'rxjs/operators';
@Injectable()
export class SearchService {
readonly searchForm = this._formBuilder.group({
query: ['']
});
readonly valueChanges$ = this.searchForm.get('query').valueChanges.pipe(startWith(''));
private _searchKey: string;
constructor(private readonly _formBuilder: FormBuilder) {}
get searchValue(): string {
return this.searchForm.get('query').value;
}
set searchValue(value: string) {
this.searchForm.patchValue({ query: value });
}
searchIn<T>(entities: T[]) {
if (!this._searchKey) return entities;
const searchValue = this.searchValue.toLowerCase();
return entities.filter(entity => this._searchField(entity).includes(searchValue));
}
setSearchKey(value: string): void {
this._searchKey = value;
}
reset(): void {
this.searchForm.reset({ query: '' }, { emitEvent: true });
}
private _searchField<T>(entity: T): string {
return entity[this._searchKey].toString().toLowerCase();
}
}

View File

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { AppStateService } from '@state/app-state.service';
import { UserService, UserWrapper } from './user.service';
import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { User, Comment } from '@redaction/red-ui-http';
import { Comment } from '@redaction/red-ui-http';
import { DossierWrapper } from '@state/model/dossier.wrapper';
@Injectable({
@ -11,10 +11,6 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
export class PermissionsService {
constructor(private readonly _appStateService: AppStateService, private readonly _userService: UserService) {}
get currentUser(): UserWrapper {
return this._userService.user;
}
private get _activeFile(): FileStatusWrapper | undefined {
return this._appStateService.activeFile;
}
@ -23,7 +19,7 @@ export class PermissionsService {
return this._appStateService.activeDossier;
}
isManager(user?: User) {
isManager(user?: UserWrapper) {
return this._userService.isManager(user);
}
@ -55,7 +51,7 @@ export class PermissionsService {
}
isFileReviewer(fileStatus = this._activeFile): boolean {
return fileStatus.currentReviewer === this._userService.userId;
return fileStatus.currentReviewer === this._userService.currentUser.id;
}
canDeleteFile(fileStatus = this._activeFile, dossier?: DossierWrapper): boolean {
@ -112,19 +108,19 @@ export class PermissionsService {
return fileStatus?.isUnderReview && this.isReviewerOrApprover(fileStatus);
}
isOwner(dossier = this._activeDossier, user = this.currentUser): boolean {
isOwner(dossier = this._activeDossier, user = this._userService.currentUser): boolean {
return dossier?.ownerId === user.id;
}
isApprover(dossier = this._activeDossier, user = this.currentUser): boolean {
isApprover(dossier = this._activeDossier, user = this._userService.currentUser): boolean {
return dossier?.approverIds.indexOf(user.id) >= 0;
}
isDossierReviewer(dossier = this._activeDossier, user = this.currentUser): boolean {
isDossierReviewer(dossier = this._activeDossier, user = this._userService.currentUser): boolean {
return this.isDossierMember(dossier, user) && !this.isApprover(dossier, user);
}
isDossierMember(dossier = this._activeDossier, user = this.currentUser): boolean {
isDossierMember(dossier = this._activeDossier, user = this._userService.currentUser): boolean {
return dossier?.memberIds.includes(user.id);
}
@ -156,18 +152,18 @@ export class PermissionsService {
}
canDeleteDossier(dossier = this._activeDossier): boolean {
return dossier.ownerId === this.currentUser.userId;
return dossier.ownerId === this._userService.currentUser.id;
}
isAdmin(user = this.currentUser): boolean {
isAdmin(user = this._userService.currentUser): boolean {
return user.isAdmin;
}
isUserAdmin(user = this.currentUser): boolean {
isUserAdmin(user = this._userService.currentUser): boolean {
return user.isUserAdmin;
}
isUser(user = this.currentUser): boolean {
isUser(user = this._userService.currentUser): boolean {
return user.isUser;
}
@ -190,6 +186,6 @@ export class PermissionsService {
}
canDeleteComment(comment: Comment, fileStatus = this._activeFile) {
return (comment.user === this._userService.userId || this.isApprover()) && !fileStatus.isApproved;
return (comment.user === this._userService.currentUser.id || this.isApprover()) && !fileStatus.isApproved;
}
}

View File

@ -1,10 +1,11 @@
import { EventEmitter, Inject, Injectable } from '@angular/core';
import { Inject, Injectable } from '@angular/core';
import { KeycloakService } from 'keycloak-angular';
import { KeycloakProfile } from 'keycloak-js';
import jwt_decode from 'jwt-decode';
import { User, UserControllerService } from '@redaction/red-ui-http';
import { wipeCaches } from '@redaction/red-cache';
import { BASE_HREF } from '../tokens';
import { Subject } from 'rxjs';
export interface ProfileModel {
username?: string;
@ -15,62 +16,27 @@ export interface ProfileModel {
}
export class UserWrapper {
name: string;
constructor(private readonly _user: KeycloakProfile | User, public roles: string[], public id: string) {}
constructor(private readonly _currentUser: KeycloakProfile | User, public roles: string[], public id: string) {
this.name = this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : this.username;
}
email = this._user.email;
username = this._user.username || this.email;
firstName = this._user.firstName;
lastName = this._user.lastName;
name = this.firstName && this.lastName ? `${this.firstName} ${this.lastName}` : this.username;
searchKey = this.name + this.username + this.email;
get username() {
return this._currentUser.username || this.email;
}
get userId() {
return this.id;
}
get email() {
return this._currentUser.email;
}
get firstName() {
return this._currentUser.firstName;
}
get lastName() {
return this._currentUser.lastName;
}
get searchKey() {
return this.name + this.username + this.email;
}
get isManager() {
return this.roles.indexOf('RED_MANAGER') >= 0;
}
get isUserAdmin() {
return this.roles.indexOf('RED_USER_ADMIN') >= 0;
}
get isUser() {
return this.roles.indexOf('RED_USER') >= 0;
}
get isAdmin() {
return this.roles.indexOf('RED_ADMIN') >= 0;
}
get hasAnyREDRoles() {
return this.isUser || this.isManager || this.isAdmin || this.isUserAdmin;
}
isManager = this.roles.indexOf('RED_MANAGER') >= 0;
isUserAdmin = this.roles.indexOf('RED_USER_ADMIN') >= 0;
isUser = this.roles.indexOf('RED_USER') >= 0;
isAdmin = this.roles.indexOf('RED_ADMIN') >= 0;
hasAnyREDRoles = this.isUser || this.isManager || this.isAdmin || this.isUserAdmin;
}
@Injectable({
providedIn: 'root'
})
export class UserService {
usersReloaded$: EventEmitter<any> = new EventEmitter<any>();
usersReloaded$ = new Subject();
private _currentUser: UserWrapper;
private _allUsers: UserWrapper[];
@ -86,31 +52,18 @@ export class UserService {
return this._allRedUsers;
}
get userId(): string {
return this._currentUser.id;
}
get managerUsers(): UserWrapper[] {
return this._allRedUsers.filter(u => u.roles.indexOf('RED_MANAGER') >= 0);
return this._allRedUsers.filter(user => user.isManager);
}
get eligibleUsers(): UserWrapper[] {
return this._allRedUsers.filter(u => u.roles.indexOf('RED_USER') >= 0 || u.roles.indexOf('RED_MANAGER') >= 0);
return this._allRedUsers.filter(user => user.isUser || user.isManager);
}
get user() {
get currentUser(): UserWrapper {
return this._currentUser;
}
private static _hasAnyRedRole(user: UserWrapper) {
return (
user.roles.indexOf('RED_USER') >= 0 ||
user.roles.indexOf('RED_MANAGER') >= 0 ||
user.roles.indexOf('RED_ADMIN') >= 0 ||
user.roles.indexOf('RED_USER_ADMIN') >= 0
);
}
logout() {
wipeCaches().then();
this._keycloakService.logout(window.location.origin + this._baseHref).then();
@ -123,14 +76,14 @@ export class UserService {
}
async loadAllUsers() {
let allUsers = [];
let allUsers: User[];
if (this._currentUser.isUserAdmin) {
allUsers = await this._userControllerService.getAllUsers().toPromise();
} else {
allUsers = await this._userControllerService.getUsers().toPromise();
}
this._allUsers = allUsers.map(user => new UserWrapper(user, user.roles, user.userId));
this._allRedUsers = this._allUsers.filter(u => UserService._hasAnyRedRole(u));
this._allRedUsers = this._allUsers.filter(user => user.hasAnyREDRoles);
this.usersReloaded$.next();
return this._allUsers;
}
@ -147,42 +100,43 @@ export class UserService {
}
getRedUserById(id: string) {
return this._allRedUsers.find(u => u.userId === id);
return this._allRedUsers.find(u => u.id === id);
}
getUserById(id: string) {
return this._allUsers ? this._allUsers.find(u => u.userId === id) : this.getRedUserById(id);
return this._allUsers ? this._allUsers.find(u => u.id === id) : this.getRedUserById(id);
}
getNameForId(userId: string) {
return this.getName(this.getUserById(userId));
getNameForId(userId: string): string | undefined {
const user = this.getUserById(userId);
return user ? this.getName(user) : undefined;
}
getName({ firstName, lastName, username }: User = {}) {
getName({ firstName, lastName, username }: UserWrapper) {
return firstName && lastName ? `${firstName} ${lastName}` : username;
}
isManager(user: User | UserWrapper = this.user): boolean {
isManager(user: UserWrapper = this._currentUser): boolean {
return user.roles.indexOf('RED_MANAGER') >= 0;
}
isUser(user: User | UserWrapper = this.user): boolean {
isUser(user: UserWrapper = this._currentUser): boolean {
return user.roles?.indexOf('RED_USER') >= 0;
}
isUserAdmin(user: User | UserWrapper = this.user): boolean {
isUserAdmin(user: UserWrapper = this._currentUser): boolean {
return user.roles?.indexOf('RED_USER_ADMIN') >= 0;
}
isAdmin(user: User | UserWrapper = this.user): boolean {
isAdmin(user: UserWrapper = this._currentUser): boolean {
return user.roles?.indexOf('RED_ADMIN') >= 0;
}
isActive(user: User | UserWrapper = this.user): boolean {
isActive(user: UserWrapper = this._currentUser): boolean {
return user.roles?.length > 0;
}
hasAnyRole(requiredRoles: string[], user: User | UserWrapper = this.user) {
hasAnyRole(requiredRoles: string[], user: UserWrapper = this._currentUser) {
if (requiredRoles?.length > 0) {
for (const role of requiredRoles) if (user.roles.indexOf(role) >= 0) return true;

View File

@ -14,11 +14,11 @@ export class AppStateGuard implements CanActivate {
) {}
async canActivate(route: ActivatedRouteSnapshot): Promise<boolean> {
if (this._userService.user.isUserAdmin) {
if (this._userService.currentUser.isUserAdmin) {
await this._userService.loadUsersIfNecessary();
}
if (this._userService.user.isUser || this._userService.user.isAdmin) {
if (this._userService.currentUser.isUser || this._userService.currentUser.isAdmin) {
await this._userService.loadUsersIfNecessary();
await this._appStateService.loadDossierTemplatesIfNecessary();
await this._appStateService.loadDictionaryDataIfNecessary();