From a1fded27ae5c3d631f183a9acd2c4b4af52c3f1d Mon Sep 17 00:00:00 2001 From: Henriette Darge Date: Fri, 12 Jun 2026 14:34:44 +0200 Subject: [PATCH] Add a tab for project attributes within the WP view --- .../project_attributes_tab_component.html.erb | 19 +++++ .../project_attributes_tab_component.rb | 49 +++++++++++++ .../project_attributes_tab_controller.rb | 47 ++++++++++++ config/initializers/permissions.rb | 3 +- config/locales/js-en.yml | 5 +- config/routes.rb | 1 + .../op-project-attributes-tab.component.html | 13 ++++ .../op-project-attributes-tab.component.ts | 71 +++++++++++++++++++ .../services/wp-tabs/wp-tabs.service.ts | 6 ++ .../openproject-work-packages.module.ts | 4 ++ 10 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 app/components/work_packages/project_attributes_tab_component.html.erb create mode 100644 app/components/work_packages/project_attributes_tab_component.rb create mode 100644 app/controllers/work_packages/project_attributes_tab_controller.rb create mode 100644 frontend/src/app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component.html create mode 100644 frontend/src/app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component.ts diff --git a/app/components/work_packages/project_attributes_tab_component.html.erb b/app/components/work_packages/project_attributes_tab_component.html.erb new file mode 100644 index 00000000000..74c9d3ccd99 --- /dev/null +++ b/app/components/work_packages/project_attributes_tab_component.html.erb @@ -0,0 +1,19 @@ + + <%= + component_wrapper do + if project_custom_fields_grouped_by_section.any? + render(Primer::OpenProject::SidePanel.new(spacious: true)) do |panel| + project_custom_fields_grouped_by_section.each do |project_custom_field_section, project_custom_fields| + panel.with_section( + Overviews::ProjectCustomFields::SectionComponent.new( + project: @project, + project_custom_field_section:, + project_custom_fields: + ) + ) + end + end + end + end + %> + diff --git a/app/components/work_packages/project_attributes_tab_component.rb b/app/components/work_packages/project_attributes_tab_component.rb new file mode 100644 index 00000000000..df6cc9c2af4 --- /dev/null +++ b/app/components/work_packages/project_attributes_tab_component.rb @@ -0,0 +1,49 @@ +# 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. +#++ + +class WorkPackages::ProjectAttributesTabComponent < ApplicationComponent + include OpPrimer::ComponentHelpers + include OpTurbo::Streamable + + def initialize(work_package:) + super + + @work_package = work_package + @project = work_package.project + end + + private + + def project_custom_fields_grouped_by_section + @project_custom_fields_grouped_by_section ||= + @project.available_custom_fields + .group_by(&:project_custom_field_section) + end +end diff --git a/app/controllers/work_packages/project_attributes_tab_controller.rb b/app/controllers/work_packages/project_attributes_tab_controller.rb new file mode 100644 index 00000000000..f0816a1536f --- /dev/null +++ b/app/controllers/work_packages/project_attributes_tab_controller.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +# -- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2010-2023 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. +# ++ + +class WorkPackages::ProjectAttributesTabController < ApplicationController + before_action :find_work_package + before_action :authorize + + def index + render(WorkPackages::ProjectAttributesTabComponent.new(work_package: @work_package)) + end + + private + + def find_work_package + @work_package = WorkPackage.visible.find(params.expect(:id)) + @project = @work_package.project + rescue ActiveRecord::RecordNotFound + render_404 + end +end diff --git a/config/initializers/permissions.rb b/config/initializers/permissions.rb index 1a24afa7f6b..b556735ae36 100644 --- a/config/initializers/permissions.rb +++ b/config/initializers/permissions.rb @@ -314,7 +314,8 @@ Rails.application.reloader.to_prepare do "work_packages/menus": %i[show], "work_packages/hover_card": %i[show], work_package_relations_tab: %i[index], - "work_packages/reminders": %i[modal_body create update destroy] + "work_packages/reminders": %i[modal_body create update destroy], + "work_packages/project_attributes_tab": %i[index] }, permissible_on: %i[work_package project], contract_actions: { work_packages: %i[read] } diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index 2f1eaf5d883..6b2f7206532 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -968,11 +968,12 @@ en: relation_filters: filter_work_packages_by_relation_type: "Filter work packages by relation type" tabs: - overview: Overview activity: Activity + files: Files + project_attributes: Project attributes + overview: Overview relations: Relations watchers: Watchers - files: Files time_relative: days: "days" weeks: "weeks" diff --git a/config/routes.rb b/config/routes.rb index e12cf76bc09..278c9abb26a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -909,6 +909,7 @@ Rails.application.routes.draw do concerns :shareable get "hover_card" => "work_packages/hover_card#show", on: :member + get "project_attributes" => "work_packages/project_attributes_tab#index", on: :member get "generate_pdf_dialog" => "work_packages#generate_pdf_dialog", on: :member post "generate_pdf" => "work_packages#generate_pdf", on: :member diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component.html b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component.html new file mode 100644 index 00000000000..c5adf2f0403 --- /dev/null +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component.html @@ -0,0 +1,13 @@ +
+ + + + + + + + + + + +
diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component.ts new file mode 100644 index 00000000000..3a585ec7153 --- /dev/null +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component.ts @@ -0,0 +1,71 @@ +// -- 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, + inject, + Input, + OnInit, +} from '@angular/core'; + +import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; +import { I18nService } from 'core-app/core/i18n/i18n.service'; +import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; +import { UIRouterGlobals } from '@uirouter/core'; + +@Component({ + selector: 'op-project-attributes-tab', + templateUrl: './op-project-attributes-tab.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +export class WorkPackageProjectAttributesTabComponent implements OnInit { + readonly I18n = inject(I18nService); + readonly uiRouterGlobals = inject(UIRouterGlobals); + readonly pathHelper = inject(PathHelperService); + + public turboFrameSrc:string; + public workPackageId:string; + + @Input() public workPackage:WorkPackageResource; + + ngOnInit() { + const { workPackageId } = this.uiRouterGlobals.params as unknown as { workPackageId:string }; + this.workPackageId = (this.workPackage.id!) || workPackageId; + + this.turboFrameSrc = this.buildTurboFrameSrc(); + } + + protected buildTurboFrameSrc():string { + const baseUrl = window.location.origin; + const url = new URL(`${this.pathHelper.staticBase}/work_packages/${this.workPackageId}/project_attributes`, baseUrl); + + return url.toString(); + } +} diff --git a/frontend/src/app/features/work-packages/components/wp-tabs/services/wp-tabs/wp-tabs.service.ts b/frontend/src/app/features/work-packages/components/wp-tabs/services/wp-tabs/wp-tabs.service.ts index 2890616f578..59feeec31be 100644 --- a/frontend/src/app/features/work-packages/components/wp-tabs/services/wp-tabs/wp-tabs.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-tabs/services/wp-tabs/wp-tabs.service.ts @@ -59,6 +59,7 @@ import { import { workPackageFilesCount, } from 'core-app/features/work-packages/components/wp-tabs/services/wp-tabs/wp-files-count.function'; +import { WorkPackageProjectAttributesTabComponent } from 'core-app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component'; @Injectable({ providedIn: 'root', @@ -146,6 +147,11 @@ export class WorkPackageTabsService { count: workPackageNotificationsCount, showCountAsBubble: true, }, + { + id: 'project_attributes', + component: WorkPackageProjectAttributesTabComponent, + name: I18n.t('js.work_packages.tabs.project_attributes'), + }, { id: 'files', component: WorkPackageFilesTabComponent, 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 ab4663dbc5a..291772e54bd 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 @@ -409,6 +409,7 @@ import { WorkPackageFullViewEntryComponent } from 'core-app/features/work-packag import { WorkPackageSplitCreateEntryComponent, } from 'core-app/features/work-packages/routing/wp-split-create/wp-split-create-entry.component'; +import { WorkPackageProjectAttributesTabComponent } from 'core-app/features/work-packages/components/wp-single-view-tabs/project-attributes-tab/op-project-attributes-tab.component'; @NgModule({ imports: [ @@ -589,6 +590,9 @@ import { // Files tab WorkPackageFilesTabComponent, + // Project attributes tab + WorkPackageProjectAttributesTabComponent, + // Split view WorkPackageDetailsViewButtonComponent, WorkPackageSplitViewComponent,