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: [
|
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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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/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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 << {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
%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 }
|
||||||
|
|
|
@ -17,6 +17,9 @@ fr:
|
||||||
disabled: Le champ « %{label} » n’est plus obligatoire
|
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_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: L’annotation privée « %{label} » a été ajoutée
|
add_private: L’annotation privée « %{label} » a été ajoutée
|
||||||
remove_private: L’annotation privée « %{label} » a été supprimé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