/// /// Copyright © 2016-2023 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, Input, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, UntypedFormArray, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator, Validators } from '@angular/forms'; import { AlarmSchedule, AlarmScheduleType, AlarmScheduleTypeTranslationMap, dayOfWeekTranslations, getAlarmScheduleRangeText, timeOfDayToUTCTimestamp, utcTimestampToTimeOfDay } from '@shared/models/device.models'; import { isDefined, isDefinedAndNotNull } from '@core/utils'; import { MatLegacyCheckboxChange as MatCheckboxChange } from '@angular/material/legacy-checkbox'; import { getDefaultTimezone } from '@shared/models/time/time.models'; @Component({ selector: 'tb-alarm-schedule', templateUrl: './alarm-schedule.component.html', styleUrls: ['./alarm-schedule.component.scss'], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AlarmScheduleComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => AlarmScheduleComponent), multi: true }] }) export class AlarmScheduleComponent implements ControlValueAccessor, Validator, OnInit { @Input() disabled: boolean; alarmScheduleForm: UntypedFormGroup; alarmScheduleTypes = Object.keys(AlarmScheduleType); alarmScheduleType = AlarmScheduleType; alarmScheduleTypeTranslate = AlarmScheduleTypeTranslationMap; dayOfWeekTranslationsArray = dayOfWeekTranslations; allDays = Array(7).fill(0).map((x, i) => i); firstRowDays = Array(4).fill(0).map((x, i) => i); secondRowDays = Array(3).fill(0).map((x, i) => i + 4); private modelValue: AlarmSchedule; private defaultItems = Array.from({length: 7}, (value, i) => ({ enabled: true, dayOfWeek: i + 1 })); private propagateChange = (v: any) => { }; constructor(private fb: UntypedFormBuilder) { } ngOnInit(): void { this.alarmScheduleForm = this.fb.group({ type: [AlarmScheduleType.ANY_TIME, Validators.required], timezone: [null, Validators.required], daysOfWeek: this.fb.array(new Array(7).fill(false), this.validateDayOfWeeks), startsOn: [0, Validators.required], endsOn: [0, Validators.required], items: this.fb.array(Array.from({length: 7}, (value, i) => this.defaultItemsScheduler(i)), this.validateItems), dynamicValue: [null] }); this.alarmScheduleForm.get('type').valueChanges.subscribe((type) => { const defaultTimezone = getDefaultTimezone(); this.alarmScheduleForm.reset({type, items: this.defaultItems, timezone: defaultTimezone}, {emitEvent: false}); this.updateValidators(type, true); this.alarmScheduleForm.updateValueAndValidity(); }); this.alarmScheduleForm.valueChanges.subscribe(() => { this.updateModel(); }); } validateDayOfWeeks(control: AbstractControl): ValidationErrors | null { const dayOfWeeks: boolean[] = control.value; if (!dayOfWeeks || !dayOfWeeks.length || !dayOfWeeks.find(v => v === true)) { return { dayOfWeeks: true }; } return null; } validateItems(control: AbstractControl): ValidationErrors | null { const items: any[] = control.value; if (!items || !items.length || !items.find(v => v.enabled === true)) { return { dayOfWeeks: true }; } return null; } registerOnChange(fn: any): void { this.propagateChange = fn; } registerOnTouched(fn: any): void { } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { this.alarmScheduleForm.disable({emitEvent: false}); } else { this.alarmScheduleForm.enable({emitEvent: false}); } } writeValue(value: AlarmSchedule): void { this.modelValue = value; if (!isDefinedAndNotNull(this.modelValue)) { this.modelValue = { type: AlarmScheduleType.ANY_TIME }; } switch (this.modelValue.type) { case AlarmScheduleType.SPECIFIC_TIME: let daysOfWeek = new Array(7).fill(false); if (isDefined(this.modelValue.daysOfWeek)) { daysOfWeek = daysOfWeek.map((item, index) => this.modelValue.daysOfWeek.indexOf(index + 1) > -1); } this.alarmScheduleForm.patchValue({ type: this.modelValue.type, timezone: this.modelValue.timezone, daysOfWeek, startsOn: utcTimestampToTimeOfDay(this.modelValue.startsOn), endsOn: utcTimestampToTimeOfDay(this.modelValue.endsOn), dynamicValue: this.modelValue.dynamicValue }, {emitEvent: false}); break; case AlarmScheduleType.CUSTOM: if (this.modelValue.items) { const alarmDays = []; this.modelValue.items .sort((a, b) => a.dayOfWeek - b.dayOfWeek) .forEach((item, index) => { this.disabledSelectedTime(item.enabled, index); alarmDays.push({ enabled: item.enabled, startsOn: utcTimestampToTimeOfDay(item.startsOn), endsOn: utcTimestampToTimeOfDay(item.endsOn) }); }); this.alarmScheduleForm.patchValue({ type: this.modelValue.type, timezone: this.modelValue.timezone, items: alarmDays, dynamicValue: this.modelValue.dynamicValue }, {emitEvent: false}); } break; default: this.alarmScheduleForm.patchValue(this.modelValue || undefined, {emitEvent: false}); } this.updateValidators(this.modelValue.type); } validate(control: UntypedFormGroup): ValidationErrors | null { return this.alarmScheduleForm.valid ? null : { alarmScheduler: { valid: false } }; } weeklyRepeatControl(index: number): UntypedFormControl { return (this.alarmScheduleForm.get('daysOfWeek') as UntypedFormArray).at(index) as UntypedFormControl; } private updateValidators(type: AlarmScheduleType, changedType = false){ switch (type){ case AlarmScheduleType.ANY_TIME: this.alarmScheduleForm.get('timezone').disable({emitEvent: false}); this.alarmScheduleForm.get('daysOfWeek').disable({emitEvent: false}); this.alarmScheduleForm.get('startsOn').disable({emitEvent: false}); this.alarmScheduleForm.get('endsOn').disable({emitEvent: false}); this.alarmScheduleForm.get('items').disable({emitEvent: false}); break; case AlarmScheduleType.SPECIFIC_TIME: this.alarmScheduleForm.get('timezone').enable({emitEvent: false}); this.alarmScheduleForm.get('daysOfWeek').enable({emitEvent: false}); this.alarmScheduleForm.get('startsOn').enable({emitEvent: false}); this.alarmScheduleForm.get('endsOn').enable({emitEvent: false}); this.alarmScheduleForm.get('items').disable({emitEvent: false}); break; case AlarmScheduleType.CUSTOM: this.alarmScheduleForm.get('timezone').enable({emitEvent: false}); this.alarmScheduleForm.get('daysOfWeek').disable({emitEvent: false}); this.alarmScheduleForm.get('startsOn').disable({emitEvent: false}); this.alarmScheduleForm.get('endsOn').disable({emitEvent: false}); if (changedType) { this.alarmScheduleForm.get('items').enable({emitEvent: false}); } break; } } private updateModel() { const value = this.alarmScheduleForm.value; if (this.modelValue) { if (isDefined(value.daysOfWeek)) { value.daysOfWeek = value.daysOfWeek .map((day: boolean, index: number) => day ? index + 1 : null) .filter(day => !!day); } if (isDefined(value.startsOn) && value.startsOn !== 0) { value.startsOn = timeOfDayToUTCTimestamp(value.startsOn); } if (isDefined(value.endsOn) && value.endsOn !== 0) { value.endsOn = timeOfDayToUTCTimestamp(value.endsOn); } if (isDefined(value.items)){ value.items = this.alarmScheduleForm.getRawValue().items; value.items = value.items.map((item) => { return { ...item, startsOn: timeOfDayToUTCTimestamp(item.startsOn), endsOn: timeOfDayToUTCTimestamp(item.endsOn)}; }); } this.modelValue = value; this.propagateChange(this.modelValue); } } private defaultItemsScheduler(index): UntypedFormGroup { return this.fb.group({ enabled: [true], dayOfWeek: [index + 1], startsOn: [0, Validators.required], endsOn: [0, Validators.required] }); } changeCustomScheduler($event: MatCheckboxChange, index: number) { const value = $event.checked; this.disabledSelectedTime(value, index, true); } private disabledSelectedTime(enable: boolean, index: number, emitEvent = false) { if (enable) { this.itemsSchedulerForm.at(index).get('startsOn').enable({emitEvent: false}); this.itemsSchedulerForm.at(index).get('endsOn').enable({emitEvent}); } else { this.itemsSchedulerForm.at(index).get('startsOn').disable({emitEvent: false}); this.itemsSchedulerForm.at(index).get('endsOn').disable({emitEvent}); } } getSchedulerRangeText(control: UntypedFormGroup | AbstractControl): string { return getAlarmScheduleRangeText(control.get('startsOn').value, control.get('endsOn').value); } get itemsSchedulerForm(): UntypedFormArray { return this.alarmScheduleForm.get('items') as UntypedFormArray; } }