From cad8628db77c1398b99b3ff7248b3eb8416a6cb3 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:38:35 +0200 Subject: [PATCH 1/6] [#51015] Mobile, the participant section should move to details section https://community.openproject.org/work_packages/51015 --- modules/meeting/app/components/_index.sass | 1 + .../sidebar/details_component.html.erb | 30 +++++++++++++++++++ .../sidebar/participants_component.sass | 14 +++++++++ .../meetings/sidebar/state_component.sass | 2 +- .../meetings/sidebar_component.html.erb | 4 ++- .../app/controllers/meetings_controller.rb | 1 + modules/meeting/config/locales/en.yml | 1 + 7 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 modules/meeting/app/components/meetings/sidebar/participants_component.sass diff --git a/modules/meeting/app/components/_index.sass b/modules/meeting/app/components/_index.sass index 2f17a54aef68..3d93216cbba6 100644 --- a/modules/meeting/app/components/_index.sass +++ b/modules/meeting/app/components/_index.sass @@ -1,3 +1,4 @@ @import "./meeting_agenda_items/item_component/show_component.sass" @import "./meeting_agenda_items/form_component.sass" @import "./meetings/sidebar/state_component.sass" +@import "./meetings/sidebar/participants_component.sass" diff --git a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb index 86da51680f2f..04ed6501feb1 100644 --- a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb @@ -85,6 +85,36 @@ end end end + + details.with_row(mt: 2, classes: 'meeting-detail-participants') do + render_meeting_attribute_row(:people) do + flex_layout do |duration| + duration.with_column(mr: 2) do + render(Primer::Beta::Text.new) do + [ + @meeting.invited_or_attended_participants.count, + Meeting.human_attribute_name( + :participants, + count: @meeting.invited_or_attended_participants.count + ) + ].join(" ") + end + end + + if @meeting.editable? + duration.with_column(mr: 2) do + render(Primer::Beta::Button.new( + font_weight: :normal, + scheme: :link, + data: { 'show-dialog-id': "edit-participants-dialog" } + )) do |link| + t("label_meeting_show_all_participants") + end + end + end + end + end + end end end end diff --git a/modules/meeting/app/components/meetings/sidebar/participants_component.sass b/modules/meeting/app/components/meetings/sidebar/participants_component.sass new file mode 100644 index 000000000000..6aa2e1979201 --- /dev/null +++ b/modules/meeting/app/components/meetings/sidebar/participants_component.sass @@ -0,0 +1,14 @@ +@import 'helpers' + +.meeting-detail-participants + visibility: collapse + +@media screen and (max-width: $breakpoint-sm) + #meetings-sidebar-component + .BorderGrid-row:nth-child(3) + visibility: collapse + #participants-component + .Overlay-backdrop--center + visibility: visible + .meeting-detail-participants + visibility: visible diff --git a/modules/meeting/app/components/meetings/sidebar/state_component.sass b/modules/meeting/app/components/meetings/sidebar/state_component.sass index 341b9bb00b73..bc7fe8fa417b 100644 --- a/modules/meeting/app/components/meetings/sidebar/state_component.sass +++ b/modules/meeting/app/components/meetings/sidebar/state_component.sass @@ -4,4 +4,4 @@ .op-meeting-sidebar-state flex-direction: row !important justify-content: space-between - align-items: center \ No newline at end of file + align-items: center diff --git a/modules/meeting/app/components/meetings/sidebar_component.html.erb b/modules/meeting/app/components/meetings/sidebar_component.html.erb index 182e5b13787a..55d99b345ab0 100644 --- a/modules/meeting/app/components/meetings/sidebar_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar_component.html.erb @@ -3,7 +3,9 @@ render(Primer::OpenProject::BorderGrid.new) do |border_grid| border_grid.with_row { render(Meetings::Sidebar::DetailsComponent.new(meeting: @meeting)) } border_grid.with_row { render(Meetings::Sidebar::StateComponent.new(meeting: @meeting)) } - border_grid.with_row { render(Meetings::Sidebar::ParticipantsComponent.new(meeting: @meeting)) } + border_grid.with_row(id: 'participants-component') do + render(Meetings::Sidebar::ParticipantsComponent.new(meeting: @meeting)) + end end end %> diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb index 386c36167fb9..6028cd0243dc 100644 --- a/modules/meeting/app/controllers/meetings_controller.rb +++ b/modules/meeting/app/controllers/meetings_controller.rb @@ -148,6 +148,7 @@ def update_participants if @meeting.errors.any? update_sidebar_participants_form_component_via_turbo_stream else + update_sidebar_details_component_via_turbo_stream update_sidebar_participants_component_via_turbo_stream end diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index ab3485306e00..b9ba8f90f615 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -189,6 +189,7 @@ en: label_meeting_manage_participants: "Manage participants" label_meeting_no_participants: "No participants" label_meeting_show_hide_participants: "Show/hide %{count} more" + label_meeting_show_all_participants: "Show all" label_meeting_add_participants: "Add participants" text_meeting_not_editable_anymore: "This meeting is not editable anymore." From f46687f4614ee30ff2bb336ec08beb9859538524 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:05:40 +0200 Subject: [PATCH 2/6] Re-use the participant editing form for the mobile view, also disable editing when the meeting is not editable --- modules/meeting/app/components/_index.sass | 1 + .../sidebar/details_component.html.erb | 22 +++++---- .../meetings/sidebar/details_component.sass | 11 +++++ .../sidebar/participants_component.html.erb | 13 +++--- .../sidebar/participants_component.sass | 8 ---- .../participants_form_component.html.erb | 45 ++++++++++++------- .../meetings/sidebar_component.html.erb | 4 +- 7 files changed, 61 insertions(+), 43 deletions(-) create mode 100644 modules/meeting/app/components/meetings/sidebar/details_component.sass diff --git a/modules/meeting/app/components/_index.sass b/modules/meeting/app/components/_index.sass index 3d93216cbba6..7655e791146e 100644 --- a/modules/meeting/app/components/_index.sass +++ b/modules/meeting/app/components/_index.sass @@ -2,3 +2,4 @@ @import "./meeting_agenda_items/form_component.sass" @import "./meetings/sidebar/state_component.sass" @import "./meetings/sidebar/participants_component.sass" +@import "./meetings/sidebar/details_component.sass" diff --git a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb index 04ed6501feb1..22af416eeab8 100644 --- a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb @@ -101,15 +101,19 @@ end end - if @meeting.editable? - duration.with_column(mr: 2) do - render(Primer::Beta::Button.new( - font_weight: :normal, - scheme: :link, - data: { 'show-dialog-id': "edit-participants-dialog" } - )) do |link| - t("label_meeting_show_all_participants") - end + duration.with_column(mr: 2) do + render(Primer::Alpha::Dialog.new( + id: "edit-participants-dialog", title: Meeting.human_attribute_name(:participants), + size: :medium_portrait + )) do |dialog| + render(Meetings::Sidebar::ParticipantsFormComponent.new(meeting: @meeting)) + end+ + render(Primer::Beta::Button.new( + font_weight: :normal, + scheme: :link, + data: { 'show-dialog-id': "edit-participants-dialog" } + )) do |link| + t("label_meeting_show_all_participants") end end end diff --git a/modules/meeting/app/components/meetings/sidebar/details_component.sass b/modules/meeting/app/components/meetings/sidebar/details_component.sass new file mode 100644 index 000000000000..b1df95165ae1 --- /dev/null +++ b/modules/meeting/app/components/meetings/sidebar/details_component.sass @@ -0,0 +1,11 @@ +.meeting-detail-participants + visibility: collapse + // The modal is rendered inside the mobile participant list element and it can be + // triggered from both mobile and desktop views. The mobile participant list is hidden + // when the desktop view is active, but the modal inside it should still be visible. + .Overlay-backdrop--center + visibility: visible + +@media screen and (max-width: $breakpoint-sm) + .meeting-detail-participants + visibility: visible diff --git a/modules/meeting/app/components/meetings/sidebar/participants_component.html.erb b/modules/meeting/app/components/meetings/sidebar/participants_component.html.erb index 13482b57ae98..a7885364b8f9 100644 --- a/modules/meeting/app/components/meetings/sidebar/participants_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar/participants_component.html.erb @@ -17,13 +17,12 @@ if @meeting.editable? heading.with_column do - render(Primer::Alpha::Dialog.new( - id: "edit-participants-dialog", title: Meeting.human_attribute_name(:participants), - size: :medium_portrait - )) do |dialog| - dialog.with_show_button(icon: :gear, 'aria-label': t("label_meeting_manage_participants"), scheme: :invisible) - render(Meetings::Sidebar::ParticipantsFormComponent.new(meeting: @meeting)) - end + render(Primer::Beta::IconButton.new( + icon: :gear, + scheme: :invisible, + 'aria-label': t("label_meeting_manage_participants"), + data: { 'show-dialog-id': "edit-participants-dialog" } + )) end end end diff --git a/modules/meeting/app/components/meetings/sidebar/participants_component.sass b/modules/meeting/app/components/meetings/sidebar/participants_component.sass index 6aa2e1979201..611d21aa1940 100644 --- a/modules/meeting/app/components/meetings/sidebar/participants_component.sass +++ b/modules/meeting/app/components/meetings/sidebar/participants_component.sass @@ -1,14 +1,6 @@ @import 'helpers' -.meeting-detail-participants - visibility: collapse - @media screen and (max-width: $breakpoint-sm) #meetings-sidebar-component .BorderGrid-row:nth-child(3) visibility: collapse - #participants-component - .Overlay-backdrop--center - visibility: visible - .meeting-detail-participants - visibility: visible diff --git a/modules/meeting/app/components/meetings/sidebar/participants_form_component.html.erb b/modules/meeting/app/components/meetings/sidebar/participants_form_component.html.erb index dbd369a60c78..5d0c7075ddfd 100644 --- a/modules/meeting/app/components/meetings/sidebar/participants_form_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar/participants_form_component.html.erb @@ -42,8 +42,11 @@ )) end existing_participant.with_column(style: "width: 90px;", text_align: :center) do - styled_check_box_tag "meeting[participants_attributes][][invited]", 1, participant.invited?, - id: "checkbox_invited_#{participant.user.id}" + styled_check_box_tag "meeting[participants_attributes][][invited]", + value = 1, + checked = participant.invited?, + id: "checkbox_invited_#{participant.user.id}", + disabled: !@meeting.editable? # Primer checkboxes currently not working in this context as they render an additional hidden input tag # messing up the nested attributes mapping when posting the data to the server # @@ -58,8 +61,11 @@ # )) end existing_participant.with_column(style: "width: 90px;", text_align: :center) do - styled_check_box_tag "meeting[participants_attributes][][attended]", 1, participant.attended?, - id: "checkbox_attended_#{participant.user.id}" + styled_check_box_tag "meeting[participants_attributes][][attended]", + value = 1, + checked = participant.attended?, + id: "checkbox_attended_#{participant.user.id}", + disabled: !@meeting.editable? end end end @@ -74,13 +80,19 @@ end new_participant.with_column(style: "width: 90px;", text_align: :center) do - styled_check_box_tag "meeting[participants_attributes][][invited]", value = "1", checked = false, - id: "checkbox_invited_#{user.id}" + styled_check_box_tag "meeting[participants_attributes][][invited]", + value = "1", + checked = false, + id: "checkbox_invited_#{user.id}", + disabled: !@meeting.editable? end new_participant.with_column(style: "width: 90px;", text_align: :center) do - styled_check_box_tag "meeting[participants_attributes][][attended]", value = "1", checked = false, - id: "checkbox_attended_#{user.id}" + styled_check_box_tag "meeting[participants_attributes][][attended]", + value = "1", + checked = false, + id: "checkbox_attended_#{user.id}", + disabled: !@meeting.editable? end end end @@ -91,14 +103,15 @@ end end - collection.with_component(Primer::Alpha::Dialog::Footer.new(show_divider: true)) do - component_collection do |footer| - footer.with_component(Primer::ButtonComponent.new(data: { 'close-dialog-id': "edit-participants-dialog" })) do - t("button_cancel") - end - - footer.with_component(Primer::ButtonComponent.new(scheme: :primary, type: :submit)) do - t("button_save") + if @meeting.editable? + collection.with_component(Primer::Alpha::Dialog::Footer.new(show_divider: true)) do + component_collection do |footer| + footer.with_component(Primer::ButtonComponent.new(data: { 'close-dialog-id': "edit-participants-dialog" })) do + t("button_cancel") + end + footer.with_component(Primer::ButtonComponent.new(scheme: :primary, type: :submit)) do + t("button_save") + end end end end diff --git a/modules/meeting/app/components/meetings/sidebar_component.html.erb b/modules/meeting/app/components/meetings/sidebar_component.html.erb index 55d99b345ab0..182e5b13787a 100644 --- a/modules/meeting/app/components/meetings/sidebar_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar_component.html.erb @@ -3,9 +3,7 @@ render(Primer::OpenProject::BorderGrid.new) do |border_grid| border_grid.with_row { render(Meetings::Sidebar::DetailsComponent.new(meeting: @meeting)) } border_grid.with_row { render(Meetings::Sidebar::StateComponent.new(meeting: @meeting)) } - border_grid.with_row(id: 'participants-component') do - render(Meetings::Sidebar::ParticipantsComponent.new(meeting: @meeting)) - end + border_grid.with_row { render(Meetings::Sidebar::ParticipantsComponent.new(meeting: @meeting)) } end end %> From b0b56c0d5a17a095f4ccd739b85bbd1f471d6606 Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:25:15 +0200 Subject: [PATCH 3/6] Make the desktop participants list display:none instead of visibility:hidden. Render participant button inside the dialog. --- .../meetings/sidebar/details_component.html.erb | 10 +++------- .../meetings/sidebar/participants_component.sass | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb index 22af416eeab8..810fd9eaa3c9 100644 --- a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb @@ -106,14 +106,10 @@ id: "edit-participants-dialog", title: Meeting.human_attribute_name(:participants), size: :medium_portrait )) do |dialog| + dialog.with_show_button(scheme: :link, font_weight: :normal) do + t("label_meeting_show_all_participants") + end render(Meetings::Sidebar::ParticipantsFormComponent.new(meeting: @meeting)) - end+ - render(Primer::Beta::Button.new( - font_weight: :normal, - scheme: :link, - data: { 'show-dialog-id': "edit-participants-dialog" } - )) do |link| - t("label_meeting_show_all_participants") end end end diff --git a/modules/meeting/app/components/meetings/sidebar/participants_component.sass b/modules/meeting/app/components/meetings/sidebar/participants_component.sass index 611d21aa1940..f12dd12b4c24 100644 --- a/modules/meeting/app/components/meetings/sidebar/participants_component.sass +++ b/modules/meeting/app/components/meetings/sidebar/participants_component.sass @@ -3,4 +3,4 @@ @media screen and (max-width: $breakpoint-sm) #meetings-sidebar-component .BorderGrid-row:nth-child(3) - visibility: collapse + display: none From a6f91419ae87114609bc1b813f6997ec950d005f Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Fri, 24 Nov 2023 18:12:19 +0200 Subject: [PATCH 4/6] Add mobile structured meeting participants specs. Fix bug of plural participants translation. --- .../sidebar/details_component.html.erb | 11 +- .../sidebar/participants_form_component.rb | 2 +- modules/meeting/config/locales/en.yml | 3 + .../mobile_structure_meeting_spec.rb | 116 ++++++++++++++++++ .../pages/structured_meeting/mobile/show.rb | 47 +++++++ .../support/pages/structured_meeting/show.rb | 36 ++++++ 6 files changed, 207 insertions(+), 8 deletions(-) create mode 100644 modules/meeting/spec/features/structured_meetings/mobile_structure_meeting_spec.rb create mode 100644 modules/meeting/spec/support/pages/structured_meeting/mobile/show.rb diff --git a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb index 810fd9eaa3c9..64ed1afd29dc 100644 --- a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb @@ -91,13 +91,10 @@ flex_layout do |duration| duration.with_column(mr: 2) do render(Primer::Beta::Text.new) do - [ - @meeting.invited_or_attended_participants.count, - Meeting.human_attribute_name( - :participants, - count: @meeting.invited_or_attended_participants.count - ) - ].join(" ") + Meeting.human_attribute_name( + :participant, + count: @meeting.invited_or_attended_participants.count + ) end end diff --git a/modules/meeting/app/components/meetings/sidebar/participants_form_component.rb b/modules/meeting/app/components/meetings/sidebar/participants_form_component.rb index 4b884f58f8d5..1f41edb8a509 100644 --- a/modules/meeting/app/components/meetings/sidebar/participants_form_component.rb +++ b/modules/meeting/app/components/meetings/sidebar/participants_form_component.rb @@ -40,7 +40,7 @@ def initialize(meeting:) end def render? - User.current.allowed_in_project?(:edit_meetings, @meeting.project) + User.current.allowed_in_project?(:view_meetings, @meeting.project) end end end diff --git a/modules/meeting/config/locales/en.yml b/modules/meeting/config/locales/en.yml index b9ba8f90f615..3a7341ee61a2 100644 --- a/modules/meeting/config/locales/en.yml +++ b/modules/meeting/config/locales/en.yml @@ -44,6 +44,9 @@ en: duration: "Duration" notes: "Notes" participants: "Participants" + participant: + one: "1 Participant" + other: "%{count} Participants" participants_attended: "Attendees" participants_invited: "Invitees" project: "Project" diff --git a/modules/meeting/spec/features/structured_meetings/mobile_structure_meeting_spec.rb b/modules/meeting/spec/features/structured_meetings/mobile_structure_meeting_spec.rb new file mode 100644 index 000000000000..83bb05b8ba1d --- /dev/null +++ b/modules/meeting/spec/features/structured_meetings/mobile_structure_meeting_spec.rb @@ -0,0 +1,116 @@ +#-- 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. +#++ + +require 'spec_helper' + +require_relative '../../support/pages/structured_meeting//mobile/show' + +RSpec.describe 'Structured meetings CRUD', + :js, + :with_cuprite do + include Components::Autocompleter::NgSelectAutocompleteHelpers + + shared_let(:project) { create(:project, enabled_module_names: %w[meetings work_package_tracking]) } + shared_let(:user) do + create(:user, + lastname: 'First', + member_with_permissions: { project => %i[view_meetings create_meetings edit_meetings delete_meetings manage_agendas + close_meeting_agendas view_work_packages] }).tap do |u| + u.pref[:time_zone] = 'utc' + + u.save! + end + end + shared_let(:other_user) do + create(:user, + lastname: 'Second', + member_with_permissions: { project => %i[view_meetings view_work_packages] }) + end + shared_let(:no_member_user) do + create(:user, + lastname: 'Third') + end + + shared_let(:meeting) do + create(:structured_meeting, project:, author: user) + end + + let(:current_user) { user } + let(:show_page) { Pages::StructuredMeeting::Mobile::Show.new(StructuredMeeting.order(id: :asc).last) } + + include_context 'with mobile screen size' + + before do + login_as current_user + show_page.visit! + end + + it 'can edit participants of a structured meeting' do + expect(page).to have_current_path(show_page.path) + show_page.expect_participants(count: 1) + + show_page.open_participant_form + show_page.in_participant_form do + show_page.expect_participant(user, invited: true, attended: false) + show_page.expect_participant(other_user, invited: false, attended: false) + show_page.expect_available_participants(count: 2) + expect(page).to have_button('Save') + + check(id: "checkbox_invited_#{other_user.id}") + click_button('Save') + end + + show_page.expect_participants(count: 2) + + # when meeting is closed, can view but not edit + show_page.close_meeting + + show_page.open_participant_form + show_page.in_participant_form do + show_page.expect_participant(user, invited: true, attended: false, editable: false) + show_page.expect_participant(other_user, invited: true, attended: false, editable: false) + show_page.expect_available_participants(count: 2) + expect(page).not_to have_button('Save') + end + show_page.close_dialog + + show_page.reopen_meeting + + # other_use can view, but not edit + login_as other_user + show_page.visit! + + show_page.open_participant_form + show_page.in_participant_form do + show_page.expect_participant(user, invited: true, attended: false, editable: false) + show_page.expect_participant(other_user, invited: true, attended: false, editable: false) + show_page.expect_available_participants(count: 2) + expect(page).not_to have_button('Save') + end + end +end diff --git a/modules/meeting/spec/support/pages/structured_meeting/mobile/show.rb b/modules/meeting/spec/support/pages/structured_meeting/mobile/show.rb new file mode 100644 index 000000000000..54ef6d3a6675 --- /dev/null +++ b/modules/meeting/spec/support/pages/structured_meeting/mobile/show.rb @@ -0,0 +1,47 @@ +#-- 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. +#++ + +require_relative '../show' + +module Pages::StructuredMeeting::Mobile + class Show < ::Pages::StructuredMeeting::Show + def expect_participants(count: 1) + within(meeting_details_container) do + expect(page).to have_text(Meeting.human_attribute_name(:participant, count:)) + expect(page).to have_button("Show all") + end + end + + def open_participant_form + within(meeting_details_container) do + click_button "Show all" + end + expect(page).to have_css('#meetings-sidebar-participants-form-component') + end + end +end diff --git a/modules/meeting/spec/support/pages/structured_meeting/show.rb b/modules/meeting/spec/support/pages/structured_meeting/show.rb index 1d74f0e8aaa4..c42025ca0d18 100644 --- a/modules/meeting/spec/support/pages/structured_meeting/show.rb +++ b/modules/meeting/spec/support/pages/structured_meeting/show.rb @@ -149,5 +149,41 @@ def clear_item_edit_work_package_title ng_select_clear page.find(".op-meeting-agenda-item-form--title") expect(page).to have_css(".ng-input ", value: nil) end + + def in_participant_form(&) + page.within('#meetings-sidebar-participants-form-component form', &) + end + + def expect_participant(participant, invited: false, attended: false, editable: true) + expect(page).to have_text(participant.name) + expect(page).to have_field(id: "checkbox_invited_#{participant.id}", checked: invited, disabled: !editable) + expect(page).to have_field(id: "checkbox_attended_#{participant.id}", checked: attended, disabled: !editable) + end + + def invite_participant(participant) + check(id: "checkbox_invited_#{participant.id}") + end + + def expect_available_participants(count:) + expect(page).to have_link(class: 'op-principal--name', count:) + end + + def close_meeting + click_button('Close meeting') + expect(page).to have_button('Reopen meeting') + end + + def reopen_meeting + click_button('Reopen meeting') + expect(page).to have_button('Close meeting') + end + + def close_dialog + click_button(class: 'Overlay-closeButton') + end + + def meeting_details_container + find_by_id('meetings-sidebar-details-component') + end end end From eca91d66067aa1f2cdbae8ebc677642b8f59463f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Mon, 27 Nov 2023 16:24:22 +0100 Subject: [PATCH 5/6] Use the AsyncDialogComponent to dynamically load --- .../sidebar/details_component.html.erb | 19 +- .../participants_form_component.html.erb | 188 +++++++++--------- .../app/controllers/meetings_controller.rb | 4 + modules/meeting/config/routes.rb | 1 + .../lib/open_project/meeting/engine.rb | 2 +- 5 files changed, 111 insertions(+), 103 deletions(-) diff --git a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb index 64ed1afd29dc..8bcb54ec4b24 100644 --- a/modules/meeting/app/components/meetings/sidebar/details_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar/details_component.html.erb @@ -99,15 +99,16 @@ end duration.with_column(mr: 2) do - render(Primer::Alpha::Dialog.new( - id: "edit-participants-dialog", title: Meeting.human_attribute_name(:participants), - size: :medium_portrait - )) do |dialog| - dialog.with_show_button(scheme: :link, font_weight: :normal) do - t("label_meeting_show_all_participants") - end - render(Meetings::Sidebar::ParticipantsFormComponent.new(meeting: @meeting)) - end + render(OpTurbo::OpPrimer::AsyncDialogComponent.new( + id: "edit-participants-dialog", + src: participants_dialog_meeting_path(@meeting), + size: :medium_portrait, + title: Meeting.human_attribute_name(:participants), + button_text: t("label_meeting_show_all_participants"), + button_attributes: { + scheme: :link, + font_weight: :normal + })) end end end diff --git a/modules/meeting/app/components/meetings/sidebar/participants_form_component.html.erb b/modules/meeting/app/components/meetings/sidebar/participants_form_component.html.erb index 5d0c7075ddfd..b1643d62d8cc 100644 --- a/modules/meeting/app/components/meetings/sidebar/participants_form_component.html.erb +++ b/modules/meeting/app/components/meetings/sidebar/participants_form_component.html.erb @@ -1,98 +1,100 @@ <%= - component_wrapper do - primer_form_with( - model: @meeting, - method: :put, - url: update_participants_meeting_path(@meeting) - ) do |f| - component_collection do |collection| - collection.with_component(Primer::Alpha::Dialog::Body.new(style: "max-height: 460px;", my: 3)) do - flex_layout(mt: 3) do |form_container| - form_container.with_row do - flex_layout(justify_content: :flex_end) do |header| - header.with_column(style: "width: 90px;", text_align: :center) do - render(Primer::Beta::Text.new(font_weight: :emphasized)) { t("description_invite").capitalize } - end + content_tag("turbo-frame", id: "edit-participants-dialog-frame") do + component_wrapper do + primer_form_with( + model: @meeting, + method: :put, + url: update_participants_meeting_path(@meeting) + ) do |f| + component_collection do |collection| + collection.with_component(Primer::Alpha::Dialog::Body.new(style: "max-height: 460px;", my: 3)) do + flex_layout(mt: 3) do |form_container| + form_container.with_row do + flex_layout(justify_content: :flex_end) do |header| + header.with_column(style: "width: 90px;", text_align: :center) do + render(Primer::Beta::Text.new(font_weight: :emphasized)) { t("description_invite").capitalize } + end - header.with_column(style: "width: 90px;", text_align: :center) do - render(Primer::Beta::Text.new(font_weight: :emphasized)) { t("description_attended").capitalize } + header.with_column(style: "width: 90px;", text_align: :center) do + render(Primer::Beta::Text.new(font_weight: :emphasized)) { t("description_attended").capitalize } + end end end - end - form_container.with_row do - flex_layout(my: 3) do |form_content| - @meeting.all_changeable_participants.sort.each do |user| - form_content.with_row do - hidden_field_tag "meeting[participants_attributes][][user_id]", user.id - end - form_content.with_row(mb: 2, pb: 1, border: :bottom) do - if @meeting.participants.present? && participant = @meeting.participants.detect { |p| p.user_id == user.id } - flex_layout do |existing_participant_container| - existing_participant_container.with_row do - hidden_field_tag "meeting[participants_attributes][][id]", participant.id - end + form_container.with_row do + flex_layout(my: 3) do |form_content| + @meeting.all_changeable_participants.sort.each do |user| + form_content.with_row do + hidden_field_tag "meeting[participants_attributes][][user_id]", user.id + end + form_content.with_row(mb: 2, pb: 1, border: :bottom) do + if @meeting.participants.present? && participant = @meeting.participants.detect { |p| p.user_id == user.id } + flex_layout do |existing_participant_container| + existing_participant_container.with_row do + hidden_field_tag "meeting[participants_attributes][][id]", participant.id + end - existing_participant_container.with_row do - flex_layout(align_items: :center) do |existing_participant| - existing_participant.with_column(flex: 1, classes: 'ellipsis') do - render(Users::AvatarComponent.new( - user: participant.user, - classes: 'op-principal_flex' - )) - end - existing_participant.with_column(style: "width: 90px;", text_align: :center) do - styled_check_box_tag "meeting[participants_attributes][][invited]", - value = 1, - checked = participant.invited?, - id: "checkbox_invited_#{participant.user.id}", - disabled: !@meeting.editable? - # Primer checkboxes currently not working in this context as they render an additional hidden input tag - # messing up the nested attributes mapping when posting the data to the server - # - # render(Primer::Alpha::CheckBox.new( - # name: "meeting[participants_attributes][][invited]", - # checked: participant.invited?, - # id: "checkbox_invited_#{participant.user.id}", - # visually_hide_label: true, - # label: "Invited", - # scheme: :boolean, - # unchecked_value: "" - # )) - end - existing_participant.with_column(style: "width: 90px;", text_align: :center) do - styled_check_box_tag "meeting[participants_attributes][][attended]", - value = 1, - checked = participant.attended?, - id: "checkbox_attended_#{participant.user.id}", - disabled: !@meeting.editable? + existing_participant_container.with_row do + flex_layout(align_items: :center) do |existing_participant| + existing_participant.with_column(flex: 1, classes: 'ellipsis') do + render(Users::AvatarComponent.new( + user: participant.user, + classes: 'op-principal_flex' + )) + end + existing_participant.with_column(style: "width: 90px;", text_align: :center) do + styled_check_box_tag "meeting[participants_attributes][][invited]", + value = 1, + checked = participant.invited?, + id: "checkbox_invited_#{participant.user.id}", + disabled: !@meeting.editable? + # Primer checkboxes currently not working in this context as they render an additional hidden input tag + # messing up the nested attributes mapping when posting the data to the server + # + # render(Primer::Alpha::CheckBox.new( + # name: "meeting[participants_attributes][][invited]", + # checked: participant.invited?, + # id: "checkbox_invited_#{participant.user.id}", + # visually_hide_label: true, + # label: "Invited", + # scheme: :boolean, + # unchecked_value: "" + # )) + end + existing_participant.with_column(style: "width: 90px;", text_align: :center) do + styled_check_box_tag "meeting[participants_attributes][][attended]", + value = 1, + checked = participant.attended?, + id: "checkbox_attended_#{participant.user.id}", + disabled: !@meeting.editable? + end end end end - end - else - flex_layout(align_items: :center) do |new_participant| - new_participant.with_column(flex: 1, classes: 'ellipsis') do - render(Users::AvatarComponent.new( - user:, - classes: 'op-principal_flex' - )) - end + else + flex_layout(align_items: :center) do |new_participant| + new_participant.with_column(flex: 1, classes: 'ellipsis') do + render(Users::AvatarComponent.new( + user:, + classes: 'op-principal_flex' + )) + end - new_participant.with_column(style: "width: 90px;", text_align: :center) do - styled_check_box_tag "meeting[participants_attributes][][invited]", - value = "1", - checked = false, - id: "checkbox_invited_#{user.id}", - disabled: !@meeting.editable? - end + new_participant.with_column(style: "width: 90px;", text_align: :center) do + styled_check_box_tag "meeting[participants_attributes][][invited]", + value = "1", + checked = false, + id: "checkbox_invited_#{user.id}", + disabled: !@meeting.editable? + end - new_participant.with_column(style: "width: 90px;", text_align: :center) do - styled_check_box_tag "meeting[participants_attributes][][attended]", - value = "1", - checked = false, - id: "checkbox_attended_#{user.id}", - disabled: !@meeting.editable? + new_participant.with_column(style: "width: 90px;", text_align: :center) do + styled_check_box_tag "meeting[participants_attributes][][attended]", + value = "1", + checked = false, + id: "checkbox_attended_#{user.id}", + disabled: !@meeting.editable? + end end end end @@ -101,16 +103,16 @@ end end end - end - if @meeting.editable? - collection.with_component(Primer::Alpha::Dialog::Footer.new(show_divider: true)) do - component_collection do |footer| - footer.with_component(Primer::ButtonComponent.new(data: { 'close-dialog-id': "edit-participants-dialog" })) do - t("button_cancel") - end - footer.with_component(Primer::ButtonComponent.new(scheme: :primary, type: :submit)) do - t("button_save") + if @meeting.editable? + collection.with_component(Primer::Alpha::Dialog::Footer.new(show_divider: true)) do + component_collection do |footer| + footer.with_component(Primer::ButtonComponent.new(data: { 'close-dialog-id': "edit-participants-dialog" })) do + t("button_cancel") + end + footer.with_component(Primer::ButtonComponent.new(scheme: :primary, type: :submit)) do + t("button_save") + end end end end diff --git a/modules/meeting/app/controllers/meetings_controller.rb b/modules/meeting/app/controllers/meetings_controller.rb index 6028cd0243dc..4eafe00791d6 100644 --- a/modules/meeting/app/controllers/meetings_controller.rb +++ b/modules/meeting/app/controllers/meetings_controller.rb @@ -141,6 +141,10 @@ def update end end + def participants_dialog + render(Meetings::Sidebar::ParticipantsFormComponent.new(meeting: @meeting), layout: false) + end + def update_participants @meeting.participants_attributes = @converted_params.delete(:participants_attributes) @meeting.save diff --git a/modules/meeting/config/routes.rb b/modules/meeting/config/routes.rb index 0993ecbd6208..13b826b70b7c 100644 --- a/modules/meeting/config/routes.rb +++ b/modules/meeting/config/routes.rb @@ -53,6 +53,7 @@ get :download_ics put :update_title put :update_details + get :participants_dialog put :update_participants put :change_state post :notify diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index 45e45d3d41a4..eb6add5eb73b 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -52,7 +52,7 @@ class Engine < ::Rails::Engine contract_actions: { meetings: %i[create] } permission :edit_meetings, { - meetings: %i[edit cancel_edit update update_title update_details update_participants], + meetings: %i[edit cancel_edit update update_title update_details update_participants participants_dialog], work_package_meetings_tab: %i[add_work_package_to_meeting_dialog add_work_package_to_meeting] }, permissible_on: :project, From 0b36a15d99fe736f75beb5dc623119a5fb9e73cd Mon Sep 17 00:00:00 2001 From: Dombi Attila <83396+dombesz@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:07:12 +0200 Subject: [PATCH 6/6] Move participants_dialog permission to the view_meetings --- modules/meeting/lib/open_project/meeting/engine.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/meeting/lib/open_project/meeting/engine.rb b/modules/meeting/lib/open_project/meeting/engine.rb index eb6add5eb73b..bf131f4a956f 100644 --- a/modules/meeting/lib/open_project/meeting/engine.rb +++ b/modules/meeting/lib/open_project/meeting/engine.rb @@ -40,7 +40,7 @@ class Engine < ::Rails::Engine bundled: true do project_module :meetings do permission :view_meetings, - { meetings: %i[index show download_ics], + { meetings: %i[index show download_ics participants_dialog], meeting_agendas: %i[history show diff], meeting_minutes: %i[history show diff], work_package_meetings_tab: %i[index count] }, @@ -52,7 +52,7 @@ class Engine < ::Rails::Engine contract_actions: { meetings: %i[create] } permission :edit_meetings, { - meetings: %i[edit cancel_edit update update_title update_details update_participants participants_dialog], + meetings: %i[edit cancel_edit update update_title update_details update_participants], work_package_meetings_tab: %i[add_work_package_to_meeting_dialog add_work_package_to_meeting] }, permissible_on: :project,