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