diff --git a/app/components/work_packages/baseline/modal_dialog_component.html.erb b/app/components/work_packages/baseline/modal_dialog_component.html.erb new file mode 100644 index 000000000000..d0bae082340a --- /dev/null +++ b/app/components/work_packages/baseline/modal_dialog_component.html.erb @@ -0,0 +1,23 @@ +<%= render(Primer::Alpha::Dialog.new( + title: I18n.t("js.baseline.baseline_comparison"), + subtitle: subtitle_text, + size: :medium_portrait, + id: MODAL_ID +)) do |dialog| %> + <% dialog.with_header(variant: :large) %> + <% dialog.with_body do %> + <%= helpers.angular_component_tag "opce-baseline", + inputs: { + showHeaderText: false, + } %> + <% end %> + <% dialog.with_footer do %> + <%= render(Primer::ButtonComponent.new(data: { "close-dialog-id": MODAL_ID })) { I18n.t(:button_cancel) } %> + <%= render(Primer::ButtonComponent.new( + data: { + "close-dialog-id": MODAL_ID, + }, + scheme: :primary, + type: :submit)) { I18n.t(:button_apply) } %> + <% end %> +<% end %> diff --git a/app/components/work_packages/baseline/modal_dialog_component.rb b/app/components/work_packages/baseline/modal_dialog_component.rb new file mode 100644 index 000000000000..cda496cc4f20 --- /dev/null +++ b/app/components/work_packages/baseline/modal_dialog_component.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +# ++ + +module WorkPackages + module Baseline + class ModalDialogComponent < ApplicationComponent + MODAL_ID = "op-work-packages-baseline-dialog" + EXPORT_FORM_ID = "op-work-packages-baseline-dialog-form" + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + attr_reader :query, :project, :query_params + + def initialize(query:, project:, title:) + super + + @query = query + @project = project + @query_params = ::API::V3::Queries::QueryParamsRepresenter.new(query).to_url_query(merge_params: { columns: [], title: }) + end + + def subtitle_text + # TODO: show different text and EE icon if no EE licence + I18n.t("js.baseline.header_description") + end + end + end +end diff --git a/app/components/work_packages/index_page_header_component.html.erb b/app/components/work_packages/index_page_header_component.html.erb index 7557119a1c7d..be01aa22a882 100644 --- a/app/components/work_packages/index_page_header_component.html.erb +++ b/app/components/work_packages/index_page_header_component.html.erb @@ -13,13 +13,20 @@ d.with_body { "TODO" } end - header.with_action_dialog(mobile_icon: :"op-baseline", + header.with_action_button(tag: :a, + mobile_icon: :"op-baseline", mobile_label: I18n.t("js.baseline.toggle_title"), - dialog_arguments: { id: "my_dialog2", title: "A great dialog" }, - button_arguments: { button_block: baseline_button_callback, test_selector: "baseline-button" }) do |d| - d.with_body { "TODO" } + href: @project.present? ? baseline_dialog_project_work_packages_path(@project) : baseline_dialog_project_work_packages_path(), + aria: { label: I18n.t("js.baseline.toggle_title") }, + title: I18n.t("js.baseline.toggle_title"), + data: { controller: "async-dialog" }, + rel: "nofollow") do |button| + button.with_leading_visual_icon(icon: :"op-baseline") + I18n.t("js.baseline.toggle_title") end + + header.with_action_zen_mode_button header.with_action_menu( diff --git a/app/components/work_packages/index_page_header_component.rb b/app/components/work_packages/index_page_header_component.rb index 405ae979aa23..92a395a5703a 100644 --- a/app/components/work_packages/index_page_header_component.rb +++ b/app/components/work_packages/index_page_header_component.rb @@ -54,13 +54,6 @@ def title @query&.name ? @query.name : t(:label_work_package_plural) end - def baseline_button_callback - lambda do |button| - button.with_leading_visual_icon(icon: :"op-baseline") - I18n.t("js.baseline.toggle_title") - end - end - def project_include_button_callback lambda do |button| button.with_leading_visual_icon(icon: :"op-include-projects") diff --git a/app/controllers/work_packages_controller.rb b/app/controllers/work_packages_controller.rb index 69d6ca48e7c6..cd0270b29777 100644 --- a/app/controllers/work_packages_controller.rb +++ b/app/controllers/work_packages_controller.rb @@ -40,11 +40,11 @@ class WorkPackagesController < ApplicationController :project, only: :show before_action :check_allowed_export, :protect_from_unauthorized_export, only: %i[index export_dialog] - before_action :find_optional_project, only: %i[split_view split_create] + before_action :find_optional_project, only: %i[split_view split_create baseline_dialog] before_action :load_and_authorize_in_optional_project, only: %i[index export_dialog new copy] - authorization_checked! :index, :show, :new, :copy, :export_dialog, :split_view, :split_create + authorization_checked! :index, :show, :new, :copy, :export_dialog, :split_view, :split_create, :baseline_dialog - before_action :load_and_validate_query, only: %i[index split_view split_create copy] + before_action :load_and_validate_query, only: %i[index split_view split_create copy baseline_dialog] before_action :load_work_packages, only: :index, if: -> { request.format.atom? } before_action :load_and_validate_query_for_export, only: :export_dialog @@ -116,6 +116,10 @@ def export_dialog respond_with_dialog WorkPackages::Exports::ModalDialogComponent.new(query: @query, project: @project, title: params[:title]) end + def baseline_dialog + respond_with_dialog WorkPackages::Baseline::ModalDialogComponent.new(query: @query, project: @project, title: params[:title]) + end + protected def split_view_base_route diff --git a/config/routes.rb b/config/routes.rb index e68138d46590..83287243a05a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -344,6 +344,7 @@ get "/report" => "work_packages/reports#report" get "menu" => "work_packages/menus#show" get "/export_dialog" => "work_packages#export_dialog" + get "/baseline_dialog" => "work_packages#baseline_dialog" end get "/copy" => "work_packages#copy", on: :member, as: "copy" @@ -616,6 +617,7 @@ end get "/export_dialog" => "work_packages#export_dialog", on: :collection, as: "export_dialog" + get "/baseline_dialog" => "work_packages#baseline_dialog", on: :collection, as: "baseline_dialog" get "/split_view/update_counter" => "work_packages/split_view#update_counter", on: :member diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index c9e9202560d7..37b326c93ead 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -168,6 +168,7 @@ import { WorkPackageSplitCreateEntryComponent } from 'core-app/features/work-pac import { WorkPackageFullCopyEntryComponent } from 'core-app/features/work-packages/routing/wp-full-copy/wp-full-copy-entry.component'; import { WorkPackageFullCreateEntryComponent } from 'core-app/features/work-packages/routing/wp-full-create/wp-full-create-entry.component'; import { WorkPackageFullViewEntryComponent } from 'core-app/features/work-packages/routing/wp-full-view/wp-full-view-entry.component'; +import { OpBaselineEntryComponent } from 'core-app/features/work-packages/components/wp-baseline/baseline/baseline-entry.component'; export function initializeServices(injector:Injector) { return () => { @@ -364,6 +365,7 @@ export class OpenProjectModule implements DoBootstrap { registerCustomElement('opce-wp-full-view', WorkPackageFullViewEntryComponent, { injector }); registerCustomElement('opce-wp-full-create', WorkPackageFullCreateEntryComponent, { injector }); registerCustomElement('opce-wp-full-copy', WorkPackageFullCopyEntryComponent, { injector }); + registerCustomElement('opce-baseline', OpBaselineEntryComponent, { injector }); registerCustomElement('opce-timer-account-menu', TimerAccountMenuComponent, { injector }); registerCustomElement('opce-remote-field-updater', RemoteFieldUpdaterComponent, { injector }); registerCustomElement('opce-modal-single-date-picker', OpModalSingleDatePickerComponent, { injector }); diff --git a/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline-entry.component.ts b/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline-entry.component.ts new file mode 100644 index 000000000000..098645c1db18 --- /dev/null +++ b/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline-entry.component.ts @@ -0,0 +1,89 @@ +//-- copyright +// OpenProject is an open source project management software. +// Copyright (C) the OpenProject GmbH +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +// Copyright (C) 2006-2013 Jean-Philippe Lang +// Copyright (C) 2010-2013 the ChiliProject Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// See COPYRIGHT and LICENSE files for more details. +//++ + +import { + ChangeDetectionStrategy, + Component, + ElementRef, + EventEmitter, + HostBinding, + Input, + OnInit, + Output, +} from '@angular/core'; + +import { I18nService } from 'core-app/core/i18n/i18n.service'; +import { UntilDestroyedMixin } from 'core-app/shared/helpers/angular/until-destroyed.mixin'; +import { HalResourceService } from 'core-app/features/hal/services/hal-resource.service'; +import SpotDropAlignmentOption from 'core-app/spot/drop-alignment-options'; +import { WeekdayService } from 'core-app/core/days/weekday.service'; +import { DayResourceService } from 'core-app/core/state/days/day.service'; +import { IDay } from 'core-app/core/state/days/day.model'; +import { TimezoneService } from 'core-app/core/datetime/timezone.service'; +import { ConfigurationService } from 'core-app/core/config/configuration.service'; +import { Observable } from 'rxjs'; +import { + DEFAULT_TIMESTAMP, + WorkPackageViewBaselineService, +} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-baseline.service'; +import { validDate } from 'core-app/shared/components/datepicker/helpers/date-modal.helpers'; +import { + baselineFilterFromValue, + getPartsFromTimestamp, + offsetToUtcString, +} from 'core-app/features/work-packages/components/wp-baseline/baseline-helpers'; +import * as moment from 'moment-timezone'; +import { BannersService } from 'core-app/core/enterprise/banners.service'; +import { enterpriseDocsUrl } from 'core-app/core/setup/globals/constants.const'; +import { DayElement } from 'flatpickr/dist/types/instance'; +import { populateInputsFromDataset } from 'core-app/shared/components/dataset-inputs'; +import { WorkPackageIsolatedQuerySpaceDirective } from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive'; + + +@Component({ + hostDirectives: [WorkPackageIsolatedQuerySpaceDirective], + template: ` + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class OpBaselineEntryComponent { + @Input() showActionBar? = false; + @Input() visible = true; + @Input() showHeaderText = true; + + constructor( + readonly elementRef:ElementRef, + ) { + populateInputsFromDataset(this); + } +} diff --git a/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline.component.html b/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline.component.html index ec8b14a386ad..4a9e16e64658 100644 --- a/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline.component.html +++ b/frontend/src/app/features/work-packages/components/wp-baseline/baseline/baseline.component.html @@ -2,11 +2,11 @@ [class.op-baseline_tab]="!showActionBar">
+ *ngIf="eeShowBanners && showHeaderText"> {{ text.baseline_comparison }}
- {{ eeShowBanners ? text.enterprise_header_description : text.header_description }} + {{ eeShowBanners ? text.enterprise_header_description : text.header_description }} = this.wpTableBaseline.nonWorkingDays$; diff --git a/frontend/src/app/features/work-packages/openproject-work-packages.module.ts b/frontend/src/app/features/work-packages/openproject-work-packages.module.ts index bf8d29a22184..bd1e68009238 100644 --- a/frontend/src/app/features/work-packages/openproject-work-packages.module.ts +++ b/frontend/src/app/features/work-packages/openproject-work-packages.module.ts @@ -420,6 +420,7 @@ import { WorkPackageSplitCreateEntryComponent } from 'core-app/features/work-pac import { WorkPackageFullCopyEntryComponent } from 'core-app/features/work-packages/routing/wp-full-copy/wp-full-copy-entry.component'; import { WorkPackageFullCreateEntryComponent } from 'core-app/features/work-packages/routing/wp-full-create/wp-full-create-entry.component'; import { WorkPackageFullViewEntryComponent } from 'core-app/features/work-packages/routing/wp-full-view/wp-full-view-entry.component'; +import { OpBaselineEntryComponent } from 'core-app/features/work-packages/components/wp-baseline/baseline/baseline-entry.component'; @NgModule({ imports: [ @@ -660,6 +661,7 @@ import { WorkPackageFullViewEntryComponent } from 'core-app/features/work-packag // Timestamps OpBaselineModalComponent, OpBaselineComponent, + OpBaselineEntryComponent, OpBaselineLoadingComponent, OpBaselineLegendsComponent,