diff --git a/.env.sample b/.env.sample index 9df289ce7..75211a527 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,8 @@ GCS_CREDENTIALS_FILE= GCS_BUCKET= MQ_BETA_ENABLED=true + +# SMTP API +SMTP_API_KEY= +SMTP_API_SECRET= +SMTP_DEFAULT_FROM= diff --git a/Gemfile b/Gemfile index 9c0436d25..22c6bc915 100644 --- a/Gemfile +++ b/Gemfile @@ -44,6 +44,7 @@ gem 'react-rails' gem 'appsignal' gem 'roo' gem 'caxlsx' +gem 'mailjet' group :development, :test do gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index fbafef674..12ed4677f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,40 +1,40 @@ GEM remote: https://rubygems.org/ specs: - actioncable (6.1.7.8) - actionpack (= 6.1.7.8) - activesupport (= 6.1.7.8) + actioncable (6.1.7.9) + actionpack (= 6.1.7.9) + activesupport (= 6.1.7.9) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.7.8) - actionpack (= 6.1.7.8) - activejob (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + actionmailbox (6.1.7.9) + actionpack (= 6.1.7.9) + activejob (= 6.1.7.9) + activerecord (= 6.1.7.9) + activestorage (= 6.1.7.9) + activesupport (= 6.1.7.9) mail (>= 2.7.1) - actionmailer (6.1.7.8) - actionpack (= 6.1.7.8) - actionview (= 6.1.7.8) - activejob (= 6.1.7.8) - activesupport (= 6.1.7.8) + actionmailer (6.1.7.9) + actionpack (= 6.1.7.9) + actionview (= 6.1.7.9) + activejob (= 6.1.7.9) + activesupport (= 6.1.7.9) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.7.8) - actionview (= 6.1.7.8) - activesupport (= 6.1.7.8) + actionpack (6.1.7.9) + actionview (= 6.1.7.9) + activesupport (= 6.1.7.9) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.7.8) - actionpack (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + actiontext (6.1.7.9) + actionpack (= 6.1.7.9) + activerecord (= 6.1.7.9) + activestorage (= 6.1.7.9) + activesupport (= 6.1.7.9) nokogiri (>= 1.8.5) - actionview (6.1.7.8) - activesupport (= 6.1.7.8) + actionview (6.1.7.9) + activesupport (= 6.1.7.9) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -60,28 +60,28 @@ GEM sassc sassc-rails xdan-datetimepicker-rails (~> 2.5.1) - activejob (6.1.7.8) - activesupport (= 6.1.7.8) + activejob (6.1.7.9) + activesupport (= 6.1.7.9) globalid (>= 0.3.6) - activemodel (6.1.7.8) - activesupport (= 6.1.7.8) + activemodel (6.1.7.9) + activesupport (= 6.1.7.9) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (6.1.7.8) - activemodel (= 6.1.7.8) - activesupport (= 6.1.7.8) + activerecord (6.1.7.9) + activemodel (= 6.1.7.9) + activesupport (= 6.1.7.9) activerecord-import (1.4.0) activerecord (>= 4.2) - activestorage (6.1.7.8) - actionpack (= 6.1.7.8) - activejob (= 6.1.7.8) - activerecord (= 6.1.7.8) - activesupport (= 6.1.7.8) + activestorage (6.1.7.9) + actionpack (= 6.1.7.9) + activejob (= 6.1.7.9) + activerecord (= 6.1.7.9) + activesupport (= 6.1.7.9) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (6.1.7.8) + activesupport (6.1.7.9) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -251,7 +251,7 @@ GEM activesupport (>= 5.2) htmlentities (4.3.4) httpclient (2.8.3) - i18n (1.14.5) + i18n (1.14.6) concurrent-ruby (~> 1.0) image_processing (1.12.2) mini_magick (>= 4.9.5, < 5) @@ -291,6 +291,11 @@ GEM net-imap net-pop net-smtp + mailjet (1.8.0) + activesupport (>= 5.0.0) + faraday (~> 2.1) + rack (>= 1.4.0) + yajl-ruby marcel (1.0.4) matrix (0.4.2) memoist (0.16.2) @@ -301,7 +306,7 @@ GEM minitest (5.25.1) msgpack (1.5.3) multi_json (1.15.0) - net-imap (0.4.14) + net-imap (0.4.17) date net-protocol net-pop (0.1.2) @@ -341,27 +346,27 @@ GEM puma (5.6.9) nio4r (~> 2.0) racc (1.8.1) - rack (2.2.9) + rack (2.2.10) rack-cors (1.1.1) rack (>= 2.0.0) rack-proxy (0.7.2) rack rack-test (2.1.0) rack (>= 1.3) - rails (6.1.7.8) - actioncable (= 6.1.7.8) - actionmailbox (= 6.1.7.8) - actionmailer (= 6.1.7.8) - actionpack (= 6.1.7.8) - actiontext (= 6.1.7.8) - actionview (= 6.1.7.8) - activejob (= 6.1.7.8) - activemodel (= 6.1.7.8) - activerecord (= 6.1.7.8) - activestorage (= 6.1.7.8) - activesupport (= 6.1.7.8) + rails (6.1.7.9) + actioncable (= 6.1.7.9) + actionmailbox (= 6.1.7.9) + actionmailer (= 6.1.7.9) + actionpack (= 6.1.7.9) + actiontext (= 6.1.7.9) + actionview (= 6.1.7.9) + activejob (= 6.1.7.9) + activemodel (= 6.1.7.9) + activerecord (= 6.1.7.9) + activestorage (= 6.1.7.9) + activesupport (= 6.1.7.9) bundler (>= 1.15.0) - railties (= 6.1.7.8) + railties (= 6.1.7.9) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -374,9 +379,9 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - railties (6.1.7.8) - actionpack (= 6.1.7.8) - activesupport (= 6.1.7.8) + railties (6.1.7.9) + actionpack (= 6.1.7.9) + activesupport (= 6.1.7.9) method_source rake (>= 12.2) thor (~> 1.0) @@ -407,8 +412,7 @@ GEM actionpack (>= 5.2) railties (>= 5.2) retriable (3.1.2) - rexml (3.3.6) - strscan + rexml (3.3.9) roo (2.10.0) nokogiri (~> 1) rubyzip (>= 1.3.0, < 3.0.0) @@ -500,13 +504,12 @@ GEM sshkit (1.21.2) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - strscan (3.1.0) super_diff (0.9.0) attr_extras (>= 6.2.4) diff-lcs patience_diff test-prof (1.0.9) - thor (1.3.1) + thor (1.3.2) tilt (2.0.10) timecop (0.9.5) timeout (0.4.1) @@ -547,7 +550,8 @@ GEM rails (>= 3.2.16) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.17) + yajl-ruby (1.4.3) + zeitwerk (2.6.18) PLATFORMS ruby @@ -589,6 +593,7 @@ DEPENDENCIES image_processing language_list listen (>= 3.0.5, < 3.2) + mailjet pg (>= 0.18, < 2.0) pg_search (= 2.3.2) pry diff --git a/app/admin/cp_assessments.rb b/app/admin/cp_assessments.rb index 13ff93da3..ff61747ac 100644 --- a/app/admin/cp_assessments.rb +++ b/app/admin/cp_assessments.rb @@ -8,7 +8,7 @@ permit_params :sector_id, :assessment_date, :publication_date, :cp_assessmentable_id, :last_reported_year, :assumptions, :cp_alignment_2025, :cp_alignment_2027, :cp_alignment_2035, :cp_alignment_2050, :region, :cp_regional_alignment_2025, :cp_regional_alignment_2027, :cp_regional_alignment_2035, - :cp_regional_alignment_2050, :years_with_targets_string, :emissions, + :cp_regional_alignment_2050, :years_with_targets_string, :emissions, :assessment_date_flag, cp_matrices_attributes: [:id, :portfolio, :cp_alignment_2025, :cp_alignment_2035, :cp_alignment_2050, :_destroy] filter :assessment_date @@ -60,6 +60,7 @@ end row :sector row :assessment_date + row :assessment_date_flag row :publication_date row :last_reported_year row :cp_alignment_2050 @@ -123,6 +124,7 @@ record.sector.name end column :assessment_date + column :assessment_date_flag column :publication_date, &:publication_date_csv year_columns.map do |year| column year do |a| diff --git a/app/assets/images/icons/hover-cursor.svg b/app/assets/images/icons/hover-cursor.svg new file mode 100644 index 000000000..508d5dd9d --- /dev/null +++ b/app/assets/images/icons/hover-cursor.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/assets/images/tpi/icons/download.svg b/app/assets/images/tpi/icons/download.svg new file mode 100644 index 000000000..caeef0de1 --- /dev/null +++ b/app/assets/images/tpi/icons/download.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/assets/images/tpi/sectors-bubble-chart-legend.svg b/app/assets/images/tpi/sectors-bubble-chart-legend.svg new file mode 100644 index 000000000..4c1dedfda --- /dev/null +++ b/app/assets/images/tpi/sectors-bubble-chart-legend.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/app/assets/stylesheets/tpi.scss b/app/assets/stylesheets/tpi.scss index 5ad486cab..904bb7d6f 100644 --- a/app/assets/stylesheets/tpi.scss +++ b/app/assets/stylesheets/tpi.scss @@ -44,6 +44,7 @@ @import "tpi/mq_beta_scores"; @import "tpi/mq-beta-modal"; @import "tpi/selector"; +@import "tpi/download-form-modal"; @import "tpi/emissions-chart"; @import "tpi/ascor-benchmarks"; @import "tpi/pages/*"; diff --git a/app/assets/stylesheets/tpi/_base-tooltip.scss b/app/assets/stylesheets/tpi/_base-tooltip.scss index c2c9eacce..c2a9cc3ef 100644 --- a/app/assets/stylesheets/tpi/_base-tooltip.scss +++ b/app/assets/stylesheets/tpi/_base-tooltip.scss @@ -9,6 +9,9 @@ div[data-react-class=BaseTooltip] { .base-tooltip { position: relative; + >div:first-child { + height: auto; + } &__default-trigger { font-family: $font-family-bold; diff --git a/app/assets/stylesheets/tpi/_bubble-chart.scss b/app/assets/stylesheets/tpi/_bubble-chart.scss index c8dbfa980..6d0c1ffe0 100644 --- a/app/assets/stylesheets/tpi/_bubble-chart.scss +++ b/app/assets/stylesheets/tpi/_bubble-chart.scss @@ -1,22 +1,30 @@ @import "colors"; @import "typography"; -$tape-height: 8px; +$tape-height: 1px; $tape-color: rgba(25, 25, 25, 0.1); -$cell-height: 80px; +$cell-height: 60px; $cell-height-banks: 100px; $legend-image-width: 60px; +.mq-sector-pie-chart-title { + display: flex; + gap: 8px; + color: $blue-darker; + margin-top: 28px; + line-height: 20px; + margin-bottom: 20px; +} + .bubble-chart__container { width: 100%; - padding: 50px 0; /* CSS GRID */ display: grid; - grid-row-gap: 4px; align-items: center; + justify-content: center; &--sectors { - grid-template-rows: 150px auto; + grid-template-rows: 144px auto; .last { border-right: none; @@ -38,14 +46,13 @@ $legend-image-width: 60px; height: $cell-height; display: flex; align-items: center; - border-right: calc(#{$tape-height / 2}) dotted $tape-color; & > *:first-child { margin: auto; z-index: 1; } - &::before { + &::after { background-color: $tape-color; content: ""; position: absolute; @@ -55,19 +62,62 @@ $legend-image-width: 60px; } } -.bubble-chart__container--banks { - .bubble-chart__cell { - height: $cell-height-banks; +.bubble-chart_circle { + circle:hover { + stroke-width: 2; + stroke: $yellow; } } +.bubble-chart_tooltip { + z-index: 10; + background-color: $white; + border: 1.25px solid $black; + padding: 20px; + font-size: 14px; + width: max-content; + max-width: 430px; + min-width: 400px; + + h4 { + color: $blue-dark; + font-size: 12px !important; + text-transform: uppercase; + font-weight: 400; + } + &_header { + font-size: 20px !important; + line-height: 24px; + font-weight: 700; + margin-block: 8px 20px; + } + &_text { + columns: 2; + margin-block: 12px; + font-size: 16px; + max-height: 200px; + overflow-y: auto; + column-gap: 40px; + + .bubble-chart_tooltip_list_item { + margin-top: 12px; + margin-left: 20px; + a { + text-decoration: underline; + color: black; + } + } -.bubble-chart_circle { - circle:hover { - stroke-width: 14; - stroke: $black; + } + .button.is-secondary.is-small { + float: right; + margin-top: 20px; + height: 40px !important; + width: 62px !important; + min-width: auto; } } + .bubble-tip { font-size: 14px; padding: 10px; @@ -100,6 +150,22 @@ $legend-image-width: 60px; } .bubble-chart__container--banks { + .bubble-chart__cell { + height: $cell-height-banks; + border-left: 4px dotted rgba(25, 25, 25, 0.1); + &:first-of-type { + border-left: none; + } + &::after { + background-color: rgba(25, 25, 25, 0.1); + content: ""; + position: absolute; + top: calc(50% - 4px); + height: 8px; + width: calc(100% + 4px); + } + } + .bubble-chart__title-container { position: absolute; top: -50px; @@ -113,6 +179,24 @@ $legend-image-width: 60px; font-size: 16px; font-weight: bold; } + .bubble-chart__level-title { + font-family: $font-family-bold; + font-size: 12px; + color: $black; + } + + .bubble-chart__level { + text-align: center; + padding-left: 0px; + } + + .bubble-chart__legend-container + .bubble-chart__level { + text-align: left; + } +} + +.bubble-chart_circle { + cursor: pointer; } .bubble-chart__title { @@ -133,34 +217,36 @@ $legend-image-width: 60px; } .bubble-chart__legend-image { - width: 60px; + width: 64px; + height: 67px; } .bubble-chart__legend-titles-container { position: absolute; color: $dark; - left: calc(#{$legend-image-width} + 10px); + left: calc(#{$legend-image-width} + 6px); top: -10px; } .bubble-chart__legend-title { font-size: 12px; + line-height: 13px; font-family: $font-family-regular; - margin: 2px 0; display: block; } .bubble-chart__level { border-right: calc(#{$tape-height / 2}) dotted $tape-color; position: relative; - height: 100%; - padding-left: 20px; + height: 60%; + } .bubble-chart__container--sectors { .bubble-chart__level-container { position: absolute; top: 20%; + padding-inline: 16px 10px; } .bubble-chart__level-title { @@ -170,26 +256,6 @@ $legend-image-width: 60px; } } -.bubble-chart__container--banks { - .bubble-chart__level-title { - font-family: $font-family-bold; - font-size: 12px; - color: $black; - } - - .bubble-chart__level { - text-align: center; - } - - .bubble-chart__legend-container + .bubble-chart__level { - text-align: left; - } - - .bubble-chart__level { - padding-left: 0px; - } -} - .bubble-chart__level-subtitle { color: $grey-dark; font-size: 12px; @@ -198,5 +264,5 @@ $legend-image-width: 60px; .bubble-chart__row-link { color: $blue-dark; text-align: right; - padding-right: 50px; + padding-right: 16px; } diff --git a/app/assets/stylesheets/tpi/_download-form-modal.scss b/app/assets/stylesheets/tpi/_download-form-modal.scss new file mode 100644 index 000000000..f5a433ff5 --- /dev/null +++ b/app/assets/stylesheets/tpi/_download-form-modal.scss @@ -0,0 +1,136 @@ +@import "colors"; +@import "typography"; + +.download { + button { + margin-right: 10px; + } +} +.download-form { + > * { + margin-bottom: 20px; + } + h2 { + font-size: 20px !important; + font-weight: bold; + } + .--mandatory { + color: $red; + } + .content { + input[type="text"] { + height: 40px; + width: 100%; + padding-inline: 10px; + border-radius: 0; + border: 1px solid #191919; + &::placeholder { + color: #595b5d; + font-size: 12px; + } + } + .text-inputs { + margin-bottom: 20px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; + @include until($breakpoint-desktop) { + grid-template-columns: 1fr; + } + label { + display: block; + line-height: 24px; + margin-bottom: 9px; + } + .selector__container { + list-style: none; + margin: 0; + .selector__header { + height: 40px; + } + .selector__input { + border: 0; + background-color: $white !important; + } + .selector__value { + font-size: 14px; + } + .selector__options { + margin: 0; + transform: translateY(-4px); + list-style: none; + max-height: 300px; + overflow-y: auto; + .selector__option { + font-size: 14px; + padding-block: 5px; + } + } + } + &.--full { + .content__input { + column-span: 2; + } + } + } + .checkbox-inputs { + display: flex; + flex-wrap: wrap; + gap: 16px 20px; + margin-bottom: 16px; + .content__input { + display: flex; + flex-direction: row-reverse; + align-items: center; + gap: 12px; + + input { + width: 24px; + height: 24px; + border-radius: 0; + display: flex; + justify-content: center; + align-items: center; + border: 2px solid $grey-dark; + appearance: none; + -webkit-appearance: none; + &::before { + content: ""; + width: 12px; + height: 12px; + display: block; + transform: scale(0); + background-color: $blue; + } + &:checked { + &::before { + transform: scale(1); + } + } + } + } + } + } + .other-purposes-text { + &.hidden { + display: none; + } + } + .error { + color: $red; + font-size: 12px; + margin-top: 10px; + } + .form-buttons { + margin-top: 20px; + width: 50%; + display: flex; + gap: 8px; + button { + min-width: auto !important; + } + .is-primary { + flex-grow: 1; + } + } +} diff --git a/app/assets/stylesheets/tpi/_dropdown-selector.scss b/app/assets/stylesheets/tpi/_dropdown-selector.scss index bb9f18ea5..9949e11e2 100644 --- a/app/assets/stylesheets/tpi/_dropdown-selector.scss +++ b/app/assets/stylesheets/tpi/_dropdown-selector.scss @@ -91,7 +91,7 @@ $header-height-mobile: 60px; left: calc(-1 * #{$box-shadow}); width: calc(100% + (2 * #{$box-shadow})); // 60px = 2 x 30px for box shadow from left and right margin-top: 20px; - z-index: 1; + z-index: 15; overflow: hidden; } diff --git a/app/assets/stylesheets/tpi/_mq_beta_scores.scss b/app/assets/stylesheets/tpi/_mq_beta_scores.scss index d7078a942..1129cf0e0 100644 --- a/app/assets/stylesheets/tpi/_mq_beta_scores.scss +++ b/app/assets/stylesheets/tpi/_mq_beta_scores.scss @@ -2,21 +2,27 @@ display: flex; flex-wrap: wrap; flex-direction: row; - - @include until($desktop) { - flex-direction: column; - } + align-items: center; &__container { display: flex; - border: 1px solid $white; - padding: 4px 4px 4px 14px; + flex-wrap: wrap; + @include until($desktop) { + padding: 0; + width: 100%; + margin-inline: 12px; + } } &__text { color: $white; line-height: 40px; margin-right: 10px; + @include until($desktop) { + flex-direction: column; + width: 100%; + // padding-inline: 12px; + } } &__divider { @@ -27,13 +33,36 @@ } } + a { + padding: 10px 12px; + &.button:not(.active):hover { + text-decoration: underline; + background-color: transparent; + } + &:first-of-type { + order: 3; + margin-right: 12px; + } + } + .button { - background-color: rgba($white ,0.2); + height: 36px !important; + @include until($desktop) { + width: 50%; + height: 40px; + margin-right: 0; + } &.active { - background-color: $white; - color: $blue-dark; + background-color: $blue-dark; + transform: translateY(1px); + color: $white; + @include until($desktop) { + background-color: $white; + color: $blue; + border-color: transparent; + } } &:last-child { @@ -42,6 +71,7 @@ } &__beta-button { + order: 2; &.disabled { opacity: 0.5; cursor: not-allowed; @@ -49,28 +79,39 @@ } } - &__beta-button:before { - content: ""; - width: 9px; - height: 9px; - border-radius: 50%; - margin-right: 5px; - display: inline-block; - background-color: $tpi-level5-background-color; - vertical-align: middle; - border: 1px solid $white; - } - &__download-button { flex-grow: 4; text-align: right; + display: flex; + gap: 5px; + justify-content: end; .button { background-color: $blue; + height: 36px !important; } @include until($desktop) { - margin-bottom: 10px; + background-color: #fff; + padding-inline: 12px; + padding-block: 40px; + flex-direction: column; + .button { + background-color: $white; + color: $blue !important; + border-color: $blue !important; + width: 100%; + &::before { + content: ' '; + width: 16px; + height: 16px; + background-image: image-url("tpi/icons/download.svg"); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + } + + } } } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/tpi/_nested-dropdown.scss b/app/assets/stylesheets/tpi/_nested-dropdown.scss index ae10a5794..9a073a86a 100644 --- a/app/assets/stylesheets/tpi/_nested-dropdown.scss +++ b/app/assets/stylesheets/tpi/_nested-dropdown.scss @@ -34,6 +34,7 @@ &__title-header { display: flex; flex-direction: column; + font-size: 16px; small { margin-top: -7px; diff --git a/app/assets/stylesheets/tpi/_selector.scss b/app/assets/stylesheets/tpi/_selector.scss index 91c6255da..be0c3b949 100644 --- a/app/assets/stylesheets/tpi/_selector.scss +++ b/app/assets/stylesheets/tpi/_selector.scss @@ -12,6 +12,10 @@ justify-content: space-between; cursor: pointer; + .selector__input { + border: 0; + } + .selector__value { width: 100%; margin-right: 16px; diff --git a/app/assets/stylesheets/tpi/buttons.scss b/app/assets/stylesheets/tpi/buttons.scss index d8e3dbfeb..12d8384c5 100644 --- a/app/assets/stylesheets/tpi/buttons.scss +++ b/app/assets/stylesheets/tpi/buttons.scss @@ -95,6 +95,16 @@ $button-border-width: 4px; justify-content: center; align-items: center; + &.is-info { + height: 20px; + width: 20px; + min-width: auto; + padding: 0; + background-color: transparent; + border-width: 2px; + margin-top: 2px; + } + &.is-small { border-radius: 0; height: 30px; diff --git a/app/assets/stylesheets/tpi/charts/companies_accordion.scss b/app/assets/stylesheets/tpi/charts/companies_accordion.scss index 48a2188de..47947987d 100644 --- a/app/assets/stylesheets/tpi/charts/companies_accordion.scss +++ b/app/assets/stylesheets/tpi/charts/companies_accordion.scss @@ -4,8 +4,12 @@ $tpi-level-chart-colors: #86A9F9 #5587F7 #2465F5 #0A4BDC #083AAB #9747FF; .mobile_bubble-chart__container { - margin: 0 -0.75rem; - padding: 50px 0 0; + margin: 0 -0.75rem 70px; + padding: 0; + + .mq-sector-pie-chart-title { + margin: 16px 12px 20px 12px; + } .accordions-list { color: $white; @@ -72,16 +76,17 @@ $tpi-level-chart-colors: #86A9F9 #5587F7 #2465F5 #0A4BDC #083AAB #9747FF; .go-to-button__container { padding-top: 1.5rem; text-align: center; - width: 100%; - + margin-inline: 12px; + a { border-width: 2px; + width: 100%; font-size: $size-5; border-radius: 0; - height: 30px; + height: 40px; &:focus { - height: 30px; + height: 40px; } } } diff --git a/app/assets/stylesheets/tpi/charts/cp-all-sectors.scss b/app/assets/stylesheets/tpi/charts/cp-all-sectors.scss index 87c4c3b6d..37206781b 100644 --- a/app/assets/stylesheets/tpi/charts/cp-all-sectors.scss +++ b/app/assets/stylesheets/tpi/charts/cp-all-sectors.scss @@ -5,6 +5,7 @@ .chart--cp-all-sectors { justify-content: unset; + margin-bottom: 50px; div[data-highcharts-chart] { width: 100%; diff --git a/app/assets/stylesheets/tpi/charts/cp-performance.scss b/app/assets/stylesheets/tpi/charts/cp-performance.scss index 95208bb8a..e37ccfde6 100644 --- a/app/assets/stylesheets/tpi/charts/cp-performance.scss +++ b/app/assets/stylesheets/tpi/charts/cp-performance.scss @@ -98,7 +98,7 @@ .chart--cp-performance { @import 'chart-company-selector'; - margin-top: 10px; + margin-block: 50px; justify-content: unset; flex-flow: column; diff --git a/app/assets/stylesheets/tpi/charts/mq-sector-pie-chart.scss b/app/assets/stylesheets/tpi/charts/mq-sector-pie-chart.scss index 09525a77a..59bb25773 100644 --- a/app/assets/stylesheets/tpi/charts/mq-sector-pie-chart.scss +++ b/app/assets/stylesheets/tpi/charts/mq-sector-pie-chart.scss @@ -1,60 +1,130 @@ -@import '../variables'; +@import "../variables"; -$tpi-pie-chart-colors: #86A9F9 #5587F7 #2465F5 #0A4BDC #083AAB #9747FF; +$tpi-pie-chart-colors: #86a9f9 #5587f7 #2465f5 #0a4bdc #083aab #9747ff; -.chart--mq-sector-pie-chart { - margin: 0 auto; - min-height: 400px; - display: flex; - align-items: center; +.mq-sector-pie-chart { + margin-top: 16px; + margin-bottom: 60px; + .mq-sector-pie-chart-title { + display: flex; + gap: 8px; + color: $blue-darker; - // remove overflow hidden from the chart - div[data-highcharts-chart], .highcharts-container, svg { - overflow: unset !important; - - @include until($desktop) { - tspan { - &:not(.highcharts-strong) { - font-size: $size-7; - font-family: $font-family-bold; - } + .--mobile { + display: none; + @include until($desktop) { + display: inline-block; } } - } - - @for $i from 1 through length($tpi-pie-chart-colors) { - $color: nth($tpi-pie-chart-colors, $i); - .highcharts-color-#{$i - 1} { - fill: $color; - stroke: $color; + .--desktop { + display: inline-block; + @include until($desktop) { + display: none; + } } - } - .highcharts-data-label { - font-size: 1rem; - font-family: $font-family-bold; - font-weight: unset; } - .highcharts-data-label-connector { - stroke: $black; - fill: none; - } + .chart--mq-sector-pie-chart { + min-height: 320px; + width: 850px; + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + @include until($desktop) { + gap: 32px; + justify-content: center; + width: 100%; + } - .chart-title { - font-family: $font-family-regular; - font-size: 16px; + .highcharts-container { + width: 320px; + @include until($desktop) { + width: 290px; + } + } + // remove overflow hidden from the chart + div[data-highcharts-chart], + .highcharts-container, + svg { + overflow: unset !important; - .companies-size { - font-size: 36px; - font-family: $font-family-bold; + @include until($desktop) { + tspan { + &:not(.highcharts-strong) { + font-size: $size-7; + font-family: $font-family-bold; + } + } + } } - @include until($desktop) { - font-size: 14px; + @for $i from 1 through length($tpi-pie-chart-colors) { + $color: nth($tpi-pie-chart-colors, $i); + .highcharts-color-#{$i - 1} { + fill: $color; + stroke: $color; + } + } + .chart--mq-sector-pie-chart-title { + font-family: $font-family-regular; + font-size: 16px; + text-align: center; + transform: translateY(-16px); + .companies-size { - font-size: 24px; + font-size: 36px; + line-height: 40px; + font-family: $font-family-bold; + } + &.--selected { + transform: translateY(-35%); + } + + @include until($desktop) { + font-size: 14px; + + .companies-size { + font-size: 24px; + } + } + } + + .chart-legends { + display: grid; + width: 372px; + grid-template-rows: repeat(3, min-content); + grid-auto-flow: column; + column-gap: 80px; + row-gap: 20px; + @include until($desktop) { + column-gap: 32px; + width: 100%; + } + .chart-legend-item { + display: flex; + gap: 8px; + .chart-legend-item__name { + .chart-legend-item__color { + height: 12px; + width: 12px; + border-radius: 50%; + } + display: flex; + align-items: center; + gap: 8px; + font-size: 16px; + font-family: $font-family-bold; + } + .chart-legend-item__value { + margin-left: 20px; + margin-top: 4px; + font-size: 12px; + font-family: $font-family-regular; + min-width: max-content; + } } } } diff --git a/app/assets/stylesheets/tpi/pages/banks-index.scss b/app/assets/stylesheets/tpi/pages/banks-index.scss index 529bab25c..bea658766 100644 --- a/app/assets/stylesheets/tpi/pages/banks-index.scss +++ b/app/assets/stylesheets/tpi/pages/banks-index.scss @@ -60,7 +60,7 @@ .dropdown-selector-wrapper { background-color: $blue; - + transform: translateY(-1px); .dropdown-selector__container { @include desktop { padding: $container-top-padding $container-side-padding 40px $container-side-padding; diff --git a/app/assets/stylesheets/tpi/pages/company.scss b/app/assets/stylesheets/tpi/pages/company.scss index b985cf308..667c792dd 100644 --- a/app/assets/stylesheets/tpi/pages/company.scss +++ b/app/assets/stylesheets/tpi/pages/company.scss @@ -2,26 +2,42 @@ .change-view { margin-top: 1.5rem; margin-bottom: 1.5rem; + + .buttons { + justify-content: end; + @include until($desktop) { + // flex-direction: column; + justify-content: center; + } + } } .companies-header { background: $blue; - - height: 100px; + // height: 100px; + min-height: 76px; display: flex; align-items: center; + position: sticky; + top: 0; + z-index: 10; + transform: translateY(-1px); a { color: white; line-height: 40px; margin-right: 10px; + @include until($desktop) { + margin-right: 0; + } } @include until($desktop) { - padding: 0 0.75rem; + padding-inline: 0 !important; font-size: $size-7; - height: 60px; + height: auto; font-family: $font-family-regular; + min-height: 76px; } } @@ -35,6 +51,9 @@ .summary-boxes { margin-top: 1.5rem; + @include until($desktop) { + margin-top: 0; + } } .summary-box { @@ -243,10 +262,6 @@ border-left: 2px solid $hawkes-blue; - &.level5 { - background-color: rgba($tpi-level5-background-color, 0.15); - } - &__header { font-size: 16px; font-weight: bold; diff --git a/app/assets/stylesheets/tpi/pages/sector.scss b/app/assets/stylesheets/tpi/pages/sector.scss index 31b33102f..b50c9176a 100644 --- a/app/assets/stylesheets/tpi/pages/sector.scss +++ b/app/assets/stylesheets/tpi/pages/sector.scss @@ -1,6 +1,6 @@ @import "../variables"; -$tape-color: rgba(25,25,25,0.1); +$tape-color: rgba(25, 25, 25, 0.1); .sector-page { .dropdown-selector-sector-page-wrapper { @@ -14,22 +14,29 @@ $tape-color: rgba(25,25,25,0.1); .sectors-header { background: $blue; - - height: 100px; display: flex; align-items: center; - + min-height: 76px; + position: sticky; + top: 0; + z-index: 10; + transform: translateY(-1px); + a { color: white; line-height: 40px; margin-right: 10px; + @include until($desktop) { + margin-right: 0; + } } @include until($desktop) { - padding: 0 0.75rem; + padding-inline: 0 !important; font-size: $size-7; - height: 60px; + height: auto; font-family: $font-family-regular; + min-height: auto; } } @@ -60,34 +67,49 @@ $tape-color: rgba(25,25,25,0.1); margin-top: 50px; .sector-level { - display: flex; - flex-direction: column; - - padding-bottom: 0; + &.column { + display: flex; + justify-content: space-between; + flex-direction: column; + padding-bottom: 0; + } &:not(:last-child) { - border-right: 5px dotted $tape-color; + border-right: 2px dashed $tape-color; } &__title { position: relative; - height: 60px; - margin-bottom: 50px; + margin-bottom: 20px; + h5 { + font-size: 16px; + line-height: 20px; + font-weight: 700; + margin-bottom: 6px; + } + p { + font-size: 12px; + line-height: 18px; + color: $grey-dark; + } } &__companies { - flex: 1; line-height: 1.25; - - padding: 12px 24px; color: white; - - a { - color: white; - } - - li { - padding: 3px 0; + display: flex; + align-items: end; + + > div:first-child { + width: 100%; + text-align: center; + padding-top: 20px; + font-size: 32px; + font-weight: 700; + cursor: pointer; + &:hover { + border: 2px solid $yellow; + } } .mq-level-trend { @@ -128,15 +150,16 @@ $tape-color: rgba(25,25,25,0.1); display: flex; justify-content: space-between; margin-bottom: 15px; + padding-top: 50px; - >p { + > p { display: none; } @include until($desktop) { display: block; - >p { + > p { display: block; margin-top: 15px; font-size: $size-6; @@ -147,7 +170,7 @@ $tape-color: rgba(25,25,25,0.1); display: block; color: $grey-dark; font-size: $size-5; - margin-top: 20px; + margin-top: 20px; max-width: 280px; #show-by-dropdown-placeholder { @@ -169,3 +192,55 @@ $tape-color: rgba(25,25,25,0.1); } } } + +.sector-level__popover { + z-index: 10; + background-color: $white; + border: 1.25px solid $black; + padding: 20px; + font-size: 14px; + width: 430px; + max-width: 430px; + min-width: 400px; + color: black; + + h4 { + color: $blue-dark; + font-size: 12px !important; + text-transform: uppercase; + font-weight: 400; + } + p { + font-size: 12px; + line-height: 18px; + color: $grey-dark; + } + &_header { + font-size: 20px !important; + line-height: 24px; + font-weight: 700; + margin-block: 8px 20px; + } + &_text { + columns: 2; + margin-block: 12px; + font-size: 16px; + max-height: 200px; + overflow-y: auto; + column-gap: 40px; + } + &_list_item { + margin-left: 20px; + a { + text-decoration: underline; + color: black; + } + } + .button.is-secondary.is-small { + float: right; + margin-top: 20px; + height: 40px !important; + width: 62px !important; + min-width: auto; + } +} diff --git a/app/assets/stylesheets/tpi/pages/sectors-index.scss b/app/assets/stylesheets/tpi/pages/sectors-index.scss index 1a25a0cf9..d23fefcf1 100644 --- a/app/assets/stylesheets/tpi/pages/sectors-index.scss +++ b/app/assets/stylesheets/tpi/pages/sectors-index.scss @@ -3,9 +3,14 @@ .sectors-index-page { .sectors-header { background: $blue; - min-height: 100px; + min-height: 76px; display: flex; align-items: center; + justify-content: space-between; + position: sticky; + top: 0; + z-index: 10; + transform: translateY(-1px); a { color: white; @@ -22,10 +27,11 @@ } @include until($desktop) { - padding: 0 0.75rem; + padding: 0; font-size: $size-7; - min-height: 60px; + height: auto; font-family: $font-family-regular; + min-height: 76px; } } @@ -33,7 +39,7 @@ margin-top: 70px; @include until($desktop) { - margin-top: 40px; + margin-top: 0px; padding: 0 0.75rem; } } @@ -43,7 +49,21 @@ margin-top: 30px; } + h3.title-blue { + color: $blue; + font-weight: bold; + line-height: 40px; + margin-bottom: 80px; + font-size: 28px !important; + } + @include until($desktop) { + h3.title-blue { + font-size: $size-4 !important; + line-height: 28px; + margin-bottom: 80px; + } + h4 { font-size: $size-5 !important; } diff --git a/app/controllers/concerns/tpi/user_download.rb b/app/controllers/concerns/tpi/user_download.rb index 72bfb1089..8fb908145 100644 --- a/app/controllers/concerns/tpi/user_download.rb +++ b/app/controllers/concerns/tpi/user_download.rb @@ -12,10 +12,8 @@ def send_tpi_user_file(mq_assessments:, cp_assessments:, filename:) .includes(sector: [:cp_units]) mq_assessments_files = mq_assessments_by_methodology.map do |methodology, assessments| - is_beta_methodology = MQ::Assessment::BETA_METHODOLOGIES.include? methodology - name = is_beta_methodology ? "#{methodology}_BETA_#{timestamp}" : "#{methodology}_#{timestamp}" { - "MQ_Assessments_Methodology_#{name}.csv" => CSVExport::User::MQAssessments.new(assessments).call + "MQ_Assessments_Methodology_#{methodology}_#{timestamp}.csv" => CSVExport::User::MQAssessments.new(assessments).call } end.reduce(&:merge) @@ -38,7 +36,7 @@ def send_tpi_user_file(mq_assessments:, cp_assessments:, filename:) 'User guide TPI files.xlsx' => user_guide ) if ENV['MQ_BETA_ENABLED'].to_s == 'true' - files = files.merge 'Company_Latest_Assessments_BETA_5.0.csv' => latest_cp_assessments_beta_csv + files = files.merge 'Company_Latest_Assessments_5.0.csv' => latest_cp_assessments_beta_csv end render zip: files.compact, filename: "#{filename} - #{timestamp}" end diff --git a/app/controllers/tpi/ascor_controller.rb b/app/controllers/tpi/ascor_controller.rb index b20de61b1..34d07899a 100644 --- a/app/controllers/tpi/ascor_controller.rb +++ b/app/controllers/tpi/ascor_controller.rb @@ -9,8 +9,8 @@ class ASCORController < TPIController def index @assessment_dates = ASCOR::Assessment.pluck(:assessment_date).uniq @publications_and_articles = ( - Publication.joins(:tags).includes(:tpi_sectors).where(tags: {name: 'ASCOR'}) + - NewsArticle.joins(:tags).where(tags: {name: 'ASCOR'}) + Publication.published.joins(:tags).includes(:tpi_sectors).where(tags: {name: 'ASCOR'}) + + NewsArticle.published.joins(:tags).where(tags: {name: 'ASCOR'}) ).uniq.sort_by(&:publication_date).reverse!.take(3) ascor_page = TPIPage.find_by(slug: 'ascor') @methodology_description = Content.find_by(page: ascor_page, code: 'methodology_description') diff --git a/app/controllers/tpi/banks_controller.rb b/app/controllers/tpi/banks_controller.rb index 5d29266c1..3ce1c72ee 100644 --- a/app/controllers/tpi/banks_controller.rb +++ b/app/controllers/tpi/banks_controller.rb @@ -12,6 +12,7 @@ class BanksController < TPIController def index @assessment_dates = BankAssessment.select(:assessment_date).distinct.pluck(:assessment_date) @publications_and_articles = TPISector.find_by(slug: 'banks')&.publications_and_articles || [] + @publications_and_articles = @publications_and_articles.select { |pa| pa.publication_date <= Time.current } bank_page = TPIPage.find_by(slug: 'banks-content') @methodology_description = Content.find_by( page: bank_page, diff --git a/app/controllers/tpi/companies_controller.rb b/app/controllers/tpi/companies_controller.rb index d2724d22b..0d6b53856 100644 --- a/app/controllers/tpi/companies_controller.rb +++ b/app/controllers/tpi/companies_controller.rb @@ -2,6 +2,7 @@ module TPI class CompaniesController < TPIController include UserDownload + before_action :enable_beta_mq_assessments before_action :fetch_company before_action :redirect_if_numeric_or_historic_slug, only: [:show] before_action :fetch_cp_assessment, only: [:show, :cp_assessment, :emissions_chart_data] diff --git a/app/controllers/tpi/sectors_controller.rb b/app/controllers/tpi/sectors_controller.rb index 2e6cd22c5..1d8bffadc 100644 --- a/app/controllers/tpi/sectors_controller.rb +++ b/app/controllers/tpi/sectors_controller.rb @@ -2,6 +2,7 @@ module TPI class SectorsController < TPIController include UserDownload + before_action :enable_beta_mq_assessments before_action :fetch_companies, only: [:show, :index] before_action :fetch_sectors, only: [:show, :index, :user_download_all] before_action :fetch_sector, only: [:show, :user_download] @@ -77,6 +78,11 @@ def cp_performance_chart_data render json: data.chart_json end + def send_download_file_info_email + DataDownloadMailer.send_download_file_info_email(permitted_email_params).deliver_now + head :ok + end + def user_download_all send_user_download_file( Company.published.select(:id).where(sector_id: @sectors.pluck(:id)), @@ -91,6 +97,17 @@ def user_download ) end + def user_download_methodology + file_path = if session[:enable_beta_mq_assessments] + Rails.root.join( + 'public', 'static_files', 'TPI’s methodology report. Management Quality and Carbon Performance.pdf' + ) + else + Rails.root.join('public', 'static_files', 'Methodology and Indicator Report v4.0_final draft.pdf') + end + send_file file_path, type: 'application/pdf', disposition: 'attachment' + end + private def any_cp_assessment? @@ -155,5 +172,9 @@ def companies_scope(params) Company.published.active end end + + def permitted_email_params + params.permit(:email, :job_title, :forename, :surname, :location, :organisation, :other_purpose, purposes: []) + end end end diff --git a/app/controllers/tpi/tpi_controller.rb b/app/controllers/tpi/tpi_controller.rb index c0893c6ec..d4add55b1 100644 --- a/app/controllers/tpi/tpi_controller.rb +++ b/app/controllers/tpi/tpi_controller.rb @@ -6,6 +6,10 @@ class TPIController < ApplicationController protected + def enable_beta_mq_assessments + session[:enable_beta_mq_assessments] = true unless session.key?(:enable_beta_mq_assessments) + end + def fixed_navbar(admin_panel_section_title, admin_panel_link) @admin_panel_section_title = admin_panel_section_title @link = admin_panel_link diff --git a/app/javascript/components/tpi/BaseTooltip.js b/app/javascript/components/tpi/BaseTooltip.js index 370bc74c7..9cee49dc0 100644 --- a/app/javascript/components/tpi/BaseTooltip.js +++ b/app/javascript/components/tpi/BaseTooltip.js @@ -28,7 +28,7 @@ BaseTooltip.propTypes = { BaseTooltip.defaultProps = { trigger: ( - ? + i ) }; diff --git a/app/javascript/components/tpi/DownloadFormModal.jsx b/app/javascript/components/tpi/DownloadFormModal.jsx new file mode 100644 index 000000000..9079098b5 --- /dev/null +++ b/app/javascript/components/tpi/DownloadFormModal.jsx @@ -0,0 +1,299 @@ +import React, { useState, useRef, useEffect, useMemo } from 'react'; + +import PropTypes from 'prop-types'; +import { getNames } from 'country-list'; +import { Modal } from './Modal'; +import { OverlayProvider } from '@react-aria/overlays'; +import Select from './Select'; +import classNames from 'classnames'; +import downloadIcon from 'images/icons/download.svg'; + +const Field = ({ label, name, value, onChange, type, required, error, placeholder, children }) => { + const ref = useRef(null); + + useEffect(() => { + if (error) { + ref.current.focus(); + } + }, [error]); + + return ( +
+ + {children || ( + + )} + {error && {error}} +
+ ); +}; + +Field.propTypes = { + label: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + type: PropTypes.string, + value: PropTypes.string, + onChange: PropTypes.func, + error: PropTypes.string, + required: PropTypes.bool, + placeholder: PropTypes.string, + children: PropTypes.node +}; + +Field.defaultProps = { + error: null, + type: 'text', + onChange: () => {}, + value: undefined, + required: false, + placeholder: '', + children: null +}; + +const checkboxInputs = [ + 'diligence_research', + 'engagement', + 'voting', + 'academic_research', + 'content_creation', + 'investment', + 'other_purpose_checkbox' +]; + +const initialFormValues = { + email: '', + job_title: '', + forename: '', + surname: '', + location: '', + organisation: '', + purposes: [], + other_purpose: '' +}; + +function DownloadFormModal({ downloadUrl }) { + const [showModal, setShowModal] = useState(false); + const [error, setError] = useState(null); + const [formValues, setFormValues] = useState(initialFormValues); + + const downloadLinkRef = useRef(null); + const countryOptions = useMemo(() => getNames()?.map((name) => ({label: name, value: name})), []); + + const handleSubmit = (e) => { + e.preventDefault(); + + if (formValues.purposes.length === 0) { + setError('Please select at least one purpose'); + return; + } + if (formValues.purposes.includes('other_purpose_checkbox') && !formValues.other_purpose) { + setError('Please specify other purposes'); + return; + } + + fetch('/sectors/send_download_file_info_email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': document.querySelector('[name="csrf-token"]').content + }, + body: JSON.stringify(formValues) + }).then(() => { + downloadLinkRef.current.click(); + setShowModal(false); + setFormValues(initialFormValues); + }, () => { + setError('Something went wrong. Please try again later.'); + }); + }; + + const handleChange = (e) => { + setFormValues({ ...formValues, [e.target.name]: e.target.value }); + if (checkboxInputs.includes(e.target.name)) { + if (e.target.checked) { + setFormValues({ + ...formValues, + purposes: [...formValues.purposes, e.target.name] + }); + setError(null); + } else { + setFormValues({ + ...formValues, + other_purpose: e.target.name === 'other_purpose_checkbox' ? '' : formValues.other_purpose, + purposes: formValues.purposes.filter((item) => item !== e.target.name) + }); + } + } + }; + + return ( +
+ + + + setShowModal(false)} + > +
+

Data Disclaimer

+

+ I have read the Use of TPI Centre Data and will not use the data for + commercial purposes unless I have sought prior permission. +

+

All + fields below are mandatory. +

+
+
+
+ + + + + + + {error && {error}} +
+ + +
+ +
+
+ + +
+ ); +} + +DownloadFormModal.propTypes = { + downloadUrl: PropTypes.string.isRequired +}; + +export default DownloadFormModal; diff --git a/app/javascript/components/tpi/DropdownSelector.js b/app/javascript/components/tpi/DropdownSelector.js index 31fc0abeb..e0eec7f44 100644 --- a/app/javascript/components/tpi/DropdownSelector.js +++ b/app/javascript/components/tpi/DropdownSelector.js @@ -73,9 +73,9 @@ const DropdownSelector = ({ sectors, companies, selectedOption, defaultFilter = ); const handleOptionClick = (option) => { - const url = isFilterBySector ? '/sectors/' : '/companies/'; + const url = isFilterBySector ? '/corporates/' : '/companies/'; setIsOpen(false); - if (!(window.location.pathname === '/sectors/' && option.id === 'all-sectors')) { + if (!(window.location.pathname === '/corporates/' && option.id === 'all-sectors')) { window.open(`${url}${option.slug}`, '_self'); } }; diff --git a/app/javascript/components/tpi/InfoTooltip.js b/app/javascript/components/tpi/InfoTooltip.js index 4629c7a7d..574a6079a 100644 --- a/app/javascript/components/tpi/InfoTooltip.js +++ b/app/javascript/components/tpi/InfoTooltip.js @@ -3,10 +3,10 @@ import PropTypes from 'prop-types'; import ReactTooltip from 'react-tooltip'; const InfoTooltip = ({ trigger, content, html }) => ( - +
{html ?
:
{trigger}
} - +
); InfoTooltip.propTypes = { @@ -17,7 +17,7 @@ InfoTooltip.propTypes = { InfoTooltip.defaultProps = { trigger: ( - ? + i ), html: false }; diff --git a/app/javascript/components/tpi/Select.js b/app/javascript/components/tpi/Select.js index abca539e0..f2e1405e5 100644 --- a/app/javascript/components/tpi/Select.js +++ b/app/javascript/components/tpi/Select.js @@ -20,7 +20,7 @@ const Select = ({ value, allowSearch, placeholder, - label + required }) => { const [searchValue, setSearchValue] = useState(''); const [isOpen, setIsOpen] = useState(false); @@ -59,6 +59,7 @@ const Select = ({ const handleOptionClick = (opt) => { setIsOpen(false); + setSearchValue(opt.value); onSelect({ name, value: opt.value, label: opt.label }); }; @@ -144,18 +145,15 @@ const Select = ({ role="button" aria-label={isOpen ? 'Close dropdown' : 'Open dropdown'} > - {isOpen && allowSearch ? ( - setSearchValue(e.target.value)} - placeholder="Type or select" - /> - ) : ( - - {value || placeholder} - - )} + setSearchValue(e.target.value)} + placeholder={placeholder} + required={required} + name={name} + value={isOpen && allowSearch ? searchValue : value} + /> isOpen && handleCloseDropdown()} className={cx('chevron-icon', { @@ -215,14 +213,15 @@ Select.propTypes = { value: PropTypes.string, allowSearch: PropTypes.bool, placeholder: PropTypes.string, - label: PropTypes.string.isRequired, - name: PropTypes.string.isRequired + name: PropTypes.string.isRequired, + required: PropTypes.bool }; Select.defaultProps = { value: '', allowSearch: false, - placeholder: '' + placeholder: '', + required: false }; export default Select; diff --git a/app/javascript/components/tpi/charts/bubble/Chart.js b/app/javascript/components/tpi/charts/bubble/Chart.js index 261879e34..9f11dcaee 100644 --- a/app/javascript/components/tpi/charts/bubble/Chart.js +++ b/app/javascript/components/tpi/charts/bubble/Chart.js @@ -1,20 +1,19 @@ -import React, {useEffect} from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import legendImage from 'images/bubble-chart-legend.svg'; +import legendImage from 'images/tpi/sectors-bubble-chart-legend.svg'; import SingleCell from './SingleCell'; -import BaseTooltip from 'components/tpi/BaseTooltip'; - -const SCALE = 5; +import hoverIcon from 'images/icons/hover-cursor.svg'; // radius of bubbles -const COMPANIES_MARKET_CAP_GROUPS = { - large: 10 * SCALE, - medium: 5 * SCALE, - small: 3 * SCALE -}; +const COMPANIES_MARKET_CAP_GROUPS = [ + '100+', + '51-100', + '11-50', + '1-10' +]; -const SINGLE_CELL_SVG_WIDTH = 120 * SCALE; -const SINGLE_CELL_SVG_HEIGHT = 80 * SCALE; +const SINGLE_CELL_SVG_WIDTH = 144; +const SINGLE_CELL_SVG_HEIGHT = 62; const LEVELS_COLORS = [ '#86A9F9', @@ -34,20 +33,60 @@ const LEVELS_SUBTITLES = { 5: 'Transition planning and implementation' }; -const tooltipDisclaimer = 'Companies have to answer “yes” to all questions on a level to move to the next one'; -let tooltip = null; +const Row = ({ dataRow, title, sectors }) => { + const sector = sectors.find((s) => s.name === title); -const BubbleChart = ({ levels, sectors }) => { - const tooltipEl = ''; - useEffect(() => { - document.body.insertAdjacentHTML('beforeend', tooltipEl); - tooltip = document.getElementById('bubble-chart-tooltip'); - }, []); - const parsedData = Object.keys(levels).map(sectorName => ({ - sector: sectorName, - data: Object.values(levels[sectorName]) - })); + return ( + +
+ {title} +
+ {dataRow.map((el, i) => { + const companies = el.map((company) => ({ + name: company.name, + url: company.path + })); + const companiesBubble = { + value: el.length, + color: LEVELS_COLORS[i], + tooltipContent: { + level: `Level ${i} - ${LEVELS_SUBTITLES[i]}`, + title, + companies + } + }; + // Remove special characters from the key to be able to use d3-select as it uses querySelector + const cleanKey = title.replace(/[^a-zA-Z0-9\-_:.]/g, ''); + const uniqueKey = `${cleanKey}-${el.length}-${i}`; + + return ( +
+ +
+ ); + })} +
+ ); +}; + +Row.propTypes = { + dataRow: PropTypes.array.isRequired, + title: PropTypes.string.isRequired, + sectors: PropTypes.array.isRequired +}; + +const BubbleChart = ({ levels, sectors }) => { /** Parsed data has this format - * [ * { sector: 'Sector1', data: [ [ {}, {}, {} ], [], [], [], [], [] ] }, @@ -60,121 +99,95 @@ const BubbleChart = ({ levels, sectors }) => { * { sector: 'Sector8', data: [ [], [], [], [], [], [] ] } * ] */ - - const levelsSignature = levels && Object.keys(levels[Object.keys(levels)[0]]); - - return ( -
-
-
- Market cap - {tooltipDisclaimer}} - /> -
-
- Bubble size description -
- {Object.keys(COMPANIES_MARKET_CAP_GROUPS).map((companySize, i) => ( - - {companySize} - - ))} -
-
-
- {levelsSignature.map((el, i) => ( -
-
-
{`Level ${el === '5' ? '5 [BETA]' : el}`}
-
{LEVELS_SUBTITLES[el]}
-
-
- ))} - {parsedData.map(dataRow => createRow(dataRow.data, dataRow.sector, sectors))} -
- ); -}; - -const ForceLayoutBubbleChart = (companiesBubbles, uniqueKey) => { - const handleBubbleClick = (company) => window.open(company.path, '_blank'); - - return ( - + const parsedData = Object.entries(levels).map( + ([sectorName, sectorValue]) => ({ + sector: sectorName, + data: Object.values(sectorValue) + }) ); -}; -const getTooltipText = ({ tooltipContent }) => { - if (tooltipContent) { - return ` -
${tooltipContent.header}
-
${tooltipContent.value}
- `; - } - return ''; -}; - -const showTooltip = (node, u) => { - const bubble = u._groups[0][node.index]; - - tooltip.innerHTML = getTooltipText(node); - tooltip.removeAttribute('hidden'); - const bubbleBoundingRect = bubble.getBoundingClientRect(); - const topOffset = bubbleBoundingRect.top - tooltip.offsetHeight + window.pageYOffset; - const leftOffset = bubbleBoundingRect.left + (bubbleBoundingRect.width - tooltip.offsetWidth) / 2 + window.pageXOffset; - - tooltip.style.left = `${leftOffset}px`; - tooltip.style.top = `${topOffset}px`; -}; - -const hideTooltip = () => { - tooltip.setAttribute('hidden', true); -}; + const levelsSignature = levels && Object.keys(levels[Object.keys(levels)[0]]); -const createRow = (dataRow, title, sectors) => { - const sector = sectors.find(s => s.name === title); + const GRID_HEIGHT = parsedData.length * SINGLE_CELL_SVG_HEIGHT + 100; return ( - -
- {title} +
+
+ Hover icon +

+ Click on the bubbles to see the detailed list of companies for each sector. +

- {dataRow.map((el, i) => { - const companiesBubbles = el.map(company => ({ - value: COMPANIES_MARKET_CAP_GROUPS[company.market_cap_group], - tooltipContent: { - header: company.name, - value: `Level ${company.level}` - }, - path: company.path, - color: LEVELS_COLORS[i] - })); - // Remove special characters from the key to be able to use d3-select as it uses querySelector - const cleanKey = title.replace(/[^a-zA-Z0-9\-_:.]/g, ''); - const uniqueKey = `${cleanKey}-${el.length}-${i}`; - - return ( -
- {ForceLayoutBubbleChart(companiesBubbles, uniqueKey)} +
+
+
+ No. of companies
- ); - })} - +
+ Bubble size description +
+ {COMPANIES_MARKET_CAP_GROUPS.map( + (companySize, i) => ( + + {companySize} + + ) + )} +
+
+
+ {levelsSignature.map((el, i) => ( +
+
+
{`Level ${el}`} +
+
+ {LEVELS_SUBTITLES[el]} +
+
+ { i > 0 + && + + } +
+ ))} + {parsedData.map((dataRow) => ( + + ))} +
+
); }; diff --git a/app/javascript/components/tpi/charts/bubble/CompaniesAccordion.js b/app/javascript/components/tpi/charts/bubble/CompaniesAccordion.js index fa38541bc..0f2222934 100644 --- a/app/javascript/components/tpi/charts/bubble/CompaniesAccordion.js +++ b/app/javascript/components/tpi/charts/bubble/CompaniesAccordion.js @@ -4,6 +4,7 @@ import Select, { components } from 'react-select'; import chevronDownIconBlack from 'images/icon_chevron_dark/chevron_down_black-1.svg'; import chevronUpIconBlack from 'images/icon_chevron_dark/chevron-up.svg'; +import hoverIcon from 'images/icons/hover-cursor.svg'; const LEVELS_SUBTITLES = { 0: 'Unaware', @@ -75,6 +76,12 @@ const CompaniesAccordion = ({ levels, by_sector }) => { return (
+
+ Hover icon +

+ Click to see the detailed list of companies for each sector. +

+
{by_sector && (