Merge pull request #9004 from mfo/poc/error-bellow-inputs

amelioration(champs.erreurs): ETQ usager, je retrouve les erreurs sous les champs + les champs de type text/number sont au format dsfr 🫖🥖
This commit is contained in:
mfo 2023-07-27 06:35:43 +00:00 committed by GitHub
commit cf4048312e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 281 additions and 258 deletions

View file

@ -42,11 +42,6 @@ $dossier-actions-bar-border-width: 1px;
}
}
.characters-count {
position: relative;
top: -1rem;
}
.warning {
margin-bottom: 20px;
background-color: #f9b91666;

View file

@ -20,6 +20,33 @@
font-style: italic;
}
.fr-input-group {
margin-bottom: 1rem;
}
.section-2 {
margin-top: 1.5rem;
padding-top: 1rem;
border-top: 2px solid var(--border-default-grey);
}
.section-2,
.section-3 {
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
.section-4,
.section-5,
.section-6 {
margin-top: 1.5rem;
margin-bottom: 1rem;
}
legend {
width: 100%;
}
hr {
width: 100%;
height: 0;
@ -36,7 +63,7 @@
.mandatory {
color: $dark-red;
fill: currentColor;
}
label:not(.fr-label),
@ -97,7 +124,6 @@
}
&.editable-champ-checkbox {
p,
label {
padding-left: 28px;
font-weight: normal;
@ -147,7 +173,7 @@
user-select: none;
&:last-of-type {
margin-bottom: $default-fields-spacer;
margin-bottom: 0;
}
&:hover {
@ -184,17 +210,20 @@
}
}
input[type=text],
input[type=email],
.fr-label {
// la description d'un champ peut contenir du markup (markdown->html),
// on herite donc la fontsize/mrgin/padding du fr-hint-text
.fr-hint-text > * {
font-size: inherit;
margin: inherit;
padding: inherit;
}
}
input[type=password],
input[type=date],
input[type=datetime-local],
input[type=number],
input[type=tel],
textarea,
select {
display: block;
margin-bottom: $default-fields-spacer;
margin-bottom: 0;
&.small-margin {
margin-bottom: $default-spacer;
@ -218,13 +247,6 @@
}
input[type=text]:not([data-address='true']),
input[type=email],
input[type=password],
input[type=date],
input[type=number],
input[type=tel],
input[type=datetime-local],
textarea,
select {
border-radius: 4px;
border: solid 1px $border-grey;
@ -266,8 +288,8 @@
input[type=date],
input[type=number],
input[type=datetime-local],
input[type=tel],
textarea {
textarea,
input[type=tel], {
@media (max-width: $two-columns-breakpoint) {
width: 100%;
}
@ -326,7 +348,7 @@
margin-left: 5px;
margin-right: 4px;
margin-bottom: $default-fields-spacer;
margin-bottom: 0;
}
[data-reach-combobox-input] {
@ -413,16 +435,6 @@
color: $dark-grey;
}
.datetime {
input[type=date] {
display: inline-block;
}
select {
display: inline-block;
}
}
.header-subsection {
font-size: 22px;
color: $blue-france-500;
@ -445,20 +457,6 @@
}
}
.siret-info {
margin-top: -$default-fields-spacer;
margin-bottom: $default-fields-spacer;
// Ensure the bottom-margin is not collapsed when the element is empty
min-height: 1px;
}
.rna-info {
margin-top: -$default-fields-spacer;
margin-bottom: $default-fields-spacer;
// Ensure the bottom-margin is not collapsed when the element is empty
min-height: 1px;
}
.send-wrapper {
display: flex;
width: 100%;
@ -532,12 +530,6 @@
}
}
[data-react-component-value]:not([data-react-component-value^="ComboMultiple"]) {
[data-reach-combobox-input]:not(.no-margin) {
margin-bottom: $default-fields-spacer;
}
}
[data-react-component-value^="ComboMultiple"] {
margin-bottom: $default-fields-spacer;

View file

@ -52,7 +52,7 @@
opacity: 0.6;
}
.mandatory {
.manager-mandatory {
color: #A10005;
font-size: 18px;
}

View file

@ -1,4 +1,4 @@
.attachment.fr-upload-group{ { id: attachment ? dom_id(attachment, :edit) : nil, class: class_names("fr-mb-2w": !(as_multiple? && downloadable?)) }.compact, **replace_controller_attributes }
.attachment.fr-upload-group{ { id: attachment ? dom_id(attachment, :edit) : nil, class: class_names("fr-mb-1w": !(as_multiple? && downloadable?)) }.compact, **replace_controller_attributes }
- if persisted?
%div{ id: dom_id(attachment, :persisted_row) }
.flex.flex-gap-2{ class: class_names("attachment-error": attachment.virus_scanner_error?) }

View file

@ -1,10 +1,11 @@
.fr-mb-4w.attachment-multiple{ class: class_names("fr-downloads-group": view_as == :download, "destroyable": user_can_destroy?), **replace_controller_attributes }
.attachment-multiple{ class: class_names("fr-downloads-group": view_as == :download, "destroyable": user_can_destroy?), **replace_controller_attributes }
= template
%ul
- each_attachment do |attachment, index|
%li{ id: dom_id(attachment) }
= render Attachment::EditComponent.new(champ:, attached_file:, attachment:, index:, as_multiple: true, view_as:, user_can_destroy:, user_can_replace:, form_object_name:)
- if @attachments.size >= 1
%ul.fr-my-1v
- each_attachment do |attachment, index|
%li{ id: dom_id(attachment) }
= render Attachment::EditComponent.new(champ:, attached_file:, attachment:, index:, as_multiple: true, view_as:, user_can_destroy:, user_can_replace:, form_object_name:)
%div{ id: empty_component_id, class: class_names("hidden": !can_attach_next?) }
= render Attachment::EditComponent.new(champ:, attached_file:, attachment: nil, index: attachments_count, user_can_destroy:, user_can_replace:, form_object_name:)

View file

@ -1,4 +1,6 @@
class Dsfr::InputComponent < ApplicationComponent
include Dsfr::InputErrorable
delegate :object, to: :@form
delegate :errors, to: :object
@ -18,10 +20,10 @@ class Dsfr::InputComponent < ApplicationComponent
# and and valid on input only if another input is invalid
def input_group_opts
opts = {
class: class_names('fr-input-group': true,
'fr-password': password?,
"fr-input-group--error": errors_on_attribute?,
"fr-input-group--valid": !errors_on_attribute? && errors_on_another_attribute?)
class: class_names({
'fr-input-group': true,
'fr-password': password?
}.merge(input_group_error_class_names))
}
if email?
opts[:data] = { controller: 'email-input' }
@ -33,38 +35,7 @@ class Dsfr::InputComponent < ApplicationComponent
{ class: class_names('fr-label': true, 'fr-password__label': password?) }
end
def input_opts
@opts[:class] = class_names(map_array_to_hash_with_true(@opts[:class])
.merge('fr-password__input': password?,
'fr-input': true,
'fr-mb-0': true,
'fr-input--error': errors_on_attribute?))
if errors_on_attribute? || describedby?
@opts.deep_merge!(aria: {
describedby: describedby_id,
invalid: errors_on_attribute?
})
end
if @required
@opts[:required] = true
end
if email?
@opts.deep_merge!(data: {
action: "blur->email-input#checkEmail",
'email-input-target': 'input'
})
end
@opts
end
# errors helpers
def errors_on_attribute?
errors.has_key?(attribute_or_rich_body)
end
def error_messages
errors.full_messages_for(attribute_or_rich_body)
end
@ -78,10 +49,6 @@ class Dsfr::InputComponent < ApplicationComponent
object.class.human_attribute_name(@attribute)
end
def hint
I18n.t("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
end
# kind of input helpers
def password?
@input_type == :password_field
@ -96,27 +63,4 @@ class Dsfr::InputComponent < ApplicationComponent
end
private
def hint?
I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
end
def errors_on_another_attribute?
!errors.empty?
end
# lookup for edge case from `form.rich_text_area`
# rich text uses _rich_#{attribute}, but it is saved on #{attribute}, as well as error messages
def attribute_or_rich_body
case @input_type
when :rich_text_area
@attribute.to_s.sub(/\Arich_/, '').to_sym
else
@attribute
end
end
def map_array_to_hash_with_true(array_or_string_or_nil)
Array(array_or_string_or_nil).to_h { [_1, true] }
end
end

View file

@ -0,0 +1,108 @@
module Dsfr
module InputErrorable
extend ActiveSupport::Concern
included do
delegate :object, to: :@form
delegate :errors, to: :object
private
# lookup for edge case from `form.rich_text_area`
# rich text uses _rich_#{attribute}, but it is saved on #{attribute}, as well as error messages
def attribute_or_rich_body
case @input_type
when :rich_text_area
@attribute.to_s.sub(/\Arich_/, '').to_sym
else
@attribute
end
end
def input_group_error_class_names
{
"fr-input-group--error": errors_on_attribute?,
"fr-input-group--valid": !errors_on_attribute? && errors_on_another_attribute?
}
end
def input_error_class_names
{ 'fr-input--error': errors_on_attribute? }
end
def input_error_opts
{
aria: {
describedby: describedby_id,
invalid: errors_on_attribute?
}
}
end
def input_opts(other_opts = {})
@opts = @opts.deep_merge!(other_opts)
@opts[:class] = class_names(map_array_to_hash_with_true(@opts[:class])
.merge({
'fr-password__input': password?,
'fr-input': true,
'fr-mb-0': true
}.merge(input_error_class_names)))
if errors_on_attribute?
@opts.deep_merge!(aria: {
describedby: describedby_id,
invalid: errors_on_attribute?
})
end
if @required
@opts[:required] = true
end
if email?
@opts.deep_merge!(data: {
action: "blur->email-input#checkEmail",
'email-input-target': 'input'
})
end
@opts
end
def describedby_id
dom_id(@champ, :error_full_messages)
end
def errors_on_another_attribute?
!errors.empty?
end
def errors_on_attribute?
errors.has_key?(attribute_or_rich_body)
end
# errors helpers
def error_full_messages
errors.full_messages_for(attribute_or_rich_body)
end
def map_array_to_hash_with_true(array_or_string_or_nil)
Array(array_or_string_or_nil).to_h { [_1, true] }
end
def hint
I18n.t("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
end
def password?
false
end
def email?
false
end
def hint?
I18n.exists?("activerecord.attributes.#{object.class.name.underscore}.hints.#{@attribute}")
end
end
end
end

View file

@ -1,5 +1,8 @@
class EditableChamp::ChampLabelComponent < ApplicationComponent
include Dsfr::InputErrorable
def initialize(form:, champ:, seen_at: nil)
@form, @champ, @seen_at = form, champ, seen_at
@attribute = :value
end
end

View file

@ -1,10 +1,9 @@
= # we do this trick because some html elements should use 'label' and some should be plain paragraphs
- if @champ.html_label?
= @form.label @champ.main_value_name, id: @champ.labelledby_id, for: @champ.input_id do
- render EditableChamp::ChampLabelContentComponent.new champ: @champ, seen_at: @seen_at
= @form.label @champ.main_value_name, id: @champ.labelledby_id, for: @champ.input_id, class: 'fr-label' do
- render EditableChamp::ChampLabelContentComponent.new form: @form, champ: @champ, seen_at: @seen_at
- else
.form-label.mb-4{ id: @champ.labelledby_id }
= render EditableChamp::ChampLabelContentComponent.new champ: @champ, seen_at: @seen_at
.fr-label.mb-4{ id: @champ.labelledby_id }
= render EditableChamp::ChampLabelContentComponent.new form: @form, champ: @champ, seen_at: @seen_at
- if @champ.description.present?
.notice{ id: @champ.describedby_id }= render SimpleFormatComponent.new(@champ.description, allow_a: true)

View file

@ -1,8 +1,10 @@
class EditableChamp::ChampLabelContentComponent < ApplicationComponent
include ApplicationHelper
include Dsfr::InputErrorable
def initialize(champ:, seen_at: nil)
@champ, @seen_at = champ, seen_at
def initialize(form:, champ:, seen_at: nil)
@form, @champ, @seen_at = form, champ, seen_at
@attribute = :value
end
def highlight_if_unseen_class

View file

@ -4,3 +4,5 @@ en:
modified_at: "modified on %{datetime}"
check_content_rebased: The type of this field or its description has been modified by the administration. Check its content.
optional_champ: (optional)
recommended_size: The recommended maximum size is %{size} characters.
titre_identite_notice: National identity card (front side only), passport, residency permit or other proof of identity.

View file

@ -4,3 +4,5 @@ fr:
modified_at: "modifié le %{datetime}"
check_content_rebased: Le type de ce champ ou sa description ont été modifiés par l'administration. Vérifier son contenu.
optional_champ: (facultatif)
recommended_size: La taille maximale conseillée est de %{size} caractères.
titre_identite_notice: Carte nationale didentité (uniquement le recto), passeport, titre de séjour ou autre justificatif didentité.

View file

@ -18,3 +18,15 @@
- if @champ.rebased_at.present? && @champ.rebased_at > (@seen_at || @champ.updated_at) && current_user.owns_or_invite?(@champ.dossier)
%span.updated-at.highlighted
= t('.check_content_rebased')
- if @champ.titre_identite?
%span.fr-hint-text= t('.titre_identite_notice')
- if hint?
%span.fr-hint-text= hint
- if @champ.description.present?
%span.fr-hint-text{ id: @champ.describedby_id }= render SimpleFormatComponent.new(@champ.description, allow_a: true)
- if @champ.textarea?
%span.sr-only= t('.recommended_size', size: @champ.character_limit_base)

View file

@ -1,7 +1,6 @@
= @form.date_field :value,
id: @champ.input_id,
input_opts(id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
value: @champ.value,
required: @champ.required?,
placeholder: 'aaaa-mm-jj',
class: "width-33-desktop"
placeholder: 'aaaa-mm-jj', class: "width-33-desktop")

View file

@ -1,2 +1 @@
.datetime
= @form.datetime_field(:value, value: formatted_value_for_datetime_locale, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, data: { controller: 'datetime' })
= @form.datetime_field(:value, input_opts(value: formatted_value_for_datetime_locale, id: @champ.input_id, aria: { describedby: @champ.describedby_id }, data: { controller: 'datetime' }))

View file

@ -1,6 +1 @@
= @form.number_field :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
step: :any,
placeholder: "3.14",
required: @champ.required?
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, step: :any, placeholder: "3.14", required: @champ.required?))

View file

@ -1,18 +1,8 @@
.dossier-link
= @form.text_field :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
inputmode: :numeric,
min: 1,
pattern: "[0-9]{1,12}",
placeholder: "Numéro de dossier",
autocomplete: 'off',
required: @champ.required?,
class: "width-33-desktop #{@champ.blank? ? '' : 'small-margin'}"
= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, inputmode: :numeric, min: 1, pattern: "[0-9]{1,12}", placeholder: "Numéro de dossier", autocomplete: 'off', required: @champ.required?, class: "width-33-desktop #{@champ.blank? ? '' : 'small-margin'}"))
- if !@champ.blank?
- if dossier.blank?
.fr-error-text.fr-mb-4w
= t('.not_found')
- else
.fr-info-text.fr-mb-4w= sanitize(dossier.text_summary)
- if !@champ.blank?
- if dossier.blank?
.fr-error-text.fr-mb-4w
= t('.not_found')
- else
.fr-info-text.fr-mb-4w= sanitize(dossier.text_summary)

View file

@ -1,5 +1,8 @@
class EditableChamp::EditableChampBaseComponent < ApplicationComponent
def initialize(form:, champ:, seen_at: nil)
@form, @champ, @seen_at = form, champ, seen_at
include Dsfr::InputErrorable
def initialize(form:, champ:, seen_at: nil, opts: {})
@form, @champ, @seen_at, @opts = form, champ, seen_at, opts
@attribute = :value
end
end

View file

@ -1,6 +1,9 @@
class EditableChamp::EditableChampComponent < ApplicationComponent
include Dsfr::InputErrorable
def initialize(form:, champ:, seen_at: nil)
@form, @champ, @seen_at = form, champ, seen_at
@attribute = :value
end
private
@ -21,8 +24,12 @@ class EditableChamp::EditableChampComponent < ApplicationComponent
def html_options
{
class: class_names(
"editable-champ-#{@champ.type_champ}": true,
"hidden": !@champ.visible?
{
'editable-champ': true,
"editable-champ-#{@champ.type_champ}": true,
"hidden": !@champ.visible?,
"fr-input-group": true
}.merge(input_group_error_class_names)
),
id: @champ.input_group_id,
data: { controller: stimulus_controller, **data_dependent_conditions, **stimulus_values }

View file

@ -1,4 +1,4 @@
---
en:
titre_identite_notice: National identity card (front side only), passport, residency permit or other proof of identity.
datetime_notice: "Expected format : dd/mm/yyyy hh:mm"

View file

@ -1,4 +1,3 @@
---
fr:
titre_identite_notice: Carte nationale didentité (uniquement le recto), passeport, titre de séjour ou autre justificatif didentité.
datetime_notice: "Format attendu : jj/mm/aaaa hh:mm"

View file

@ -1,10 +1,15 @@
.editable-champ{ html_options }
= content_tag(:div, html_options) do
- if has_label?(@champ)
= render EditableChamp::ChampLabelComponent.new form: @form, champ: @champ, seen_at: @seen_at
- if @champ.titre_identite?
%p.notice= t('.titre_identite_notice')
- elsif @champ.datetime?
%p.notice= t('.datetime_notice')
= render component_class.new(form: @form, champ: @champ, seen_at: @seen_at)
- if errors_on_attribute?
- if error_full_messages.size == 1
%p.fr-error-text{ id: describedby_id }= error_full_messages.first
- else
.fr-error-text{ id: describedby_id }
%ul.list-style-type-none.fr-pl-0
- error_full_messages.each do |error_message|
%li= error_message
= @form.hidden_field :id, value: @champ.id
= render component_class.new(form: @form, champ: @champ, seen_at: @seen_at)

View file

@ -1,2 +1,5 @@
class EditableChamp::EmailComponent < EditableChamp::EditableChampBaseComponent
def email?
true
end
end

View file

@ -1,5 +1 @@
= @form.email_field :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
placeholder: t(".placeholder"),
required: @champ.required?
= @form.email_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: t(".placeholder"), required: @champ.required?))

View file

@ -1,4 +1,4 @@
.explication
%p.fr-info-text
= raison_sociale_or_name(@etablissement)
= @etablissement.entreprise_forme_juridique
- if @etablissement.entreprise_capital_social.present?

View file

@ -1,8 +1 @@
= @form.text_field :value,
id: @champ.input_id,
placeholder: t(".placeholder"),
required: @champ.required?,
aria: { describedby: @champ.describedby_id },
data: { controller: 'format', format: 'iban' },
class: "width-66-desktop",
maxlength: 34 + 9 # count space separator of 4 digits-groups
= @form.text_field(:value, input_opts(id: @champ.input_id, placeholder: t(".placeholder"), required: @champ.required?, aria: { describedby: @champ.describedby_id }, data: { controller: 'format', format: 'iban' }, class: "width-66-desktop", maxlength: 34 + 9)) # count space separator of 4 digits-groups

View file

@ -1,5 +1 @@
= @form.number_field :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
placeholder: 5,
required: @champ.required?
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: 5, required: @champ.required?))

View file

@ -1,5 +1 @@
= @form.number_field :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
placeholder: @champ.libelle,
required: @champ.required?
= @form.number_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: @champ.libelle, required: @champ.required?))

View file

@ -1,9 +1,4 @@
-# Allowed @formats:
-# very light validation is made client-side
-# stronger validation is made server-side
= @form.phone_field :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
placeholder: t(".placeholder"),
required: @champ.required?,
pattern: "[^a-z^A-Z]+"
= @form.phone_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: t(".placeholder"), required: @champ.required?, pattern: "[^a-z^A-Z]+"))

View file

@ -1,12 +1,4 @@
= @form.text_field :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
placeholder: t(".placeholder"),
data: { controller: 'turbo-input', turbo_input_load_on_connect_value: @champ.prefilled? && @champ.value.present? && @champ.data.blank?, turbo_input_url_value: champs_rna_path(@champ.id) },
required: @champ.required?,
pattern: "W[0-9]{9}",
title: t(".title"),
class: "width-33-desktop",
maxlength: 10
= @form.text_field(:value, input_opts( id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: t(".placeholder"), data: { controller: 'turbo-input', turbo_input_load_on_connect_value: @champ.prefilled? && @champ.value.present? && @champ.data.blank?, turbo_input_url_value: champs_rna_path(@champ.id) }, required: @champ.required?, pattern: "W[0-9]{9}", title: t(".title"), class: "width-33-desktop", maxlength: 10))
.rna-info{ id: dom_id(@champ, :rna_info) }
= render 'shared/champs/rna/association', champ: @champ, error: nil

View file

@ -1,13 +1,4 @@
= @form.text_field :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
placeholder: t(".placeholder"),
data: { controller: 'turbo-input', turbo_input_load_on_connect_value: @champ.prefilled? && @champ.value.present? && @champ.etablissement.blank?, turbo_input_url_value: champs_siret_path(@champ.id) },
required: @champ.required?,
pattern: "[0-9]{14}",
title: t(".title"),
class: "width-33-desktop",
maxlength: 14
= @form.text_field(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, placeholder: t(".placeholder"), data: { controller: 'turbo-input', turbo_input_load_on_connect_value: @champ.prefilled? && @champ.value.present? && @champ.etablissement.blank?, turbo_input_url_value: champs_siret_path(@champ.id) }, required: @champ.required?, pattern: "[0-9]{14}", title: t(".title"), class: "width-33-desktop", maxlength: 14))
.spinner.right.hidden
.siret-info{ id: dom_id(@champ, :siret_info) }
- if @champ.etablissement.present?

View file

@ -1,4 +1 @@
= @form.text_field :value,
id: @champ.input_id,
required: @champ.required?,
aria: { describedby: @champ.describedby_id }
= @form.text_field(:value, input_opts(id: @champ.input_id, required: @champ.required?, aria: { describedby: @champ.describedby_id }))

View file

@ -1,4 +1,3 @@
en:
remaining_characters: You have %{remaining_words} characters remaining.
excess_characters: You have %{excess_words} characters too many.
recommended_size: The recommended maximum size is %{size} characters.

View file

@ -1,4 +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. Réduire le nombre de caractères.
recommended_size: La taille maximale conseillée est de %{size} caractères.

View file

@ -1,16 +1,9 @@
%span.sr-only= t('.recommended_size', size: @champ.character_limit_base)
~ @form.text_area :value,
id: @champ.input_id,
aria: { describedby: @champ.describedby_id },
rows: 6,
required: @champ.required?,
value: html_to_string(@champ.value)
~ @form.text_area(:value, input_opts(id: @champ.input_id, aria: { describedby: @champ.describedby_id }, rows: 6, required: @champ.required?, value: html_to_string(@champ.value)))
- if @champ.character_limit_info?
%span.fr-icon-information-fill.fr-text-default--info.characters-count
%p.fr-info-text
= t('.remaining_characters', remaining_words: @champ.remaining_characters)
- if @champ.character_limit_warning?
%span.fr-icon-warning-fill.fr-text-default--warning.characters-count
%p.fr-icon--sm.fr-mt-4v.fr-mb-0.fr-hint-text.fr-icon-warning-fill.fr-text-default--warning.characters-count
= t('.excess_characters', excess_words: @champ.excess_characters)

View file

@ -57,6 +57,7 @@ class Champ < ApplicationRecord
:dossier_link?,
:departement?,
:region?,
:textarea?,
:titre_identite?,
:header_section?,
:checkbox?,

View file

@ -2,7 +2,7 @@
%td.cell-data
= type_de_champ.libelle
- if type_de_champ.mandatory?
%span.mandatory *
%span.manager-mandatory *
%td.cell-data
= I18n.t("activerecord.attributes.type_de_champ.type_champs.#{type_de_champ.type_champ}")

View file

@ -1,11 +1,11 @@
- case error
- when :invalid
%p.pt-1
%p.fr-error-text
Le numéro RNA doit commencer par un W majuscule suivi de 9 chiffres
- when :not_found
%p.pt-1= t('.not_found')
%p.fr-error-text= t('.not_found')
- when :network_error
%p.pt-1= t('.network_error')
%p.fr-error-text= t('.network_error')
- else
- if champ.value.present?
%p.pt-1= t('.data_fetched', title: champ.title)
%p.fr-info-text= t('.data_fetched', title: champ.title)

View file

@ -1,19 +1,20 @@
- case siret
- when :invalid_length
= t('errors.messages.invalid_siret_length')
%p.fr-error-text= t('errors.messages.invalid_siret_length')
- when :invalid_checksum
= t('errors.messages.invalid_siret_checksum')
%p.fr-error-text= t('errors.messages.invalid_siret_checksum')
- when :not_found
Nous navons pas trouvé détablissement correspondant à ce numéro de SIRET.
= link_to('Plus dinformations', t("links.common.faq.erreur_siret_url"), **external_link_attributes)
%p.fr-error-text
Nous navons pas trouvé détablissement correspondant à ce numéro de SIRET.
= link_to('Plus dinformations', t("links.common.faq.erreur_siret_url"), **external_link_attributes)
- when :network_error
= t('errors.messages.siret_network_error')
%p.fr-error-text= t('errors.messages.siret_network_error')
- when :api_entreprise_down
= t('errors.messages.api_entreprise_down')
%p.fr-error-text= t('errors.messages.api_entreprise_down')
- when :empty
// NOOP
@ -22,4 +23,4 @@
- if siret == etablissement&.siret && raison_sociale_or_name(etablissement).present?
= render EditableChamp::EtablissementTitreComponent.new(etablissement: etablissement)
- else
Ce SIRET existe, nous en récupérons les informations.
%p.fr-info-text Ce SIRET existe, nous en récupérons les informations.

View file

@ -9,7 +9,7 @@
= render EditableChamp::EditableChampComponent.new champ:, form:
- else
= turbo_stream.update champ.labelledby_id do
= render EditableChamp::ChampLabelContentComponent.new champ:
= render EditableChamp::ChampLabelContentComponent.new champ:, form:
= turbo_stream.remove_all(".editable-champ .spinner-removable");
= turbo_stream.hide_all(".editable-champ .spinner");

View file

@ -0,0 +1,7 @@
en:
activerecord:
attributes:
champs/datetime_champ:
hints:
value: "Expected format : dd/mm/yyyy hh:mm"

View file

@ -0,0 +1,7 @@
fr:
activerecord:
attributes:
champs/datetime_champ:
hints:
value: "Format attendu : jj/mm/aaaa hh:mm"

View file

@ -419,7 +419,7 @@ describe 'The user' do
let(:procedure) do
create(:procedure, :published, :for_individual,
types_de_champ_public: [
{ type: :integer_number, libelle: 'age', stable_id: age_stable_id },
{ type: :integer_number, libelle: 'age du candidat', stable_id: age_stable_id },
{ type: :yes_no, libelle: 'permis de conduire', stable_id: permis_stable_id, condition: permis_condition },
{ type: :header_section, libelle: 'info voiture', condition: permis_condition },
{ type: :integer_number, libelle: 'tonnage', stable_id: tonnage_stable_id, condition: tonnage_condition },
@ -432,12 +432,12 @@ describe 'The user' do
fill_individual
expect(page).to have_css('label', text: 'age', visible: true)
expect(page).to have_css('label', text: 'age du candidat', visible: true)
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('legend h2', text: 'info voiture', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
fill_in('age (facultatif)', with: '18')
fill_in('age du candidat (facultatif)', with: '18')
expect(page).to have_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_css('legend h2', text: 'info voiture', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
@ -453,7 +453,7 @@ describe 'The user' do
fill_in('tonnage (facultatif)', with: 'a')
expect(page).to have_no_css('label', text: 'parking', visible: true)
fill_in('age (facultatif)', with: '2')
fill_in('age du candidat (facultatif)', with: '2')
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
@ -461,11 +461,11 @@ describe 'The user' do
click_on 'Accéder à votre dossier'
click_on 'Modifier mon dossier'
expect(page).to have_css('label', text: 'age', visible: true)
expect(page).to have_css('label', text: 'age du candidat', visible: true)
expect(page).to have_no_css('label', text: 'permis de conduire', visible: true)
expect(page).to have_no_css('label', text: 'tonnage', visible: true)
fill_in('age (facultatif)', with: '18')
fill_in('age du candidat (facultatif)', with: '18')
wait_for_autosave(false)
# the champ keeps their previous value so they are all displayed