diff --git a/app/models/work_package.rb b/app/models/work_package.rb index 85a8f245e67b..de43b280d884 100644 --- a/app/models/work_package.rb +++ b/app/models/work_package.rb @@ -61,8 +61,8 @@ class WorkPackage < ApplicationRecord has_many :storages, through: :project - has_many :meeting_agenda_items # should become through issue relation - has_many :issues, dependent: :destroy + has_many :issues, class_name: 'WorkPackageIssue', dependent: :destroy + has_many :meeting_agenda_items, through: :issues has_and_belongs_to_many :changesets, -> { # rubocop:disable Rails/HasAndBelongsToMany order("#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC") diff --git a/frontend/src/stimulus/controllers/dynamic/meeting-agenda-item-form.controller.ts b/frontend/src/stimulus/controllers/dynamic/meeting-agenda-item-form.controller.ts index 59f83d7bab14..65f4e95b287d 100644 --- a/frontend/src/stimulus/controllers/dynamic/meeting-agenda-item-form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/meeting-agenda-item-form.controller.ts @@ -37,13 +37,11 @@ export default class extends Controller { } declare cancelUrlValue: string - static targets = [ "titleInput", "clarificationNeedInput", "clarificationInput", "workPackageInput", "workPackageButton", "detailsInput"] + static targets = [ "titleInput", "issueInput", "issueButton", "detailsInput"] declare readonly titleInputTarget: HTMLInputElement - declare readonly clarificationNeedInputTarget: HTMLInputElement - declare readonly clarificationInputTarget: HTMLInputElement declare readonly detailsInputTarget: HTMLInputElement - declare readonly workPackageInputTarget: HTMLInputElement - declare readonly workPackageButtonTarget: HTMLInputElement + declare readonly issueInputTarget: HTMLInputElement + declare readonly issueButtonTarget: HTMLInputElement connect(): void { this.focusInput(); @@ -76,26 +74,6 @@ export default class extends Controller { } } - async addClarificationNeed() { - this.clarificationNeedInputTarget.classList.remove("d-none"); - const textarea = this.element.querySelector('textarea[name="meeting_agenda_item[input]"]'); - setTimeout(() => { - if(textarea) { - (textarea as HTMLInputElement).focus(); - } - }, 100); - } - - async addClarification() { - this.clarificationInputTarget.classList.remove("d-none"); - const textarea = this.element.querySelector('textarea[name="meeting_agenda_item[output]"]'); - setTimeout(() => { - if(textarea) { - (textarea as HTMLInputElement).focus(); - } - }, 100); - } - async addDetails() { this.detailsInputTarget.classList.remove("d-none"); const textarea = this.element.querySelector('textarea[name="meeting_agenda_item[details]"]'); @@ -106,11 +84,11 @@ export default class extends Controller { }, 100); } - async addWorkPackage() { + async addIssue() { this.titleInputTarget.classList.add("d-none"); - this.workPackageButtonTarget.classList.add("d-none"); - this.workPackageInputTarget.classList.remove("d-none"); - const select = this.element.querySelector('select[name="meeting_agenda_item[work_package_id]"]'); + this.issueButtonTarget.classList.add("d-none"); + this.issueInputTarget.classList.remove("d-none"); + const select = this.element.querySelector('select[name="meeting_agenda_item[work_package_issue_id]"]'); setTimeout(() => { if(select) { (select as HTMLInputElement).focus(); diff --git a/modules/meeting/app/components/meeting_agenda_items/form_component.rb b/modules/meeting/app/components/meeting_agenda_items/form_component.rb index 32792609e188..ba647ecaa183 100644 --- a/modules/meeting/app/components/meeting_agenda_items/form_component.rb +++ b/modules/meeting/app/components/meeting_agenda_items/form_component.rb @@ -30,12 +30,11 @@ module MeetingAgendaItems class FormComponent < Base::Component include OpTurbo::Streamable - def initialize(meeting:, meeting_agenda_item:, method:, submit_path:, cancel_path:, active_work_package: nil) + def initialize(meeting:, meeting_agenda_item:, method:, submit_path:, cancel_path:) super @meeting = meeting @meeting_agenda_item = meeting_agenda_item - @active_work_package = active_work_package @method = method @submit_path = submit_path @cancel_path = cancel_path @@ -45,65 +44,10 @@ def call component_wrapper(data: wrapper_data_attributes) do primer_form_with( model: @meeting_agenda_item, - method: :post, method: @method, url: @submit_path ) do |f| - flex_layout do |flex| - flex.with_row do - hidden_field_tag :work_package_id, @active_work_package&.id - end - flex.with_row(flex_layout: true) do |flex| - flex.with_column(flex: 1, flex_layout: true, mr: 5) do |flex| - flex.with_column(flex: 1, data: { 'meeting-agenda-item-form-target': "titleInput" }, - display: display_title_input_value) do - render(MeetingAgendaItem::New::Title.new(f, disabled: !@meeting.agenda_items_open?)) - end - unless @active_work_package.present? - flex.with_column(flex: 1, data: { 'meeting-agenda-item-form-target': "workPackageInput" }, - display: display_work_package_input_value) do - render(MeetingAgendaItem::New::WorkPackage.new(f, disabled: !@meeting.agenda_items_open?)) - end - if @meeting.agenda_items_open? - flex.with_column(ml: 2, data: { 'meeting-agenda-item-form-target': "workPackageButton" }, - display: display_work_package_button_value) do - render(Primer::Beta::Button.new(data: { action: 'click->meeting-agenda-item-form#addWorkPackage keydown.enter->meeting-agenda-item-form#addWorkPackage' })) do |_button| - "Reference work package instead" - end - end - end - end - end - unless @active_work_package.present? - flex.with_column(ml: 2) do - render(MeetingAgendaItem::New::Duration.new(f, disabled: !@meeting.agenda_items_open?)) - end - flex.with_column(ml: 2) do - render(MeetingAgendaItem::New::Author.new(f, disabled: !@meeting.agenda_items_open?)) - end - end - end - flex.with_row(mt: 2, data: { 'meeting-agenda-item-form-target': "detailsInput" }, - display: display_details_input_value) do - render(MeetingAgendaItem::New::Details.new(f)) - end - flex.with_row(mt: 2, data: { 'meeting-agenda-item-form-target': "clarificationNeedInput" }, - display: display_clarification_need_input_value) do - render(MeetingAgendaItem::New::ClarificationNeed.new(f)) - end - flex.with_row(mt: 2, data: { 'meeting-agenda-item-form-target': "clarificationInput" }, - display: display_clarification_input_value) do - render(MeetingAgendaItem::New::Clarification.new(f)) - end - flex.with_row(mt: 2) do - action_menu_partial - end - flex.with_row(flex_layout: true, justify_content: :flex_end, mt: 2) do |flex| - flex.with_column do - render(MeetingAgendaItem::New::Submit.new(f, preselected_work_package: @active_work_package)) - end - end - end + form_content_partial(f) end end end @@ -118,32 +62,66 @@ def wrapper_data_attributes } end + def form_content_partial(f) + flex_layout do |flex| + flex.with_row(flex_layout: true) do |flex| + flex.with_column(flex: 1, flex_layout: true, mr: 5) do |flex| + flex.with_column(flex: 1, data: { 'meeting-agenda-item-form-target': "titleInput" }, + display: display_title_input_value) do + render(MeetingAgendaItem::Title.new(f, disabled: !@meeting.agenda_items_open?)) + end + flex.with_column(flex: 1, data: { 'meeting-agenda-item-form-target': "issueInput" }, + display: display_issue_input_value) do + render(MeetingAgendaItem::Issue.new(f, disabled: !@meeting.agenda_items_open?)) + end + if @meeting.agenda_items_open? + flex.with_column(ml: 2, data: { 'meeting-agenda-item-form-target': "issueButton" }, + display: display_issue_button_value) do + render(Primer::Beta::Button.new(data: { action: 'click->meeting-agenda-item-form#addIssue keydown.enter->meeting-agenda-item-form#addIssue' })) do |_button| + "Reference issue instead" + end + end + end + end + flex.with_column(ml: 2) do + render(MeetingAgendaItem::Duration.new(f, disabled: !@meeting.agenda_items_open?)) + end + # flex.with_column(ml: 2) do + # render(MeetingAgendaItem::Author.new(f, disabled: !@meeting.agenda_items_open?)) + # end + end + # flex.with_row(mt: 2) do + # action_menu_partial + # end + flex.with_row(flex_layout: true, justify_content: :flex_end, mt: 2) do |flex| + flex.with_column(mr: 2) do + back_link_partial + end + flex.with_column do + render(MeetingAgendaItem::Submit.new(f)) + end + end + end + end + def display_title_input_value - @meeting_agenda_item.work_package.present? ? :none : :block + @meeting_agenda_item.work_package_issue.present? ? :none : :block end - def display_work_package_button_value + def display_issue_button_value display_title_input_value end - def display_work_package_input_value - @meeting_agenda_item.work_package.nil? ? :none : nil + def display_issue_input_value + @meeting_agenda_item.work_package_issue.nil? ? :none : nil end def display_details_input_value @meeting_agenda_item.details.blank? ? :none : nil end - def display_clarification_need_input_value - @meeting_agenda_item.input.blank? ? :none : nil - end - - def display_clarification_input_value - @meeting_agenda_item.output.blank? ? :none : nil - end - def action_menu_partial - if @meeting_agenda_item.details.blank? || @meeting_agenda_item.input.blank? || @meeting_agenda_item.output.blank? + if @meeting_agenda_item.details.blank? render(Primer::Alpha::ActionMenu.new(menu_id: "meeting-agenda-item-additional-fields-menu-#{@meeting_agenda_item.id || 'new'}")) do |menu| menu.with_show_button do |button| button.with_trailing_action_icon(icon: :'triangle-down') @@ -153,16 +131,19 @@ def action_menu_partial menu.with_item(label: "Details", data: { action: 'click->meeting-agenda-item-form#addDetails keydown.enter->meeting-agenda-item-form#addDetails' }) end - if @meeting_agenda_item.input.blank? - menu.with_item(label: "Clarification need", - data: { action: 'click->meeting-agenda-item-form#addClarificationNeed keydown.enter->meeting-agenda-item-form#addClarificationNeed' }) - end - if @meeting_agenda_item.output.blank? - menu.with_item(label: "Clarification", - data: { action: 'click->meeting-agenda-item-form#addClarification keydown.enter->meeting-agenda-item-form#addClarifciation' }) - end end end end + + def back_link_partial + render(Primer::Beta::Button.new( + scheme: :secondary, + tag: :a, + href: @cancel_path, + data: { confirm: 'Are you sure?', 'turbo-stream': true } + )) do |_c| + "Cancel" + end + end end end diff --git a/modules/meeting/app/components/meeting_agenda_items/item_component.rb b/modules/meeting/app/components/meeting_agenda_items/item_component.rb index 5f196d1333a4..e7367bb1e558 100644 --- a/modules/meeting/app/components/meeting_agenda_items/item_component.rb +++ b/modules/meeting/app/components/meeting_agenda_items/item_component.rb @@ -30,11 +30,10 @@ module MeetingAgendaItems class ItemComponent < Base::Component include OpTurbo::Streamable - def initialize(meeting_agenda_item:, active_work_package: nil, state: :show) + def initialize(meeting_agenda_item:, state: :show) super @meeting_agenda_item = meeting_agenda_item - @active_work_package = active_work_package @state = state end @@ -57,8 +56,7 @@ def call def child_component_params { - meeting_agenda_item: @meeting_agenda_item, - active_work_package: @active_work_package + meeting_agenda_item: @meeting_agenda_item } end end diff --git a/modules/meeting/app/components/meeting_agenda_items/item_component/edit_component.rb b/modules/meeting/app/components/meeting_agenda_items/item_component/edit_component.rb index c2e8a9524df2..480d49557ec9 100644 --- a/modules/meeting/app/components/meeting_agenda_items/item_component/edit_component.rb +++ b/modules/meeting/app/components/meeting_agenda_items/item_component/edit_component.rb @@ -28,18 +28,16 @@ module MeetingAgendaItems class ItemComponent::EditComponent < Base::Component - def initialize(meeting_agenda_item:, active_work_package: nil) + def initialize(meeting_agenda_item:) super @meeting_agenda_item = meeting_agenda_item - @active_work_package = active_work_package end def call render(MeetingAgendaItems::FormComponent.new( meeting: @meeting_agenda_item.meeting, meeting_agenda_item: @meeting_agenda_item, - active_work_package: @active_work_package, method: :put, submit_path: meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), cancel_path: cancel_edit_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item) diff --git a/modules/meeting/app/components/meeting_agenda_items/item_component/issue_resolution_component.rb b/modules/meeting/app/components/meeting_agenda_items/item_component/issue_resolution_component.rb new file mode 100644 index 000000000000..231bc8e71d33 --- /dev/null +++ b/modules/meeting/app/components/meeting_agenda_items/item_component/issue_resolution_component.rb @@ -0,0 +1,118 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-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. +#++ + +module MeetingAgendaItems + class ItemComponent::IssueResolutionComponent < Base::Component + include OpTurbo::Streamable + + def initialize(meeting_agenda_item:, issue:, state: :initial) + super + + @meeting_agenda_item = meeting_agenda_item + @issue = issue + @state = state + end + + def wrapper_uniq_by + @issue.id + end + + def call + component_wrapper do + return if @issue.closed? + + case @state + when :initial + initial_state_partial + when :edit + edit_state_partial + end + end + end + + private + + def initial_state_partial + flex_layout do |flex| + flex.with_column do + resolve_issue_button_partial + end + end + end + + def resolve_issue_button_partial + return if @meeting_agenda_item.meeting.agenda_items_closed? + + render(Primer::Beta::Button.new( + scheme: :secondary, + size: :small, + tag: :a, + href: edit_issue_resolution_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), + data: { 'turbo-stream': true } + )) do |_c| + "Resolve issue" + end + end + + def edit_state_partial + primer_form_with( + model: @issue, + url: submit_path + ) do |form| + flex_layout do |flex| + flex.with_row(mt: 2) do + render(Issue::Resolution.new(form)) + end + flex.with_row(flex_layout: true, justify_content: :flex_end, mt: 2) do |flex| + flex.with_column(mr: 2) do + back_link_partial + end + flex.with_column do + render(Issue::Submit.new(form)) + end + end + end + end + end + + def submit_path + resolve_issue_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item) + end + + def back_link_partial + render(Primer::Beta::Button.new( + scheme: :secondary, + tag: :a, + href: cancel_edit_issue_resolution_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), + data: { confirm: 'Are you sure?', 'turbo-stream': true } + )) do |_c| + "Cancel" + end + end + end +end diff --git a/modules/meeting/app/components/meeting_agenda_items/item_component/notes_component.rb b/modules/meeting/app/components/meeting_agenda_items/item_component/notes_component.rb new file mode 100644 index 000000000000..e14cb21347c3 --- /dev/null +++ b/modules/meeting/app/components/meeting_agenda_items/item_component/notes_component.rb @@ -0,0 +1,145 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-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. +#++ + +module MeetingAgendaItems + class ItemComponent::NotesComponent < Base::Component + include OpTurbo::Streamable + + def initialize(meeting_agenda_item:, state: :initial) + super + + @meeting_agenda_item = meeting_agenda_item + @state = state + end + + def wrapper_uniq_by + @meeting_agenda_item.id + end + + def call + component_wrapper do + case @state + when :initial + initial_state_partial + when :edit + edit_state_partial + end + end + end + + private + + def initial_state_partial + flex_layout do |flex| + if @meeting_agenda_item.details.present? + flex.with_row do + notes_display_partial + end + else + flex.with_row(mt: 1) do + add_notes_button_partial + end + end + end + end + + def notes_display_partial + render(Primer::Box.new(font_size: :small, bg: :subtle, border: true, border_radius: 2, p: 3)) do + flex_layout do |flex| + flex.with_row do + notes_content_partial + end + flex.with_row(mt: 2, ml: 4) do + add_notes_button_partial("Edit notes") + end + end + end + end + + def notes_content_partial + flex_layout do |flex| + flex.with_column(mr: 2) do + render(Primer::Beta::Octicon.new(icon: "note", 'aria-label': "open", color: :muted)) + end + flex.with_column do + simple_format(@meeting_agenda_item.details, {}, wrapper_tag: "span") + end + end + end + + def add_notes_button_partial(text = "Add notes") + return if @meeting_agenda_item.meeting.agenda_items_closed? + + render(Primer::Beta::Button.new( + scheme: :secondary, + size: :small, + tag: :a, + href: edit_notes_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), + data: { 'turbo-stream': true } + )) do |_c| + text + end + end + + def edit_state_partial + primer_form_with( + model: @meeting_agenda_item, + url: submit_path + ) do |form| + flex_layout do |flex| + flex.with_row(mt: 2) do + render(MeetingAgendaItem::Details.new(form)) + end + flex.with_row(flex_layout: true, justify_content: :flex_end, mt: 2) do |flex| + flex.with_column(mr: 2) do + back_link_partial + end + flex.with_column do + render(MeetingAgendaItem::Submit.new(form)) + end + end + end + end + end + + def submit_path + save_notes_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item) + end + + def back_link_partial + render(Primer::Beta::Button.new( + scheme: :secondary, + tag: :a, + href: cancel_edit_notes_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), + data: { confirm: 'Are you sure?', 'turbo-stream': true } + )) do |_c| + "Cancel" + end + end + end +end diff --git a/modules/meeting/app/components/meeting_agenda_items/item_component/show_component.rb b/modules/meeting/app/components/meeting_agenda_items/item_component/show_component.rb index 0b69c6f739c7..a1bbfd47ea25 100644 --- a/modules/meeting/app/components/meeting_agenda_items/item_component/show_component.rb +++ b/modules/meeting/app/components/meeting_agenda_items/item_component/show_component.rb @@ -28,17 +28,23 @@ module MeetingAgendaItems class ItemComponent::ShowComponent < Base::Component - def initialize(meeting_agenda_item:, active_work_package: nil) + def initialize(meeting_agenda_item:) super @meeting_agenda_item = meeting_agenda_item - @active_work_package = active_work_package end def call flex_layout(justify_content: :space_between, align_items: :flex_start) do |flex| - flex.with_column do - description_partial + flex.with_column(flex: 1, flex_layout: true) do |flex| + if drag_and_drop_enabled? + flex.with_column(mr: 2) do + drag_handler_partial + end + end + flex.with_column(flex: 1, mt: 2) do + description_partial + end end flex.with_column do right_column_partial @@ -60,25 +66,16 @@ def right_column_partial actions_partial end end - if drag_and_drop_enabled? - flex.with_column do - drag_handler_partial - end - end end end def drag_and_drop_enabled? - if @meeting_agenda_item.meeting.agenda_items_locked? || @meeting_agenda_item.meeting.agenda_items_closed? - false - else - @active_work_package.nil? - end + @meeting_agenda_item.meeting.agenda_items_open? end def drag_handler_partial render(Primer::Beta::IconButton.new( - ml: 2, + scheme: :invisible, classes: "handle", size: :medium, disabled: false, @@ -89,27 +86,47 @@ def drag_handler_partial end def show_time_slot? - @active_work_package.nil? + true end def edit_enabled? - if @active_work_package.nil? - true - else - @active_work_package&.id == @meeting_agenda_item.work_package&.id - end + true end - def work_package_partial - render(Primer::Box.new) do - link_to(work_package_path(@meeting_agenda_item.work_package), target: "_blank", rel: "noopener") do - render(Primer::Beta::Text.new(font_size: :normal, font_weight: :bold)) do - "##{@meeting_agenda_item.work_package.id} #{@meeting_agenda_item.work_package.subject}" + def issue_partial + flex_layout do |flex| + flex.with_row do + issue_link_partial + end + flex.with_row(flex_layout: true, mt: 2, bg: :subtle, border: true, border_radius: 2, p: 3) do |flex| + flex.with_row do + issue_content_partial end + flex.with_row(mt: 2, pl: 4) do + issue_resolution_partial + end + end + end + end + + def issue_link_partial + link_to(work_package_path(@meeting_agenda_item.work_package_issue.work_package), target: "_blank", rel: "noopener") do + render(Primer::Beta::Text.new(font_size: :normal, font_weight: :bold)) do + "##{@meeting_agenda_item.work_package_issue.work_package.id} #{@meeting_agenda_item.work_package_issue.work_package.subject}" end end end + def issue_content_partial + render(WorkPackageTab::Issues::ItemComponent.new(issue: @meeting_agenda_item.work_package_issue, + called_from_meeting: @meeting_agenda_item.meeting)) + end + + def issue_resolution_partial + render(MeetingAgendaItems::ItemComponent::IssueResolutionComponent.new(issue: @meeting_agenda_item.work_package_issue, + meeting_agenda_item: @meeting_agenda_item)) + end + def description_partial flex_layout do |flex| flex.with_row(mb: 2) do @@ -118,30 +135,18 @@ def description_partial # flex.with_row do # meta_info_partial # end - if @meeting_agenda_item.details.present? - flex.with_row do - details_partial - end - end - if @meeting_agenda_item.input.present? - flex.with_row do - input_partial - end - end - if @meeting_agenda_item.output.present? - flex.with_row do - output_partial - end + flex.with_row do + details_partial end end end def title_partial - if @meeting_agenda_item.work_package.present? - work_package_partial + if @meeting_agenda_item.work_package_issue.present? + issue_partial else render(Primer::Beta::Text.new(font_size: :normal, font_weight: :bold)) do - "#{@meeting_agenda_item.title}" + @meeting_agenda_item.title end end end @@ -166,48 +171,7 @@ def meta_info_partial end def details_partial - flex_layout do |flex| - flex.with_row do - render(Primer::Beta::Text.new(font_size: :small)) do - "Details:" - end - end - flex.with_row do - render(Primer::Box.new(font_size: :small, color: :muted)) do - simple_format(@meeting_agenda_item.details, {}, wrapper_tag: "span") - end - end - end - end - - def input_partial - flex_layout do |flex| - flex.with_row do - render(Primer::Beta::Text.new(font_size: :small)) do - "Clarification need:" - end - end - flex.with_row do - render(Primer::Box.new(font_size: :small, color: :muted)) do - simple_format(@meeting_agenda_item.input, {}, wrapper_tag: "span") - end - end - end - end - - def output_partial - flex_layout(mt: 1) do |flex| - flex.with_row do - render(Primer::Beta::Text.new(font_size: :small)) do - "Clarification:" - end - end - flex.with_row do - render(Primer::Box.new(font_size: :small, color: :muted)) do - simple_format(@meeting_agenda_item.output, {}, wrapper_tag: "span") - end - end - end + render(MeetingAgendaItems::ItemComponent::NotesComponent.new(meeting_agenda_item: @meeting_agenda_item)) end def time_slot_partial @@ -222,68 +186,92 @@ def time_slot_partial end def actions_partial - flex_layout(justify_content: :flex_end) do |flex| - flex.with_column do - edit_action_partial - end - flex.with_column do - delete_action_partial - end + render(Primer::Alpha::ActionMenu.new) do |menu| + menu.with_show_button(icon: "kebab-horizontal", 'aria-label': "Agenda item actions") + edit_action_item(menu) + # add_notes_action_item(menu) + delete_action_item(menu) end end - def edit_action_partial + def edit_action_item(menu) return if @meeting_agenda_item.meeting.agenda_items_closed? - form_with( - url: edit_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), - method: :get, - data: { 'turbo-stream': true } - ) do |_form| - flex_layout do |flex| - flex.with_row do - hidden_field_tag :work_package_id, @active_work_package&.id - end - flex.with_row do - render(Primer::Beta::IconButton.new( - size: :medium, - disabled: false, - icon: :pencil, - show_tooltip: true, - type: :submit, - 'aria-label': "Edit agenda item" - )) - end - end - end + menu.with_item(label: "Edit agenda item", + href: edit_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), + content_arguments: { + data: { 'turbo-stream': true } + }) end - def delete_action_partial + def delete_action_item(menu) return unless @meeting_agenda_item.meeting.agenda_items_open? - form_with( - url: meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), - method: :delete, - data: { 'turbo-stream': true, confirm: "Are you sure?" } - ) do |_form| - flex_layout do |flex| - flex.with_row do - hidden_field_tag :work_package_id, @active_work_package&.id - end - flex.with_row do - render(Primer::Beta::IconButton.new( - ml: 2, - scheme: :danger, - size: :medium, - disabled: false, - icon: :trash, - show_tooltip: true, - type: :submit, - 'aria-label': "Delete agenda item" - )) - end - end - end + menu.with_item(label: "Delete agenda item", + color: :danger, + href: meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), + form_arguments: { + method: :delete, data: { confirm: "Are you sure?", 'turbo-stream': true } + }) end + + # def actions_partial + # flex_layout(justify_content: :flex_end) do |flex| + # flex.with_column do + # edit_action_partial + # end + # flex.with_column do + # delete_action_partial + # end + # end + # end + + # def edit_action_partial + # return if @meeting_agenda_item.meeting.agenda_items_closed? + + # form_with( + # url: edit_meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), + # method: :get, + # data: { 'turbo-stream': true } + # ) do |_form| + # flex_layout do |flex| + # flex.with_row do + # render(Primer::Beta::IconButton.new( + # size: :medium, + # disabled: false, + # icon: :pencil, + # show_tooltip: true, + # type: :submit, + # 'aria-label': "Edit agenda item" + # )) + # end + # end + # end + # end + + # def delete_action_partial + # return unless @meeting_agenda_item.meeting.agenda_items_open? + + # form_with( + # url: meeting_agenda_item_path(@meeting_agenda_item.meeting, @meeting_agenda_item), + # method: :delete, + # data: { 'turbo-stream': true, confirm: "Are you sure?" } + # ) do |_form| + # flex_layout do |flex| + # flex.with_row do + # render(Primer::Beta::IconButton.new( + # ml: 2, + # scheme: :danger, + # size: :medium, + # disabled: false, + # icon: :trash, + # show_tooltip: true, + # type: :submit, + # 'aria-label': "Delete agenda item" + # )) + # end + # end + # end + # end end end diff --git a/modules/meeting/app/components/meeting_agenda_items/list_component.rb b/modules/meeting/app/components/meeting_agenda_items/list_component.rb index 78ea1437adcc..cd21299d0067 100644 --- a/modules/meeting/app/components/meeting_agenda_items/list_component.rb +++ b/modules/meeting/app/components/meeting_agenda_items/list_component.rb @@ -30,11 +30,10 @@ module MeetingAgendaItems class ListComponent < Base::Component include OpTurbo::Streamable - def initialize(meeting:, active_work_package: nil) + def initialize(meeting:) super @meeting = meeting - @active_work_package = active_work_package end def call @@ -59,24 +58,13 @@ def wrapper_data_attributes def row_partial(border_box, meeting_agenda_item) border_box.with_row( - scheme: row_scheme(meeting_agenda_item), + scheme: :default, data: { id: meeting_agenda_item.id, 'drop-url': drop_meeting_agenda_item_path(meeting_agenda_item.meeting, meeting_agenda_item) } ) do - render(MeetingAgendaItems::ItemComponent.new( - meeting_agenda_item:, - active_work_package: @active_work_package - )) - end - end - - def row_scheme(meeting_agenda_item) - if @active_work_package.present? && (@active_work_package&.id == meeting_agenda_item.work_package&.id) - :info - else - :default + render(MeetingAgendaItems::ItemComponent.new(meeting_agenda_item:)) end end end diff --git a/modules/meeting/app/components/meeting_agenda_items/new_section_component.rb b/modules/meeting/app/components/meeting_agenda_items/new_section_component.rb index 4e1a8cc172bf..c6ee239f4770 100644 --- a/modules/meeting/app/components/meeting_agenda_items/new_section_component.rb +++ b/modules/meeting/app/components/meeting_agenda_items/new_section_component.rb @@ -30,13 +30,11 @@ module MeetingAgendaItems class NewSectionComponent < Base::Component include OpTurbo::Streamable - def initialize(meeting:, meeting_agenda_item: nil, active_work_package: nil, state: :initial) + def initialize(meeting:, meeting_agenda_item: nil, state: :initial) super @meeting = meeting - @meeting_agenda_item = meeting_agenda_item || MeetingAgendaItem.new(meeting:, work_package: active_work_package, - user: User.current) - @active_work_package = active_work_package + @meeting_agenda_item = meeting_agenda_item || MeetingAgendaItem.new(meeting:, user: User.current) @state = state end @@ -62,13 +60,8 @@ def initial_state_partial url: new_meeting_agenda_item_path(@meeting), method: :get, data: { 'turbo-stream': true } - ) do |form| + ) do |_form| box_collection do |collection| - if @active_work_package.present? - collection.with_box do - form.hidden_field :work_package_id, value: @active_work_package.id - end - end collection.with_box do button_content_partial end @@ -88,11 +81,7 @@ def button_content_partial type: :submit, 'aria-label': "Add agenda item" )) do - if @active_work_package.present? - "Add work package to agenda" - else - "Add agenda item" - end + "Add agenda item" end end @@ -105,7 +94,6 @@ def form_state_partial render(MeetingAgendaItems::FormComponent.new( meeting: @meeting, meeting_agenda_item: @meeting_agenda_item, - active_work_package: @active_work_package, method: :post, submit_path: meeting_agenda_items_path(@meeting), cancel_path: cancel_new_meeting_agenda_items_path(@meeting) diff --git a/modules/meeting/app/components/work_package_tab/issues/item_component.rb b/modules/meeting/app/components/work_package_tab/issues/item_component.rb index 89eb48432fc3..8be5d69a8dcd 100644 --- a/modules/meeting/app/components/work_package_tab/issues/item_component.rb +++ b/modules/meeting/app/components/work_package_tab/issues/item_component.rb @@ -28,10 +28,11 @@ module WorkPackageTab class Issues::ItemComponent < Base::Component - def initialize(issue:) + def initialize(issue:, called_from_meeting: nil) super @issue = issue + @called_from_meeting = called_from_meeting end def call @@ -39,8 +40,10 @@ def call flex.with_column do content_partial end - flex.with_column do - actions_partial + unless @called_from_meeting + flex.with_column do + actions_partial + end end end end @@ -80,9 +83,16 @@ def issue_description_partial issue_meta_info_partial end end - flex.with_row(mt: 2, pl: 0) do + flex.with_row(mt: 2, mb: 1, pl: 0) do description_partial end + if @issue.meeting_agenda_items.any? && !@called_from_meeting + @issue.meeting_agenda_items.each do |meeting_agenda_item| + flex.with_row(mt: 1, pl: 0) do + meeting_assignment_partial(meeting_agenda_item) + end + end + end end end @@ -176,13 +186,44 @@ def reopen_action_item(menu) def add_to_meeting_action_item(menu) return unless @issue.open? - menu.with_item(label: "Add to meeting", href: "/") + menu.with_item(label: "Add to meeting", + href: new_meeting_work_package_issue_path( + work_package_id: @issue.work_package.id, id: @issue.id + )) end def count_active_work_package_references_in_meeting @meeting.agenda_items.where(work_package_id: @active_work_package.id).count if @active_work_package.present? end + def meeting_assignment_partial(meeting_agenda_item) + flex_layout do |flex| + flex.with_column(mr: 1) do + render(Primer::Beta::Octicon.new(icon: "comment-discussion", 'aria-label': "meeting", color: :muted)) + end + flex.with_column(flex_layout: true, mr: 1) do |flex| + flex.with_row do + render(Primer::Beta::Text.new(font_size: :small, color: :muted)) do + "In meeting #{meeting_link(meeting_agenda_item.meeting)} from #{meeting_agenda_item.meeting.start_time.strftime('%d.%m.%Y')}".html_safe + end + end + if meeting_agenda_item.details.present? + flex.with_row(mt: 1, ml: 1, border: :left, pl: 2) do + render(Primer::Box.new(font_size: :small, color: :default)) do + simple_format(meeting_agenda_item.details, {}, wrapper_tag: "span") + end + end + end + end + end + end + + def meeting_link(meeting) + link_to(meeting_path(meeting), target: "_blank", rel: "noopener") do + meeting.title + end + end + def description_partial if @issue.open? open_description_partial @@ -215,8 +256,8 @@ def historic_description_partial flex.with_row do original_author_partial end - flex.with_row do - open_description_partial(:muted) + flex.with_row(mt: 1) do + open_description_partial end end end diff --git a/modules/meeting/app/components/work_package_tab/meeting_agenda_items/list_component.rb b/modules/meeting/app/components/work_package_tab/meeting_agenda_items/list_component.rb deleted file mode 100644 index 591f9e40b63c..000000000000 --- a/modules/meeting/app/components/work_package_tab/meeting_agenda_items/list_component.rb +++ /dev/null @@ -1,190 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-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. -#++ - -module WorkPackageTab - class MeetingAgendaItems::ListComponent < Base::Component - def initialize(work_package:, meeting_agenda_items:) - super - - @meeting_agenda_items = meeting_agenda_items - @work_package = work_package - end - - def call - render(Primer::Beta::BorderBox.new(padding: :condensed)) do |component| - @meeting_agenda_items.each do |meeting_agenda_item| - component.with_row do - row_content_partial(meeting_agenda_item) - end - end - end - end - - private - - def row_content_partial(meeting_agenda_item) - flex_layout(justify_content: :space_between, align_items: :flex_start) do |flex| - flex.with_column do - content_partial(meeting_agenda_item) - end - flex.with_column do - actions_partial(meeting_agenda_item) - end - end - end - - def content_partial(meeting_agenda_item) - flex_layout do |flex| - flex.with_row(mb: 2) do - meeting_partial(meeting_agenda_item) - end - flex.with_row(pl: 2) do - description_partial(meeting_agenda_item) - end - end - end - - def meeting_partial(meeting_agenda_item) - flex_layout do |flex| - flex.with_column(pr: 1) do - render(Primer::Beta::Label.new(size: :large)) do - "Meeting: #{meeting_agenda_item.meeting.title}" - end - end - flex.with_column do - render(Primer::Beta::Label.new(size: :large)) do - format_date(meeting_agenda_item.meeting.start_time) - end - end - end - end - - def description_partial(meeting_agenda_item) - flex_layout do |flex| - # flex.with_row do - # title_partial(meeting_agenda_item) - # end - # flex.with_row do - # meta_info_partial(meeting_agenda_item) - # end - if meeting_agenda_item.input.present? - flex.with_row do - input_partial(meeting_agenda_item) - end - end - if meeting_agenda_item.output.present? - flex.with_row(mt: 2) do - output_partial(meeting_agenda_item) - end - end - end - end - - def title_partial(_meeting_agenda_item) - flex_layout do |flex| - flex.with_column(pr: 1) do - render(Primer::Beta::Text.new(font_size: :normal, color: :muted, font_weight: :bold)) do - "Agenda item" - end - end - # flex.with_column do - # render(Primer::Beta::Text.new(font_size: :normal, font_weight: :bold)) do - # "#{meeting_agenda_item.title}" - # end - # end - end - end - - def meta_info_partial(meeting_agenda_item) - flex_layout do |flex| - flex.with_column(pr: 1) do - render(Primer::Beta::Text.new(font_size: :small, color: :muted)) do - "created by #{meeting_agenda_item.user.name}" - end - end - flex.with_column do - render(Primer::Beta::RelativeTime.new( - font_size: :small, - color: :muted, - tense: :past, - lang: :en, - datetime: meeting_agenda_item.created_at - )) - end - end - end - - def input_partial(meeting_agenda_item) - flex_layout do |flex| - flex.with_row do - render(Primer::Beta::Text.new(font_size: :small)) do - "Clarifaction need:" - end - end - flex.with_row do - render(Primer::Box.new(font_size: :small, color: :muted)) do - simple_format(meeting_agenda_item.input, {}, wrapper_tag: "span") - end - end - end - end - - def output_partial(meeting_agenda_item) - flex_layout do |flex| - flex.with_row do - render(Primer::Beta::Text.new(font_size: :small)) do - "Clarification:" - end - end - flex.with_row do - render(Primer::Box.new(font_size: :small, color: :muted)) do - simple_format(meeting_agenda_item.output, {}, wrapper_tag: "span") - end - end - end - end - - def actions_partial(meeting_agenda_item) - form_with( - url: show_in_wp_tab_meeting_path(meeting_agenda_item.meeting, work_package_id: @work_package&.id), - method: :get, - data: { 'turbo-stream': true } - ) do |_form| - render(Primer::Beta::IconButton.new( - mr: 2, - size: :medium, - disabled: false, - icon: "arrow-right", - show_tooltip: true, - type: :submit, - 'aria-label': "Show meeting" - )) - end - end - end -end diff --git a/modules/meeting/app/components/work_package_tab/meetings/item_component.rb b/modules/meeting/app/components/work_package_tab/meetings/item_component.rb deleted file mode 100644 index 66771824c7d1..000000000000 --- a/modules/meeting/app/components/work_package_tab/meetings/item_component.rb +++ /dev/null @@ -1,127 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-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. -#++ - -module WorkPackageTab - class Meetings::ItemComponent < Base::Component - def initialize(meeting:, active_work_package: nil) - super - - @meeting = meeting - @active_work_package = active_work_package - end - - def call - flex_layout(justify_content: :space_between, align_items: :flex_start) do |flex| - flex.with_column do - content_partial - end - flex.with_column do - actions_partial - end - end - end - - private - - def content_partial - flex_layout do |flex| - flex.with_row do - meeting_date_partial - end - flex.with_row(pl: 2, mt: 2) do - meeting_title_and_author_partial - end - end - end - - def meeting_date_partial - render(Primer::Beta::Label.new(size: :large)) do - "#{format_date(@meeting.start_time)}" - end - end - - def meeting_title_and_author_partial - flex_layout do |flex| - flex.with_row do - meeting_title_partial - end - # flex.with_row do - # author_partial - # end - end - end - - def meeting_title_partial - flex_layout(align_items: :baseline) do |flex| - flex.with_column(pr: 1) do - render(Primer::Beta::Text.new(font_size: :normal, color: :muted, font_weight: :bold)) do - "Meeting:" - end - end - flex.with_column(pr: 1) do - render(Primer::Beta::Text.new(font_size: :normal, font_weight: :bold)) do - "#{@meeting.title}" - end - end - flex.with_column do - render(Primer::Beta::Counter.new( - scheme: :primary, count: count_active_work_package_references_in_meeting || 0, hide_if_zero: true - )) - end - end - end - - def author_partial - render(Primer::Beta::Text.new(font_size: :normal, color: :muted, font_weight: :bold)) do - "created by #{@meeting.author.name}" - end - end - - def actions_partial - form_with( - url: show_in_wp_tab_meeting_path(@meeting, work_package_id: @active_work_package&.id), - method: :get, - data: { 'turbo-stream': true } - ) do |_form| - render(Primer::Beta::IconButton.new( - mr: 2, - size: :medium, - disabled: false, - icon: "arrow-right", - show_tooltip: true, - type: :submit, - 'aria-label': "Add to meeting" - )) - end - end - - def count_active_work_package_references_in_meeting - @meeting.agenda_items.where(work_package_id: @active_work_package.id).count if @active_work_package.present? - end - end -end diff --git a/modules/meeting/app/components/work_package_tab/meetings/list_component.rb b/modules/meeting/app/components/work_package_tab/meetings/list_component.rb deleted file mode 100644 index 2076d37ba3b6..000000000000 --- a/modules/meeting/app/components/work_package_tab/meetings/list_component.rb +++ /dev/null @@ -1,48 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-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. -#++ - -module WorkPackageTab - class Meetings::ListComponent < Base::Component - def initialize(meetings:, active_work_package: nil) - super - - @meetings = meetings - @active_work_package = active_work_package - end - - def call - render(Primer::Beta::BorderBox.new(padding: :condensed)) do |component| - @meetings.each do |meeting| - component.with_row do - render(WorkPackageTab::Meetings::ItemComponent.new(meeting:, active_work_package: @active_work_package)) - end - end - end - end - end -end diff --git a/modules/meeting/app/controllers/concerns/agenda_component_streams.rb b/modules/meeting/app/controllers/concerns/agenda_component_streams.rb index daaf6602e042..80761deed0f3 100644 --- a/modules/meeting/app/controllers/concerns/agenda_component_streams.rb +++ b/modules/meeting/app/controllers/concerns/agenda_component_streams.rb @@ -30,25 +30,22 @@ module AgendaComponentStreams extend ActiveSupport::Concern included do - def update_new_section_via_turbo_stream(state: :initial, meeting_agenda_item: nil, meeting: @meeting, - active_work_package: @active_work_package) + def update_new_section_via_turbo_stream(state: :initial, meeting_agenda_item: nil, meeting: @meeting) update_via_turbo_stream( component: MeetingAgendaItems::NewSectionComponent.new( state:, meeting:, - meeting_agenda_item:, - active_work_package: + meeting_agenda_item: ) ) end - def update_list_via_turbo_stream(meeting: @meeting, active_work_package: @active_work_package) + def update_list_via_turbo_stream(meeting: @meeting) # replace needs to be called in order to mount the drag and drop handlers again # update would not do that and drag and drop would stop working after the first update replace_via_turbo_stream( component: MeetingAgendaItems::ListComponent.new( - meeting:, - active_work_package: + meeting: ) ) end @@ -61,13 +58,11 @@ def update_heading_via_turbo_stream(meeting: @meeting) ) end - def update_item_via_turbo_stream(state: :show, meeting_agenda_item: @meeting_agenda_item, - active_work_package: @active_work_package) + def update_item_via_turbo_stream(state: :show, meeting_agenda_item: @meeting_agenda_item) update_via_turbo_stream( component: MeetingAgendaItems::ItemComponent.new( state:, - meeting_agenda_item:, - active_work_package: + meeting_agenda_item: ) ) end diff --git a/modules/meeting/app/controllers/meeting_agenda_items_controller.rb b/modules/meeting/app/controllers/meeting_agenda_items_controller.rb index 86b1456fc7ee..9995b8037769 100644 --- a/modules/meeting/app/controllers/meeting_agenda_items_controller.rb +++ b/modules/meeting/app/controllers/meeting_agenda_items_controller.rb @@ -31,7 +31,6 @@ class MeetingAgendaItemsController < ApplicationController include AgendaComponentStreams before_action :set_meeting - before_action :set_optional_active_work_package before_action :set_meeting_agenda_item, except: %i[index new cancel_new create lock unlock close] def new @@ -131,6 +130,82 @@ def close respond_with_turbo_streams end + def edit_issue_resolution + update_via_turbo_stream( + component: MeetingAgendaItems::ItemComponent::IssueResolutionComponent.new(issue: @meeting_agenda_item.work_package_issue, + meeting_agenda_item: @meeting_agenda_item, + state: :edit) + ) + + respond_with_turbo_streams + end + + def cancel_edit_issue_resolution + update_via_turbo_stream( + component: MeetingAgendaItems::ItemComponent::IssueResolutionComponent.new(issue: @meeting_agenda_item.work_package_issue, + meeting_agenda_item: @meeting_agenda_item, + state: :initial) + ) + + respond_with_turbo_streams + end + + def resolve_issue + @issue = @meeting_agenda_item.work_package_issue + if @issue.resolve(User.current, issue_params[:resolution]) + update_item_via_turbo_stream + else + update_via_turbo_stream( + component: MeetingAgendaItems::ItemComponent::IssueResolutionComponent.new(issue: @issue, + meeting_agenda_item: @meeting_agenda_item, + state: :edit) + ) + end + respond_with_turbo_streams + end + + def reopen_issue + if @issue.reopen # keep the former resolution in place + redirect_to open_work_package_issues_path(@work_package) + end + end + + def edit_notes + update_via_turbo_stream( + component: MeetingAgendaItems::ItemComponent::NotesComponent.new( + meeting_agenda_item: @meeting_agenda_item, + state: :edit + ) + ) + + respond_with_turbo_streams + end + + def cancel_edit_notes + update_via_turbo_stream( + component: MeetingAgendaItems::ItemComponent::NotesComponent.new( + meeting_agenda_item: @meeting_agenda_item, + state: :initial + ) + ) + + respond_with_turbo_streams + end + + def save_notes + if @meeting_agenda_item.update(details: meeting_agenda_item_params[:details]) + update_item_via_turbo_stream + else + update_via_turbo_stream( + component: MeetingAgendaItems::ItemComponent::NotesComponent.new( + meeting_agenda_item: @meeting_agenda_item, + state: :edit + ) + ) + end + respond_with_turbo_streams + end + private def set_meeting @@ -141,11 +216,11 @@ def set_meeting_agenda_item @meeting_agenda_item = MeetingAgendaItem.find(params[:id]) end - def set_optional_active_work_package - @active_work_package = WorkPackage.find_by(id: params[:work_package_id]) if params[:work_package_id].present? + def meeting_agenda_item_params + params.require(:meeting_agenda_item).permit(:title, :duration_in_minutes, :work_package_issue_id, :details, :user_id) end - def meeting_agenda_item_params - params.require(:meeting_agenda_item).permit(:title, :duration_in_minutes, :work_package_id, :input, :output, :details) + def issue_params + params.require(:work_package_issue).permit(:resolution, :resolved_by_id) end end diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb index e7eef01b414c..765b3a731ca5 100644 --- a/modules/meeting/app/controllers/meetings_controller.rb +++ b/modules/meeting/app/controllers/meetings_controller.rb @@ -28,12 +28,12 @@ class MeetingsController < ApplicationController around_action :set_time_zone - before_action :find_optional_project, only: %i[index index_in_wp_tab new create] + before_action :find_optional_project, only: %i[index new create] before_action :build_meeting, only: %i[new create] - before_action :find_meeting, except: %i[index index_in_wp_tab new create] + before_action :find_meeting, except: %i[index new create] before_action :convert_params, only: %i[create update] - before_action :authorize, except: %i[index new index_in_wp_tab] - before_action :authorize_global, only: %i[index new index_in_wp_tab] + before_action :authorize, except: %i[index new] + before_action :authorize_global, only: %i[index new] helper :watchers helper :meeting_contents @@ -55,20 +55,6 @@ def index render 'index', locals: { menu_name: project_or_global_menu } end - def index_in_wp_tab - @active_work_package = WorkPackage.find(params[:work_package_id]) if params[:work_package_id].present? - @upcoming_meetings = @project.meetings.from_today.limit(10).reorder('start_time ASC') - @past_meetings = @project.meetings.joins(:agenda_items) - .where(['meetings.start_time < ?', Time.now.utc]).order('start_time DESC') - .where('meeting_agenda_items.work_package_id = ?', @active_work_package.id) - .distinct - - @discussed_agenda_items = @active_work_package.meeting_agenda_items.where.not(output: "") - @open_agenda_items = @active_work_package.meeting_agenda_items.where(output: "").where.not(input: "") - - render layout: false - end - def show params[:tab] ||= 'minutes' if @meeting.agenda.present? && @meeting.agenda.locked? end diff --git a/modules/meeting/app/controllers/issues_controller.rb b/modules/meeting/app/controllers/work_package_issues_controller.rb similarity index 77% rename from modules/meeting/app/controllers/issues_controller.rb rename to modules/meeting/app/controllers/work_package_issues_controller.rb index 3da27f577942..f1d1bc751a12 100644 --- a/modules/meeting/app/controllers/issues_controller.rb +++ b/modules/meeting/app/controllers/work_package_issues_controller.rb @@ -26,10 +26,10 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class IssuesController < ApplicationController +class WorkPackageIssuesController < ApplicationController include OpTurbo::ComponentStream - before_action :set_issue, only: %i[edit update destroy edit_resolution resolve reopen] + before_action :set_issue, only: %i[edit update destroy edit_resolution resolve reopen new_meeting save_meeting] before_action :set_work_package def open @@ -41,13 +41,13 @@ def closed end def new - @issue = Issue.new(work_package: @work_package) + @issue = WorkPackageIssue.new(work_package: @work_package) render layout: false end def create - @issue = Issue.new(issue_params.merge(work_package: @work_package, author: User.current)) + @issue = WorkPackageIssue.new(issue_params.merge(work_package: @work_package, author: User.current)) if @issue.save redirect_to open_work_package_issues_path(@work_package) @@ -114,6 +114,24 @@ def reopen end end + def new_meeting + render layout: false + end + + def save_meeting + if @issue.agenda_items << MeetingAgendaItem.new(meeting: Meeting.find(params[:meeting_id])) + redirect_to open_work_package_issues_path(@work_package) + else + # simply rendering the new view again as a turbo-frame messes up the src attribute of the frame + # using turbo-stream instead as a quick fix + update_via_turbo_stream( + component: WorkPackageTab::Issues::AssignMeetingComponent.new(issue: @issue) + ) + + respond_with_turbo_streams + end + end + private def set_work_package @@ -121,10 +139,10 @@ def set_work_package end def set_issue - @issue = Issue.find(params[:id]) + @issue = WorkPackageIssue.find(params[:id]) end def issue_params - params.require(:issue).permit(:description, :issue_type, :resolution) + params.require(:work_package_issue).permit(:description, :issue_type, :resolution) end end diff --git a/modules/meeting/app/forms/meeting_agenda_item/new/clarification_need.rb b/modules/meeting/app/forms/issue/meeting.rb similarity index 76% rename from modules/meeting/app/forms/meeting_agenda_item/new/clarification_need.rb rename to modules/meeting/app/forms/issue/meeting.rb index dfe84585d557..38ce2a076ab0 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/new/clarification_need.rb +++ b/modules/meeting/app/forms/issue/meeting.rb @@ -26,12 +26,18 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class MeetingAgendaItem::New::ClarificationNeed < ApplicationForm - form do |agenda_item_form| - agenda_item_form.text_area( - name: :input, - label: "Clarification need", - autofocus: true, - ) +class Issue::Meeting < ApplicationForm + form do |issue_form| + issue_form.select_list( + name: :meeting_id, + label: "Meeting" + ) do |issue_select_list| + Meeting.future.each do |meeting| + issue_select_list.option( + label: "#{meeting.title} #{meeting.start_time.strftime('%d.%m.%Y')}", + value: meeting.id + ) + end + end end -end \ No newline at end of file +end diff --git a/modules/meeting/app/forms/issue/type.rb b/modules/meeting/app/forms/issue/type.rb index 776cf10db000..fc5cb9549cc8 100644 --- a/modules/meeting/app/forms/issue/type.rb +++ b/modules/meeting/app/forms/issue/type.rb @@ -33,7 +33,7 @@ class Issue::Type < ApplicationForm label: "Type", include_blank: false ) do |type_select_list| - Issue.issue_types.to_a.each do |k, _v| + WorkPackageIssue.issue_types.to_a.each do |k, _v| type_select_list.option( label: k.humanize.capitalize, value: k diff --git a/modules/meeting/app/forms/meeting_agenda_item/new/author.rb b/modules/meeting/app/forms/meeting_agenda_item/author.rb similarity index 96% rename from modules/meeting/app/forms/meeting_agenda_item/new/author.rb rename to modules/meeting/app/forms/meeting_agenda_item/author.rb index d8d53b32cd39..ea4b4a9e5b9d 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/new/author.rb +++ b/modules/meeting/app/forms/meeting_agenda_item/author.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class MeetingAgendaItem::New::Author < ApplicationForm +class MeetingAgendaItem::Author < ApplicationForm form do |agenda_item_form| agenda_item_form.select_list( name: :user_id, diff --git a/modules/meeting/app/forms/meeting_agenda_item/new/clarification.rb b/modules/meeting/app/forms/meeting_agenda_item/details.rb similarity index 82% rename from modules/meeting/app/forms/meeting_agenda_item/new/clarification.rb rename to modules/meeting/app/forms/meeting_agenda_item/details.rb index 8cd0ee73ef1a..fd15e8f7c807 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/new/clarification.rb +++ b/modules/meeting/app/forms/meeting_agenda_item/details.rb @@ -26,12 +26,20 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class MeetingAgendaItem::New::Clarification < ApplicationForm +class MeetingAgendaItem::Details < ApplicationForm form do |agenda_item_form| agenda_item_form.text_area( - name: :output, - label: "Clarification", + name: :details, + placeholder: "Notes", + label: "Notes", + visually_hide_label: true, + required: true, autofocus: true, + disabled: @disabled ) end -end \ No newline at end of file + + def initialize(disabled: false) + @disabled = disabled + end +end diff --git a/modules/meeting/app/forms/meeting_agenda_item/new/duration.rb b/modules/meeting/app/forms/meeting_agenda_item/duration.rb similarity index 96% rename from modules/meeting/app/forms/meeting_agenda_item/new/duration.rb rename to modules/meeting/app/forms/meeting_agenda_item/duration.rb index 581ec554e3e0..f4be10ddc548 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/new/duration.rb +++ b/modules/meeting/app/forms/meeting_agenda_item/duration.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class MeetingAgendaItem::New::Duration < ApplicationForm +class MeetingAgendaItem::Duration < ApplicationForm form do |agenda_item_form| agenda_item_form.text_field( name: :duration_in_minutes, diff --git a/modules/meeting/app/forms/meeting_agenda_item/new/work_package.rb b/modules/meeting/app/forms/meeting_agenda_item/issue.rb similarity index 76% rename from modules/meeting/app/forms/meeting_agenda_item/new/work_package.rb rename to modules/meeting/app/forms/meeting_agenda_item/issue.rb index 7cfcccb06658..1a06beba112b 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/new/work_package.rb +++ b/modules/meeting/app/forms/meeting_agenda_item/issue.rb @@ -26,24 +26,21 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class MeetingAgendaItem::New::WorkPackage < ApplicationForm +class MeetingAgendaItem::Issue < ApplicationForm form do |agenda_item_form| agenda_item_form.select_list( - name: :work_package_id, - label: "Work package", + name: :work_package_issue_id, + label: "Issue", include_blank: true, visually_hide_label: true, disabled: @disabled - ) do |wp_select_list| - WorkPackage.visible - .order(:id) - .map { |wp| [wp.subject, wp.id] } - .each do |subject, id| - wp_select_list.option( - label: "##{id} #{subject}", - value: id - ) - end + ) do |issue_select_list| + WorkPackageIssue.includes(:work_package).open.each do |issue| + issue_select_list.option( + label: "##{issue.work_package.id} #{issue.work_package.subject.truncate(100)} - #{issue.issue_type.humanize}: #{issue.description.truncate(100)}", + value: issue.id + ) + end end end diff --git a/modules/meeting/app/forms/meeting_agenda_item/new/submit.rb b/modules/meeting/app/forms/meeting_agenda_item/submit.rb similarity index 76% rename from modules/meeting/app/forms/meeting_agenda_item/new/submit.rb rename to modules/meeting/app/forms/meeting_agenda_item/submit.rb index dbd4f4fc4302..d2b3104d3507 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/new/submit.rb +++ b/modules/meeting/app/forms/meeting_agenda_item/submit.rb @@ -26,18 +26,11 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class MeetingAgendaItem::New::Submit < ApplicationForm +class MeetingAgendaItem::Submit < ApplicationForm form do |agenda_item_form| - unless @preselected_work_package&.id.nil? - agenda_item_form.hidden(name: :work_package_id, value: @preselected_work_package&.id) - end agenda_item_form.group(layout: :horizontal) do |button_group| - button_group.button(name: :button, label: "Cancel", data: { action: 'click->meeting-agenda-item-form#cancel' }) + # button_group.button(name: :button, label: "Cancel", data: { action: 'click->meeting-agenda-item-form#cancel' }) button_group.submit(name: :submit, label: "Submit", scheme: :primary) end end - - def initialize(preselected_work_package: nil) - @preselected_work_package = preselected_work_package - end -end \ No newline at end of file +end diff --git a/modules/meeting/app/forms/meeting_agenda_item/new/title.rb b/modules/meeting/app/forms/meeting_agenda_item/title.rb similarity index 96% rename from modules/meeting/app/forms/meeting_agenda_item/new/title.rb rename to modules/meeting/app/forms/meeting_agenda_item/title.rb index 4ed996ce2401..44df769ed859 100644 --- a/modules/meeting/app/forms/meeting_agenda_item/new/title.rb +++ b/modules/meeting/app/forms/meeting_agenda_item/title.rb @@ -26,7 +26,7 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class MeetingAgendaItem::New::Title < ApplicationForm +class MeetingAgendaItem::Title < ApplicationForm form do |agenda_item_form| agenda_item_form.text_field( name: :title, diff --git a/modules/meeting/app/forms/meeting_agenda_item_form.rb b/modules/meeting/app/forms/meeting_agenda_item_form.rb deleted file mode 100644 index 488bba7e0db6..000000000000 --- a/modules/meeting/app/forms/meeting_agenda_item_form.rb +++ /dev/null @@ -1,76 +0,0 @@ -#-- copyright -# OpenProject is an open source project management software. -# Copyright (C) 2012-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 MeetingAgendaItemForm < ApplicationForm - form do |agenda_item_form| - unless @preselected_work_package.present? - agenda_item_form.text_field( - name: :duration_in_minutes, - label: "Duration in minutes", - type: :number - ) - end - agenda_item_form.select_list( - name: :work_package_id, - label: "Work package", - include_blank: true, - # disabled: @preselected_work_package.present? # does not work, work_package_id is nil when form gets submitted - ) do |wp_select_list| - WorkPackage.visible - .order(:id) - .map { |wp| [wp.subject, wp.id] } - .each do |subject, id| - wp_select_list.option( - label: "##{id} #{subject}", - value: id - ) - end - end - agenda_item_form.text_field( - name: :title, - label: "Title", - required: true - ) - agenda_item_form.text_area( - name: :input, - label: "Input", - ) - unless @preselected_work_package.present? - agenda_item_form.text_area( - name: :output, - label: "Output", - ) - end - - agenda_item_form.submit(name: "Save", label: "Save", scheme: :primary) - end - - def initialize(preselected_work_package: nil) - @preselected_work_package = preselected_work_package - end -end \ No newline at end of file diff --git a/modules/meeting/app/models/meeting_agenda_item.rb b/modules/meeting/app/models/meeting_agenda_item.rb index 860d6c9fbf01..58db463fb124 100644 --- a/modules/meeting/app/models/meeting_agenda_item.rb +++ b/modules/meeting/app/models/meeting_agenda_item.rb @@ -30,19 +30,19 @@ class MeetingAgendaItem < ApplicationRecord belongs_to :meeting has_one :project, through: :meeting - belongs_to :work_package, optional: true + belongs_to :work_package_issue, class_name: 'WorkPackageIssue', optional: true belongs_to :user acts_as_list scope: :meeting default_scope { order(:position) } - validates :title, presence: true, if: Proc.new { |item| item.work_package_id.blank? } - validates :work_package, presence: true, if: Proc.new { |item| item.title.blank? } + validates :title, presence: true, if: Proc.new { |item| item.work_package_issue_id.blank? } + validates :work_package_issue_id, presence: true, if: Proc.new { |item| item.title.blank? } validates :duration_in_minutes, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true after_create :trigger_meeting_agenda_item_time_slots_calculation - after_save :trigger_meeting_agenda_item_time_slots_calculation, if: Proc.new { - |item| item.duration_in_minutes_previously_changed? || item.position_previously_changed? + after_save :trigger_meeting_agenda_item_time_slots_calculation, if: Proc.new { |item| + item.duration_in_minutes_previously_changed? || item.position_previously_changed? } after_destroy :trigger_meeting_agenda_item_time_slots_calculation diff --git a/modules/meeting/app/models/issue.rb b/modules/meeting/app/models/work_package_issue.rb similarity index 89% rename from modules/meeting/app/models/issue.rb rename to modules/meeting/app/models/work_package_issue.rb index 1d34839b47ec..2a140204e823 100644 --- a/modules/meeting/app/models/issue.rb +++ b/modules/meeting/app/models/work_package_issue.rb @@ -26,14 +26,14 @@ # See COPYRIGHT and LICENSE files for more details. #++ -class Issue < ApplicationRecord - self.table_name = 'issues' +class WorkPackageIssue < ApplicationRecord + self.table_name = 'work_package_issues' belongs_to :work_package belongs_to :author, class_name: 'User' belongs_to :resolved_by, class_name: 'User', optional: true - # has_many :meeting_agenda_items, dependent: :destroy, class_name: 'MeetingAgendaItem' + has_many :meeting_agenda_items, dependent: :destroy, class_name: 'MeetingAgendaItem' enum issue_type: %i[input_need clarification_need decision_need] @@ -43,6 +43,7 @@ class Issue < ApplicationRecord scope :closed, -> { where.not(resolved_at: nil) } validates :description, presence: true + validates :resolution, presence: true, if: :closed? def open? resolved_at.nil? diff --git a/modules/meeting/app/views/issues/closed.html.erb b/modules/meeting/app/views/work_package_issues/closed.html.erb similarity index 100% rename from modules/meeting/app/views/issues/closed.html.erb rename to modules/meeting/app/views/work_package_issues/closed.html.erb diff --git a/modules/meeting/app/views/issues/edit.html.erb b/modules/meeting/app/views/work_package_issues/edit.html.erb similarity index 100% rename from modules/meeting/app/views/issues/edit.html.erb rename to modules/meeting/app/views/work_package_issues/edit.html.erb diff --git a/modules/meeting/app/views/issues/edit_resolution.html.erb b/modules/meeting/app/views/work_package_issues/edit_resolution.html.erb similarity index 100% rename from modules/meeting/app/views/issues/edit_resolution.html.erb rename to modules/meeting/app/views/work_package_issues/edit_resolution.html.erb diff --git a/modules/meeting/app/views/issues/new.html.erb b/modules/meeting/app/views/work_package_issues/new.html.erb similarity index 100% rename from modules/meeting/app/views/issues/new.html.erb rename to modules/meeting/app/views/work_package_issues/new.html.erb diff --git a/modules/meeting/app/views/work_package_issues/new_meeting.html.erb b/modules/meeting/app/views/work_package_issues/new_meeting.html.erb new file mode 100644 index 000000000000..ba519e4d1a2b --- /dev/null +++ b/modules/meeting/app/views/work_package_issues/new_meeting.html.erb @@ -0,0 +1,44 @@ +<%#-- copyright +OpenProject is an open source project management software. +Copyright (C) 2012-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. + +++#%> + + + <%= link_to(open_work_package_issues_path(@work_package)) do %> + <%= render(Primer::Beta::Button.new( + scheme: :secondary, + block: false, + mb: 3 + )) do |component| %> + <% component.with_leading_visual_icon(icon: "arrow-left") %> + Back to issues + <% end %> + <% end %> +

+ To be implemented! +

+
diff --git a/modules/meeting/app/views/issues/open.html.erb b/modules/meeting/app/views/work_package_issues/open.html.erb similarity index 100% rename from modules/meeting/app/views/issues/open.html.erb rename to modules/meeting/app/views/work_package_issues/open.html.erb diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index ed8c2cf5ea63..4b3aeaad057e 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -28,15 +28,11 @@ OpenProject::Application.routes.draw do resources :projects, only: %i[] do - resources :meetings, only: %i[index new create] do - collection do - get 'index_in_wp_tab/:work_package_id', action: :index_in_wp_tab, as: :index_in_wp_tab - end - end + resources :meetings, only: %i[index new create] end resources :work_packages, only: %i[] do - resources :issues, only: %i[new create edit update destroy] do + resources :issues, only: %i[new create edit update destroy], controller: 'work_package_issues' do collection do get :open get :closed @@ -45,14 +41,13 @@ get :edit_resolution patch :resolve patch :reopen + get :new_meeting + patch :save_meeting end end end resources :meetings do - member do - get 'show_in_wp_tab/:work_package_id', action: :show_in_wp_tab, as: :show_in_wp_tab - end resources :agenda_items, controller: 'meeting_agenda_items' do collection do get 'new(/:work_package_id)', action: :new, as: :new @@ -65,6 +60,12 @@ member do get :cancel_edit put :drop + get :edit_issue_resolution + get :cancel_edit_issue_resolution + patch :resolve_issue + get :edit_notes + get :cancel_edit_notes + patch :save_notes end end diff --git a/modules/meeting/db/migrate/20230728115607_create_issues.rb b/modules/meeting/db/migrate/20230728115607_create_work_package_issues.rb similarity index 77% rename from modules/meeting/db/migrate/20230728115607_create_issues.rb rename to modules/meeting/db/migrate/20230728115607_create_work_package_issues.rb index 2a6065881f4b..4a2286aba0a4 100644 --- a/modules/meeting/db/migrate/20230728115607_create_issues.rb +++ b/modules/meeting/db/migrate/20230728115607_create_work_package_issues.rb @@ -1,6 +1,6 @@ -class CreateIssues < ActiveRecord::Migration[5.1] +class CreateWorkPackageIssues < ActiveRecord::Migration[5.1] def change - create_table :issues do |t| + create_table :work_package_issues do |t| t.references :work_package t.references :author, foreign_key: { to_table: :users } t.references :resolved_by, foreign_key: { to_table: :users }, null: true diff --git a/modules/meeting/db/migrate/20230801102556_add_optional_issue_reference_to_meeting_agenda_items.rb b/modules/meeting/db/migrate/20230801102556_add_optional_issue_reference_to_meeting_agenda_items.rb new file mode 100644 index 000000000000..c7a6fda2ee66 --- /dev/null +++ b/modules/meeting/db/migrate/20230801102556_add_optional_issue_reference_to_meeting_agenda_items.rb @@ -0,0 +1,8 @@ +class AddOptionalIssueReferenceToMeetingAgendaItems < ActiveRecord::Migration[5.1] + def change + remove_reference :meeting_agenda_items, :work_package + remove_column :meeting_agenda_items, :input, :text + remove_column :meeting_agenda_items, :output, :text + add_reference :meeting_agenda_items, :work_package_issue, foreign_key: true + end +end diff --git a/modules/meeting/frontend/module/main.ts b/modules/meeting/frontend/module/main.ts index 8935b1c2c4a0..6c059109e3ce 100644 --- a/modules/meeting/frontend/module/main.ts +++ b/modules/meeting/frontend/module/main.ts @@ -32,17 +32,10 @@ import { import { OpSharedModule } from 'core-app/shared/shared.module'; import { OpenprojectTabsModule } from 'core-app/shared/components/tabs/openproject-tabs.module'; import { WorkPackageTabsService } from 'core-app/features/work-packages/components/wp-tabs/services/wp-tabs/wp-tabs.service'; -import { MeetingsTabComponent } from './meetings-tab/meetings-tab.component'; import { IssuesTabComponent } from './issues-tab/issues-tab.component'; export function initializeMeetingPlugin(injector:Injector) { const wpTabService = injector.get(WorkPackageTabsService); - wpTabService.register({ - component: MeetingsTabComponent, - name: "Meetings", - id: 'meetings', - displayable: (workPackage) => true, - }); wpTabService.register({ component: IssuesTabComponent, name: "Issues", @@ -57,11 +50,9 @@ export function initializeMeetingPlugin(injector:Injector) { OpenprojectTabsModule, ], declarations: [ - MeetingsTabComponent, IssuesTabComponent, ], exports: [ - MeetingsTabComponent, IssuesTabComponent, ], schemas: [CUSTOM_ELEMENTS_SCHEMA], diff --git a/modules/meeting/frontend/module/meetings-tab/meetings-tab.component.ts b/modules/meeting/frontend/module/meetings-tab/meetings-tab.component.ts deleted file mode 100644 index aaa196c8e098..000000000000 --- a/modules/meeting/frontend/module/meetings-tab/meetings-tab.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -//-- copyright -// OpenProject is an open source project management software. -// Copyright (C) 2012-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. -//++ - -import { Component, Input, OnInit, AfterViewInit, ElementRef } from '@angular/core'; -import { WorkPackageResource } from "core-app/features/hal/resources/work-package-resource"; -import { TabComponent } from "core-app/features/work-packages/components/wp-tabs/components/wp-tab-wrapper/tab"; -import { I18nService } from "core-app/core/i18n/i18n.service"; -import { PathHelperService } from "core-app/core/path-helper/path-helper.service"; - -@Component({ - selector: 'meetings-tab', - templateUrl: './meetings-tab.template.html' -}) -export class MeetingsTabComponent implements OnInit, TabComponent { - @Input() public workPackage: WorkPackageResource; - turboFrameSrc: string; - - constructor( - private elementRef: ElementRef, - readonly PathHelper: PathHelperService, - readonly I18n: I18nService - ) {} - - ngOnInit(): void { - const storedSrc = localStorage.getItem(`turboFrameSrcMeetingsTabForWorkPackage${this.workPackage.id}`); - this.turboFrameSrc = storedSrc ? storedSrc : `/projects/${this.workPackage.project.id}/meetings/index_in_wp_tab/${this.workPackage.id}`; - } - - ngAfterViewInit(): void { - const turboFrame = this.elementRef.nativeElement.querySelector('#work-package-meetings-tab-content'); - if (turboFrame) { - turboFrame.addEventListener('turbo:frame-load', (event: Event) => { - const target = event.target as HTMLElement; - const newSrc = target.getAttribute('src'); - localStorage.setItem(`turboFrameSrcMeetingsTabForWorkPackage${this.workPackage.id}`, newSrc||''); - }); - } - } -} diff --git a/modules/meeting/frontend/module/meetings-tab/meetings-tab.template.html b/modules/meeting/frontend/module/meetings-tab/meetings-tab.template.html deleted file mode 100644 index ef9443893f7f..000000000000 --- a/modules/meeting/frontend/module/meetings-tab/meetings-tab.template.html +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file