Merge pull request #2831 from tchak/carto-type-de-champ

Champ Carte
This commit is contained in:
Paul Chavard 2018-10-23 11:07:59 +02:00 committed by GitHub
commit f19a0694cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 627 additions and 224 deletions

View file

@ -10,6 +10,7 @@ module.exports = {
plugins: ['prettier'], plugins: ['prettier'],
extends: ['eslint:recommended', 'prettier'], extends: ['eslint:recommended', 'prettier'],
env: { env: {
es6: true,
browser: true browser: true
}, },
rules: { rules: {

View file

@ -27,6 +27,7 @@ $(document).on('change', 'select.form-control.type-champ', function() {
parent.removeClass('header-section'); parent.removeClass('header-section');
parent.children('.drop-down-list').removeClass('show-inline'); parent.children('.drop-down-list').removeClass('show-inline');
parent.children('.pj-template').removeClass('show-inline'); parent.children('.pj-template').removeClass('show-inline');
parent.children('.carte-options').removeClass('show-inline');
$('.mandatory', parent).show(); $('.mandatory', parent).show();
@ -42,6 +43,9 @@ $(document).on('change', 'select.form-control.type-champ', function() {
case 'piece_justificative': case 'piece_justificative':
parent.children('.pj-template').addClass('show-inline'); parent.children('.pj-template').addClass('show-inline');
break; break;
case 'carte':
parent.children('.carte-options').addClass('show-inline');
break;
case 'explication': case 'explication':
$('.mandatory', parent).hide(); $('.mandatory', parent).hide();
break; break;

View file

@ -32,7 +32,8 @@
} }
.form-group.drop-down-list, .form-group.drop-down-list,
.form-group.pj-template { .form-group.pj-template,
.form-group.carte-options {
display: none; display: none;
} }

View file

@ -228,8 +228,14 @@
// scss-lint:enable // scss-lint:enable
} }
.algolia-autocomplete { .editable-champ {
margin-bottom: 2 * $default-padding; &:not(.editable-champ-carte) .algolia-autocomplete {
margin-bottom: 2 * $default-padding;
}
.geo-areas {
margin-bottom: 2 * $default-padding;
}
} }
input.aa-input, input.aa-input,

View file

@ -1,4 +1,77 @@
#map { #map,
.carte {
height: 400px; height: 400px;
margin-bottom: 16px; margin-bottom: 16px;
z-index: 0;
}
.leaflet-container path {
cursor: url("/assets/edit.png"), default !important;
}
.carte {
&.edit {
g path.leaflet-polygon {
transition: all 0.25s;
stroke-width: 4px;
stroke-opacity: 1;
stroke: #D7217E;
position: absolute;
z-index: 100;
fill: #D7217E;
fill-opacity: 0.75;
}
div.leaflet-edge {
box-shadow: 0 0 0 2px #FFFFFF, 0 0 10px rgba(0, 0, 0, 0.35);
border: 5px solid #D7217E;
border-radius: 10px;
transition: opacity 0.25s;
cursor: move;
opacity: 0;
pointer-events: none;
box-sizing: border-box;
width: 0 !important;
height: 0 !important;
}
}
&.mode-create {
cursor: url("/assets/pencil.png"), crosshair !important;
}
&.mode-edit div.leaflet-edge {
opacity: 1;
pointer-events: all;
}
&.mode-delete path.leaflet-polygon {
cursor: no-drop !important;
&:hover {
fill: #4D4D4D !important;
}
}
}
.editable-champ-carte {
.toolbar {
display: flex;
align-items: center;
margin-bottom: 10px;
.button {
width: 200px;
margin-right: 10px;
}
}
}
.areas-title {
font-weight: bold;
margin-bottom: 5px;
}
.areas {
margin-bottom: 10px;
} }

View file

@ -37,6 +37,10 @@ class ApplicationController < ActionController::Base
logged_user.present? logged_user.present?
end end
def logged_user_ids
logged_users.map(&:id)
end
helper_method :logged_in? helper_method :logged_in?
protected protected

View file

@ -0,0 +1,61 @@
class Champs::CarteController < ApplicationController
before_action :authenticate_logged_user!
def show
@selector = ".carte-#{params[:position]}"
if params[:dossier].key?(:champs_attributes)
geo_json = params[:dossier][:champs_attributes][params[:position]][:value]
else
geo_json = params[:dossier][:champs_private_attributes][params[:position]][:value]
end
if params[:champ_id].present?
@champ = Champ
.joins(:dossier)
.where(dossiers: { user_id: logged_user_ids })
.find_by(id: params[:champ_id])
else
@champ = Champs::CarteChamp.new(type_de_champ: TypeDeChamp.new(
type_champ: TypeDeChamp.type_champs.fetch(:carte),
options: {
quartiers_prioritaires: true,
cadastres: true
}
))
end
geo_areas = []
geo_json = geo_json.blank? ? [] : JSON.parse(geo_json)
if geo_json.first == ["error", "TooManyPolygons"]
@error = true
elsif geo_json.present?
if @champ.cadastres?
cadastres = ModuleApiCartoService.generate_cadastre(geo_json)
geo_areas += cadastres.map do |cadastre|
cadastre[:source] = GeoArea.sources.fetch(:cadastre)
cadastre
end
end
if @champ.quartiers_prioritaires?
quartiers_prioritaires = ModuleApiCartoService.generate_qp(geo_json)
geo_areas += quartiers_prioritaires.map do |qp|
qp[:source] = GeoArea.sources.fetch(:quartier_prioritaire)
qp
end
end
end
@champ.geo_areas = geo_areas.map do |geo_area|
GeoArea.new(geo_area)
end
@champ.value = geo_json.to_json
if @champ.persisted?
@champ.save
end
end
end

View file

@ -3,4 +3,15 @@ module ChampHelper
types_without_label = [TypeDeChamp.type_champs.fetch(:header_section), TypeDeChamp.type_champs.fetch(:explication)] types_without_label = [TypeDeChamp.type_champs.fetch(:header_section), TypeDeChamp.type_champs.fetch(:explication)]
!types_without_label.include?(champ.type_champ) !types_without_label.include?(champ.type_champ)
end end
def geo_data(champ)
# rubocop:disable Rails/OutputSafety
raw({
position: champ.position,
selection: champ.value.present? ? JSON.parse(champ.value) : [],
quartiersPrioritaires: champ.quartiers_prioritaires? ? champ.quartiers_prioritaires : [],
cadastres: champ.cadastres? ? champ.cadastres : []
}.to_json)
# rubocop:enable Rails/OutputSafety
end
end end

View file

@ -1,16 +1,17 @@
import { getData } from '../shared/data'; import { getData } from '../shared/data';
import { initMap } from '../shared/carto';
import { import {
initMap,
drawCadastre, drawCadastre,
drawQuartiersPrioritaires, drawQuartiersPrioritaires,
drawUserSelection drawUserSelection
} from './carto/draw'; } from '../shared/carte';
function initialize() { function initialize() {
if (document.getElementById('map')) { const element = document.getElementById('map');
const position = getData('carto').position;
const map = initMap(position); if (element) {
const data = getData('carto'); const data = getData('carto');
const map = initMap(element, data.position);
// draw external polygons // draw external polygons
drawCadastre(map, data); drawCadastre(map, data);

View file

@ -1,35 +0,0 @@
import L from 'leaflet';
import {
drawLayer,
noEditStyle,
CADASTRE_POLYGON_STYLE,
QP_POLYGON_STYLE
} from '../../shared/carto';
export function drawCadastre(map, data) {
drawLayer(
map,
data.cadastres,
noEditStyle(CADASTRE_POLYGON_STYLE),
'cadastres'
);
}
export function drawQuartiersPrioritaires(map, data) {
drawLayer(
map,
data.quartiersPrioritaires,
noEditStyle(QP_POLYGON_STYLE),
'quartiersPrioritaires'
);
}
export function drawUserSelection(map, data) {
if (data.selection.length > 0) {
const polygon = L.polygon(data.selection, {
color: 'red',
zIndex: 3
}).addTo(map);
map.fitBounds(polygon.getBounds());
}
}

View file

@ -0,0 +1,62 @@
import { CREATE } from 'leaflet-freedraw';
import { delegate } from '@utils';
import {
initMap,
getCurrentMap,
geocodeAddress,
drawCadastre,
drawQuartiersPrioritaires,
drawUserSelection,
addFreeDrawEvents
} from '../../shared/carte';
function initialize() {
for (let element of document.querySelectorAll('.carte')) {
diplayMap(element, null, true);
}
window.DS.drawMapData = (selector, data) => {
let element = document.querySelector(selector);
diplayMap(element, data);
};
}
function diplayMap(element, data, initial = false) {
data = data || JSON.parse(element.dataset.geo);
let editable = element.classList.contains('edit');
let map = initMap(element, data.position, editable);
// draw external polygons
drawCadastre(map, data, editable);
drawQuartiersPrioritaires(map, data, editable);
// draw user polygon
if (initial) {
drawUserSelection(map, data, editable);
if (editable) {
let input = element.parentElement.querySelector('input[data-remote]');
addFreeDrawEvents(map, input);
}
}
}
addEventListener('turbolinks:load', initialize);
delegate('click', '.toolbar .new-area', event => {
event.preventDefault();
let map = getCurrentMap(event.target);
if (map) {
map.freeDraw.mode(CREATE);
}
});
delegate('autocomplete:select', '.toolbar [data-address]', event => {
let map = getCurrentMap(event.target);
if (map) {
geocodeAddress(map, event.detail.label);
}
});

View file

@ -1,81 +1,52 @@
import L from 'leaflet'; import { CREATE } from 'leaflet-freedraw';
import { on } from '@utils';
import FreeDraw, { NONE, CREATE } from 'leaflet-freedraw';
import { fire, on, getJSON } from '@utils';
import { getData } from '../shared/data'; import { getData } from '../shared/data';
import { initMap } from '../shared/carto'; import {
initMap,
import polygonArea from './carto/polygon_area'; geocodeAddress,
import drawFactory from './carto/draw'; drawUserSelection,
drawCadastre,
drawQuartiersPrioritaires,
addFreeDrawEvents
} from '../shared/carte';
function initialize() { function initialize() {
if (document.getElementById('map')) { const element = document.getElementById('map');
const data = getData('carto');
const position = data.position;
const map = initMap(position); if (element) {
const freeDraw = new FreeDraw({ const data = getData('carto');
mode: NONE, const map = initMap(element, data.position, true);
smoothFactor: 4,
mergePolygons: false addAddressSelectEvent(map);
on('#new', 'click', () => {
map.freeDraw.mode(CREATE);
}); });
map.addLayer(freeDraw); const cartoDrawZones = data => {
drawCadastre(map, data, true);
drawQuartiersPrioritaires(map, data, true);
};
addEventFreeDraw(freeDraw);
addEventSearchAddress(map);
const cartoDrawZones = drawFactory(map, freeDraw);
window.DS = { cartoDrawZones }; window.DS = { cartoDrawZones };
// draw external polygons
cartoDrawZones(data); cartoDrawZones(data);
if (freeDraw.polygons[0]) { // draw user polygon
map.setZoom(18); drawUserSelection(map, data, true);
map.fitBounds(freeDraw.polygons[0].getBounds()); addFreeDrawEvents(map, 'input[name=selection]');
}
} }
} }
addEventListener('turbolinks:load', initialize); addEventListener('turbolinks:load', initialize);
function addEventFreeDraw(freeDraw) { function addAddressSelectEvent(map) {
freeDraw.on('markers', ({ latLngs }) => {
const input = document.querySelector('input[name=selection]');
if (polygonArea(latLngs) < 300000) {
input.value = JSON.stringify(latLngs);
} else {
input.value = '{ "error": "TooManyPolygons" }';
}
fire(input, 'change');
});
on('#map', 'click', () => {
freeDraw.mode(NONE);
});
on('#new', 'click', () => {
freeDraw.mode(CREATE);
});
}
function getAddressPoint(map, request) {
getJSON('/address/geocode', { request }).then(data => {
if (data.lat !== null) {
map.setView(new L.LatLng(data.lat, data.lon), data.zoom);
}
});
}
function addEventSearchAddress(map) {
on( on(
'#search-by-address input[type=address]', '#search-by-address input[type=address]',
'autocomplete:select', 'autocomplete:select',
(_, seggestion) => { (_, { label }) => {
getAddressPoint(map, seggestion['label']); geocodeAddress(map, label);
} }
); );
} }

View file

@ -1,45 +0,0 @@
import { EDIT, DELETE } from 'leaflet-freedraw';
import { on } from '@utils';
import {
drawLayer,
CADASTRE_POLYGON_STYLE,
QP_POLYGON_STYLE
} from '../../shared/carto';
const SOURCES = {
cadastres: CADASTRE_POLYGON_STYLE,
quartiersPrioritaires: QP_POLYGON_STYLE
};
export default function draw(map, freeDraw) {
return data => {
if (data.selection) {
drawSelection(freeDraw, data.selection);
}
for (let source of Object.keys(SOURCES)) {
if (data[source]) {
drawLayer(map, data[source], SOURCES[source], source);
}
}
addEventEdit(freeDraw);
};
}
function drawSelection(selection, freeDraw) {
for (let polygon of selection) {
freeDraw.createPolygon(polygon);
}
}
function addEventEdit(freeDraw) {
document
.querySelector('.leaflet-container svg')
.removeAttribute('pointer-events');
on('.leaflet-container g path', 'click', () => {
setTimeout(() => {
freeDraw.mode(EDIT | DELETE);
}, 50);
});
}

View file

@ -18,6 +18,7 @@ import '../new_design/form-validation';
import '../new_design/carto'; import '../new_design/carto';
import '../new_design/select2'; import '../new_design/select2';
import '../new_design/champs/carte';
import '../new_design/champs/linked-drop-down-list'; import '../new_design/champs/linked-drop-down-list';
import { toggleCondidentielExplanation } from '../new_design/avis'; import { toggleCondidentielExplanation } from '../new_design/avis';

View file

@ -0,0 +1,188 @@
import L from 'leaflet';
import FreeDraw, { NONE, EDIT, DELETE } from 'leaflet-freedraw';
import { fire, getJSON, delegate } from '@utils';
import polygonArea from './polygon_area';
const LAYERS = {};
const MAPS = new WeakMap();
export function initMap(element, position, editable = false) {
if (MAPS.has(element)) {
return MAPS.get(element);
} else {
const map = L.map(element, {
scrollWheelZoom: false
}).setView([position.lat, position.lon], editable ? 18 : position.zoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
if (editable) {
const freeDraw = new FreeDraw({
mode: NONE,
smoothFactor: 4,
mergePolygons: false
});
map.addLayer(freeDraw);
map.freeDraw = freeDraw;
}
MAPS.set(element, map);
return map;
}
}
export function drawCadastre(map, { cadastres }, editable = false) {
drawLayer(
map,
cadastres,
editable ? CADASTRE_POLYGON_STYLE : noEditStyle(CADASTRE_POLYGON_STYLE),
'cadastres'
);
}
export function drawQuartiersPrioritaires(
map,
{ quartiersPrioritaires },
editable = false
) {
drawLayer(
map,
quartiersPrioritaires,
editable ? QP_POLYGON_STYLE : noEditStyle(QP_POLYGON_STYLE),
'quartiersPrioritaires'
);
}
export function drawUserSelection(map, { selection }, editable = false) {
let hasSelection = selection && selection.length > 0;
if (editable) {
if (hasSelection) {
selection.forEach(polygon => map.freeDraw.create(polygon));
let polygon = map.freeDraw.all()[0];
if (polygon) {
map.fitBounds(polygon.getBounds());
}
}
} else if (hasSelection) {
const polygon = L.polygon(selection, {
color: 'red',
zIndex: 3
}).addTo(map);
map.fitBounds(polygon.getBounds());
}
}
export function geocodeAddress(map, query) {
getJSON('/address/geocode', { request: query }).then(data => {
if (data.lat !== null) {
map.setView(new L.LatLng(data.lat, data.lon), data.zoom);
}
});
}
export function getCurrentMap(input) {
let element = input.closest('.toolbar').parentElement.querySelector('.carte');
if (MAPS.has(element)) {
return MAPS.get(element);
}
}
export function addFreeDrawEvents(map, selector) {
const input = findInput(selector);
map.freeDraw.on('markers', ({ latLngs }) => {
if (latLngs.length === 0) {
input.value = '';
} else if (polygonArea(latLngs) < 300000) {
input.value = JSON.stringify(latLngs);
} else {
input.value = '{ "error": "TooManyPolygons" }';
}
fire(input, 'change');
});
}
function findInput(selector) {
return typeof selector === 'string'
? document.querySelector(selector)
: selector;
}
function createLayer(map, layerName) {
const layer = (LAYERS[layerName] = new L.GeoJSON(undefined, {
interactive: false
}));
layer.addTo(map);
return layer;
}
function removeLayer(map, layerName) {
const layer = LAYERS[layerName];
if (layer) {
delete LAYERS[layerName];
map.removeLayer(layer);
}
}
function drawLayer(map, data, style, layerName = 'default') {
removeLayer(map, layerName);
if (Array.isArray(data) && data.length > 0) {
const layer = createLayer(map, layerName);
data.forEach(function(item) {
layer.addData(item.geometry);
});
layer.setStyle(style).addTo(map);
}
}
function noEditStyle(style) {
return Object.assign({}, style, {
opacity: 0.7,
fillOpacity: 0.5,
color: style.fillColor
});
}
const POLYGON_STYLE = {
weight: 2,
opacity: 0.3,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
const CADASTRE_POLYGON_STYLE = Object.assign({}, POLYGON_STYLE, {
fillColor: '#8a6d3b'
});
const QP_POLYGON_STYLE = Object.assign({}, POLYGON_STYLE, {
fillColor: '#31708f'
});
delegate('click', '.carte.edit', event => {
let element = event.target;
let isPath = element.matches('.leaflet-container g path');
let map = element.matches('.carte') ? element : element.closest('.carte');
let freeDraw = MAPS.has(map) ? MAPS.get(map).freeDraw : null;
if (freeDraw) {
if (isPath) {
setTimeout(() => {
freeDraw.mode(EDIT | DELETE);
}, 50);
} else {
freeDraw.mode(NONE);
}
}
});

View file

@ -1,71 +0,0 @@
import L from 'leaflet';
const LAYERS = {};
export function initMap(position) {
const map = L.map('map', {
scrollWheelZoom: false
}).setView([position.lat, position.lon], position.zoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution:
'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
return map;
}
export function drawLayer(map, data, style, layerName = 'default') {
removeLayer(map, layerName);
if (Array.isArray(data) && data.length > 0) {
const layer = createLayer(map, layerName);
data.forEach(function(item) {
layer.addData(item.geometry);
});
layer.setStyle(style).addTo(map);
}
}
export function noEditStyle(style) {
return Object.assign({}, style, {
opacity: 0.7,
fillOpacity: 0.5,
color: style.fillColor
});
}
const POLYGON_STYLE = {
weight: 2,
opacity: 0.3,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
export const CADASTRE_POLYGON_STYLE = Object.assign({}, POLYGON_STYLE, {
fillColor: '#8a6d3b'
});
export const QP_POLYGON_STYLE = Object.assign({}, POLYGON_STYLE, {
fillColor: '#31708f'
});
function createLayer(map, layerName) {
const layer = (LAYERS[layerName] = new L.GeoJSON(undefined, {
interactive: false
}));
layer.addTo(map);
return layer;
}
function removeLayer(map, layerName) {
const layer = LAYERS[layerName];
if (layer) {
delete LAYERS[layerName];
map.removeLayer(layer);
}
}

View file

@ -17,7 +17,16 @@ class Champ < ApplicationRecord
end end
def mandatory_and_blank? def mandatory_and_blank?
mandatory? && value.blank? if mandatory?
case type_de_champ.type_champ
when TypeDeChamp.type_champs.fetch(:carte)
value.blank? || value == '[]'
else
value.blank?
end
else
false
end
end end
def search_terms def search_terms

View file

@ -15,6 +15,14 @@ class Champs::CarteChamp < Champ
end end
end end
def cadastres?
type_de_champ&.cadastres && type_de_champ.cadastres != '0'
end
def quartiers_prioritaires?
type_de_champ&.quartiers_prioritaires && type_de_champ.quartiers_prioritaires != '0'
end
def position def position
if dossier.present? if dossier.present?
dossier.geo_position dossier.geo_position
@ -26,4 +34,8 @@ class Champs::CarteChamp < Champ
{ lon: lon, lat: lat, zoom: zoom } { lon: lon, lat: lat, zoom: zoom }
end end
end end
def zones
value.blank? ? [] : JSON.parse(value)
end
end end

View file

@ -14,6 +14,8 @@ class TypesDeChampService
:id, :id,
:mandatory, :mandatory,
:piece_justificative_template, :piece_justificative_template,
:quartiers_prioritaires,
:cadastres,
drop_down_list_attributes: [:value, :id] drop_down_list_attributes: [:value, :id]
]) ])

View file

@ -38,6 +38,16 @@
= ff.file_field :piece_justificative_template, = ff.file_field :piece_justificative_template,
direct_upload: true direct_upload: true
.form-group.carte-options{ class: (type_champ == TypeDeChamp.type_champs.fetch(:carte)) ? 'show-inline' : nil }
%h4 Utilisation de la cartographie
%label
= ff.check_box :quartiers_prioritaires
Quartiers prioritaires
%br
%label
= ff.check_box :cadastres
Cadastre
- hide_mandatory = (ff.object.object.private? || type_champ == TypeDeChamp.type_champs.fetch(:explication)) - hide_mandatory = (ff.object.object.private? || type_champ == TypeDeChamp.type_champs.fetch(:explication))
.form-group.mandatory{ style: hide_mandatory ? 'visibility: hidden;' : nil } .form-group.mandatory{ style: hide_mandatory ? 'visibility: hidden;' : nil }
%h4 Obligatoire ? %h4 Obligatoire ?

View file

@ -0,0 +1,5 @@
<%= render_to_element("#{@selector} + .geo-areas",
partial: 'shared/champs/carte/geo_areas',
locals: { champ: @champ, error: @error }) %>
DS.drawMapData("<%= @selector %>", <%= geo_data(@champ) %>);

View file

@ -0,0 +1,27 @@
- if champ.quartiers_prioritaires?
.areas-title Quartiers prioritaires
.areas
- if error.present?
.error Merci de dessiner une surface plus petite afin de récupérer les quartiers prioritaires.
- elsif champ.value.blank?
Aucune zone tracée
- elsif champ.quartiers_prioritaires.blank?
= t('errors.messages.quartiers_prioritaires_empty', count: champ.zones.size)
- else
%ul
- champ.quartiers_prioritaires.each do |qp|
%li #{qp.commune} : #{qp.nom}
- if champ.cadastres?
.areas-title Parcelles cadastrales
.areas
- if error.present?
.error Merci de dessiner une surface plus petite afin de récupérer les parcelles cadastrales.
- elsif champ.value.blank?
Aucune zone tracée
- elsif champ.cadastres.blank?
= t('errors.messages.cadastres_empty', count: champ.zones.size)
- else
%ul
- champ.cadastres.each do |pc|
%li Parcelle n° #{pc.numero} - Feuille #{pc.code_arr} #{pc.section} #{pc.feuille}

View file

@ -0,0 +1,3 @@
.carte{ data: { geo: geo_data(champ) } }
.geo-areas
= render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false }

View file

@ -56,6 +56,13 @@
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) } %span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
- if c.etablissement.present? - if c.etablissement.present?
= render partial: "shared/dossiers/identite_entreprise", locals: { etablissement: c.etablissement, profile: profile } = render partial: "shared/dossiers/identite_entreprise", locals: { etablissement: c.etablissement, profile: profile }
- when TypeDeChamp.type_champs.fetch(:carte)
%th.libelle
= "#{c.libelle} :"
%td.rich-text
%span{ class: highlight_if_unseen_class(demande_seen_at, c.updated_at) }
- if c.value.present?
= render partial: "shared/champs/carte/show", locals: { champ: c }
- else - else
%th.libelle %th.libelle
= "#{c.libelle} :" = "#{c.libelle} :"

View file

@ -16,4 +16,4 @@
%li %li
= "Parcelle n° #{p.numero} - Feuille #{p.code_arr} #{p.section} #{p.feuille}" = "Parcelle n° #{p.numero} - Feuille #{p.code_arr} #{p.section} #{p.feuille}"
= render partial: 'shared/champs/carto/init', locals: { dossier: dossier } = render partial: 'shared/champs/carte/init', locals: { dossier: dossier }

View file

@ -0,0 +1,11 @@
.toolbar
%button.button.primary.new-area Ajouter une zone
%input.address{ data: { address: true, autocomplete: 'address' }, placeholder: 'Saissisez une adresse ou positionner la carte' }
.carte.edit{ data: { geo: geo_data(champ) }, class: "carte-#{form.index}" }
.geo-areas
= render partial: 'shared/champs/carte/geo_areas', locals: { champ: champ, error: false }
= form.hidden_field :value,
data: { remote: true, url: champs_carte_path(form.index), params: { champ_id: champ&.id }.to_query, method: 'post' }

View file

@ -1,8 +1,8 @@
#carte-page.row #carte-page.row
.col-md-12.col-lg-12 .col-md-12.col-lg-12
#map.edit #map.carte.edit
%span.zones %span.zones
= render partial: 'zones', locals: { dossier: dossier, error: @error } = render partial: 'zones', locals: { dossier: dossier, error: @error }
= render partial: 'shared/champs/carto/init', locals: { dossier: dossier } = render partial: 'shared/champs/carte/init', locals: { dossier: dossier }

View file

@ -1,5 +1,24 @@
{ {
"ignored_warnings": [ "ignored_warnings": [
{
"warning_type": "Cross-Site Scripting",
"warning_code": 2,
"fingerprint": "0d61a1267d264f1e61cc2398a2683703ac60878129dc9515542f246a80ad575b",
"check_name": "CrossSiteScripting",
"message": "Unescaped model attribute",
"file": "app/views/champs/carto/show.js.erb",
"line": 5,
"link": "https://brakemanscanner.org/docs/warning_types/cross_site_scripting",
"code": "geo_data((Champ.joins(:dossier).where(:dossiers => ({ :user_id => logged_user_ids })).find_by(:id => params.permit(:champ_id)) or CartoChamp.new))",
"render_path": [{"type":"controller","class":"Champs::CartoController","method":"show","line":48,"file":"app/controllers/champs/carto_controller.rb"}],
"location": {
"type": "template",
"template": "champs/carto/show"
},
"user_input": "Champ.joins(:dossier).where(:dossiers => ({ :user_id => logged_user_ids }))",
"confidence": "Weak",
"note": "Not an injection because logged_user_ids have no user input"
},
{ {
"warning_type": "SQL Injection", "warning_type": "SQL Injection",
"warning_code": 0, "warning_code": 0,
@ -61,6 +80,6 @@
"note": "Not an injection because of `sanitized_column`" "note": "Not an injection because of `sanitized_column`"
} }
], ],
"updated": "2018-10-11 12:09:03 +0200", "updated": "2018-10-16 11:28:34 +0300",
"brakeman_version": "4.3.1" "brakeman_version": "4.3.1"
} }

View file

@ -177,6 +177,12 @@ fr:
connexion: "Erreur lors de la connexion à France Connect." connexion: "Erreur lors de la connexion à France Connect."
extension_white_list_error: "Le format de fichier de la pièce jointe n'est pas valide." extension_white_list_error: "Le format de fichier de la pièce jointe n'est pas valide."
procedure_archived: "Cette démarche en ligne a été fermée, il n'est plus possible de déposer de dossier." procedure_archived: "Cette démarche en ligne a été fermée, il n'est plus possible de déposer de dossier."
cadastres_empty:
one: "Aucune parcelle cadastrale sur la zone séléctionnée"
other: "Aucune parcelle cadastrale sur les zones séléctionnées"
quartiers_prioritaires_empty:
one: "Aucun quartier prioritaire sur la zone séléctionnée"
other: "Aucun quartier prioritaire sur les zones séléctionnées"
date: date:
abbr_day_names: abbr_day_names:

View file

@ -28,3 +28,4 @@ fr:
dossier_link: 'Lien vers un autre dossier' dossier_link: 'Lien vers un autre dossier'
piece_justificative: 'Pièce justificative' piece_justificative: 'Pièce justificative'
siret: 'SIRET' siret: 'SIRET'
carte: 'Carte'

View file

@ -125,6 +125,7 @@ Rails.application.routes.draw do
namespace :champs do namespace :champs do
get ':position/siret', to: 'siret#show', as: :siret get ':position/siret', to: 'siret#show', as: :siret
get ':position/dossier_link', to: 'dossier_link#show', as: :dossier_link get ':position/dossier_link', to: 'dossier_link#show', as: :dossier_link
post ':position/carte', to: 'carte#show', as: :carte
end end
namespace :commencer do namespace :commencer do

View file

@ -0,0 +1,51 @@
require 'spec_helper'
describe Champs::CarteController, type: :controller do
let(:user) { create(:user) }
let(:procedure) { create(:procedure, :published) }
let(:dossier) { create(:dossier, user: user, procedure: procedure) }
let(:params) do
{
dossier: {
champs_attributes: {
'1' => { value: selection.to_json }
}
},
position: '1',
champ_id: champ.id
}
end
let(:champ) do
create(:type_de_champ_carte, options: {
quartiers_prioritaires: true
}).champ.create(dossier: dossier)
end
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]]]] } }])
post :show, params: params, format: 'js'
end
context 'when coordinates are empty' do
let(:selection) { [] }
it { expect(response.body).to include("DS.drawMapData(\".carte-1\", {\"position\":{\"lon\":\"2.428462\",\"lat\":\"46.538192\",\"zoom\":\"13\"},\"selection\":[],\"quartiersPrioritaires\":[],\"cadastres\":[]});") }
end
context 'when coordinates are informed' do
let(:selection) { [[{ "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 }]] }
it { expect(response.body).not_to be_nil }
it { expect(response.body).to include('QPCODE1234') }
it { expect(response.body).to include('MultiPolygon') }
it { expect(response.body).to include('[2.38715792094576,48.8723062632126]') }
end
end
end

View file

@ -9,6 +9,12 @@ shared_examples 'champ_spec' do
it { expect(champ.mandatory_and_blank?).to be(true) } it { expect(champ.mandatory_and_blank?).to be(true) }
end end
context 'when carte mandatory and blank' do
let(:type_de_champ) { build(:type_de_champ_carte, mandatory: mandatory) }
let(:value) { '[]' }
it { expect(champ.mandatory_and_blank?).to be(true) }
end
context 'when not blank' do context 'when not blank' do
let(:value) { 'yop' } let(:value) { 'yop' }
it { expect(champ.mandatory_and_blank?).to be(false) } it { expect(champ.mandatory_and_blank?).to be(false) }