diff --git a/app/javascript/components/ComboMultiple.tsx b/app/javascript/components/ComboMultiple.tsx index 41982d3cc..713e64a8c 100644 --- a/app/javascript/components/ComboMultiple.tsx +++ b/app/javascript/components/ComboMultiple.tsx @@ -87,6 +87,7 @@ export default function ComboMultiple({ : options.filter((o) => o).map((o) => [o, o]), [options] ); + const extraOptions = useMemo( () => acceptNewValues && @@ -97,6 +98,15 @@ export default function ComboMultiple({ : [], [acceptNewValues, term, optionsWithLabels, newValues] ); + + const extraListOptions = useMemo( + () => + acceptNewValues && term && term.length > 2 && term.includes(';') + ? term.split(';').map((val) => [val.trim(), val.trim()]) + : [], + [acceptNewValues, term] + ); + const results = useMemo( () => [ @@ -129,12 +139,22 @@ export default function ComboMultiple({ const maybeValue = [...extraOptions, ...optionsWithLabels].find( ([, val]) => val == value ); - const selectedValue = maybeValue && maybeValue[1]; + + const maybeValueFromListOptions = extraListOptions.find( + ([, val]) => val == value + ); + + const selectedValue = + term.includes(';') && acceptNewValues + ? maybeValueFromListOptions && maybeValueFromListOptions[1] + : maybeValue && maybeValue[1]; + if (selectedValue) { if ( - acceptNewValues && - extraOptions[0] && - extraOptions[0][0] == selectedValue + (acceptNewValues && + extraOptions[0] && + extraOptions[0][0] == selectedValue) || + (acceptNewValues && extraListOptions[0]) ) { setNewValues((newValues) => { const set = new Set(newValues); @@ -172,7 +192,12 @@ export default function ComboMultiple({ isHotkey(',', event) || isHotkey(';', event) ) { - if ( + if (term.includes(';')) { + for (const val of term.split(';')) { + event.preventDefault(); + onSelect(val.trim()); + } + } else if ( term && [...extraOptions, ...optionsWithLabels] .map(([label]) => label) @@ -204,7 +229,11 @@ export default function ComboMultiple({ .includes(term); awaitFormSubmit(() => { - if (shouldSelect) { + if (term.includes(';')) { + for (const val of term.split(';')) { + onSelect(val.trim()); + } + } else if (shouldSelect) { onSelect(term); } else { hidePopover(); diff --git a/spec/system/instructeurs/expert_spec.rb b/spec/system/instructeurs/expert_spec.rb index 01b4e741d..64a66c1e9 100644 --- a/spec/system/instructeurs/expert_spec.rb +++ b/spec/system/instructeurs/expert_spec.rb @@ -54,6 +54,78 @@ describe 'Inviting an expert:', js: true do expect(invitation_email.body).to include(targeted_user_url) end + scenario 'I can paste a list of experts emails' do + allow(ClamavService).to receive(:safe_file?).and_return(true) + + # assign instructeur to linked dossier + instructeur.assign_to_procedure(linked_dossier.procedure) + + login_as instructeur.user, scope: :user + visit instructeur_dossier_path(procedure, dossier) + + click_on 'Avis externes' + expect(page).to have_current_path(avis_instructeur_dossier_path(procedure, dossier)) + within('.fr-sidemenu') { click_on 'Demander un avis' } + expect(page).to have_current_path(avis_new_instructeur_dossier_path(procedure, dossier)) + + fill_in 'Emails', with: "expert1@gouv.fr; expert2@gouv.fr; test@test.fr; email-invalide" + fill_in 'avis_introduction', with: 'Bonjour, merci de me donner votre avis sur ce dossier.' + check 'avis_invite_linked_dossiers' + page.select 'confidentiel', from: 'avis_confidentiel' + + within('form#new_avis') { click_on 'Demander un avis' } + perform_enqueued_jobs + + expect(page).to have_content('Une demande d’avis a été envoyée') + expect(page).to have_content('Avis des invités') + within('section') do + expect(page).to have_content('expert1@gouv.fr') + expect(page).to have_content('expert2@gouv.fr') + expect(page).to have_content('test@test.fr') + expect(page).not_to have_content('email-invalide') + end + end + + context 'when experts list is restricted by admin' do + let!(:expert_procedure) { ExpertsProcedure.create(expert: expert, procedure: procedure, allow_decision_access: true) } + let(:expert_email) { expert.email } + let(:expert2_email) { expert2.email } + + before do + procedure.update!(experts_require_administrateur_invitation: true) + end + + scenario 'only allowed experts are invited' do + allow(ClamavService).to receive(:safe_file?).and_return(true) + + # assign instructeur to linked dossier + instructeur.assign_to_procedure(linked_dossier.procedure) + + login_as instructeur.user, scope: :user + visit instructeur_dossier_path(procedure, dossier) + + click_on 'Avis externes' + expect(page).to have_current_path(avis_instructeur_dossier_path(procedure, dossier)) + within('.fr-sidemenu') { click_on 'Demander un avis' } + expect(page).to have_current_path(avis_new_instructeur_dossier_path(procedure, dossier)) + + fill_in 'Emails', with: "#{expert.email}; #{expert2.email}" + fill_in 'avis_introduction', with: 'Bonjour, merci de me donner votre avis sur ce dossier.' + check 'avis_invite_linked_dossiers' + page.select 'confidentiel', from: 'avis_confidentiel' + + within('form#new_avis') { click_on 'Demander un avis' } + perform_enqueued_jobs + + expect(page).to have_content('Une demande d’avis a été envoyée') + expect(page).to have_content('Avis des invités') + within('section') do + expect(page).to have_content(expert.email) + expect(page).not_to have_content(expert2.email) + end + end + end + context 'when experts submitted their answer' do let(:experts_procedure) { create(:experts_procedure, expert: expert, procedure: procedure) } let!(:answered_avis) { create(:avis, :with_answer, dossier: dossier, claimant: instructeur, experts_procedure: experts_procedure) }