From 17b8a1eb27a4fee348496632d19dec0c6a8c0d52 Mon Sep 17 00:00:00 2001 From: Mir Bhatia Date: Thu, 24 Oct 2024 15:51:21 +0200 Subject: [PATCH] Add meeting creation dialog --- .../index/new_dialog_component.html.erb | 10 ++++ .../meetings/index/new_dialog_component.rb | 51 ++++++++++++++++ .../index/new_form_component.html.erb | 60 +++++++++++++++++++ .../meetings/index/new_form_component.rb | 52 ++++++++++++++++ .../index_sub_header_component.html.erb | 36 +++++------ .../app/controllers/meetings_controller.rb | 42 ++++++++++--- modules/meeting/app/forms/meeting/location.rb | 6 +- .../forms/meeting/project_autocompleter.rb | 46 ++++++++++++++ modules/meeting/app/forms/meeting/title.rb | 39 ++++++++++++ modules/meeting/app/forms/meeting/type.rb | 33 ++++++++++ modules/meeting/config/locales/en.yml | 4 +- modules/meeting/config/routes.rb | 1 + .../lib/open_project/meeting/engine.rb | 2 +- 13 files changed, 350 insertions(+), 32 deletions(-) create mode 100644 modules/meeting/app/components/meetings/index/new_dialog_component.html.erb create mode 100644 modules/meeting/app/components/meetings/index/new_dialog_component.rb create mode 100644 modules/meeting/app/components/meetings/index/new_form_component.html.erb create mode 100644 modules/meeting/app/components/meetings/index/new_form_component.rb create mode 100644 modules/meeting/app/forms/meeting/project_autocompleter.rb create mode 100644 modules/meeting/app/forms/meeting/title.rb create mode 100644 modules/meeting/app/forms/meeting/type.rb diff --git a/modules/meeting/app/components/meetings/index/new_dialog_component.html.erb b/modules/meeting/app/components/meetings/index/new_dialog_component.html.erb new file mode 100644 index 000000000000..7eb444b066e8 --- /dev/null +++ b/modules/meeting/app/components/meetings/index/new_dialog_component.html.erb @@ -0,0 +1,10 @@ +<%= + render(Primer::Alpha::Dialog.new( + id: "new-meeting-dialog", title: I18n.t("label_meeting_new_one_time"), + size: :medium_portrait, + data: { 'keep-open-on-submit': true } + )) do |dialog| + dialog.with_header(variant: :large) + render(Meetings::Index::NewFormComponent.new(meeting: @meeting, project: @project)) + end +%> diff --git a/modules/meeting/app/components/meetings/index/new_dialog_component.rb b/modules/meeting/app/components/meetings/index/new_dialog_component.rb new file mode 100644 index 000000000000..b0b6b46b5412 --- /dev/null +++ b/modules/meeting/app/components/meetings/index/new_dialog_component.rb @@ -0,0 +1,51 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Meetings + class Index::NewDialogComponent < ApplicationComponent + include ApplicationHelper + include OpenProject::FormTagHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(meeting:, project:) + super + + @meeting = meeting + @project = project + end + + def render? + if @project + User.current.allowed_in_project?(:create_meetings, @project) + else + User.current.allowed_in_any_project?(:create_meetings) + end + end + end +end diff --git a/modules/meeting/app/components/meetings/index/new_form_component.html.erb b/modules/meeting/app/components/meetings/index/new_form_component.html.erb new file mode 100644 index 000000000000..b67ab14605ce --- /dev/null +++ b/modules/meeting/app/components/meetings/index/new_form_component.html.erb @@ -0,0 +1,60 @@ +<%= + component_wrapper do + primer_form_with( + scope: :meeting, + model: @meeting, + method: :post, + data: { turbo: true }, + html: { :id => 'meeting-form' }, + url: {:controller => '/meetings', :action => 'create', :project_id => @project} + ) do |f| + component_collection do |collection| + collection.with_component(Primer::Alpha::Dialog::Body.new) do + flex_layout(mb: 3) do |modal_body| + if @project.nil? + modal_body.with_row(mt: 3) do + render(Meeting::ProjectAutocompleter.new(f)) + end + end + + modal_body.with_row(mt: 3) do + render(Meeting::Title.new(f)) + end + + modal_body.with_row(mt: 3) do + render(Meeting::StartDate.new(f, initial_value: start_date_initial_value)) + end + + modal_body.with_row(mt: 3) do + render(Meeting::StartTime.new(f, initial_value: start_time_initial_value)) + end + + modal_body.with_row(mt: 3) do + render(Meeting::Duration.new(f)) + end + + modal_body.with_row(mt: 3) do + render(Meeting::Location.new(f, icon: :location)) + end + + modal_body.with_row do + render(Meeting::Type.new(f)) + end + end + end + + collection.with_component(Primer::Alpha::Dialog::Footer.new) do + component_collection do |modal_footer| + modal_footer.with_component(Primer::ButtonComponent.new(data: { 'close-dialog-id': "new-meeting-dialog" })) do + I18n.t("button_cancel") + end + + modal_footer.with_component(Primer::ButtonComponent.new(scheme: :primary, type: :submit)) do + I18n.t("label_meeting_create") + end + end + end + end + end + end +%> diff --git a/modules/meeting/app/components/meetings/index/new_form_component.rb b/modules/meeting/app/components/meetings/index/new_form_component.rb new file mode 100644 index 000000000000..12b036f28838 --- /dev/null +++ b/modules/meeting/app/components/meetings/index/new_form_component.rb @@ -0,0 +1,52 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +module Meetings + class Index::NewFormComponent < ApplicationComponent + include ApplicationHelper + include OpTurbo::Streamable + include OpPrimer::ComponentHelpers + + def initialize(meeting:, project:) + super + + @meeting = meeting + @project = project + end + + private + + def start_date_initial_value + format_time_as_date(@meeting.start_time, format: "%Y-%m-%d") + end + + def start_time_initial_value + format_time(@meeting.start_time, include_date: false, format: "%H:%M") + end + end +end diff --git a/modules/meeting/app/components/meetings/index_sub_header_component.html.erb b/modules/meeting/app/components/meetings/index_sub_header_component.html.erb index a27c25c6690b..30d5ea1f23e3 100644 --- a/modules/meeting/app/components/meetings/index_sub_header_component.html.erb +++ b/modules/meeting/app/components/meetings/index_sub_header_component.html.erb @@ -7,42 +7,36 @@ render(Meetings::MeetingFilterButtonComponent.new(query: @query, project: @project)) end - # subheader.with_action_button(tag: :a, - # href: dynamic_path, - # scheme: :primary, - # mobile_icon: :plus, - # mobile_label: label_text, - # aria: { label: accessibility_label_text }, - # title: accessibility_label_text, - # id: id, - # test_selector: "add-meeting-button") do |button| - # button.with_leading_visual_icon(icon: :plus) - # label_text - # end if render_create_button? - if render_create_button? subheader.with_action_component do render Primer::Alpha::ActionMenu.new(anchor_align: :end) do |menu| menu.with_show_button(scheme: :primary, - test_selector: "op-admin-synchronized-groups--button-new", - aria: { label: I18n.t(:button_add) }) do |button| + test_selector: "add-meeting-button", + mobile_icon: :plus, + mobile_label: label_text, + id: id, + title: accessibility_label_text, + aria: { label: accessibility_label_text }) do |button| button.with_leading_visual_icon(icon: :plus) button.with_trailing_action_icon(icon: :"triangle-down") I18n.t(:label_meeting) end - menu.with_item(label: I18n.t("meeting.types.structured")) do |item| - end + menu.with_item(label: I18n.t("meeting.types.classic"), + tag: :a, + href: dynamic_path + ) - menu.with_item(label: I18n.t("meeting.types.classic")) do |item| - end + menu.with_item(label: I18n.t("meeting.types.structured"), + tag: :a, + href: new_dialog_meetings_path(project_id: @project&.id), + content_arguments: { data: { controller: "async-dialog" }} + ) end - end end - subheader.with_bottom_pane_component(mt: 0) do render(Meetings::MeetingFiltersComponent.new(query: @query, project: @project)) end diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb index 5fb9334eb5c6..e7fd751d4d86 100644 --- a/modules/meeting/app/controllers/meetings_controller.rb +++ b/modules/meeting/app/controllers/meetings_controller.rb @@ -27,17 +27,18 @@ #++ class MeetingsController < ApplicationController - before_action :load_and_authorize_in_optional_project, only: %i[index new show create history] + before_action :load_and_authorize_in_optional_project, only: %i[index new new_dialog show create history] before_action :verify_activities_module_activated, only: %i[history] before_action :determine_date_range, only: %i[history] before_action :determine_author, only: %i[history] - before_action :build_meeting, only: %i[new] - before_action :find_meeting, except: %i[index new create] + before_action :build_meeting, only: %i[new new_dialog] + before_action :find_meeting, except: %i[index new create new_dialog] before_action :set_activity, only: %i[history] before_action :find_copy_from_meeting, only: %i[create] before_action :convert_params, only: %i[create update update_participants] - before_action :authorize, except: %i[index new create update_title update_details update_participants change_state] - before_action :authorize_global, only: %i[index new create update_title update_details update_participants change_state] + before_action :authorize, except: %i[index new create update_title update_details update_participants change_state new_dialog] + before_action :authorize_global, + only: %i[index new create update_title update_details update_participants change_state new_dialog] helper :watchers helper :meeting_contents @@ -49,6 +50,7 @@ class MeetingsController < ApplicationController include OpTurbo::ComponentStream include OpTurbo::FlashStreamHelper + include OpTurbo::DialogStreamHelper include Meetings::AgendaComponentStreams include MetaTagsHelper @@ -93,6 +95,8 @@ def create # rubocop:disable Metrics/AbcSize .call(@converted_params) end + @meeting = call.result + if call.success? text = I18n.t(:notice_successful_create) unless User.current.pref.time_zone? @@ -102,13 +106,35 @@ def create # rubocop:disable Metrics/AbcSize end flash[:notice] = text.html_safe # rubocop:disable Rails/OutputSafety - redirect_to action: "show", id: call.result + respond_to do |format| + format.html do + redirect_to action: "show", id: @meeting + end + + format.turbo_stream do + render turbo_stream: turbo_stream.redirect_to(meeting_path(@meeting)) + end + end else - @meeting = call.result - render template: "meetings/new", project_id: @project, locals: { copy_from: @copy_from } + respond_to do |format| + format.html do + render template: "meetings/new", project_id: @project, locals: { copy_from: @copy_from } + end + + format.turbo_stream do + update_via_turbo_stream(component: Meetings::Index::NewFormComponent.new(meeting: @meeting, project: @project), + status: :bad_request) + + respond_with_turbo_streams + end + end end end + def new_dialog + respond_with_dialog Meetings::Index::NewDialogComponent.new(meeting: @meeting, project: @project) + end + def new; end current_menu_item :new do diff --git a/modules/meeting/app/forms/meeting/location.rb b/modules/meeting/app/forms/meeting/location.rb index b18d1ed6243f..a26657a2a6bf 100644 --- a/modules/meeting/app/forms/meeting/location.rb +++ b/modules/meeting/app/forms/meeting/location.rb @@ -33,7 +33,11 @@ class Meeting::Location < ApplicationForm placeholder: Meeting.human_attribute_name(:location), label: Meeting.human_attribute_name(:location), visually_hide_label: false, - leading_visual: { icon: :link } + leading_visual: { icon: @icon } ) end + + def initialize(icon: :link) + @icon = icon + end end diff --git a/modules/meeting/app/forms/meeting/project_autocompleter.rb b/modules/meeting/app/forms/meeting/project_autocompleter.rb new file mode 100644 index 000000000000..37e62534d3c6 --- /dev/null +++ b/modules/meeting/app/forms/meeting/project_autocompleter.rb @@ -0,0 +1,46 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class Meeting::ProjectAutocompleter < ApplicationForm + form do |f| + f.project_autocompleter( + name: "project_id", + id: "project_id", + label: Project.model_name.human, + autocomplete_options: { + with_search_icon: true, + openDirectly: false, + focusDirectly: false, + dropdownPosition: "bottom", + inputName: "project_id", + inputValue: @project&.id, + filters: [{ name: "user_action", operator: "=", values: ["meetings/create"] }] + } + ) + end +end diff --git a/modules/meeting/app/forms/meeting/title.rb b/modules/meeting/app/forms/meeting/title.rb new file mode 100644 index 000000000000..9cb02e7b6e25 --- /dev/null +++ b/modules/meeting/app/forms/meeting/title.rb @@ -0,0 +1,39 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class Meeting::Title < ApplicationForm + form do |meeting_form| + meeting_form.text_field( + name: :title, + required: true, + label: Meeting.human_attribute_name(:title), + placeholder: Meeting.human_attribute_name(:title), + visually_hide_label: false + ) + end +end diff --git a/modules/meeting/app/forms/meeting/type.rb b/modules/meeting/app/forms/meeting/type.rb new file mode 100644 index 000000000000..bc8135b7dd3a --- /dev/null +++ b/modules/meeting/app/forms/meeting/type.rb @@ -0,0 +1,33 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See COPYRIGHT and LICENSE files for more details. +#++ + +class Meeting::Type < ApplicationForm + form do |meeting_form| + meeting_form.hidden(name: :type, value: "StructuredMeeting") + end +end diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index 7ff3afe8f649..1cb5b87e3cb5 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -104,6 +104,8 @@ en: label_meeting: "Meeting" label_meeting_plural: "Meetings" label_meeting_new: "New Meeting" + label_meeting_new_one_time: "New one-time meeting" + label_meeting_create: "Create meeting" label_meeting_edit: "Edit Meeting" label_meeting_agenda: "Agenda" label_meeting_minutes: "Minutes" @@ -156,7 +158,7 @@ en: new_date_time: "New date/time" label_mail_all_participants: "Send email to all participants" types: - classic: "Classic (deprecated)" + classic: "Classic" classic_text: "Organize your meeting in a formattable text agenda and protocol." structured: "One-time" structured_text: "Organize your meeting as a list of agenda items, optionally linking them to a work package." diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index 0bf45d5545bb..a22ef0828d5d 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -56,6 +56,7 @@ end resources :meetings do + get :new_dialog, on: :collection member do get :check_for_updates get :cancel_edit diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index 41ee96644035..4c60ed2fedef 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -47,7 +47,7 @@ class Engine < ::Rails::Engine work_package_meetings_tab: %i[index count] }, permissible_on: :project permission :create_meetings, - { meetings: %i[new create copy], + { meetings: %i[new create copy new_dialog], "meetings/menus": %i[show] }, permissible_on: :project, require: :member,