From 23a5aaaeba2aea79baa7dff2da3c77569fbddd83 Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 21 Aug 2024 16:44:35 +0200 Subject: [PATCH 01/20] Enable `pgcrypto` PSQL extension --- ...0240819123818_enable_extension_pgcrypto.rb | 5 +++++ db/structure.sql | 20 +++++++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20240819123818_enable_extension_pgcrypto.rb diff --git a/db/migrate/20240819123818_enable_extension_pgcrypto.rb b/db/migrate/20240819123818_enable_extension_pgcrypto.rb new file mode 100644 index 000000000..5c1efa5ad --- /dev/null +++ b/db/migrate/20240819123818_enable_extension_pgcrypto.rb @@ -0,0 +1,5 @@ +class EnableExtensionPgcrypto < ActiveRecord::Migration[7.1] + def change + enable_extension("pgcrypto") + end +end diff --git a/db/structure.sql b/db/structure.sql index 0fae4c66c..243fc783d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -10,24 +10,31 @@ SET client_min_messages = warning; SET row_security = off; -- --- Name: public; Type: SCHEMA; Schema: -; Owner: - +-- Name: hstore; Type: EXTENSION; Schema: -; Owner: - -- --- *not* creating schema, since initdb creates it +CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA public; -- --- Name: hstore; Type: EXTENSION; Schema: -; Owner: - +-- Name: EXTENSION hstore; Type: COMMENT; Schema: -; Owner: - -- -CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA public; +COMMENT ON EXTENSION hstore IS 'data type for storing sets of (key, value) pairs'; -- --- Name: EXTENSION hstore; Type: COMMENT; Schema: -; Owner: - +-- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - -- -COMMENT ON EXTENSION hstore IS 'data type for storing sets of (key, value) pairs'; +CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; + + +-- +-- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; SET default_tablespace = ''; @@ -3654,6 +3661,7 @@ ALTER TABLE ONLY public.support_letters SET search_path TO "$user", public; INSERT INTO "schema_migrations" (version) VALUES +('20240819123818'), ('20240216144428'), ('20211214111643'), ('20211104074415'), From 29f289676c98b57fab44bff033da6281fb1d8c85 Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 21 Aug 2024 16:08:04 +0200 Subject: [PATCH 02/20] Add `ProtectedFile` model w/ migrations --- .../admin/protected_files_controller.rb | 3 ++ .../concerns/protected_file_mixin.rb | 13 +++++ app/models/admin.rb | 1 + app/models/assessor.rb | 2 + app/models/group_leader.rb | 2 + app/models/lieutenant.rb | 3 +- app/models/protected_file.rb | 48 +++++++++++++++++++ app/models/user.rb | 2 + app/uploaders/protected_file_uploader.rb | 5 ++ config/routes.rb | 2 + .../20240819143818_create_protected_files.rb | 11 +++++ db/structure.sql | 30 ++++++++++++ 12 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 app/controllers/admin/protected_files_controller.rb create mode 100644 app/controllers/concerns/protected_file_mixin.rb create mode 100644 app/models/protected_file.rb create mode 100644 app/uploaders/protected_file_uploader.rb create mode 100644 db/migrate/20240819143818_create_protected_files.rb diff --git a/app/controllers/admin/protected_files_controller.rb b/app/controllers/admin/protected_files_controller.rb new file mode 100644 index 000000000..e07e900ba --- /dev/null +++ b/app/controllers/admin/protected_files_controller.rb @@ -0,0 +1,3 @@ +class Admin::ProtectedFilesController < Admin::BaseController + include ProtectedFileMixin +end \ No newline at end of file diff --git a/app/controllers/concerns/protected_file_mixin.rb b/app/controllers/concerns/protected_file_mixin.rb new file mode 100644 index 000000000..bedce6075 --- /dev/null +++ b/app/controllers/concerns/protected_file_mixin.rb @@ -0,0 +1,13 @@ +module ProtectedFileMixin + extend ActiveSupport::Concern + + def self.included(base) + base.skip_after_action :verify_authorized + end + + def show + file = current_subject.protected_files.find(params[:id]) + file.mark_as_downloaded! + redirect_to file.file.url, allow_other_host: true + end +end diff --git a/app/models/admin.rb b/app/models/admin.rb index 532f6861e..fb2f24096 100644 --- a/app/models/admin.rb +++ b/app/models/admin.rb @@ -12,6 +12,7 @@ class Admin < ApplicationRecord validates :first_name, :last_name, presence: true has_many :form_answer_attachments, as: :attachable + has_many :protected_files, as: :entity, dependent: :destroy pg_search_scope :basic_search, against: [ diff --git a/app/models/assessor.rb b/app/models/assessor.rb index 4a69cf018..69cfed63e 100644 --- a/app/models/assessor.rb +++ b/app/models/assessor.rb @@ -22,6 +22,8 @@ class Assessor < ApplicationRecord foreign_key: :sub_group, primary_key: :sub_group + has_many :protected_files, as: :entity, dependent: :destroy + pg_search_scope :basic_search, against: [ :first_name, diff --git a/app/models/group_leader.rb b/app/models/group_leader.rb index 874cb95d1..92081be88 100644 --- a/app/models/group_leader.rb +++ b/app/models/group_leader.rb @@ -23,6 +23,8 @@ class GroupLeader < ApplicationRecord belongs_to :form_answer, optional: true + has_many :protected_files, as: :entity, dependent: :destroy + scope :by_email, -> { order(:email) } scope :confirmed, -> { where.not(confirmed_at: nil) } diff --git a/app/models/lieutenant.rb b/app/models/lieutenant.rb index 3f56165ef..a335f7ac4 100644 --- a/app/models/lieutenant.rb +++ b/app/models/lieutenant.rb @@ -12,6 +12,8 @@ class Lieutenant < ApplicationRecord belongs_to :ceremonial_county + has_many :protected_files, as: :entity, dependent: :destroy + validates :first_name, :last_name, :role, :ceremonial_county, presence: true enumerize :role, in: %w(regular advanced) @@ -30,7 +32,6 @@ class Lieutenant < ApplicationRecord } } - def assigned_nominations nominations_scope end diff --git a/app/models/protected_file.rb b/app/models/protected_file.rb new file mode 100644 index 000000000..31403a0cb --- /dev/null +++ b/app/models/protected_file.rb @@ -0,0 +1,48 @@ +class ProtectedFile < ApplicationRecord + mount_uploader :file, ProtectedFileUploader + + belongs_to :entity, polymorphic: true + + validates :entity_type, inclusion: { in: %w(Admin Assessor GroupLeader Lieutenant User) }, presence: true + validates :entity_id, presence: true + + after_create :cleanup_tempfile + + def mark_as_downloaded! + touch(:last_downloaded_at) + end + + def self.build_from_raw_data(data, filename, **attrs) + @file = Tempfile.new([get_basename(filename), get_extname(filename)]) + @file.write(data) + @file.close + + self.build( + file: @file, + **attrs + ) + end + + def self.create_from_raw_data(data, filename, **attrs) + build_from_raw_data(data, filename, **attrs).tap do |record| + record.save + end + end + + private + + def cleanup_tempfile + @file.unlink if @file + end + + def self.get_basename(filename) + File.basename(filename, get_extname(filename)) + end + private_class_method :get_basename + + + def self.get_extname(filename) + File.extname(filename) + end + private_class_method :get_extname +end diff --git a/app/models/user.rb b/app/models/user.rb index 5c50b2a24..c0a19b002 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -44,6 +44,8 @@ class User < ApplicationRecord belongs_to :account has_many :form_answer_attachments, as: :attachable has_many :support_letter_attachments, dependent: :destroy + + has_many :protected_files, as: :entity, dependent: :destroy end begin :scopes diff --git a/app/uploaders/protected_file_uploader.rb b/app/uploaders/protected_file_uploader.rb new file mode 100644 index 000000000..d58837c3b --- /dev/null +++ b/app/uploaders/protected_file_uploader.rb @@ -0,0 +1,5 @@ +class ProtectedFileUploader < FileUploader + def extension_allowlist + super + %w(csv) + end +end diff --git a/config/routes.rb b/config/routes.rb index e9bb73c10..6b2bc743c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -268,6 +268,8 @@ patch 'update_password' end end + + resources :protected_files, path: "/files", only: :show end namespace :lieutenant do diff --git a/db/migrate/20240819143818_create_protected_files.rb b/db/migrate/20240819143818_create_protected_files.rb new file mode 100644 index 000000000..df5ae61a9 --- /dev/null +++ b/db/migrate/20240819143818_create_protected_files.rb @@ -0,0 +1,11 @@ +class CreateProtectedFiles < ActiveRecord::Migration[7.1] + def change + create_table :protected_files, id: :uuid do |t| + t.belongs_to :entity, polymorphic: true + t.string :file + + t.datetime :created_at, precision: 6, null: false + t.datetime :last_downloaded_at, precision: 6 + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 243fc783d..a5798ab4c 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1056,6 +1056,20 @@ CREATE SEQUENCE public.previous_wins_id_seq ALTER SEQUENCE public.previous_wins_id_seq OWNED BY public.previous_wins.id; +-- +-- Name: protected_files; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.protected_files ( + id uuid DEFAULT gen_random_uuid() NOT NULL, + entity_type character varying, + entity_id bigint, + file character varying, + created_at timestamp(6) without time zone NOT NULL, + last_downloaded_at timestamp(6) without time zone +); + + -- -- Name: scans; Type: TABLE; Schema: public; Owner: - -- @@ -3059,6 +3073,14 @@ ALTER TABLE ONLY public.previous_wins ADD CONSTRAINT previous_wins_pkey PRIMARY KEY (id); +-- +-- Name: protected_files protected_files_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.protected_files + ADD CONSTRAINT protected_files_pkey PRIMARY KEY (id); + + -- -- Name: scans scans_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3439,6 +3461,13 @@ CREATE INDEX index_palace_attendees_on_palace_invite_id ON public.palace_attende CREATE INDEX index_palace_invites_on_form_answer_id ON public.palace_invites USING btree (form_answer_id); +-- +-- Name: index_protected_files_on_entity; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_protected_files_on_entity ON public.protected_files USING btree (entity_type, entity_id); + + -- -- Name: index_scans_on_uuid; Type: INDEX; Schema: public; Owner: - -- @@ -3661,6 +3690,7 @@ ALTER TABLE ONLY public.support_letters SET search_path TO "$user", public; INSERT INTO "schema_migrations" (version) VALUES +('20240819143818'), ('20240819123818'), ('20240216144428'), ('20211214111643'), From 3c753c2e8f2469c55e4b8953de239dcac4c325cd Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 21 Aug 2024 16:10:31 +0200 Subject: [PATCH 03/20] Add `NominationStatsSearch` service to get grouped data for report --- app/search/nomination_stats_search.rb | 84 +++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/search/nomination_stats_search.rb diff --git a/app/search/nomination_stats_search.rb b/app/search/nomination_stats_search.rb new file mode 100644 index 000000000..979d4b2c4 --- /dev/null +++ b/app/search/nomination_stats_search.rb @@ -0,0 +1,84 @@ +class NominationStatsSearch < Search + TRACKED_STATES = %i[ + submitted + admin_eligible + admin_not_eligible_nominator + admin_not_eligible_group + withdrawn + local_assessment_not_recommended + local_assessment_recommended + not_recommended + shortlisted + awarded + ] + + FETCH_QUERY = %Q{ + ceremonial_counties.name AS ceremonial_county_name, + #{TRACKED_STATES.map { |s| "COUNT(CASE WHEN form_answers.state = '#{s}' THEN 1 END) AS #{s}_count" }.join(',')}, + COUNT(CASE WHEN form_answers.state IN (#{TRACKED_STATES.map { |s| "'#{s}'" }.join(',')}) THEN 1 END) AS total_count + }.squish.freeze + + def self.default_search + { + sort: "ceremonial_county_name", + search_filter: { + year: "all_years", + assigned_ceremonial_county: ceremonial_county_options.map(&:second) + } + } + end + + def results + super + + @search_results = @search_results + .select(FETCH_QUERY) + .joins(:ceremonial_county) + .group("ceremonial_counties.name") + + @search_results + end + + def filter_by_year(scoped_results, value) + if value == "all_years" + scoped_results + else + (year = AwardYear.find_by(year: value)) ? scoped_results.where(award_year: year) : scoped_results.none + end + end + + def filter_by_assigned_ceremonial_county(scoped_results, value) + value = value.map do |v| + v == "not_assigned" ? nil : v + end + scoped_results.where(ceremonial_county_id: value) + end + + def sort_by_ceremonial_county_name(scoped_results, desc = false) + scoped_results.order("ceremonial_counties.name #{sort_order(desc)}") + end + + class << self + def ceremonial_county_options + collection_mapping(county_options) + end + + private + + def collection_mapping(options) + options.map do |k, v| + [v[:label], k] + end + end + + def county_options + options = Hash[not_assigned: { label: "Not assigned" }] + + CeremonialCounty.ordered.collect do |county| + options[county.id] = { label: county.name } + end + + options + end + end +end From d7b5a60e175ac97480f583cf00f2ca551a941bbe Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 21 Aug 2024 16:13:08 +0200 Subject: [PATCH 04/20] Views & policies for nomination statistics --- app/assets/stylesheets/admin/tables.scss | 37 ++++++ .../statistics/nominations_controller.rb | 96 ++++++++++++++++ app/policies/statistics_policy.rb | 9 ++ .../statistics/nominations/_list.html.slim | 106 ++++++++++++++++++ .../statistics/nominations/index.html.slim | 44 ++++++++ app/views/layouts/navbar/_admin.html.slim | 4 + config/routes.rb | 4 + 7 files changed, 300 insertions(+) create mode 100644 app/controllers/admin/statistics/nominations_controller.rb create mode 100644 app/policies/statistics_policy.rb create mode 100644 app/views/admin/statistics/nominations/_list.html.slim create mode 100644 app/views/admin/statistics/nominations/index.html.slim diff --git a/app/assets/stylesheets/admin/tables.scss b/app/assets/stylesheets/admin/tables.scss index 8d8b975f9..93288ce5f 100644 --- a/app/assets/stylesheets/admin/tables.scss +++ b/app/assets/stylesheets/admin/tables.scss @@ -5,3 +5,40 @@ [role="region"][aria-labelledby][tabindex]:focus { outline: .1em solid rgba(0,0,0,.1); } + +.kavs-table { + border-top: 1px solid #b1b4b6; + border-bottom: 1px solid #b1b4b6; + width: max-content; + + thead { + background-color: #f2f2f2; + + .govuk-table__header:last-child, + .govuk-table__cell:last-child, + .govuk-table__footer:last-child { + padding-right: 10px; + } + } + + tfoot { + &.govuk-table__footer { + background-color: #f2f2f2; + font-weight: 700; + text-align: left; + } + } +} + +.kavs-table__header--dense, +.kavs-table__cell--dense, +.kavs-table__footer--dense { + font-size: 1rem; + padding: 10px +} + +.kavs-table__header--border-right, +.kavs-table__cell--border-right, +.kavs-table__footer--border-right { + border-right: 1px solid #b1b4b6; +} \ No newline at end of file diff --git a/app/controllers/admin/statistics/nominations_controller.rb b/app/controllers/admin/statistics/nominations_controller.rb new file mode 100644 index 000000000..667dccd21 --- /dev/null +++ b/app/controllers/admin/statistics/nominations_controller.rb @@ -0,0 +1,96 @@ +class Admin::Statistics::NominationsController < Admin::BaseController + def index + authorize :statistics, :index? + + @search = NominationStatsSearch.new(FormAnswer.all).search(permitted_params) + end + + def create + authorize :statistics, :send? + + @search = NominationStatsSearch.new(FormAnswer.all).search(permitted_params) + + redirect_to admin_statistics_nominations_path(search: permitted_params), success: "CSV with nomination statistics has been sent to #{current_admin.email}." + end + + private + + def permitted_params + params.fetch(:search, NominationStatsSearch.default_search).permit! + end + + def generate_csv(data) + CSV.generate(encoding: "UTF-8", force_quotes: true) do |csv| + csv << csv_mapping.map { |m| m[:label] } + data.each do |row| + csv << csv_mapping.map do |m| + func = m[:method] + row[func] + end + end + + csv << csv_mapping.map do |m| + func = m[:method] + + if func == :ceremonial_county_name + "Total" + else + data.sum(&func) + end + end + end + end + + def csv_mapping + [ + { + label: "Lieutenancy", + method: :ceremonial_county_name + }, + { + label: "Nominations submitted", + method: :submitted_count + }, + { + label: "Eligiblity - Admin eligible", + method: :admin_eligible_count + }, + { + label: "Eligiblity - Not eligible nominator", + method: :admin_not_eligible_nominator_count + }, + { + label: "Eligiblity - Not eligible group", + method: :admin_not_eligible_group_count + }, + { + label: "Eligiblity - Withdrawn", + method: :withdrawn_count + }, + { + label: "Local Assessment - Not recommended", + method: :local_assessment_not_recommended_count + }, + { + label: "Local Assessment - Recommended", + method: :local_assessment_recommended_count + }, + { + label: "National Assessment - Not recommended", + method: :not_recommended_count + }, + { + label: "National Assessment - Recommended", + method: :shortlisted_count + }, + { + label: "Royal Approval - Awarded", + method: :awarded_count + }, + { + label: "Total", + method: :total_count + } + ] + end +end diff --git a/app/policies/statistics_policy.rb b/app/policies/statistics_policy.rb new file mode 100644 index 000000000..0a40f801f --- /dev/null +++ b/app/policies/statistics_policy.rb @@ -0,0 +1,9 @@ +class StatisticsPolicy < ApplicationPolicy + def index? + admin? + end + + def send? + admin? + end +end diff --git a/app/views/admin/statistics/nominations/_list.html.slim b/app/views/admin/statistics/nominations/_list.html.slim new file mode 100644 index 000000000..b68d0e0a7 --- /dev/null +++ b/app/views/admin/statistics/nominations/_list.html.slim @@ -0,0 +1,106 @@ +ruby: + results = object.results + +div role="region" aria-labelledby="table-list-nomination-statistics-caption" tabindex="0" + table.govuk-table.kavs-table#table-list-nomination-statistics + caption.govuk-visually-hidden#table-list-nomination-statistics-caption + | nomination statistics report + colgroup + col style="width: 135px;" + col style="width: 110px;" + col style="width: 55px;" + col style="width: 90px;" + col style="width: 90px;" + col style="width: auto;" + col style="width: 110px;" + col style="width: auto;" + col style="width: 110px;" + col style="width: auto;" + col style="width: 75px;" + col style="width: auto;" + thead.govuk-table__head + tr.govuk-table__row + th.sortable.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" rowspan="2" + = sort_link f, 'Lieutenancy', object, :ceremonial_county_name + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" rowspan="2" + | Submitted nominations + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" colspan="4" + | Eligibility + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" colspan="2" + | Local Assessment + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" colspan="2" + | National Assessment + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" rowspan="2" + | Royal Approval - Awarded + th.govuk-table__header.kavs-table__header--dense scope="col" rowspan="2" + | Total + tr.govuk-table__row + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" + | Admin eligible + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" + | Not eligible nominator + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" + | Not eligible group + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" + | Withdrawn + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" + | Not recommended + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" + | Recommended + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" + | Not recommended + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" + | Recommended + tbody.govuk-table__body + - results.each do |row| + tr.govuk-table__row + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="row" + = row[:ceremonial_county_name] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:submitted_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:admin_eligible_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:admin_not_eligible_nominator_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:admin_not_eligible_group_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:withdrawn_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:local_assessment_not_recommended_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:local_assessment_recommended_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:not_recommended_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:shortlisted_count] + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = row[:awarded_count] + td.govuk-table__cell.kavs-table__cell--dense + = row[:total_count] + tfoot.govuk-table__footer + tr.govuk-table__row + th.kavs-table__footer--dense.kavs-table__footer--border-right scope="row" + | Total + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:submitted_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:admin_eligible_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:admin_not_eligible_nominator_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:admin_not_eligible_group_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:withdrawn_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:local_assessment_not_recommended_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:local_assessment_recommended_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:not_recommended_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:shortlisted_count) + td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right + = results.sum(&:awarded_count) + td.govuk-table__cell.kavs-table__cell--dense + = results.sum(&:total_count) diff --git a/app/views/admin/statistics/nominations/index.html.slim b/app/views/admin/statistics/nominations/index.html.slim new file mode 100644 index 000000000..2a9c77720 --- /dev/null +++ b/app/views/admin/statistics/nominations/index.html.slim @@ -0,0 +1,44 @@ +- title "Nomination statistics" + +h1.govuk-heading-xl + | Nomination statistics + += simple_form_for @search, url: "#", method: :get, as: :search, html: { id: "nomination-statistics-form", class: "search-form" } do |f| + = f.simple_fields_for [:filters, @search.filters] do |g| + div[class="govuk-grid-row"] + div[class="govuk-grid-column-one-quarter"] + div[class="govuk-form-group" aria-label="Award year"] + label[class="govuk-label" for="award-year-select"] + ' Award year + = g.select :year, award_years_collection, {}, { id: "award-year-select", class: "govuk-select custom-select", style: "height: 40px;", aria: { label: "award year select" } } + + = render "shared/form_answers/filters/assigned_lieutenancy_filter", g: g, options: NominationStatsSearch.ceremonial_county_options + + div[class="govuk-button-group"] + = f.submit "Apply filters", + class: "govuk-button", + id: "apply-nomination-filters" + + = link_to "Remove filters", + admin_statistics_nominations_path, + class: "govuk-button govuk-button--secondary", + role: "button" + + div[class="govuk-button-group"] + = link_to "Receive CSV with nomination statistics via email", + admin_statistics_nominations_path(params.to_unsafe_h), + class: "govuk-link", + method: :post + + = render partial: "list", locals: { f: f, object: @search } + +scss: + .autocomplete__input { + min-height: 2.5rem; + } + + @media (max-width: 991px) { + .autocomplete__input { + min-height: 2rem; + } + } diff --git a/app/views/layouts/navbar/_admin.html.slim b/app/views/layouts/navbar/_admin.html.slim index ffbd9c17c..45575f594 100644 --- a/app/views/layouts/navbar/_admin.html.slim +++ b/app/views/layouts/navbar/_admin.html.slim @@ -5,6 +5,10 @@ li.govuk-header__navigation-item class=('active' if controller_name == 'form_answers') = link_to "Nominations", admin_form_answers_path, class: 'govuk-header__link' +- if policy(:statistics).index? + li.govuk-header__navigation-item class=('active' if controller_name == 'nominations') + = link_to "Statistics", admin_statistics_nominations_path, class: 'govuk-header__link' + - if policy(:user).index? li.govuk-header__navigation-item class=('active' if %w[users admins assessors].include?(controller_name)) = link_to "Users", admin_users_path, class: 'govuk-header__link' diff --git a/config/routes.rb b/config/routes.rb index 6b2bc743c..0eec6a3cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -269,6 +269,10 @@ end end + namespace :statistics do + resources :nominations, only: [:index, :create], path_names: { create: :send } + end + resources :protected_files, path: "/files", only: :show end From 316aa5c243422ce958354ecbf22910baad08aecd Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Wed, 21 Aug 2024 16:32:44 +0200 Subject: [PATCH 05/20] Add mailer to send requested CSV file --- .../admin/statistics/nominations_controller.rb | 5 +++++ .../admin/statistics/nomination_mailer.rb | 12 ++++++++++++ .../nomination_mailer/notify.html.slim | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 app/mailers/admin/statistics/nomination_mailer.rb create mode 100644 app/views/admin/statistics/nomination_mailer/notify.html.slim diff --git a/app/controllers/admin/statistics/nominations_controller.rb b/app/controllers/admin/statistics/nominations_controller.rb index 667dccd21..fc0e67529 100644 --- a/app/controllers/admin/statistics/nominations_controller.rb +++ b/app/controllers/admin/statistics/nominations_controller.rb @@ -9,6 +9,11 @@ def create authorize :statistics, :send? @search = NominationStatsSearch.new(FormAnswer.all).search(permitted_params) + + data = generate_csv(@search.results) + file = current_admin.protected_files.create_from_raw_data(data, "nomination-statistics-export.csv") + + Admin::Statistics::NominationMailer.notify(current_admin.id, file.id).deliver_now redirect_to admin_statistics_nominations_path(search: permitted_params), success: "CSV with nomination statistics has been sent to #{current_admin.email}." end diff --git a/app/mailers/admin/statistics/nomination_mailer.rb b/app/mailers/admin/statistics/nomination_mailer.rb new file mode 100644 index 000000000..0477cf90c --- /dev/null +++ b/app/mailers/admin/statistics/nomination_mailer.rb @@ -0,0 +1,12 @@ +class Admin::Statistics::NominationMailer < ApplicationMailer + layout "mailer" + + def notify(identifier, file_identifier) + @admin = Admin.find(identifier) + @file = @admin.protected_files.find(file_identifier) + + subject = "Nomination statistics export - King's Award for Voluntary Service" + + view_mail ENV["GOV_UK_NOTIFY_API_TEMPLATE_ID"], to: @admin.email, subject: subject_with_env_prefix(subject) + end +end diff --git a/app/views/admin/statistics/nomination_mailer/notify.html.slim b/app/views/admin/statistics/nomination_mailer/notify.html.slim new file mode 100644 index 000000000..08c162ba1 --- /dev/null +++ b/app/views/admin/statistics/nomination_mailer/notify.html.slim @@ -0,0 +1,18 @@ +p.govuk-body + = "Dear #{@admin.first_name}," + +p.govuk-body + ' You requested an export of nomination statistics. + +p.govuk-body + ' Please find this report on the link below: + +p.govuk-body + = link_to admin_protected_file_url(@file), admin_protected_file_url(@file) + +p.govuk-body + ' Kind Regards, + + br + + ' The King's Awards Office From 410a09736adf6154f3e533b12e6d3318403a7fab Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Thu, 22 Aug 2024 08:43:15 +0100 Subject: [PATCH 06/20] Modify support letter fields so each upload section is a new question --- app/assets/stylesheets/frontend/forms.scss | 5 ++ .../frontend/views/award_form.scss | 6 ++ app/views/qae_form/_question_ref.html.slim | 86 +++++++++++++++++++ .../qae_form/_supporter_fields.html.slim | 33 +++++-- .../qae_form/_supporters_question.html.slim | 1 - ...tachment_with_virus_check_status.html.slim | 2 +- .../support_letters/_attachment.html.slim | 8 +- forms/award_years/v2025/qavs/qavs_step3.rb | 37 ++++---- 8 files changed, 147 insertions(+), 31 deletions(-) create mode 100644 app/views/qae_form/_question_ref.html.slim diff --git a/app/assets/stylesheets/frontend/forms.scss b/app/assets/stylesheets/frontend/forms.scss index d17d03335..46a4363a9 100644 --- a/app/assets/stylesheets/frontend/forms.scss +++ b/app/assets/stylesheets/frontend/forms.scss @@ -660,6 +660,11 @@ input[type="file"] { padding: 20px !important; border: 2px solid #000 !important; + &.borderless { + border: 0 !important; + padding: 0 !important; + } + .lte-ie7 & { display: inline; width: 100%; diff --git a/app/assets/stylesheets/frontend/views/award_form.scss b/app/assets/stylesheets/frontend/views/award_form.scss index 0b32cc711..97163db5f 100644 --- a/app/assets/stylesheets/frontend/views/award_form.scss +++ b/app/assets/stylesheets/frontend/views/award_form.scss @@ -162,6 +162,12 @@ label > .visible-read-only, display: block; } +.support-letter-attachment-container { + padding: 15px; + display: flex; + background: $govuk-light-grey; +} + .view-only, .read-only { label { diff --git a/app/views/qae_form/_question_ref.html.slim b/app/views/qae_form/_question_ref.html.slim new file mode 100644 index 000000000..d916491bc --- /dev/null +++ b/app/views/qae_form/_question_ref.html.slim @@ -0,0 +1,86 @@ +- if question.header + h2.govuk-heading-l + = question.header +- if question.header_context + == question.header_context +fieldset class=question.fieldset_classes data=question.fieldset_data_hash + = condition_divs question do + .govuk-form-group class=(" govuk-form-group--error" if @form_answer.validator_errors && @form_answer.validator_errors[question.hash_key]) + - ref = question.ref ? question.ref : question.sub_ref + - if question.title != "" || question.show_ref_always.present? + - if question.label_as_legend? + legend.govuk-label aria-label="#{ref.to_s.gsub(' ', '-')} #{question.title.blank? ? '' : ':' + question.title}" + - if question.ref || question.sub_ref + span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" + span.todo + = ref.to_s + - unless question.title.blank? + - font_size = question.delegate_obj.is_a?(QaeFormBuilder::HeaderQuestion) ? "govuk-!-font-size-36" : "govuk-!-font-size-24" + span class="govuk-body #{ font_size } govuk-!-font-weight-bold govuk-!-display-block" + == question.title + + = render "qae_form/question_sub_title", question: question + + - else + legend.govuk-label aria-label="#{ref.to_s.gsub(' ', '-')} #{question.title.blank? ? '' : ':' + question.title}" + label.govuk-label for="q_#{question.key}" id="q_#{question.key}_label" aria-label="#{ref.to_s.gsub(' ', '-')}: #{question.title}" + - if question.ref || question.sub_ref + span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" + span.todo + = ref.to_s + - unless question.title.blank? + span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" + == question.title + + = render "qae_form/question_sub_title", question: question + + - else + - if question.ref || question.sub_ref + .if-js-hide + label.govuk-label for="q_#{question.key}" aria-label="#{ref.to_s.gsub(' ', '-')}: #{question.title}" + span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" + span.visuallyhidden + = ref.to_s + span.todo + = ref.to_s + + - if question.delegate_obj.is_a?(QaeFormBuilder::HeaderQuestion) + - if question.ref || question.sub_ref + span.question-context.question-debug.govuk-hint + = "Please note #{(question.ref || question.sub_ref).delete(" ")} is just a heading for the following sub-questions." + - if question.context + == question.context + - for help in question.help + == help.text + - else + - if question.context + span.question-context.question-debug.govuk-hint + == question.context + - if question.online_context + span.question-context.question-debug.govuk-hint + == question.online_context + - for help in question.help + span.question-context.question-debug.govuk-hint + == help.text + + - question.hint.each_with_index do |help, index| + details.govuk-details data-module='govuk-details' + - if help.title.present? + summary.govuk-details__summary + span.govuk-details__summary-text + = help.title.html_safe + .govuk-details__text + == help.text + + span.govuk-error-message + - if @form_answer.validator_errors && @form_answer.validator_errors[question.hash_key].present? && @form_answer.validator_errors[question.hash_key].is_a?(String) + span.govuk-visually-hidden + | Error: + =< @form_answer.validator_errors[question.hash_key] + - unless question.form_hint.blank? + span.govuk-hint = question.form_hint + = render partial: "qae_form/#{question.delegate_obj.class.name.demodulize.underscore}", object: question, as: 'question', locals: {answers: answers, attachments: attachments} + + / Conditional hints + - if question.can_have_conditional_hints? + = render "qae_form/conditional_hints/list", question: question diff --git a/app/views/qae_form/_supporter_fields.html.slim b/app/views/qae_form/_supporter_fields.html.slim index aee0fad3b..15fab752f 100644 --- a/app/views/qae_form/_supporter_fields.html.slim +++ b/app/views/qae_form/_supporter_fields.html.slim @@ -1,37 +1,54 @@ +- first_or_second = index == 0 ? "first" : "second" - persisted = supporter["support_letter_id"].present? || supporter["supporter_id"].present? - create_url = users_form_answer_support_letters_url(@form_answer) - update_url = users_form_answer_support_letter_path(@form_answer, supporter["support_letter_id"]) if persisted -li[class=class_names("js-add-example", "js-support-letter-received" => persisted) data-create-url=create_url data-update-url=update_url] +li.borderless[class=class_names("js-add-example", "js-support-letter-received" => persisted) data-create-url=create_url data-update-url=update_url] + legend.govuk-label + span class="steps" + span.todo C #{index + 1} label[class="govuk-label"] - span[class="govuk-body govuk-!-font-size-20 govuk-!-font-weight-bold govuk-!-display-block"] - = "Letter of Support #{index + 1}" + span[class="govuk-body govuk-!-font-size-36 govuk-!-font-weight-bold govuk-!-display-block"] + = "The #{first_or_second} letter of support" input.js-support-entry-id type="hidden" name="form[#{question.key}][#{index}][support_letter_id]" value=supporter["support_letter_id"] *possible_read_only_ops(question.step.opts[:id]) .js-system-tag data-new-hidden-input-name="form[#{question.key}][#{index}][support_letter_id]" + legend.govuk-label aria-label="C #{index + 1}.1: Name" + span class="steps" + span.todo C #{index + 1}.1 + span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" + ' Name of the person who wrote the #{first_or_second} letter of support .govuk-form-group label.govuk-label for="form[#{question.key}][#{index}][first_name]" ' First Name: span.govuk-error-message input.js-support-letter-field.js-support-letter-first-name.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][first_name]" id="form[#{question.key}][#{index}][first_name]" type="text" value=supporter["first_name"] *possible_read_only_ops(question.step.opts[:id]) - .govuk-form-group label.govuk-label for="form[#{question.key}][#{index}][last_name]" ' Surname: span.govuk-error-message input.js-support-letter-field.js-support-letter-last-name.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][last_name]" id="form[#{question.key}][#{index}][last_name]" type="text" value=supporter["last_name"] *possible_read_only_ops(question.step.opts[:id]) + legend.govuk-label aria-label="C #{index + 1}.2: Relationship to Group" + span class="steps" + span.todo C #{index + 1}.2 .govuk-form-group - label.govuk-label for="form[#{question.key}][#{index}][relationship_to_nominee]" - ' Relationship to Group: + label class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" for="form[#{question.key}][#{index}][relationship_to_nominee]" + ' The person's who wrote the #{first_or_second} letter of support relationship to the group + span.question-context.question-debug.govuk-hint + ' For example, a beneficiary of the group, local resident or member of a partner charity. span.govuk-error-message input.js-support-letter-field.js-support-letter-relationship-to-nominee.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][relationship_to_nominee]" id="form[#{question.key}][#{index}][relationship_to_nominee]" type="text" value=supporter["relationship_to_nominee"] *possible_read_only_ops(question.step.opts[:id]) + legend.govuk-label aria-label="C #{index + 1}.3: Relationship to Group" + span class="steps" + span.todo C #{index + 1}.3 .govuk-form-group label.govuk-label for="form[#{question.key}][#{index}][letter_of_support]" - = "Upload Letter of Support #{index + 1}" + ' Upload the #{first_or_second} letter of support + span.question-context.question-debug.govuk-hint + ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will appear, allowing you to select the correct file. span.govuk-error-message - input class="js-support-letter-field js-trigger-autosave js-support-letter-attachment govuk-input medium" name="form[#{question.key}][#{index}][letter_of_support]" id="form[#{question.key}][#{index}][letter_of_support]" type='file' *possible_read_only_ops(question.step.opts[:id]) - if supporter['letter_of_support'].present? = render "support_letters/attachment", question: question, index: index, attachment_id: supporter['letter_of_support'] diff --git a/app/views/qae_form/_supporters_question.html.slim b/app/views/qae_form/_supporters_question.html.slim index 81ae6406f..95abd4c3a 100644 --- a/app/views/qae_form/_supporters_question.html.slim +++ b/app/views/qae_form/_supporters_question.html.slim @@ -2,4 +2,3 @@ input name="form[#{question.key}][array]" value="true" type="hidden" *possible_r ul.list-add.supporters-list data-need-to-clear-example=true data-add-limit=question.limit data-attachments-url=(users_form_answer_support_letter_attachments_path(@form_answer)) data-example-has-file-field=true - question.entities.each_with_index do |supporter, index| = render 'qae_form/supporter_fields', question: question, supporter: supporter, index: index - diff --git a/app/views/shared/_attachment_with_virus_check_status.html.slim b/app/views/shared/_attachment_with_virus_check_status.html.slim index bef7d0bb7..18420519e 100644 --- a/app/views/shared/_attachment_with_virus_check_status.html.slim +++ b/app/views/shared/_attachment_with_virus_check_status.html.slim @@ -7,7 +7,7 @@ class: 'govuk-link' - elsif scan_results == "scanning" || scan_results == "pending" = item.try(:original_filename) - | (scanning on viruses) + | (File uploaded and is being scanned for viruses. Preview available once the scan is complete.) - elsif scan_results == "infected" = item.try(:original_filename) | has been blocked (virus detected), please remove. diff --git a/app/views/support_letters/_attachment.html.slim b/app/views/support_letters/_attachment.html.slim index 611b93a17..3c2b1dead 100644 --- a/app/views/support_letters/_attachment.html.slim +++ b/app/views/support_letters/_attachment.html.slim @@ -1,5 +1,9 @@ - file = support_letter_attachments[attachment_id.to_i] -p.govuk-body.support-letter-attachment-filename - = render "shared/attachment_with_virus_check_status", item: file, mount_name: :attachment +.support-letter-attachment-container + div + p.govuk-body.support-letter-attachment-filename + = render "shared/attachment_with_virus_check_status", item: file, mount_name: :attachment + button.govuk-link.govuk-button--secondary data-module="govuk-button" type="button" onclick="removeAttachment(this)" + | Remove input.js-support-letter-attachment-id type="hidden" name="form[#{question.key}][#{index}][letter_of_support]" value=attachment_id *possible_read_only_ops diff --git a/forms/award_years/v2025/qavs/qavs_step3.rb b/forms/award_years/v2025/qavs/qavs_step3.rb index d8b99639f..da935217f 100644 --- a/forms/award_years/v2025/qavs/qavs_step3.rb +++ b/forms/award_years/v2025/qavs/qavs_step3.rb @@ -6,36 +6,35 @@ def qavs_step3

Please note your answers are being saved automatically in the background.

) - supporters :supporter_letters_list, "" do - ref "C 1" - context %( + header :supporter_letters_list_key_criteria, "" do + help "About this section", %(

Letters of support are an essential part of your nomination, as they help to clarify and explain the impact of the nominated group's work in the local community. You will need to provide 2 letters of support alongside your nomination.

For more information on what letters can cover, please see the Letters of Support page on our website.

-

Key criteria:

+

Key criteria:

    -
  1. letters must be written by individuals who are familiar with the group's work, for example: a beneficiary, local resident or member of a partner charity
  2. -
  3. letters must not be written by a volunteer, employee, trustee, or anyone involved in the running of the group
  4. -
  5. letters written by the nominator will be ineligible
  6. -
  7. each letter should be no more than 500 words
  8. -
  9. only 2 letters of support can be submitted
  10. +
  11. Letters must be written by individuals who are familiar with the group's work, for example: a beneficiary, local resident or member of a partner charity.
  12. +
  13. Letters must not be written by a volunteer, employee, trustee, or anyone involved in the running of the group.
  14. +
  15. Letters written by the nominator will be ineligible.
  16. +
  17. Each letter should be no more than 500 words.
  18. +
  19. Only 2 letters of support can be submitted.
-

For each letter uploaded below, please note the letter writer's relationship to the group, for example: a beneficiary of the group, local resident or member of a partner charity.

-

Once uploaded, all files are saved automatically. If you make a mistake and upload the wrong letter, please use the same button to upload the correct file as it will automatically replace the previous version.

) pdf_context_with_header_blocks [ [:normal, %(Letters of support are an essential part of your nomination, as they help to clarify and explain the impact of the nominated group's work in the local community. You will need to provide 2 letters of support alongside your nomination.)], [:normal, %(For more information on what letters can cover, please see the Letters of Support page on our website.)], - [:normal, %(Key criteria: + [:bold, %(Key criteria: - 1. letters must be written by individuals who are familiar with the group's work, for example: a beneficiary, local resident or member of a partner charity - 2. letters must not be written by a volunteer, employee, trustee, or anyone involved in the running of the group - 3. letters written by the nominator will be ineligible - 4. each letter should be no more than 500 words - 5. only 2 letters of support can be submitted + 1. Letters must be written by individuals who are familiar with the group's work, for example: a beneficiary, local resident or member of a partner charity. + 2. Letters must not be written by a volunteer, employee, trustee, or anyone involved in the running of the group. + 3. Letters written by the nominator will be ineligible. + 4. Each letter should be no more than 500 words. + 5. Only 2 letters of support can be submitted. )], - [:normal, %(For each letter uploaded below, please note the letter writer's relationship to the group, for example: a beneficiary of the group, local resident or member of a partner charity.)], - [:bold, %(Once uploaded, all files are saved automatically. If you make a mistake and upload the wrong letter, please use the same button to upload the correct file as it will automatically replace the previous version.)] ] + end + + supporters :supporter_letters_list, "" do + ref "C 1" classes "question-support-uploads" limit 2 default 2 From e4377e225a35a4b16bb2a2dd76dc4e5d288c11e1 Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Thu, 22 Aug 2024 08:53:35 +0100 Subject: [PATCH 07/20] Extract question ref code into partial so it only has to be mofified in one place --- .../frontend/views/award_form.scss | 20 +++-- .../assessor/form_answers/_question.html.slim | 14 +-- app/views/qae_form/_question.html.slim | 14 +-- app/views/qae_form/_question_ref.html.slim | 89 +------------------ .../qae_form/_supporter_fields.html.slim | 19 ++-- .../qae_form/_supporters_question.html.slim | 1 + ...tachment_with_virus_check_status.html.slim | 4 +- .../support_letters/_attachment.html.slim | 8 +- 8 files changed, 40 insertions(+), 129 deletions(-) diff --git a/app/assets/stylesheets/frontend/views/award_form.scss b/app/assets/stylesheets/frontend/views/award_form.scss index 97163db5f..e7a27600e 100644 --- a/app/assets/stylesheets/frontend/views/award_form.scss +++ b/app/assets/stylesheets/frontend/views/award_form.scss @@ -158,14 +158,24 @@ label > .visible-read-only, font-weight: normal; } -.support-letter-attachment-filename { - display: block; -} - .support-letter-attachment-container { - padding: 15px; + padding: 15px 15px 0; display: flex; + justify-content: space-between; + align-items: start; background: $govuk-light-grey; + + .flex { + display: flex; + justify-content: space-between; + align-items: start; + } + + .js-remove-attachment{ + color: $govuk-red; + word-break: keep-all; + margin-top: 0 !important; + } } .view-only, diff --git a/app/views/assessor/form_answers/_question.html.slim b/app/views/assessor/form_answers/_question.html.slim index 9078c0863..bb0f616dd 100644 --- a/app/views/assessor/form_answers/_question.html.slim +++ b/app/views/assessor/form_answers/_question.html.slim @@ -9,9 +9,7 @@ fieldset class=question.fieldset_classes data=question.fieldset_data_hash - if question.label_as_legend? legend.govuk-label aria-label="#{ref.to_s.gsub(' ', '-')} #{question.title.blank? ? '' : ':' + question.title}" - if question.ref || question.sub_ref - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.todo - = ref.to_s + = render "qae_form/question_ref", question: question, ref: ref - unless question.title.blank? - font_size = question.delegate_obj.is_a?(QaeFormBuilder::HeaderQuestion) ? "govuk-!-font-size-36" : "govuk-!-font-size-24" span class="govuk-body #{ font_size } govuk-!-font-weight-bold govuk-!-display-block" @@ -23,9 +21,7 @@ fieldset class=question.fieldset_classes data=question.fieldset_data_hash legend.govuk-label aria-label="#{ref.to_s.gsub(' ', '-')} #{question.title.blank? ? '' : ':' + question.title}" label.govuk-label for="q_#{question.key}" id="q_#{question.key}_label" aria-label="#{ref.to_s.gsub(' ', '-')}: #{question.title}" - if question.ref || question.sub_ref - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.todo - = ref.to_s + = render "qae_form/question_ref", question: question, ref: ref - unless question.title.blank? span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" == question.title @@ -36,10 +32,6 @@ fieldset class=question.fieldset_classes data=question.fieldset_data_hash - if question.ref || question.sub_ref .if-js-hide label.govuk-label for="q_#{question.key}" aria-label="#{ref.to_s.gsub(' ', '-')}: #{question.title}" - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.visuallyhidden - = ref.to_s - span.todo - = ref.to_s + = render "qae_form/question_ref", question: question, ref: ref = render "assessor/form_answers/questions/#{question.delegate_obj.class.name.demodulize.underscore}", question: question, answers: answers, attachments: attachments diff --git a/app/views/qae_form/_question.html.slim b/app/views/qae_form/_question.html.slim index d916491bc..dd0e9eb5d 100644 --- a/app/views/qae_form/_question.html.slim +++ b/app/views/qae_form/_question.html.slim @@ -11,9 +11,7 @@ fieldset class=question.fieldset_classes data=question.fieldset_data_hash - if question.label_as_legend? legend.govuk-label aria-label="#{ref.to_s.gsub(' ', '-')} #{question.title.blank? ? '' : ':' + question.title}" - if question.ref || question.sub_ref - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.todo - = ref.to_s + = render "qae_form/question_ref", question: question, ref: ref - unless question.title.blank? - font_size = question.delegate_obj.is_a?(QaeFormBuilder::HeaderQuestion) ? "govuk-!-font-size-36" : "govuk-!-font-size-24" span class="govuk-body #{ font_size } govuk-!-font-weight-bold govuk-!-display-block" @@ -25,9 +23,7 @@ fieldset class=question.fieldset_classes data=question.fieldset_data_hash legend.govuk-label aria-label="#{ref.to_s.gsub(' ', '-')} #{question.title.blank? ? '' : ':' + question.title}" label.govuk-label for="q_#{question.key}" id="q_#{question.key}_label" aria-label="#{ref.to_s.gsub(' ', '-')}: #{question.title}" - if question.ref || question.sub_ref - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.todo - = ref.to_s + = render "qae_form/question_ref", question: question, ref: ref - unless question.title.blank? span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" == question.title @@ -38,11 +34,7 @@ fieldset class=question.fieldset_classes data=question.fieldset_data_hash - if question.ref || question.sub_ref .if-js-hide label.govuk-label for="q_#{question.key}" aria-label="#{ref.to_s.gsub(' ', '-')}: #{question.title}" - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.visuallyhidden - = ref.to_s - span.todo - = ref.to_s + = render "qae_form/question_ref", question: question, ref: ref - if question.delegate_obj.is_a?(QaeFormBuilder::HeaderQuestion) - if question.ref || question.sub_ref diff --git a/app/views/qae_form/_question_ref.html.slim b/app/views/qae_form/_question_ref.html.slim index d916491bc..16b3acf99 100644 --- a/app/views/qae_form/_question_ref.html.slim +++ b/app/views/qae_form/_question_ref.html.slim @@ -1,86 +1,3 @@ -- if question.header - h2.govuk-heading-l - = question.header -- if question.header_context - == question.header_context -fieldset class=question.fieldset_classes data=question.fieldset_data_hash - = condition_divs question do - .govuk-form-group class=(" govuk-form-group--error" if @form_answer.validator_errors && @form_answer.validator_errors[question.hash_key]) - - ref = question.ref ? question.ref : question.sub_ref - - if question.title != "" || question.show_ref_always.present? - - if question.label_as_legend? - legend.govuk-label aria-label="#{ref.to_s.gsub(' ', '-')} #{question.title.blank? ? '' : ':' + question.title}" - - if question.ref || question.sub_ref - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.todo - = ref.to_s - - unless question.title.blank? - - font_size = question.delegate_obj.is_a?(QaeFormBuilder::HeaderQuestion) ? "govuk-!-font-size-36" : "govuk-!-font-size-24" - span class="govuk-body #{ font_size } govuk-!-font-weight-bold govuk-!-display-block" - == question.title - - = render "qae_form/question_sub_title", question: question - - - else - legend.govuk-label aria-label="#{ref.to_s.gsub(' ', '-')} #{question.title.blank? ? '' : ':' + question.title}" - label.govuk-label for="q_#{question.key}" id="q_#{question.key}_label" aria-label="#{ref.to_s.gsub(' ', '-')}: #{question.title}" - - if question.ref || question.sub_ref - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.todo - = ref.to_s - - unless question.title.blank? - span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" - == question.title - - = render "qae_form/question_sub_title", question: question - - - else - - if question.ref || question.sub_ref - .if-js-hide - label.govuk-label for="q_#{question.key}" aria-label="#{ref.to_s.gsub(' ', '-')}: #{question.title}" - span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" - span.visuallyhidden - = ref.to_s - span.todo - = ref.to_s - - - if question.delegate_obj.is_a?(QaeFormBuilder::HeaderQuestion) - - if question.ref || question.sub_ref - span.question-context.question-debug.govuk-hint - = "Please note #{(question.ref || question.sub_ref).delete(" ")} is just a heading for the following sub-questions." - - if question.context - == question.context - - for help in question.help - == help.text - - else - - if question.context - span.question-context.question-debug.govuk-hint - == question.context - - if question.online_context - span.question-context.question-debug.govuk-hint - == question.online_context - - for help in question.help - span.question-context.question-debug.govuk-hint - == help.text - - - question.hint.each_with_index do |help, index| - details.govuk-details data-module='govuk-details' - - if help.title.present? - summary.govuk-details__summary - span.govuk-details__summary-text - = help.title.html_safe - .govuk-details__text - == help.text - - span.govuk-error-message - - if @form_answer.validator_errors && @form_answer.validator_errors[question.hash_key].present? && @form_answer.validator_errors[question.hash_key].is_a?(String) - span.govuk-visually-hidden - | Error: - =< @form_answer.validator_errors[question.hash_key] - - unless question.form_hint.blank? - span.govuk-hint = question.form_hint - = render partial: "qae_form/#{question.delegate_obj.class.name.demodulize.underscore}", object: question, as: 'question', locals: {answers: answers, attachments: attachments} - - / Conditional hints - - if question.can_have_conditional_hints? - = render "qae_form/conditional_hints/list", question: question +span class="steps step-#{ref.to_s.parameterize} #{'if-js-hide' if question.sub_ref && !question.display_sub_ref_on_js_form}" + span.todo + = ref.to_s diff --git a/app/views/qae_form/_supporter_fields.html.slim b/app/views/qae_form/_supporter_fields.html.slim index 15fab752f..cdf5b9f24 100644 --- a/app/views/qae_form/_supporter_fields.html.slim +++ b/app/views/qae_form/_supporter_fields.html.slim @@ -2,10 +2,10 @@ - persisted = supporter["support_letter_id"].present? || supporter["supporter_id"].present? - create_url = users_form_answer_support_letters_url(@form_answer) - update_url = users_form_answer_support_letter_path(@form_answer, supporter["support_letter_id"]) if persisted + li.borderless[class=class_names("js-add-example", "js-support-letter-received" => persisted) data-create-url=create_url data-update-url=update_url] legend.govuk-label - span class="steps" - span.todo C #{index + 1} + = render "qae_form/question_ref", question: question, ref: "C #{index + 1}" label[class="govuk-label"] span[class="govuk-body govuk-!-font-size-36 govuk-!-font-weight-bold govuk-!-display-block"] = "The #{first_or_second} letter of support" @@ -14,8 +14,7 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = .js-system-tag data-new-hidden-input-name="form[#{question.key}][#{index}][support_letter_id]" legend.govuk-label aria-label="C #{index + 1}.1: Name" - span class="steps" - span.todo C #{index + 1}.1 + = render "qae_form/question_ref", question: question, ref: "C #{index + 1}.1" span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" ' Name of the person who wrote the #{first_or_second} letter of support .govuk-form-group @@ -30,24 +29,22 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = input.js-support-letter-field.js-support-letter-last-name.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][last_name]" id="form[#{question.key}][#{index}][last_name]" type="text" value=supporter["last_name"] *possible_read_only_ops(question.step.opts[:id]) legend.govuk-label aria-label="C #{index + 1}.2: Relationship to Group" - span class="steps" - span.todo C #{index + 1}.2 + = render "qae_form/question_ref", question: question, ref: "C #{index + 1}.2" .govuk-form-group label class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" for="form[#{question.key}][#{index}][relationship_to_nominee]" - ' The person's who wrote the #{first_or_second} letter of support relationship to the group + ' Relationship to group span.question-context.question-debug.govuk-hint ' For example, a beneficiary of the group, local resident or member of a partner charity. span.govuk-error-message input.js-support-letter-field.js-support-letter-relationship-to-nominee.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][relationship_to_nominee]" id="form[#{question.key}][#{index}][relationship_to_nominee]" type="text" value=supporter["relationship_to_nominee"] *possible_read_only_ops(question.step.opts[:id]) legend.govuk-label aria-label="C #{index + 1}.3: Relationship to Group" - span class="steps" - span.todo C #{index + 1}.3 + = render "qae_form/question_ref", question: question, ref: "C #{index + 1}.2" .govuk-form-group - label.govuk-label for="form[#{question.key}][#{index}][letter_of_support]" + label class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" for="form[#{question.key}][#{index}][letter_of_support]" ' Upload the #{first_or_second} letter of support span.question-context.question-debug.govuk-hint - ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will appear, allowing you to select the correct file. + ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will reappear, allowing you to select the correct file. span.govuk-error-message input class="js-support-letter-field js-trigger-autosave js-support-letter-attachment govuk-input medium" name="form[#{question.key}][#{index}][letter_of_support]" id="form[#{question.key}][#{index}][letter_of_support]" type='file' *possible_read_only_ops(question.step.opts[:id]) - if supporter['letter_of_support'].present? diff --git a/app/views/qae_form/_supporters_question.html.slim b/app/views/qae_form/_supporters_question.html.slim index 95abd4c3a..81ae6406f 100644 --- a/app/views/qae_form/_supporters_question.html.slim +++ b/app/views/qae_form/_supporters_question.html.slim @@ -2,3 +2,4 @@ input name="form[#{question.key}][array]" value="true" type="hidden" *possible_r ul.list-add.supporters-list data-need-to-clear-example=true data-add-limit=question.limit data-attachments-url=(users_form_answer_support_letter_attachments_path(@form_answer)) data-example-has-file-field=true - question.entities.each_with_index do |supporter, index| = render 'qae_form/supporter_fields', question: question, supporter: supporter, index: index + diff --git a/app/views/shared/_attachment_with_virus_check_status.html.slim b/app/views/shared/_attachment_with_virus_check_status.html.slim index 18420519e..f4e624841 100644 --- a/app/views/shared/_attachment_with_virus_check_status.html.slim +++ b/app/views/shared/_attachment_with_virus_check_status.html.slim @@ -7,7 +7,9 @@ class: 'govuk-link' - elsif scan_results == "scanning" || scan_results == "pending" = item.try(:original_filename) - | (File uploaded and is being scanned for viruses. Preview available once the scan is complete.) + br + .govuk-hint + | (File uploaded and is being scanned for viruses. Preview available once the scan is complete.) - elsif scan_results == "infected" = item.try(:original_filename) | has been blocked (virus detected), please remove. diff --git a/app/views/support_letters/_attachment.html.slim b/app/views/support_letters/_attachment.html.slim index 3c2b1dead..b1162788d 100644 --- a/app/views/support_letters/_attachment.html.slim +++ b/app/views/support_letters/_attachment.html.slim @@ -1,9 +1,9 @@ - file = support_letter_attachments[attachment_id.to_i] .support-letter-attachment-container - div - p.govuk-body.support-letter-attachment-filename + .flex + .govuk-body.support-letter-attachment-filename = render "shared/attachment_with_virus_check_status", item: file, mount_name: :attachment - button.govuk-link.govuk-button--secondary data-module="govuk-button" type="button" onclick="removeAttachment(this)" - | Remove + button.govuk-link.js-remove-attachment data-module="govuk-button" type="button" + | Remove input.js-support-letter-attachment-id type="hidden" name="form[#{question.key}][#{index}][letter_of_support]" value=attachment_id *possible_read_only_ops From 829f49c57e2e89c6f5471f2d6199d69cf139f1fc Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Thu, 22 Aug 2024 16:12:54 +0200 Subject: [PATCH 08/20] Adjust centering & styling on statistics page --- app/assets/stylesheets/admin/tables.scss | 4 ++++ .../admin/statistics/nominations/_list.html.slim | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/admin/tables.scss b/app/assets/stylesheets/admin/tables.scss index 93288ce5f..5c817ce6b 100644 --- a/app/assets/stylesheets/admin/tables.scss +++ b/app/assets/stylesheets/admin/tables.scss @@ -11,6 +11,10 @@ border-bottom: 1px solid #b1b4b6; width: max-content; + .text-center { + text-align: center; + } + thead { background-color: #f2f2f2; diff --git a/app/views/admin/statistics/nominations/_list.html.slim b/app/views/admin/statistics/nominations/_list.html.slim index b68d0e0a7..62d71c2cb 100644 --- a/app/views/admin/statistics/nominations/_list.html.slim +++ b/app/views/admin/statistics/nominations/_list.html.slim @@ -16,7 +16,7 @@ div role="region" aria-labelledby="table-list-nomination-statistics-caption" tab col style="width: auto;" col style="width: 110px;" col style="width: auto;" - col style="width: 75px;" + col style="width: 95px;" col style="width: auto;" thead.govuk-table__head tr.govuk-table__row @@ -24,14 +24,14 @@ div role="region" aria-labelledby="table-list-nomination-statistics-caption" tab = sort_link f, 'Lieutenancy', object, :ceremonial_county_name th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" rowspan="2" | Submitted nominations - th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" colspan="4" + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right.text-center scope="col" colspan="4" | Eligibility - th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" colspan="2" + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right.text-center scope="col" colspan="2" | Local Assessment - th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" colspan="2" + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right.text-center scope="col" colspan="2" | National Assessment th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="col" rowspan="2" - | Royal Approval - Awarded + | Royal Approval - Awarded th.govuk-table__header.kavs-table__header--dense scope="col" rowspan="2" | Total tr.govuk-table__row @@ -54,7 +54,7 @@ div role="region" aria-labelledby="table-list-nomination-statistics-caption" tab tbody.govuk-table__body - results.each do |row| tr.govuk-table__row - th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right scope="row" + th.govuk-table__header.kavs-table__header--dense.kavs-table__header--border-right = row[:ceremonial_county_name] td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right = row[:submitted_count] @@ -80,7 +80,7 @@ div role="region" aria-labelledby="table-list-nomination-statistics-caption" tab = row[:total_count] tfoot.govuk-table__footer tr.govuk-table__row - th.kavs-table__footer--dense.kavs-table__footer--border-right scope="row" + th.kavs-table__footer--dense.kavs-table__footer--border-right | Total td.govuk-table__cell.kavs-table__cell--dense.kavs-table__cell--border-right = results.sum(&:submitted_count) From ae5174249d85c3b62c873718373d14779f002bdc Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Thu, 22 Aug 2024 16:18:15 +0200 Subject: [PATCH 09/20] Switch to text layout for email sent to user --- .../nomination_mailer/notify.html.slim | 18 ------------------ .../nomination_mailer/notify.text.erb | 12 ++++++++++++ 2 files changed, 12 insertions(+), 18 deletions(-) delete mode 100644 app/views/admin/statistics/nomination_mailer/notify.html.slim create mode 100644 app/views/admin/statistics/nomination_mailer/notify.text.erb diff --git a/app/views/admin/statistics/nomination_mailer/notify.html.slim b/app/views/admin/statistics/nomination_mailer/notify.html.slim deleted file mode 100644 index 08c162ba1..000000000 --- a/app/views/admin/statistics/nomination_mailer/notify.html.slim +++ /dev/null @@ -1,18 +0,0 @@ -p.govuk-body - = "Dear #{@admin.first_name}," - -p.govuk-body - ' You requested an export of nomination statistics. - -p.govuk-body - ' Please find this report on the link below: - -p.govuk-body - = link_to admin_protected_file_url(@file), admin_protected_file_url(@file) - -p.govuk-body - ' Kind Regards, - - br - - ' The King's Awards Office diff --git a/app/views/admin/statistics/nomination_mailer/notify.text.erb b/app/views/admin/statistics/nomination_mailer/notify.text.erb new file mode 100644 index 000000000..1324b30b8 --- /dev/null +++ b/app/views/admin/statistics/nomination_mailer/notify.text.erb @@ -0,0 +1,12 @@ +Dear <%= @admin.first_name %>, + +You requested an export of nomination statistics. + +Please find this report on the link below: +<%= admin_protected_file_url(@file) %> + +Kind Regards, + +The King's Awards Office + + From 6ff3e11d71cd8cd7448d37a35b2269164038bf2f Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:47:07 +0100 Subject: [PATCH 10/20] Update non-js views with new format for Letters of Support --- .../form/support_letters/_form.html.slim | 58 +++++++++++++------ app/views/form/supporters/index.html.slim | 15 +---- forms/award_years/v2025/qavs/qavs_step3.rb | 4 +- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/app/views/form/support_letters/_form.html.slim b/app/views/form/support_letters/_form.html.slim index 3d11283b7..1d6c3dfb6 100644 --- a/app/views/form/support_letters/_form.html.slim +++ b/app/views/form/support_letters/_form.html.slim @@ -1,26 +1,46 @@ = f.simple_fields_for :support_letters do |ff| - idx = ff.options[:child_index] + 1 - li - .govuk-grid-row - .govuk-grid-column-one-half - label[class="govuk-label"] - span[class="govuk-body govuk-!-font-size-20 govuk-!-font-weight-bold govuk-!-display-block"] - = "Letter of Support #{idx}" - = ff.input :first_name, label: "First Name:", input_html: { class: "form-control" } - = ff.input :last_name, label: "Surname:", input_html: { class: "form-control" } - = ff.input :relationship_to_nominee, label: "Relationship to Group:", input_html: { class: "form-control" } + - first_or_second = idx == 1 ? "first" : "second" + li.borderless + .question-block + label[class="govuk-label"] + legend.govuk-label + = render "qae_form/question_ref", question: question, ref: "C #{idx}" + span[class="govuk-body govuk-!-font-size-36 govuk-!-font-weight-bold govuk-!-display-block"] + = "The #{first_or_second} letter of support" - = ff.input :manual, as: :hidden, input_html: { value: true } - = ff.input :user_id, as: :hidden, input_html: { value: current_user.id } + legend.govuk-label aria-label="C #{idx}.1: Name" + = render "qae_form/question_ref", question: question, ref: "C #{idx}.1" + span[class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block"] + = "Name of the person who wrote the #{first_or_second} letter of support" + = ff.input :first_name, label: "First Name:", input_html: { class: "form-control medium" } + = ff.input :last_name, label: "Surname:", input_html: { class: "form-control medium" } - = ff.simple_fields_for :support_letter_attachment, (ff.object.support_letter_attachment || ff.object.build_support_letter_attachment) do |fff| - = fff.input :attachment, as: :file, label: "Upload Letter of Support #{idx}", input_html: { class: "form-control" }, wrapper_html: { style: "margin-bottom: -1rem;" } - - if fff.object.attachment.present? - p.govuk-body.support-letter-attachment-filename - = render "shared/attachment_with_virus_check_status", item: fff.object, mount_name: :attachment + legend.govuk-label aria-label="C #{idx}.2: Relationship to Group" + = render "qae_form/question_ref", question: question, ref: "C #{idx}.2" + label for="form_answer_support_letters_attributes_#{ff.options[:child_index]}_relationship_to_nominee" class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" + = "Relationship to group" + span.question-context.question-debug.govuk-hint + ' For example, a beneficiary of the group, local resident or member of a partner charity. + = ff.input :relationship_to_nominee, label: false, input_html: { class: "form-control medium" } - = fff.input :attachment_cache, as: :hidden + = ff.input :manual, as: :hidden, input_html: { value: true } + = ff.input :user_id, as: :hidden, input_html: { value: current_user.id } - = fff.input :form_answer_id, as: :hidden, input_html: { value: @form_answer.id } - = fff.input :user_id, as: :hidden, input_html: { value: current_user.id } + legend.govuk-label aria-label="C #{idx}.3: Upload Letter of Support" + = render "qae_form/question_ref", question: question, ref: "C #{idx}.3" + label for="form_answer_support_letters_attributes_#{ff.options[:child_index]}_support_letter_attachment" class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" + = "Upload the #{first_or_second} letter of support" + span.question-context.question-debug.govuk-hint + ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will reappear, allowing you to select the correct file. + = ff.simple_fields_for :support_letter_attachment, (ff.object.support_letter_attachment || ff.object.build_support_letter_attachment) do |fff| + = fff.input :attachment, as: :file, label: false, input_html: { class: "form-control" }, wrapper_html: { style: "margin-bottom: -1rem;" } + - if fff.object.attachment.present? + p.govuk-body.support-letter-attachment-filename + = render "shared/attachment_with_virus_check_status", item: fff.object, mount_name: :attachment + + = fff.input :attachment_cache, as: :hidden + + = fff.input :form_answer_id, as: :hidden, input_html: { value: @form_answer.id } + = fff.input :user_id, as: :hidden, input_html: { value: current_user.id } diff --git a/app/views/form/supporters/index.html.slim b/app/views/form/supporters/index.html.slim index e5d73b67f..27f6e9caa 100644 --- a/app/views/form/supporters/index.html.slim +++ b/app/views/form/supporters/index.html.slim @@ -15,19 +15,10 @@ h1.govuk-heading-xl article.group role="article" div - letters_intro_question = @step.questions.detect { |q| q.key == :supporter_letters_list } + - letters_context = @step.questions.detect { |q| q.key == :supporter_letters_list_context} - checkbox_questions = @step.questions.select { |q| q.key.in?(%i(independent_individual not_nominator)) } - .question-block - label.govuk-label - span class="steps step-d-1" - span.visuallyhidden - ' C 1 - span.todo - ' C 1 - span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" - ' Upload your letters of support - - == letters_intro_question&.context + == letters_context&.context - for help in (letters_intro_question&.hint || []) details.govuk-details data-module="govuk-details" @@ -45,7 +36,7 @@ h1.govuk-heading-xl - f.object.support_letters.build unless f.object.support_letters[n].present? ul.list-add.supporters-list - = render partial: "form/support_letters/form", locals: { f: f } + = render partial: "form/support_letters/form", locals: { f: f, question: letters_intro_question } - checkbox_questions.each do |question| .govuk-form-group diff --git a/forms/award_years/v2025/qavs/qavs_step3.rb b/forms/award_years/v2025/qavs/qavs_step3.rb index da935217f..acfb87a1e 100644 --- a/forms/award_years/v2025/qavs/qavs_step3.rb +++ b/forms/award_years/v2025/qavs/qavs_step3.rb @@ -6,8 +6,8 @@ def qavs_step3

Please note your answers are being saved automatically in the background.

) - header :supporter_letters_list_key_criteria, "" do - help "About this section", %( + header :supporter_letters_list_context, "" do + context %(

Letters of support are an essential part of your nomination, as they help to clarify and explain the impact of the nominated group's work in the local community. You will need to provide 2 letters of support alongside your nomination.

For more information on what letters can cover, please see the Letters of Support page on our website.

Key criteria:

From 1c4c572cbc535886fc429c81ebfb43bc5d13ec8f Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Thu, 22 Aug 2024 16:47:51 +0100 Subject: [PATCH 11/20] Update how index is written --- app/views/qae_form/_supporter_fields.html.slim | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/views/qae_form/_supporter_fields.html.slim b/app/views/qae_form/_supporter_fields.html.slim index cdf5b9f24..762e75c6f 100644 --- a/app/views/qae_form/_supporter_fields.html.slim +++ b/app/views/qae_form/_supporter_fields.html.slim @@ -1,11 +1,12 @@ - first_or_second = index == 0 ? "first" : "second" +- idx = index + 1 - persisted = supporter["support_letter_id"].present? || supporter["supporter_id"].present? - create_url = users_form_answer_support_letters_url(@form_answer) - update_url = users_form_answer_support_letter_path(@form_answer, supporter["support_letter_id"]) if persisted li.borderless[class=class_names("js-add-example", "js-support-letter-received" => persisted) data-create-url=create_url data-update-url=update_url] legend.govuk-label - = render "qae_form/question_ref", question: question, ref: "C #{index + 1}" + = render "qae_form/question_ref", question: question, ref: "C #{idx}" label[class="govuk-label"] span[class="govuk-body govuk-!-font-size-36 govuk-!-font-weight-bold govuk-!-display-block"] = "The #{first_or_second} letter of support" @@ -14,7 +15,7 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = .js-system-tag data-new-hidden-input-name="form[#{question.key}][#{index}][support_letter_id]" legend.govuk-label aria-label="C #{index + 1}.1: Name" - = render "qae_form/question_ref", question: question, ref: "C #{index + 1}.1" + = render "qae_form/question_ref", question: question, ref: "C #{idx}.1" span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" ' Name of the person who wrote the #{first_or_second} letter of support .govuk-form-group @@ -28,8 +29,8 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = span.govuk-error-message input.js-support-letter-field.js-support-letter-last-name.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][last_name]" id="form[#{question.key}][#{index}][last_name]" type="text" value=supporter["last_name"] *possible_read_only_ops(question.step.opts[:id]) - legend.govuk-label aria-label="C #{index + 1}.2: Relationship to Group" - = render "qae_form/question_ref", question: question, ref: "C #{index + 1}.2" + legend.govuk-label aria-label="C #{idx}.2: Relationship to Group" + = render "qae_form/question_ref", question: question, ref: "C #{idx}.2" .govuk-form-group label class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" for="form[#{question.key}][#{index}][relationship_to_nominee]" ' Relationship to group @@ -38,8 +39,8 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = span.govuk-error-message input.js-support-letter-field.js-support-letter-relationship-to-nominee.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][relationship_to_nominee]" id="form[#{question.key}][#{index}][relationship_to_nominee]" type="text" value=supporter["relationship_to_nominee"] *possible_read_only_ops(question.step.opts[:id]) - legend.govuk-label aria-label="C #{index + 1}.3: Relationship to Group" - = render "qae_form/question_ref", question: question, ref: "C #{index + 1}.2" + legend.govuk-label aria-label="C #{idx}.3: Relationship to Group" + = render "qae_form/question_ref", question: question, ref: "C #{idx}.3" .govuk-form-group label class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" for="form[#{question.key}][#{index}][letter_of_support]" ' Upload the #{first_or_second} letter of support From 00e75c81fac3a13af6e6c192a610a22cdab1a842 Mon Sep 17 00:00:00 2001 From: Lubos Hricak Date: Thu, 22 Aug 2024 18:43:04 +0200 Subject: [PATCH 12/20] Use `LEFT JOIN` to get also not assigned lieutenancies --- app/search/nomination_stats_search.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/search/nomination_stats_search.rb b/app/search/nomination_stats_search.rb index 979d4b2c4..31f4f3213 100644 --- a/app/search/nomination_stats_search.rb +++ b/app/search/nomination_stats_search.rb @@ -13,7 +13,7 @@ class NominationStatsSearch < Search ] FETCH_QUERY = %Q{ - ceremonial_counties.name AS ceremonial_county_name, + CASE WHEN ceremonial_counties.name IS NULL THEN '-' ELSE ceremonial_counties.name END AS ceremonial_county_name, #{TRACKED_STATES.map { |s| "COUNT(CASE WHEN form_answers.state = '#{s}' THEN 1 END) AS #{s}_count" }.join(',')}, COUNT(CASE WHEN form_answers.state IN (#{TRACKED_STATES.map { |s| "'#{s}'" }.join(',')}) THEN 1 END) AS total_count }.squish.freeze @@ -33,7 +33,7 @@ def results @search_results = @search_results .select(FETCH_QUERY) - .joins(:ceremonial_county) + .left_joins(:ceremonial_county) .group("ceremonial_counties.name") @search_results From 13651e395139e2b405f2b1eecf0c2b9b84a90f85 Mon Sep 17 00:00:00 2001 From: Louis Kirkham Date: Tue, 27 Aug 2024 13:29:07 +0100 Subject: [PATCH 13/20] chore: Update email layout and styling for nomination statistics report Signed-off-by: Louis Kirkham --- .../admin/statistics/nomination_mailer/notify.text.erb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/admin/statistics/nomination_mailer/notify.text.erb b/app/views/admin/statistics/nomination_mailer/notify.text.erb index 1324b30b8..62c1a7e9c 100644 --- a/app/views/admin/statistics/nomination_mailer/notify.text.erb +++ b/app/views/admin/statistics/nomination_mailer/notify.text.erb @@ -2,11 +2,9 @@ Dear <%= @admin.first_name %>, You requested an export of nomination statistics. -Please find this report on the link below: +Please find this report on the link below: <%= admin_protected_file_url(@file) %> -Kind Regards, +Kind regards, The King's Awards Office - - From 10810b4a406f494f850d2fbf2e7eff3876127cff Mon Sep 17 00:00:00 2001 From: Louis Kirkham Date: Wed, 28 Aug 2024 07:57:56 +0100 Subject: [PATCH 14/20] chore: Use existing code for lieutenancy filter Signed-off-by: Louis Kirkham --- app/search/nomination_stats_search.rb | 29 ++----------------- .../statistics/nominations/index.html.slim | 2 +- 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/app/search/nomination_stats_search.rb b/app/search/nomination_stats_search.rb index 31f4f3213..cd05edb16 100644 --- a/app/search/nomination_stats_search.rb +++ b/app/search/nomination_stats_search.rb @@ -11,9 +11,9 @@ class NominationStatsSearch < Search shortlisted awarded ] - + FETCH_QUERY = %Q{ - CASE WHEN ceremonial_counties.name IS NULL THEN '-' ELSE ceremonial_counties.name END AS ceremonial_county_name, + CASE WHEN ceremonial_counties.name IS NULL THEN 'Not assigned' ELSE ceremonial_counties.name END AS ceremonial_county_name, #{TRACKED_STATES.map { |s| "COUNT(CASE WHEN form_answers.state = '#{s}' THEN 1 END) AS #{s}_count" }.join(',')}, COUNT(CASE WHEN form_answers.state IN (#{TRACKED_STATES.map { |s| "'#{s}'" }.join(',')}) THEN 1 END) AS total_count }.squish.freeze @@ -23,7 +23,7 @@ def self.default_search sort: "ceremonial_county_name", search_filter: { year: "all_years", - assigned_ceremonial_county: ceremonial_county_options.map(&:second) + assigned_ceremonial_county: FormAnswerStatus::AdminFilter.values('assigned county') } } end @@ -58,27 +58,4 @@ def sort_by_ceremonial_county_name(scoped_results, desc = false) scoped_results.order("ceremonial_counties.name #{sort_order(desc)}") end - class << self - def ceremonial_county_options - collection_mapping(county_options) - end - - private - - def collection_mapping(options) - options.map do |k, v| - [v[:label], k] - end - end - - def county_options - options = Hash[not_assigned: { label: "Not assigned" }] - - CeremonialCounty.ordered.collect do |county| - options[county.id] = { label: county.name } - end - - options - end - end end diff --git a/app/views/admin/statistics/nominations/index.html.slim b/app/views/admin/statistics/nominations/index.html.slim index 2a9c77720..5dc75466c 100644 --- a/app/views/admin/statistics/nominations/index.html.slim +++ b/app/views/admin/statistics/nominations/index.html.slim @@ -12,7 +12,7 @@ h1.govuk-heading-xl ' Award year = g.select :year, award_years_collection, {}, { id: "award-year-select", class: "govuk-select custom-select", style: "height: 40px;", aria: { label: "award year select" } } - = render "shared/form_answers/filters/assigned_lieutenancy_filter", g: g, options: NominationStatsSearch.ceremonial_county_options + = render "shared/form_answers/filters/assigned_lieutenancy_filter", g: g, options: FormAnswerStatus::AdminFilter.collection('assigned county') div[class="govuk-button-group"] = f.submit "Apply filters", From b153dcf0543eb8d30bfbb8a9eac682496162c60e Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Fri, 23 Aug 2024 15:35:26 +0100 Subject: [PATCH 15/20] Non-js destroy action for support letter attachments --- .../frontend/views/award_form.scss | 6 +++- .../support_letter_attachments_controller.rb | 29 +++++++++++++++++++ .../support_letter_attachments/show.html.slim | 20 +++++++++++++ .../form/support_letters/_form.html.slim | 16 ++++++---- ...tachment_with_virus_check_status.html.slim | 6 ++-- config/routes.rb | 4 ++- 6 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 app/controllers/form/support_letter_attachments_controller.rb create mode 100644 app/views/form/support_letter_attachments/show.html.slim diff --git a/app/assets/stylesheets/frontend/views/award_form.scss b/app/assets/stylesheets/frontend/views/award_form.scss index e7a27600e..bcd0b9ace 100644 --- a/app/assets/stylesheets/frontend/views/award_form.scss +++ b/app/assets/stylesheets/frontend/views/award_form.scss @@ -159,7 +159,7 @@ label > .visible-read-only, } .support-letter-attachment-container { - padding: 15px 15px 0; + padding: 15px; display: flex; justify-content: space-between; align-items: start; @@ -176,6 +176,10 @@ label > .visible-read-only, word-break: keep-all; margin-top: 0 !important; } + + .non-js-remove-attachment { + color: $govuk-red; + } } .view-only, diff --git a/app/controllers/form/support_letter_attachments_controller.rb b/app/controllers/form/support_letter_attachments_controller.rb new file mode 100644 index 000000000..1be939752 --- /dev/null +++ b/app/controllers/form/support_letter_attachments_controller.rb @@ -0,0 +1,29 @@ +class Form::SupportLetterAttachmentsController < Form::BaseController + include FormAnswerSubmissionMixin + before_action :set_support_letter + + def show; end + + def destroy + attachment = SupportLetterAttachment.find(params[:id]) + form_answer = attachment.form_answer + + if attachment.destroy + updated_list = form_answer.document['supporter_letters_list'].reject { |letter| letter['letter_of_support'] == attachment.id } + form_answer.update(document: form_answer.document.merge(supporter_letters_list: updated_list)) + + flash[:notice] = 'Attachment successfully deleted.' + else + flash[:alert] = 'Failed to delete attachment.' + end + + redirect_to form_form_answer_supporters_path(form_answer) + end + + private + + def set_support_letter + @support_letter = SupportLetter.find(params[:support_letter_id]) + @form_answer = @support_letter.form_answer + end +end diff --git a/app/views/form/support_letter_attachments/show.html.slim b/app/views/form/support_letter_attachments/show.html.slim new file mode 100644 index 000000000..10b3b01ac --- /dev/null +++ b/app/views/form/support_letter_attachments/show.html.slim @@ -0,0 +1,20 @@ +- content_for :page_title, "Support Letter Attachment" + +.govuk-grid-row + .govuk-grid-column-two-thirds + h1.govuk-heading-xl Support Letter Attachment + + - if @support_letter.support_letter_attachment.present? + .govuk-summary-list + .govuk-summary-list__row + dt.govuk-summary-list__key + | File Name + dd.govuk-summary-list__value + = @support_letter.support_letter_attachment.original_filename + + = button_to "Delete Attachment", + form_form_answer_support_letter_support_letter_attachment_path(@form_answer, @support_letter, @support_letter.support_letter_attachment), + method: :delete, + class: "govuk-button govuk-button--warning" + - else + p.govuk-body No attachment found for this support letter. diff --git a/app/views/form/support_letters/_form.html.slim b/app/views/form/support_letters/_form.html.slim index 1d6c3dfb6..b25715bd8 100644 --- a/app/views/form/support_letters/_form.html.slim +++ b/app/views/form/support_letters/_form.html.slim @@ -33,14 +33,18 @@ = "Upload the #{first_or_second} letter of support" span.question-context.question-debug.govuk-hint ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will reappear, allowing you to select the correct file. + = ff.simple_fields_for :support_letter_attachment, (ff.object.support_letter_attachment || ff.object.build_support_letter_attachment) do |fff| - = fff.input :attachment, as: :file, label: false, input_html: { class: "form-control" }, wrapper_html: { style: "margin-bottom: -1rem;" } - if fff.object.attachment.present? - p.govuk-body.support-letter-attachment-filename + p.govuk-body.support-letter-attachment-container = render "shared/attachment_with_virus_check_status", item: fff.object, mount_name: :attachment + - if ff.object.support_letter_attachment.id.present? + = link_to 'Remove', form_form_answer_support_letter_support_letter_attachment_path(@form_answer, ff.object, ff.object.support_letter_attachment), method: :delete, class: 'govuk-link non-js-remove-attachment' - = fff.input :attachment_cache, as: :hidden - - = fff.input :form_answer_id, as: :hidden, input_html: { value: @form_answer.id } - = fff.input :user_id, as: :hidden, input_html: { value: current_user.id } + = fff.input :attachment_cache, as: :hidden + = fff.input :form_answer_id, as: :hidden, input_html: { value: @form_answer.id } + = fff.input :user_id, as: :hidden, input_html: { value: current_user.id } + - puts "fff.object.attachment.id.present? #{fff.object.attachment.inspect}" + - unless fff.object.id.present? + = fff.input :attachment, as: :file, label: false, input_html: { class: "form-control" }, wrapper_html: { style: "margin-bottom: -1rem;" } diff --git a/app/views/shared/_attachment_with_virus_check_status.html.slim b/app/views/shared/_attachment_with_virus_check_status.html.slim index f4e624841..4b9bcb9e5 100644 --- a/app/views/shared/_attachment_with_virus_check_status.html.slim +++ b/app/views/shared/_attachment_with_virus_check_status.html.slim @@ -12,9 +12,9 @@ | (File uploaded and is being scanned for viruses. Preview available once the scan is complete.) - elsif scan_results == "infected" = item.try(:original_filename) - | has been blocked (virus detected), please remove. + | has been blocked (virus detected), please upload another file. - else - = item.try(:original_filename) - | didn't pass virus scanner check, please remove + = item.try(:original_filename) || "File" + | didn't pass virus scanner check, please upload another file. - else = item.try(:original_filename) diff --git a/config/routes.rb b/config/routes.rb index e9bb73c10..241a6acc4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -104,7 +104,9 @@ namespace :form do resources :form_answers do resources :supporters, only: [:new, :create, :index, :destroy] - resources :support_letters, only: [:create] + resources :support_letters, only: [:create] do + resources :support_letter_attachments, only: [:show, :destroy] + end resources :form_attachments, only: [:index, :new, :create, :destroy] end end From 3c77bbefb30f718af1260a24dd70ded1900f6aa4 Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Tue, 27 Aug 2024 19:52:19 +0100 Subject: [PATCH 16/20] JS remove support letter attachment feature --- .../support_letters.js.coffee | 21 +++++++++++++++---- .../frontend/views/award_form.scss | 5 ++--- .../form/support_letters/_form.html.slim | 6 +++--- .../qae_form/_supporter_fields.html.slim | 4 ++-- .../support_letters/_attachment.html.slim | 4 ++-- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee b/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee index af3e491b8..2dbca938b 100644 --- a/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee +++ b/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee @@ -5,6 +5,10 @@ window.SupportLetters = $(document).on 'change', '.js-trigger-autosave', debounce(SupportLetters.submit, 1000) + $(document).on 'click', '.js-remove-support-letter-attachment', (e) -> + e.preventDefault() + SupportLetters.removeFile($(this).closest('.govuk-form-group').find('input:first'), e) + new_item_init: (el) -> SupportLetters.clean_up_system_tags(el) SupportLetters.enable_item_fields_and_controls(el) @@ -25,11 +29,11 @@ window.SupportLetters = parent.find('.govuk-error-message').html('') parent.find('.govuk-error-message').closest('.govuk-form-group').removeClass('govuk-form-group--error') - - label = $('

' + filename + '

') + + textContainer = parent.find('.support-letter-attachment-container') + textContainer.removeClass('govuk-!-display-none') + textContainer.find('.flex').prepend('

' + filename + '

') hiddenInput = $("") - - parent.append(label) parent.append(hiddenInput) SupportLetters.autosave() SupportLetters.submit(e) @@ -61,6 +65,15 @@ window.SupportLetters = parent.find('input[type="hidden"]').remove() parent.find('.support-letter-attachment-filename').remove() + removeFile: (el, e) -> + $el = $(el) + $el.val('') + $el.siblings('.js-support-letter-attachment-id').first().val('') + $el.siblings('.support-letter-attachment-container').addClass('govuk-!-display-none') + $el.removeClass('govuk-!-display-none') + SupportLetters.autosave() + SupportLetters.submit(e) + enable_item_fields_and_controls: (parent) -> parent.find('.govuk-error-message').html('') prefixed = parent.find('.js-system-tag').data('new-hidden-input-name') diff --git a/app/assets/stylesheets/frontend/views/award_form.scss b/app/assets/stylesheets/frontend/views/award_form.scss index bcd0b9ace..8f7269b8d 100644 --- a/app/assets/stylesheets/frontend/views/award_form.scss +++ b/app/assets/stylesheets/frontend/views/award_form.scss @@ -151,7 +151,6 @@ margin: 0.75em 0; } -.support-letter-attachment-filename, label > .visible-read-only, .view-value { margin-bottom: 10px; @@ -171,13 +170,13 @@ label > .visible-read-only, align-items: start; } - .js-remove-attachment{ + .js-remove-support-letter-attachment{ color: $govuk-red; word-break: keep-all; margin-top: 0 !important; } - .non-js-remove-attachment { + .non-js-remove-support-letter-attachment { color: $govuk-red; } } diff --git a/app/views/form/support_letters/_form.html.slim b/app/views/form/support_letters/_form.html.slim index b25715bd8..f04e1db4f 100644 --- a/app/views/form/support_letters/_form.html.slim +++ b/app/views/form/support_letters/_form.html.slim @@ -36,15 +36,15 @@ = ff.simple_fields_for :support_letter_attachment, (ff.object.support_letter_attachment || ff.object.build_support_letter_attachment) do |fff| - if fff.object.attachment.present? - p.govuk-body.support-letter-attachment-container + p.govuk-body.support-letter-attachment-container class="govuk-!-font-size-19" = render "shared/attachment_with_virus_check_status", item: fff.object, mount_name: :attachment - if ff.object.support_letter_attachment.id.present? - = link_to 'Remove', form_form_answer_support_letter_support_letter_attachment_path(@form_answer, ff.object, ff.object.support_letter_attachment), method: :delete, class: 'govuk-link non-js-remove-attachment' + = link_to 'Remove', form_form_answer_support_letter_support_letter_attachment_path(@form_answer, ff.object, ff.object.support_letter_attachment), method: :delete, class: 'govuk-link non-js-remove-support-letter-attachment govuk-!-font-size-19' = fff.input :attachment_cache, as: :hidden = fff.input :form_answer_id, as: :hidden, input_html: { value: @form_answer.id } = fff.input :user_id, as: :hidden, input_html: { value: current_user.id } - - puts "fff.object.attachment.id.present? #{fff.object.attachment.inspect}" + - unless fff.object.id.present? = fff.input :attachment, as: :file, label: false, input_html: { class: "form-control" }, wrapper_html: { style: "margin-bottom: -1rem;" } diff --git a/app/views/qae_form/_supporter_fields.html.slim b/app/views/qae_form/_supporter_fields.html.slim index 762e75c6f..b0580301c 100644 --- a/app/views/qae_form/_supporter_fields.html.slim +++ b/app/views/qae_form/_supporter_fields.html.slim @@ -47,6 +47,6 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = span.question-context.question-debug.govuk-hint ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will reappear, allowing you to select the correct file. span.govuk-error-message - input class="js-support-letter-field js-trigger-autosave js-support-letter-attachment govuk-input medium" name="form[#{question.key}][#{index}][letter_of_support]" id="form[#{question.key}][#{index}][letter_of_support]" type='file' *possible_read_only_ops(question.step.opts[:id]) + input class="js-support-letter-field js-trigger-autosave js-support-letter-attachment govuk-input medium #{'govuk-!-display-none' if supporter['letter_of_support'].present?}" name="form[#{question.key}][#{index}][letter_of_support]" id="form[#{question.key}][#{index}][letter_of_support]" type='file' *possible_read_only_ops(question.step.opts[:id]) - if supporter['letter_of_support'].present? - = render "support_letters/attachment", question: question, index: index, attachment_id: supporter['letter_of_support'] + = render "support_letters/attachment", question: question, index: index, supporter: supporter, attachment_id: supporter['letter_of_support'] diff --git a/app/views/support_letters/_attachment.html.slim b/app/views/support_letters/_attachment.html.slim index b1162788d..86406f38a 100644 --- a/app/views/support_letters/_attachment.html.slim +++ b/app/views/support_letters/_attachment.html.slim @@ -2,8 +2,8 @@ .support-letter-attachment-container .flex - .govuk-body.support-letter-attachment-filename + .support-letter-attachment-filename class="govuk-!-font-size-19" = render "shared/attachment_with_virus_check_status", item: file, mount_name: :attachment - button.govuk-link.js-remove-attachment data-module="govuk-button" type="button" + button.govuk-link.js-remove-support-letter-attachment class="govuk-!-font-size-19" data-module="govuk-button" type="button" | Remove input.js-support-letter-attachment-id type="hidden" name="form[#{question.key}][#{index}][letter_of_support]" value=attachment_id *possible_read_only_ops From 90270c91fe38443565fac0a1f1f3005d77871c17 Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:36:04 +0100 Subject: [PATCH 17/20] Fix validation on Letters of Support subquestions --- .../frontend/custom_questions/support_letters.js.coffee | 2 +- app/assets/javascripts/frontend/form-validation.js.coffee | 3 +-- app/controllers/form/support_letters_controller.rb | 2 +- app/views/form/support_letters/_form.html.slim | 2 +- app/views/qae_form/_supporter_fields.html.slim | 8 ++++---- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee b/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee index 2dbca938b..efc8f0a45 100644 --- a/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee +++ b/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee @@ -32,7 +32,7 @@ window.SupportLetters = textContainer = parent.find('.support-letter-attachment-container') textContainer.removeClass('govuk-!-display-none') - textContainer.find('.flex').prepend('

' + filename + '

') + textContainer.find('.flex').html('

' + filename + '

') hiddenInput = $("") parent.append(hiddenInput) SupportLetters.autosave() diff --git a/app/assets/javascripts/frontend/form-validation.js.coffee b/app/assets/javascripts/frontend/form-validation.js.coffee index fd40f0a05..63fa8d8e0 100644 --- a/app/assets/javascripts/frontend/form-validation.js.coffee +++ b/app/assets/javascripts/frontend/form-validation.js.coffee @@ -12,7 +12,7 @@ window.FormValidation = clearErrors: (container) -> if container.closest(".question-financial").length > 0 container.closest("label").find(".govuk-error-message").empty() - else if container.closest('.question-block').data('answer').indexOf('address') > -1 + else if container.closest('.question-block').data('answer') && container.closest('.question-block').data('answer').indexOf('address') > -1 container.closest(".govuk-form-group").find(".govuk-error-message").empty() else container.closest(".question-block").find(".govuk-error-message").empty() @@ -489,7 +489,6 @@ window.FormValidation = stepContainer.find(".govuk-form-group--error").removeClass("govuk-form-group--error") stepContainer.find(".govuk-error-message").empty() $(".steps-progress-bar .js-step-link[data-step='" + currentStep + "']").removeClass("step-errors") - for question in stepContainer.find(".question-block") question = $(question) @validateIndividualQuestion(question) diff --git a/app/controllers/form/support_letters_controller.rb b/app/controllers/form/support_letters_controller.rb index fa581277a..0e422180a 100644 --- a/app/controllers/form/support_letters_controller.rb +++ b/app/controllers/form/support_letters_controller.rb @@ -49,7 +49,7 @@ def add_support_letters_to_document! h[:first_name] = support_letter.first_name h[:last_name] = support_letter.last_name h[:relationship_to_nominee] = support_letter.relationship_to_nominee - h[:letter_of_support] = support_letter.support_letter_attachment.id + h[:letter_of_support] = support_letter.support_letter_attachment&.id end end diff --git a/app/views/form/support_letters/_form.html.slim b/app/views/form/support_letters/_form.html.slim index f04e1db4f..34605d172 100644 --- a/app/views/form/support_letters/_form.html.slim +++ b/app/views/form/support_letters/_form.html.slim @@ -35,7 +35,7 @@ ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will reappear, allowing you to select the correct file. = ff.simple_fields_for :support_letter_attachment, (ff.object.support_letter_attachment || ff.object.build_support_letter_attachment) do |fff| - - if fff.object.attachment.present? + - if ff.object.support_letter_attachment.present? p.govuk-body.support-letter-attachment-container class="govuk-!-font-size-19" = render "shared/attachment_with_virus_check_status", item: fff.object, mount_name: :attachment - if ff.object.support_letter_attachment.id.present? diff --git a/app/views/qae_form/_supporter_fields.html.slim b/app/views/qae_form/_supporter_fields.html.slim index b0580301c..fd6fc0756 100644 --- a/app/views/qae_form/_supporter_fields.html.slim +++ b/app/views/qae_form/_supporter_fields.html.slim @@ -18,12 +18,12 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = = render "qae_form/question_ref", question: question, ref: "C #{idx}.1" span class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" ' Name of the person who wrote the #{first_or_second} letter of support - .govuk-form-group + .govuk-form-group.question-block.question-required label.govuk-label for="form[#{question.key}][#{index}][first_name]" ' First Name: span.govuk-error-message input.js-support-letter-field.js-support-letter-first-name.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][first_name]" id="form[#{question.key}][#{index}][first_name]" type="text" value=supporter["first_name"] *possible_read_only_ops(question.step.opts[:id]) - .govuk-form-group + .govuk-form-group.question-block.question-required label.govuk-label for="form[#{question.key}][#{index}][last_name]" ' Surname: span.govuk-error-message @@ -31,7 +31,7 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = legend.govuk-label aria-label="C #{idx}.2: Relationship to Group" = render "qae_form/question_ref", question: question, ref: "C #{idx}.2" - .govuk-form-group + .govuk-form-group.question-block.question-required label class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" for="form[#{question.key}][#{index}][relationship_to_nominee]" ' Relationship to group span.question-context.question-debug.govuk-hint @@ -41,7 +41,7 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = legend.govuk-label aria-label="C #{idx}.3: Relationship to Group" = render "qae_form/question_ref", question: question, ref: "C #{idx}.3" - .govuk-form-group + .govuk-form-group.question-block.question-required label class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block" for="form[#{question.key}][#{index}][letter_of_support]" ' Upload the #{first_or_second} letter of support span.question-context.question-debug.govuk-hint From e30117c26805748b14c6b9a6c7b51a14f13e361b Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:41:11 +0100 Subject: [PATCH 18/20] - Remove validation for old letters of support question. This checks if 2 upload blocks have been filled in and adds an overarching error, we now use individual errors beside each input. - Hide file upload input after successful upload. - Fix js & non-js validation for support letter attachments. --- .../custom_questions/support_letters.js.coffee | 1 + .../frontend/form-validation.js.coffee | 18 ++++++------------ app/views/form/support_letters/_form.html.slim | 9 +++++---- app/views/qae_form/_supporter_fields.html.slim | 3 +-- .../support_letters/_attachment.html.slim | 2 +- 5 files changed, 14 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee b/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee index efc8f0a45..ee26595e5 100644 --- a/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee +++ b/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee @@ -35,6 +35,7 @@ window.SupportLetters = textContainer.find('.flex').html('

' + filename + '

') hiddenInput = $("") parent.append(hiddenInput) + parent.find('.js-support-letter-attachment').addClass('govuk-!-display-none') SupportLetters.autosave() SupportLetters.submit(e) diff --git a/app/assets/javascripts/frontend/form-validation.js.coffee b/app/assets/javascripts/frontend/form-validation.js.coffee index 63fa8d8e0..9cf164baa 100644 --- a/app/assets/javascripts/frontend/form-validation.js.coffee +++ b/app/assets/javascripts/frontend/form-validation.js.coffee @@ -56,6 +56,9 @@ window.FormValidation = isCheckboxQuestion: (question) -> question.find("input[type='checkbox']").length + isSupportLetterAttachment: (question) -> + question.find(".js-support-letter-attachment").length + toDate: (str) -> moment(str, "DD/MM/YYYY") @@ -81,6 +84,9 @@ window.FormValidation = if @isCheckboxQuestion(question) return question.find("input[type='checkbox']").filter(":checked").length + if @isSupportLetterAttachment(question) + return (question.find(".js-support-letter-attachment-id").val() || '').toString().trim().length + validateRequiredQuestion: (question) -> # if it's a conditional question, but condition was not satisfied conditional = true @@ -352,13 +358,6 @@ window.FormValidation = @addErrorMessage(question, errorMessage) return - validateSupportLetters: (question) -> - lettersReceived = $(".js-support-letter-received").length - if lettersReceived < 2 - @logThis(question, "validateSupportLetters", "Upload two letters of support") - @appendMessage(question, "Upload two letters of support") - @addErrorClass(question) - validateSelectionLimit: (question) -> selection_limit = question.data("selection-limit") current_selection_count = question.find("input[type=checkbox]:checked").length @@ -455,11 +454,6 @@ window.FormValidation = # console.log "validateDropBlockCondition" @validateDropBlockCondition(question) - if question.hasClass("question-support-requests") || - question.hasClass("question-support-uploads") - # console.log "validateSupportLetters" - @validateSupportLetters(question) - if question.hasClass("question-limited-selections") @validateSelectionLimit(question) diff --git a/app/views/form/support_letters/_form.html.slim b/app/views/form/support_letters/_form.html.slim index 34605d172..96721fe79 100644 --- a/app/views/form/support_letters/_form.html.slim +++ b/app/views/form/support_letters/_form.html.slim @@ -35,16 +35,17 @@ ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will reappear, allowing you to select the correct file. = ff.simple_fields_for :support_letter_attachment, (ff.object.support_letter_attachment || ff.object.build_support_letter_attachment) do |fff| - - if ff.object.support_letter_attachment.present? + + - if ff.object.support_letter_attachment.id.present? p.govuk-body.support-letter-attachment-container class="govuk-!-font-size-19" = render "shared/attachment_with_virus_check_status", item: fff.object, mount_name: :attachment - if ff.object.support_letter_attachment.id.present? = link_to 'Remove', form_form_answer_support_letter_support_letter_attachment_path(@form_answer, ff.object, ff.object.support_letter_attachment), method: :delete, class: 'govuk-link non-js-remove-support-letter-attachment govuk-!-font-size-19' - = fff.input :attachment_cache, as: :hidden + = fff.input :attachment_cache, as: :hidden - = fff.input :form_answer_id, as: :hidden, input_html: { value: @form_answer.id } - = fff.input :user_id, as: :hidden, input_html: { value: current_user.id } + = fff.input :form_answer_id, as: :hidden, input_html: { value: @form_answer.id } + = fff.input :user_id, as: :hidden, input_html: { value: current_user.id } - unless fff.object.id.present? = fff.input :attachment, as: :file, label: false, input_html: { class: "form-control" }, wrapper_html: { style: "margin-bottom: -1rem;" } diff --git a/app/views/qae_form/_supporter_fields.html.slim b/app/views/qae_form/_supporter_fields.html.slim index fd6fc0756..5717db4ac 100644 --- a/app/views/qae_form/_supporter_fields.html.slim +++ b/app/views/qae_form/_supporter_fields.html.slim @@ -48,5 +48,4 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = ' If you upload the wrong file, click the 'Remove' link next to the file name to delete it. The file upload button will reappear, allowing you to select the correct file. span.govuk-error-message input class="js-support-letter-field js-trigger-autosave js-support-letter-attachment govuk-input medium #{'govuk-!-display-none' if supporter['letter_of_support'].present?}" name="form[#{question.key}][#{index}][letter_of_support]" id="form[#{question.key}][#{index}][letter_of_support]" type='file' *possible_read_only_ops(question.step.opts[:id]) - - if supporter['letter_of_support'].present? - = render "support_letters/attachment", question: question, index: index, supporter: supporter, attachment_id: supporter['letter_of_support'] + = render "support_letters/attachment", question: question, index: index, supporter: supporter, attachment_id: supporter['letter_of_support'] diff --git a/app/views/support_letters/_attachment.html.slim b/app/views/support_letters/_attachment.html.slim index 86406f38a..49f374dfc 100644 --- a/app/views/support_letters/_attachment.html.slim +++ b/app/views/support_letters/_attachment.html.slim @@ -1,6 +1,6 @@ - file = support_letter_attachments[attachment_id.to_i] -.support-letter-attachment-container +.support-letter-attachment-container class="#{'govuk-!-display-none' unless attachment_id.present?}" .flex .support-letter-attachment-filename class="govuk-!-font-size-19" = render "shared/attachment_with_virus_check_status", item: file, mount_name: :attachment From 9bb0e70c28548a9882665f3e02b657517dc73a1f Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Thu, 29 Aug 2024 10:17:19 +0100 Subject: [PATCH 19/20] - Show status of attachment when first uploaded. - Correct file name styling to govuk-body. - Update first name label on support letters form. --- .../frontend/custom_questions/support_letters.js.coffee | 3 ++- app/assets/stylesheets/frontend/views/award_form.scss | 6 ------ app/views/form/support_letters/_form.html.slim | 2 +- app/views/qae_form/_supporter_fields.html.slim | 2 +- app/views/support_letters/_attachment.html.slim | 5 ++--- 5 files changed, 6 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee b/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee index ee26595e5..997ec6639 100644 --- a/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee +++ b/app/assets/javascripts/frontend/custom_questions/support_letters.js.coffee @@ -32,7 +32,8 @@ window.SupportLetters = textContainer = parent.find('.support-letter-attachment-container') textContainer.removeClass('govuk-!-display-none') - textContainer.find('.flex').html('

' + filename + '

') + scanningText = '

(File uploaded and is being scanned for viruses. Preview available once the scan is complete.)

' + textContainer.prepend('

' + filename + '

' + scanningText + '
') hiddenInput = $("") parent.append(hiddenInput) parent.find('.js-support-letter-attachment').addClass('govuk-!-display-none') diff --git a/app/assets/stylesheets/frontend/views/award_form.scss b/app/assets/stylesheets/frontend/views/award_form.scss index 8f7269b8d..6de7f48b7 100644 --- a/app/assets/stylesheets/frontend/views/award_form.scss +++ b/app/assets/stylesheets/frontend/views/award_form.scss @@ -164,12 +164,6 @@ label > .visible-read-only, align-items: start; background: $govuk-light-grey; - .flex { - display: flex; - justify-content: space-between; - align-items: start; - } - .js-remove-support-letter-attachment{ color: $govuk-red; word-break: keep-all; diff --git a/app/views/form/support_letters/_form.html.slim b/app/views/form/support_letters/_form.html.slim index 96721fe79..e5f3cdac2 100644 --- a/app/views/form/support_letters/_form.html.slim +++ b/app/views/form/support_letters/_form.html.slim @@ -13,7 +13,7 @@ = render "qae_form/question_ref", question: question, ref: "C #{idx}.1" span[class="govuk-body govuk-!-font-size-24 govuk-!-font-weight-bold govuk-!-display-block"] = "Name of the person who wrote the #{first_or_second} letter of support" - = ff.input :first_name, label: "First Name:", input_html: { class: "form-control medium" } + = ff.input :first_name, label: "First name:", input_html: { class: "form-control medium" } = ff.input :last_name, label: "Surname:", input_html: { class: "form-control medium" } legend.govuk-label aria-label="C #{idx}.2: Relationship to Group" diff --git a/app/views/qae_form/_supporter_fields.html.slim b/app/views/qae_form/_supporter_fields.html.slim index 5717db4ac..e345cd1e9 100644 --- a/app/views/qae_form/_supporter_fields.html.slim +++ b/app/views/qae_form/_supporter_fields.html.slim @@ -20,7 +20,7 @@ li.borderless[class=class_names("js-add-example", "js-support-letter-received" = ' Name of the person who wrote the #{first_or_second} letter of support .govuk-form-group.question-block.question-required label.govuk-label for="form[#{question.key}][#{index}][first_name]" - ' First Name: + ' First name: span.govuk-error-message input.js-support-letter-field.js-support-letter-first-name.js-trigger-autosave.govuk-input autocomplete="off" class="js-trigger-autosave medium" name="form[#{question.key}][#{index}][first_name]" id="form[#{question.key}][#{index}][first_name]" type="text" value=supporter["first_name"] *possible_read_only_ops(question.step.opts[:id]) .govuk-form-group.question-block.question-required diff --git a/app/views/support_letters/_attachment.html.slim b/app/views/support_letters/_attachment.html.slim index 49f374dfc..2d5dd4906 100644 --- a/app/views/support_letters/_attachment.html.slim +++ b/app/views/support_letters/_attachment.html.slim @@ -1,9 +1,8 @@ - file = support_letter_attachments[attachment_id.to_i] .support-letter-attachment-container class="#{'govuk-!-display-none' unless attachment_id.present?}" - .flex - .support-letter-attachment-filename class="govuk-!-font-size-19" - = render "shared/attachment_with_virus_check_status", item: file, mount_name: :attachment + .support-letter-attachment-filename class="govuk-body govuk-!-font-size-19" + = render "shared/attachment_with_virus_check_status", item: file, mount_name: :attachment button.govuk-link.js-remove-support-letter-attachment class="govuk-!-font-size-19" data-module="govuk-button" type="button" | Remove input.js-support-letter-attachment-id type="hidden" name="form[#{question.key}][#{index}][letter_of_support]" value=attachment_id *possible_read_only_ops From f06a4a6b8fb8f292563ea2bf03f80968cef625d0 Mon Sep 17 00:00:00 2001 From: DaniBitZesty <84323332+DaniBitZesty@users.noreply.github.com> Date: Thu, 29 Aug 2024 13:17:24 +0100 Subject: [PATCH 20/20] Remove random square bracket in icon link - causing deployment errors --- app/views/layouts/govuk_template.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/govuk_template.html.erb b/app/views/layouts/govuk_template.html.erb index e536f6ee1..aff39db55 100644 --- a/app/views/layouts/govuk_template.html.erb +++ b/app/views/layouts/govuk_template.html.erb @@ -31,7 +31,7 @@ <%= stylesheet_pack_tag 'application' %> - +