Skip to content

Commit

Permalink
Merge pull request #464 from Vizzuality/feature/separate-publication-…
Browse files Browse the repository at this point in the history
…page

feat: separate publication page
  • Loading branch information
martintomas authored Oct 24, 2023
2 parents 0b056bf + d01fea1 commit 0fd8cdc
Show file tree
Hide file tree
Showing 31 changed files with 253 additions and 63 deletions.
6 changes: 5 additions & 1 deletion app/admin/news_article.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

decorate_with NewsArticleDecorator

permit_params :title, :content, :publication_date,
permit_params :title, :content, :publication_date, :is_insight,
:created_by_id, :updated_by_id,
:image, :keywords_string, tpi_sector_ids: []

Expand All @@ -30,6 +30,7 @@
row :image do |t|
image_tag(url_for(t.image)) if t.image.present?
end
row :is_insight
row :updated_at
row :updated_by
row :created_at
Expand All @@ -43,6 +44,7 @@
index do
column 'Title', :title_link
column :publication_date
column :is_insight

actions
end
Expand All @@ -54,6 +56,7 @@
column(:sectors) { |l| l.tpi_sectors.map(&:name).join(Rails.application.config.csv_options[:entity_sep]) }
column :keywords, &:keywords_csv
column :publication_date
column :is_insight
end

form html: {'data-controller' => 'check-modified'} do |f|
Expand All @@ -67,6 +70,7 @@
collection: TPISector.order(:name), input_html: {multiple: true}
f.input :keywords_string, label: 'Keywords', hint: t('hint.tag'), as: :tags, collection: Keyword.pluck(:name)
f.input :image, as: :file, input_html: {accept: 'image/*'}
f.input :is_insight
end

f.actions
Expand Down
4 changes: 3 additions & 1 deletion app/admin/publication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
decorate_with PublicationDecorator

permit_params :title, :author, :author_image, :short_description, :publication_date,
:file, :image, :created_by_id, :updated_by_id,
:file, :image, :created_by_id, :updated_by_id, :summary,
:keywords_string, tpi_sector_ids: []

filter :title
Expand All @@ -20,6 +20,7 @@
attributes_table do
row :title
row :short_description
row :summary
row :author
row :author_image do |p|
if p.author_image.present?
Expand Down Expand Up @@ -63,6 +64,7 @@
f.input :author
f.input :author_image, as: :file, hint: preview_file_tag(f.object.author_image), input_html: {accept: 'image/*'}
f.input :short_description, as: :text
f.input :summary, as: :trix, embed_youtube: true
f.input :publication_date, as: :date_time_picker
f.input :tpi_sector_ids, label: 'Sectors', as: :select,
collection: TPISector.order(:name), input_html: {multiple: true}
Expand Down
6 changes: 6 additions & 0 deletions app/assets/stylesheets/tpi/_pages.scss
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ $margin-between-list-items: 20px;
flex-direction: column;
}
}

&__flex {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
}

.pages__content-title {
Expand Down
10 changes: 10 additions & 0 deletions app/assets/stylesheets/tpi/_publications.scss
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,16 @@ $max-lines: 3;
margin-bottom: 22px;
}
}

&__content-type {
color: $grey-dark;
margin-top: 10px;
font-size: $size-7;
border: 1px solid rgba($grey-dark, 0.5);
padding-left: $size-7;
padding-right: $size-7;
display: inline-block;
}
}

.publications__grid {
Expand Down
22 changes: 13 additions & 9 deletions app/controllers/tpi/publications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
65 changes: 55 additions & 10 deletions app/javascript/components/tpi/Filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand All @@ -81,7 +90,7 @@ const Filters = ({ tags, sectors, resultsSize }) => {
.map(s => encodeURIComponent(s.name))
.join(', ');

const query = `tags=${activeTagsQueryParam}&sectors=${activeSectorsQueryParam}&offset=${_offset}`;
const query = `types=${activeTypesQueryParam}&tags=${activeTagsQueryParam}&sectors=${activeSectorsQueryParam}&offset=${_offset}`;
const url = `/publications/partial?${query}`;

fetch(url)
Expand All @@ -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);
Expand Down Expand Up @@ -172,6 +204,18 @@ const Filters = ({ tags, sectors, resultsSize }) => {
{isFilterOpen
&& (
<div className="filters__container">
<div className="filters__title">Type</div>
<div className="filters__tags-container">
{activeTypes.length && activeTypes.map(type => (
<button
type="button"
onClick={() => handleTypeClick(type)}
className={cx('filters__tag', {'filters__tag-selected': type.active})}
>
{type.name}
</button>
))}
</div>
<div className="filters__title">Tag</div>
<div className="filters__tags-container">
{activeTags.length && activeTags.map(tag => (
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions app/models/bank_assessment_indicator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# text :text not null
# created_at :datetime not null
# updated_at :datetime not null
# comment :text
# is_placeholder :boolean default(FALSE)
#
class BankAssessmentIndicator < ApplicationRecord
INDICATOR_TYPES = %w[area sub_area indicator sub_indicator].freeze
Expand Down
3 changes: 3 additions & 0 deletions app/models/news_article.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
# updated_by_id :bigint
# created_at :datetime not null
# updated_at :datetime not null
# is_insight :boolean default(FALSE)
#

class NewsArticle < ApplicationRecord
Expand All @@ -23,6 +24,8 @@ class NewsArticle < ApplicationRecord
has_and_belongs_to_many :tpi_sectors

scope :published, -> { where('publication_date <= ?', DateTime.now) }
scope :insights, -> { where(is_insight: true) }
scope :not_insights, -> { where(is_insight: false) }

validates_presence_of :title, :content, :publication_date

Expand Down
2 changes: 2 additions & 0 deletions app/models/publication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# created_at :datetime not null
# updated_at :datetime not null
# author :string
# slug :text not null
# summary :text
#

class Publication < ApplicationRecord
Expand Down
16 changes: 13 additions & 3 deletions app/services/queries/tpi/news_publications_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,30 @@ 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!
(publications + news + insights).uniq.sort_by(&:publication_date).reverse!
end

private

def publications
return Publication.none if types.present? && !types.include?('Publications')

filter_scope(Publication.published)
end

def news
filter_scope(NewsArticle.published)
return NewsArticle.none if types.present? && !types.include?('News')

filter_scope(NewsArticle.published.not_insights)
end

def insights
return NewsArticle.none if types.present? && !types.include?('Insights')

filter_scope(NewsArticle.published.insights)
end

def filter_scope(scope)
Expand Down
15 changes: 13 additions & 2 deletions app/views/tpi/publications/_list.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
<div class="content__title-wrapper">
<div class="content__title">
<% if publication.is_a?(Publication) %>
<%= link_to publication.title, tpi_publication_download_file_path(slug: publication.slug), target: '_blank', class: 'link is-strong' %>
<% if publication.summary.present? %>
<%= link_to publication.title, tpi_publication_path(id: publication.slug), class: 'link is-strong' %>
<% else %>
<%= link_to publication.title, tpi_publication_download_file_path(slug: publication.slug), target: '_blank', class: 'link is-strong' %>
<% end %>
<% 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 %>
</div>
</div>
Expand All @@ -25,6 +29,13 @@
<% end %>
</p>
</div>
<div class="publication__content-type">
<% if publication.is_a?(NewsArticle) %>
<%= publication.is_insight? ? 'Insights' : 'News' %>
<% else %>
Publications
<% end %>
</div>
<% if publication.keywords.any? || publication.tpi_sectors.any? %>
<div class="publication__tags">
<% publication.tags_and_sectors.each do |tag| %>
Expand Down
7 changes: 6 additions & 1 deletion app/views/tpi/publications/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
<img src="<%= asset_path 'icons/filter-blue.svg'%>" class="filters__filter-icon" alt="filter icon">
</button>
</div>
<%= react_component("Filters", { tags: @tags, sectors: @sectors, resultsSize: @publications_and_articles_count }) %>
<%= react_component("Filters", {
types: %w[Publications News Insights],
tags: @tags,
sectors: @sectors,
resultsSize: @publications_and_articles_count
}) %>
<div id="publications" class="publications__container container">
<%= render 'promoted', publications_and_articles: @publications_and_articles, count: @publications_and_articles_count %>
</div>
Expand Down
Loading

0 comments on commit 0fd8cdc

Please sign in to comment.