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:
commit
1fee02e6e4
15 changed files with 173 additions and 8 deletions
|
@ -57,6 +57,7 @@ module NewAdministrateur
|
|||
],
|
||||
methods: [
|
||||
:drop_down_list_value,
|
||||
:drop_down_other,
|
||||
:drop_down_secondary_libelle,
|
||||
:drop_down_secondary_description,
|
||||
:piece_justificative_template_filename,
|
||||
|
@ -75,6 +76,7 @@ module NewAdministrateur
|
|||
:parent_id,
|
||||
:private,
|
||||
:drop_down_list_value,
|
||||
:drop_down_other,
|
||||
:drop_down_secondary_libelle,
|
||||
:drop_down_secondary_description,
|
||||
:piece_justificative_template,
|
||||
|
@ -98,6 +100,7 @@ module NewAdministrateur
|
|||
:description,
|
||||
:mandatory,
|
||||
:drop_down_list_value,
|
||||
:drop_down_other,
|
||||
:drop_down_secondary_libelle,
|
||||
:drop_down_secondary_description,
|
||||
:piece_justificative_template,
|
||||
|
|
|
@ -337,8 +337,8 @@ module Users
|
|||
def champs_params
|
||||
params.permit(dossier: {
|
||||
champs_attributes: [
|
||||
:id, :value, :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: []]
|
||||
:id, :value, :value_other, :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
|
||||
|
|
|
@ -11,6 +11,7 @@ import MoveButton from './MoveButton';
|
|||
import TypeDeChampCarteOption from './TypeDeChampCarteOption';
|
||||
import TypeDeChampCarteOptions from './TypeDeChampCarteOptions';
|
||||
import TypeDeChampDropDownOptions from './TypeDeChampDropDownOptions';
|
||||
import TypeDeChampDropDownOther from './TypeDeChampDropDownOther';
|
||||
import TypeDeChampPieceJustificative from './TypeDeChampPieceJustificative';
|
||||
import TypeDeChampRepetitionOptions from './TypeDeChampRepetitionOptions';
|
||||
import TypeDeChampTypesSelect from './TypeDeChampTypesSelect';
|
||||
|
@ -24,6 +25,7 @@ const TypeDeChamp = sortableElement(
|
|||
'linked_drop_down_list'
|
||||
].includes(typeDeChamp.type_champ);
|
||||
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 isCarte = typeDeChamp.type_champ === 'carte';
|
||||
const isExplication = typeDeChamp.type_champ === 'explication';
|
||||
|
@ -137,6 +139,10 @@ const TypeDeChamp = sortableElement(
|
|||
libelleHandler={updateHandlers.drop_down_secondary_libelle}
|
||||
descriptionHandler={updateHandlers.drop_down_secondary_description}
|
||||
/>
|
||||
<TypeDeChampDropDownOther
|
||||
isVisible={isSimpleDropDown}
|
||||
handler={updateHandlers.drop_down_other}
|
||||
/>
|
||||
<TypeDeChampPieceJustificative
|
||||
isVisible={isFile}
|
||||
directUploadUrl={state.directUploadUrl}
|
||||
|
@ -241,6 +247,7 @@ const OPTIONS_FIELDS = {
|
|||
export const FIELDS = [
|
||||
'description',
|
||||
'drop_down_list_value',
|
||||
'drop_down_other',
|
||||
'libelle',
|
||||
'mandatory',
|
||||
'parent_id',
|
||||
|
|
|
@ -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 'autre' avec un texte libre
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
TypeDeChampDropDownOther.propTypes = {
|
||||
isVisible: PropTypes.bool,
|
||||
handler: PropTypes.object
|
||||
};
|
||||
|
||||
export default TypeDeChampDropDownOther;
|
20
app/javascript/new_design/champs/drop-down-list.js
Normal file
20
app/javascript/new_design/champs/drop-down-list.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
|
@ -27,6 +27,7 @@ import '../new_design/dossiers/auto-upload';
|
|||
import '../new_design/champs/carte';
|
||||
import '../new_design/champs/linked-drop-down-list';
|
||||
import '../new_design/champs/repetition';
|
||||
import '../new_design/champs/drop-down-list';
|
||||
|
||||
import {
|
||||
toggleCondidentielExplanation,
|
||||
|
|
|
@ -38,6 +38,7 @@ class Champ < ApplicationRecord
|
|||
:mandatory?,
|
||||
:description,
|
||||
:drop_down_list_options,
|
||||
:drop_down_other,
|
||||
:drop_down_list_options?,
|
||||
:drop_down_list_disabled_options,
|
||||
:drop_down_list_enabled_non_empty_options,
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#
|
||||
class Champs::DropDownListChamp < Champ
|
||||
THRESHOLD_NB_OPTIONS_AS_RADIO = 5
|
||||
OTHER = '__other__'
|
||||
|
||||
def render_as_radios?
|
||||
enabled_non_empty_options.size <= THRESHOLD_NB_OPTIONS_AS_RADIO
|
||||
|
@ -31,7 +32,15 @@ class Champs::DropDownListChamp < Champ
|
|||
end
|
||||
|
||||
def options
|
||||
drop_down_list_options
|
||||
if drop_down_other?
|
||||
drop_down_list_options + [["Autre", OTHER]]
|
||||
else
|
||||
drop_down_list_options
|
||||
end
|
||||
end
|
||||
|
||||
def selected
|
||||
other_value_present? ? OTHER : value
|
||||
end
|
||||
|
||||
def disabled_options
|
||||
|
@ -41,4 +50,26 @@ class Champs::DropDownListChamp < Champ
|
|||
def enabled_non_empty_options
|
||||
drop_down_list_enabled_non_empty_options
|
||||
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
|
||||
|
|
|
@ -243,6 +243,17 @@ class ProcedureRevision < ApplicationRecord
|
|||
}
|
||||
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?
|
||||
if from_type_de_champ.carte_optional_layers != to_type_de_champ.carte_optional_layers
|
||||
changes << {
|
||||
|
|
|
@ -58,7 +58,7 @@ class TypeDeChamp < ApplicationRecord
|
|||
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
|
||||
|
||||
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 :revisions, through: :revision_types_de_champ
|
||||
|
||||
|
@ -331,6 +331,7 @@ class TypeDeChamp < ApplicationRecord
|
|||
],
|
||||
methods: [
|
||||
:drop_down_list_value,
|
||||
:drop_down_other,
|
||||
:piece_justificative_template_filename,
|
||||
:piece_justificative_template_url,
|
||||
:editable_options,
|
||||
|
|
|
@ -41,6 +41,11 @@
|
|||
%li= t(:add_option, scope: [:new_administrateur, :revision_changes], items: added.map{ |term| "« #{term.strip} »" }.join(", "))
|
||||
- if removed.present?
|
||||
%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
|
||||
- added = change[:to].sort - change[:from].sort
|
||||
- removed = change[:from].sort - change[:to].sort
|
||||
|
|
|
@ -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?
|
|
@ -5,13 +5,18 @@
|
|||
%label
|
||||
= form.radio_button :value, option
|
||||
= option
|
||||
|
||||
- if !champ.mandatory?
|
||||
%label.blank-radio
|
||||
= form.radio_button :value, ''
|
||||
Non renseigné
|
||||
|
||||
- if champ.drop_down_other?
|
||||
%label
|
||||
= form.radio_button :value, Champs::DropDownListChamp::OTHER, checked: champ.other_value_present?
|
||||
Autre
|
||||
- else
|
||||
= form.select :value,
|
||||
champ.options,
|
||||
disabled: champ.disabled_options,
|
||||
required: champ.mandatory?
|
||||
= form.select :value, champ.options, selected: champ.selected, required: champ.mandatory?
|
||||
|
||||
- if champ.drop_down_other?
|
||||
= render partial: "shared/dossiers/drop_down_other_input", locals: { form: form, champ: champ }
|
||||
|
|
|
@ -17,6 +17,9 @@ fr:
|
|||
disabled: Le champ « %{label} » n’est plus obligatoire
|
||||
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_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
|
||||
add_private: L’annotation privée « %{label} » a été ajoutée
|
||||
remove_private: L’annotation privée « %{label} » a été supprimée
|
||||
|
|
43
spec/system/users/dropdown_spec.rb
Normal file
43
spec/system/users/dropdown_spec.rb
Normal 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
|
Loading…
Reference in a new issue