diff --git a/app/controllers/admin/activities_controller.rb b/app/controllers/admin/activities_controller.rb index 3c2402992..924966f0e 100644 --- a/app/controllers/admin/activities_controller.rb +++ b/app/controllers/admin/activities_controller.rb @@ -21,8 +21,11 @@ def show end def create - @activity = Activity.new(activity_post_params.except(:_destroy)) + @activity = Activity.new(activity_post_params.except(:_destroy, :notes_options)) + if @activity.notes_input_type != 'text' && params[:activity][:notes_options].present? + @activity.notes = params[:activity][:notes_options].compact_blank.join("\n") + end if @activity.save # manual call to impressionist, because otherwise the activity doesn't have an id yet impressionist(@activity) @@ -63,13 +66,16 @@ def update @activity = Activity.find(params[:id]) params = activity_post_params + if params[:notes_input_type] != 'text' && params[:notes_options].present? + params[:notes] = params[:notes_options].compact_blank.join("\n") + end # removing the images from disk if params[:_destroy] == 'true' logger.debug('remove poster from activity') @activity.poster.purge end - if @activity.update(params.except(:_destroy)) + if @activity.update(params.except(:_destroy, :notes_options)) redirect_to(@activity) else @recipients = @activity.payment_mail_recipients @@ -119,6 +125,8 @@ def activity_post_params :is_seniors, :participant_limit, :show_participants, - :_destroy) + :notes_input_type, + :_destroy, + notes_options: []) end end diff --git a/app/controllers/members/participants_controller.rb b/app/controllers/members/participants_controller.rb index 4d6906df7..091217ee0 100644 --- a/app/controllers/members/participants_controller.rb +++ b/app/controllers/members/participants_controller.rb @@ -28,7 +28,8 @@ def create end @member = Member.find(current_user.credentials_id) - @notes = params[:par_notes] + @notes = params[:par_notes] + reservist = false # Deny if already enrolled diff --git a/app/javascript/src/admin/activities.js b/app/javascript/src/admin/activities.js index 06b089899..d7608a4fc 100644 --- a/app/javascript/src/admin/activities.js +++ b/app/javascript/src/admin/activities.js @@ -540,3 +540,110 @@ function posterHandlers() { $(this).find('button[type="submit"].wait').addClass("disabled"); }); } + +$(document).on("click", "#add-notes-option", function () { + var optionIndex = $("#notes-options .col-md-6").length; + var optionHtml = + '
' + + '
' + + '' + + '
' + + '" + + "
" + + "
" + + "
"; + $("#notes-options").append(optionHtml); +}); + +$(document).on("click", ".delete-option", function () { + $(this).closest(".col-md-6").remove(); +}); + +$(document).on("change", "#activity_notes_input_type", function () { + var notesInputType = $(this).val(); + var notesContainer = $("#notes-container"); + + if (notesInputType === "text") { + var notesValue = $("#notes-options input") + .map(function () { + return $(this).val(); + }) + .get() + .join("\n"); + notesContainer.html( + '", + ); + } else { + var optionsHtml = ""; + var notesValue = $("#activity_notes").val(); + var optionsArray = []; + + if (notesValue && notesValue.trim() !== "") { + optionsArray = notesValue.split("\n"); + } + + if (optionsArray.length > 0) { + var titleValue = optionsArray[0]; + optionsHtml += + '
' + + '
' + + 'Title' + + '' + + "
" + + "
"; + + optionsArray.slice(1).forEach(function (optionValue) { + optionsHtml += + '
' + + '
' + + '' + + '
' + + '" + + "
" + + "
" + + "
"; + }); + } else { + optionsHtml += + '
' + + '
' + + 'Title' + + '' + + "
" + + "
" + + '
' + + '
' + + '' + + '
' + + '" + + "
" + + "
" + + "
"; + } + + notesContainer.html( + '
' + + optionsHtml + + "
" + + '
' + + '
' + + '" + + "
" + + "
", + ); + } +}); diff --git a/app/javascript/src/members/activities/activity.js b/app/javascript/src/members/activities/activity.js index 91bcd083d..a6e2d8301 100644 --- a/app/javascript/src/members/activities/activity.js +++ b/app/javascript/src/members/activities/activity.js @@ -113,6 +113,19 @@ export class Activity { has_notes() { return this.notes.length !== 0; } + + has_notes_checkboxes() { + return find_in_object(this.notes, function (note) { + return note.type === "checkbox"; + }); + } + + has_notes_radio_buttons() { + return find_in_object(this.notes, function (note) { + return note.type === "radio"; + }); + } + are_notes_filled() { return $.trim(this.notes.val()).length > 0; } @@ -184,12 +197,29 @@ Object.defineProperties(Activity.prototype, { */ value: function (method) { var activity = this; + var par_notes; + var notes = []; + console.log(activity.has_notes_checkboxes); + if (activity.has_notes_checkboxes()) { + console.log(this.notes.find(":checked")); + this.notes.each(function () { + if (this.checked) { + notes.push(this.value); + } + }); + par_notes = notes.join(", "); + } else if (activity.has_notes_radio_buttons()) { + par_notes = this.notes.find(":checked").val(); + } else { + par_notes = this.notes.val(); + } + var request = $.ajax({ url: "/activities/" + activity.id + "/participants", type: method, data: { authenticity_token: this.token, - par_notes: this.notes.val(), + par_notes: par_notes, }, }) .done(function (response) { @@ -374,7 +404,7 @@ Object.defineProperties( }, notes: function () { - return this.panel.find(".notes"); + return this.panel.find(".notes"); // .find function returns a jQuery object with the found elements }, notes_mandatory: function () { diff --git a/app/models/activity.rb b/app/models/activity.rb index 0f430faec..bf28d361a 100644 --- a/app/models/activity.rb +++ b/app/models/activity.rb @@ -33,8 +33,8 @@ def content_type end end + enum notes_input_type: { text: 0, checkboxes: 1, radio_buttons: 2 } validates :notes, presence: true, if: proc { |a| a.notes_public? || a.notes_mandatory? } - is_impressionable before_validation :validate_enrollable diff --git a/app/views/admin/activities/partials/_edit.html.haml b/app/views/admin/activities/partials/_edit.html.haml index 159adc5fc..b3e3de8b8 100644 --- a/app/views/admin/activities/partials/_edit.html.haml +++ b/app/views/admin/activities/partials/_edit.html.haml @@ -127,16 +127,56 @@ = label(:activity, :organized_by) .ui-select = f.select :organized_by, options_for_select(Group.has_members.order(:category, :name).map{ |group| [group.name, group.id] }, @activity.organized_by), :include_blank => '-' - .form-group .row .col-md-12 = f.label :notes - + .row + .col-md-12#notes-container + - if @activity.notes_input_type == 'text' + = f.text_area :notes, value: @activity.notes, class: 'form-control', id: 'activity-notes' + - elsif @activity.notes_input_type == 'checkboxes' || @activity.notes_input_type == 'radio_buttons' + .row#notes-options + - if @activity.notes.present? + - notes_lines = @activity.notes.split("\n") + - if notes_lines.length > 1 + - title = notes_lines.first + .col-md-12 + .input-group.mb-3 + %span.input-group-text Title + = text_field_tag "activity[notes_options][]", title, class: 'form-control' + - notes_lines.drop(1).each do |option| + .col-md-6 + .input-group.mb-3 + = text_field_tag "activity[notes_options][]", option, class: 'form-control' + .input-group-append + %button.btn.btn-sm.btn-danger.delete-option{ type: 'button' } + %i.fa.fa-fw.fa-minus + - else + .col-md-12 + .input-group.mb-3 + %span.input-group-text Title + = text_field_tag "activity[notes_options][]", @activity.notes, class: 'form-control' + - else + .col-md-12 + .input-group.mb-3 + %span.input-group-text Title + = text_field_tag "activity[notes_options][]", '', class: 'form-control' + .col-md-6 + .input-group.mb-3 + = text_field_tag "activity[notes_options][]", '', class: 'form-control' + .input-group-append + %button.btn.btn-sm.btn-danger.delete-option{ type: 'button' } + %i.fa.fa-fw.fa-minus + .row + .col-md-12 + %button.btn.btn-primary#add-notes-option{ type: 'button' } + %i.fa.fa-fw.fa-plus + Add Option .row .col-md-12 - = f.text_field :notes, :value => @activity.notes, :class => 'form-control', id: "activity-notes" - + = f.label :notes_input_type + = f.select :notes_input_type, Activity.notes_input_types.keys.map { |type| [type.humanize, type] }, {}, class: 'form-control' .row .col-sm-6 = f.check_box :notes_public, checked: @activity.notes_public diff --git a/app/views/members/activities/partials/_view.html.haml b/app/views/members/activities/partials/_view.html.haml index 509b8fbe6..633ec9ceb 100644 --- a/app/views/members/activities/partials/_view.html.haml +++ b/app/views/members/activities/partials/_view.html.haml @@ -1,14 +1,13 @@ - -# @param {activity} activity @param {?} @enrollment @param {member} @member -.card.panel-activity{ data: {:'activity-id' => activity.id, :'notes-mandatory' => activity.notes_mandatory, :'unenroll-date' => activity.unenroll_date.at_end_of_day.to_i }} +.card.panel-activity{ data: { :'activity-id' => activity.id, :'notes-mandatory' => activity.notes_mandatory, :'unenroll-date' => activity.unenroll_date.at_end_of_day.to_i }} .card-header.text-right - %span.card-title.float-left.activity-title{ :style => 'text-overflow: ellipsis;'} + %span.card-title.float-left.activity-title{ style: 'text-overflow: ellipsis;' } - if view == 'show' - = link_to ("/activities/") do + = link_to("/activities") do %i.fa.fa-chevron-left = activity.name - else @@ -20,101 +19,115 @@ - if activity.poster.attached? .media-left.poster-thumbnail.col-12.col-sm-6 %a.show-poster-modal{'data-toggle': 'modal', 'data-target': '#poster-modal'} - = image_tag activity.thumbnail_representation, class: "small-poster" + = image_tag(activity.thumbnail_representation, class: "small-poster") .mask.d-flex.justify-content-center - %span.align-self-center{'style': 'color: White'} + %span.align-self-center{ style: 'color: White' } %i.fas.fa-search.fa-3x .media-body.col-12.col-sm-6 - if activity.start_date != activity.end_date %strong= I18n.t("activerecord.labels.activities.when") = l(activity.start_date, format: "%A %d/%m/%Y") - - if !activity.start_time.nil? + - if activity.start_time.present? = activity.start_time.strftime("%H:%M") \- = l(activity.end_date, format: "%A %d/%m/%Y") - - if !activity.end_time.nil? + - if activity.end_time.present? = activity.end_time.strftime("%H:%M") - else %strong= I18n.t("activerecord.labels.activities.date") = l(activity.start_date, format: "%A %d/%m/%Y") - - if !activity.start_time.nil? -
+ - if activity.start_time.present? + %br/ %strong= I18n.t("activerecord.labels.activities.time") = activity.start_time.strftime("%H:%M") - - if !activity.end_time.nil? + - if activity.end_time.present? \- = activity.end_time.strftime("%H:%M") -
+ %br/ %strong= I18n.t("activerecord.attributes.activity.price") - - if !activity.price.nil? and activity.price != 0 + - if activity.price.present? && activity.price != 0 € - = number_with_precision(activity.price, :precision => 2) -
+ = number_with_precision(activity.price, precision: 2) + %br/ - else = I18n.t("activerecord.missing_value_placeholders.activity.free") -
- - if !activity.location.nil? && activity.location != '' + %br/ + - if activity.location.present? %strong= I18n.t("activerecord.attributes.activity.location") = activity.location -
- - if !activity.unenroll_date.nil? && activity.open? + %br/ + - if activity.unenroll_date.present? && activity.open? %strong= I18n.t("activerecord.attributes.activity.unenroll_date") %span.activity-unenroll = l(activity.unenroll_date, format: "%A %d/%m/%Y") 23:59 -
- - if !activity.google_event().nil? - %a{href: activity.google_event(), target: "_blank"} + %br/ + - if activity.google_event.present? + %a{ href: activity.google_event, target: "_blank" } = I18n.t("activerecord.attributes.activity.google_event") -
+ %br/ - if view == 'index' - if activity.notes_mandatory && activity.notes.present? - %span{style: 'color: red; font-weight: bold; display:block; padding-top: 14px'}= I18n.t("members.activities.info.notes_mandatory") - - if view =='show' + %span{ style: 'color: red; font-weight: bold; display:block; padding-top: 14px' }= I18n.t("members.activities.info.notes_mandatory") + - if view == 'show' .card-body.show-activity-topborder %strong= I18n.t("activerecord.attributes.activity.description") -
- - if @user.language == 'nl' && !activity.description_nl.blank? + %br/ + - if @user.language == 'nl' && activity.description_nl.present? = simple_format(activity.description_nl) - - elsif @user.language == 'en' && !activity.description_en.blank? + - elsif @user.language == 'en' && activity.description_en.present? = simple_format(activity.description_en) - - elsif !activity.description_nl.blank? + - elsif activity.description_nl.present? = simple_format(activity.description_nl) - - elsif !activity.description_en.blank? + - elsif activity.description_en.present? = simple_format(activity.description_en) - else = I18n.t("activerecord.missing_value_placeholders.activity.description") - - if !activity.notes.blank? + - if activity.notes.present? .card-body.show-activity-topborder %strong - = activity.notes - %textarea.form-control.notes{:maxlength => '100', :value => (@enrollment.notes unless @enrollment.nil?)} - = @enrollment.notes unless @enrollment.nil? + = activity.notes.split("\n").first + - if activity.checkboxes? + .form-group + - activity.notes.split("\n").drop(1).each do |option| + .row + .col-sm-6 + %input.notes{ type: "checkbox", name: "enrollment[notes][]", value: option, checked: !@enrollment.nil? && @enrollment.notes&.include?(option), disabled: !activity.open? } + %label= option + - elsif activity.radio_buttons? + .form-group + - activity.notes.split("\n").drop(1).each do |option| + .row + .col-sm-6 + %input.notes{ type: "radio", name: "enrollment[notes]", value: option, checked: !@enrollment.nil? && @enrollment.notes == option, disabled: !activity.open?} + %label= option + - else + %textarea.form-control.notes{ maxlength: '100', disabled: !activity.open? } + = @enrollment.nil? ? activity.notes : @enrollment.notes .card-footer.clearfix .row - if view == 'index' - - if !activity.notes.blank? + - if activity.notes.present? = link_to raw("#{I18n.t('members.activities.info.more_info')}   "), "/activities/#{ activity.id }", class: "btn btn-secondary more-info col-12 col-sm-6" - else = link_to I18n.t('members.activities.info.more_info'), "/activities/#{ activity.id }", class: "btn btn-secondary more-info col-12 col-sm-6" - - if activity.notes.present? && view == 'show' - %button.btn.btn-info.update-enrollment.col-9.col-sm-6{ :class => ('d-none' unless (@member.confirmed_activities.ids.include? activity.id) || (@member.reservist_activities.ids.include? activity.id))} - = I18n.t("members.activities.actions.update_info") + %button.btn.btn-info.update-enrollment.col-12.col-sm-6{ class: ('d-none' unless (@member.confirmed_activities.ids.include?(activity.id) || @member.reservist_activities.ids.include?(activity.id))) } + = I18n.t("members.activities.actions.update_info") # confirmed sign-in - if activity.open? - - if @member.confirmed_activities.ids.include? activity.id # confirmed sign-in + - if @member.confirmed_activities.ids.include?(activity.id) - button_text = I18n.t("members.activities.actions.unenroll") - button_class = 'btn-danger col-12 col-sm-6' - - if !activity.unenroll_date.nil? && activity.unenroll_date < Date.today # confirmed sign-in -- past sign-out deadline + - if activity.unenroll_date.present? && activity.unenroll_date < Date.today # confirmed sign-in -- past unenroll date - button_class += ' disabled' - - elsif @member.reservist_activities.ids.include? activity.id # signed in as reservist (time is before sign-out deadline so can sign out) - - button_text = I18n.t("members.activities.actions.reservist_unenroll") # there is no case where you can sign up as reservist after sign-out deadline, as of writing + - elsif @member.reservist_activities.ids.include?(activity.id)# signed in as reservist (time is before sign-out deadline so can sign out) + - button_text = I18n.t("members.activities.actions.reservist_unenroll") # there is no case where you can sign up as reservist after sign-out deadline, as of writing - button_class = 'btn-reservistSignout col-12 col-sm-6' - - elsif activity.participant_limit != nil && activity.participant_limit <= activity.participants.count # not signed in -- but can do so as reservist only + - elsif activity.participant_limit.present? && activity.participant_limit <= activity.participants.count # not signed in -- but can do so as reservist only - button_text = I18n.t("members.activities.actions.reservist_enroll") - button_class = 'btn-reservistSignup col-12 col-sm-6' - - else # not signed in -- and but can do so, not as reservist + - else # not signed in -- and but can do so, not as reservist - button_text = I18n.t("members.activities.actions.enroll") - button_class = 'btn-success col-12 col-sm-6' - %button.btn.enrollment{ :class => button_class} + %button.btn.enrollment{ class: button_class } = button_text diff --git a/db/migrate/20240530151252_add_notes_input_type_to_activities.rb b/db/migrate/20240530151252_add_notes_input_type_to_activities.rb new file mode 100644 index 000000000..26a44076b --- /dev/null +++ b/db/migrate/20240530151252_add_notes_input_type_to_activities.rb @@ -0,0 +1,5 @@ +class AddNotesInputTypeToActivities < ActiveRecord::Migration[6.1] + def change + add_column :activities, :notes_input_type, :integer, default: 0 + end +end diff --git a/db/structure.sql b/db/structure.sql index 075e5bfba..5e78ffd55 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -163,7 +163,8 @@ CREATE TABLE public.activities ( open_time time without time zone, is_sophomores boolean, is_seniors boolean, - payable_updated_at date + payable_updated_at date, + notes_input_type integer DEFAULT 0 ); @@ -1736,6 +1737,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20220221195220'), ('20220406092056'), ('20220524203723'), -('20240125003700'); +('20240125003700'), +('20240530151252');