diff --git a/app/assets/stylesheets/new_design/_constants.scss b/app/assets/stylesheets/new_design/_constants.scss index 8429f3f29..fca67e46a 100644 --- a/app/assets/stylesheets/new_design/_constants.scss +++ b/app/assets/stylesheets/new_design/_constants.scss @@ -7,3 +7,6 @@ $default-padding: 2 * $default-spacer; // layouts $two-columns-padding: 60px; $two-columns-breakpoint: $page-width + (2 * $two-columns-padding); + +// z-order +$alert-z-index: 100; diff --git a/app/assets/stylesheets/new_design/forms.scss b/app/assets/stylesheets/new_design/forms.scss index a5edd446a..04617dbfe 100644 --- a/app/assets/stylesheets/new_design/forms.scss +++ b/app/assets/stylesheets/new_design/forms.scss @@ -311,8 +311,9 @@ margin-left: $default-spacer; } - .button.danger { - margin-right: auto; + // If there are more than one button, align the "Send" button to the right + .button:not(:first-of-type).send { + margin-left: auto; } } @@ -321,8 +322,7 @@ flex-direction: column-reverse; align-items: center; - .button, - .button.danger { + .button { width: 100%; max-width: 350px; line-height: 30px; diff --git a/app/assets/stylesheets/new_design/new_alert.scss b/app/assets/stylesheets/new_design/new_alert.scss index 37dbe064b..84d8e997e 100644 --- a/app/assets/stylesheets/new_design/new_alert.scss +++ b/app/assets/stylesheets/new_design/new_alert.scss @@ -1,4 +1,5 @@ @import "colors"; +@import "constants"; .alert { padding: 15px; @@ -24,8 +25,10 @@ .alert-fixed { position: fixed; left: 50%; - margin-left: -100px; - width: 200px; + transform: translate(-50%); + max-width: 700px; top: 10px; border-radius: 10px; + // Ensure fixed flash messages are above `position: absolute` elements (like maps) + z-index: $alert-z-index; } diff --git a/app/controllers/champs/carte_controller.rb b/app/controllers/champs/carte_controller.rb index eb799ea92..5374c5111 100644 --- a/app/controllers/champs/carte_controller.rb +++ b/app/controllers/champs/carte_controller.rb @@ -74,5 +74,9 @@ class Champs::CarteController < ApplicationController if @champ.persisted? @champ.save end + + rescue RestClient::ResourceNotFound + flash.alert = 'Les données cartographiques sont temporairement indisponibles. Réessayez dans un instant.' + response.status = 503 end end diff --git a/app/controllers/manager/demandes_controller.rb b/app/controllers/manager/demandes_controller.rb index 932cfd8d6..ffdac506c 100644 --- a/app/controllers/manager/demandes_controller.rb +++ b/app/controllers/manager/demandes_controller.rb @@ -17,7 +17,7 @@ module Manager flash.notice = "Administrateur créé" redirect_to manager_demandes_path else - flash.now.alert = administrateur.errors.full_messages + flash.now.alert = administrateur.errors.full_messages.to_sentence @pending_demandes = pending_demandes render :index end diff --git a/app/dashboards/procedure_dashboard.rb b/app/dashboards/procedure_dashboard.rb index b2cd24301..7e2b0df69 100644 --- a/app/dashboards/procedure_dashboard.rb +++ b/app/dashboards/procedure_dashboard.rb @@ -18,6 +18,7 @@ class ProcedureDashboard < Administrate::BaseDashboard id: Field::Number.with_options(searchable: true), libelle: Field::String, description: Field::String, + lien_site_web: Field::String, # TODO: use Field::Url when administrate-v0.12 will be released organisation: Field::String, direction: Field::String, created_at: Field::DateTime, @@ -59,6 +60,7 @@ class ProcedureDashboard < Administrate::BaseDashboard :administrateur, :libelle, :description, + :lien_site_web, :organisation, :direction, :service, diff --git a/app/lib/api_carto/api.rb b/app/lib/api_carto/api.rb index 80cadb1b0..84bcd5d70 100644 --- a/app/lib/api_carto/api.rb +++ b/app/lib/api_carto/api.rb @@ -13,10 +13,10 @@ class ApiCarto::API def self.call(url, geojson) params = geojson.to_s - RestClient.post(url, params, content_type: 'application/json') - rescue RestClient::InternalServerError + rescue RestClient::InternalServerError, RestClient::BadGateway, RestClient::GatewayTimeout => e + Rails.logger.error "[ApiCarto] Error on #{url}: #{e}" raise RestClient::ResourceNotFound end end diff --git a/app/models/champs/checkbox_champ.rb b/app/models/champs/checkbox_champ.rb index dd8540892..b69e20e02 100644 --- a/app/models/champs/checkbox_champ.rb +++ b/app/models/champs/checkbox_champ.rb @@ -1,19 +1,9 @@ -class Champs::CheckboxChamp < Champ - def search_terms - if value == 'on' - [libelle] - end - end - - def to_s - value == 'on' ? 'Oui' : 'Non' +class Champs::CheckboxChamp < Champs::YesNoChamp + def true? + value == 'on' end def for_export - value == 'on' ? 'on' : 'off' - end - - def for_api - value == 'on' ? 'on' : 'off' + true? ? 'on' : 'off' end end diff --git a/app/models/champs/yes_no_champ.rb b/app/models/champs/yes_no_champ.rb index ca7155b03..4f82a406b 100644 --- a/app/models/champs/yes_no_champ.rb +++ b/app/models/champs/yes_no_champ.rb @@ -1,6 +1,6 @@ -class Champs::YesNoChamp < Champs::CheckboxChamp +class Champs::YesNoChamp < Champ def search_terms - if value == 'true' + if true? [libelle] end end @@ -13,13 +13,13 @@ class Champs::YesNoChamp < Champs::CheckboxChamp processed_value end - def for_api - processed_value + def true? + value == 'true' end private def processed_value - value == 'true' ? 'Oui' : 'Non' + true? ? 'Oui' : 'Non' end end diff --git a/app/views/admin/attestation_templates/show.pdf.prawn b/app/views/admin/attestation_templates/show.pdf.prawn index 228bdc375..0c3d367b9 100644 --- a/app/views/admin/attestation_templates/show.pdf.prawn +++ b/app/views/admin/attestation_templates/show.pdf.prawn @@ -1,8 +1,7 @@ require 'prawn/measurement_extensions' prawn_document(margin: [50, 100, 20, 100]) do |pdf| - pdf.font_families.update( 'open sans' => { normal: './lib/prawn/fonts/OpenSans-Regular.ttf' }) - pdf.font 'open sans' + pdf.font 'Times-Roman' grey = '555555' black = '333333' @@ -21,7 +20,7 @@ prawn_document(margin: [50, 100, 20, 100]) do |pdf| pdf.pad_top(40) { pdf.text @title, size: 18, character_spacing: -0.2 } pdf.fill_color grey - pdf.pad_top(30) { pdf.text @body, size: 10, character_spacing: -0.2 } + pdf.pad_top(30) { pdf.text @body, size: 10, character_spacing: -0.2, align: :justify } if @signature.present? pdf.pad_top(40) do diff --git a/app/views/admin/procedures/edit.html.haml b/app/views/admin/procedures/edit.html.haml index 1eb571224..5efaea830 100644 --- a/app/views/admin/procedures/edit.html.haml +++ b/app/views/admin/procedures/edit.html.haml @@ -3,7 +3,4 @@ = form_for @procedure, url: url_for({ controller: 'admin/procedures', action: :update, id: @procedure.id }), multipart: true do |f| = render partial: 'informations', locals: { f: f } .text-right - - if !Flipflop.publish_draft? || @availability.in?(Procedure::PATH_CAN_PUBLISH) - = f.button 'Enregistrer', class: 'btn btn-success' - - else - = f.button 'Enregistrer', class: 'btn btn-success', disabled: true + = f.button 'Enregistrer', class: 'btn btn-success' diff --git a/app/views/champs/carte/show.js.erb b/app/views/champs/carte/show.js.erb index 45ae01b28..e5c1e7055 100644 --- a/app/views/champs/carte/show.js.erb +++ b/app/views/champs/carte/show.js.erb @@ -1,3 +1,5 @@ +<%= render_flash(timeout: 5000, fixed: true) %> + <%= render_to_element("#{@selector} + .geo-areas", partial: 'shared/champs/carte/geo_areas', locals: { champ: @champ, error: @error }) %> diff --git a/app/views/new_user/dossiers/index.html.haml b/app/views/new_user/dossiers/index.html.haml index 347961d81..0c83b7eeb 100644 --- a/app/views/new_user/dossiers/index.html.haml +++ b/app/views/new_user/dossiers/index.html.haml @@ -52,7 +52,7 @@ = dossier.updated_at.strftime("%d/%m/%Y") %td.action-col.delete-col - if dossier.brouillon? - = link_to(ask_deletion_dossier_path(dossier), method: :post, class: 'button danger', data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraine l’annulation de la démarche en cours.\n\nConfirmer la suppression ?" }) do + = link_to(ask_deletion_dossier_path(dossier), method: :post, class: 'button danger', data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraine l’annulation de la démarche en cours.\n\nConfirmer la suppression ?" }) do %span.icon.delete Supprimer = paginate(@dossiers) diff --git a/app/views/root/patron.html.haml b/app/views/root/patron.html.haml index 35173bda4..048804ceb 100644 --- a/app/views/root/patron.html.haml +++ b/app/views/root/patron.html.haml @@ -40,7 +40,7 @@ %input{ type: "password", value: "12345678" } .send-wrapper = f.submit 'Enregistrer un brouillon (formnovalidate)', formnovalidate: true, class: 'button send' - = f.submit 'Envoyer', class: 'button send' + = f.submit 'Envoyer', class: 'button send primary' %hr diff --git a/app/views/shared/dossiers/_edit.html.haml b/app/views/shared/dossiers/_edit.html.haml index a0229d625..3d52cd697 100644 --- a/app/views/shared/dossiers/_edit.html.haml +++ b/app/views/shared/dossiers/_edit.html.haml @@ -70,13 +70,6 @@ - if !apercu .send-wrapper - if dossier.brouillon? - - if current_user.owns?(dossier) - = link_to ask_deletion_dossier_path(dossier), - method: :post, - class: 'button danger', - data: { disable: true, confirm: "En continuant, vous allez supprimer ce dossier ainsi que les informations qu’il contient. Toute suppression entraine l’annulation de la démarche en cours.\n\nConfirmer la suppression ?" } do - Supprimer le brouillon - = f.button 'Enregistrer le brouillon', formnovalidate: true, name: :save_draft, diff --git a/app/views/shared/dossiers/editable_champs/_phone.html.haml b/app/views/shared/dossiers/editable_champs/_phone.html.haml index 00ff6f5c6..5ef3c3dfa 100644 --- a/app/views/shared/dossiers/editable_champs/_phone.html.haml +++ b/app/views/shared/dossiers/editable_champs/_phone.html.haml @@ -1,3 +1,23 @@ +-# Allowed formats: +-# 0123456789 +-# 01 23 45 67 89 +-# 01.23.45.67.89 +-# 0123 45.67.89 +-# 0033 123-456-789 +-# 0035 123-456-789 +-# 0033 123-456-789 +-# 0033(0)123456789 +-# +33-1.23.45.67.89 +-# +33 - 123 456 789 +-# +33(0) 123 456 789 +-# +33 (0)123 45 67 89 +-# +33 (0)1 2345-6789 +-# +33(0) - 123456789 +-# +1(0) - 123456789 +-# +2 123456789 +-# 012345678 +-# 01234567890 = form.phone_field :value, placeholder: champ.libelle, - required: champ.mandatory? + required: champ.mandatory?, + pattern: "([\\+\\d\\(][\\(\\)\\s\\.\\-\\d]{4,}\\d)" diff --git a/spec/controllers/champs/carte_controller_spec.rb b/spec/controllers/champs/carte_controller_spec.rb index a530593eb..374574e09 100644 --- a/spec/controllers/champs/carte_controller_spec.rb +++ b/spec/controllers/champs/carte_controller_spec.rb @@ -25,43 +25,65 @@ describe Champs::CarteController, type: :controller do describe 'POST #show' do render_views - before { sign_in user } - before do - allow_any_instance_of(ApiCarto::QuartiersPrioritairesAdapter) - .to receive(:results) - .and_return([{ code: "QPCODE1234", geometry: { type: "MultiPolygon", coordinates: [[[[2.38715792094576, 48.8723062632126], [2.38724851642619, 48.8721392348061]]]] } }]) + context 'when the API is available' do + render_views - post :show, params: params, format: 'js' + before do + sign_in user + + allow_any_instance_of(ApiCarto::QuartiersPrioritairesAdapter) + .to receive(:results) + .and_return([{ code: "QPCODE1234", geometry: { type: "MultiPolygon", coordinates: [[[[2.38715792094576, 48.8723062632126], [2.38724851642619, 48.8721392348061]]]] } }]) + + post :show, params: params, format: 'js' + end + + context 'when coordinates are empty' do + let(:value) { '[]' } + + it { + expect(assigns(:error)).to eq(nil) + expect(champ.reload.value).to eq(nil) + expect(champ.reload.geo_areas).to eq([]) + expect(response.body).to include("DS.drawMapData(\".carte-1\", {\"position\":{\"lon\":\"2.428462\",\"lat\":\"46.538192\",\"zoom\":\"13\"},\"selection\":null,\"quartiersPrioritaires\":[],\"cadastres\":[],\"parcellesAgricoles\":[]});") + } + end + + context 'when coordinates are informed' do + let(:value) { [[{ "lat": 48.87442541960633, "lng": 2.3859214782714844 }, { "lat": 48.87273183590832, "lng": 2.3850631713867183 }, { "lat": 48.87081237174292, "lng": 2.3809432983398438 }, { "lat": 48.8712640169951, "lng": 2.377510070800781 }, { "lat": 48.87510283703279, "lng": 2.3778533935546875 }, { "lat": 48.87544154230615, "lng": 2.382831573486328 }, { "lat": 48.87442541960633, "lng": 2.3859214782714844 }]].to_json } + + it { expect(response.body).not_to be_nil } + it { expect(response.body).to include('MultiPolygon') } + it { expect(response.body).to include('[2.38715792094576,48.8723062632126]') } + end + + context 'when error' do + let(:geojson) { [[{ "lat": 48.87442541960633, "lng": 2.3859214782714844 }, { "lat": 48.87273183590832, "lng": 2.3850631713867183 }, { "lat": 48.87081237174292, "lng": 2.3809432983398438 }, { "lat": 48.8712640169951, "lng": 2.377510070800781 }, { "lat": 48.87510283703279, "lng": 2.3778533935546875 }, { "lat": 48.87544154230615, "lng": 2.382831573486328 }, { "lat": 48.87442541960633, "lng": 2.3859214782714844 }]] } + let(:value) { '' } + + it { + expect(assigns(:error)).to eq(true) + expect(champ.reload.value).to eq(nil) + expect(champ.reload.geo_areas).to eq([]) + } + end end - context 'when coordinates are empty' do - let(:value) { '[]' } + context 'when the API is unavailable' do + before do + sign_in user - it { - expect(assigns(:error)).to eq(nil) - expect(champ.reload.value).to eq(nil) - expect(champ.reload.geo_areas).to eq([]) - expect(response.body).to include("DS.drawMapData(\".carte-1\", {\"position\":{\"lon\":\"2.428462\",\"lat\":\"46.538192\",\"zoom\":\"13\"},\"selection\":null,\"quartiersPrioritaires\":[],\"cadastres\":[],\"parcellesAgricoles\":[]});") - } - end + allow_any_instance_of(ApiCarto::QuartiersPrioritairesAdapter) + .to receive(:results) + .and_raise(RestClient::ResourceNotFound) + + post :show, params: params, format: 'js' + end - context 'when coordinates are informed' do let(:value) { [[{ "lat": 48.87442541960633, "lng": 2.3859214782714844 }, { "lat": 48.87273183590832, "lng": 2.3850631713867183 }, { "lat": 48.87081237174292, "lng": 2.3809432983398438 }, { "lat": 48.8712640169951, "lng": 2.377510070800781 }, { "lat": 48.87510283703279, "lng": 2.3778533935546875 }, { "lat": 48.87544154230615, "lng": 2.382831573486328 }, { "lat": 48.87442541960633, "lng": 2.3859214782714844 }]].to_json } - it { expect(response.body).not_to be_nil } - it { expect(response.body).to include('MultiPolygon') } - it { expect(response.body).to include('[2.38715792094576,48.8723062632126]') } - end - - context 'when error' do - let(:geojson) { [[{ "lat": 48.87442541960633, "lng": 2.3859214782714844 }, { "lat": 48.87273183590832, "lng": 2.3850631713867183 }, { "lat": 48.87081237174292, "lng": 2.3809432983398438 }, { "lat": 48.8712640169951, "lng": 2.377510070800781 }, { "lat": 48.87510283703279, "lng": 2.3778533935546875 }, { "lat": 48.87544154230615, "lng": 2.382831573486328 }, { "lat": 48.87442541960633, "lng": 2.3859214782714844 }]] } - let(:value) { '' } - - it { - expect(assigns(:error)).to eq(true) - expect(champ.reload.value).to eq(nil) - expect(champ.reload.geo_areas).to eq([]) - } + it { expect(response.status).to eq 503 } + it { expect(response.body).to include('Les données cartographiques sont temporairement indisponibles') } end end end diff --git a/spec/features/new_user/brouillon_spec.rb b/spec/features/new_user/brouillon_spec.rb index 4cc1142e2..660d084a8 100644 --- a/spec/features/new_user/brouillon_spec.rb +++ b/spec/features/new_user/brouillon_spec.rb @@ -112,20 +112,6 @@ feature 'The user' do expect(page).to have_current_path(merci_dossier_path(user_dossier)) end - scenario 'delete a draft', js: true do - log_in(user.email, password, simple_procedure) - fill_individual - - page.accept_alert('Confirmer la suppression ?') do - click_on 'Supprimer le brouillon' - end - - expect(page).to have_current_path(dossiers_path) - expect(page).to have_text('Votre dossier a bien été supprimé') - expect(page).not_to have_text(user_dossier.procedure.libelle) - expect(user_dossier.reload.hidden_at).to be_present - end - private def log_in(email, password, procedure) diff --git a/spec/features/new_user/list_dossiers_spec.rb b/spec/features/new_user/list_dossiers_spec.rb index 3e36e97d8..f47133bac 100644 --- a/spec/features/new_user/list_dossiers_spec.rb +++ b/spec/features/new_user/list_dossiers_spec.rb @@ -49,13 +49,13 @@ describe 'user access to the list of his dossier' do expect(page).not_to have_link(nil, href: ask_deletion_dossier_path(dossier1)) end - context 'when user clicks on delete brouillon list', js: true do - before do - find(:xpath, "//a[@href='#{ask_deletion_dossier_path(dossier_brouillon)}']").click - page.driver.browser.switch_to.alert.accept - end + context 'when user clicks on delete brouillon', js: true do scenario 'dossier is deleted' do - expect(page).not_to have_link("Supprimer", href: dossier_brouillon.procedure.libelle) + page.accept_alert('Confirmer la suppression ?') do + find(:xpath, "//a[@href='#{ask_deletion_dossier_path(dossier_brouillon)}']").click + end + + expect(page).to have_content('Votre dossier a bien été supprimé.') end end diff --git a/spec/serializers/champ_serializer_spec.rb b/spec/serializers/champ_serializer_spec.rb index 7e82150b2..d88916685 100644 --- a/spec/serializers/champ_serializer_spec.rb +++ b/spec/serializers/champ_serializer_spec.rb @@ -196,5 +196,65 @@ describe ChampSerializer do expect(subject[:entreprise]).to include(capital_social: etablissement.entreprise_capital_social) } end + + context 'when type champ yes_no' do + context 'true' do + let(:champ) { create(:champ_yes_no, value: 'true') } + + it { is_expected.to include(value: 'true') } + end + + context 'false' do + let(:champ) { create(:champ_yes_no, value: 'false') } + + it { is_expected.to include(value: 'false') } + end + + context 'nil' do + let(:champ) { create(:champ_yes_no, value: nil) } + + it { is_expected.to include(value: nil) } + end + end + + context 'when type champ checkbox' do + context 'on' do + let(:champ) { create(:champ_checkbox, value: 'on') } + + it { is_expected.to include(value: 'on') } + end + + context 'off' do + let(:champ) { create(:champ_checkbox, value: 'off') } + + it { is_expected.to include(value: 'off') } + end + + context 'nil' do + let(:champ) { create(:champ_checkbox, value: nil) } + + it { is_expected.to include(value: nil) } + end + end + + context 'when type champ engagement' do + context 'on' do + let(:champ) { create(:champ_engagement, value: 'on') } + + it { is_expected.to include(value: 'on') } + end + + context 'off' do + let(:champ) { create(:champ_engagement, value: 'off') } + + it { is_expected.to include(value: 'off') } + end + + context 'nil' do + let(:champ) { create(:champ_engagement, value: nil) } + + it { is_expected.to include(value: nil) } + end + end end end