alarm-assignee-select.component.ts 7.82 KB
///
/// Copyright © 2016-2024 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
///     http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///

import { Component, forwardRef, Injector, Input, OnInit, StaticProvider, ViewContainerRef } from '@angular/core';
import { UtilsService } from '@core/services/utils.service';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { TranslateService } from '@ngx-translate/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { UserId } from '@shared/models/id/user-id';
import { UserService } from '@core/http/user.service';
import { User, UserEmailInfo } from '@shared/models/user.model';
import { catchError, map, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import {
  ALARM_ASSIGNEE_SELECT_PANEL_DATA,
  AlarmAssigneeSelectPanelComponent,
  AlarmAssigneeSelectPanelData
} from '@home/components/alarm/alarm-assignee-select-panel.component';
import { coerceBoolean } from '@shared/decorators/coercion';
import { AlarmAssigneeOption } from '@shared/models/alarm.models';

@Component({
  selector: 'tb-alarm-assignee-select',
  templateUrl: './alarm-assignee-select.component.html',
  styleUrls: ['./alarm-assignee.component.scss'],
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => AlarmAssigneeSelectComponent),
      multi: true
    }
  ]
})
export class AlarmAssigneeSelectComponent implements OnInit, ControlValueAccessor {

  @Input() disabled: boolean;

  @coerceBoolean()
  @Input()
  inline = false;

  @coerceBoolean()
  @Input()
  userMode = false;

  assigneeFormGroup: UntypedFormGroup;
  assignee?: User | UserEmailInfo;
  assigneeOption?: AlarmAssigneeOption;

  private propagateChange = (_: any) => {};

  constructor(private utilsService: UtilsService,
              private overlay: Overlay,
              private fb: UntypedFormBuilder,
              private userService: UserService,
              private viewContainerRef: ViewContainerRef,
              private translateService: TranslateService) {
  }

  ngOnInit(): void {
    this.assigneeFormGroup = this.fb.group({
      assignee: [null, []]
    });
  }

  registerOnChange(fn: any): void {
    this.propagateChange = fn;
  }

  registerOnTouched(fn: any): void {
  }

  setDisabledState(isDisabled: boolean): void {
    this.disabled = isDisabled;
    if (this.disabled) {
      this.assigneeFormGroup.disable({emitEvent: false});
    } else {
      this.assigneeFormGroup.enable({emitEvent: false});
    }
  }

  writeValue(value?: UserId | AlarmAssigneeOption): void {
    let userId: UserId;
    if (value && (value as UserId).id) {
      userId = value as UserId;
      this.assigneeOption = null;
    } else {
      userId = null;
      this.assigneeOption = value ? value as AlarmAssigneeOption : AlarmAssigneeOption.noAssignee;
    }
    const userObservable = userId ? this.userService.getUser(userId.id, {ignoreErrors: true}).pipe(
      catchError(() => of(null))
    ) : of(null);
    userObservable.pipe(
      tap((user) => {
        this.assignee = user;
      }),
      map((user) => this.getAssignee(user))
    ).subscribe((assignee) => {
      if (assignee) {
        this.assigneeFormGroup.get('assignee').patchValue(assignee, {emitEvent: false});
      } else {
        if (!this.assigneeOption) {
          this.assigneeOption = AlarmAssigneeOption.noAssignee;
        }
        assignee = this.getAssigneeOption(this.assigneeOption);
        this.assigneeFormGroup.get('assignee').patchValue(assignee, {emitEvent: false});
      }
    });
  }

  private getAssignee(user?: User| UserEmailInfo): string | null {
    if (user) {
      return this.getUserDisplayName(user);
    } else {
      return null;
    }
  }

  private getAssigneeOption(assigneeOption: AlarmAssigneeOption): string {
    if (assigneeOption === AlarmAssigneeOption.noAssignee) {
      return this.translateService.instant('alarm.assignee-not-set');
    } else {
      return this.translateService.instant(this.userMode ? 'alarm.assigned-to-me' : 'alarm.assigned-to-current-user');
    }
  }

  private getUserDisplayName(user?: User | UserEmailInfo): string {
    let displayName = '';
    if ((user?.firstName && user?.firstName.length > 0) ||
      (user?.lastName && user?.lastName.length > 0)) {
      if (user?.firstName) {
        displayName += user?.firstName;
      }
      if (user?.lastName) {
        if (displayName.length > 0) {
          displayName += ' ';
        }
        displayName += user?.lastName;
      }
    } else {
      displayName = user?.email;
    }
    return displayName;
  }

  getUserInitials(): string {
    let initials = '';
    if (this.assignee?.firstName && this.assignee?.firstName.length ||
      this.assignee?.lastName && this.assignee?.lastName.length) {
      if (this.assignee?.firstName) {
        initials += this.assignee?.firstName.charAt(0);
      }
      if (this.assignee?.lastName) {
        initials += this.assignee?.lastName.charAt(0);
      }
    } else {
      initials += this.assignee?.email.charAt(0);
    }
    return initials.toUpperCase();
  }

  getAvatarBgColor(): string {
    return this.utilsService.stringToHslColor(this.getUserDisplayName(this.assignee), 40, 60);
  }

  openAlarmAssigneeSelectPanel($event: Event) {
    if ($event) {
      $event.stopPropagation();
    }
    if (!this.disabled) {
      const target = $event.currentTarget;
      const config = new OverlayConfig();
      config.backdropClass = 'cdk-overlay-transparent-backdrop';
      config.hasBackdrop = true;
      const connectedPosition: ConnectedPosition = {
        originX: 'center',
        originY: 'bottom',
        overlayX: 'center',
        overlayY: 'top'
      };
      config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement)
        .withPositions([connectedPosition]);
      config.width = (target as HTMLElement).offsetWidth;
      const overlayRef = this.overlay.create(config);
      overlayRef.backdropClick().subscribe(() => {
        overlayRef.dispose();
      });
      const providers: StaticProvider[] = [
        {
          provide: ALARM_ASSIGNEE_SELECT_PANEL_DATA,
          useValue: {
            assigneeId: this.assignee?.id?.id,
            assigneeOption: this.assigneeOption,
            userMode: this.userMode
          } as AlarmAssigneeSelectPanelData
        },
        {
          provide: OverlayRef,
          useValue: overlayRef
        }
      ];
      const injector = Injector.create({parent: this.viewContainerRef.injector, providers});
      const component = overlayRef.attach(new ComponentPortal(AlarmAssigneeSelectPanelComponent,
        this.viewContainerRef, injector));
      component.onDestroy(() => {
        if (component.instance.userSelected) {
          this.assignee = component.instance.result;
          this.assigneeOption = component.instance.optionResult;
          if (this.assignee) {
            this.assigneeFormGroup.get('assignee').patchValue(this.getAssignee(this.assignee), {emitEvent: false});
            this.propagateChange(this.assignee?.id);
          } else if (this.assigneeOption) {
            this.assigneeFormGroup.get('assignee').patchValue(this.getAssigneeOption(this.assigneeOption), {emitEvent: false});
            this.propagateChange(this.assigneeOption);
          }
        }
      });
    }
  }

}