Adjust end date if a start date is entered that is after the current start date

pull/11034/head
Henriette Darge 2 years ago
parent 4c3b952869
commit 882355c4d9
  1. 12
      frontend/src/app/shared/components/datepicker/datepicker.modal.html
  2. 5
      frontend/src/app/shared/components/datepicker/datepicker.modal.service.ts
  3. 51
      frontend/src/app/shared/components/datepicker/datepicker.modal.ts
  4. 5
      spec/features/work_packages/details/date_editor_spec.rb
  5. 5
      spec/features/work_packages/scheduling/scheduling_mode_spec.rb
  6. 3
      spec/features/work_packages/table/scheduling/manual_scheduling_spec.rb
  7. 4
      spec/support/components/datepicker/datepicker.rb
  8. 4
      spec/support/edit_fields/date_edit_field.rb

@ -36,8 +36,8 @@
name="date"
class="op-datepicker-modal--date-field"
[ngClass]="{'op-datepicker-modal--date-field_current' : showFieldAsActive('date')}"
[ngModel]="dates.date"
(ngModelChange)="updateDate('date', $event)"
[(ngModel)]="dates.date"
(ngModelChange)="dateChangedManually$.next()"
[showClearButton]="datepickerService.isStateOfCurrentActivatedField('date')"
(click)="datepickerService.setCurrentActivatedField('date')"
></spot-text-field>
@ -65,8 +65,8 @@
data-qa-selector="op-datepicker-modal--start-date-field"
class="op-datepicker-modal--date-field"
[ngClass]="{'op-datepicker-modal--date-field_current' : showFieldAsActive('start')}"
[ngModel]="dates.start"
(ngModelChange)="updateDate('start', $event)"
[(ngModel)]="dates.start"
(ngModelChange)="dateChangedManually$.next()"
[disabled]="!isSchedulable"
[showClearButton]="datepickerService.isStateOfCurrentActivatedField('start')"
(focusin)="datepickerService.setCurrentActivatedField('start')"
@ -93,8 +93,8 @@
data-qa-selector="op-datepicker-modal--end-date-field"
class="op-datepicker-modal--date-field"
[ngClass]="{'op-datepicker-modal--date-field_current' : showFieldAsActive('end')}"
[ngModel]="dates.end"
(ngModelChange)="updateDate('end', $event)"
[(ngModel)]="dates.end"
(ngModelChange)="dateChangedManually$.next()"
[disabled]="!isSchedulable"
[showClearButton]="datepickerService.isStateOfCurrentActivatedField('end')"
(focusin)="datepickerService.setCurrentActivatedField('end')"

@ -201,18 +201,18 @@ export class DatepickerModalService {
return this.currentlyActivatedDateField === val;
}
// eslint-disable-next-line class-methods-use-this
setDates(dates:DateOption|DateOption[], datePicker:DatePicker, enforceDate?:Date):void {
const { currentMonth } = datePicker.datepickerInstance;
const { currentYear } = datePicker.datepickerInstance;
datePicker.setDates(dates);
/* eslint-disable no-param-reassign */
if (enforceDate) {
const enforcedMonth = enforceDate.getMonth();
const enforcedYear = enforceDate.getFullYear();
const monthDiff = enforcedMonth - currentMonth + 12 * (enforcedYear - currentYear);
// Because of the two-month layout we only have to update the calendar
// if the month is further in the past/future than the one additional month that is shown anyway
if (Math.abs(monthDiff) > 1) {
datePicker.datepickerInstance.currentMonth = enforcedMonth;
datePicker.datepickerInstance.currentYear = enforcedYear;
@ -224,7 +224,6 @@ export class DatepickerModalService {
}
datePicker.datepickerInstance.redraw();
/* eslint-enable no-param-reassign */
}
private keepCurrentlyActiveMonth(datePicker:DatePicker, currentMonth:number, currentYear:number) {

@ -52,8 +52,12 @@ import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { DayElement } from 'flatpickr/dist/types/instance';
import flatpickr from 'flatpickr';
import { DatepickerModalService } from 'core-app/shared/components/datepicker/datepicker.modal.service';
import { take } from 'rxjs/operators';
import {
debounceTime,
take,
} from 'rxjs/operators';
import { activeFieldContainerClassName } from 'core-app/shared/components/fields/edit/edit-form/edit-form';
import { Subject } from 'rxjs';
export type DateKeys = 'date'|'start'|'end';
@ -104,6 +108,8 @@ export class DatePickerModalComponent extends OpModalComponent implements AfterV
end: '',
};
dateChangedManually$ = new Subject<void>();
private changeset:ResourceChangeset;
private datePickerInstance:DatePicker;
@ -142,6 +148,27 @@ export class DatePickerModalComponent extends OpModalComponent implements AfterV
this.initializeDatepicker(this.minimalDateFromPrecedingRelationship(relation));
this.onDataChange();
});
this
.dateChangedManually$
.pipe(
// Avoid that the manual changes are moved to the datepicker too early.
// The debounce is chosen quite large on purpose to catch the following case:
// 1. Start date is for example 2022-07-15. The user wants to set the end date to the 19th.
// 2. So he/she starts entering the finish date 2022-07-1 .
// 3. This is already a valid date. Since it is before the start date,the start date would be changed automatically to the first without the debounce.
// 4. The debounce gives the user enough time to type the last number "9" before the changes are converted to the datepicker and the start date would be affected.
debounceTime(800),
)
.subscribe(() => {
// Always update the whole form to ensure that no values are lost/inconsistent
if (this.singleDate) {
this.updateDate('date', this.dates.date);
} else {
this.updateDate('start', this.dates.start);
this.updateDate('end', this.dates.end);
}
});
}
changeSchedulingMode():void {
@ -258,7 +285,27 @@ export class DatePickerModalComponent extends OpModalComponent implements AfterV
const date = this.datepickerService.parseDate(this.dates.date);
this.datepickerService.setDates(date, this.datePickerInstance, enforceDate);
} else {
const dates = [this.datepickerService.parseDate(this.dates.start), this.datepickerService.parseDate(this.dates.end)];
let startDate = this.datepickerService.parseDate(this.dates.start);
let endDate = this.datepickerService.parseDate(this.dates.end);
if (startDate && endDate) {
// If the start date is manually changed to be after the end date,
// we adjust the end date to be at least the same as the newly entered start date.
// Same applies if the end date is set manually before the current start date
if (startDate > endDate && this.datepickerService.isStateOfCurrentActivatedField('start')) {
endDate = startDate;
this.dates.end = this.timezoneService.formattedISODate(endDate);
this.cdRef.detectChanges();
} else if (endDate < startDate && this.datepickerService.isStateOfCurrentActivatedField('end')) {
startDate = endDate;
this.dates.start = this.timezoneService.formattedISODate(startDate);
this.cdRef.detectChanges();
}
}
const dates = [startDate, endDate];
this.datepickerService.setDates(dates, this.datePickerInstance, enforceDate);
if (toggleField) {

@ -185,8 +185,13 @@ describe 'date inplace editor',
start_date.activate!
start_date.expect_active!
# The calendar needs some time to get initialised.
sleep 2
start_date.datepicker.expect_visible
# Set the due date
start_date.datepicker.set_date Time.zone.today, true
# As the to be selected date is automatically toggled,
# we can directly set the start date afterwards to the same day
start_date.datepicker.set_date Time.zone.today, true

@ -188,6 +188,11 @@ describe 'scheduling mode',
combined_field.expect_active!
combined_field.expect_scheduling_mode manually: false
combined_field.toggle_scheduling_mode
# The calendar needs some time to get initialised.
sleep 2
combined_field.expect_calendar
# Increasing the duration while at it
combined_field.update(%w[2015-12-20 2015-12-31])

@ -106,6 +106,9 @@ describe 'Manual scheduling', js: true do
fill_in 'endDate', with: '2020-07-25'
end
# Wait for the debounce to be done
sleep 1
start_date.save!
start_date.expect_state_text '07/20/2020'
due_date.expect_state_text '07/25/2020'

@ -12,6 +12,10 @@ module Components
page.find("#{context_selector} .flatpickr-calendar")
end
def expect_visible
expect(container).to be_present
end
##
# Select year from input
def select_year(value)

@ -108,7 +108,9 @@ class DateEditField < EditField
activate_edition
within_modal do
if value.is_a?(Array)
value.each { |el| select_value(el) }
value.each do |el|
select_value(el)
end
else
select_value value
end

Loading…
Cancel
Save