diff --git a/frontend/src/app/core/state/days/day.service.ts b/frontend/src/app/core/state/days/day.service.ts index c2c7968974..a4150db72b 100644 --- a/frontend/src/app/core/state/days/day.service.ts +++ b/frontend/src/app/core/state/days/day.service.ts @@ -57,6 +57,17 @@ export class DayResourceService extends ResourceCollectionService { return this.require({ filters }); } + requireNonWorkingYears$(start:Date|string, end:Date|string):Observable { + const from = moment(start).startOf('year').format('YYYY-MM-DD'); + const to = moment(end).endOf('year').format('YYYY-MM-DD'); + + const filters:ApiV3ListFilter[] = [ + ['date', '<>d', [from, to]], + ]; + + return this.require({ filters }); + } + fetchCollection(params:ApiV3ListParameters):Observable> { const collectionURL = collectionKey(params); diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts index 4b1b6b17ef..5fe6d7d9e5 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts @@ -321,7 +321,7 @@ export class TimelineCellRenderer { break; } // Extend the duration if the currentDate is non-working - if (this.weekdayService.isNonWorkingDay(currentDate.toDate())) { + if (this.weekdayService.isNonWorkingDay(currentDate.toDate() || this.workPackageTimeline.isNonWorkingDay(currentDate.toDate()))) { duration += 1; } } @@ -472,10 +472,11 @@ export class TimelineCellRenderer { const dates = (evOrDates instanceof MouseEvent) ? [this.cursorDateAndDayOffset(evOrDates, renderInfo)[0]] : evOrDates; - if (!renderInfo.workPackage.ignoreNonWorkingDays && direction === 'both' && this.weekdayService.isNonWorkingDay(dates[dates.length - 1].toDate())) { + if (!renderInfo.workPackage.ignoreNonWorkingDays && direction === 'both' + && (this.weekdayService.isNonWorkingDay(dates[dates.length - 1].toDate() || this.workPackageTimeline.isNonWorkingDay(dates[dates.length - 1].toDate())))) { return false; } - return dates.some((date) => this.weekdayService.isNonWorkingDay(date.toDate())); + return dates.some((date) => (this.weekdayService.isNonWorkingDay(date.toDate()) || this.workPackageTimeline.isNonWorkingDay(date.toDate()))); } /** diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/container/wp-timeline-container.directive.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/container/wp-timeline-container.directive.ts index b19c7e7ec6..6028c2bfe6 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/container/wp-timeline-container.directive.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/container/wp-timeline-container.directive.ts @@ -34,7 +34,7 @@ import { IToast, ToastService } from 'core-app/shared/components/toaster/toast.s import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import * as moment from 'moment'; import { Moment } from 'moment'; -import { filter, takeUntil } from 'rxjs/operators'; +import { filter, takeUntil, take } from 'rxjs/operators'; import { input, InputState } from 'reactivestates'; import { WorkPackageTable } from 'core-app/features/work-packages/components/wp-fast-table/wp-fast-table'; import { WorkPackageTimelineCellsRenderer } from 'core-app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cells-renderer'; @@ -68,6 +68,8 @@ import { } from '../wp-timeline'; import { WeekdayService } from 'core-app/core/days/weekday.service'; import * as Mousetrap from 'mousetrap'; +import { DayResourceService } from 'core-app/core/state/days/day.service'; +import { IDay } from 'core-app/core/state/days/day.model'; @Component({ selector: 'wp-timeline-container', @@ -137,6 +139,7 @@ export class WorkPackageTimelineTableController extends UntilDestroyedMixin impl readonly I18n:I18nService, private workPackageViewCollapsedGroupsService:WorkPackageViewCollapsedGroupsService, private weekdaysService:WeekdayService, + private daysService:DayResourceService, ) { super(); } @@ -144,6 +147,13 @@ export class WorkPackageTimelineTableController extends UntilDestroyedMixin impl ngAfterViewInit() { this.$element = jQuery(this.elementRef.nativeElement); + const scrollBar = document.querySelector('.work-packages-tabletimeline--timeline-side'); + if (scrollBar) { + scrollBar.addEventListener('scroll', () => { + this.requireNonWorkingDays(this.getFirstDayInViewport().format('YYYY-MM-DD'), this.getLastDayInViewport().format('YYYY-MM-DD')); + }); + } + this.text = { selectionMode: this.I18n.t('js.timelines.selection_mode.notification'), }; @@ -176,6 +186,8 @@ export class WorkPackageTimelineTableController extends UntilDestroyedMixin impl this.setupManageCollapsedGroupHeaderCells(); } + public nonWorkingDays:IDay[] = []; + workPackageCells(wpId:string):WorkPackageTimelineCell[] { return this.cellsRenderer.getCellsFor(wpId); } @@ -225,12 +237,14 @@ export class WorkPackageTimelineTableController extends UntilDestroyedMixin impl this.wpTableTimeline.appliedZoomLevel = this.wpTableTimeline.zoomLevel; } - timeOutput('refreshView() in timeline container', () => { + timeOutput('refreshView() in timeline container', async () => { // Reset the width of the outer container if its content shrinks this.outerContainer.css('width', 'auto'); this.calculateViewParams(this._viewParameters); + await this.requireNonWorkingDays(this.getFirstDayInViewport().format('YYYY-MM-DD'), this.getLastDayInViewport().format('YYYY-MM-DD')); + // Update all cells this.cellsRenderer.refreshAllCells(); @@ -348,6 +362,19 @@ export class WorkPackageTimelineTableController extends UntilDestroyedMixin impl this.refreshView(); } + async requireNonWorkingDays(start:Date|string, end:Date|string) { + this.nonWorkingDays = await this + .daysService + .requireNonWorkingYears$(start, end) + .pipe(take(1)) + .toPromise(); + } + + isNonWorkingDay(date:Date|string):boolean { + const formatted = moment(date).format('YYYY-MM-DD'); + return (this.nonWorkingDays.findIndex((el) => el.date === formatted) !== -1); + } + private calculateViewParams(currentParams:TimelineViewParameters):boolean { if (this.disableViewParamsCalculation) { return false; diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/grid/wp-timeline-grid.directive.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/grid/wp-timeline-grid.directive.ts index 70540d3532..4cd244e7b3 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/grid/wp-timeline-grid.directive.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/grid/wp-timeline-grid.directive.ts @@ -187,8 +187,7 @@ export class WorkPackageTableTimelineGrid implements AfterViewInit { private checkForNonWorkingDayHighlight(date:Moment, cell:HTMLElement) { const day = date.toDate(); - - if (this.weekdaysService.isNonWorkingDay(day)) { + if (this.weekdaysService.isNonWorkingDay(day) || this.wpTimeline.isNonWorkingDay(day)) { cell.classList.add('wp-timeline--non-working-day'); cell.dataset.qaSelector = `wp-timeline--non-working-day_${day.getDate()}-${day.getMonth() + 1}-${day.getFullYear()}`; } diff --git a/spec/features/work_packages/timeline/timeline_dates_spec.rb b/spec/features/work_packages/timeline/timeline_dates_spec.rb index fe475ec2b8..a5cbc4a609 100644 --- a/spec/features/work_packages/timeline/timeline_dates_spec.rb +++ b/spec/features/work_packages/timeline/timeline_dates_spec.rb @@ -125,14 +125,18 @@ RSpec.describe 'Work package timeline date formatting', let(:current_user) { create :admin, language: 'en' } shared_let(:week_days) { week_with_saturday_and_sunday_as_weekend } + shared_let(:non_working_day) do + create(:non_working_day, + date: '28-12-2020') + end it 'shows them as disabled' do expect_date_week work_package.start_date.iso8601, '01' expect(page).to have_selector('[data-qa-selector="wp-timeline--non-working-day_27-12-2020"]') expect(page).to have_selector('[data-qa-selector="wp-timeline--non-working-day_2-1-2021"]') + expect(page).to have_selector('[data-qa-selector="wp-timeline--non-working-day_28-12-2020"]') - expect(page).to have_no_selector('[data-qa-selector="wp-timeline--non-working-day_28-12-2020"]') expect(page).to have_no_selector('[data-qa-selector="wp-timeline--non-working-day_29-12-2020"]') expect(page).to have_no_selector('[data-qa-selector="wp-timeline--non-working-day_30-12-2020"]') expect(page).to have_no_selector('[data-qa-selector="wp-timeline--non-working-day_31-12-2020"]') @@ -171,6 +175,11 @@ RSpec.describe 'Work package timeline date formatting', let(:current_user) { create :admin } let(:row) { wp_timeline.timeline_row work_package_with_non_working_days.id } + shared_let(:non_working_day) do + create(:non_working_day, + date: '06-01-2021') + end + shared_examples "sets dates, duration and displays bar" do it 'sets dates, duration and duration bar' do subject @@ -224,7 +233,7 @@ RSpec.describe 'Work package timeline date formatting', let(:expected_bar_duration) { work_package_with_non_working_days.duration } let(:expected_start_date) { Date.parse('2021-01-04') } let(:expected_due_date) { Date.parse('2021-01-08') } - let(:expected_duration) { 5 } + let(:expected_duration) { 4 } let(:expected_label) { work_package_with_non_working_days.subject } end end @@ -237,7 +246,7 @@ RSpec.describe 'Work package timeline date formatting', let(:expected_bar_duration) { work_package_with_non_working_days.duration + 2 } let(:expected_start_date) { Date.parse('2021-01-05') } let(:expected_due_date) { Date.parse('2021-01-11') } - let(:expected_duration) { 5 } + let(:expected_duration) { 4 } let(:expected_label) { work_package_with_non_working_days.subject } end end @@ -250,7 +259,7 @@ RSpec.describe 'Work package timeline date formatting', let(:expected_bar_duration) { work_package_with_non_working_days.duration } let(:expected_start_date) { Date.parse('2021-01-04') } let(:expected_due_date) { Date.parse('2021-01-08') } - let(:expected_duration) { 5 } + let(:expected_duration) { 4 } let(:expected_label) { work_package_with_non_working_days.subject } end end @@ -263,7 +272,7 @@ RSpec.describe 'Work package timeline date formatting', let(:expected_bar_duration) { work_package_with_non_working_days.duration + 2 } let(:expected_start_date) { Date.parse('2021-01-05') } let(:expected_due_date) { Date.parse('2021-01-11') } - let(:expected_duration) { 5 } + let(:expected_duration) { 4 } let(:expected_label) { work_package_with_non_working_days.subject } end end @@ -276,7 +285,7 @@ RSpec.describe 'Work package timeline date formatting', let(:expected_bar_duration) { work_package_with_non_working_days.duration + 3 } let(:expected_start_date) { Date.parse('2021-01-05') } let(:expected_due_date) { Date.parse('2021-01-12') } - let(:expected_duration) { 6 } + let(:expected_duration) { 5 } let(:expected_label) { work_package_with_non_working_days.subject } end end