Merge pull request #6553 from betagouv/feat/6517

ETQ Administrateur je voudrai pouvoir ajouter une option autre aux menus déroulants
This commit is contained in:
Kara Diaby 2021-10-27 12:52:37 +02:00 committed by GitHub
commit 1fee02e6e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 173 additions and 8 deletions

View file

@ -57,6 +57,7 @@ module NewAdministrateur
], ],
methods: [ methods: [
:drop_down_list_value, :drop_down_list_value,
:drop_down_other,
:drop_down_secondary_libelle, :drop_down_secondary_libelle,
:drop_down_secondary_description, :drop_down_secondary_description,
:piece_justificative_template_filename, :piece_justificative_template_filename,
@ -75,6 +76,7 @@ module NewAdministrateur
:parent_id, :parent_id,
:private, :private,
:drop_down_list_value, :drop_down_list_value,
:drop_down_other,
:drop_down_secondary_libelle, :drop_down_secondary_libelle,
:drop_down_secondary_description, :drop_down_secondary_description,
:piece_justificative_template, :piece_justificative_template,
@ -98,6 +100,7 @@ module NewAdministrateur
:description, :description,
:mandatory, :mandatory,
:drop_down_list_value, :drop_down_list_value,
:drop_down_other,
:drop_down_secondary_libelle, :drop_down_secondary_libelle,
:drop_down_secondary_description, :drop_down_secondary_description,
:piece_justificative_template, :piece_justificative_template,

View file

@ -337,8 +337,8 @@ module Users
def champs_params def champs_params
params.permit(dossier: { params.permit(dossier: {
champs_attributes: [ champs_attributes: [
:id, :value, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, value: [], :id, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, value: [],
champs_attributes: [:id, :_destroy, :value, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, value: []] champs_attributes: [:id, :_destroy, :value, :value_other, :external_id, :primary_value, :secondary_value, :numero_allocataire, :code_postal, :piece_justificative_file, value: []]
] ]
}) })
end end

View file

@ -11,6 +11,7 @@ import MoveButton from './MoveButton';
import TypeDeChampCarteOption from './TypeDeChampCarteOption'; import TypeDeChampCarteOption from './TypeDeChampCarteOption';
import TypeDeChampCarteOptions from './TypeDeChampCarteOptions'; import TypeDeChampCarteOptions from './TypeDeChampCarteOptions';
import TypeDeChampDropDownOptions from './TypeDeChampDropDownOptions'; import TypeDeChampDropDownOptions from './TypeDeChampDropDownOptions';
import TypeDeChampDropDownOther from './TypeDeChampDropDownOther';
import TypeDeChampPieceJustificative from './TypeDeChampPieceJustificative'; import TypeDeChampPieceJustificative from './TypeDeChampPieceJustificative';
import TypeDeChampRepetitionOptions from './TypeDeChampRepetitionOptions'; import TypeDeChampRepetitionOptions from './TypeDeChampRepetitionOptions';
import TypeDeChampTypesSelect from './TypeDeChampTypesSelect'; import TypeDeChampTypesSelect from './TypeDeChampTypesSelect';
@ -24,6 +25,7 @@ const TypeDeChamp = sortableElement(
'linked_drop_down_list' 'linked_drop_down_list'
].includes(typeDeChamp.type_champ); ].includes(typeDeChamp.type_champ);
const isLinkedDropDown = typeDeChamp.type_champ === 'linked_drop_down_list'; const isLinkedDropDown = typeDeChamp.type_champ === 'linked_drop_down_list';
const isSimpleDropDown = typeDeChamp.type_champ === 'drop_down_list';
const isFile = typeDeChamp.type_champ === 'piece_justificative'; const isFile = typeDeChamp.type_champ === 'piece_justificative';
const isCarte = typeDeChamp.type_champ === 'carte'; const isCarte = typeDeChamp.type_champ === 'carte';
const isExplication = typeDeChamp.type_champ === 'explication'; const isExplication = typeDeChamp.type_champ === 'explication';
@ -137,6 +139,10 @@ const TypeDeChamp = sortableElement(
libelleHandler={updateHandlers.drop_down_secondary_libelle} libelleHandler={updateHandlers.drop_down_secondary_libelle}
descriptionHandler={updateHandlers.drop_down_secondary_description} descriptionHandler={updateHandlers.drop_down_secondary_description}
/> />
<TypeDeChampDropDownOther
isVisible={isSimpleDropDown}
handler={updateHandlers.drop_down_other}
/>
<TypeDeChampPieceJustificative <TypeDeChampPieceJustificative
isVisible={isFile} isVisible={isFile}
directUploadUrl={state.directUploadUrl} directUploadUrl={state.directUploadUrl}
@ -241,6 +247,7 @@ const OPTIONS_FIELDS = {
export const FIELDS = [ export const FIELDS = [
'description', 'description',
'drop_down_list_value', 'drop_down_list_value',
'drop_down_other',
'libelle', 'libelle',
'mandatory', 'mandatory',
'parent_id', 'parent_id',

View file

@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
function TypeDeChampDropDownOther({ isVisible, handler }) {
if (isVisible) {
return (
<div className="cell">
<label htmlFor={handler.id}>
<input
type="checkbox"
id={handler.id}
name={handler.name}
checked={!!handler.value}
onChange={handler.onChange}
className="small-margin small"
/>
Proposer une option &apos;autre&apos; avec un texte libre
</label>
</div>
);
}
return null;
}
TypeDeChampDropDownOther.propTypes = {
isVisible: PropTypes.bool,
handler: PropTypes.object
};
export default TypeDeChampDropDownOther;

View file

@ -0,0 +1,20 @@
import { delegate, show, hide } from '@utils';
delegate(
'change',
'.editable-champ-drop_down_list select, .editable-champ-drop_down_list input[type="radio"]',
(event) => {
const parent = event.target.closest('.editable-champ-drop_down_list');
const inputGroup = parent?.querySelector('.drop_down_other');
if (inputGroup) {
const input = inputGroup.querySelector('input');
if (event.target.value === '__other__') {
show(inputGroup);
input.disabled = false;
} else {
hide(inputGroup);
input.disabled = true;
}
}
}
);

View file

@ -27,6 +27,7 @@ import '../new_design/dossiers/auto-upload';
import '../new_design/champs/carte'; import '../new_design/champs/carte';
import '../new_design/champs/linked-drop-down-list'; import '../new_design/champs/linked-drop-down-list';
import '../new_design/champs/repetition'; import '../new_design/champs/repetition';
import '../new_design/champs/drop-down-list';
import { import {
toggleCondidentielExplanation, toggleCondidentielExplanation,

View file

@ -38,6 +38,7 @@ class Champ < ApplicationRecord
:mandatory?, :mandatory?,
:description, :description,
:drop_down_list_options, :drop_down_list_options,
:drop_down_other,
:drop_down_list_options?, :drop_down_list_options?,
:drop_down_list_disabled_options, :drop_down_list_disabled_options,
:drop_down_list_enabled_non_empty_options, :drop_down_list_enabled_non_empty_options,

View file

@ -21,6 +21,7 @@
# #
class Champs::DropDownListChamp < Champ class Champs::DropDownListChamp < Champ
THRESHOLD_NB_OPTIONS_AS_RADIO = 5 THRESHOLD_NB_OPTIONS_AS_RADIO = 5
OTHER = '__other__'
def render_as_radios? def render_as_radios?
enabled_non_empty_options.size <= THRESHOLD_NB_OPTIONS_AS_RADIO enabled_non_empty_options.size <= THRESHOLD_NB_OPTIONS_AS_RADIO
@ -31,8 +32,16 @@ class Champs::DropDownListChamp < Champ
end end
def options def options
if drop_down_other?
drop_down_list_options + [["Autre", OTHER]]
else
drop_down_list_options drop_down_list_options
end end
end
def selected
other_value_present? ? OTHER : value
end
def disabled_options def disabled_options
drop_down_list_disabled_options drop_down_list_disabled_options
@ -41,4 +50,26 @@ class Champs::DropDownListChamp < Champ
def enabled_non_empty_options def enabled_non_empty_options
drop_down_list_enabled_non_empty_options drop_down_list_enabled_non_empty_options
end end
def other_value_present?
drop_down_other? && value.present? && drop_down_list_options.exclude?(value)
end
def drop_down_other?
drop_down_other
end
def value=(value)
if value != OTHER
write_attribute(:value, value)
end
end
def value_other=(value)
write_attribute(:value, value)
end
def value_other
other_value_present? ? value : ""
end
end end

View file

@ -243,6 +243,17 @@ class ProcedureRevision < ApplicationRecord
} }
end end
end end
if from_type_de_champ.drop_down_other != to_type_de_champ.drop_down_other
changes << {
op: :update,
attribute: :drop_down_other,
label: from_type_de_champ.libelle,
private: from_type_de_champ.private?,
from: from_type_de_champ.drop_down_other,
to: to_type_de_champ.drop_down_other,
stable_id: from_type_de_champ.stable_id
}
end
elsif to_type_de_champ.carte? elsif to_type_de_champ.carte?
if from_type_de_champ.carte_optional_layers != to_type_de_champ.carte_optional_layers if from_type_de_champ.carte_optional_layers != to_type_de_champ.carte_optional_layers
changes << { changes << {

View file

@ -58,7 +58,7 @@ class TypeDeChamp < ApplicationRecord
belongs_to :parent, class_name: 'TypeDeChamp', optional: true belongs_to :parent, class_name: 'TypeDeChamp', optional: true
has_many :types_de_champ, -> { ordered }, foreign_key: :parent_id, class_name: 'TypeDeChamp', inverse_of: :parent, dependent: :destroy has_many :types_de_champ, -> { ordered }, foreign_key: :parent_id, class_name: 'TypeDeChamp', inverse_of: :parent, dependent: :destroy
store_accessor :options, :cadastres, :old_pj, :drop_down_options, :skip_pj_validation, :skip_content_type_pj_validation, :drop_down_secondary_libelle, :drop_down_secondary_description store_accessor :options, :cadastres, :old_pj, :drop_down_options, :skip_pj_validation, :skip_content_type_pj_validation, :drop_down_secondary_libelle, :drop_down_secondary_description, :drop_down_other
has_many :revision_types_de_champ, class_name: 'ProcedureRevisionTypeDeChamp', dependent: :destroy, inverse_of: :type_de_champ has_many :revision_types_de_champ, class_name: 'ProcedureRevisionTypeDeChamp', dependent: :destroy, inverse_of: :type_de_champ
has_many :revisions, through: :revision_types_de_champ has_many :revisions, through: :revision_types_de_champ
@ -331,6 +331,7 @@ class TypeDeChamp < ApplicationRecord
], ],
methods: [ methods: [
:drop_down_list_value, :drop_down_list_value,
:drop_down_other,
:piece_justificative_template_filename, :piece_justificative_template_filename,
:piece_justificative_template_url, :piece_justificative_template_url,
:editable_options, :editable_options,

View file

@ -41,6 +41,11 @@
%li= t(:add_option, scope: [:new_administrateur, :revision_changes], items: added.map{ |term| "« #{term.strip} »" }.join(", ")) %li= t(:add_option, scope: [:new_administrateur, :revision_changes], items: added.map{ |term| "« #{term.strip} »" }.join(", "))
- if removed.present? - if removed.present?
%li= t(:remove_option, scope: [:new_administrateur, :revision_changes], items: removed.map{ |term| "« #{term.strip} »" }.join(", ")) %li= t(:remove_option, scope: [:new_administrateur, :revision_changes], items: removed.map{ |term| "« #{term.strip} »" }.join(", "))
- when :drop_down_other
- if change[:from] == false
%li.mb-1= t("new_administrateur.revision_changes.update_drop_down_other#{postfix}.enabled", label: change[:label])
- else
%li.mb-1= t("new_administrateur.revision_changes.update_drop_down_other#{postfix}.disabled", label: change[:label])
- when :carte_layers - when :carte_layers
- added = change[:to].sort - change[:from].sort - added = change[:to].sort - change[:from].sort
- removed = change[:from].sort - change[:to].sort - removed = change[:from].sort - change[:to].sort

View file

@ -0,0 +1,4 @@
.drop_down_other{ class: champ.other_value_present? ? '' : 'hidden' }
.notice
%p Veuillez saisir votre autre choix
= form.text_field :value_other, maxlength: "200", placeholder: "Saisissez ici", disabled: !champ.other_value_present?

View file

@ -5,13 +5,18 @@
%label %label
= form.radio_button :value, option = form.radio_button :value, option
= option = option
- if !champ.mandatory? - if !champ.mandatory?
%label.blank-radio %label.blank-radio
= form.radio_button :value, '' = form.radio_button :value, ''
Non renseigné Non renseigné
- if champ.drop_down_other?
%label
= form.radio_button :value, Champs::DropDownListChamp::OTHER, checked: champ.other_value_present?
Autre
- else - else
= form.select :value, = form.select :value, champ.options, selected: champ.selected, required: champ.mandatory?
champ.options,
disabled: champ.disabled_options, - if champ.drop_down_other?
required: champ.mandatory? = render partial: "shared/dossiers/drop_down_other_input", locals: { form: form, champ: champ }

View file

@ -17,6 +17,9 @@ fr:
disabled: Le champ « %{label} » nest plus obligatoire disabled: Le champ « %{label} » nest plus obligatoire
update_piece_justificative_template: Le modèle de pièce justificative du champ « %{label} » a été modifié update_piece_justificative_template: Le modèle de pièce justificative du champ « %{label} » a été modifié
update_drop_down_options: Les options de sélection du champ « %{label} » ont été modifiées update_drop_down_options: Les options de sélection du champ « %{label} » ont été modifiées
update_drop_down_other:
enabled: Le champ « %{label} » comporte maintenant un choix « Autre »
disabled: Le champ « %{label} » ne comporte plus de choix « Autre »
update_carte_layers: Les référentiels cartographiques du champ « %{label} » ont été modifiés update_carte_layers: Les référentiels cartographiques du champ « %{label} » ont été modifiés
add_private: Lannotation privée « %{label} » a été ajoutée add_private: Lannotation privée « %{label} » a été ajoutée
remove_private: Lannotation privée « %{label} » a été supprimée remove_private: Lannotation privée « %{label} » a été supprimée

View file

@ -0,0 +1,43 @@
describe 'dropdown list with other option activated' do
let(:password) { 'my-s3cure-p4ssword' }
let!(:user) { create(:user, password: password) }
let(:list_items) do
<<~END_OF_LIST
--Primary 1--
Secondary 1.1
Secondary 1.2
END_OF_LIST
end
let(:type_de_champ) { build(:type_de_champ_drop_down_list, libelle: 'simple dropdown other', drop_down_list_value: list_items, drop_down_other: true) }
let(:procedure) do
create(:procedure, :published, :for_individual, types_de_champ: [type_de_champ])
end
let(:user_dossier) { user.dossiers.first }
before do
login_as(user, scope: :user)
visit "/commencer/#{procedure.path}"
click_on 'Commencer la démarche'
end
scenario 'Select other option and the other input hidden must appear', js: true do
fill_individual
find('.radios').find('label:last-child').find('input').select_option
expect(page).to have_selector('.drop_down_other', visible: true)
end
private
def fill_individual
choose 'Monsieur'
fill_in('individual_prenom', with: 'prenom')
fill_in('individual_nom', with: 'nom')
click_on 'Continuer'
expect(page).to have_current_path(brouillon_dossier_path(user_dossier))
end
end