diff --git a/Gemfile.lock b/Gemfile.lock
index 143c83271..6fd9b7f5e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -245,7 +245,7 @@ GEM
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.1)
geo_coord (0.1.0)
- geocoder (1.6.0)
+ geocoder (1.6.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
gon (6.3.2)
diff --git a/app/assets/stylesheets/new_design/carte.scss b/app/assets/stylesheets/new_design/carte.scss
new file mode 100644
index 000000000..b23ec84e6
--- /dev/null
+++ b/app/assets/stylesheets/new_design/carte.scss
@@ -0,0 +1,13 @@
+.areas-title {
+ font-weight: bold;
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+.areas {
+ margin-bottom: 10px;
+
+ input {
+ margin-top: 5px;
+ }
+}
diff --git a/app/assets/stylesheets/new_design/map.scss b/app/assets/stylesheets/new_design/map.scss
deleted file mode 100644
index 588030137..000000000
--- a/app/assets/stylesheets/new_design/map.scss
+++ /dev/null
@@ -1,80 +0,0 @@
-.carte {
- height: 400px;
- 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;
- }
-
- .select2-container {
- margin-bottom: 0;
- }
- }
-}
-
-.areas-title {
- font-weight: bold;
- margin-bottom: 5px;
-}
-
-.areas {
- margin-bottom: 10px;
-}
diff --git a/app/assets/stylesheets/new_design/utils.scss b/app/assets/stylesheets/new_design/utils.scss
index 9e5b14290..62e44fab0 100644
--- a/app/assets/stylesheets/new_design/utils.scss
+++ b/app/assets/stylesheets/new_design/utils.scss
@@ -53,3 +53,7 @@
.mt-2 {
margin-top: 2 * $default-spacer;
}
+
+.mb-2 {
+ margin-bottom: 2 * $default-spacer;
+}
diff --git a/app/controllers/champs/carte_controller.rb b/app/controllers/champs/carte_controller.rb
index 6790a9767..fc9a55833 100644
--- a/app/controllers/champs/carte_controller.rb
+++ b/app/controllers/champs/carte_controller.rb
@@ -19,7 +19,7 @@ class Champs::CarteController < ApplicationController
def create
champ = policy_scope(Champ).find(params[:champ_id])
geo_area = champ.geo_areas.selections_utilisateur.new
- save_geometry!(geo_area, params_feature)
+ save_feature!(geo_area, params_feature)
render json: { feature: geo_area.to_feature }, status: :created
end
@@ -27,7 +27,7 @@ class Champs::CarteController < ApplicationController
def update
champ = policy_scope(Champ).find(params[:champ_id])
geo_area = champ.geo_areas.selections_utilisateur.find(params[:id])
- save_geometry!(geo_area, params_feature)
+ save_feature!(geo_area, params_feature)
head :no_content
end
@@ -43,7 +43,7 @@ class Champs::CarteController < ApplicationController
champ = policy_scope(Champ).find(params[:champ_id])
params_features.each do |feature|
geo_area = champ.geo_areas.selections_utilisateur.new
- save_geometry!(geo_area, feature)
+ save_feature!(geo_area, feature)
end
render json: champ.to_feature_collection, status: :created
@@ -59,8 +59,13 @@ class Champs::CarteController < ApplicationController
params[:features]
end
- def save_geometry!(geo_area, feature)
- geo_area.geometry = feature[:geometry]
+ def save_feature!(geo_area, feature)
+ if feature[:geometry]
+ geo_area.geometry = feature[:geometry]
+ end
+ if feature[:properties] && feature[:properties][:description]
+ geo_area.description = feature[:properties][:description]
+ end
geo_area.save!
end
diff --git a/app/controllers/champs/siret_controller.rb b/app/controllers/champs/siret_controller.rb
index 169134aab..98ebb81ee 100644
--- a/app/controllers/champs/siret_controller.rb
+++ b/app/controllers/champs/siret_controller.rb
@@ -46,7 +46,7 @@ class Champs::SiretController < ApplicationController
@champ = policy_scope(Champ).find(params[:champ_id])
@etablissement = @champ&.etablissement
end
- @procedure_id = @champ&.dossier&.procedure_id || 'aperçu'
+ @procedure_id = @champ&.dossier&.procedure&.id || 'aperçu'
end
def find_etablissement_with_siret
diff --git a/app/controllers/instructeurs/recherche_controller.rb b/app/controllers/instructeurs/recherche_controller.rb
index 8d14ce2f8..298abb3ea 100644
--- a/app/controllers/instructeurs/recherche_controller.rb
+++ b/app/controllers/instructeurs/recherche_controller.rb
@@ -5,7 +5,7 @@ module Instructeurs
@dossiers = DossierSearchService.matching_dossiers_for_instructeur(@search_terms, current_instructeur)
@followed_dossiers_id = current_instructeur
.followed_dossiers
- .where(procedure_id: @dossiers.pluck(:procedure_id))
+ .where(groupe_instructeur_id: @dossiers.pluck(:groupe_instructeur_id))
.pluck(:id)
end
end
diff --git a/app/javascript/components/MapEditor/index.js b/app/javascript/components/MapEditor/index.js
index d98fbdc13..7d3921fc7 100644
--- a/app/javascript/components/MapEditor/index.js
+++ b/app/javascript/components/MapEditor/index.js
@@ -1,69 +1,102 @@
-import React, { useState, useRef, useEffect } from 'react';
+import React, { useState, useCallback, useRef } from 'react';
import PropTypes from 'prop-types';
import mapboxgl from 'mapbox-gl';
import ReactMapboxGl, { GeoJSONLayer, ZoomControl } from 'react-mapbox-gl';
import DrawControl from 'react-mapbox-gl-draw';
-import SwitchMapStyle from './SwitchMapStyle';
-import SearchInput from './SearchInput';
-import { getJSON, ajax } from '@utils';
import { gpx, kml } from '@tmcw/togeojson/dist/togeojson.es.js';
-import ortho from '../MapStyles/ortho.json';
-import orthoCadastre from '../MapStyles/orthoCadastre.json';
-import vector from '../MapStyles/vector.json';
-import vectorCadastre from '../MapStyles/vectorCadastre.json';
-import { polygonCadastresFill, polygonCadastresLine } from './utils';
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
+import { getJSON, ajax, fire } from '@utils';
+
+import SwitchMapStyle from './SwitchMapStyle';
+import { getMapStyle } from '../MapStyles';
+
+import SearchInput from './SearchInput';
+import { polygonCadastresFill, polygonCadastresLine } from './utils';
+import {
+ noop,
+ filterFeatureCollection,
+ fitBounds,
+ generateId,
+ useEvent,
+ findFeature
+} from '../shared/map';
+
const Map = ReactMapboxGl({});
-function filterFeatureCollection(featureCollection, source) {
- return {
- type: 'FeatureCollection',
- features: featureCollection.features.filter(
- (feature) => feature.properties.source === source
- )
- };
-}
-
-function noop() {}
-
function MapEditor({ featureCollection, url, preview, hasCadastres }) {
const drawControl = useRef(null);
+ const [currentMap, setCurrentMap] = useState(null);
+
const [style, setStyle] = useState('ortho');
const [coords, setCoords] = useState([1.7, 46.9]);
const [zoom, setZoom] = useState([5]);
- const [currentMap, setCurrentMap] = useState({});
const [bbox, setBbox] = useState(featureCollection.bbox);
const [importInputs, setImportInputs] = useState([]);
- let mapStyle = style === 'ortho' ? ortho : vector;
+ const [cadastresFeatureCollection, setCadastresFeatureCollection] = useState(
+ filterFeatureCollection(featureCollection, 'cadastre')
+ );
+ const mapStyle = getMapStyle(style, hasCadastres);
- if (hasCadastres) {
- mapStyle = style === 'ortho' ? orthoCadastre : vectorCadastre;
- }
-
- const cadastresFeatureCollection = filterFeatureCollection(
- featureCollection,
- 'cadastre'
+ const onFeatureFocus = useCallback(
+ ({ detail }) => {
+ const { id } = detail;
+ const featureCollection = drawControl.current.draw.getAll();
+ const feature = findFeature(featureCollection, id);
+ if (feature) {
+ fitBounds(currentMap, feature);
+ }
+ },
+ [currentMap, drawControl.current]
);
- function updateFeaturesList(features) {
- const cadastres = features.find(
- ({ geometry }) => geometry.type === 'Polygon'
+ const onFeatureUpdate = useCallback(
+ async ({ detail }) => {
+ const { id, properties } = detail;
+ const featureCollection = drawControl.current.draw.getAll();
+ const feature = findFeature(featureCollection, id);
+
+ if (feature) {
+ getJSON(`${url}/${id}`, { feature: { properties } }, 'patch');
+ }
+ },
+ [url, drawControl.current]
+ );
+
+ const updateFeaturesList = useCallback(
+ async (features) => {
+ const cadastres = features.find(
+ ({ geometry }) => geometry.type === 'Polygon'
+ );
+ await ajax({
+ url,
+ type: 'get',
+ data: cadastres ? 'cadastres=update' : ''
+ });
+ fire(document, 'ds:page:update');
+ },
+ [url]
+ );
+
+ const onCadastresUpdate = useCallback(({ detail }) => {
+ setCadastresFeatureCollection(
+ filterFeatureCollection(detail.featureCollection, 'cadastre')
);
- ajax({ url, type: 'get', data: cadastres ? 'cadastres=update' : '' });
- }
+ }, []);
+
+ useEvent('map:feature:focus', onFeatureFocus);
+ useEvent('map:feature:update', onFeatureUpdate);
+ useEvent('cadastres:update', onCadastresUpdate);
function setFeatureId(lid, feature) {
const draw = drawControl.current.draw;
draw.setFeatureProperty(lid, 'id', feature.properties.id);
}
- const generateId = () => Math.random().toString(20).substr(2, 6);
-
- const updateImportInputs = (inputs, inputId) => {
+ function updateImportInputs(inputs, inputId) {
const updatedInputs = inputs.filter((input) => input.id !== inputId);
setImportInputs(updatedInputs);
- };
+ }
async function onDrawCreate({ features }) {
for (const feature of features) {
@@ -92,23 +125,13 @@ function MapEditor({ featureCollection, url, preview, hasCadastres }) {
updateFeaturesList(features);
}
- const onMapLoad = (map) => {
+ function onMapLoad(map) {
setCurrentMap(map);
drawControl.current.draw.set(
filterFeatureCollection(featureCollection, 'selection_utilisateur')
);
- };
-
- const onCadastresUpdate = (evt) => {
- if (currentMap) {
- currentMap
- .getSource('cadastres-layer')
- .setData(
- filterFeatureCollection(evt.detail.featureCollection, 'cadastre')
- );
- }
- };
+ }
const onFileImport = (e, inputId) => {
const isGpxFile = e.target.files[0].name.includes('.gpx');
@@ -190,11 +213,6 @@ function MapEditor({ featureCollection, url, preview, hasCadastres }) {
updateImportInputs(inputs, inputId);
};
- useEffect(() => {
- addEventListener('cadastres:update', onCadastresUpdate);
- return () => removeEventListener('cadastres:update', onCadastresUpdate);
- });
-
if (!mapboxgl.supported()) {
return (
diff --git a/app/javascript/components/MapReader/index.js b/app/javascript/components/MapReader/index.js
index c2d4e7d1c..145007b0a 100644
--- a/app/javascript/components/MapReader/index.js
+++ b/app/javascript/components/MapReader/index.js
@@ -1,25 +1,100 @@
-import React, { useState } from 'react';
+import React, { useState, useCallback, useMemo } from 'react';
import ReactMapboxGl, { ZoomControl, GeoJSONLayer } from 'react-mapbox-gl';
-import mapboxgl from 'mapbox-gl';
-import SwitchMapStyle from './SwitchMapStyle';
-import ortho from '../MapStyles/ortho.json';
-import orthoCadastre from '../MapStyles/orthoCadastre.json';
-import vector from '../MapStyles/vector.json';
-import vectorCadastre from '../MapStyles/vectorCadastre.json';
+import mapboxgl, { Popup } from 'mapbox-gl';
import PropTypes from 'prop-types';
+import SwitchMapStyle from './SwitchMapStyle';
+import { getMapStyle } from '../MapStyles';
+
+import {
+ filterFeatureCollection,
+ filterFeatureCollectionByGeometryType,
+ useEvent,
+ findFeature,
+ fitBounds,
+ getCenter
+} from '../shared/map';
+
const Map = ReactMapboxGl({});
const MapReader = ({ featureCollection }) => {
+ const [currentMap, setCurrentMap] = useState(null);
const [style, setStyle] = useState('ortho');
- const hasCadastres = featureCollection.features.find(
- (feature) => feature.properties.source === 'cadastre'
+ const cadastresFeatureCollection = useMemo(
+ () => filterFeatureCollection(featureCollection, 'cadastre'),
+ [featureCollection]
+ );
+ const selectionsUtilisateurFeatureCollection = useMemo(
+ () => filterFeatureCollection(featureCollection, 'selection_utilisateur'),
+ [featureCollection]
+ );
+ const selectionsLineFeatureCollection = useMemo(
+ () =>
+ filterFeatureCollectionByGeometryType(
+ selectionsUtilisateurFeatureCollection,
+ 'LineString'
+ ),
+ [selectionsUtilisateurFeatureCollection]
+ );
+ const selectionsPolygonFeatureCollection = useMemo(
+ () =>
+ filterFeatureCollectionByGeometryType(
+ selectionsUtilisateurFeatureCollection,
+ 'Polygon'
+ ),
+ [selectionsUtilisateurFeatureCollection]
+ );
+ const selectionsPointFeatureCollection = useMemo(
+ () =>
+ filterFeatureCollectionByGeometryType(
+ selectionsUtilisateurFeatureCollection,
+ 'Point'
+ ),
+ [selectionsUtilisateurFeatureCollection]
+ );
+ const mapStyle = useMemo(
+ () => getMapStyle(style, cadastresFeatureCollection.length),
+ [style, cadastresFeatureCollection]
+ );
+ const popup = useMemo(
+ () =>
+ new Popup({
+ closeButton: false,
+ closeOnClick: false
+ })
);
- let mapStyle = style === 'ortho' ? ortho : vector;
- if (hasCadastres) {
- mapStyle = style === 'ortho' ? orthoCadastre : vectorCadastre;
- }
+ const onMouseEnter = useCallback(
+ (event) => {
+ const feature = event.features[0];
+ if (feature.properties && feature.properties.description) {
+ const coordinates = getCenter(feature.geometry, event.lngLat);
+ const description = feature.properties.description;
+ currentMap.getCanvas().style.cursor = 'pointer';
+ popup.setLngLat(coordinates).setHTML(description).addTo(currentMap);
+ } else {
+ popup.remove();
+ }
+ },
+ [currentMap, popup]
+ );
+
+ const onMouseLeave = useCallback(() => {
+ currentMap.getCanvas().style.cursor = '';
+ popup.remove();
+ }, [currentMap, popup]);
+
+ const onFeatureFocus = useCallback(
+ ({ detail }) => {
+ const feature = findFeature(featureCollection, detail.id);
+ if (feature) {
+ fitBounds(currentMap, feature);
+ }
+ },
+ [currentMap, featureCollection]
+ );
+
+ useEvent('map:feature:focus', onFeatureFocus);
const [a1, a2, b1, b2] = featureCollection.bbox;
const boundData = [
@@ -27,26 +102,6 @@ const MapReader = ({ featureCollection }) => {
[b1, b2]
];
- const cadastresFeatureCollection = {
- type: 'FeatureCollection',
- features: []
- };
-
- const selectionsLineFeatureCollection = {
- type: 'FeatureCollection',
- features: []
- };
-
- const selectionsPolygonFeatureCollection = {
- type: 'FeatureCollection',
- features: []
- };
-
- const selectionsPointFeatureCollection = {
- type: 'FeatureCollection',
- features: []
- };
-
const polygonSelectionFill = {
'fill-color': '#EC3323',
'fill-opacity': 0.5
@@ -77,25 +132,8 @@ const MapReader = ({ featureCollection }) => {
'line-dasharray': [1, 1]
};
- for (let feature of featureCollection.features) {
- switch (feature.properties.source) {
- case 'selection_utilisateur':
- switch (feature.geometry.type) {
- case 'LineString':
- selectionsLineFeatureCollection.features.push(feature);
- break;
- case 'Polygon':
- selectionsPolygonFeatureCollection.features.push(feature);
- break;
- case 'Point':
- selectionsPointFeatureCollection.features.push(feature);
- break;
- }
- break;
- case 'cadastre':
- cadastresFeatureCollection.features.push(feature);
- break;
- }
+ function onMapLoad(map) {
+ setCurrentMap(map);
}
if (!mapboxgl.supported()) {
@@ -110,6 +148,7 @@ const MapReader = ({ featureCollection }) => {
return (