refactor input with action

This commit is contained in:
Dan Percic 2021-08-21 00:42:58 +03:00 committed by Timo Bejan
parent 02a4b46476
commit eed3aecf4a
19 changed files with 102 additions and 150 deletions

View File

@ -90,9 +90,8 @@
</div> </div>
<div *ngIf="isSearchOpen" class="search-input-container"> <div *ngIf="isSearchOpen" class="search-input-container">
<redaction-input-with-action <redaction-input-with-action
[form]="searchService.searchForm" [(value)]="searchService.searchValue"
[placeholder]="'file-attributes-csv-import.search.placeholder' | translate" [placeholder]="'file-attributes-csv-import.search.placeholder' | translate"
type="search"
width="full" width="full"
></redaction-input-with-action> ></redaction-input-with-action>
</div> </div>

View File

@ -126,9 +126,8 @@
<div class="attributes-actions-container"> <div class="attributes-actions-container">
<redaction-input-with-action <redaction-input-with-action
[form]="searchService.searchForm" [(value)]="searchService.searchValue"
[placeholder]="'dictionary-listing.search' | translate" [placeholder]="'dictionary-listing.search' | translate"
type="search"
></redaction-input-with-action> ></redaction-input-with-action>
<div class="actions"> <div class="actions">
<iqser-icon-button <iqser-icon-button

View File

@ -97,9 +97,8 @@
<div class="attributes-actions-container"> <div class="attributes-actions-container">
<redaction-input-with-action <redaction-input-with-action
[form]="searchService.searchForm" [(value)]="searchService.searchValue"
[placeholder]="'dossier-attributes-listing.search' | translate" [placeholder]="'dossier-attributes-listing.search' | translate"
type="search"
></redaction-input-with-action> ></redaction-input-with-action>
<iqser-icon-button <iqser-icon-button

View File

@ -16,9 +16,8 @@
> >
<div class="actions flex-1"> <div class="actions flex-1">
<redaction-input-with-action <redaction-input-with-action
[form]="searchService.searchForm" [(value)]="searchService.searchValue"
[placeholder]="'dossier-templates-listing.search' | translate" [placeholder]="'dossier-templates-listing.search' | translate"
type="search"
></redaction-input-with-action> ></redaction-input-with-action>
<iqser-icon-button <iqser-icon-button

View File

@ -107,9 +107,8 @@
<div class="attributes-actions-container"> <div class="attributes-actions-container">
<redaction-input-with-action <redaction-input-with-action
[form]="searchService.searchForm" [(value)]="searchService.searchValue"
[placeholder]="'file-attributes-listing.search' | translate" [placeholder]="'file-attributes-listing.search' | translate"
type="search"
></redaction-input-with-action> ></redaction-input-with-action>
<input #fileInput (change)="importCSV($event.target['files'])" accept=".csv" class="csv-input" type="file" /> <input #fileInput (change)="importCSV($event.target['files'])" accept=".csv" class="csv-input" type="file" />

View File

@ -9,9 +9,8 @@
<div class="actions"> <div class="actions">
<redaction-input-with-action <redaction-input-with-action
[form]="searchService.searchForm" [(value)]="searchService.searchValue"
[placeholder]="'user-listing.search' | translate" [placeholder]="'user-listing.search' | translate"
type="search"
></redaction-input-with-action> ></redaction-input-with-action>
<iqser-icon-button <iqser-icon-button
(action)="openAddEditUserDialog($event)" (action)="openAddEditUserDialog($event)"

View File

@ -19,13 +19,11 @@
</div> </div>
<redaction-input-with-action <redaction-input-with-action
(action)="addComment()" (action)="addComment($event)"
*ngIf="permissionsService.canAddComment()" *ngIf="permissionsService.canAddComment()"
[form]="commentForm"
[placeholder]="'comments.add-comment' | translate" [placeholder]="'comments.add-comment' | translate"
autocomplete="off" autocomplete="off"
icon="red:collapse" icon="red:collapse"
type="action"
width="full" width="full"
></redaction-input-with-action> ></redaction-input-with-action>

View File

@ -1,11 +1,8 @@
import { ChangeDetectorRef, Component, HostBinding, Input } from '@angular/core'; import { Component, HostBinding, Input } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Comment } from '@redaction/red-ui-http'; import { Comment } from '@redaction/red-ui-http';
import { ManualAnnotationService } from '../../services/manual-annotation.service'; import { ManualAnnotationService } from '../../services/manual-annotation.service';
import { AnnotationWrapper } from '@models/file/annotation.wrapper'; import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { UserService } from '@services/user.service'; import { UserService } from '@services/user.service';
import { AppStateService } from '@state/app-state.service';
import { TranslateService } from '@ngx-translate/core';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
@Component({ @Component({
@ -15,49 +12,43 @@ import { PermissionsService } from '@services/permissions.service';
}) })
export class CommentsComponent { export class CommentsComponent {
@Input() annotation: AnnotationWrapper; @Input() annotation: AnnotationWrapper;
commentForm: FormGroup;
@HostBinding('class.hidden') private _hidden = true; @HostBinding('class.hidden') private _hidden = true;
constructor( constructor(
readonly translateService: TranslateService,
readonly permissionsService: PermissionsService, readonly permissionsService: PermissionsService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly _appStateService: AppStateService,
private readonly _formBuilder: FormBuilder,
private readonly _userService: UserService, private readonly _userService: UserService,
private readonly _manualAnnotationService: ManualAnnotationService private readonly _manualAnnotationService: ManualAnnotationService
) { ) {}
this.commentForm = this._formBuilder.group({
value: ['']
});
}
addComment(): void { addComment(value: string): void {
const value = this.commentForm.value.value; if (!value) return;
if (value) { this._manualAnnotationService
this._manualAnnotationService.addComment(value, this.annotation.id).subscribe(commentResponse => { .addComment(value, this.annotation.id)
.toPromise()
.then(commentResponse => {
this.annotation.comments.push({ this.annotation.comments.push({
text: value, text: value,
id: commentResponse.commentId, id: commentResponse.commentId,
user: this._userService.currentUser.id user: this._userService.currentUser.id
}); });
}); });
this.commentForm.reset();
}
} }
toggleExpandComments($event?: MouseEvent) { toggleExpandComments($event?: MouseEvent): void {
$event?.stopPropagation(); $event?.stopPropagation();
this._hidden = !this._hidden; this._hidden = !this._hidden;
} }
deleteComment(comment: Comment): void { deleteComment(comment: Comment): void {
this._manualAnnotationService.deleteComment(comment.id, this.annotation.id).subscribe(() => { this._manualAnnotationService
this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1); .deleteComment(comment.id, this.annotation.id)
if (!this.annotation.comments.length) { .toPromise()
this._hidden = true; .then(() => {
} this.annotation.comments.splice(this.annotation.comments.indexOf(comment), 1);
}); if (!this.annotation.comments.length) {
this._hidden = true;
}
});
} }
getOwnerName(comment: Comment): string { getOwnerName(comment: Comment): string {

View File

@ -26,6 +26,10 @@ import { map } from 'rxjs/operators';
const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape']; const COMMAND_KEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Escape'];
const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown']; const ALL_HOTKEY_ARRAY = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'];
interface Target extends EventTarget {
localName: string;
}
@Component({ @Component({
selector: 'redaction-file-workload', selector: 'redaction-file-workload',
templateUrl: './file-workload.component.html', templateUrl: './file-workload.component.html',
@ -155,6 +159,7 @@ export class FileWorkloadComponent {
} }
annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void { annotationClicked(annotation: AnnotationWrapper, $event: MouseEvent): void {
if (($event.target as Target).localName === 'input') return;
this.pagesPanelActive = false; this.pagesPanelActive = false;
this.logAnnotation(annotation); this.logAnnotation(annotation);
if (this.isSelected(annotation)) { if (this.isSelected(annotation)) {
@ -175,7 +180,7 @@ export class FileWorkloadComponent {
if ( if (
!ALL_HOTKEY_ARRAY.includes($event.key) || !ALL_HOTKEY_ARRAY.includes($event.key) ||
this.dialogRef?.getState() === MatDialogState.OPEN || this.dialogRef?.getState() === MatDialogState.OPEN ||
($event.target as any).localName === 'input' ($event.target as Target).localName === 'input'
) { ) {
return; return;
} }

View File

@ -1,12 +1,10 @@
<div *ngIf="permissionsService.canExcludePages()" class="exclude-pages-input-container"> <div *ngIf="permissionsService.canExcludePages()" class="exclude-pages-input-container">
<redaction-input-with-action <redaction-input-with-action
(action)="excludePagesRange()" (action)="excludePagesRange($event)"
[form]="excludePagesForm"
[hint]="'file-preview.tabs.exclude-pages.hint' | translate" [hint]="'file-preview.tabs.exclude-pages.hint' | translate"
[placeholder]="'file-preview.tabs.exclude-pages.input-placeholder' | translate" [placeholder]="'file-preview.tabs.exclude-pages.input-placeholder' | translate"
autocomplete="off" autocomplete="off"
icon="red:check" icon="red:check"
type="action"
width="full" width="full"
></redaction-input-with-action> ></redaction-input-with-action>
</div> </div>

View File

@ -1,11 +1,11 @@
import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core';
import { PermissionsService } from '@services/permissions.service'; import { PermissionsService } from '@services/permissions.service';
import { FormBuilder, FormGroup } from '@angular/forms';
import { PageRange, ReanalysisControllerService } from '@redaction/red-ui-http'; import { PageRange, ReanalysisControllerService } from '@redaction/red-ui-http';
import { Toaster } from '@iqser/common-ui'; import { Toaster } from '@iqser/common-ui';
import { LoadingService } from '@services/loading.service'; import { LoadingService } from '@services/loading.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker'; import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { FileStatusWrapper } from '@models/file/file-status.wrapper'; import { FileStatusWrapper } from '@models/file/file-status.wrapper';
import { InputWithActionComponent } from '@shared/components/input-with-action/input-with-action.component';
@Component({ @Component({
selector: 'redaction-page-exclusion', selector: 'redaction-page-exclusion',
@ -14,24 +14,19 @@ import { FileStatusWrapper } from '@models/file/file-status.wrapper';
}) })
export class PageExclusionComponent implements OnChanges { export class PageExclusionComponent implements OnChanges {
@Input() fileStatus: FileStatusWrapper; @Input() fileStatus: FileStatusWrapper;
@Output() actionPerformed = new EventEmitter<string>(); @Output() readonly actionPerformed = new EventEmitter<string>();
excludePagesForm: FormGroup;
excludedPagesRanges: PageRange[] = []; excludedPagesRanges: PageRange[] = [];
@ViewChild(InputWithActionComponent) private readonly _inputComponent: InputWithActionComponent;
constructor( constructor(
readonly permissionsService: PermissionsService, readonly permissionsService: PermissionsService,
private readonly _formBuilder: FormBuilder,
private readonly _reanalysisControllerService: ReanalysisControllerService, private readonly _reanalysisControllerService: ReanalysisControllerService,
private readonly _toaster: Toaster, private readonly _toaster: Toaster,
private readonly _loadingService: LoadingService private readonly _loadingService: LoadingService
) { ) {}
this.excludePagesForm = this._formBuilder.group({
value: ['']
});
}
ngOnChanges() { ngOnChanges(): void {
const excludedPages = (this.fileStatus?.excludedPages || []).sort((p1, p2) => p1 - p2); const excludedPages = (this.fileStatus?.excludedPages || []).sort((p1, p2) => p1 - p2);
this.excludedPagesRanges = excludedPages.reduce((ranges, page) => { this.excludedPagesRanges = excludedPages.reduce((ranges, page) => {
if (!ranges.length) { if (!ranges.length) {
@ -47,24 +42,21 @@ export class PageExclusionComponent implements OnChanges {
}, []); }, []);
} }
async excludePagesRange() { async excludePagesRange(value: string): Promise<void> {
this._loadingService.start(); this._loadingService.start();
try { try {
const pageRanges = this.excludePagesForm const pageRanges = value.split(',').map(range => {
.get('value') const splitted = range.split('-');
.value.split(',') const startPage = parseInt(splitted[0], 10);
.map(range => { const endPage = splitted.length > 1 ? parseInt(splitted[1], 10) : startPage;
const splitted = range.split('-'); if (!startPage || !endPage) {
const startPage = parseInt(splitted[0], 10); throw new Error();
const endPage = splitted.length > 1 ? parseInt(splitted[1], 10) : startPage; }
if (!startPage || !endPage) { return {
throw new Error(); startPage,
} endPage
return { };
startPage, });
endPage
};
});
await this._reanalysisControllerService await this._reanalysisControllerService
.excludePages( .excludePages(
{ {
@ -74,7 +66,7 @@ export class PageExclusionComponent implements OnChanges {
this.fileStatus.fileId this.fileStatus.fileId
) )
.toPromise(); .toPromise();
this.excludePagesForm.reset(); this._inputComponent.reset();
this.actionPerformed.emit('exclude-pages'); this.actionPerformed.emit('exclude-pages');
} catch (e) { } catch (e) {
this._toaster.error(_('file-preview.tabs.exclude-pages.error')); this._toaster.error(_('file-preview.tabs.exclude-pages.error'));
@ -82,7 +74,7 @@ export class PageExclusionComponent implements OnChanges {
} }
} }
async includePagesRange(range: PageRange) { async includePagesRange(range: PageRange): Promise<void> {
this._loadingService.start(); this._loadingService.start();
await this._reanalysisControllerService await this._reanalysisControllerService
.includePages( .includePages(
@ -93,7 +85,7 @@ export class PageExclusionComponent implements OnChanges {
this.fileStatus.fileId this.fileStatus.fileId
) )
.toPromise(); .toPromise();
this.excludePagesForm.reset(); this._inputComponent.reset();
this.actionPerformed.emit('exclude-pages'); this.actionPerformed.emit('exclude-pages');
} }
} }

View File

@ -36,11 +36,11 @@
<pre *ngIf="selectedReviewersList.length === 0" [innerHTML]="'assign-dossier-owner.dialog.no-reviewers' | translate" class="info"></pre> <pre *ngIf="selectedReviewersList.length === 0" [innerHTML]="'assign-dossier-owner.dialog.no-reviewers' | translate" class="info"></pre>
<redaction-input-with-action <redaction-input-with-action
[form]="searchForm" [(value)]="searchQuery"
(valueChange)="setMembersSelectOptions()"
[placeholder]="'assign-dossier-owner.dialog.search' | translate" [placeholder]="'assign-dossier-owner.dialog.search' | translate"
[width]="560" [width]="560"
class="search-container" class="search-container"
type="search"
></redaction-input-with-action> ></redaction-input-with-action>
<div class="members-list"> <div class="members-list">

View File

@ -13,12 +13,12 @@ import { DossierWrapper } from '@state/model/dossier.wrapper';
}) })
export class TeamMembersManagerComponent implements OnInit { export class TeamMembersManagerComponent implements OnInit {
teamForm: FormGroup; teamForm: FormGroup;
searchForm: FormGroup; searchQuery = '';
@Input() dossierWrapper: DossierWrapper; @Input() dossierWrapper: DossierWrapper;
@Output() save = new EventEmitter<Dossier>(); @Output() readonly save = new EventEmitter<Dossier>();
ownersSelectOptions: string[] = this.userService.managerUsers.map(m => m.id); readonly ownersSelectOptions = this.userService.managerUsers.map(m => m.id);
selectedReviewersList: string[] = []; selectedReviewersList: string[] = [];
membersSelectOptions: string[] = []; membersSelectOptions: string[] = [];
changed = false; changed = false;
@ -137,10 +137,9 @@ export class TeamMembersManagerComponent implements OnInit {
this.selectedReviewersList = this.selectedMembersList.filter(m => this.selectedApproversList.indexOf(m) === -1); this.selectedReviewersList = this.selectedMembersList.filter(m => this.selectedApproversList.indexOf(m) === -1);
} }
private _setMembersSelectOptions() { setMembersSelectOptions(): void {
const searchQuery = this.searchForm.get('query').value;
this.membersSelectOptions = this.userService.eligibleUsers this.membersSelectOptions = this.userService.eligibleUsers
.filter(user => this.userService.getNameForId(user.id).toLowerCase().includes(searchQuery.toLowerCase())) .filter(user => this.userService.getNameForId(user.id).toLowerCase().includes(this.searchQuery.toLowerCase()))
.filter(user => this.selectedOwnerId !== user.id) .filter(user => this.selectedOwnerId !== user.id)
.map(user => user.id); .map(user => user.id);
} }
@ -151,9 +150,6 @@ export class TeamMembersManagerComponent implements OnInit {
approvers: [[...this.dossierWrapper?.approverIds]], approvers: [[...this.dossierWrapper?.approverIds]],
members: [[...this.dossierWrapper?.memberIds]] members: [[...this.dossierWrapper?.memberIds]]
}); });
this.searchForm = this._formBuilder.group({
query: ['']
});
this.teamForm.get('owner').valueChanges.subscribe(owner => { this.teamForm.get('owner').valueChanges.subscribe(owner => {
if (!this.isApprover(owner)) { if (!this.isApprover(owner)) {
this.toggleApprover(owner); this.toggleApprover(owner);
@ -161,15 +157,12 @@ export class TeamMembersManagerComponent implements OnInit {
// If it is an approver, it is already a member, no need to check // If it is an approver, it is already a member, no need to check
this._updateLists(); this._updateLists();
}); });
this.searchForm.get('query').valueChanges.subscribe(() => {
this._setMembersSelectOptions();
});
this._updateLists(); this._updateLists();
} }
private _updateLists() { private _updateLists() {
this._setSelectedReviewersList(); this._setSelectedReviewersList();
this._setMembersSelectOptions(); this.setMembersSelectOptions();
this._updateChanged(); this._updateChanged();
} }

View File

@ -85,10 +85,7 @@ export class SearchScreenComponent extends ListingComponent<ListItem> implements
) )
.subscribe(mappedValue => this._updateValues(mappedValue)); .subscribe(mappedValue => this._updateValues(mappedValue));
this.addSubscription = this.searchService.searchForm this.addSubscription = this.searchService.valueChanges$.pipe(debounceTime(300)).subscribe(value => this.updateNavigation(value));
.get('query')
.valueChanges.pipe(debounceTime(300))
.subscribe(value => this.updateNavigation(value));
this.addSubscription = this.filterService.filterGroups$.pipe(skip(1)).subscribe(group => { this.addSubscription = this.filterService.filterGroups$.pipe(skip(1)).subscribe(group => {
const dossierIds = group[0].filters.filter(v => v.checked).map(v => v.key); const dossierIds = group[0].filters.filter(v => v.checked).map(v => v.key);

View File

@ -14,8 +14,6 @@ export class AnnotationIconComponent implements OnChanges {
@ViewChild('icon', { static: true }) icon: ElementRef; @ViewChild('icon', { static: true }) icon: ElementRef;
constructor() {}
get isHint() { get isHint() {
return this.type === 'circle' || this.dictType?.type === 'hint'; return this.type === 'circle' || this.dictType?.type === 'hint';
} }

View File

@ -1,34 +1,25 @@
<form [autocomplete]="autocomplete" [formGroup]="form"> <div [style.max-width]="computedWidth" [style.width]="computedWidth" class="red-input-group">
<div [style.max-width]="computedWidth" [style.width]="computedWidth" class="red-input-group"> <input
<input [(ngModel)]="value"
[formControlName]="formControlName" (ngModelChange)="valueChange.emit($event)"
[name]="formControlName" [autocomplete]="autocomplete"
[placeholder]="placeholder" [placeholder]="placeholder"
class="with-icon mt-0" class="with-icon mt-0"
type="text" type="text"
/> />
<span *ngIf="hint" class="hint">{{ hint }}</span> <span *ngIf="hint" class="hint">{{ hint }}</span>
<!-- Search--> <mat-icon *ngIf="isSearch && !hasContent" class="icon-right" svgIcon="red:search"></mat-icon>
<mat-icon *ngIf="type === 'search' && !hasContent" class="icon-right" svgIcon="red:search"></mat-icon>
<iqser-circle-button <iqser-circle-button (action)="reset()" *ngIf="isSearch && hasContent" [size]="25" icon="red:close"></iqser-circle-button>
(action)="clearContent()"
*ngIf="type === 'search' && hasContent"
[disabled]="form.invalid"
[size]="25"
icon="red:close"
></iqser-circle-button>
<!-- Submit--> <iqser-circle-button
<iqser-circle-button (action)="executeAction($event)"
(action)="executeAction($event)" *ngIf="!isSearch"
*ngIf="type === 'action'" [disabled]="!hasContent"
[disabled]="!hasContent" [icon]="icon"
[icon]="icon" [isSubmit]="true"
[isSubmit]="true" [size]="25"
[size]="25" ></iqser-circle-button>
></iqser-circle-button> </div>
</div>
</form>

View File

@ -1,5 +1,4 @@
import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({ @Component({
selector: 'redaction-input-with-action', selector: 'redaction-input-with-action',
@ -7,35 +6,36 @@ import { FormGroup } from '@angular/forms';
styleUrls: ['./input-with-action.component.scss'] styleUrls: ['./input-with-action.component.scss']
}) })
export class InputWithActionComponent { export class InputWithActionComponent {
@Input() form: FormGroup;
@Input() placeholder: string; @Input() placeholder: string;
@Input() hint: string; @Input() hint: string;
@Input() width: number | 'full' = 250; @Input() width: number | 'full' = 250;
@Input() type: 'search' | 'action';
@Input() icon: string; @Input() icon: string;
@Input() autocomplete: 'on' | 'off' = 'on'; @Input() autocomplete: 'on' | 'off' = 'on';
@Output() action = new EventEmitter<any>(); @Input() value = '';
@Output() readonly action = new EventEmitter<string>();
@Output() readonly valueChange = new EventEmitter<string>();
get formControlName(): 'query' | 'value' { get hasContent(): boolean {
return this.type === 'search' ? 'query' : 'value'; return !!this.value.length;
} }
get hasContent() { get computedWidth(): string {
return !!this.form.get(this.formControlName).value?.length;
}
get computedWidth() {
return this.width === 'full' ? '100%' : `${this.width}px`; return this.width === 'full' ? '100%' : `${this.width}px`;
} }
clearContent() { reset(): void {
this.form.patchValue({ query: '' }, { emitEvent: true }); this.value = '';
} }
executeAction($event?: MouseEvent) { get isSearch(): boolean {
$event?.stopPropagation(); return this.action.observers.length === 0;
}
executeAction($event: MouseEvent): void {
$event.stopPropagation();
if (this.hasContent) { if (this.hasContent) {
this.action.emit(); this.action.emit(this.value);
} }
} }
} }

View File

@ -53,11 +53,10 @@
<ng-template #searchBar> <ng-template #searchBar>
<redaction-input-with-action <redaction-input-with-action
[(value)]="searchService.searchValue"
*ngIf="searchPlaceholder && searchService" *ngIf="searchPlaceholder && searchService"
[form]="searchService.searchForm"
[placeholder]="searchPlaceholder" [placeholder]="searchPlaceholder"
[width]="searchWidth" [width]="searchWidth"
[class.mr-8]="searchPosition === searchPositions.beforeFilters" [class.mr-8]="searchPosition === searchPositions.beforeFilters"
type="search"
></redaction-input-with-action> ></redaction-input-with-action>
</ng-template> </ng-template>

View File

@ -107,11 +107,7 @@ export class UserService {
getNameForId(userId: string): string | undefined { getNameForId(userId: string): string | undefined {
const user = this.getUserById(userId); const user = this.getUserById(userId);
return user ? this.getName(user) : undefined; return user ? user.name : undefined;
}
getName({ firstName, lastName, username }: UserWrapper) {
return firstName && lastName ? `${firstName} ${lastName}` : username;
} }
isManager(user: UserWrapper = this._currentUser): boolean { isManager(user: UserWrapper = this._currentUser): boolean {