Merge pull request #12630 from ChantsovaEkaterina/improvement/datetime-period-validation

Timewindow date-time period - improve behavior on changing start/end time
This commit is contained in:
Andrew Shvayka 2025-02-11 15:03:23 +02:00 committed by GitHub
commit 1ca7e1de3e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 89 additions and 51 deletions

View File

@ -15,17 +15,17 @@
limitations under the License. limitations under the License.
--> -->
<section class="tb-form-row column-xs no-border no-padding tb-standard-fields"> <section class="tb-form-row column-xs no-border no-padding tb-standard-fields" [formGroup]="dateTimePeriodFormGroup">
<mat-form-field class="flex" [subscriptSizing]="subscriptSizing" [appearance]="appearance"> <mat-form-field class="flex" [subscriptSizing]="subscriptSizing" [appearance]="appearance">
<mat-label translate>datetime.from</mat-label> <mat-label translate>datetime.from</mat-label>
<mat-datetimepicker-toggle [for]="startDatePicker" matSuffix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle [for]="startDatePicker" matSuffix></mat-datetimepicker-toggle>
<mat-datetimepicker #startDatePicker type="datetime" openOnFocus="true"></mat-datetimepicker> <mat-datetimepicker #startDatePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input matInput [disabled]="disabled" [(ngModel)]="startDate" [matDatetimepicker]="startDatePicker" (ngModelChange)="onStartDateChange()"> <input matInput formControlName="startDate" [matDatetimepicker]="startDatePicker" [max]="maxStartDate">
</mat-form-field> </mat-form-field>
<mat-form-field class="flex" [subscriptSizing]="subscriptSizing" [appearance]="appearance"> <mat-form-field class="flex" [subscriptSizing]="subscriptSizing" [appearance]="appearance">
<mat-label translate>datetime.to</mat-label> <mat-label translate>datetime.to</mat-label>
<mat-datetimepicker-toggle [for]="endDatePicker" matSuffix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle [for]="endDatePicker" matSuffix></mat-datetimepicker-toggle>
<mat-datetimepicker #endDatePicker type="datetime" openOnFocus="true"></mat-datetimepicker> <mat-datetimepicker #endDatePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input matInput [disabled]="disabled" [(ngModel)]="endDate" [matDatetimepicker]="endDatePicker" (ngModelChange)="onEndDateChange()"> <input matInput formControlName="endDate" [matDatetimepicker]="endDatePicker" [max]="maxEndDate">
</mat-form-field> </mat-form-field>
</section> </section>

View File

@ -14,10 +14,17 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { Component, forwardRef, Input } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms';
import { FixedWindow } from '@shared/models/time/time.models'; import { DAY, FixedWindow, MINUTE } from '@shared/models/time/time.models';
import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { distinctUntilChanged } from 'rxjs/operators';
interface DateTimePeriod {
startDate: Date;
endDate: Date;
}
@Component({ @Component({
selector: 'tb-datetime-period', selector: 'tb-datetime-period',
@ -31,7 +38,7 @@ import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-
} }
] ]
}) })
export class DatetimePeriodComponent implements OnInit, ControlValueAccessor { export class DatetimePeriodComponent implements ControlValueAccessor {
@Input() disabled: boolean; @Input() disabled: boolean;
@ -41,25 +48,45 @@ export class DatetimePeriodComponent implements OnInit, ControlValueAccessor {
@Input() @Input()
appearance: MatFormFieldAppearance = 'fill'; appearance: MatFormFieldAppearance = 'fill';
modelValue: FixedWindow; private modelValue: FixedWindow;
startDate: Date;
endDate: Date;
endTime: any;
maxStartDate: Date; maxStartDate: Date;
minEndDate: Date;
maxEndDate: Date; maxEndDate: Date;
changePending = false; private maxStartDateTs: number;
private minEndDateTs: number;
private maxStartTs: number;
private maxEndTs: number;
private timeShiftMs = MINUTE;
dateTimePeriodFormGroup = this.fb.group({
startDate: this.fb.control<Date>(null),
endDate: this.fb.control<Date>(null)
});
private changePending = false;
private propagateChange = null; private propagateChange = null;
constructor() { constructor(private fb: FormBuilder) {
} this.dateTimePeriodFormGroup.valueChanges.pipe(
distinctUntilChanged((prevDateTimePeriod, dateTimePeriod) =>
prevDateTimePeriod.startDate === dateTimePeriod.startDate && prevDateTimePeriod.endDate === dateTimePeriod.endDate),
takeUntilDestroyed()
).subscribe((dateTimePeriod: DateTimePeriod) => {
this.updateMinMaxDates(dateTimePeriod);
this.updateView();
});
ngOnInit(): void { this.dateTimePeriodFormGroup.get('startDate').valueChanges.pipe(
distinctUntilChanged(),
takeUntilDestroyed()
).subscribe(startDate => this.onStartDateChange(startDate));
this.dateTimePeriodFormGroup.get('endDate').valueChanges.pipe(
distinctUntilChanged(),
takeUntilDestroyed()
).subscribe(endDate => this.onEndDateChange(endDate));
} }
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
@ -75,35 +102,38 @@ export class DatetimePeriodComponent implements OnInit, ControlValueAccessor {
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled; this.disabled = isDisabled;
if (this.disabled) {
this.dateTimePeriodFormGroup.disable({emitEvent: false});
} else {
this.dateTimePeriodFormGroup.enable({emitEvent: false});
}
} }
writeValue(datePeriod: FixedWindow): void { writeValue(datePeriod: FixedWindow): void {
this.modelValue = datePeriod; this.modelValue = datePeriod;
if (this.modelValue) { if (this.modelValue) {
this.startDate = new Date(this.modelValue.startTimeMs); this.dateTimePeriodFormGroup.patchValue({
this.endDate = new Date(this.modelValue.endTimeMs); startDate: new Date(this.modelValue.startTimeMs),
endDate: new Date(this.modelValue.endTimeMs)
}, {emitEvent: false});
} else { } else {
const date = new Date(); const date = new Date();
this.startDate = new Date( this.dateTimePeriodFormGroup.patchValue({
date.getFullYear(), startDate: new Date(date.getTime() - DAY),
date.getMonth(), endDate: date
date.getDate() - 1, }, {emitEvent: false});
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds());
this.endDate = date;
this.updateView(); this.updateView();
} }
this.updateMinMaxDates(); this.updateMinMaxDates(this.dateTimePeriodFormGroup.value);
} }
updateView() { private updateView() {
let value: FixedWindow = null; let value: FixedWindow = null;
if (this.startDate && this.endDate) { const dateTimePeriod = this.dateTimePeriodFormGroup.value;
if (dateTimePeriod.startDate && dateTimePeriod.endDate) {
value = { value = {
startTimeMs: this.startDate.getTime(), startTimeMs: dateTimePeriod.startDate.getTime(),
endTimeMs: this.endDate.getTime() endTimeMs: dateTimePeriod.endDate.getTime()
}; };
} }
this.modelValue = value; this.modelValue = value;
@ -114,32 +144,40 @@ export class DatetimePeriodComponent implements OnInit, ControlValueAccessor {
} }
} }
updateMinMaxDates() { private updateMinMaxDates(dateTimePeriod: Partial<DateTimePeriod>) {
this.maxStartDate = new Date(this.endDate.getTime() - 1000);
this.minEndDate = new Date(this.startDate.getTime() + 1000);
this.maxEndDate = new Date(); this.maxEndDate = new Date();
this.maxEndTs = this.maxEndDate.getTime();
this.maxStartTs = this.maxEndTs - this.timeShiftMs;
this.maxStartDate = new Date(this.maxStartTs);
if (dateTimePeriod.endDate) {
this.maxStartDateTs = dateTimePeriod.endDate.getTime() - this.timeShiftMs;
}
if (dateTimePeriod.startDate) {
this.minEndDateTs = dateTimePeriod.startDate.getTime() + this.timeShiftMs;
}
} }
onStartDateChange() { private onStartDateChange(startDate: Date) {
if (this.startDate) { if (startDate) {
if (this.startDate.getTime() > this.maxStartDate.getTime()) { if (startDate.getTime() > this.maxStartTs) {
this.startDate = new Date(this.maxStartDate.getTime()); this.dateTimePeriodFormGroup.get('startDate').patchValue(new Date(this.maxStartTs), { emitEvent: false });
}
if (startDate.getTime() > this.maxStartDateTs) {
this.dateTimePeriodFormGroup.get('endDate').patchValue(new Date(startDate.getTime() + this.timeShiftMs), { emitEvent: false });
} }
this.updateMinMaxDates();
} }
this.updateView();
} }
onEndDateChange() { private onEndDateChange(endDate: Date) {
if (this.endDate) { if (endDate) {
if (this.endDate.getTime() < this.minEndDate.getTime()) { if (endDate.getTime() > this.maxEndTs) {
this.endDate = new Date(this.minEndDate.getTime()); this.dateTimePeriodFormGroup.get('endDate').patchValue(new Date(this.maxEndTs), { emitEvent: false });
} else if (this.endDate.getTime() > this.maxEndDate.getTime()) { }
this.endDate = new Date(this.maxEndDate.getTime()); if (endDate.getTime() < this.minEndDateTs) {
this.dateTimePeriodFormGroup.get('startDate').patchValue(new Date(endDate.getTime() - this.timeShiftMs), { emitEvent: false });
} }
this.updateMinMaxDates();
} }
this.updateView();
} }
} }