UI: fixed alarm tables and alarm details breaking if deleted user was assigned or left comments

This commit is contained in:
rusikv 2024-09-19 18:52:23 +03:00
parent 9039607780
commit 7df466d0db
14 changed files with 200 additions and 310 deletions

View File

@ -31,6 +31,7 @@ import {
hashCode, hashCode,
isDefined, isDefined,
isDefinedAndNotNull, isDefinedAndNotNull,
isNotEmptyStr,
isString, isString,
isUndefined, isUndefined,
objToBase64, objToBase64,
@ -41,7 +42,13 @@ import { TranslateService } from '@ngx-translate/core';
import { customTranslationsPrefix, i18nPrefix } from '@app/shared/models/constants'; import { customTranslationsPrefix, i18nPrefix } from '@app/shared/models/constants';
import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models'; import { DataKey, Datasource, DatasourceType, KeyInfo } from '@shared/models/widget.models';
import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models'; import { DataKeyType } from '@app/shared/models/telemetry/telemetry.models';
import { alarmFields, alarmSeverityTranslations, alarmStatusTranslations } from '@shared/models/alarm.models'; import {
AlarmAssignee,
AlarmCommentInfo,
alarmFields,
alarmSeverityTranslations,
alarmStatusTranslations
} from '@shared/models/alarm.models';
import { materialColors } from '@app/shared/models/material.models'; import { materialColors } from '@app/shared/models/material.models';
import { WidgetInfo } from '@home/models/widget-component.models'; import { WidgetInfo } from '@home/models/widget-component.models';
import jsonSchemaDefaults from 'json-schema-defaults'; import jsonSchemaDefaults from 'json-schema-defaults';
@ -374,6 +381,39 @@ export class UtilsService {
}); });
} }
public getUserDisplayName(alarmAssignee: AlarmAssignee | AlarmCommentInfo) {
let displayName = '';
if (isNotEmptyStr(alarmAssignee.firstName) || isNotEmptyStr(alarmAssignee.lastName)) {
if (alarmAssignee.firstName) {
displayName += alarmAssignee.firstName;
}
if (alarmAssignee.lastName) {
if (displayName.length > 0) {
displayName += ' ';
}
displayName += alarmAssignee.lastName;
}
} else {
displayName = alarmAssignee.email;
}
return displayName;
}
getUserInitials(alarmAssignee: AlarmAssignee): string {
let initials = '';
if (isNotEmptyStr(alarmAssignee.firstName) || isNotEmptyStr(alarmAssignee.lastName)) {
if (alarmAssignee.firstName) {
initials += alarmAssignee.firstName.charAt(0);
}
if (alarmAssignee.lastName) {
initials += alarmAssignee.lastName.charAt(0);
}
} else {
initials += alarmAssignee.email.charAt(0);
}
return initials.toUpperCase();
}
public stringToHslColor(str: string, saturationPercentage: number, lightnessPercentage: number): string { public stringToHslColor(str: string, saturationPercentage: number, lightnessPercentage: number): string {
if (str && str.length) { if (str && str.length) {
const hue = hashCode(str) % 360; const hue = hashCode(str) % 360;

View File

@ -110,9 +110,7 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
this.filteredUsers = this.selectUserFormGroup.get('user').valueChanges this.filteredUsers = this.selectUserFormGroup.get('user').valueChanges
.pipe( .pipe(
debounceTime(150), debounceTime(150),
map(value => { map(value => value ? (typeof value === 'string' ? value : '') : ''),
return value ? (typeof value === 'string' ? value : '') : ''
}),
distinctUntilChanged(), distinctUntilChanged(),
switchMap(name => this.fetchUsers(name)), switchMap(name => this.fetchUsers(name)),
share(), share(),
@ -123,7 +121,7 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
ngAfterViewInit() { ngAfterViewInit() {
setTimeout(() => { setTimeout(() => {
this.userInput.nativeElement.focus(); this.userInput.nativeElement.focus();
}, 0) }, 0);
} }
ngOnDestroy() { ngOnDestroy() {
@ -149,7 +147,7 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
this.alarmService.assignAlarm(this.alarmId, user.id.id, {ignoreLoading: true}).subscribe( this.alarmService.assignAlarm(this.alarmId, user.id.id, {ignoreLoading: true}).subscribe(
() => { () => {
this.reassigned = true; this.reassigned = true;
this.overlayRef.dispose() this.overlayRef.dispose();
}); });
} }
@ -157,7 +155,7 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
this.alarmService.unassignAlarm(this.alarmId, {ignoreLoading: true}).subscribe( this.alarmService.unassignAlarm(this.alarmId, {ignoreLoading: true}).subscribe(
() => { () => {
this.reassigned = true; this.reassigned = true;
this.overlayRef.dispose() this.overlayRef.dispose();
}); });
} }
@ -170,9 +168,7 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
return this.userService.getUsersForAssign(this.alarmId, pageLink, {ignoreLoading: true}) return this.userService.getUsersForAssign(this.alarmId, pageLink, {ignoreLoading: true})
.pipe( .pipe(
catchError(() => of(emptyPageData<UserEmailInfo>())), catchError(() => of(emptyPageData<UserEmailInfo>())),
map(pageData => { map(pageData => pageData.data)
return pageData.data;
})
); );
} }
@ -191,42 +187,11 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
}, 0); }, 0);
} }
getUserDisplayName(entity: User) { getUserInitials(entity: UserEmailInfo): string {
let displayName = ''; return this.utilsService.getUserInitials(entity);
if ((entity.firstName && entity.firstName.length > 0) ||
(entity.lastName && entity.lastName.length > 0)) {
if (entity.firstName) {
displayName += entity.firstName;
}
if (entity.lastName) {
if (displayName.length > 0) {
displayName += ' ';
}
displayName += entity.lastName;
}
} else {
displayName = entity.email;
}
return displayName;
} }
getUserInitials(entity: User): string { getFullName(entity: UserEmailInfo): string {
let initials = '';
if (entity.firstName && entity.firstName.length ||
entity.lastName && entity.lastName.length) {
if (entity.firstName) {
initials += entity.firstName.charAt(0);
}
if (entity.lastName) {
initials += entity.lastName.charAt(0);
}
} else {
initials += entity.email.charAt(0);
}
return initials.toUpperCase();
}
getFullName(entity: User): string {
let fullName = ''; let fullName = '';
if ((entity.firstName && entity.firstName.length > 0) || if ((entity.firstName && entity.firstName.length > 0) ||
(entity.lastName && entity.lastName.length > 0)) { (entity.lastName && entity.lastName.length > 0)) {
@ -243,8 +208,8 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe
return fullName; return fullName;
} }
getAvatarBgColor(entity: User) { getAvatarBgColor(entity: UserEmailInfo) {
return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); return this.utilsService.stringToHslColor(this.utilsService.getUserDisplayName(entity), 40, 60);
} }
} }

View File

@ -165,39 +165,8 @@ export class AlarmAssigneeSelectPanelComponent implements OnInit, AfterViewInit
}, 0); }, 0);
} }
getUserDisplayName(entity: UserEmailInfo) {
let displayName = '';
if ((entity.firstName && entity.firstName.length > 0) ||
(entity.lastName && entity.lastName.length > 0)) {
if (entity.firstName) {
displayName += entity.firstName;
}
if (entity.lastName) {
if (displayName.length > 0) {
displayName += ' ';
}
displayName += entity.lastName;
}
} else {
displayName = entity.email;
}
return displayName;
}
getUserInitials(entity: UserEmailInfo): string { getUserInitials(entity: UserEmailInfo): string {
let initials = ''; return this.utilsService.getUserInitials(entity);
if (entity.firstName && entity.firstName.length ||
entity.lastName && entity.lastName.length) {
if (entity.firstName) {
initials += entity.firstName.charAt(0);
}
if (entity.lastName) {
initials += entity.lastName.charAt(0);
}
} else {
initials += entity.email.charAt(0);
}
return initials.toUpperCase();
} }
getFullName(entity: UserEmailInfo): string { getFullName(entity: UserEmailInfo): string {
@ -218,7 +187,7 @@ export class AlarmAssigneeSelectPanelComponent implements OnInit, AfterViewInit
} }
getAvatarBgColor(entity: UserEmailInfo) { getAvatarBgColor(entity: UserEmailInfo) {
return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); return this.utilsService.stringToHslColor(this.utilsService.getUserDisplayName(entity), 40, 60);
} }
} }

View File

@ -20,10 +20,12 @@
subscriptSizing="dynamic"> subscriptSizing="dynamic">
<mat-label translate>alarm.assignee</mat-label> <mat-label translate>alarm.assignee</mat-label>
<input matInput readonly [value]="getAssignee()"> <input matInput readonly [value]="getAssignee()">
<span *ngIf="alarm?.assigneeId" matPrefix class="user-avatar" <span *ngIf="userAssigned; else unassigned" matPrefix class="user-avatar"
[style.backgroundColor]="getAvatarBgColor(alarm.assignee)"> [style.background-color]="getAvatarBgColor(alarm.assignee)">
{{ getUserInitials(alarm.assignee) }} {{ getUserInitials(alarm.assignee) }}
</span> </span>
<mat-icon *ngIf="!alarm?.assigneeId" matPrefix class="unassigned-icon">account_circle</mat-icon> <ng-template #unassigned>
<mat-icon matPrefix class="material-icons unassigned-icon">{{ alarm?.assigneeId ? 'no_accounts' : 'account_circle' }}</mat-icon>
</ng-template>
<mat-icon matSuffix>arrow_drop_down</mat-icon> <mat-icon matSuffix>arrow_drop_down</mat-icon>
</mat-form-field> </mat-form-field>

View File

@ -25,6 +25,7 @@ import {
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal'; import { ComponentPortal } from '@angular/cdk/portal';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { isNotEmptyStr } from '@core/utils';
@Component({ @Component({
selector: 'tb-alarm-assignee', selector: 'tb-alarm-assignee',
@ -41,6 +42,8 @@ export class AlarmAssigneeComponent {
@Output() @Output()
alarmReassigned = new EventEmitter<boolean>(); alarmReassigned = new EventEmitter<boolean>();
userAssigned: boolean;
constructor(private utilsService: UtilsService, constructor(private utilsService: UtilsService,
private overlay: Overlay, private overlay: Overlay,
private viewContainerRef: ViewContainerRef, private viewContainerRef: ViewContainerRef,
@ -49,68 +52,22 @@ export class AlarmAssigneeComponent {
getAssignee() { getAssignee() {
if (this.alarm) { if (this.alarm) {
if (this.alarm.assignee) { this.userAssigned = this.alarm.assigneeId && ((isNotEmptyStr(this.alarm.assignee?.firstName) ||
return this.getUserDisplayName(this.alarm.assignee); isNotEmptyStr(this.alarm.assignee?.lastName)) || isNotEmptyStr(this.alarm.assignee?.email));
if (this.userAssigned) {
return this.utilsService.getUserDisplayName(this.alarm.assignee);
} else { } else {
return this.translateService.instant('alarm.unassigned'); return this.translateService.instant(this.alarm.assigneeId ? 'alarm.user-deleted' : 'alarm.unassigned');
} }
} }
} }
getUserDisplayName(entity: AlarmAssignee) { getUserInitials(alarmAssignee: AlarmAssignee): string {
let displayName = ''; return this.utilsService.getUserInitials(alarmAssignee);
if ((entity.firstName && entity.firstName.length > 0) ||
(entity.lastName && entity.lastName.length > 0)) {
if (entity.firstName) {
displayName += entity.firstName;
}
if (entity.lastName) {
if (displayName.length > 0) {
displayName += ' ';
}
displayName += entity.lastName;
}
} else {
displayName = entity.email;
}
return displayName;
}
getUserInitials(entity: AlarmAssignee): string {
let initials = '';
if (entity.firstName && entity.firstName.length ||
entity.lastName && entity.lastName.length) {
if (entity.firstName) {
initials += entity.firstName.charAt(0);
}
if (entity.lastName) {
initials += entity.lastName.charAt(0);
}
} else {
initials += entity.email.charAt(0);
}
return initials.toUpperCase();
}
getFullName(entity: AlarmAssignee): string {
let fullName = '';
if ((entity.firstName && entity.firstName.length > 0) ||
(entity.lastName && entity.lastName.length > 0)) {
if (entity.firstName) {
fullName += entity.firstName;
}
if (entity.lastName) {
if (fullName.length > 0) {
fullName += ' ';
}
fullName += entity.lastName;
}
}
return fullName;
} }
getAvatarBgColor(entity: AlarmAssignee) { getAvatarBgColor(entity: AlarmAssignee) {
return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60); return this.utilsService.stringToHslColor(this.utilsService.getUserDisplayName(entity), 40, 60);
} }
openAlarmAssigneePanel($event: Event, alarm: AlarmInfo) { openAlarmAssigneePanel($event: Event, alarm: AlarmInfo) {

View File

@ -69,10 +69,14 @@
*ngIf="!displayDataElement.edit; else commentEditing" *ngIf="!displayDataElement.edit; else commentEditing"
(mouseenter)="onCommentMouseEnter(displayDataElement.commentId, i)" (mouseenter)="onCommentMouseEnter(displayDataElement.commentId, i)"
(mouseleave)="onCommentMouseLeave(i)"> (mouseleave)="onCommentMouseLeave(i)">
<div class="user-avatar" fxLayout="row" fxLayoutAlign="center center" fxFlexAlign="start" fxHide.xs <div *ngIf="displayDataElement.userExists; else userDeleted" class="user-avatar"
fxLayout="row" fxLayoutAlign="center center" fxFlexAlign="start" fxHide.xs
[style.background-color]="displayDataElement.avatarBgColor"> [style.background-color]="displayDataElement.avatarBgColor">
{{ getUserInitials(displayDataElement.displayName) }} {{ getUserInitials(displayDataElement.displayName) }}
</div> </div>
<ng-template #userDeleted>
<mat-icon matPrefix class="mat-icon tb-mat-30" fxHide.xs>no_accounts</mat-icon>
</ng-template>
<div fxFlex fxLayout="column" fxLayoutGap="5px"> <div fxFlex fxLayout="column" fxLayoutGap="5px">
<div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center"> <div fxLayout="row" fxLayoutGap="8px" fxLayoutAlign="start center">
<span class="user-name">{{ displayDataElement.displayName }}</span> <span class="user-name">{{ displayDataElement.displayName }}</span>

View File

@ -86,7 +86,7 @@ $border: 1px solid mat.get-color-from-palette($tb-primary);
background-color: $primary-color; background-color: $primary-color;
border: $border; border: $border;
border-bottom: none; border-bottom: none;
border-radius: 8px 8px 0px 0px; border-radius: 8px 8px 0 0;
&.activity-only { &.activity-only {
border: none; border: none;
@ -94,7 +94,7 @@ $border: 1px solid mat.get-color-from-palette($tb-primary);
&.asc { &.asc {
border-bottom: $border; border-bottom: $border;
box-shadow: 0px 4px 10px rgba(23, 33, 90, 0.08); box-shadow: 0 4px 10px rgba(23, 33, 90, 0.08);
&.activity-only { &.activity-only {
border: none; border: none;
@ -116,7 +116,7 @@ $border: 1px solid mat.get-color-from-palette($tb-primary);
.comments-container { .comments-container {
border: $border; border: $border;
border-top: none; border-top: none;
border-radius: 0px 0px 8px 8px; border-radius: 0 0 8px 8px;
&.activity-only { &.activity-only {
border: none; border: none;
@ -134,6 +134,11 @@ $border: 1px solid mat.get-color-from-palette($tb-primary);
&:hover { &:hover {
background-color: $primary-color; background-color: $primary-color;
} }
.mat-icon {
align-self: start;
color: rgba(0, 0, 0, 0.38);
}
} }
.user-avatar { .user-avatar {

View File

@ -21,31 +21,33 @@ import { TranslateService } from '@ngx-translate/core';
import { AlarmCommentService } from '@core/http/alarm-comment.service'; import { AlarmCommentService } from '@core/http/alarm-comment.service';
import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { AuthUser, User } from '@shared/models/user.model'; import { AuthUser } from '@shared/models/user.model';
import { getCurrentAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; import { getCurrentAuthUser, selectUserDetails } from '@core/auth/auth.selectors';
import { Direction, SortOrder } from '@shared/models/page/sort-order'; import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { MAX_SAFE_PAGE_SIZE, PageLink } from '@shared/models/page/page-link'; import { MAX_SAFE_PAGE_SIZE, PageLink } from '@shared/models/page/page-link';
import { DateAgoPipe } from '@shared/pipe/date-ago.pipe'; import { DateAgoPipe } from '@shared/pipe/date-ago.pipe';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { AlarmComment, AlarmCommentInfo, AlarmCommentType } from '@shared/models/alarm.models'; import { AlarmComment, AlarmCommentType } from '@shared/models/alarm.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { ImportExportService } from '@shared/import-export/import-export.service'; import { ImportExportService } from '@shared/import-export/import-export.service';
import { isNotEmptyStr } from '@core/utils';
interface AlarmCommentsDisplayData { interface AlarmCommentsDisplayData {
commentId?: string, commentId?: string;
displayName?: string, displayName?: string;
createdTime: string, createdTime: string;
createdDateAgo?: string, createdDateAgo?: string;
edit?: boolean, edit?: boolean;
isEdited?: boolean, isEdited?: boolean;
editedTime?: string; editedTime?: string;
editedDateAgo?: string, editedDateAgo?: string;
showActions?: boolean, showActions?: boolean;
commentText?: string, commentText?: string;
isSystemComment?: boolean, isSystemComment?: boolean;
avatarBgColor?: string avatarBgColor?: string;
userExists?: boolean;
} }
@Component({ @Component({
@ -58,7 +60,7 @@ export class AlarmCommentComponent implements OnInit {
alarmId: string; alarmId: string;
@Input() @Input()
alarmActivityOnly: boolean = false; alarmActivityOnly = false;
authUser: AuthUser; authUser: AuthUser;
@ -73,11 +75,11 @@ export class AlarmCommentComponent implements OnInit {
direction: Direction.DESC direction: Direction.DESC
}; };
editMode: boolean = false; editMode = false;
userDisplayName$ = this.store.pipe( userDisplayName$ = this.store.pipe(
select(selectUserDetails), select(selectUserDetails),
map((user) => this.getUserDisplayName(user)) map((user) => this.utilsService.getUserDisplayName(user))
); );
currentUserDisplayName: string; currentUserDisplayName: string;
@ -115,15 +117,18 @@ export class AlarmCommentComponent implements OnInit {
(pagedData) => { (pagedData) => {
this.alarmComments = pagedData.data; this.alarmComments = pagedData.data;
this.displayData.length = 0; this.displayData.length = 0;
for (let alarmComment of pagedData.data) { for (const alarmComment of pagedData.data) {
let displayDataElement = {} as AlarmCommentsDisplayData; const displayDataElement = {} as AlarmCommentsDisplayData;
displayDataElement.createdTime = this.datePipe.transform(alarmComment.createdTime, 'yyyy-MM-dd HH:mm:ss'); displayDataElement.createdTime = this.datePipe.transform(alarmComment.createdTime, 'yyyy-MM-dd HH:mm:ss');
displayDataElement.createdDateAgo = this.dateAgoPipe.transform(alarmComment.createdTime); displayDataElement.createdDateAgo = this.dateAgoPipe.transform(alarmComment.createdTime);
displayDataElement.commentText = alarmComment.comment.text; displayDataElement.commentText = alarmComment.comment.text;
displayDataElement.isSystemComment = alarmComment.type === AlarmCommentType.SYSTEM; displayDataElement.isSystemComment = alarmComment.type === AlarmCommentType.SYSTEM;
if (alarmComment.type === AlarmCommentType.OTHER) { if (alarmComment.type === AlarmCommentType.OTHER) {
displayDataElement.commentId = alarmComment.id.id; displayDataElement.commentId = alarmComment.id.id;
displayDataElement.displayName = this.getUserDisplayName(alarmComment); displayDataElement.userExists = isNotEmptyStr(alarmComment.firstName) || isNotEmptyStr(alarmComment.lastName) ||
isNotEmptyStr(alarmComment.email);
displayDataElement.displayName = displayDataElement.userExists ? this.utilsService.getUserDisplayName(alarmComment) :
this.translate.instant('alarm.user-deleted');
displayDataElement.edit = false; displayDataElement.edit = false;
displayDataElement.isEdited = alarmComment.comment.edited; displayDataElement.isEdited = alarmComment.comment.edited;
displayDataElement.editedTime = this.datePipe.transform(alarmComment.comment.editedOn, 'yyyy-MM-dd HH:mm:ss'); displayDataElement.editedTime = this.datePipe.transform(alarmComment.comment.editedOn, 'yyyy-MM-dd HH:mm:ss');
@ -136,17 +141,17 @@ export class AlarmCommentComponent implements OnInit {
this.displayData.push(displayDataElement); this.displayData.push(displayDataElement);
} }
} }
) );
} }
changeSortDirection() { changeSortDirection() {
let currentDirection = this.alarmCommentSortOrder.direction; const currentDirection = this.alarmCommentSortOrder.direction;
this.alarmCommentSortOrder.direction = currentDirection === Direction.DESC ? Direction.ASC : Direction.DESC; this.alarmCommentSortOrder.direction = currentDirection === Direction.DESC ? Direction.ASC : Direction.DESC;
this.loadAlarmComments(); this.loadAlarmComments();
} }
exportAlarmActivity() { exportAlarmActivity() {
let fileName = this.translate.instant('alarm.alarm') + '_' + this.translate.instant('alarm-activity.activity'); const fileName = this.translate.instant('alarm.alarm') + '_' + this.translate.instant('alarm-activity.activity');
this.importExportService.exportCsv(this.getDataForExport(), fileName.toLowerCase()); this.importExportService.exportCsv(this.getDataForExport(), fileName.toLowerCase());
} }
@ -162,7 +167,7 @@ export class AlarmCommentComponent implements OnInit {
comment: { comment: {
text: commentInputValue text: commentInputValue
} }
} };
this.doSave(comment); this.doSave(comment);
this.clearCommentInput(); this.clearCommentInput();
} }
@ -185,7 +190,7 @@ export class AlarmCommentComponent implements OnInit {
() => { () => {
this.loadAlarmComments(); this.loadAlarmComments();
} }
) );
} }
editComment(commentId: string): void { editComment(commentId: string): void {
@ -217,18 +222,18 @@ export class AlarmCommentComponent implements OnInit {
.subscribe(() => { .subscribe(() => {
this.loadAlarmComments(); this.loadAlarmComments();
} }
) );
} }
} }
) );
} }
getSortDirectionIcon() { getSortDirectionIcon() {
return this.alarmCommentSortOrder.direction === Direction.DESC ? 'mdi:sort-descending' : 'mdi:sort-ascending' return this.alarmCommentSortOrder.direction === Direction.DESC ? 'mdi:sort-descending' : 'mdi:sort-ascending';
} }
getSortDirectionTooltipText() { getSortDirectionTooltipText() {
let text = this.alarmCommentSortOrder.direction === Direction.DESC ? 'alarm-activity.newest-first' : const text = this.alarmCommentSortOrder.direction === Direction.DESC ? 'alarm-activity.newest-first' :
'alarm-activity.oldest-first'; 'alarm-activity.oldest-first';
return this.translate.instant(text); return this.translate.instant(text);
} }
@ -264,25 +269,6 @@ export class AlarmCommentComponent implements OnInit {
return this.utilsService.stringToHslColor(userDisplayName, 40, 60); return this.utilsService.stringToHslColor(userDisplayName, 40, 60);
} }
private getUserDisplayName(alarmCommentInfo: AlarmCommentInfo | User): string {
let name = '';
if ((alarmCommentInfo.firstName && alarmCommentInfo.firstName.length > 0) ||
(alarmCommentInfo.lastName && alarmCommentInfo.lastName.length > 0)) {
if (alarmCommentInfo.firstName) {
name += alarmCommentInfo.firstName;
}
if (alarmCommentInfo.lastName) {
if (name.length > 0) {
name += ' ';
}
name += alarmCommentInfo.lastName;
}
} else {
name = alarmCommentInfo.email;
}
return name;
}
getAlarmCommentFormControl(): AbstractControl { getAlarmCommentFormControl(): AbstractControl {
return this.alarmCommentFormGroup.get('alarmComment'); return this.alarmCommentFormGroup.get('alarmComment');
} }
@ -308,16 +294,16 @@ export class AlarmCommentComponent implements OnInit {
} }
private getDataForExport() { private getDataForExport() {
let dataToExport = []; const dataToExport = [];
for (let row of this.displayData) { for (const row of this.displayData) {
let exportRow = { const exportRow = {
[this.translate.instant('alarm-activity.author')]: row.isSystemComment ? [this.translate.instant('alarm-activity.author')]: row.isSystemComment ?
this.translate.instant('alarm-activity.system') : row.displayName, this.translate.instant('alarm-activity.system') : row.displayName,
[this.translate.instant('alarm-activity.created-date')]: row.createdTime, [this.translate.instant('alarm-activity.created-date')]: row.createdTime,
[this.translate.instant('alarm-activity.edited-date')]: row.editedTime, [this.translate.instant('alarm-activity.edited-date')]: row.editedTime,
[this.translate.instant('alarm-activity.text')]: row.commentText [this.translate.instant('alarm-activity.text')]: row.commentText
} };
dataToExport.push(exportRow) dataToExport.push(exportRow);
} }
return dataToExport; return dataToExport;
} }

View File

@ -31,13 +31,13 @@ import { forkJoin, Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { import {
AlarmAssignee,
AlarmInfo, AlarmInfo,
AlarmQueryV2, AlarmQueryV2,
AlarmSearchStatus, AlarmSearchStatus,
alarmSeverityColors, alarmSeverityColors,
alarmSeverityTranslations, alarmSeverityTranslations,
AlarmsMode, AlarmsMode,
AlarmStatus,
alarmStatusTranslations alarmStatusTranslations
} from '@app/shared/models/alarm.models'; } from '@app/shared/models/alarm.models';
import { AlarmService } from '@app/core/http/alarm.service'; import { AlarmService } from '@app/core/http/alarm.service';
@ -60,7 +60,7 @@ import {
AlarmAssigneePanelData AlarmAssigneePanelData
} from '@home/components/alarm/alarm-assignee-panel.component'; } from '@home/components/alarm/alarm-assignee-panel.component';
import { ComponentPortal } from '@angular/cdk/portal'; import { ComponentPortal } from '@angular/cdk/portal';
import { getEntityDetailsPageURL, isDefinedAndNotNull } from '@core/utils'; import { getEntityDetailsPageURL, isDefinedAndNotNull, isNotEmptyStr } from '@core/utils';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { AlarmFilterConfig } from '@shared/models/query/query.models'; import { AlarmFilterConfig } from '@shared/models/query/query.models';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
@ -129,22 +129,15 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
}))); })));
this.columns.push( this.columns.push(
new EntityTableColumn<AlarmInfo>('assignee', 'alarm.assignee', '240px', new EntityTableColumn<AlarmInfo>('assignee', 'alarm.assignee', '240px',
(entity) => { (entity) => this.getAssigneeTemplate(entity), () => ({}), false, () => ({}), () => undefined, false,
return this.getAssigneeTemplate(entity)
},
() => ({}),
false,
() => ({}),
(entity) => undefined,
false,
{ {
icon: 'keyboard_arrow_down', icon: 'keyboard_arrow_down',
type: CellActionDescriptorType.DEFAULT, type: CellActionDescriptorType.DEFAULT,
isEnabled: (entity) => true, isEnabled: () => true,
name: this.translate.instant('alarm.assign'), name: this.translate.instant('alarm.assign'),
onAction: ($event, entity) => this.openAlarmAssigneePanel($event, entity) onAction: ($event, entity) => this.openAlarmAssigneePanel($event, entity)
}) })
) );
this.columns.push( this.columns.push(
new EntityTableColumn<AlarmInfo>('status', 'alarm.status', '25%', new EntityTableColumn<AlarmInfo>('status', 'alarm.status', '25%',
(entity) => this.translate.instant(alarmStatusTranslations.get(entity.status)))); (entity) => this.translate.instant(alarmStatusTranslations.get(entity.status))));
@ -177,7 +170,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
isEnabled: true, isEnabled: true,
onAction: ($event, entities) => this.deleteAlarms($event, entities) onAction: ($event, entities) => this.deleteAlarms($event, entities)
} }
) );
} }
fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> { fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> {
@ -216,61 +209,32 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
} }
getAssigneeTemplate(entity: AlarmInfo): string { getAssigneeTemplate(entity: AlarmInfo): string {
return ` const hasAssigneeId = isDefinedAndNotNull(entity.assigneeId);
<span class="assignee-cell"> let templateContent: string;
${isDefinedAndNotNull(entity.assigneeId) ?
`<span class="assigned-container"> if (hasAssigneeId && ((isNotEmptyStr(entity.assignee?.firstName) || isNotEmptyStr(entity.assignee?.lastName)) ||
<span class="user-avatar" style="background-color: ${this.getAvatarBgColor(entity)}"> isNotEmptyStr(entity.assignee?.email))) {
${this.getUserInitials(entity)} templateContent = `
<span class="assigned-container">
<span class="user-avatar" style="background-color: ${this.getAvatarBgColor(entity.assignee)}">
${this.utilsService.getUserInitials(entity.assignee)}
</span> </span>
<span class="user-display-name">${this.getUserDisplayName(entity)}</span> <span class="user-display-name">${this.utilsService.getUserDisplayName(entity.assignee)}</span>
</span>` </span>`;
:
`<span class="unassigned-container">
<mat-icon class="material-icons unassigned-icon">account_circle</mat-icon>
<span>${this.translate.instant('alarm.unassigned')}</span>
</span>`
}
</span>`
}
getUserDisplayName(entity: AlarmInfo) {
let displayName = '';
if ((entity.assignee.firstName && entity.assignee.firstName.length > 0) ||
(entity.assignee.lastName && entity.assignee.lastName.length > 0)) {
if (entity.assignee.firstName) {
displayName += entity.assignee.firstName;
}
if (entity.assignee.lastName) {
if (displayName.length > 0) {
displayName += ' ';
}
displayName += entity.assignee.lastName;
}
} else { } else {
displayName = entity.assignee.email; templateContent = `
<span class="unassigned-container">
<mat-icon class="material-icons unassigned-icon">
${hasAssigneeId ? 'no_accounts' : 'account_circle'}
</mat-icon>
<span>${this.translate.instant(hasAssigneeId ? 'alarm.user-deleted' : 'alarm.unassigned')}</span>
</span>`;
} }
return displayName; return `<span class="assignee-cell">${templateContent}</span>`;
} }
getUserInitials(entity: AlarmInfo): string { getAvatarBgColor(alarmAssignee: AlarmAssignee) {
let initials = ''; return this.utilsService.stringToHslColor(this.utilsService.getUserDisplayName(alarmAssignee), 40, 60);
if (entity.assignee.firstName && entity.assignee.firstName.length ||
entity.assignee.lastName && entity.assignee.lastName.length) {
if (entity.assignee.firstName) {
initials += entity.assignee.firstName.charAt(0);
}
if (entity.assignee.lastName) {
initials += entity.assignee.lastName.charAt(0);
}
} else {
initials += entity.assignee.email.charAt(0);
}
return initials.toUpperCase();
}
getAvatarBgColor(entity: AlarmInfo) {
return this.utilsService.stringToHslColor(this.getUserDisplayName(entity), 40, 60);
} }
openAlarmAssigneePanel($event: Event, entity: AlarmInfo) { openAlarmAssigneePanel($event: Event, entity: AlarmInfo) {
@ -312,7 +276,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
this.viewContainerRef, injector)); this.viewContainerRef, injector));
componentRef.onDestroy(() => { componentRef.onDestroy(() => {
if (componentRef.instance.reassigned) { if (componentRef.instance.reassigned) {
this.updateData() this.updateData();
} }
}); });
} }

View File

@ -88,24 +88,24 @@
<span [innerHTML]="cellContent(alarm, column, row)"></span> <span [innerHTML]="cellContent(alarm, column, row)"></span>
<ng-container *ngIf="column.entityKey.key === 'assignee'"> <ng-container *ngIf="column.entityKey.key === 'assignee'">
<span class="assignee-cell" fxLayout="row" fxLayoutAlign="start center"> <span class="assignee-cell" fxLayout="row" fxLayoutAlign="start center">
<span *ngIf="alarm.assigneeId" class="assigned-container"> <span *ngIf="alarm.assigneeId && checkAssigneeHasName(alarm.assignee); else unassigned"
<span class="user-avatar" [style.backgroundColor]="getAvatarBgColor(alarm)"> class="assigned-container">
{{ getUserInitials(alarm) }} <span class="user-avatar" [style.backgroundColor]="getAvatarBgColor(alarm.assignee)">
{{ getUserInitials(alarm.assignee) }}
</span> </span>
<span [matTooltip]="getUserDisplayName(alarm)" <span [matTooltip]="getUserDisplayName(alarm.assignee)" matTooltipPosition="above">
matTooltipPosition="above"> {{ getUserDisplayName(alarm.assignee) }}
{{ getUserDisplayName(alarm) }}
</span> </span>
</span> </span>
<span *ngIf="!alarm.assigneeId" class="unassigned-container"> <ng-template #unassigned>
<mat-icon class="material-icons unassigned-icon">account_circle</mat-icon> <span class="unassigned-container">
<span matTooltip="{{ 'alarm.unassigned' | translate }}" <mat-icon class="material-icons unassigned-icon">{{ alarm.assigneeId ? 'no_accounts' : 'account_circle' }}</mat-icon>
matTooltipPosition="above" <span matTooltip="{{ (alarm.assigneeId ? 'alarm.user-deleted' : 'alarm.unassigned') | translate }}"
style="vertical-align: middle" matTooltipPosition="above" style="vertical-align: middle">
translate> {{ (alarm.assigneeId ? 'alarm.user-deleted' : 'alarm.unassigned') | translate }}
alarm.unassigned
</span> </span>
</span> </span>
</ng-template>
<button *ngIf="allowAssign" <button *ngIf="allowAssign"
mat-icon-button [disabled]="isLoading$ | async" mat-icon-button [disabled]="isLoading$ | async"
matTooltip="{{ 'alarm.assign' | translate }}" matTooltip="{{ 'alarm.assign' | translate }}"

View File

@ -37,7 +37,16 @@ import { DataKey, WidgetActionDescriptor, WidgetConfig } from '@shared/models/wi
import { IWidgetSubscription } from '@core/api/widget-api.models'; import { IWidgetSubscription } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { deepClone, hashCode, isDefined, isDefinedAndNotNull, isNumber, isObject, isUndefined } from '@core/utils'; import {
deepClone,
hashCode,
isDefined,
isDefinedAndNotNull,
isNotEmptyStr,
isNumber,
isObject,
isUndefined
} from '@core/utils';
import cssjs from '@core/css/css'; import cssjs from '@core/css/css';
import { sortItems } from '@shared/models/page/page-link'; import { sortItems } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order'; import { Direction } from '@shared/models/page/sort-order';
@ -83,7 +92,14 @@ import {
DisplayColumnsPanelComponent, DisplayColumnsPanelComponent,
DisplayColumnsPanelData DisplayColumnsPanelData
} from '@home/components/widget/lib/display-columns-panel.component'; } from '@home/components/widget/lib/display-columns-panel.component';
import { AlarmDataInfo, alarmFields, AlarmInfo, alarmSeverityColors, AlarmStatus } from '@shared/models/alarm.models'; import {
AlarmAssignee,
AlarmDataInfo,
alarmFields,
AlarmInfo,
alarmSeverityColors,
AlarmStatus
} from '@shared/models/alarm.models';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { import {
AlarmDetailsDialogComponent, AlarmDetailsDialogComponent,
@ -1097,43 +1113,21 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
this.rowStyleCache.length = 0; this.rowStyleCache.length = 0;
} }
getUserDisplayName(entity: AlarmInfo) { checkAssigneeHasName(alarmAssignee: AlarmAssignee): boolean {
let displayName = ''; return (isNotEmptyStr(alarmAssignee?.firstName) || isNotEmptyStr(alarmAssignee?.lastName)) ||
if ((entity.assignee.firstName && entity.assignee.firstName.length > 0) || isNotEmptyStr(alarmAssignee?.email);
(entity.assignee.lastName && entity.assignee.lastName.length > 0)) {
if (entity.assignee.firstName) {
displayName += entity.assignee.firstName;
}
if (entity.assignee.lastName) {
if (displayName.length > 0) {
displayName += ' ';
}
displayName += entity.assignee.lastName;
}
} else {
displayName = entity.assignee.email;
}
return displayName;
} }
getUserInitials(entity: AlarmInfo): string { getUserDisplayName(alarmAssignee: AlarmAssignee) {
let initials = ''; return this.utils.getUserDisplayName(alarmAssignee);
if (entity.assignee.firstName && entity.assignee.firstName.length ||
entity.assignee.lastName && entity.assignee.lastName.length) {
if (entity.assignee.firstName) {
initials += entity.assignee.firstName.charAt(0);
}
if (entity.assignee.lastName) {
initials += entity.assignee.lastName.charAt(0);
}
} else {
initials += entity.assignee.email.charAt(0);
}
return initials.toUpperCase();
} }
getAvatarBgColor(entity: AlarmInfo) { getUserInitials(alarmAssignee: AlarmAssignee): string {
return this.utils.stringToHslColor(this.getUserDisplayName(entity), 40, 60); return this.utils.getUserInitials(alarmAssignee);
}
getAvatarBgColor(alarmAssignee: AlarmAssignee) {
return this.utils.stringToHslColor(this.getUserDisplayName(alarmAssignee), 40, 60);
} }
openAlarmAssigneePanel($event: Event, entity: AlarmInfo) { openAlarmAssigneePanel($event: Event, entity: AlarmInfo) {

View File

@ -127,7 +127,7 @@ export interface AlarmComment extends BaseData<AlarmCommentId> {
text: string; text: string;
edited?: boolean; edited?: boolean;
editedOn?: number; editedOn?: number;
} };
} }
export interface AlarmCommentInfo extends AlarmComment { export interface AlarmCommentInfo extends AlarmComment {
@ -173,9 +173,9 @@ export const simulatedAlarm: AlarmInfo = {
originatorName: 'Simulated', originatorName: 'Simulated',
originatorLabel: 'Simulated', originatorLabel: 'Simulated',
assignee: { assignee: {
firstName: "", firstName: '',
lastName: "", lastName: '',
email: "test@example.com", email: 'test@example.com',
}, },
originator: { originator: {
entityType: EntityType.DEVICE, entityType: EntityType.DEVICE,

View File

@ -594,6 +594,7 @@
"assignee-last-name": "Assignee last name", "assignee-last-name": "Assignee last name",
"assignee-email": "Assignee email", "assignee-email": "Assignee email",
"unassigned": "Unassigned", "unassigned": "Unassigned",
"user-deleted": "User deleted",
"assignee-not-set": "All", "assignee-not-set": "All",
"status": "Status", "status": "Status",
"alarm-details": "Alarm details", "alarm-details": "Alarm details",

View File

@ -912,6 +912,9 @@ mat-icon {
&.tb-mat-28 { &.tb-mat-28 {
@include tb-mat-icon-size(28); @include tb-mat-icon-size(28);
} }
&.tb-mat-30 {
@include tb-mat-icon-size(30);
}
&.tb-mat-32 { &.tb-mat-32 {
@include tb-mat-icon-size(32); @include tb-mat-icon-size(32);
} }