diff --git a/app/controllers/community_news_controller.rb b/app/controllers/community_news_controller.rb index e9ea567b0..3b0a922a4 100644 --- a/app/controllers/community_news_controller.rb +++ b/app/controllers/community_news_controller.rb @@ -117,7 +117,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @sectors = Sector.published.order(:name) @community_news.build_primary_asset if @community_news.primary_asset.blank? diff --git a/app/controllers/events_controller.rb b/app/controllers/events_controller.rb index f46054aaf..2ac026004 100644 --- a/app/controllers/events_controller.rb +++ b/app/controllers/events_controller.rb @@ -107,7 +107,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @sectors = Sector.published.order(:name) end diff --git a/app/controllers/resources_controller.rb b/app/controllers/resources_controller.rb index 8c9da4b8f..2b42dfce5 100644 --- a/app/controllers/resources_controller.rb +++ b/app/controllers/resources_controller.rb @@ -159,7 +159,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @sectors = Sector.published.order(:name) end diff --git a/app/controllers/stories_controller.rb b/app/controllers/stories_controller.rb index 0cdaa2dd6..4b915ff98 100644 --- a/app/controllers/stories_controller.rb +++ b/app/controllers/stories_controller.rb @@ -57,7 +57,7 @@ def edit end def create - @story = Story.new(story_params) + @story = Story.new(story_params.except(:category_ids, :sector_ids)) authorize! @story success = false @@ -91,8 +91,11 @@ def update success = false Story.transaction do - if @story.update(story_params.except(:images)) + if @story.update(story_params.except(:images, :category_ids, :sector_ids)) assign_associations(@story) + if params[:promote_idea_assets] == "true" + @story.attach_assets_from_idea! + end success = true end rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e @@ -135,8 +138,17 @@ def set_form_variables .order(:position, :name) .group_by(&:category_type) .select { |type, _| type.nil? || type.published? } - .sort_by { |type, _| type&.name.to_s.downcase } + .sort_by { |type, _| [ type&.story_specific? ? 0 : 1, type&.name.to_s.downcase ] } @sectors = Sector.published.order(:name) + submitted_sector_ids = Array(params.dig(:story, :sector_ids)).reject(&:blank?) + submitted_category_ids = Array(params.dig(:story, :category_ids)).reject(&:blank?) + if submitted_sector_ids.any? || submitted_category_ids.any? + @preselected_sector_ids = submitted_sector_ids.map(&:to_i) + @preselected_category_ids = submitted_category_ids.map(&:to_i) + elsif @story_idea + @preselected_sector_ids = @story_idea.sector_ids + @preselected_category_ids = @story_idea.category_ids + end @story.build_primary_asset if @story.primary_asset.blank? @story.gallery_assets.build end @@ -166,12 +178,13 @@ def story_params category_ids: [], sector_ids: [], primary_asset_attributes: [ :id, :file, :_destroy ], - gallery_assets_attributes: [ :id, :file, :_destroy ] + gallery_assets_attributes: [ :id, :file, :_destroy ], ) end def set_story_attributes_from(idea) { + story_idea_id: idea.id, rhino_body: idea.body, organization_id: idea.organization.id, workshop_id: idea.workshop_id, diff --git a/app/controllers/story_ideas_controller.rb b/app/controllers/story_ideas_controller.rb index 3e61c49e1..a99572a7c 100644 --- a/app/controllers/story_ideas_controller.rb +++ b/app/controllers/story_ideas_controller.rb @@ -31,19 +31,30 @@ def edit end def create - @story_idea = StoryIdea.new(story_idea_params) + @story_idea = StoryIdea.new(story_idea_params.except(:category_ids, :sector_ids)) @story_idea.created_by = current_user @story_idea.updated_by = current_user authorize! @story_idea - if @story_idea.save - NotificationServices::CreateNotification.call( - noticeable: @story_idea, - kind: :idea_submitted_fyi, - recipient_role: :admin, - recipient_email: ENV.fetch("REPLY_TO_EMAIL", "programs@awbw.org"), - notification_type: 0) + success = false + + StoryIdea.transaction do + if @story_idea.save + assign_associations(@story_idea) + NotificationServices::CreateNotification.call( + noticeable: @story_idea, + kind: :idea_submitted_fyi, + recipient_role: :admin, + recipient_email: ENV.fetch("REPLY_TO_EMAIL", "programs@awbw.org"), + notification_type: 0) + success = true + end + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e + Rails.logger.error "StoryIdea create failed: #{e.class} - #{e.message}" + raise ActiveRecord::Rollback + end + if success flash[:notice] = "StoryIdea was successfully created." if allowed_to?(:index?, StoryIdea) redirect_to story_ideas_path @@ -60,7 +71,19 @@ def update @story_idea.updated_by = current_user authorize! @story_idea - if @story_idea.update(story_idea_params.except(:images)) + success = false + + StoryIdea.transaction do + if @story_idea.update(story_idea_params.except(:images, :category_ids, :sector_ids)) + assign_associations(@story_idea) + success = true + end + rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e + Rails.logger.error "StoryIdea update failed: #{e.class} - #{e.message}" + raise ActiveRecord::Rollback + end + + if success flash[:notice] = "StoryIdea was successfully updated." if allowed_to?(:index?, StoryIdea) redirect_to story_ideas_path, status: :see_other @@ -89,13 +112,43 @@ def set_form_variables @workshops = authorized_scope(Workshop.all).includes(:windows_type).order(:title) - @users = authorized_scope(User.has_access.includes(:person)) - @users = @users.or(User.where(id: @story_idea.created_by_id)) if @story_idea&.created_by_id - @users = @users.includes(:person).distinct.order("people.first_name, people.last_name") + users = authorized_scope(User.has_access.includes(:person)) + users = users.or(User.where(id: @story_idea.created_by_id)) if @story_idea&.created_by_id + @users = users.distinct.order("people.first_name, people.last_name") + + @story_population_type = CategoryType.find_by(name: "StoryPopulation") + @story_population_categories = @story_population_type&.categories&.published&.order(:name) || [] + @sectors = Sector.published.order(:name) + submitted_sector_ids = Array(params.dig(:story_idea, :sector_ids)).reject(&:blank?) + submitted_category_ids = Array(params.dig(:story_idea, :category_ids)).reject(&:blank?) + if submitted_sector_ids.any? || submitted_category_ids.any? + @preselected_sector_ids = submitted_sector_ids.map(&:to_i) + @preselected_category_ids = submitted_category_ids.map(&:to_i) + end + + if @story_idea.persisted? + @categories_grouped = + Category + .includes(:category_type) + .published + .order(:position, :name) + .group_by(&:category_type) + .select { |type, _| type.nil? || type.published? } + .sort_by { |type, _| [ type&.story_specific? ? 0 : 1, type&.name.to_s.downcase ] } + end @story_idea.build_primary_asset if @story_idea.primary_asset.blank? @story_idea.gallery_assets.build end + def assign_associations(story_idea) + selected_category_ids = Array(params[:story_idea][:category_ids]).reject(&:blank?).map(&:to_i) + story_idea.categories = Category.where(id: selected_category_ids) + + selected_sector_ids = Array(params[:story_idea][:sector_ids]).reject(&:blank?).map(&:to_i) + story_idea.sectors = Sector.where(id: selected_sector_ids) + story_idea.save! + end + private def set_story_idea @@ -107,6 +160,9 @@ def story_idea_params :title, :body, :youtube_url, :permission_given, :publish_preferences, :promoted_to_story, :windows_type_id, :organization_id, :workshop_id, :external_workshop_title, + :created_by_id, :updated_by_id, + category_ids: [], + sector_ids: [], primary_asset_attributes: [ :id, :file, :_destroy ], gallery_assets_attributes: [ :id, :file, :_destroy ] ) diff --git a/app/controllers/workshop_ideas_controller.rb b/app/controllers/workshop_ideas_controller.rb index 6d4435c86..86f8de0ae 100644 --- a/app/controllers/workshop_ideas_controller.rb +++ b/app/controllers/workshop_ideas_controller.rb @@ -79,7 +79,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @workshop_idea.build_primary_asset if @workshop_idea.primary_asset.blank? @workshop_idea.gallery_assets.build diff --git a/app/controllers/workshops_controller.rb b/app/controllers/workshops_controller.rb index 01fbcf61a..25efd5af9 100644 --- a/app/controllers/workshops_controller.rb +++ b/app/controllers/workshops_controller.rb @@ -201,7 +201,7 @@ def set_form_variables .published .order(:position, :name) .group_by(&:category_type) - .select { |type, _| type.nil? || type.published? } + .select { |type, _| type.nil? || (type.published? && !type.story_specific?) } .sort_by { |type, _| type&.name.to_s.downcase } @sectors = Sector.published.order(:name) diff --git a/app/helpers/person_helper.rb b/app/helpers/person_helper.rb index 189ad27be..6d997f199 100644 --- a/app/helpers/person_helper.rb +++ b/app/helpers/person_helper.rb @@ -1,5 +1,5 @@ module PersonHelper - def person_profile_button(person, truncate_at: nil, subtitle: nil) + def person_profile_button(person, truncate_at: nil, subtitle: nil, display_name: nil) bg = DomainTheme.bg_class_for(:people, intensity: 100) hover_bg = DomainTheme.bg_class_for(:people, intensity: 100, hover: true) text = DomainTheme.text_class_for(:people) @@ -28,7 +28,8 @@ def person_profile_button(person, truncate_at: nil, subtitle: nil) border border-sky-300 shadow-sm flex-shrink-0") end - display_name = truncate_at ? truncate(person.name.to_s, length: truncate_at) : person.name.to_s + display_name = display_name || person.name.to_s + display_name = truncate(display_name, length: truncate_at) if truncate_at name = content_tag( :span, diff --git a/app/helpers/title_display_helper.rb b/app/helpers/title_display_helper.rb index 21249f4db..a454844db 100644 --- a/app/helpers/title_display_helper.rb +++ b/app/helpers/title_display_helper.rb @@ -23,6 +23,16 @@ def title_with_badges(record, font_size: "text-lg", record_title: nil, ) end + # --- Promoted from story idea badge --- + if record.respond_to?(:story_idea) && record.story_idea.present? + fragments << content_tag( + :span, + content_tag(:i, "", class: "fa-solid fa-arrow-up-from-bracket mr-1") + " Promoted", + class: "inline-flex items-center px-2 py-0.5 rounded-full + text-sm font-medium bg-green-100 text-green-800 whitespace-nowrap" + ) + end + title_content = record_title || record.title.to_s if display_windows_type && record.respond_to?(:windows_type) && record.windows_type.present? diff --git a/app/models/category.rb b/app/models/category.rb index 811f620e5..80457c213 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -10,6 +10,7 @@ class Category < ApplicationRecord # Scopes # See NameFilterable, Publishable scope :age_ranges, -> { joins(:category_type).where(category_types: { name: "AgeRange" }) } + scope :story_categories, -> { joins(:category_type).where(category_types: { name: "StoryCategory" }) } scope :ordered_by_position_and_name, -> { reorder(position: :asc, name: :asc) } # Validations diff --git a/app/models/category_type.rb b/app/models/category_type.rb index 4a8abd1c9..0952f338d 100644 --- a/app/models/category_type.rb +++ b/app/models/category_type.rb @@ -9,4 +9,10 @@ class CategoryType < ApplicationRecord # Scopes # See Publishable + scope :general, -> { where(story_specific: false) } + scope :story_specific, -> { where(story_specific: true) } + + def display_label + display_text.presence || name.titleize + end end diff --git a/app/models/sector.rb b/app/models/sector.rb index 968042fee..e1323d903 100644 --- a/app/models/sector.rb +++ b/app/models/sector.rb @@ -24,6 +24,8 @@ class Sector < ApplicationRecord "Other" ] + STORY_DISPLAY_TEXT = "Which sectors apply?" + has_many :sectorable_items, dependent: :destroy has_many :workshops, through: :sectorable_items, source: :sectorable, source_type: "Workshop" diff --git a/app/models/story.rb b/app/models/story.rb index 2b20225ee..5dffdf2c8 100644 --- a/app/models/story.rb +++ b/app/models/story.rb @@ -93,6 +93,7 @@ def sector_names_all def attach_assets_from_idea! return unless story_idea + assets.destroy_all story_idea.assets.find_each do |asset| new_asset = assets.build(type: asset.type) new_asset.file.attach(asset.file.blob) diff --git a/app/models/story_idea.rb b/app/models/story_idea.rb index 4b57f7347..c0124a2be 100644 --- a/app/models/story_idea.rb +++ b/app/models/story_idea.rb @@ -22,6 +22,8 @@ def self.search_by_params(params) belongs_to :windows_type belongs_to :workshop, optional: true has_many :bookmarks, as: :bookmarkable, dependent: :destroy + has_many :categorizable_items, dependent: :destroy, inverse_of: :categorizable, as: :categorizable + has_many :sectorable_items, dependent: :destroy, inverse_of: :sectorable, as: :sectorable has_many :notifications, as: :noticeable, dependent: :destroy has_many :stories # Asset associations @@ -30,6 +32,9 @@ def self.search_by_params(params) has_many :gallery_assets, -> { where(type: "GalleryAsset") }, as: :owner, class_name: "GalleryAsset", dependent: :destroy has_many :assets, as: :owner, dependent: :destroy + # has_many through + has_many :categories, through: :categorizable_items + has_many :sectors, through: :sectorable_items # Validations validates :created_by_id, presence: true diff --git a/app/models/user.rb b/app/models/user.rb index adc9f1f6e..d1fd26642 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -115,6 +115,10 @@ def full_name end end + def full_name_with_email + "#{full_name} (#{email})" + end + def devise_email_name person&.first_name.presence || first_name.presence || email end diff --git a/app/views/stories/_form.html.erb b/app/views/stories/_form.html.erb index fbb6424d5..a74bb577f 100644 --- a/app/views/stories/_form.html.erb +++ b/app/views/stories/_form.html.erb @@ -2,6 +2,18 @@ <%= render 'shared/errors', resource: @story if @story.errors.any? %> <% story_idea = @story_idea || f.object.story_idea %> + <% selected_sector_ids = @preselected_sector_ids || @story.sector_ids %> + <% selected_category_ids = @preselected_category_ids || @story.category_ids %> + + <% if story_idea %> +
+ + This will replace any existing attachments on this story once you click submit. +
+