diff --git a/app/assets/stylesheets/dossier_edit.scss b/app/assets/stylesheets/dossier_edit.scss index 0d15e692a..ca32e9e2b 100644 --- a/app/assets/stylesheets/dossier_edit.scss +++ b/app/assets/stylesheets/dossier_edit.scss @@ -42,6 +42,11 @@ $dossier-actions-bar-border-width: 1px; } } + .characters-count { + position: relative; + top: -1rem; + } + .warning { margin-bottom: 20px; background-color: #f9b91666; diff --git a/app/components/editable_champ/textarea_component/textarea_component.en.yml b/app/components/editable_champ/textarea_component/textarea_component.en.yml new file mode 100644 index 000000000..8e9b49901 --- /dev/null +++ b/app/components/editable_champ/textarea_component/textarea_component.en.yml @@ -0,0 +1,3 @@ +en: + remaining_characters: You have %{remaining_words} characters remaining. + excess_characters: You have %{excess_words} characters too many. diff --git a/app/components/editable_champ/textarea_component/textarea_component.fr.yml b/app/components/editable_champ/textarea_component/textarea_component.fr.yml new file mode 100644 index 000000000..ca85e677e --- /dev/null +++ b/app/components/editable_champ/textarea_component/textarea_component.fr.yml @@ -0,0 +1,3 @@ +fr: + remaining_characters: Il vous reste %{remaining_words} caractères. + excess_characters: Vous avez dépassé la taille conseillée de %{excess_words} caractères. diff --git a/app/components/editable_champ/textarea_component/textarea_component.html.haml b/app/components/editable_champ/textarea_component/textarea_component.html.haml index 4eeb28a1c..a19653e0d 100644 --- a/app/components/editable_champ/textarea_component/textarea_component.html.haml +++ b/app/components/editable_champ/textarea_component/textarea_component.html.haml @@ -1,6 +1,20 @@ +- character_limit = @champ.textarea_character_limit.to_i if !@champ.textarea_character_limit&.empty? +- character_count = @champ.character_count(@champ.value) +- analyze_character_count = @champ.analyze_character_count(character_count, character_limit) + ~ @form.text_area :value, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, rows: 6, required: @champ.required?, value: html_to_string(@champ.value) + +- if @champ.textarea_character_limit? + + - if analyze_character_count == :info + %span.fr-icon-information-fill.fr-text-default--info.characters-count + = t('.remaining_characters', remaining_words: @champ.remaining_characters(character_count, character_limit)) + + - if analyze_character_count == :warning + %span.fr-icon-close-circle-fill.fr-text-default--error.characters-count + = t('.excess_characters', excess_words: @champ.excess_characters(character_count, character_limit)) diff --git a/app/components/procedure/revision_changes_component/revision_changes_component.fr.yml b/app/components/procedure/revision_changes_component/revision_changes_component.fr.yml index eac9a5bc8..cb04139a3 100644 --- a/app/components/procedure/revision_changes_component/revision_changes_component.fr.yml +++ b/app/components/procedure/revision_changes_component/revision_changes_component.fr.yml @@ -36,6 +36,8 @@ fr: add_condition: Une condition a été ajoutée sur le champ « %{label} ». La nouvelle condition est « %{to} ». remove_condition: La condition du champ « %{label} » a été supprimée. update_condition: La condition du champ « %{label} » a été modifiée. La nouvelle condition est « %{to} ». + update_textarea_character_limit: La limite de caractères du champ texte « %{label} » a été modifiée. La nouvelle limite est « %{to} ». + remove_textarea_character_limit: La limite de caractères du champ texte « %{label} » a été supprimée. private: add: L’annotation privée « %{label} » a été ajoutée. remove: L’annotation privée « %{label} » a été supprimée. diff --git a/app/components/procedure/revision_changes_component/revision_changes_component.html.haml b/app/components/procedure/revision_changes_component/revision_changes_component.html.haml index 76e42c741..bc4b24c82 100644 --- a/app/components/procedure/revision_changes_component/revision_changes_component.html.haml +++ b/app/components/procedure/revision_changes_component/revision_changes_component.html.haml @@ -130,7 +130,14 @@ = t(".#{prefix}.update_condition", label: change.label, to: change.to) - if !total_dossiers.zero? && !change.can_rebase? .fr-alert.fr-alert--warning.fr-mt-1v - %p= t('.breaking_change', count: total_dossiers) + %p= t('.breakigng_change', count: total_dossiers) + - when :textarea_character_limit + - if change.to.nil? + - list.with_item do + = t(".#{prefix}.remove_textarea_character_limit", label: change.label, to: change.to) + - else + - list.with_item do + = t(".#{prefix}.update_textarea_character_limit", label: change.label, to: change.to) - if @public_move_changes.present? - list.with_item do diff --git a/app/components/types_de_champ_editor/champ_component/champ_component.html.haml b/app/components/types_de_champ_editor/champ_component/champ_component.html.haml index 682cbd322..6de2696fb 100644 --- a/app/components/types_de_champ_editor/champ_component/champ_component.html.haml +++ b/app/components/types_de_champ_editor/champ_component/champ_component.html.haml @@ -101,8 +101,11 @@ = form.label :collapsible_explanation_text, for: dom_id(type_de_champ, :collapsible_explanation_text) do = "Texte à afficher quand l'utiliser a choisi de l'afficher" = form.text_area :collapsible_explanation_text, class: "small-margin small", id: dom_id(type_de_champ, :collapsible_explanation_text) - - + - if type_de_champ.textarea? + .cell + = form.label :textarea_character_limit, for: dom_id(type_de_champ, :textarea_character_limit) do + Spécifier un nombre maximal de caractères (non restrictif) : + = form.number_field :textarea_character_limit, min: 400, value: form.object.textarea_character_limit, id: dom_id(type_de_champ, :textarea_character_limit) - if type_de_champ.block? .flex.justify-start.section.ml-1 diff --git a/app/controllers/administrateurs/types_de_champ_controller.rb b/app/controllers/administrateurs/types_de_champ_controller.rb index c77310dbd..2b87a60ba 100644 --- a/app/controllers/administrateurs/types_de_champ_controller.rb +++ b/app/controllers/administrateurs/types_de_champ_controller.rb @@ -119,28 +119,29 @@ module Administrateurs def type_de_champ_update_params params.required(:type_de_champ).permit(:type_champ, - :libelle, - :description, - :mandatory, - :drop_down_list_value, - :drop_down_other, - :drop_down_secondary_libelle, - :drop_down_secondary_description, - :collapsible_explanation_enabled, - :collapsible_explanation_text, - :header_section_level, - editable_options: [ - :cadastres, - :unesco, - :arretes_protection, - :conservatoire_littoral, - :reserves_chasse_faune_sauvage, - :reserves_biologiques, - :reserves_naturelles, - :natura_2000, - :zones_humides, - :znieff - ]) + :libelle, + :description, + :mandatory, + :drop_down_list_value, + :drop_down_other, + :drop_down_secondary_libelle, + :drop_down_secondary_description, + :collapsible_explanation_enabled, + :collapsible_explanation_text, + :header_section_level, + :textarea_character_limit, + editable_options: [ + :cadastres, + :unesco, + :arretes_protection, + :conservatoire_littoral, + :reserves_chasse_faune_sauvage, + :reserves_biologiques, + :reserves_naturelles, + :natura_2000, + :zones_humides, + :znieff + ]) end def draft diff --git a/app/models/champ.rb b/app/models/champ.rb index 5060baae8..7ff1074d3 100644 --- a/app/models/champ.rb +++ b/app/models/champ.rb @@ -75,6 +75,8 @@ class Champ < ApplicationRecord :mandatory?, :prefillable?, :refresh_after_update?, + :textarea_character_limit?, + :textarea_character_limit, to: :type_de_champ delegate :to_typed_id, :to_typed_id_for_query, to: :type_de_champ, prefix: true diff --git a/app/models/champs/textarea_champ.rb b/app/models/champs/textarea_champ.rb index 9ce4954f8..e729bc1ad 100644 --- a/app/models/champs/textarea_champ.rb +++ b/app/models/champs/textarea_champ.rb @@ -24,4 +24,29 @@ class Champs::TextareaChamp < Champs::TextChamp def for_export value.present? ? ActionView::Base.full_sanitizer.sanitize(value) : nil end + + def character_count(text) + return text&.bytesize + end + + def analyze_character_count(characters, limit) + if characters + threshold_75 = limit * 0.75 + + if characters >= limit + return :warning + elsif characters >= threshold_75 + return :info + end + end + end + + def remaining_characters(characters, limit) + threshold_75 = limit * 0.75 + limit - characters if characters >= threshold_75 + end + + def excess_characters(characters, limit) + characters - limit if characters > limit + end end diff --git a/app/models/procedure_revision.rb b/app/models/procedure_revision.rb index 5e192e166..11b964050 100644 --- a/app/models/procedure_revision.rb +++ b/app/models/procedure_revision.rb @@ -377,6 +377,13 @@ class ProcedureRevision < ApplicationRecord from_type_de_champ.piece_justificative_template_filename, to_type_de_champ.piece_justificative_template_filename) end + elsif to_type_de_champ.textarea? + if from_type_de_champ.textarea_character_limit != to_type_de_champ.textarea_character_limit + changes << ProcedureRevisionChange::UpdateChamp.new(from_type_de_champ, + :textarea_character_limit, + from_type_de_champ.textarea_character_limit, + to_type_de_champ.textarea_character_limit) + end end changes end diff --git a/app/models/type_de_champ.rb b/app/models/type_de_champ.rb index a64e8bdd6..4c738da20 100644 --- a/app/models/type_de_champ.rb +++ b/app/models/type_de_champ.rb @@ -19,6 +19,7 @@ class TypeDeChamp < ApplicationRecord FILE_MAX_SIZE = 200.megabytes FEATURE_FLAGS = {} + MINIMUM_TEXTAREA_CHARACTER_LIMIT_LENGTH = 400 STRUCTURE = :structure ETAT_CIVIL = :etat_civil @@ -118,6 +119,7 @@ class TypeDeChamp < ApplicationRecord :drop_down_secondary_libelle, :drop_down_secondary_description, :drop_down_other, + :textarea_character_limit, :collapsible_explanation_enabled, :collapsible_explanation_text, :header_section_level @@ -177,6 +179,11 @@ class TypeDeChamp < ApplicationRecord validates :libelle, presence: true, allow_blank: false, allow_nil: false validates :type_champ, presence: true, allow_blank: false, allow_nil: false + validates :textarea_character_limit, numericality: { + greater_than_or_equal_to: MINIMUM_TEXTAREA_CHARACTER_LIMIT_LENGTH, + only_integer: true, + allow_nil: true + } before_validation :check_mandatory before_validation :normalize_libelle @@ -235,6 +242,10 @@ class TypeDeChamp < ApplicationRecord drop_down_other == "1" || drop_down_other == true end + def textarea_character_limit? + textarea_character_limit.present? + end + def collapsible_explanation_enabled? collapsible_explanation_enabled == "1" end @@ -340,6 +351,10 @@ class TypeDeChamp < ApplicationRecord type_champ == TypeDeChamp.type_champs.fetch(:number) end + def textarea? + type_champ == TypeDeChamp.type_champs.fetch(:textarea) + end + def titre_identite? type_champ == TypeDeChamp.type_champs.fetch(:titre_identite) end @@ -539,7 +554,8 @@ class TypeDeChamp < ApplicationRecord type_champs.fetch(:multiple_drop_down_list), type_champs.fetch(:dossier_link), type_champs.fetch(:linked_drop_down_list), - type_champs.fetch(:drop_down_list) + type_champs.fetch(:drop_down_list), + type_champs.fetch(:textarea) true else false