feat(dossier): multiselect without react

This commit is contained in:
Paul Chavard 2023-03-29 23:31:13 +02:00
parent cd74ea46cc
commit fce78bbf30
13 changed files with 104 additions and 50 deletions

View file

@ -3,13 +3,12 @@
= @form.collection_check_boxes(:value, @champ.enabled_non_empty_options, :to_s, :to_s) do |b|
- tag.div(class: 'editable-champ editable-champ-checkbox') do
- b.label(for: @champ.checkbox_id(b.value)) do
- b.check_box({ multiple: true, checked: @champ&.selected_options&.include?(b.value), aria: { describedby: @champ.describedby_id }, id: @champ.checkbox_id(b.value) }) + b.text
- b.check_box({ multiple: true, checked: @champ.selected_options.include?(b.value), aria: { describedby: @champ.describedby_id }, id: @champ.checkbox_id(b.value) }) + b.text
- else
= @form.hidden_field :value
= react_component("ComboMultipleDropdownList",
options: @champ.options,
selected: @champ.selected_options,
disabled: @champ.disabled_options,
id: @champ.input_id,
labelledby: @champ.labelledby_id,
describedby: @champ.describedby_id)
- if @champ.selected_options.present?
.fr-mb-2w{ "data-turbo": "true" }
- @champ.selected_options.each do |option|
= render NestedForms::OwnedButtonComponent.new(formaction: champs_options_path(@champ.id, option:), http_method: :delete, opt: { class: 'fr-tag fr-tag--dismiss fr-mb-1w fr-mr-1w', id: @champ.checkbox_id(option) }) do
= option
- if @champ.unselected_options.present?
= @form.select :value, @champ.unselected_options, { selected: '', include_blank: '' }, id: @champ.input_id, aria: { describedby: @champ.describedby_id }

View file

@ -0,0 +1,9 @@
class Champs::OptionsController < ApplicationController
before_action :authenticate_logged_user!
def remove
@champ = policy_scope(Champ).includes(:champs).find(params[:champ_id])
@next_checkbox_id = @champ.next_checkbox_id(params[:option])
@champ.remove_option([params[:option]].compact)
end
end

View file

@ -1,11 +0,0 @@
import React from 'react';
import { groupId } from './shared/hooks';
import ComboMultiple, { ComboMultipleProps } from './ComboMultiple';
export default function ComboMultipleDropdownList({
id,
...props
}: ComboMultipleProps & { id: string }) {
return <ComboMultiple id={id} {...props} group={groupId(id)} />;
}

View file

@ -21,8 +21,6 @@
# type_de_champ_id :integer
#
class Champs::MultipleDropDownListChamp < Champ
before_save :format_before_save
validate :values_are_in_options, if: -> { value.present? }
def options?
@ -84,23 +82,41 @@ class Champs::MultipleDropDownListChamp < Champ
end
def checkbox_id(value)
"#{input_id}-#{value.parameterize}"
"#{input_id}-#{Digest::MD5.hexdigest(value)}"
end
def next_checkbox_id(value)
return nil if value.blank? || !selected_options.include?(value)
index = selected_options.index(value)
next_values = selected_options.reject { _1 == value }
next_value = next_values[index] || next_values.last
next_value ? checkbox_id(next_value) : nil
end
def unselected_options
enabled_non_empty_options - selected_options
end
def value=(value)
return super(nil) if value.blank?
values = if value.is_a?(Array)
value
elsif value.starts_with?('[')
JSON.parse(value)
else
selected_options + [value]
end.uniq.without('')
if values.empty?
super(nil)
else
super(values.to_json)
end
end
private
def format_before_save
if value.present?
json = JSON.parse(value)
if json == ['']
self.value = nil
else
json = json - ['']
self.value = json.to_s
end
end
end
def values_are_in_options
json = selected_options.reject(&:blank?)
return if json.empty?

View file

@ -500,7 +500,9 @@ class TypeDeChamp < ApplicationRecord
def self.refresh_after_update?(type_champ)
case type_champ
when type_champs.fetch(:epci), type_champs.fetch(:communes)
when type_champs.fetch(:epci),
type_champs.fetch(:communes),
type_champs.fetch(:multiple_drop_down_list)
true
else
false

View file

@ -0,0 +1,8 @@
= fields_for @champ.input_name, @champ do |form|
= turbo_stream.replace @champ.input_group_id do
= render EditableChamp::EditableChampComponent.new champ: @champ, form:
- if @next_checkbox_id.present?
= turbo_stream.focus @next_checkbox_id
- else
= turbo_stream.focus @champ.input_id

View file

@ -170,6 +170,7 @@ Rails.application.routes.draw do
get ':champ_id/dossier_link', to: 'dossier_link#show', as: :dossier_link
post ':champ_id/repetition', to: 'repetition#add', as: :repetition
delete ':champ_id/repetition', to: 'repetition#remove'
delete ':champ_id/options', to: 'options#remove', as: :options
get ':champ_id/carte/features', to: 'carte#index', as: :carte_features
post ':champ_id/carte/features', to: 'carte#create'

View file

@ -824,7 +824,7 @@ describe Instructeurs::DossiersController, type: :controller do
end
it {
expect(champ_multiple_drop_down_list.value).to eq('["val1", "val2"]')
expect(champ_multiple_drop_down_list.value).to eq('["val1","val2"]')
expect(champ_linked_drop_down_list.primary_value).to eq('primary')
expect(champ_linked_drop_down_list.secondary_value).to eq('secondary')
expect(champ_datetime.value).to eq('2019-12-21T13:17:00+01:00')

View file

@ -117,7 +117,7 @@ describe Champ do
# when using the old form, and the ChampsService Class
# TODO: to remove
context 'when the value is already deserialized' do
let(:value) { '["val1", "val2"]' }
let(:value) { '["val1","val2"]' }
it { expect(champ.value).to eq(value) }
@ -135,7 +135,7 @@ describe Champ do
context 'when a choice is selected' do
let(:value) { '["", "val1", "val2"]' }
it { expect(champ.value).to eq('["val1", "val2"]') }
it { expect(champ.value).to eq('["val1","val2"]') }
end
context 'when all choices are removed' do

View file

@ -1,12 +1,11 @@
describe Champs::MultipleDropDownListChamp do
let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list, drop_down_list_value: "val1\r\nval2\r\nval3") }
let(:value) { nil }
subject { build(:champ_multiple_drop_down_list, type_de_champ:, value:) }
describe 'validations' do
describe 'inclusion' do
let(:type_de_champ) { build(:type_de_champ_multiple_drop_down_list, drop_down_list_value: "val1\r\nval2\r\nval3") }
subject { build(:champ_multiple_drop_down_list, type_de_champ:, value:) }
context 'when the value is nil' do
let(:value) { nil }
it { is_expected.to be_valid }
end
@ -35,4 +34,28 @@ describe Champs::MultipleDropDownListChamp do
end
end
end
describe '#next_checkbox_id' do
let(:value) { ["val1", "val2", "val3"] }
context 'when the value has next value' do
it {
expect(subject.next_checkbox_id("val1")).to eq(subject.checkbox_id("val2"))
expect(subject.next_checkbox_id("val2")).to eq(subject.checkbox_id("val3"))
}
end
context 'when the value is last' do
it { expect(subject.next_checkbox_id("val3")).to eq(subject.checkbox_id("val2")) }
end
context 'when the value is invalid' do
it { expect(subject.next_checkbox_id("val4")).to eq(nil) }
end
context 'when the values are empty' do
let(:value) { [] }
it { expect(subject.next_checkbox_id("val1")).to eq(nil) }
end
end
end

View file

@ -454,7 +454,7 @@ describe DossierRebaseConcern do
tdc_to_update.update(drop_down_list_value: "option\nupdated")
end
it { expect { subject }.to change { dossier.champs_public.first.value }.from('["v1", "option"]').to('["option"]') }
it { expect { subject }.to change { dossier.champs_public.first.value }.from('["v1","option"]').to('["option"]') }
end
context 'when a dropdown unused option is removed' do

View file

@ -38,8 +38,8 @@ describe 'The user' do
check('val1')
check('val3')
select('bravo', from: form_id_for('simple_choice_drop_down_list_long'))
select_combobox('multiple_choice_drop_down_list_long', 'alp', 'alpha')
select_combobox('multiple_choice_drop_down_list_long', 'cha', 'charly')
select('alpha', from: form_id_for('multiple_choice_drop_down_list_long'))
select('charly', from: form_id_for('multiple_choice_drop_down_list_long'))
select('Australie', from: form_id_for('pays'))
select('Martinique', from: form_id_for('regions'))
@ -97,7 +97,10 @@ describe 'The user' do
expect(page).to have_selected_value('pays', selected: 'Australie')
expect(page).to have_selected_value('regions', selected: 'Martinique')
expect(page).to have_selected_value('departements', selected: '02 Aisne')
check_selected_value('multiple_choice_drop_down_list_long', with: ['alpha', 'charly'])
within("##{champ_for('multiple_choice_drop_down_list_long').input_group_id}") do
expect(page).to have_button('alpha')
expect(page).to have_button('charly')
end
expect(page).to have_selected_value('communes', selected: 'Brétigny (60400)')
expect(page).to have_selected_value('pays', selected: 'Australie')
expect(page).to have_field('dossier_link', with: '123')
@ -530,8 +533,12 @@ describe 'The user' do
end
def champ_value_for(libelle)
champ_for(libelle).value
end
def champ_for(libelle)
champs = user_dossier.reload.champs_public
champs.find { |c| c.libelle == libelle }.value
champs.find { |c| c.libelle == libelle }
end
def fill_individual

View file

@ -101,7 +101,7 @@ describe 'shared/dossiers/edit.html.haml', type: :view do
let(:champ_value) { ['banana', 'grapefruit'].to_json }
it 'renders the list as a multiple-selection dropdown' do
expect(subject).to have_selector('[data-react-component-value="ComboMultipleDropdownList"]')
expect(subject).to have_selector('select')
end
end
end