diff --git a/app/assets/stylesheets/tpi/_pages.scss b/app/assets/stylesheets/tpi/_pages.scss index e55a2fe0b..3678e66f7 100644 --- a/app/assets/stylesheets/tpi/_pages.scss +++ b/app/assets/stylesheets/tpi/_pages.scss @@ -174,6 +174,12 @@ $margin-between-list-items: 20px; flex-direction: column; } } + + &__flex { + display: flex; + gap: 20px; + flex-wrap: wrap; + } } .pages__content-title { diff --git a/app/controllers/tpi/publications_controller.rb b/app/controllers/tpi/publications_controller.rb index 102a510ee..a50b00248 100644 --- a/app/controllers/tpi/publications_controller.rb +++ b/app/controllers/tpi/publications_controller.rb @@ -7,6 +7,7 @@ class PublicationsController < TPIController before_action :fetch_tags, only: [:index] before_action :fetch_sectors, only: [:index] before_action :fetch_publication, only: [:show] + before_action :fetch_news_article, only: [:show_news_article] def index results = Queries::TPI::NewsPublicationsQuery.new(filter_params).call @@ -36,14 +37,17 @@ def show respond_to do |format| format.html do admin_panel_path = polymorphic_path([:admin, @publication]) - fixed_navbar("#{@publication.class.name.underscore.humanize} #{@publication.title}", admin_panel_path) - - redirect_to '' unless @publication + fixed_navbar(@publication.title.to_s, admin_panel_path) end format.pdf { stream_publication_file } end end + def show_news_article + admin_panel_path = polymorphic_path([:admin, @news_article]) + fixed_navbar((@news_article[:title]).to_s, admin_panel_path) + end + def download_file @publication = Publication.published.find_by! slug: params[:slug] stream_publication_file @@ -70,15 +74,15 @@ def offset end def filter_params - params.permit(:tags, :sectors) + params.permit(:tags, :sectors, :types) end def fetch_publication - @publication = if params[:type].eql?('NewsArticle') - NewsArticle.published.find(params[:id]) - else - Publication.published.find_by(id: params[:id]) || Publication.published.find_by!(slug: params[:id]) - end + @publication = Publication.published.find_by(id: params[:id]) || Publication.published.find_by!(slug: params[:id]) + end + + def fetch_news_article + @news_article = NewsArticle.published.find params[:id] end def fetch_tags diff --git a/app/javascript/components/tpi/Filters.js b/app/javascript/components/tpi/Filters.js index 991a52050..a8922c03e 100644 --- a/app/javascript/components/tpi/Filters.js +++ b/app/javascript/components/tpi/Filters.js @@ -9,20 +9,25 @@ import { useQueryParam } from 'shared/hooks'; const ALL_OPTION_NAME = 'All'; const SHOW_ON_PAGE = 9; -const Filters = ({ tags, sectors, resultsSize }) => { +const Filters = ({ types, tags, sectors, resultsSize }) => { const [isFilterOpen, setIsFiltersOpen] = useState(false); const [resultsCount, setResultsCount] = useState(resultsSize); + const [queryTypesParam, setQueryTypes] = useQueryParam('types'); const [queryTagsParam, setQueryTags] = useQueryParam('tags'); const [querySectorsParam, setQuerySectors] = useQueryParam('sectors'); const [offset, setOffset] = useState(0); + const activeTypes = useMemo(() => { + const typesWithAllOption = [ALL_OPTION_NAME, ...types]; + const queryTypes = (queryTypesParam || '').split(',').filter(x => x); + return typesWithAllOption.map(type => ({ + name: type, + active: queryTypes.length > 0 ? queryTypes.includes(type) : type === ALL_OPTION_NAME + })); + }, [types, queryTypesParam]); const activeTags = useMemo(() => { const queryTags = (queryTagsParam || '').split(',').filter(x => x); - const tagsWithAllOption = [ - ALL_OPTION_NAME, - ...queryTags.filter(x => !tags.includes(x)), - ...tags - ]; + const tagsWithAllOption = [ALL_OPTION_NAME, ...tags]; return tagsWithAllOption.map(tag => ({ name: tag, active: queryTags.length > 0 ? queryTags.includes(tag) : tag === ALL_OPTION_NAME @@ -71,7 +76,11 @@ const Filters = ({ tags, sectors, resultsSize }) => { }; }, [handleLoadMore]); - const refreshPublicationsHtml = (_tags, _sectors, _offset) => { + const refreshPublicationsHtml = (_types, _tags, _sectors, _offset) => { + const activeTypesQueryParam = _types + .filter(t => t.active && t.name !== ALL_OPTION_NAME) + .map(t => encodeURIComponent(t.name)) + .join(', '); const activeTagsQueryParam = _tags .filter(t => t.active && t.name !== ALL_OPTION_NAME) .map(t => encodeURIComponent(t.name)) @@ -81,7 +90,7 @@ const Filters = ({ tags, sectors, resultsSize }) => { .map(s => encodeURIComponent(s.name)) .join(', '); - const query = `tags=${activeTagsQueryParam}§ors=${activeSectorsQueryParam}&offset=${_offset}`; + const query = `types=${activeTypesQueryParam}&tags=${activeTagsQueryParam}§ors=${activeSectorsQueryParam}&offset=${_offset}`; const url = `/publications/partial?${query}`; fetch(url) @@ -98,8 +107,31 @@ const Filters = ({ tags, sectors, resultsSize }) => { }; useEffect(() => { - refreshPublicationsHtml(activeTags, activeSectors, offset); - }, [activeTags, activeSectors, offset]); + refreshPublicationsHtml(activeTypes, activeTags, activeSectors, offset); + }, [activeTypes, activeTags, activeSectors, offset]); + + const handleTypeClick = (type) => { + const otherOptions = optionsWithoutALL(activeTypes); + + const shouldALLbeSelected = isAllClicked(type) && (isOtherOptionsActive(otherOptions) || isAllSelected(otherOptions)); + + if (shouldALLbeSelected) { + const typesWithALLSelected = activeTypes.map(s => ({ + name: s.name, + active: s.name === ALL_OPTION_NAME + })); + setOffset(0); + setQueryTypes(typesWithALLSelected.filter(s => s.active).map((s) => s.name).join(',')); + } else { + const updatedTypes = activeTypes.map(s => { + if (s.name === type.name) { return { name: s.name, active: !s.active }; } + if (s.name === ALL_OPTION_NAME) { return { name: s.name, active: false }; } + return { name: s.name, active: s.active }; + }); + setOffset(0); + setQueryTypes(updatedTypes.filter(s => s.active).map((s) => s.name).join(',')); + } + }; const handleTagClick = (tag) => { const otherOptions = optionsWithoutALL(activeTags); @@ -172,6 +204,18 @@ const Filters = ({ tags, sectors, resultsSize }) => { {isFilterOpen && (
+
Type
+
+ {activeTypes.length && activeTypes.map(type => ( + + ))} +
Tag
{activeTags.length && activeTags.map(tag => ( @@ -204,6 +248,7 @@ const Filters = ({ tags, sectors, resultsSize }) => { }; Filters.propTypes = { + types: PropTypes.array.isRequired, tags: PropTypes.array.isRequired, sectors: PropTypes.array.isRequired, resultsSize: PropTypes.number.isRequired diff --git a/app/services/queries/tpi/news_publications_query.rb b/app/services/queries/tpi/news_publications_query.rb index a90dd26e4..69bb0d941 100644 --- a/app/services/queries/tpi/news_publications_query.rb +++ b/app/services/queries/tpi/news_publications_query.rb @@ -4,7 +4,7 @@ class NewsPublicationsQuery include ActiveModel::Model attr_reader :publications_scope, :news_scope - attr_accessor :tags, :sectors + attr_accessor :tags, :sectors, :types def call (publications + news).uniq.sort_by(&:publication_date).reverse! @@ -13,10 +13,14 @@ def call private def publications + return Publication.none if types.present? && !types.include?('Publications') + filter_scope(Publication.published) end def news + return NewsArticle.none if types.present? && !types.include?('News') + filter_scope(NewsArticle.published) end diff --git a/app/views/tpi/publications/_list.html.erb b/app/views/tpi/publications/_list.html.erb index 9628166f1..4bf5de83f 100644 --- a/app/views/tpi/publications/_list.html.erb +++ b/app/views/tpi/publications/_list.html.erb @@ -8,9 +8,9 @@
<% if publication.is_a?(Publication) %> - <%= link_to publication.title, tpi_publication_download_file_path(slug: publication.slug), target: '_blank', class: 'link is-strong' %> + <%= link_to publication.title, tpi_publication_path(id: publication.slug), class: 'link is-strong' %> <% else %> - <%= link_to publication.title, tpi_publication_path(id: publication.id, type: publication.class.name), class: "link is-strong" %> + <%= link_to publication.title, show_news_article_tpi_publication_path(id: publication.id), class: "link is-strong" %> <% end %>
diff --git a/app/views/tpi/publications/index.html.erb b/app/views/tpi/publications/index.html.erb index 52c2257da..5d6fc2489 100644 --- a/app/views/tpi/publications/index.html.erb +++ b/app/views/tpi/publications/index.html.erb @@ -7,7 +7,12 @@ filter icon
- <%= react_component("Filters", { tags: @tags, sectors: @sectors, resultsSize: @publications_and_articles_count }) %> + <%= react_component("Filters", { + types: %w[Publications News], + tags: @tags, + sectors: @sectors, + resultsSize: @publications_and_articles_count + }) %>
<%= render 'promoted', publications_and_articles: @publications_and_articles, count: @publications_and_articles_count %>
diff --git a/app/views/tpi/publications/show.html.erb b/app/views/tpi/publications/show.html.erb index e6c4ea216..d21e300c2 100644 --- a/app/views/tpi/publications/show.html.erb +++ b/app/views/tpi/publications/show.html.erb @@ -1,24 +1,28 @@ <% content_for :page_title, "#{@publication.title} - Transition Pathway Initiative" %> -<% if @publication.class.name.eql?("NewsArticle") %> - <% content_for :page_description, strip_tags(@publication.content).first(160) if @publication.content.present? %> -<% else %> - <% content_for :page_description, @publication.short_description&.first(160) %> -<% end %> +<% content_for :page_description, @publication.short_description&.first(160) %>

<%= @publication.title %>

<%= @publication.publication_date.strftime('%d/%m/%Y') %>

- <% if @publication.class.name.eql?("NewsArticle") && @publication.image.present? %> -
<%= image_tag(@publication.image) %>
- <% end %> -
- <% if @publication.class.name.eql?("NewsArticle") %> - <%= @publication.content&.html_safe %> - <% else %> - <%= @publication.short_description %> + +
+ <% if @publication.image.present? %> +
<%= image_tag(@publication.image) %>
<% end %> +
+

<%= @publication.short_description %>

+ <% if @publication.keywords.any? || @publication.tpi_sectors.any? %> +
+ <% @publication.tags_and_sectors.each do |tag| %> + <%= tag %> + <% end %> +
+ <% end %> + + <%= link_to 'Download file', tpi_publication_download_file_path(slug: @publication.slug), target: '_blank', class: 'button is-primary' %> +
diff --git a/app/views/tpi/publications/show_news_article.erb b/app/views/tpi/publications/show_news_article.erb new file mode 100644 index 000000000..96496f89e --- /dev/null +++ b/app/views/tpi/publications/show_news_article.erb @@ -0,0 +1,17 @@ +<% content_for :page_title, "#{@news_article.title} - Transition Pathway Initiative" %> +<% content_for :page_description, strip_tags(@news_article.content).first(160) if @news_article.content.present? %> + +
+
+
+

<%= @news_article.title %>

+

<%= @news_article.publication_date.strftime('%d/%m/%Y') %>

+ <% if @news_article.image.present? %> +
<%= image_tag(@news_article.image) %>
+ <% end %> +
+ <%= @news_article.content&.html_safe %> +
+
+
+
diff --git a/app/views/tpi/sitemaps/index.xml.erb b/app/views/tpi/sitemaps/index.xml.erb index ce63b3e21..a1bbcea04 100644 --- a/app/views/tpi/sitemaps/index.xml.erb +++ b/app/views/tpi/sitemaps/index.xml.erb @@ -40,12 +40,12 @@ <% @publications.each do |publication| %> - <%= "#{@host}#{tpi_publication_download_file_path(slug: publication.slug)}" %> + <%= "#{@host}#{tpi_publication_path(id: publication.slug)}" %> <% end %> <% @news.each do |news| %> - <%= "#{@host}#{tpi_publication_path(news, type: 'NewsArticle')}" %> + <%= "#{@host}#{show_news_article_tpi_publication_path(id: news.id)}" %> <% end %> diff --git a/config/routes.rb b/config/routes.rb index 78e0acd69..0eec8873d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -55,6 +55,9 @@ end resources :publications, only: [:index, :show] do + member do + get :show_news_article + end collection do get :partial end diff --git a/spec/controllers/tpi/publications_controller_spec.rb b/spec/controllers/tpi/publications_controller_spec.rb index 3a8291dd4..80f5f7926 100644 --- a/spec/controllers/tpi/publications_controller_spec.rb +++ b/spec/controllers/tpi/publications_controller_spec.rb @@ -29,19 +29,19 @@ describe 'GET show' do context 'publication' do context 'published' do - subject { get :show, params: {id: publication1.id, type: 'Publication'} } + subject { get :show, params: {id: publication1.id} } it { is_expected.to be_successful } context 'when publication is searched by slug' do - subject { get :show, params: {id: publication1.slug, type: 'Publication'} } + subject { get :show, params: {id: publication1.slug} } it { is_expected.to be_successful } end end context 'unpublished' do - subject { get :show, params: {id: publication4.id, type: 'Publication'} } + subject { get :show, params: {id: publication4.slug} } it 'not found' do expect { subject }.to raise_exception(ActiveRecord::RecordNotFound) @@ -51,13 +51,13 @@ context 'news article' do context 'published' do - subject { get :show, params: {id: news_article1.id, type: 'NewsArticle'} } + subject { get :show_news_article, params: {id: news_article1.id} } it { is_expected.to be_successful } end context 'unpublished' do - subject { get :show, params: {id: news_article4.id, type: 'NewsArticle'} } + subject { get :show_news_article, params: {id: news_article4.id} } it 'not found' do expect { subject }.to raise_exception(ActiveRecord::RecordNotFound) diff --git a/spec/controllers/tpi/sitemaps_controller_spec.rb b/spec/controllers/tpi/sitemaps_controller_spec.rb index e888f44b0..3df09186a 100644 --- a/spec/controllers/tpi/sitemaps_controller_spec.rb +++ b/spec/controllers/tpi/sitemaps_controller_spec.rb @@ -1,6 +1,5 @@ require 'rails_helper' -# rubocop:disable Layout/LineLength RSpec.describe TPI::SitemapsController, type: :controller do let_it_be(:company1) { create(:company, :published) } let_it_be(:company2) { create(:company, :draft) } @@ -27,16 +26,15 @@ expect(response.body).to have_css('url loc', text: tpi_company_url(company1.slug, **host_params)) expect(response.body).to have_css('url loc', text: tpi_bank_url(bank.slug, **host_params)) expect(response.body).to have_css('url loc', text: "https://#{host_params[:host]}/#{page1.slug}") - expect(response.body).to have_css('url loc', text: tpi_publication_download_file_path(slug: publication1.slug, **host_params)) - expect(response.body).to have_css('url loc', text: tpi_publication_url(article1, type: 'NewsArticle', **host_params)) + expect(response.body).to have_css('url loc', text: tpi_publication_url(id: publication1.slug, **host_params)) + expect(response.body).to have_css('url loc', text: show_news_article_tpi_publication_path(article1, **host_params)) end it('should not return unpublished entities') do subject expect(response.body).not_to have_css('url loc', text: tpi_company_url(company2.slug, **host_params)) - expect(response.body).not_to have_css('url loc', text: tpi_publication_download_file_path(slug: publication2.slug, **host_params)) - expect(response.body).not_to have_css('url loc', text: tpi_publication_url(article2, type: 'NewsArticle', **host_params)) + expect(response.body).not_to have_css('url loc', text: tpi_publication_url(id: publication2.slug, **host_params)) + expect(response.body).not_to have_css('url loc', text: show_news_article_tpi_publication_path(article2, **host_params)) end end end -# rubocop:enable Layout/LineLength