diff --git a/app/components/work_packages/full_view/copy_component.html.erb b/app/components/work_packages/full_view/copy_component.html.erb new file mode 100644 index 00000000000..869327b933a --- /dev/null +++ b/app/components/work_packages/full_view/copy_component.html.erb @@ -0,0 +1,11 @@ +<%= + helpers.angular_component_tag "opce-wp-full-copy", + inputs: { + type: @type, + copiedFromWorkPackageId: @copied_from_work_package_id, + projectIdentifier: @project.present? ? @project.identifier : nil, + resizerClass: "op-work-package-split-view", + routedFromAngular: false, + } + +%> diff --git a/app/components/work_packages/full_view/copy_component.rb b/app/components/work_packages/full_view/copy_component.rb new file mode 100644 index 00000000000..e2d142a7603 --- /dev/null +++ b/app/components/work_packages/full_view/copy_component.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module WorkPackages + module FullView + class CopyComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + def initialize(type:, copied_from_work_package_id:, project:) + super + + @type = type + @copied_from_work_package_id = copied_from_work_package_id + @project = project + end + end + end +end diff --git a/app/components/work_packages/full_view/create_component.html.erb b/app/components/work_packages/full_view/create_component.html.erb new file mode 100644 index 00000000000..ec6842ae08f --- /dev/null +++ b/app/components/work_packages/full_view/create_component.html.erb @@ -0,0 +1,7 @@ +<%= helpers.angular_component_tag "opce-wp-full-create", + inputs: { + type: @type, + projectIdentifier: @project.present? ? @project.identifier : nil, + routedFromAngular: false, + } +%> diff --git a/app/components/work_packages/full_view/create_component.rb b/app/components/work_packages/full_view/create_component.rb new file mode 100644 index 00000000000..7bbec152bef --- /dev/null +++ b/app/components/work_packages/full_view/create_component.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module WorkPackages + module FullView + class CreateComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + def initialize(type:, project:) + super + + @type = type + @project = project + end + end + end +end diff --git a/app/components/work_packages/full_view/show_component.html.erb b/app/components/work_packages/full_view/show_component.html.erb new file mode 100644 index 00000000000..4755438946c --- /dev/null +++ b/app/components/work_packages/full_view/show_component.html.erb @@ -0,0 +1,17 @@ +<%= + if @work_package.nil? + render(Primer::Beta::Blankslate.new(spacious: true)) do |component| + component.with_visual_icon(icon: :inbox) + component.with_heading(tag: :h2).with_content( + I18n.t(:error_work_package_id_not_found) + ) + end + else + helpers.angular_component_tag "opce-wp-full-view", + inputs: { + workPackageId: @id, + activeTab: @tab, + routedFromAngular: false + } + end +%> diff --git a/app/components/work_packages/full_view/show_component.rb b/app/components/work_packages/full_view/show_component.rb new file mode 100644 index 00000000000..2547c4a3e68 --- /dev/null +++ b/app/components/work_packages/full_view/show_component.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module WorkPackages + module FullView + class ShowComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + def self.wrapper_key = :"work-package-full-view" + + def initialize(id:, tab: "activity") + super + + @id = id + @tab = tab + @work_package = WorkPackage.visible.find_by(id:) + end + + def wrapper_uniq_by + @id + end + end + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5a99a5d3550..0ce5dc4cbf4 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -258,6 +258,8 @@ class ApplicationController < ActionController::Base # Find project by project_id if given def find_optional_project @project = Project.find(params[:project_id]) if params[:project_id].present? + rescue ActiveRecord::RecordNotFound + render_404 end # Finds and sets @project based on @object.project diff --git a/app/controllers/work_packages/bulk_controller.rb b/app/controllers/work_packages/bulk_controller.rb index acb99ebf93a..56e36faa6df 100644 --- a/app/controllers/work_packages/bulk_controller.rb +++ b/app/controllers/work_packages/bulk_controller.rb @@ -64,7 +64,7 @@ class WorkPackages::BulkController < ApplicationController respond_to do |format| format.html do - redirect_back_or_default(project_work_packages_path(@work_packages.first.project)) + redirect_to (project_work_packages_path(@work_packages.first.project)) end format.json do head :ok diff --git a/app/controllers/work_packages_controller.rb b/app/controllers/work_packages_controller.rb index 01a034f47ff..0f2f9b8a8a9 100644 --- a/app/controllers/work_packages_controller.rb +++ b/app/controllers/work_packages_controller.rb @@ -39,14 +39,15 @@ class WorkPackagesController < ApplicationController before_action :authorize_on_work_package, :project, only: %i[show generate_pdf_dialog generate_pdf] - before_action :load_and_authorize_in_optional_project, - :check_allowed_export, + before_action :check_allowed_export, :protect_from_unauthorized_export, only: %i[index export_dialog] + before_action :load_and_authorize_in_optional_project, only: %i[index new show copy export_dialog] before_action :authorize, only: %i[show_conflict_flash_message share_upsell] - authorization_checked! :index, :show, :export_dialog, :generate_pdf_dialog, :generate_pdf + authorization_checked! :index, :show, :new, :copy, :export_dialog, :generate_pdf_dialog, :generate_pdf + + before_action :load_and_validate_query, only: %i[index copy], unless: -> { request.format.html? } - before_action :load_and_validate_query, only: :index, unless: -> { request.format.html? } before_action :load_work_packages, only: :index, if: -> { request.format.atom? } before_action :load_and_validate_query_for_export, only: :export_dialog @@ -71,21 +72,34 @@ class WorkPackagesController < ApplicationController def show respond_to do |format| format.html do + if show_route_incomplete? + redirect_to_complete_route + + return + end + render :show, - locals: { work_package:, menu_name: project_or_global_menu }, - layout: "angular/angular" + locals: { work_package:, menu_name: project_or_global_menu } end - format.any(*supported_single_formats) do - export_single(request.format.symbol) - end + handle_standard_show_formats(format) + end + end - format.atom do - atom_journals + def copy + respond_to do |format| + format.html do + render :copy, + locals: { query: @query, project: @project, menu_name: project_or_global_menu } end + end + end - format.all do - head :not_acceptable + def new + respond_to do |format| + format.html do + render :new, + locals: { query: @query, project: @project, menu_name: project_or_global_menu } end end end @@ -177,6 +191,20 @@ class WorkPackagesController < ApplicationController private + def handle_standard_show_formats(format) + format.any(*supported_single_formats) do + export_single(request.format.symbol) + end + + format.atom do + atom_journals + end + + format.all do + head :not_acceptable + end + end + def save_export_settings # Saving export settings is only allowed for saved queries return false if @query.new_record? @@ -213,7 +241,9 @@ class WorkPackagesController < ApplicationController end def work_package - @work_package ||= WorkPackage.visible(current_user).find_by(id: params[:id]) + return @work_package if defined?(@work_package) + + @work_package = WorkPackage.visible(current_user).find_by(id: params[:id]) end def journals @@ -258,4 +288,16 @@ class WorkPackagesController < ApplicationController def login_back_url_params params.permit(:query_id, :state, :query_props) end + + def redirect_to_complete_route + # redirect /work_packages/:id to a full route with project and tab + redirect_to action: "show", + id: params[:id], + project_id: params[:project_id] || work_package.project.identifier, + tab: params[:tab] || "activity" + end + + def show_route_incomplete? + params[:project_id].blank? || params[:tab].blank? + end end diff --git a/app/models/work_package.rb b/app/models/work_package.rb index 438ed0e2b0c..f0611c9626f 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -277,6 +277,11 @@ class WorkPackage < ApplicationRecord "#{type.name unless type.is_standard} ##{id}: #{subject}" end + def infoline(show_standard_type: true) + type_name = show_standard_type || !type.is_standard ? type.name : "" + "#{type_name}: #{subject} (##{id})" + end + # Return true if the work_package is closed, otherwise false def closed? status.nil? || status.is_closed? diff --git a/app/seeders/demo_data/references.rb b/app/seeders/demo_data/references.rb index 192de2624df..543e10f821c 100644 --- a/app/seeders/demo_data/references.rb +++ b/app/seeders/demo_data/references.rb @@ -133,7 +133,7 @@ module DemoData url_helpers.project_work_package_path( id: work_package.id, project_id: work_package.project.identifier, - state: "activity" + tab: "activity" ) end end diff --git a/app/views/layouts/angular/angular.html.erb b/app/views/layouts/angular/angular.html.erb index 31a27e4b7f4..ba72c7ab00f 100644 --- a/app/views/layouts/angular/angular.html.erb +++ b/app/views/layouts/angular/angular.html.erb @@ -31,6 +31,8 @@ See COPYRIGHT and LICENSE files for more details. <% html_title(*local_assigns[:page_title]) if local_assigns[:page_title].present? %> <%= call_hook :view_work_package_overview_attributes %> + + <% end -%> <%= content_for :content_body do %> diff --git a/app/views/work_packages/copy.html.erb b/app/views/work_packages/copy.html.erb new file mode 100644 index 00000000000..0431d67cbf9 --- /dev/null +++ b/app/views/work_packages/copy.html.erb @@ -0,0 +1,45 @@ +<%#-- 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. + +++#%> + +<% html_title(t('activerecord.attributes.project.work_packages')) -%> + +<% content_for :sidebar do %> + <%= render partial: "sidebar" %> +<% end %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {query_id: query, format: "atom", page: nil, key: User.current.rss_key}, title: t(:label_work_package_plural)) %> + <%= auto_discovery_link_tag(:atom, {controller: "/journals", action: "index", query_id: query, format: "atom", page: nil, key: User.current.rss_key}, title: t(:label_changes_details)) %> +<% end %> + +<% content_for :content_body do %> + <%= render WorkPackages::FullView::CopyComponent.new(type: params[:type], + copied_from_work_package_id: params[:id], + project: @project) %> +<% end %> diff --git a/app/views/work_packages/new.html.erb b/app/views/work_packages/new.html.erb new file mode 100644 index 00000000000..b9fa694d61e --- /dev/null +++ b/app/views/work_packages/new.html.erb @@ -0,0 +1,44 @@ +<%#-- 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. + +++#%> + +<% html_title(t('activerecord.attributes.project.work_packages')) -%> + +<% content_for :sidebar do %> + <%= render partial: "sidebar" %> +<% end %> + +<% content_for :header_tags do %> + <%= auto_discovery_link_tag(:atom, {query_id: query, format: "atom", page: nil, key: User.current.rss_key}, title: t(:label_work_package_plural)) %> + <%= auto_discovery_link_tag(:atom, {controller: "/journals", action: "index", query_id: query, format: "atom", page: nil, key: User.current.rss_key}, title: t(:label_changes_details)) %> +<% end %> + +<% content_for :content_body do %> + <%= render WorkPackages::FullView::CreateComponent.new(type: params[:type], + project: @project) %> +<% end %> diff --git a/app/views/work_packages/show.html.erb b/app/views/work_packages/show.html.erb index 136812d9175..ba824c6b45d 100644 --- a/app/views/work_packages/show.html.erb +++ b/app/views/work_packages/show.html.erb @@ -27,7 +27,7 @@ See COPYRIGHT and LICENSE files for more details. ++#%> -<% html_title h(work_package.to_s) %> +<% html_title h(work_package.infoline) %> <% content_for :sidebar do %> <%= render partial: 'sidebar' %> @@ -38,3 +38,7 @@ See COPYRIGHT and LICENSE files for more details. { format: 'atom', key: User.current.rss_key }, title: "#{work_package.project} - #{work_package.to_s}") %> <% end %> + +<% content_for :content_body do %> + <%= render WorkPackages::FullView::ShowComponent.new(id: work_package.id, tab: params[:tab] || "activity") %> +<% end %> diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index 3366186bc60..731e088d339 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -290,7 +290,8 @@ Rails.application.reloader.to_prepare do wpt.permission :add_work_packages, { - work_package_relations: %i[new create] + work_package_relations: %i[new create], + work_packages: %i[new] }, permissible_on: :project, dependencies: :view_work_packages, @@ -313,7 +314,10 @@ Rails.application.reloader.to_prepare do contract_actions: { work_packages: %i[move] } wpt.permission :copy_work_packages, - { "work_packages/moves": %i[new create] }, + { + "work_packages/moves": %i[new create], + work_packages: %i[copy] + }, permissible_on: %i[work_package project], require: :loggedin, dependencies: :view_work_packages, diff --git a/config/routes.rb b/config/routes.rb index 8f6004d5be9..e25b85f913d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -386,7 +386,7 @@ Rails.application.routes.draw do # work as a catchall for everything under /wiki get "wiki" => "wiki#show" - resources :work_packages, only: [] do + resources :work_packages, only: %i[index show] do collection do get "/report/:detail" => "work_packages/reports#report_details" get "/report" => "work_packages/reports#report" @@ -394,14 +394,16 @@ Rails.application.routes.draw do get "/export_dialog" => "work_packages#export_dialog" end + get "/copy" => "work_packages#copy", on: :member, as: "copy" + get "/new" => "work_packages#new", on: :collection, as: "new" + + get "(/:tab)" => "work_packages#show", on: :member, as: "", + constraints: { id: /\d+/, state: /(?!(shares|copy|dialog)).+/ } + # states managed by client-side routing on work_package#index - get "(/*state)" => "work_packages#index", on: :collection, as: "", constraints: { state: /(?!(dialog)).+/ } + get "(/*state)" => "work_packages#index", on: :collection, as: "", constraints: { state: /(?!(dialog|new)).+/ } get "/create_new" => "work_packages#index", on: :collection, as: "new_split" - get "/new" => "work_packages#index", on: :collection, as: "new" - - # state for show view in project context - get "(/*state)" => "work_packages#show", on: :member, as: "", constraints: { id: /\d+/, state: /(?!(dialog)).+/ } end namespace :work_packages do @@ -715,7 +717,7 @@ Rails.application.routes.draw do get "/bulk" => "bulk#destroy" end - resources :work_packages, only: [:index] do + resources :work_packages, only: %i[index show new] do concerns :shareable get "hover_card" => "work_packages/hover_card#show", on: :member @@ -797,12 +799,13 @@ Rails.application.routes.draw do get "/split_view/get_relations_counter" => "work_packages/split_view#get_relations_counter", on: :member + get "/copy" => "work_packages#copy", on: :member, as: "copy" + get "(/:tab)" => "work_packages#show", on: :member, as: "", constraints: { id: /\d+/, state: /(?!(shares|new|copy)).+/ } + # states managed by client-side (angular) routing on work_package#show get "/" => "work_packages#index", on: :collection, as: "index" get "/create_new" => "work_packages#index", on: :collection, as: "new_split" - get "/new" => "work_packages#index", on: :collection, as: "new", state: "new" - # We do not want to match the work package export routes - get "(/*state)" => "work_packages#show", on: :member, as: "", constraints: { id: /\d+/, state: /(?!(shares|split_view)).+/ } + get "/share_upsell" => "work_packages#share_upsell", on: :collection, as: "share_upsell" get "/edit" => "work_packages#show", on: :member, as: "edit" end diff --git a/docker/pullpreview/docker-compose.yml b/docker/pullpreview/docker-compose.yml index 6950a0deb7e..7be578a1adf 100644 --- a/docker/pullpreview/docker-compose.yml +++ b/docker/pullpreview/docker-compose.yml @@ -13,6 +13,8 @@ volumes: x-defaults: &defaults build: context: . + args: + DEBIAN_BASE: bookworm restart: unless-stopped env_file: - .env.pullpreview diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index a70a5ae9348..e053997a690 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -204,6 +204,9 @@ import { OpWpDatePickerInstanceComponent, } from 'core-app/shared/components/datepicker/wp-date-picker-modal/wp-date-picker-instance.component'; import { TimeEntryTimerService } from 'core-app/shared/components/time_entries/services/time-entry-timer.service'; +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 { MyPageComponent } from './features/my-page/my-page.component'; import { DashboardComponent } from './features/overview/dashboard.component'; @@ -392,6 +395,9 @@ export class OpenProjectModule implements DoBootstrap { registerCustomElement('opce-reminder-settings', ReminderSettingsPageComponent, { injector }); registerCustomElement('opce-notification-center', InAppNotificationCenterComponent, { injector }); registerCustomElement('opce-wp-split-view', WorkPackageSplitViewEntryComponent, { injector }); + registerCustomElement('opce-wp-full-view', WorkPackageFullViewEntryComponent, { injector }); + registerCustomElement('opce-wp-full-create', WorkPackageFullCreateEntryComponent, { injector }); + registerCustomElement('opce-wp-full-copy', WorkPackageFullCopyEntryComponent, { injector }); registerCustomElement('opce-timer-account-menu', TimerAccountMenuComponent, { injector }); registerCustomElement('opce-remote-field-updater', RemoteFieldUpdaterComponent, { injector }); registerCustomElement('opce-wp-date-picker-instance', OpWpDatePickerInstanceComponent, { injector }); diff --git a/frontend/src/app/core/path-helper/path-helper.service.ts b/frontend/src/app/core/path-helper/path-helper.service.ts index 72eda9eed25..026ac4529d2 100644 --- a/frontend/src/app/core/path-helper/path-helper.service.ts +++ b/frontend/src/app/core/path-helper/path-helper.service.ts @@ -212,6 +212,10 @@ export class PathHelperService { return `${this.staticBase}/work_packages`; } + public workPackageNewPath():string { + return `${this.staticBase}/work_packages/new`; + } + public projectWorkPackageNewPath(projectId:string) { return `${this.workPackagesPath(projectId)}/new`; } @@ -300,11 +304,23 @@ export class PathHelperService { return `${this.staticBase}/work_packages/${id}`; } + public genericWorkPackagePath(projectIdentifier:string|null, workPackageId:string|number, tab = 'activity') { + if (projectIdentifier) { + return `${this.projectWorkPackagePath(projectIdentifier, workPackageId)}/${tab}`; + } + + return `${this.workPackagePath(workPackageId)}/${tab}`; + } + public workPackageShortPath(id:string|number) { return `${this.staticBase}/wp/${id}`; } - public workPackageCopyPath(workPackageId:string|number) { + public workPackageCopyPath(projectIdentifier:string|null, workPackageId:string|number) { + if (projectIdentifier) { + return `${this.workPackagesPath(projectIdentifier)}/${workPackageId}/copy`; + } + return `${this.workPackagePath(workPackageId)}/copy`; } @@ -316,6 +332,7 @@ export class PathHelperService { return `${this.workPackagesPath(projectIdentifier)}/details/${workPackageId}`; } + // Todo: Remove? public workPackageDetailsCopyPath(projectIdentifier:string, workPackageId:string|number) { return this.workPackageDetailsPath(projectIdentifier, workPackageId, 'copy'); } diff --git a/frontend/src/app/core/setup/globals/onboarding/helpers.ts b/frontend/src/app/core/setup/globals/onboarding/helpers.ts index ee162ca3901..fc125415959 100644 --- a/frontend/src/app/core/setup/globals/onboarding/helpers.ts +++ b/frontend/src/app/core/setup/globals/onboarding/helpers.ts @@ -1,5 +1,5 @@ export const onboardingTourStorageKey = 'openProject-onboardingTour'; -export type OnboardingTourNames = 'homescreen'|'workPackages'|'gantt'|'final'|'boards'|'teamPlanner'; +export type OnboardingTourNames = 'homescreen'|'workPackages'|'workPackagesFullView'|'gantt'|'final'|'boards'|'teamPlanner'; function matchingFilter(list:NodeListOf, filterFunction:(match:HTMLElement) => boolean):HTMLElement|null { for (let i = 0; i < list.length; i++) { diff --git a/frontend/src/app/core/setup/globals/onboarding/onboarding_tour.ts b/frontend/src/app/core/setup/globals/onboarding/onboarding_tour.ts index a1b652395a0..b299a9094f6 100644 --- a/frontend/src/app/core/setup/globals/onboarding/onboarding_tour.ts +++ b/frontend/src/app/core/setup/globals/onboarding/onboarding_tour.ts @@ -18,6 +18,7 @@ import { ganttOnboardingTourSteps } from 'core-app/core/setup/globals/onboarding import { ConfigurationService } from 'core-app/core/config/configuration.service'; import 'core-vendor/enjoyhint'; +import { wpFullViewOnboardingTourSteps } from 'core-app/core/setup/globals/onboarding/tours/work_package_full_view_tour'; declare global { interface Window { @@ -77,6 +78,16 @@ function workPackageTour() { }); } + +function workPackageFullViewTour() { + initializeTour('wpFullViewTourFinished'); + waitForElement('.work-package--single-view', '#content', () => { + const steps:OnboardingStep[] = wpFullViewOnboardingTourSteps(); + + startTour(steps); + }); +} + function ganttTour(configuration:ConfigurationService) { initializeTour('ganttTourFinished'); @@ -147,6 +158,9 @@ export function start(name:OnboardingTourNames, configuration:ConfigurationServi case 'workPackages': workPackageTour(); break; + case 'workPackagesFullView': + workPackageFullViewTour(); + break; case 'gantt': ganttTour(configuration); break; diff --git a/frontend/src/app/core/setup/globals/onboarding/onboarding_tour_trigger.ts b/frontend/src/app/core/setup/globals/onboarding/onboarding_tour_trigger.ts index fe54b4945ed..d709489d7a8 100644 --- a/frontend/src/app/core/setup/globals/onboarding/onboarding_tour_trigger.ts +++ b/frontend/src/app/core/setup/globals/onboarding/onboarding_tour_trigger.ts @@ -76,8 +76,13 @@ export function detectOnboardingTour():void { void triggerTour('workPackages'); } - // ------------------------------- Tutorial Gantt module ------------------------------- + // ------------------------------- Tutorial WP Full page ------------------------------- if (currentTourPart === 'wpTourFinished') { + void triggerTour('workPackagesFullView'); + } + + // ------------------------------- Tutorial Gantt module ------------------------------- + if (currentTourPart === 'wpFullViewTourFinished') { void triggerTour('gantt'); return; } diff --git a/frontend/src/app/core/setup/globals/onboarding/tours/work_package_full_view_tour.ts b/frontend/src/app/core/setup/globals/onboarding/tours/work_package_full_view_tour.ts new file mode 100644 index 00000000000..e5a73eea2c7 --- /dev/null +++ b/frontend/src/app/core/setup/globals/onboarding/tours/work_package_full_view_tour.ts @@ -0,0 +1,29 @@ +import { OnboardingStep } from 'core-app/core/setup/globals/onboarding/onboarding_tour'; + +export function wpFullViewOnboardingTourSteps():OnboardingStep[] { + return [ + { + 'next .work-packages-full-view--split-left': I18n.t('js.onboarding.steps.wp.full_view'), + showSkip: false, + nextButton: { text: I18n.t('js.onboarding.buttons.next') }, + containerClass: '-dark -hidden-arrow', + onNext() { + jQuery('.main-menu--arrow-left-to-project')[0].click(); + }, + }, + { + 'next #main-menu-gantt': I18n.t('js.onboarding.steps.wp.gantt_menu'), + showSkip: false, + nextButton: { text: I18n.t('js.onboarding.buttons.next') }, + onNext() { + jQuery('#main-menu-gantt')[0].click(); + }, + }, + { + containerClass: '-dark -hidden-arrow', + onBeforeStart() { + window.location.href = `${window.location.origin}/projects/demo-project/gantt`; + }, + }, + ]; +} diff --git a/frontend/src/app/core/setup/globals/onboarding/tours/work_package_tour.ts b/frontend/src/app/core/setup/globals/onboarding/tours/work_package_tour.ts index e366a94916d..2973030e08f 100644 --- a/frontend/src/app/core/setup/globals/onboarding/tours/work_package_tour.ts +++ b/frontend/src/app/core/setup/globals/onboarding/tours/work_package_tour.ts @@ -3,28 +3,6 @@ import { OnboardingStep } from 'core-app/core/setup/globals/onboarding/onboardin export function wpOnboardingTourSteps():OnboardingStep[] { return [ - { - 'next .wp-table--row': I18n.t('js.onboarding.steps.wp.list'), - showSkip: false, - nextButton: { text: I18n.t('js.onboarding.buttons.next') }, - onNext() { - jQuery('.inline-edit--display-field.id a ')[0].click(); - }, - }, - { - 'next .work-packages-full-view--split-left': I18n.t('js.onboarding.steps.wp.full_view'), - showSkip: false, - nextButton: { text: I18n.t('js.onboarding.buttons.next') }, - containerClass: '-dark -hidden-arrow', - }, - { - 'next .work-packages-back-button': I18n.t('js.onboarding.steps.wp.back_button'), - showSkip: false, - nextButton: { text: I18n.t('js.onboarding.buttons.next') }, - onNext() { - jQuery('.work-packages-back-button')[0].click(); - }, - }, { 'next .add-work-package': I18n.t('js.onboarding.steps.wp.create_button'), showSkip: false, @@ -37,24 +15,16 @@ export function wpOnboardingTourSteps():OnboardingStep[] { waitForElement('#work-packages-filter-toggle-button .badge', '#content', () => { resolve(undefined); }); - }), - onNext() { - jQuery('.main-menu--arrow-left-to-project')[0].click(); - }, + }) }, { - 'next #main-menu-gantt': I18n.t('js.onboarding.steps.wp.gantt_menu'), + 'next .wp-table--row': I18n.t('js.onboarding.steps.wp.list'), showSkip: false, nextButton: { text: I18n.t('js.onboarding.buttons.next') }, onNext() { - jQuery('#main-menu-gantt')[0].click(); + const firstId = document.querySelectorAll('.inline-edit--display-field.id a ')[0].innerHTML; + window.location.href = `${window.location.origin}/projects/demo-project/work_packages/${firstId}/activity`; }, - }, - { - containerClass: '-dark -hidden-arrow', - onBeforeStart() { - window.location.href = `${window.location.origin}/projects/demo-project/gantt`; - }, - }, + } ]; } diff --git a/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts b/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts index 61930df3c99..e2166f50c39 100644 --- a/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts +++ b/frontend/src/app/features/bim/ifc_models/pages/viewer/ifc-viewer-page.component.ts @@ -110,7 +110,6 @@ export class IFCViewerPageComponent component: WorkPackageCreateButtonComponent, inputs: { stateName$: of(this.newRoute), - allowed: ['work_packages.createWorkPackage', 'work_package.copy'], }, }, { diff --git a/frontend/src/app/features/boards/board/board-list/board-list.component.ts b/frontend/src/app/features/boards/board/board-list/board-list.component.ts index afe97dd31ab..bef95fe310f 100644 --- a/frontend/src/app/features/boards/board/board-list/board-list.component.ts +++ b/frontend/src/app/features/boards/board/board-list/board-list.component.ts @@ -15,9 +15,7 @@ import { LoadingIndicatorService, withLoadingIndicator, } from 'core-app/core/loading-indicator/loading-indicator.service'; -import { - WorkPackageInlineCreateService, -} from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service'; +import { WorkPackageInlineCreateService } from 'core-app/features/work-packages/components/wp-inline-create/wp-inline-create.service'; import { BoardInlineCreateService } from 'core-app/features/boards/board/board-list/board-inline-create.service'; import { AbstractWidgetComponent } from 'core-app/shared/components/grids/widgets/abstract-widget.component'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -25,59 +23,49 @@ import { ToastService } from 'core-app/shared/components/toaster/toast.service'; import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space'; import { Board } from 'core-app/features/boards/board/board'; import { AuthorisationService } from 'core-app/core/model-auth/model-auth.service'; -import { - Highlighting, -} from 'core-app/features/work-packages/components/wp-fast-table/builders/highlighting/highlighting.functions'; -import { - WorkPackageCardViewComponent, -} from 'core-app/features/work-packages/components/wp-card-view/wp-card-view.component'; -import { - WorkPackageStatesInitializationService, -} from 'core-app/features/work-packages/components/wp-list/wp-states-initialization.service'; +import { Highlighting } from 'core-app/features/work-packages/components/wp-fast-table/builders/highlighting/highlighting.functions'; +import { WorkPackageCardViewComponent } from 'core-app/features/work-packages/components/wp-card-view/wp-card-view.component'; +import { WorkPackageStatesInitializationService } from 'core-app/features/work-packages/components/wp-list/wp-states-initialization.service'; import { BoardService } from 'core-app/features/boards/board/board.service'; -import { - HalResourceEditingService, -} from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service'; +import { HalResourceEditingService } from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service'; import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service'; -import { - BoardActionsRegistryService, -} from 'core-app/features/boards/board/board-actions/board-actions-registry.service'; +import { BoardActionsRegistryService } from 'core-app/features/boards/board/board-actions/board-actions-registry.service'; import { BoardActionService } from 'core-app/features/boards/board/board-actions/board-action.service'; import { ComponentType } from '@angular/cdk/portal'; import { CausedUpdatesService } from 'core-app/features/boards/board/caused-updates/caused-updates.service'; import { BoardListMenuComponent } from 'core-app/features/boards/board/board-list/board-list-menu.component'; import { debugLog } from 'core-app/shared/helpers/debug_output'; -import { - WorkPackageCardDragAndDropService, -} from 'core-app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service'; +import { WorkPackageCardDragAndDropService } from 'core-app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service'; import { BoardFiltersService } from 'core-app/features/boards/board/board-filter/board-filters.service'; -import { StateService, TransitionService } from '@uirouter/core'; import { - WorkPackageViewFocusService, -} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-focus.service'; + StateService, + TransitionService, +} from '@uirouter/core'; +import { WorkPackageViewFocusService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-focus.service'; +import { WorkPackageViewSelectionService } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-selection.service'; +import { BoardListCrossSelectionService } from 'core-app/features/boards/board/board-list/board-list-cross-selection.service'; import { - WorkPackageViewSelectionService, -} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-selection.service'; -import { - BoardListCrossSelectionService, -} from 'core-app/features/boards/board/board-list/board-list-cross-selection.service'; -import { debounceTime, filter, map } from 'rxjs/operators'; + debounceTime, + filter, + map, +} from 'rxjs/operators'; import { ChangeItem } from 'core-app/shared/components/fields/changeset/changeset'; import { WorkPackageChangeset } from 'core-app/features/work-packages/components/wp-edit/work-package-changeset'; import { SchemaCacheService } from 'core-app/core/schemas/schema-cache.service'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import { ApiV3Filter } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder'; -import { - KeepTabService, -} from 'core-app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service'; +import { KeepTabService } from 'core-app/features/work-packages/components/wp-single-view-tabs/keep-tab/keep-tab.service'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { QueryResource } from 'core-app/features/hal/resources/query-resource'; -import { HalEvent, HalEventsService } from 'core-app/features/hal/services/hal-events.service'; +import { + HalEvent, + HalEventsService, +} from 'core-app/features/hal/services/hal-events.service'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { firstValueFrom } from 'rxjs'; -import { - WorkPackageIsolatedQuerySpaceDirective, -} from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive'; +import { WorkPackageIsolatedQuerySpaceDirective } from 'core-app/features/work-packages/directives/query-space/wp-isolated-query-space.directive'; +import { CurrentProjectService } from 'core-app/core/current-project/current-project.service'; +import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; export interface DisabledButtonPlaceholder { text:string; @@ -160,7 +148,8 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni public buttonPlaceholder:DisabledButtonPlaceholder|undefined; - constructor(readonly apiv3Service:ApiV3Service, + constructor( + readonly apiv3Service:ApiV3Service, readonly I18n:I18nService, readonly state:StateService, readonly cdRef:ChangeDetectorRef, @@ -184,7 +173,9 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni readonly boardActionRegistry:BoardActionsRegistryService, readonly causedUpdates:CausedUpdatesService, readonly keepTab:KeepTabService, - readonly $state:StateService) { + readonly currentProject:CurrentProjectService, + readonly pathHelper:PathHelperService, + ) { super(I18n, injector); } @@ -498,10 +489,9 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni openFullViewOnDoubleClick(event:{ workPackageId:string, double:boolean }) { if (event.double) { - this.state.go( - 'work-packages.show', - { workPackageId: event.workPackageId }, - ); + const projectIdentifier = this.currentProject.identifier; + const link = this.pathHelper.genericWorkPackagePath(projectIdentifier, event.workPackageId) + window.location.search; + Turbo.visit(link, { action: 'advance' }); } } @@ -511,7 +501,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni if (event.requestedState === 'split') { this.keepTab.goCurrentDetailsState(params); } else { - this.keepTab.goCurrentShowState(params); + this.keepTab.goCurrentShowState(params.workPackageId); } } diff --git a/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts b/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts index 4ca0647a9d1..3868f298ab7 100644 --- a/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts +++ b/frontend/src/app/features/boards/board/board-partitioned-page/board-partitioned-page.component.ts @@ -84,9 +84,6 @@ export class BoardPartitionedPageComponent extends UntilDestroyedMixin { /** Whether the board is editable */ editable:boolean; - /** Go back to boards using back-button */ - backButtonCallback:() => void; - /** Current query title to render */ selectedTitle?:string; diff --git a/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts b/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts index 63b8dc130ef..1035ee11671 100644 --- a/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts +++ b/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts @@ -70,9 +70,6 @@ export class WorkPackagesCalendarPageComponent extends PartitionedQuerySpacePage unsaved_title: this.I18n.t('js.calendar.unsaved_title'), }; - /** Go back using back-button */ - backButtonCallback:() => void; - breadcrumbItems() { return [ { href: this.pathHelperService.homePath(), text: this.titleService.appTitle }, diff --git a/frontend/src/app/features/hal/resources/work-package-resource.ts b/frontend/src/app/features/hal/resources/work-package-resource.ts index 41eaaaabbab..9e05688eaeb 100644 --- a/frontend/src/app/features/hal/resources/work-package-resource.ts +++ b/frontend/src/app/features/hal/resources/work-package-resource.ts @@ -176,10 +176,8 @@ export class WorkPackageBaseResource extends HalResource { * Return ": (#)" if type and id are known. */ public subjectWithType(truncateSubject = 40):string { - const type = this.type ? `${this.type.name}: ` : ''; - const subject = this.subjectWithId(truncateSubject); - - return `${type}${subject}`; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + return `${this.type.name}: ${this.subjectWithId(truncateSubject)}`; } /** @@ -187,9 +185,12 @@ export class WorkPackageBaseResource extends HalResource { */ public subjectWithId(truncateSubject = 40):string { const id = isNewResource(this) ? '' : ` (#${this.id || ''})`; - const subject = truncateSubject <= 0 ? this.subject : _.truncate(this.subject, { length: truncateSubject }); - return `${subject}${id}`; + return `${this.truncatedSubject(truncateSubject)}${id}`; + } + + public truncatedSubject(length = 40):string { + return length <= 0 ? this.subject : _.truncate(this.subject, { length: length }); } public get isLeaf():boolean { diff --git a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html index bfcb2b6dcc4..01e403fe667 100644 --- a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html +++ b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.html @@ -14,9 +14,9 @@ class="op-ian-item--work-package-id-link spot-link" [class.spot-link_inactive]="isMobile()" [attr.title]="workPackage.subject" - uiSref="work-packages.show" - [uiParams]="{workPackageId: workPackage.id, projects: null, projectPath: null}" [textContent]="'#' + workPackage.id" + [attr.href]="fullScreenLink()" + (click)="onLinkClick($event)" > @if (project) { diff --git a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts index 0d368a7595d..cca7189c6a8 100644 --- a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts @@ -110,10 +110,20 @@ export class InAppNotificationEntryComponent implements OnInit { } showFullView():void { - const href = this.notification._links.resource?.href; - const id = href && HalResource.matchFromLink(href, 'work_packages'); + if (!this.workPackageId) { + return; + } - this.storeService.openFullView(id); + const link = this.pathHelper.workPackagePath(this.workPackageId) + window.location.search; + Turbo.visit(link, { action: 'advance' }); + } + + fullScreenLink():string { + return this.workPackageId ? this.pathHelper.workPackagePath(this.workPackageId) : this.pathHelper.workPackagesPath(null); + } + + onLinkClick(e:Event):void { + e.stopPropagation(); } projectClicked(event:MouseEvent):void { // eslint-disable-line class-methods-use-this diff --git a/frontend/src/app/features/team-planner/team-planner/page/team-planner-page.component.ts b/frontend/src/app/features/team-planner/team-planner/page/team-planner-page.component.ts index 2551c433579..d1760f9120c 100644 --- a/frontend/src/app/features/team-planner/team-planner/page/team-planner-page.component.ts +++ b/frontend/src/app/features/team-planner/team-planner/page/team-planner-page.component.ts @@ -52,9 +52,6 @@ export class TeamPlannerPageComponent extends PartitionedQuerySpacePageComponent unsaved_title: this.I18n.t('js.team_planner.unsaved_title'), }; - /** Go back using back-button */ - backButtonCallback:() => void; - /** Current query title to render */ selectedTitle = this.text.unsaved_title; diff --git a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts index ba3c405a15f..d33d7121eb1 100644 --- a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts +++ b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts @@ -855,7 +855,7 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit, if (event.requestedState === 'split') { this.keepTab.goCurrentDetailsState(params); } else { - this.keepTab.goCurrentShowState(params); + this.keepTab.goCurrentShowState(params.workPackageId); } } diff --git a/frontend/src/app/features/work-packages/components/back-routing/back-button.component.sass b/frontend/src/app/features/work-packages/components/back-routing/back-button.component.sass index 1ff3ecbb8bc..4b81c9a5de4 100644 --- a/frontend/src/app/features/work-packages/components/back-routing/back-button.component.sass +++ b/frontend/src/app/features/work-packages/components/back-routing/back-button.component.sass @@ -10,7 +10,3 @@ &--icon font-size: 1rem line-height: 22px - - &_mobile-limited-width - @media only screen and (max-width: $breakpoint-sm) - width: 64px diff --git a/frontend/src/app/features/work-packages/components/back-routing/back-button.component.ts b/frontend/src/app/features/work-packages/components/back-routing/back-button.component.ts index 026eafaf792..8cda6114a8c 100644 --- a/frontend/src/app/features/work-packages/components/back-routing/back-button.component.ts +++ b/frontend/src/app/features/work-packages/components/back-routing/back-button.component.ts @@ -40,21 +40,17 @@ import { I18nService } from 'core-app/core/i18n/i18n.service'; export class BackButtonComponent { @Input() public linkClass:string; - @Input() public customBackMethod:() => unknown; - public text = { goBack: this.I18n.t('js.button_back'), }; - constructor(readonly backRoutingService:BackRoutingService, - readonly I18n:I18nService) { + constructor( + readonly backRoutingService:BackRoutingService, + readonly I18n:I18nService, + ) { } public goBack():void { - if (this.customBackMethod) { - this.customBackMethod(); - } else { - this.backRoutingService.goBack(); - } + this.backRoutingService.goBack(); } } diff --git a/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.component.ts b/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.component.ts index e8c11bca152..b287fabf2ed 100644 --- a/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.component.ts @@ -27,12 +27,16 @@ //++ import { - Component, Input, EventEmitter, Output, + Component, + EventEmitter, + Input, + Output, } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { WorkPackageRelationsHierarchyService } from 'core-app/features/work-packages/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { WorkPackageNotificationService } from 'core-app/features/work-packages/services/notifications/work-package-notification.service'; +import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; @Component({ templateUrl: './wp-breadcrumb-parent.html', @@ -59,6 +63,7 @@ export class WorkPackageBreadcrumbParentComponent { protected readonly I18n:I18nService, protected readonly wpRelationsHierarchy:WorkPackageRelationsHierarchyService, protected readonly notificationService:WorkPackageNotificationService, + protected readonly pathHelper:PathHelperService, ) { } @@ -103,4 +108,9 @@ export class WorkPackageBreadcrumbParentComponent { this.onSwitch.emit(this.editing); } } + + public switchToFullscreenForWp(wp:WorkPackageResource):void { + const link = this.pathHelper.genericWorkPackagePath(wp.project?.identifier, wp.id!) + window.location.search; + Turbo.visit(link, { action: 'advance' }); + } } diff --git a/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.html b/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.html index 7d5b038daaf..8a9cdbaa175 100644 --- a/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.html +++ b/frontend/src/app/features/work-packages/components/wp-breadcrumb/wp-breadcrumb-parent.html @@ -2,8 +2,7 @@ @if (parent) { diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.component.ts b/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.component.ts index 99f47a24988..5ea48055cdf 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.component.ts @@ -43,10 +43,10 @@ import { CurrentUserService } from 'core-app/core/current-user/current-user.serv standalone: false, }) export class WorkPackageCreateButtonComponent extends UntilDestroyedMixin implements OnInit, OnDestroy { - @Input('allowed') allowedWhen:string[]; - @Input('stateName$') stateName$:Observable; + @Input() routedFromAngular:boolean = true; + allowed:boolean; disabled:boolean; diff --git a/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.html b/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.html index da0b5279f33..bd58022e259 100644 --- a/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.html +++ b/frontend/src/app/features/work-packages/components/wp-buttons/wp-create-button/wp-create-button.html @@ -8,6 +8,7 @@ [stateName]="stateName$ | async" [dropdownActive]="allowed" [title]="text.title" + [routedFromAngular]="routedFromAngular" > ((resolve, reject) => { diff --git a/frontend/src/app/features/work-packages/components/wp-details/wp-details-toolbar.html b/frontend/src/app/features/work-packages/components/wp-details/wp-details-toolbar.html index b79fca923d7..3706b4ff49d 100644 --- a/frontend/src/app/features/work-packages/components/wp-details/wp-details-toolbar.html +++ b/frontend/src/app/features/work-packages/components/wp-details/wp-details-toolbar.html @@ -20,6 +20,7 @@